diff --git a/src/Core/Interpolation/DialogueInterpolator.cs b/src/Core/Interpolation/DialogueInterpolator.cs index 07c2da2c..aff385b0 100644 --- a/src/Core/Interpolation/DialogueInterpolator.cs +++ b/src/Core/Interpolation/DialogueInterpolator.cs @@ -205,6 +205,15 @@ private string InterpolatePlayerLances(InterpolateType interpolateType, CastDef // Continue with interpolation if (unitKey.StartsWith(DialogueInterpolationConstants.TeamPilot_Random)) { + // Rebind if the referenced pilot is dead or ejected, keep trying until a live one is found + int textRebindAttempts = 0; + int maxTextRebindAttempts = MissionControl.Instance.CurrentContract.Lances.GetLanceUnits(TeamUtils.GetTeamGuid("Player1")).Length + 1; + while (unit != null && unit.IsDead && PilotCastInterpolator.Instance.BoundAbstractActors.ContainsKey(unitKey) && textRebindAttempts < maxTextRebindAttempts) { + textRebindAttempts++; + PilotCastInterpolator.Instance.RebindDeadUnit(unitKey); + unit = GetBoundUnit(unitKey); + } + if (unitDataKey == "DisplayName") { if (PilotCastInterpolator.Instance.DynamicCastDefs.ContainsKey(unitKey)) { string castDefId = PilotCastInterpolator.Instance.DynamicCastDefs[unitKey]; @@ -667,7 +676,10 @@ public void HandleDeadActorFromDialogueContent(ref CastDef castDef) { // Handle bound units if (bindingKey != null) { AbstractActor unit = DialogueInterpolator.Instance.GetBoundUnit(bindingKey); - while (unit != null && unit.IsDead) { + int rebindAttempts = 0; + int maxRebindAttempts = MissionControl.Instance.CurrentContract.Lances.GetLanceUnits(TeamUtils.GetTeamGuid("Player1")).Length + 1; + while (unit != null && unit.IsDead && rebindAttempts < maxRebindAttempts) { + rebindAttempts++; string reboundCastDefID = PilotCastInterpolator.Instance.RebindDeadUnit(bindingKey); Main.LogDebug($"[Interpolate.HandleDeadActorFromDialogueContent] Unit '{unit.UnitName} with pilot '{unit.GetPilot().Name}' is dead (or ejected). Rebinding all castdefs and references for unit key '{reboundCastDefID}'"); CastDef reboundCastDef = UnityGameInstance.Instance.Game.DataManager.CastDefs.Get(reboundCastDefID); @@ -675,13 +687,27 @@ public void HandleDeadActorFromDialogueContent(ref CastDef castDef) { castDef = reboundCastDef; unit = GetBoundUnit(bindingKey == DialogueInterpolationConstants.Commander ? DialogueInterpolationConstants.Darius : bindingKey); } + + if (rebindAttempts >= maxRebindAttempts) { + Main.Logger.LogWarning($"[HandleDeadActorFromDialogueContent] Exhausted rebind attempts for '{bindingKey}'. Falling back to Darius."); + castDef = RuntimeCastFactory.GetCastDef(CustomCastDef.castDef_Darius); + } } else { // Attempt to see if a unit exists against the castDef pilot (PureRandom units) AbstractActor unit = GetSpeakerUnit(RuntimeCastFactory.GetPilotDefIDFromCastDefID(castDef.id)); - while (unit != null && unit.IsDead) { - Contract contract = MissionControl.Instance.CurrentContract; - SpawnableUnit[] lanceConfigUnits = contract.Lances.GetLanceUnits(TeamUtils.GetTeamGuid("Player1")); - int randomPosition = UnityEngine.Random.Range(0, lanceConfigUnits.Length); - string pilotDefID = lanceConfigUnits[randomPosition].PilotId; + Contract pureRandomContract = MissionControl.Instance.CurrentContract; + SpawnableUnit[] pureRandomLanceConfigUnits = pureRandomContract.Lances.GetLanceUnits(TeamUtils.GetTeamGuid("Player1")); + + // Shuffle positions to sample without replacement — guarantees finding a survivor if one exists + List shuffledPositions = Enumerable.Range(0, pureRandomLanceConfigUnits.Length).ToList(); + for (int i = shuffledPositions.Count - 1; i > 0; i--) { + int j = UnityEngine.Random.Range(0, i + 1); + (shuffledPositions[i], shuffledPositions[j]) = (shuffledPositions[j], shuffledPositions[i]); + } + + int positionIndex = 0; + while (unit != null && unit.IsDead && positionIndex < shuffledPositions.Count) { + string pilotDefID = pureRandomLanceConfigUnits[shuffledPositions[positionIndex]].PilotId; + positionIndex++; string pilotCastDefID = RuntimeCastFactory.GetCastDefIDFromPilotDefID(pilotDefID); CastDef updatedCastDef = RuntimeCastFactory.GetCastDef(pilotCastDefID); @@ -706,6 +732,11 @@ public void HandleDeadActorFromDialogueContent(ref CastDef castDef) { unit = null; } } + + if (positionIndex >= shuffledPositions.Count && unit != null && unit.IsDead) { + Main.Logger.LogWarning($"[HandleDeadActorFromDialogueContent] Exhausted PureRandom rebind attempts. Falling back to Darius."); + castDef = RuntimeCastFactory.GetCastDef(CustomCastDef.castDef_Darius); + } } } } diff --git a/src/Core/Interpolation/PilotCastInterpolator.cs b/src/Core/Interpolation/PilotCastInterpolator.cs index b99518ca..2efba14d 100644 --- a/src/Core/Interpolation/PilotCastInterpolator.cs +++ b/src/Core/Interpolation/PilotCastInterpolator.cs @@ -90,6 +90,7 @@ private string HandleFallback(int pilotPosition, string selectedCastDefID) { if (IsBindableRandomCastDefID(selectedCastDefID)) { string bindingKey = GetBindingKey(selectedCastDefID); PilotCastInterpolator.Instance.DynamicCastDefs[bindingKey] = fallbackCastDefID; + PilotCastInterpolator.Instance.BoundAbstractActorsFullIndex.Remove(bindingKey); } return fallbackCastDefID; @@ -170,9 +171,27 @@ private void BindAbstractActorToBindingKey() { List units = lance.GetLanceUnits(); foreach (KeyValuePair entry in BoundAbstractActorsFullIndex) { - AbstractActor actor = units[entry.Value - 1]; - // Main.LogDebug($"[PilotCastInterpolator.BindAbstractActorToBindingKey] Binding AbstractActor '{actor.UnitName}' with pilot '{actor.GetPilot().Name}' using '{entry.Key}:{entry.Value - 1}'"); - BoundAbstractActors[entry.Key] = actor; + AbstractActor matchedActor = null; + + // Primary: match by pilot identity (robust against other mods injecting temporary actors into the lance) + if (DynamicCastDefs.TryGetValue(entry.Key, out string castDefId)) { + string pilotDefId = RuntimeCastFactory.GetPilotDefIDFromCastDefID(castDefId); + matchedActor = units.FirstOrDefault(u => + u.GetPilot().pilotDef.Description.Id.ToUpperFirst() == pilotDefId.ToUpperFirst()); + } + + // Fallback: index-based with bounds checking + if (matchedActor == null) { + int index = entry.Value - 1; + if (index >= 0 && index < units.Count) { + matchedActor = units[index]; + } + } + + if (matchedActor != null) { + // Main.LogDebug($"[PilotCastInterpolator.BindAbstractActorToBindingKey] Binding AbstractActor '{matchedActor.UnitName}' with pilot '{matchedActor.GetPilot().Name}' using '{entry.Key}'"); + BoundAbstractActors[entry.Key] = matchedActor; + } } // Bind the commander if they are in combat @@ -209,7 +228,7 @@ private string RebindDeadUnitCastDef(string bindKey) { int pilotPosition = FindNonUsedPilotPosition("Player1", lanceConfigUnits); if (!IsPilotPositionValid(pilotPosition)) { - return HandleFallback(pilotPosition, bindKey); + return HandleFallback(pilotPosition, GetDynamicCastDefIDFromBindKey(bindKey)); } return BindCastDefAndActorIndex(pilotPosition, GetDynamicCastDefIDFromBindKey(bindKey), lanceConfigUnits, fullLanceConfigUnits);