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.
This commit is contained in:
dongresource 2022-02-12 23:53:04 +01:00
parent d3af99fcef
commit 57c9f139a2
3 changed files with 38 additions and 14 deletions

View File

@ -217,6 +217,30 @@ int Combat::hitMob(CNSocket *sock, Mob *mob, int damage) {
return 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<int, int>& 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) { void Combat::killMob(CNSocket *sock, Mob *mob) {
mob->state = MobState::DEAD; mob->state = MobState::DEAD;
mob->target = nullptr; mob->target = nullptr;
@ -231,19 +255,19 @@ void Combat::killMob(CNSocket *sock, Mob *mob) {
Items::DropRoll rolled; Items::DropRoll rolled;
Items::DropRoll eventRolled; Items::DropRoll eventRolled;
int rolledQItem = Rand::rand(); std::map<int, int> qitemRolls;
Player *leader = PlayerManager::getPlayerFromID(plr->iIDGroup);
assert(leader != nullptr); // should never happen
genQItemRolls(leader, qitemRolls);
if (plr->groupCnt == 1 && plr->iIDGroup == plr->iID) { if (plr->groupCnt == 1 && plr->iIDGroup == plr->iID) {
Items::giveMobDrop(sock, mob, rolled, eventRolled); Items::giveMobDrop(sock, mob, rolled, eventRolled);
Missions::mobKilled(sock, mob->appearanceData.iNPCType, rolledQItem); Missions::mobKilled(sock, mob->appearanceData.iNPCType, qitemRolls);
} else { } else {
Player* otherPlayer = PlayerManager::getPlayerFromID(plr->iIDGroup); for (int i = 0; i < leader->groupCnt; i++) {
CNSocket* sockTo = PlayerManager::getSockFromID(leader->groupIDs[i]);
if (otherPlayer == nullptr)
return;
for (int i = 0; i < otherPlayer->groupCnt; i++) {
CNSocket* sockTo = PlayerManager::getSockFromID(otherPlayer->groupIDs[i]);
if (sockTo == nullptr) if (sockTo == nullptr)
continue; continue;
@ -255,7 +279,7 @@ void Combat::killMob(CNSocket *sock, Mob *mob) {
continue; continue;
Items::giveMobDrop(sockTo, mob, rolled, eventRolled); 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; sP_CL2FE_REQ_PC_ATTACK_CHARs* pkt = (sP_CL2FE_REQ_PC_ATTACK_CHARs*)data->buf;
Player *plr = PlayerManager::getPlayer(sock); Player *plr = PlayerManager::getPlayer(sock);
// only GMs can use this this variant // only GMs can use this variant
if (plr->accountLevel > 30) if (plr->accountLevel > 30)
return; return;

View File

@ -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)); 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<int, int>& rolls) {
Player *plr = PlayerManager::getPlayer(sock); Player *plr = PlayerManager::getPlayer(sock);
bool missionmob = false; bool missionmob = false;
@ -593,7 +593,7 @@ void Missions::mobKilled(CNSocket *sock, int mobid, int rolledQItem) {
// drop quest item // drop quest item
if (task["m_iCSUItemNumNeeded"][j] != 0 && !isQuestItemFull(sock, task["m_iCSUItemID"][j], task["m_iCSUItemNumNeeded"][j]) ) { 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) { if (drop) {
dropQuestItem(sock, plr->tasks[i], 1, task["m_iCSUItemID"][j], mobid); dropQuestItem(sock, plr->tasks[i], 1, task["m_iCSUItemID"][j], mobid);

View File

@ -47,7 +47,7 @@ namespace Missions {
// checks if player doesn't have n/n quest items // checks if player doesn't have n/n quest items
void updateFusionMatter(CNSocket* sock, int fusion); void updateFusionMatter(CNSocket* sock, int fusion);
void mobKilled(CNSocket *sock, int mobid, int rolledQItem); void mobKilled(CNSocket *sock, int mobid, std::map<int, int>& rolls);
void quitTask(CNSocket* sock, int32_t taskNum, bool manual); void quitTask(CNSocket* sock, int32_t taskNum, bool manual);