mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2024-12-23 11:50:04 +00:00
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.
This commit is contained in:
parent
2af33da4e8
commit
d21f727e9d
@ -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
|
||||
|
@ -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<int32_t, time_t>::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<int32_t, time_t>::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<int,int> 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));
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -715,15 +715,15 @@ void activePower(CNSocket *sock, CNPacketData *data,
|
||||
|
||||
// active nano power dispatch table
|
||||
std::vector<ActivePower> ActivePowers = {
|
||||
ActivePower(StunPowers, activePower<sSkillResult_Damage_N_Debuff, doDebuff>, EST_STUN, CSB_BIT_STUN, 2250),
|
||||
ActivePower(StunPowers, activePower<sSkillResult_Damage_N_Debuff, doDebuff>, EST_STUN, CSB_BIT_STUN, 4000),
|
||||
ActivePower(HealPowers, activePower<sSkillResult_Heal_HP, doHeal>, EST_HEAL_HP, CSB_BIT_NONE, 35),
|
||||
ActivePower(GroupHealPowers, activePower<sSkillResult_Heal_HP, doGroupHeal, GHEAL>,EST_HEAL_HP, CSB_BIT_NONE, 20),
|
||||
// TODO: Recall
|
||||
ActivePower(DrainPowers, activePower<sSkillResult_Buff, doBuff>, EST_BOUNDINGBALL, CSB_BIT_BOUNDINGBALL, 3000),
|
||||
ActivePower(SnarePowers, activePower<sSkillResult_Damage_N_Debuff, doDebuff>, EST_SNARE, CSB_BIT_DN_MOVE_SPEED, 4500),
|
||||
ActivePower(DrainPowers, activePower<sSkillResult_Buff, doBuff>, EST_BOUNDINGBALL, CSB_BIT_BOUNDINGBALL, 6000),
|
||||
ActivePower(SnarePowers, activePower<sSkillResult_Damage_N_Debuff, doDebuff>, EST_SNARE, CSB_BIT_DN_MOVE_SPEED, 8000),
|
||||
ActivePower(DamagePowers, activePower<sSkillResult_Damage, doDamage>, EST_DAMAGE, CSB_BIT_NONE, 12),
|
||||
ActivePower(LeechPowers, activePower<sSkillResult_Heal_HP, doLeech, LEECH>, EST_BLOODSUCKING, CSB_BIT_NONE, 18),
|
||||
ActivePower(SleepPowers, activePower<sSkillResult_Damage_N_Debuff, doDebuff>, EST_SLEEP, CSB_BIT_MEZ, 4500),
|
||||
ActivePower(SleepPowers, activePower<sSkillResult_Damage_N_Debuff, doDebuff>, EST_SLEEP, CSB_BIT_MEZ, 8000),
|
||||
};
|
||||
|
||||
}; // namespace
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user