From 0513c3a6c249614790e3636990d5dcb48d4f938c Mon Sep 17 00:00:00 2001 From: roman_gr Date: Wed, 12 Aug 2026 22:34:32 +0200 Subject: [PATCH] Seed quest name generation so retries cost one synced RNG draw MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Quest naming retries while the generated name collides with an existing quest's name, and NameGenerator's GrammarRequest overload retries while the validator rejects the text. Both retry counts depend on the generated strings and therefore on the client's language — the Russian rule packs collide constantly where English rarely does. With mixed-language players the synced Rand streams diverge and quest sites land on different tiles. Wrap QuestNode_ResolveQuestName.Resolve and the GrammarRequest overload of NameGenerator.GenerateName in SeedGrammar so the whole retry loop costs exactly one synced draw regardless of language. --- Source/Client/Patches/Seeds.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Source/Client/Patches/Seeds.cs b/Source/Client/Patches/Seeds.cs index 31e4368a..abdaed99 100644 --- a/Source/Client/Patches/Seeds.cs +++ b/Source/Client/Patches/Seeds.cs @@ -6,6 +6,7 @@ using System.Reflection; using System.Reflection.Emit; using Multiplayer.Client.Util; +using RimWorld.QuestGen; using Verse; using Verse.Grammar; @@ -168,6 +169,14 @@ static IEnumerable TargetMethods() yield return AccessTools.Method(typeof(GrammarResolver), nameof(GrammarResolver.Resolve)); yield return AccessTools.Method(typeof(PawnBioAndNameGenerator), nameof(PawnBioAndNameGenerator.GeneratePawnName)); yield return AccessTools.Method(typeof(NameGenerator), nameof(NameGenerator.GenerateName), [typeof(RulePackDef), typeof(Predicate), typeof(bool), typeof(string), typeof(string), typeof(List)]); + // The GrammarRequest overload holds the validator retry loop and is called directly + // (bypassing the RulePackDef overload above) by quest naming among others; a validator + // rejection count depends on the generated text and therefore on the client's language. + yield return AccessTools.Method(typeof(NameGenerator), nameof(NameGenerator.GenerateName), [typeof(GrammarRequest), typeof(Predicate), typeof(bool), typeof(string), typeof(string)]); + // Quest naming additionally retries whole NameGenerator calls while the name collides + // with an existing quest's name — wrap the outer loop too so quest naming costs exactly + // one synced draw regardless of language or collisions. + yield return AccessTools.Method(typeof(QuestNode_ResolveQuestName), nameof(QuestNode_ResolveQuestName.Resolve)); } [HarmonyPriority(MpPriority.MpFirst)]