From 57c9f139a2b8da3bbebedb33ece59ff41d61e4d7 Mon Sep 17 00:00:00 2001 From: dongresource Date: Sat, 12 Feb 2022 23:53:04 +0100 Subject: [PATCH] Fix quest item drop chances being shared between missions In our original implementation, quest item drops were rolled on the spot, so the chances of getting two quest items for different missions in a single kill (where both missions have you kill the same mob) were independent of each other. When we made quest item drop chances shared between group members so players doing missions together would progress at the same rate, we accidentally linked the quest item odds of different missions together. This change makes it so that the odds are per-task, so they're shared between different group members doing the same tasks, but distinct for different tasks being done by the same player. --- src/Combat.cpp | 46 +++++++++++++++++++++++++++++++++++----------- src/Missions.cpp | 4 ++-- src/Missions.hpp | 2 +- 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/Combat.cpp b/src/Combat.cpp index 391a267..1320be7 100644 --- a/src/Combat.cpp +++ b/src/Combat.cpp @@ -217,6 +217,30 @@ int Combat::hitMob(CNSocket *sock, Mob *mob, int damage) { return damage; } +/* + * When a group of players is doing missions together, we want them to all get + * quest items at the same time, but we don't want the odds of quest item + * drops from different missions to be linked together. That's why we use a + * single RNG roll per mission task, and every group member shares that same + * set of rolls. + */ +static void genQItemRolls(Player *leader, std::map& rolls) { + for (int i = 0; i < leader->groupCnt; i++) { + if (leader->groupIDs[i] == 0) + continue; + + CNSocket *otherSock = PlayerManager::getSockFromID(leader->groupIDs[i]); + if (otherSock == nullptr) + continue; + + Player *member = PlayerManager::getPlayer(otherSock); + + for (int j = 0; j < ACTIVE_MISSION_COUNT; j++) + if (member->tasks[j] != 0) + rolls[member->tasks[j]] = Rand::rand(); + } +} + void Combat::killMob(CNSocket *sock, Mob *mob) { mob->state = MobState::DEAD; mob->target = nullptr; @@ -231,19 +255,19 @@ void Combat::killMob(CNSocket *sock, Mob *mob) { Items::DropRoll rolled; Items::DropRoll eventRolled; - int rolledQItem = Rand::rand(); + std::map qitemRolls; + + Player *leader = PlayerManager::getPlayerFromID(plr->iIDGroup); + assert(leader != nullptr); // should never happen + + genQItemRolls(leader, qitemRolls); if (plr->groupCnt == 1 && plr->iIDGroup == plr->iID) { Items::giveMobDrop(sock, mob, rolled, eventRolled); - Missions::mobKilled(sock, mob->appearanceData.iNPCType, rolledQItem); + Missions::mobKilled(sock, mob->appearanceData.iNPCType, qitemRolls); } else { - Player* otherPlayer = PlayerManager::getPlayerFromID(plr->iIDGroup); - - if (otherPlayer == nullptr) - return; - - for (int i = 0; i < otherPlayer->groupCnt; i++) { - CNSocket* sockTo = PlayerManager::getSockFromID(otherPlayer->groupIDs[i]); + for (int i = 0; i < leader->groupCnt; i++) { + CNSocket* sockTo = PlayerManager::getSockFromID(leader->groupIDs[i]); if (sockTo == nullptr) continue; @@ -255,7 +279,7 @@ void Combat::killMob(CNSocket *sock, Mob *mob) { continue; Items::giveMobDrop(sockTo, mob, rolled, eventRolled); - Missions::mobKilled(sockTo, mob->appearanceData.iNPCType, rolledQItem); + Missions::mobKilled(sockTo, mob->appearanceData.iNPCType, qitemRolls); } } } @@ -379,7 +403,7 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) { sP_CL2FE_REQ_PC_ATTACK_CHARs* pkt = (sP_CL2FE_REQ_PC_ATTACK_CHARs*)data->buf; Player *plr = PlayerManager::getPlayer(sock); - // only GMs can use this this variant + // only GMs can use this variant if (plr->accountLevel > 30) return; diff --git a/src/Missions.cpp b/src/Missions.cpp index 8e05479..9d2dd69 100644 --- a/src/Missions.cpp +++ b/src/Missions.cpp @@ -567,7 +567,7 @@ void Missions::updateFusionMatter(CNSocket* sock, int fusion) { PlayerManager::sendToViewable(sock, (void*)&bcast, P_FE2CL_PC_EVENT, sizeof(sP_FE2CL_PC_EVENT)); } -void Missions::mobKilled(CNSocket *sock, int mobid, int rolledQItem) { +void Missions::mobKilled(CNSocket *sock, int mobid, std::map& rolls) { Player *plr = PlayerManager::getPlayer(sock); bool missionmob = false; @@ -593,7 +593,7 @@ void Missions::mobKilled(CNSocket *sock, int mobid, int rolledQItem) { // drop quest item if (task["m_iCSUItemNumNeeded"][j] != 0 && !isQuestItemFull(sock, task["m_iCSUItemID"][j], task["m_iCSUItemNumNeeded"][j]) ) { - bool drop = rolledQItem % 100 < task["m_iSTItemDropRate"][j]; + bool drop = rolls[plr->tasks[i]] % 100 < task["m_iSTItemDropRate"][j]; if (drop) { dropQuestItem(sock, plr->tasks[i], 1, task["m_iCSUItemID"][j], mobid); diff --git a/src/Missions.hpp b/src/Missions.hpp index acaf223..4e572a6 100644 --- a/src/Missions.hpp +++ b/src/Missions.hpp @@ -47,7 +47,7 @@ namespace Missions { // checks if player doesn't have n/n quest items void updateFusionMatter(CNSocket* sock, int fusion); - void mobKilled(CNSocket *sock, int mobid, int rolledQItem); + void mobKilled(CNSocket *sock, int mobid, std::map& rolls); void quitTask(CNSocket* sock, int32_t taskNum, bool manual);