Skip to content

Commit

Permalink
Added compat for Vanilla Aspirations Expanded (#461)
Browse files Browse the repository at this point in the history
* Added compat for Vanilla Aspirations Expanded

The mod uses vanilla growth moment letter and dialog as its base for 2 of its own letters/dialogs.

In case of "Aspirations Fulfilled" letter/dialog, those are basically the vanilla growth moment letter/dialog with a slight changes. It's mostly handled by MP syncing, only needing the default choice handling (for which we use the vanilla growth moment default choice method).

For "Growth Moment with Aspirations" dialog (which replaces the vanilla one when a pawn reaches its last growth moment), this letter and dialog (due to extra features and no easy way to modify vanilla dialog itself) include a lot of new code, as well as copying a big chunk of the vanilla dialog's (with some changes).

The second letter required extra work to properly handle. We needed to seed the aspiration choices (similarly to how MP handles vanilla's trait and passion choices). We also need to prevent the removal of the letter when the dialog is finished, similarly to how MP does it. And finally, we needed the default choice handling for aspiration selection as well (although we use MP method to handle traits/passions).

* Include GitHub/Workshop links
  • Loading branch information
SokyranTheDragon authored Aug 17, 2024
1 parent 98faa5e commit 9ab9fd1
Showing 1 changed file with 133 additions and 0 deletions.
133 changes: 133 additions & 0 deletions Source/Mods/VanillaAspirationsExpanded.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using HarmonyLib;
using Multiplayer.API;
using RimWorld;
using Verse;

namespace Multiplayer.Compat;

/// <summary>Vanilla Aspirations Expanded by Oskar Potocki, Legodude17, Sarg Bjornson</summary>
/// <see href="https://github.com/Vanilla-Expanded/VanillaAspirationsExpanded"/>
/// <see href="https://steamcommunity.com/sharedfiles/filedetails/?id=3268401022"/>
[MpCompatFor("VanillaExpanded.VanillaAspirationsExpanded")]
public class VanillaAspirationsExpanded
{
#region Fields

// MP Compat
private static bool isDrawingDialog = false;

// List<AspirationDef>
private static Type aspirationDefListType;

// SyncDelegates
private static FastInvokeHandler pickRandomTraitAndPassionsMethod;

// ChoiceLetter_GrowthMoment_Aspirations
private static AccessTools.FieldRef<ChoiceLetter_GrowthMoment, IList> aspirationChoicesField;
private static AccessTools.FieldRef<ChoiceLetter_GrowthMoment, int> aspirationGainsCountField;
private static FastInvokeHandler trySetAspirationChoicesMethod;
private static FastInvokeHandler makeAspirationChoicesMethod;

#endregion

#region Main Patch

public VanillaAspirationsExpanded(ModContentPack mod)
{
// Run the patches
MpCompatPatchLoader.LoadPatch(this);

// Setup list type with a generic we can't reference
var type = AccessTools.TypeByName("VAspirE.AspirationDef");
aspirationDefListType = typeof(List<>).MakeGenericType(type);

// Access MP method
type = AccessTools.TypeByName("Multiplayer.Client.SyncDelegates");
var method = AccessTools.DeclaredMethod(type, "PickRandomTraitAndPassions");
pickRandomTraitAndPassionsMethod = MethodInvoker.GetHandler(method);

// Handle aspirations fulfilled letter/dialog
type = AccessTools.TypeByName("VAspirE.ChoiceLetter_AspirationsFulfilled");
// This letter/dialog have different type, but are otherwise mostly the
// same as vanilla growth moment ones. MP should handle those already,
// and we only need to register the default choice for the letter.
MP.RegisterDefaultLetterChoice(method, type);

// Handle growth moment with aspirations letter/dialog
type = AccessTools.TypeByName("VAspirE.ChoiceLetter_GrowthMoment_Aspirations");
// Setup fields
aspirationChoicesField = AccessTools.FieldRefAccess<IList>(type, "aspirationChoices");
aspirationGainsCountField = AccessTools.FieldRefAccess<int>(type, "aspirationGainsCount");
// Setup methods
trySetAspirationChoicesMethod = MethodInvoker.GetHandler(AccessTools.DeclaredMethod(type, "TrySetAspirationChoices"));
var makeAspirationChoices = AccessTools.DeclaredMethod(type, "MakeAspirationChoices");
makeAspirationChoicesMethod = MethodInvoker.GetHandler(makeAspirationChoices);
// Sync methods
MP.RegisterSyncMethod(makeAspirationChoices);
// Letter timeout handling
MP.RegisterDefaultLetterChoice(MpMethodUtil.MethodOf(PickRandomAspirationsTraitPassions), type);
}

#endregion

#region Choice letter expiry handling

private static void PickRandomAspirationsTraitPassions(ChoiceLetter_GrowthMoment letter)
{
// Make sure the aspirations are prepared
trySetAspirationChoicesMethod(letter);

var aspirationChoices = aspirationChoicesField(letter);
var aspirationCount = aspirationGainsCountField(letter);

// If there's any aspirations to pick from, pick some random ones.
if (aspirationCount > 0 && aspirationChoices != null)
makeAspirationChoicesMethod(letter,
Activator.CreateInstance(aspirationDefListType, aspirationChoices.Cast<Def>().InRandomOrder().Take(aspirationCount)));
// This is technically no-op due to null check, but include it just in case
// the mod decides to do something with it at some point (or a different mod uses it).
else
makeAspirationChoicesMethod(letter, null);

// MP method to pick traits and passions, which handles normal, vanilla growth moment.
// This will also handle closing the letter.
pickRandomTraitAndPassionsMethod(letter);
}

#endregion

#region Seed aspiration generation RNG

// The letter tries to generate the options when opened. Make the picks seeded, so all players will get the same ones.
[MpCompatPrefix("VAspirE.ChoiceLetter_GrowthMoment_Aspirations", "TrySetAspirationChoices")]
private static void PreTrySetAspirationChoices(ChoiceLetter_GrowthMoment __instance)
=> Rand.PushState(Gen.HashCombineInt(__instance.pawn.thingIDNumber, __instance.arrivalTick));

[MpCompatPostfix("VAspirE.ChoiceLetter_GrowthMoment_Aspirations", "TrySetAspirationChoices")]
private static void PostTrySetAspirationChoices()
=> Rand.PopState();

#endregion

#region Don't close dialog when drawing it

// Patches to not remove the letter if it's in the process of being drawn.
// Rather than prefixing LetterStack.RemoveLetter we could instead change
// Multiplayer.IsDrawingGrowthMomentDialog.isDrawing to true/false, and let
// MP handle this itself. However, it's safer to do a new patch for this.

[MpCompatPrefix("VAspirE.Dialog_GrowthMomentChoices_Aspirations", nameof(Dialog_GrowthMomentChoices.DoWindowContents))]
private static void PreDoWindowContents() => isDrawingDialog = true;

[MpCompatFinalizer("VAspirE.Dialog_GrowthMomentChoices_Aspirations", nameof(Dialog_GrowthMomentChoices.DoWindowContents))]
private static void PostDoWindowContents() => isDrawingDialog = false;

[MpCompatPrefix(typeof(LetterStack), nameof(LetterStack.RemoveLetter))]
private static bool DontRemoveChoiceLetter() => !MP.IsInMultiplayer || !isDrawingDialog;

#endregion
}

0 comments on commit 9ab9fd1

Please sign in to comment.