-
-
Notifications
You must be signed in to change notification settings - Fork 141
Expand file tree
/
Copy pathMultiplayerAsyncQuest.cs
More file actions
299 lines (262 loc) · 11.2 KB
/
MultiplayerAsyncQuest.cs
File metadata and controls
299 lines (262 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
using HarmonyLib;
using Multiplayer.Common;
using RimWorld;
using RimWorld.Planet;
using RimWorld.QuestGen;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Verse;
namespace Multiplayer.Client.Comp
{
[HarmonyPatch(typeof(SettlementAbandonUtility), nameof(SettlementAbandonUtility.Abandon))]
static class RemoveMapCacheOnAbandon
{
static void Prefix(MapParent settlement)
{
if (Multiplayer.Client == null) return;
if (!Multiplayer.GameComp.asyncTime) return;
var mapAsyncTimeComp = settlement.Map.AsyncTime();
if (mapAsyncTimeComp != null)
{
MultiplayerAsyncQuest.TryRemoveCachedMap(mapAsyncTimeComp);
}
}
}
[HarmonyPatch(typeof(QuestGen), nameof(QuestGen.Generate))]
static class CacheQuestAfterGeneration
{
static void Postfix(ref Quest __result)
{
if (Multiplayer.Client == null) return;
if (!Multiplayer.GameComp.asyncTime) return;
MultiplayerAsyncQuest.CacheQuest(__result);
}
}
[HarmonyPatch(typeof(QuestManager), nameof(QuestManager.QuestManagerTick))]
[HarmonyPriority(Priority.Last)]
static class DisableQuestManagerTickTest
{
static bool Prefix()
{
if (Multiplayer.Client == null) return true;
if (!Multiplayer.GameComp.asyncTime) return true;
//Only tick world quest during world time
MultiplayerAsyncQuest.TickWorldQuests();
return false;
}
}
//Clear Cache then Cache all quests on game load
[HarmonyPatch(typeof(Game), nameof(Game.FinalizeInit))]
[HarmonyPriority(Priority.Last)]
static class SetupAsyncTimeLookupForQuests
{
static void Postfix()
{
if (Multiplayer.Client == null) return;
if (!Multiplayer.GameComp.asyncTime) return;
MultiplayerAsyncQuest.Reset();
foreach (var quest in Find.QuestManager.QuestsListForReading)
{
MultiplayerAsyncQuest.CacheQuest(quest);
}
}
}
[HarmonyPatch]
static class SetContextForQuest
{
static IEnumerable<MethodInfo> TargetMethods()
{
yield return AccessTools.PropertyGetter(typeof(Quest), nameof(Quest.TicksSinceAppeared));
yield return AccessTools.PropertyGetter(typeof(Quest), nameof(Quest.TicksSinceAccepted));
yield return AccessTools.PropertyGetter(typeof(Quest), nameof(Quest.TicksSinceCleanup));
yield return AccessTools.Method(typeof(Quest), nameof(Quest.SetInitiallyAccepted));
yield return AccessTools.Method(typeof(Quest), nameof(Quest.CleanupQuestParts));
}
static void Prefix(Quest __instance, ref AsyncTimeComp __state)
{
if (Multiplayer.Client == null) return;
if (!Multiplayer.GameComp.asyncTime) return;
__state = MultiplayerAsyncQuest.TryGetCachedQuestMap(__instance);
__state?.PreContext();
}
static void Postfix(AsyncTimeComp __state) => __state?.PostContext();
}
[HarmonyPatch(typeof(Quest), nameof(Quest.Accept))]
static class SetContextForAccept
{
static void Prefix(Quest __instance, ref AsyncTimeComp __state)
{
if (Multiplayer.Client == null) return;
//Make sure quest is accepted and async time is enabled and there are parts to this quest
if (__instance.State != QuestState.NotYetAccepted || !Multiplayer.GameComp.asyncTime || __instance.parts == null) return;
__state = MultiplayerAsyncQuest.CacheQuest(__instance);
__state?.PreContext();
}
static void Postfix(AsyncTimeComp __state) => __state?.PostContext();
}
[HarmonyPatch(typeof(Quest), nameof(Quest.End))]
static class RemoveQuestFromCacheOnQuestEnd
{
static void Postfix(Quest __instance)
{
if (Multiplayer.Client == null) return;
MultiplayerAsyncQuest.TryRemoveCachedQuest(__instance);
}
}
public static class MultiplayerAsyncQuest
{
//Probably should move this to a class so its not a Dictionary of Lists
private static readonly Dictionary<AsyncTimeComp, List<Quest>> mapQuestsCache = new Dictionary<AsyncTimeComp, List<Quest>>();
private static readonly List<Quest> worldQuestsCache = new List<Quest>();
//List of quest parts that have mapParent as field
private static readonly List<Type> questPartsToCheck = new List<Type>()
{
typeof(QuestPart_DropPods),
typeof(QuestPart_SpawnThing),
typeof(QuestPart_PawnsArrive),
typeof(QuestPart_Incident),
typeof(QuestPart_RandomRaid),
typeof(QuestPart_ThreatsGenerator),
typeof(QuestPart_Infestation),
typeof(QuestPart_GameCondition),
typeof(QuestPart_JoinPlayer),
typeof(QuestPart_TrackWhenExitMentalState),
typeof(QuestPart_RequirementsToAcceptBedroom),
typeof(QuestPart_MechCluster),
typeof(QuestPart_DropMonumentMarkerCopy),
typeof(QuestPart_PawnsAvailable)
};
/// <summary>
/// Tries to remove Map from cache, then moves all Quests cached to that map to WorldQuestsCache
/// </summary>
/// <param name="mapAsyncTimeComp">Map to remove</param>
/// <returns>If map is found in cache</returns>
public static bool TryRemoveCachedMap(AsyncTimeComp mapAsyncTimeComp)
{
if (mapQuestsCache.TryGetValue(mapAsyncTimeComp, out var quests))
{
worldQuestsCache.AddRange(quests);
return mapQuestsCache.Remove(mapAsyncTimeComp);
}
return false;
}
/// <summary>
/// Tries to remove Quest from Map and World cache
/// </summary>
/// <param name="quest">Quest to remove</param>
/// <returns>If quest is found in cache</returns>
public static bool TryRemoveCachedQuest(Quest quest)
=> mapQuestsCache.SingleOrDefault(x => x.Value.Contains(quest)).Value?.Remove(quest) ?? false | worldQuestsCache.Remove(quest);
/// <summary>
/// Attempts to get the MapAsyncTimeComp cached for that quest
/// </summary>
/// <param name="quest">Quest to find MapAsyncTimeComp for</param>
/// <returns>MapAsyncTimeComp for that quest or Null if not found</returns>
public static AsyncTimeComp TryGetCachedQuestMap(Quest quest)
{
if (!Multiplayer.GameComp.asyncTime || quest == null) return null;
return mapQuestsCache.FirstOrDefault(x => x.Value.Contains(quest)).Key;
}
/// <summary>
/// Determines if a quest has a MapAsyncTimeComp, if it does it will cache it to the mapQuestsCache otherwise to worldQuestsCache
/// </summary>
/// <param name="quest">Quest to Cache</param>
/// <returns>MapAsyncTimeComp for that quest or Null if not found</returns>
public static AsyncTimeComp CacheQuest(Quest quest)
{
if (!Multiplayer.GameComp.asyncTime || quest == null) return null;
//Check if quest targets players map
var mapAsyncTimeComp = TryGetQuestMap(quest);
//if it does add it to the cache for that map
if (mapAsyncTimeComp != null)
{
UpsertQuestMap(mapAsyncTimeComp, quest);
if (MpVersion.IsDebug)
Log.Message($"Info: Found AsyncTimeMap: '{mapAsyncTimeComp.map.Parent.Label}' for Quest: '{quest.name}'");
}
//if it doesn't add it to the world quest list
else
{
worldQuestsCache.Add(quest);
if (MpVersion.IsDebug)
Log.Message($"Info: Could not find AsyncTimeMap for Quest: '{quest.name}'");
}
return mapAsyncTimeComp;
}
/// <summary>
/// Clears all cache for both mapQuestsCache and worldQuestsCache as they are static and requires manually clearing on load
/// </summary>
public static void Reset()
{
mapQuestsCache.Clear();
worldQuestsCache.Clear();
}
/// <summary>
/// Runs QuestTick() on all quests cached for worldQuestsCache
/// </summary>
public static void TickWorldQuests()
{
TickQuests(worldQuestsCache);
}
/// <summary>
/// Runs QuestTick() on all quests cached for a specific map
/// </summary>
/// <param name="mapAsyncTimeComp">Map to tick Quests for</param>
public static void TickMapQuests(AsyncTimeComp mapAsyncTimeComp)
{
if (mapQuestsCache.TryGetValue(mapAsyncTimeComp, out var quests))
{
TickQuests(quests);
}
}
/// <summary>
/// Runs QuestTick() on all quests passed
/// </summary>
/// <param name="quests">Quests to run QuestTick() on</param>
private static void TickQuests(IEnumerable<Quest> quests)
{
foreach (var quest in quests)
{
quest.QuestTick();
}
}
/// <summary>
/// Attempts to find the map the quest will target, via its Quest Parts. Filtering on Map.IsPlayerHome and then check if it contains a MapAsyncTimeComp
/// </summary>
/// <param name="quest">Quest to check Map Parts on</param>
/// <returns>MapAsyncTimeComp for that quest or Null if not found</returns>
private static AsyncTimeComp TryGetQuestMap(Quest quest)
{
//Really terrible way to determine if any quest parts have a map which also has an async time
foreach (var part in quest.parts.Where(x => x != null && questPartsToCheck.Contains(x.GetType())))
{
if (part.GetType().GetField("mapParent")?.GetValue(part) is MapParent { Map: not null } mapParent)
{
var mapAsyncTimeComp = mapParent.Map.IsPlayerHome ? mapParent.Map.AsyncTime() : null;
if (mapAsyncTimeComp != null) return mapAsyncTimeComp;
}
}
return null;
}
/// <summary>
/// Attempts to update or insert a new cache for a Map and Quest, will always attempt to remove the quest before adding in case it existed on another map.
/// </summary>
/// <param name="mapAsyncTimeComp">Map that will hold the quest</param>
/// <param name="quest">Quest for the map</param>
private static void UpsertQuestMap(AsyncTimeComp mapAsyncTimeComp, Quest quest)
{
//if there is more then one map with the quest something went wrong - removes quest object in case it changes map
TryRemoveCachedQuest(quest);
if (mapQuestsCache.TryGetValue(mapAsyncTimeComp, out var quests))
{
quests.Add(quest);
}
else
{
mapQuestsCache[mapAsyncTimeComp] = new List<Quest> { quest };
}
}
}
}