Skip to content

Commit 5bdcd37

Browse files
committed
feat: Implement mutually exclusive quests
Some world quests should not be rolled and active at the same time. Allow a way for these dependencies to be described and give the user a way to override the list via the GameServerSettings.csx. Then on party create and instance reset when the party rolled world quest list is generated/updated, enforce those restrictions for the party.
1 parent 02ecdc6 commit 5bdcd37

2 files changed

Lines changed: 111 additions & 2 deletions

File tree

Arrowgene.Ddon.GameServer/Quests/QuestStateManager.cs

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,49 @@ public SharedQuestStateManager(PartyGroup party, DdonGameServer server)
860860
this.Server = server;
861861
}
862862

863+
private Dictionary<QuestId, int> BuildMutualExclusionLookup()
864+
{
865+
var lookup = new Dictionary<QuestId, int>();
866+
var groups = Server.GameSettings.GameServerSettings.MutuallyExclusiveWorldQuestGroups;
867+
for (int i = 0; i < groups.Count; i++)
868+
{
869+
foreach (var questId in groups[i])
870+
{
871+
lookup[(QuestId)questId] = i;
872+
}
873+
}
874+
return lookup;
875+
}
876+
877+
private void EnforceMutualExclusion()
878+
{
879+
var lookup = BuildMutualExclusionLookup();
880+
var rolledGroups = new HashSet<int>();
881+
882+
lock (ActiveQuests)
883+
{
884+
foreach (var scheduleIds in RolledInstanceWorldQuests.Values)
885+
{
886+
var toRemove = new List<uint>();
887+
foreach (var scheduleId in scheduleIds)
888+
{
889+
var quest = QuestManager.GetQuestByScheduleId(scheduleId);
890+
if (quest != null && lookup.TryGetValue(quest.QuestId, out int groupIndex))
891+
{
892+
if (!rolledGroups.Add(groupIndex))
893+
{
894+
toRemove.Add(scheduleId);
895+
}
896+
}
897+
}
898+
foreach (var scheduleId in toRemove)
899+
{
900+
scheduleIds.Remove(scheduleId);
901+
}
902+
}
903+
}
904+
}
905+
863906
public override void EnforceInitialPoolEligibility()
864907
{
865908
var settings = Server.GameSettings.GameServerSettings;
@@ -868,14 +911,23 @@ public override void EnforceInitialPoolEligibility()
868911
{
869912
// Copy the server-wide pool and apply rank filter (no re-roll replacement).
870913
ApplyServerPool(Server.WorldQuestManager.GetCurrentPool());
914+
EnforceMutualExclusion();
871915
return;
872916
}
873917

874918
// InstanceReset mode: re-roll ineligible slots for this party.
875-
if (!settings.WorldQuestFilterByLeaderAreaRank) return;
919+
if (!settings.WorldQuestFilterByLeaderAreaRank)
920+
{
921+
EnforceMutualExclusion();
922+
return;
923+
}
876924

877925
var leaderCharacter = Party.Leader?.Client.Character;
878-
if (leaderCharacter == null) return;
926+
if (leaderCharacter == null)
927+
{
928+
EnforceMutualExclusion();
929+
return;
930+
}
879931

880932
lock (ActiveQuests)
881933
{
@@ -898,6 +950,8 @@ public override void EnforceInitialPoolEligibility()
898950
}
899951
}
900952
}
953+
954+
EnforceMutualExclusion();
901955
}
902956

903957
/// <summary>
@@ -949,6 +1003,7 @@ public void OnServerWorldQuestReset(Dictionary<QuestAreaId, HashSet<uint>> serve
9491003
}
9501004

9511005
ApplyServerPool(serverPool);
1006+
EnforceMutualExclusion();
9521007
SendWorldQuestListNtc();
9531008
}
9541009

@@ -986,9 +1041,34 @@ protected override uint GetEffectiveAreaRank(Character character, QuestAreaId ar
9861041

9871042
protected override Quest RollQuestVariant(QuestId questId)
9881043
{
1044+
var lookup = BuildMutualExclusionLookup();
1045+
if (lookup.TryGetValue(questId, out int groupIndex))
1046+
{
1047+
var rolledQuestIds = GetRolledQuestIds();
1048+
var groups = Server.GameSettings.GameServerSettings.MutuallyExclusiveWorldQuestGroups;
1049+
if (groups[groupIndex].Any(gid => (QuestId)gid != questId && rolledQuestIds.Contains((QuestId)gid)))
1050+
{
1051+
return null;
1052+
}
1053+
}
9891054
return RollEligibleQuestVariant(questId, Party.Leader?.Client.Character);
9901055
}
9911056

1057+
private HashSet<QuestId> GetRolledQuestIds()
1058+
{
1059+
lock (ActiveQuests)
1060+
{
1061+
var result = new HashSet<QuestId>();
1062+
foreach (var scheduleIds in RolledInstanceWorldQuests.Values)
1063+
foreach (var sid in scheduleIds)
1064+
{
1065+
var quest = QuestManager.GetQuestByScheduleId(sid);
1066+
if (quest != null) result.Add(quest.QuestId);
1067+
}
1068+
return result;
1069+
}
1070+
}
1071+
9921072
public override bool CompleteQuestProgress(uint questScheduleId, DbConnection? connectionIn = null)
9931073
{
9941074
Quest quest = GetQuest(questScheduleId);

Arrowgene.Ddon.Server/Settings/GameServerSettings.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,35 @@ public uint AdventureGuideMaxQuestList
258258
}
259259
private const uint _AdventureGuideMaxQuestList = 50;
260260

261+
/// <summary>
262+
/// Groups of mutually exclusive world quest IDs. When rolling world quests for a party,
263+
/// at most one quest from each group will be selected at a time. Use this to prevent
264+
/// conflicting quests from appearing simultaneously. Quest IDs should be specified as
265+
/// uint values (e.g. 21000079).
266+
/// </summary>
267+
[DefaultValue(
268+
"new List<List<uint>>\n" +
269+
"{\n" +
270+
" new List<uint> { 21000079, 21000089 },\n" +
271+
" new List<uint> { 20995009, 21000008 },\n" +
272+
"}"
273+
)]
274+
public List<List<uint>> MutuallyExclusiveWorldQuestGroups
275+
{
276+
set
277+
{
278+
SetSetting("MutuallyExclusiveWorldQuestGroups", value);
279+
}
280+
get
281+
{
282+
return TryGetSetting("MutuallyExclusiveWorldQuestGroups", new List<List<uint>>
283+
{
284+
new List<uint> { 21000079, 21000089 },
285+
new List<uint> { 20995009, 21000008 },
286+
});
287+
}
288+
}
289+
261290
/// <summary>
262291
/// Uses the automatic exp calculation system for all enemies instead of just using the
263292
/// ones marked in quest files.

0 commit comments

Comments
 (0)