From 4fea2ae896e9ffb4eb5d545a25718c3fc245a965 Mon Sep 17 00:00:00 2001 From: Jade Date: Sun, 27 Sep 2020 02:53:03 +0100 Subject: [PATCH 1/2] Variable damage to/from mobs * Player weapons and armor ratings are taken into account when damaging/getting damaged by mobs. * Players have a 5% chance to critical strike mobs, this doubles the player's weapon power. * Aside from player and mob stat based damage variance, there is also an inherent 20% variance to any damage. --- src/ItemManager.cpp | 19 +++++++++++++++++++ src/ItemManager.hpp | 3 ++- src/MobManager.cpp | 37 +++++++++++++++++++++++++++++++------ src/MobManager.hpp | 1 + src/Player.hpp | 4 ++++ src/PlayerManager.cpp | 3 +++ src/TableData.cpp | 3 +-- 7 files changed, 61 insertions(+), 9 deletions(-) diff --git a/src/ItemManager.cpp b/src/ItemManager.cpp index 11feb73..2a62035 100644 --- a/src/ItemManager.cpp +++ b/src/ItemManager.cpp @@ -123,6 +123,9 @@ void ItemManager::itemMoveHandler(CNSocket* sock, CNPacketData* data) { // send equip event to other players PlayerManager::sendToViewable(sock, (void*)&equipChange, P_FE2CL_PC_EQUIP_CHANGE, sizeof(sP_FE2CL_PC_EQUIP_CHANGE)); + + // set equipment stats serverside + setItemStats(plr.plr); } // send response @@ -859,3 +862,19 @@ void ItemManager::checkItemExpire(CNSocket* sock, Player* player) { player->toRemoveVehicle.eIL = 0; player->toRemoveVehicle.iSlotNum = 0; } + +void ItemManager::setItemStats(Player* plr) { + + plr->pointDamage = 8 + plr->level * 2; + plr->groupDamage = 8 + plr->level * 2; + plr->defense = 16 + plr->level * 4; + + Item* itemStatsDat; + + for (int i = 0; i < 4; i++) { + itemStatsDat = ItemManager::getItemData(plr->Equip[i].iID, plr->Equip[i].iType); + plr->pointDamage += itemStatsDat->pointDamage; + plr->groupDamage += itemStatsDat->groupDamage; + plr->defense += itemStatsDat->defense; + } +} diff --git a/src/ItemManager.hpp b/src/ItemManager.hpp index b7221c3..feb96e2 100644 --- a/src/ItemManager.hpp +++ b/src/ItemManager.hpp @@ -5,7 +5,7 @@ struct Item { bool tradeable, sellable; - int buyPrice, sellPrice, stackSize, level, rarity; // TODO: implement more as needed + int buyPrice, sellPrice, stackSize, level, rarity, pointDamage, groupDamage, defense; // TODO: implement more as needed }; struct VendorListing { int sort, type, iID; @@ -49,4 +49,5 @@ namespace ItemManager { int findFreeSlot(Player *plr); Item* getItemData(int32_t id, int32_t type); void checkItemExpire(CNSocket* sock, Player* player); + void setItemStats(Player* plr); } diff --git a/src/MobManager.cpp b/src/MobManager.cpp index ad93cfc..ebabcb6 100644 --- a/src/MobManager.cpp +++ b/src/MobManager.cpp @@ -61,12 +61,19 @@ void MobManager::pcAttackNpcs(CNSocket *sock, CNPacketData *data) { } Mob *mob = Mobs[pktdata[i]]; - int damage = hitMob(sock, mob, 150); + std::pair damage; + + if (pkt->iNPCCnt > 1) + damage = getDamage(plr->groupDamage, (int)mob->data["m_iProtection"], true); + else + damage = getDamage(plr->pointDamage, (int)mob->data["m_iProtection"], true); + + damage.first = hitMob(sock, mob, damage.first); respdata[i].iID = mob->appearanceData.iNPC_ID; - respdata[i].iDamage = damage; + respdata[i].iDamage = damage.first; respdata[i].iHP = mob->appearanceData.iHP; - respdata[i].iHitFlag = 2; // hitscan, not a rocket or a grenade + respdata[i].iHitFlag = damage.second; // hitscan, not a rocket or a grenade } sock->sendPacket((void*)respbuf, P_FE2CL_PC_ATTACK_NPCs_SUCC, resplen); @@ -93,15 +100,16 @@ void MobManager::npcAttackPc(Mob *mob) { sP_FE2CL_NPC_ATTACK_PCs *pkt = (sP_FE2CL_NPC_ATTACK_PCs*)respbuf; sAttackResult *atk = (sAttackResult*)(respbuf + sizeof(sP_FE2CL_NPC_ATTACK_PCs)); - plr->HP += (int)mob->data["m_iPower"]; // already negative + auto damage = getDamage(440 + (int)mob->data["m_iPower"], plr->defense, false); + plr->HP -= damage.first; pkt->iNPC_ID = mob->appearanceData.iNPC_ID; pkt->iPCCnt = 1; atk->iID = plr->iID; - atk->iDamage = -(int)mob->data["m_iPower"]; + atk->iDamage = damage.first; atk->iHP = plr->HP; - atk->iHitFlag = 2; + atk->iHitFlag = damage.second; mob->target->sendPacket((void*)respbuf, P_FE2CL_NPC_ATTACK_PCs, resplen); PlayerManager::sendToViewable(mob->target, (void*)respbuf, P_FE2CL_NPC_ATTACK_PCs, resplen); @@ -564,3 +572,20 @@ void MobManager::playerTick(CNServer *serv, time_t currTime) { } } } + +std::pair MobManager::getDamage(int attackPower, int defensePower, bool shouldCrit) { + + std::pair ret = {}; + + int damage = attackPower * (rand() % 40 + 80) / 100; // 20% variance + ret.second = 1; + + if (shouldCrit && rand() % 20 == 0) { + damage *= 2; // critical hit + ret.second = 2; + } + + ret.first = std::max(std::min(damage, damage * damage / defensePower / 4), std::min(damage * damage / defensePower / 2, damage - defensePower)); + + return ret; +} \ No newline at end of file diff --git a/src/MobManager.hpp b/src/MobManager.hpp index 5b3e237..4e7f266 100644 --- a/src/MobManager.hpp +++ b/src/MobManager.hpp @@ -104,4 +104,5 @@ namespace MobManager { void killMob(CNSocket *sock, Mob *mob); void giveReward(CNSocket *sock); std::pair lerp(int, int, int, int, int); + std::pair getDamage(int, int, bool); } diff --git a/src/Player.hpp b/src/Player.hpp index 57c24f0..21ffe8b 100644 --- a/src/Player.hpp +++ b/src/Player.hpp @@ -46,6 +46,10 @@ struct Player { bool inCombat; bool dotDamage; + + int pointDamage; + int groupDamage; + int defense; int64_t aQuestFlag[16]; int tasks[ACTIVE_MISSION_COUNT]; diff --git a/src/PlayerManager.cpp b/src/PlayerManager.cpp index bffb513..e29a69f 100644 --- a/src/PlayerManager.cpp +++ b/src/PlayerManager.cpp @@ -316,6 +316,9 @@ void PlayerManager::enterPlayer(CNSocket* sock, CNPacketData* data) { addPlayer(sock, plr); //check if there is an expiring vehicle ItemManager::checkItemExpire(sock, getPlayer(sock)); + + //set player equip stats + ItemManager::setItemStats(getPlayer(sock)); } void PlayerManager::sendToViewable(CNSocket* sock, void* buf, uint32_t type, size_t size) { diff --git a/src/TableData.cpp b/src/TableData.cpp index 89ce7f2..2d4e4ba 100644 --- a/src/TableData.cpp +++ b/src/TableData.cpp @@ -117,8 +117,7 @@ void TableData::init() { auto item = _item.value(); int typeOverride = getItemType(i); // used for special cases where iEquipLoc doesn't indicate item type ItemManager::ItemData[std::pair(item["m_iItemNumber"], typeOverride != -1 ? typeOverride : (int)item["m_iEquipLoc"])] - = { item["m_iTradeAble"] == 1, item["m_iSellAble"] == 1, item["m_iItemPrice"], item["m_iItemSellPrice"], item["m_iStackNumber"], i > 9 ? 0 : (int)item["m_iMinReqLev"], - i > 9 ? 1 : (int)item["m_iRarity"] }; + = { item["m_iTradeAble"] == 1, item["m_iSellAble"] == 1, item["m_iItemPrice"], item["m_iItemSellPrice"], item["m_iStackNumber"], i > 9 ? 0 : (int)item["m_iMinReqLev"], i > 9 ? 1 : (int)item["m_iRarity"], i > 9 ? 0 : (int)item["m_iPointRat"], i > 9 ? 0 : (int)item["m_iGroupRat"], i > 9 ? 0 : (int)item["m_iDefenseRat"] }; } } From a5d3160588c5d8f9b51318206b7d89c53fba115f Mon Sep 17 00:00:00 2001 From: Jade Date: Sun, 27 Sep 2020 03:28:06 +0100 Subject: [PATCH 2/2] Mob movement smoothening + Bugfixes * Mobs now move at a tickrate per second of 2 as opposed to less than 1 before. * How lerping works was changed slightly, mobs are bumped down to half the speed to account for the higher tickrate. * Damage formula was altered to more closely match the OG game. --- src/MobManager.cpp | 47 ++++++++++++++++++++++++++++++++-------------- src/MobManager.hpp | 2 +- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/MobManager.cpp b/src/MobManager.cpp index ebabcb6..c7653ed 100644 --- a/src/MobManager.cpp +++ b/src/MobManager.cpp @@ -7,6 +7,13 @@ #include #include +#ifndef MIN +# define MIN(A,B) ((A)<(B)?(A):(B)) +#endif +#ifndef MAX +# define MAX(A,B) ((A)>(B)?(A):(B)) +#endif + std::map MobManager::Mobs; void MobManager::init() { @@ -64,9 +71,9 @@ void MobManager::pcAttackNpcs(CNSocket *sock, CNPacketData *data) { std::pair damage; if (pkt->iNPCCnt > 1) - damage = getDamage(plr->groupDamage, (int)mob->data["m_iProtection"], true); + damage = getDamage(plr->groupDamage, (int)mob->data["m_iProtection"], true, (int)mob->data["m_iNpcLevel"]); else - damage = getDamage(plr->pointDamage, (int)mob->data["m_iProtection"], true); + damage = getDamage(plr->pointDamage, (int)mob->data["m_iProtection"], true, (int)mob->data["m_iNpcLevel"]); damage.first = hitMob(sock, mob, damage.first); @@ -100,7 +107,7 @@ void MobManager::npcAttackPc(Mob *mob) { sP_FE2CL_NPC_ATTACK_PCs *pkt = (sP_FE2CL_NPC_ATTACK_PCs*)respbuf; sAttackResult *atk = (sAttackResult*)(respbuf + sizeof(sP_FE2CL_NPC_ATTACK_PCs)); - auto damage = getDamage(440 + (int)mob->data["m_iPower"], plr->defense, false); + auto damage = getDamage(440 + (int)mob->data["m_iPower"], plr->defense, false, 36 - (int)mob->data["m_iNpcLevel"]); plr->HP -= damage.first; pkt->iNPC_ID = mob->appearanceData.iNPC_ID; @@ -280,7 +287,7 @@ void MobManager::combatStep(Mob *mob, time_t currTime) { // movement logic if (mob->nextMovement != 0 && currTime < mob->nextMovement) return; - mob->nextMovement = currTime + (int)mob->data["m_iDelayTime"] * 100; + mob->nextMovement = currTime + 500; int speed = mob->data["m_iRunSpeed"]; @@ -386,6 +393,11 @@ void MobManager::roamingStep(Mob *mob, time_t currTime) { void MobManager::retreatStep(Mob *mob, time_t currTime) { // distance between spawn point and current location + if (mob->nextMovement != 0 && currTime < mob->nextMovement) + return; + + mob->nextMovement = currTime + 500; + int distance = hypot(mob->appearanceData.iX - mob->spawnX, mob->appearanceData.iY - mob->spawnY); if (distance > mob->data["m_iIdleRange"]) { @@ -458,13 +470,21 @@ void MobManager::step(CNServer *serv, time_t currTime) { std::pair MobManager::lerp(int x1, int y1, int x2, int y2, int speed) { std::pair ret = {}; + speed /= 2; int distance = hypot(x1 - x2, y1 - y2); - int lerps = distance / speed; - // interpolate only the first point - float frac = 1.0f / (lerps+1); - ret.first = (x1 * (1.0f - frac)) + (x2 * frac); - ret.second = (y1 * (1.0f - frac)) + (y2 * frac); + if (distance > speed) { + int lerps = distance / speed; + + // interpolate only the first point + float frac = 1.0f / (lerps); + + ret.first = (x1 + (x2 - x1) * frac); + ret.second = (y1 + (y2 - y1) * frac); + } else { + ret.first = x2; + ret.second = y2; + } return ret; } @@ -573,19 +593,18 @@ void MobManager::playerTick(CNServer *serv, time_t currTime) { } } -std::pair MobManager::getDamage(int attackPower, int defensePower, bool shouldCrit) { +std::pair MobManager::getDamage(int attackPower, int defensePower, bool shouldCrit, int crutchLevel) { std::pair ret = {}; - int damage = attackPower * (rand() % 40 + 80) / 100; // 20% variance + int damage = (MAX(40, attackPower - defensePower) * (34 + crutchLevel) + MIN(attackPower, attackPower * attackPower / defensePower) * (36 - crutchLevel)) / 70; + ret.first = damage * (rand() % 40 + 80) / 100; // 20% variance ret.second = 1; if (shouldCrit && rand() % 20 == 0) { - damage *= 2; // critical hit + ret.first *= 2; // critical hit ret.second = 2; } - ret.first = std::max(std::min(damage, damage * damage / defensePower / 4), std::min(damage * damage / defensePower / 2, damage - defensePower)); - return ret; } \ No newline at end of file diff --git a/src/MobManager.hpp b/src/MobManager.hpp index 4e7f266..e8db4e1 100644 --- a/src/MobManager.hpp +++ b/src/MobManager.hpp @@ -104,5 +104,5 @@ namespace MobManager { void killMob(CNSocket *sock, Mob *mob); void giveReward(CNSocket *sock); std::pair lerp(int, int, int, int, int); - std::pair getDamage(int, int, bool); + std::pair getDamage(int, int, bool, int); }