From d21f727e9d81ab0328af9f01ba11bbb74b1aef06 Mon Sep 17 00:00:00 2001 From: Jade Date: Wed, 28 Oct 2020 21:05:01 +0000 Subject: [PATCH] Moved Debuff handling to Combat + Bugfixes * Majority of mob debuff handling is moved to combatStep(). * Drain now kills the mob and does 40% overall damage. * Bumped up active nano debuff durations, debuffs like drain linger longer but damage less. * Debuffs are cleared upon mob death and retreating. * Patched out vehicle off success packet spam * Boosts and potions now cost the right amount (100 taros) and give the right quantity (100). * Damage was tweaked slightly. At higher levels you are more likely to fall prey to rand(). * Enemies now use run animations during combat and retreating. --- src/ItemManager.cpp | 6 +-- src/MobManager.cpp | 94 ++++++++++++++++++++++++++----------------- src/MobManager.hpp | 3 +- src/NPCManager.cpp | 11 +++-- src/NanoManager.cpp | 8 ++-- src/PlayerManager.cpp | 25 ++++++------ 6 files changed, 85 insertions(+), 62 deletions(-) diff --git a/src/ItemManager.cpp b/src/ItemManager.cpp index 82d7b0e..fea0a15 100644 --- a/src/ItemManager.cpp +++ b/src/ItemManager.cpp @@ -268,9 +268,9 @@ void ItemManager::itemUseHandler(CNSocket* sock, CNPacketData* data) { response.eIL = 1; response.iSlotNum = request->iSlotNum; response.RemainItem = gumball; - // response.iTargetCnt = ? - // response.eST = ? - // response.iSkillID = ? + // response.iTargetCnt = ? + // response.eST = ? + // response.iSkillID = ? sock->sendPacket((void*)&response, P_FE2CL_REP_PC_ITEM_USE_SUCC, sizeof(sP_FE2CL_REP_PC_ITEM_USE_SUCC)); // update inventory serverside diff --git a/src/MobManager.cpp b/src/MobManager.cpp index 5b1ff67..0283712 100644 --- a/src/MobManager.cpp +++ b/src/MobManager.cpp @@ -144,7 +144,8 @@ void MobManager::npcAttackPc(Mob *mob, time_t currTime) { if (plr->HP <= 0) { mob->target = nullptr; mob->state = MobState::RETREAT; - aggroCheck(mob, currTime); + if (!aggroCheck(mob, currTime)) + clearDebuff(mob); } } @@ -351,6 +352,7 @@ void MobManager::killMob(CNSocket *sock, Mob *mob) { mob->state = MobState::DEAD; mob->target = nullptr; mob->appearanceData.iConditionBitFlag = 0; + mob->unbuffTimes.clear(); mob->killedTime = getTime(); // XXX: maybe introduce a shard-global time for each step? // check for the edge case where hitting the mob did not aggro it @@ -449,7 +451,8 @@ void MobManager::combatStep(Mob *mob, time_t currTime) { if (PlayerManager::players.find(mob->target) == PlayerManager::players.end()) { mob->target = nullptr; mob->state = MobState::RETREAT; - aggroCheck(mob, currTime); + if (!aggroCheck(mob, currTime)) + clearDebuff(mob); return; } @@ -462,10 +465,40 @@ void MobManager::combatStep(Mob *mob, time_t currTime) { if (plr->HP <= 0) { mob->target = nullptr; mob->state = MobState::RETREAT; - aggroCheck(mob, currTime); + if (!aggroCheck(mob, currTime)) + clearDebuff(mob); return; } + // drain + if ((mob->lastDrainTime == 0 || currTime - mob->lastDrainTime >= 1000) && mob->appearanceData.iConditionBitFlag & CSB_BIT_BOUNDINGBALL) { + drainMobHP(mob, mob->maxHealth / 15); // lose 6.67% every second + mob->lastDrainTime = currTime; + } + + // unbuffing + std::unordered_map::iterator it = mob->unbuffTimes.begin(); + while (it != mob->unbuffTimes.end()) { + + if (currTime >= it->second) { + mob->appearanceData.iConditionBitFlag &= ~it->first; + + INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1); + pkt1.eCT = 2; + pkt1.iID = mob->appearanceData.iNPC_ID; + pkt1.iConditionBitFlag = mob->appearanceData.iConditionBitFlag; + NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); + + it = mob->unbuffTimes.erase(it); + } else { + it++; + } + } + + // skip attack if stunned or asleep + if (mob->appearanceData.iConditionBitFlag & (CSB_BIT_STUN|CSB_BIT_MEZ)) + return; + int distance = hypot(plr->x - mob->appearanceData.iX, plr->y - mob->appearanceData.iY); /* @@ -481,6 +514,7 @@ void MobManager::combatStep(Mob *mob, time_t currTime) { pkt.iToX = mob->appearanceData.iX; pkt.iToY = mob->appearanceData.iY; pkt.iToZ = mob->target->plr->z; + pkt.iMoveStyle = 1; NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); mob->nextAttack = currTime + (int)mob->data["m_iInitalTime"] * 100; //I *think* this is what this is @@ -514,6 +548,7 @@ void MobManager::combatStep(Mob *mob, time_t currTime) { pkt.iToX = mob->appearanceData.iX = targ.first; pkt.iToY = mob->appearanceData.iY = targ.second; pkt.iToZ = mob->target->plr->z; + pkt.iMoveStyle = 1; // notify all nearby players NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); @@ -525,6 +560,7 @@ void MobManager::combatStep(Mob *mob, time_t currTime) { if (distance >= mob->data["m_iCombatRange"]) { mob->target = nullptr; mob->state = MobState::RETREAT; + clearDebuff(mob); } } @@ -615,6 +651,7 @@ void MobManager::retreatStep(Mob *mob, time_t currTime) { pkt.iToX = mob->appearanceData.iX = targ.first; pkt.iToY = mob->appearanceData.iY = targ.second; pkt.iToZ = mob->appearanceData.iZ = mob->spawnZ; + pkt.iMoveStyle = 1; // notify all nearby players NPCManager::sendToViewable(mob, &pkt, P_FE2CL_NPC_MOVE, sizeof(sP_FE2CL_NPC_MOVE)); @@ -631,12 +668,11 @@ void MobManager::retreatStep(Mob *mob, time_t currTime) { // HACK: we haven't found a better way to refresh a mob's client-side status drainMobHP(mob, 0); + clearDebuff(mob); } } void MobManager::step(CNServer *serv, time_t currTime) { - static time_t lastDrainTime = 0; - for (auto& pair : Mobs) { int x = pair.second->appearanceData.iX; int y = pair.second->appearanceData.iY; @@ -645,40 +681,11 @@ void MobManager::step(CNServer *serv, time_t currTime) { if (!ChunkManager::inPopulatedChunks(x, y, pair.second->instanceID)) continue; - // drain - if (currTime - lastDrainTime >= 600 && pair.second->appearanceData.iConditionBitFlag & CSB_BIT_BOUNDINGBALL) { - drainMobHP(pair.second, pair.second->maxHealth * 3 / 50); // lose 10% every second - } - - // unbuffing - std::unordered_map::iterator it = pair.second->unbuffTimes.begin(); - while (it != pair.second->unbuffTimes.end()) { - - if (currTime >= it->second) { - pair.second->appearanceData.iConditionBitFlag &= ~it->first; - - INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1); - pkt1.eCT = 2; - pkt1.iID = pair.second->appearanceData.iNPC_ID; - pkt1.iConditionBitFlag = pair.second->appearanceData.iConditionBitFlag; - NPCManager::sendToViewable(pair.second, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); - - it = pair.second->unbuffTimes.erase(it); - } else { - it++; - } - } - // skip mob movement and combat if disabled if (!simulateMobs && pair.second->state != MobState::DEAD && pair.second->state != MobState::RETREAT) continue; - // skip attack/move if stunned or asleep - if (pair.second->appearanceData.iConditionBitFlag & (CSB_BIT_STUN|CSB_BIT_MEZ) - && (pair.second->state == MobState::ROAMING || pair.second->state == MobState::COMBAT)) - continue; - switch (pair.second->state) { case MobState::INACTIVE: // no-op @@ -697,9 +704,6 @@ void MobManager::step(CNServer *serv, time_t currTime) { break; } } - - if (currTime - lastDrainTime >= 600) - lastDrainTime = currTime; // deallocate all NPCs queued for removal while (RemovalQueue.size() > 0) { @@ -920,7 +924,7 @@ std::pair MobManager::getDamage(int attackPower, int defensePower, bool // base calculation int damage = attackPower * attackPower / (attackPower + defensePower); - damage = std::max(10 + attackPower / 10, damage - defensePower * (4 + difficulty) / 40); + //damage = std::max(10 + attackPower / 10, damage - defensePower * (4 + difficulty) / 100); damage = damage * (rand() % 40 + 80) / 100; // Adaptium/Blastons/Cosmix @@ -1082,6 +1086,9 @@ void MobManager::drainMobHP(Mob *mob, int amount) { drain->iHP = mob->appearanceData.iHP -= amount; NPCManager::sendToViewable(mob, (void*)&respbuf, P_FE2CL_CHAR_TIME_BUFF_TIME_TICK, resplen); + + if (mob->appearanceData.iHP <= 0) + killMob(mob->target, mob); } /* @@ -1137,3 +1144,14 @@ bool MobManager::aggroCheck(Mob *mob, time_t currTime) { return false; } + +void MobManager::clearDebuff(Mob *mob) { + mob->appearanceData.iConditionBitFlag = 0; + mob->unbuffTimes.clear(); + + INITSTRUCT(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT, pkt1); + pkt1.eCT = 2; + pkt1.iID = mob->appearanceData.iNPC_ID; + pkt1.iConditionBitFlag = mob->appearanceData.iConditionBitFlag; + NPCManager::sendToViewable(mob, &pkt1, P_FE2CL_CHAR_TIME_BUFF_TIME_OUT, sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_OUT)); +} \ No newline at end of file diff --git a/src/MobManager.hpp b/src/MobManager.hpp index cf11c67..aeefbfa 100644 --- a/src/MobManager.hpp +++ b/src/MobManager.hpp @@ -46,7 +46,7 @@ struct Mob : public BaseNPC { // combat CNSocket *target = nullptr; time_t nextAttack = 0; - + time_t lastDrainTime = 0; // drop int dropType; @@ -142,4 +142,5 @@ namespace MobManager { void drainMobHP(Mob *mob, int amount); void incNextMovement(Mob *mob, time_t currTime=0); bool aggroCheck(Mob *mob, time_t currTime); + void clearDebuff(Mob *mob); } diff --git a/src/NPCManager.cpp b/src/NPCManager.cpp index f45fd57..f1952ae 100644 --- a/src/NPCManager.cpp +++ b/src/NPCManager.cpp @@ -406,16 +406,16 @@ void NPCManager::npcVendorBuyBattery(CNSocket* sock, CNPacketData* data) { if (plr == nullptr) return; - int cost = req->Item.iOpt * 10; + int cost = req->Item.iOpt * 100; if ((req->Item.iID == 3 ? (plr->batteryW >= 9999) : (plr->batteryN >= 9999)) || plr->money < cost) { // sanity check INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL, failResp); failResp.iErrorCode = 0; sock->sendPacket((void*)&failResp, P_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL, sizeof(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_FAIL)); } - plr->money -= cost; - plr->batteryW += req->Item.iID == 3 ? req->Item.iOpt * 10 : 0; - plr->batteryN += req->Item.iID == 4 ? req->Item.iOpt * 10 : 0; + 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) @@ -423,6 +423,9 @@ void NPCManager::npcVendorBuyBattery(CNSocket* sock, CNPacketData* data) { if (plr->batteryN > 9999) plr->batteryN = 9999; + cost = plr->batteryW + plr->batteryN - cost; + plr->money -= cost; + INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_BATTERY_BUY_SUCC, resp); resp.iCandy = plr->money; diff --git a/src/NanoManager.cpp b/src/NanoManager.cpp index 0819c91..2cd4472 100644 --- a/src/NanoManager.cpp +++ b/src/NanoManager.cpp @@ -715,15 +715,15 @@ void activePower(CNSocket *sock, CNPacketData *data, // active nano power dispatch table std::vector ActivePowers = { - ActivePower(StunPowers, activePower, EST_STUN, CSB_BIT_STUN, 2250), + ActivePower(StunPowers, activePower, EST_STUN, CSB_BIT_STUN, 4000), ActivePower(HealPowers, activePower, EST_HEAL_HP, CSB_BIT_NONE, 35), ActivePower(GroupHealPowers, activePower,EST_HEAL_HP, CSB_BIT_NONE, 20), // TODO: Recall - ActivePower(DrainPowers, activePower, EST_BOUNDINGBALL, CSB_BIT_BOUNDINGBALL, 3000), - ActivePower(SnarePowers, activePower, EST_SNARE, CSB_BIT_DN_MOVE_SPEED, 4500), + ActivePower(DrainPowers, activePower, EST_BOUNDINGBALL, CSB_BIT_BOUNDINGBALL, 6000), + ActivePower(SnarePowers, activePower, EST_SNARE, CSB_BIT_DN_MOVE_SPEED, 8000), ActivePower(DamagePowers, activePower, EST_DAMAGE, CSB_BIT_NONE, 12), ActivePower(LeechPowers, activePower, EST_BLOODSUCKING, CSB_BIT_NONE, 18), - ActivePower(SleepPowers, activePower, EST_SLEEP, CSB_BIT_MEZ, 4500), + ActivePower(SleepPowers, activePower, EST_SLEEP, CSB_BIT_MEZ, 8000), }; }; // namespace diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index 9527b9e..80e9a06 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -855,10 +855,10 @@ void PlayerManager::enterPlayerVehicle(CNSocket* sock, CNPacketData* data) { sock->sendPacket((void*)&response, P_FE2CL_PC_VEHICLE_ON_SUCC, sizeof(sP_FE2CL_PC_VEHICLE_ON_SUCC)); // send to other players - plr.plr->iPCState = 8; + plr.plr->iPCState |= 8; INITSTRUCT(sP_FE2CL_PC_STATE_CHANGE, response2); response2.iPC_ID = plr.plr->iID; - response2.iState = 8; + response2.iState = plr.plr->iPCState; for (Chunk* chunk : players[sock].currentChunks) { for (CNSocket* otherSock : chunk->players) { @@ -879,19 +879,20 @@ void PlayerManager::enterPlayerVehicle(CNSocket* sock, CNPacketData* data) { } void PlayerManager::exitPlayerVehicle(CNSocket* sock, CNPacketData* data) { - - INITSTRUCT(sP_FE2CL_PC_VEHICLE_OFF_SUCC, response); - sock->sendPacket((void*)&response, P_FE2CL_PC_VEHICLE_OFF_SUCC, sizeof(sP_FE2CL_PC_VEHICLE_OFF_SUCC)); - PlayerView plr = PlayerManager::players[sock]; - // send to other players - plr.plr->iPCState = 0; - INITSTRUCT(sP_FE2CL_PC_STATE_CHANGE, response2); - response2.iPC_ID = plr.plr->iID; - response2.iState = 0; + if (plr.plr->iPCState & 8) { + INITSTRUCT(sP_FE2CL_PC_VEHICLE_OFF_SUCC, response); + sock->sendPacket((void*)&response, P_FE2CL_PC_VEHICLE_OFF_SUCC, sizeof(sP_FE2CL_PC_VEHICLE_OFF_SUCC)); - sendToViewable(sock, (void*)&response2, P_FE2CL_PC_STATE_CHANGE, sizeof(sP_FE2CL_PC_STATE_CHANGE)); + // send to other players + plr.plr->iPCState &= ~8; + INITSTRUCT(sP_FE2CL_PC_STATE_CHANGE, response2); + response2.iPC_ID = plr.plr->iID; + response2.iState = plr.plr->iPCState; + + sendToViewable(sock, (void*)&response2, P_FE2CL_PC_STATE_CHANGE, sizeof(sP_FE2CL_PC_STATE_CHANGE)); + } } void PlayerManager::setSpecialSwitchPlayer(CNSocket* sock, CNPacketData* data) {