/// <summary> /// Moves the Duplicant from the cell to a destination cell, using a smooth transition /// if possible. /// </summary> /// <param name="instance">The fall monitor to update if successful.</param> /// <param name="destination">The destination cell.</param> /// <param name="navigator">The navigator to move.</param> private static void ForceMoveTo(FallMonitor.Instance instance, int destination, Navigator navigator, ref bool flipEmote) { var navType = navigator.CurrentNavType; var navGrid = navigator.NavGrid; int cell = Grid.PosToCell(navigator); bool moved = false; foreach (var transition in navGrid.transitions) if (transition.isEscape && navType == transition.start) { int possibleDest = transition.IsValid(cell, navGrid.NavTable); if (destination == possibleDest) { // The "nice" method, use their animation Grid.CellToXY(cell, out int startX, out _); Grid.CellToXY(destination, out int endX, out _); flipEmote = endX < startX; navigator.BeginTransition(transition); moved = true; break; } } if (!moved) { var transform = instance.transform; // Teleport to the new location transform.SetPosition(Grid.CellToPosCBC(destination, Grid.SceneLayer.Move)); navigator.Stop(false, true); if (instance.gameObject.HasTag(GameTags.Incapacitated)) navigator.SetCurrentNavType(NavType.Floor); instance.UpdateFalling(); instance.GoTo(instance.sm.standing); } }
/// <summary> /// Applied after MountPole runs. We do not apply damage there and reinitialise the variables for next time. /// </summary> /// <param name="__instance">The current FallMonitor.Instance.</param> /// <param name="__navigator">The current FallMonitor.Instance.navigator.</param> internal static void Postfix(FallMonitor.Instance __instance, Navigator ___navigator) { //First disable completly if FallDamageDifficulty == None if (DangerousWorldOptions.Instance.FallDamageDifficultyOption == FallDamageDifficulty.None) { return; } #if DEBUG PUtil.LogDebug(("MountPole: {0}").F(__instance.gameObject.name)); #endif if (__instance.gameObject.HasTag(GameTags.Minion)) { if (!dupesDict.ContainsKey(__instance.gameObject)) { #if DEBUG PUtil.LogError("Unknown dupe mountPole"); #endif dupesDict.Add(__instance.gameObject, -1); } dupesDict[__instance.gameObject] = -1; #if DEBUG PUtil.LogDebug(("Dupe: {0}, recoverd to Pole").F(__instance.gameObject.name)); #endif } }
/// <summary> /// Applied before TryEntombedEscape runs. /// </summary> internal static bool Prefix(FallMonitor.Instance __instance, Navigator ___navigator, ref bool ___flipRecoverEmote) { // This is not run too often so searching is fine bool moved = false; var layers = ___navigator?.transitionDriver?.overrideLayers; if (layers != null) foreach (var layer in layers) if (layer is LocationHistoryTransitionLayer lhs) { moved = TryClearEntombment(lhs, ___navigator, __instance, ref ___flipRecoverEmote); if (moved) break; } return !moved; }
/// <summary> /// Tries to move a Duplicant to a more sensible location when they are about to fall. /// </summary> /// <param name="instance">The fall monitor to update if successful.</param> /// <param name="navigator">The Duplicant to check.</param> /// <param name="layer">The location history of the Duplicant.</param> /// <returns>true if the Duplicant was successfully moved away from entombment, or /// false otherwise.</returns> private static bool TryEscapeFalling(LocationHistoryTransitionLayer layer, Navigator navigator, FallMonitor.Instance instance, ref bool flipEmote) { bool moved = false; for (int i = 0; i < LocationHistoryTransitionLayer.TRACK_CELLS; i++) { int last = layer.VisitedCells[i], above = Grid.CellAbove(last); #if DEBUG PUtil.LogDebug("{0} is falling, trying to move to {1:D}".F(navigator. gameObject?.name, last)); #endif if (Grid.IsValidCell(last) && IsValidNavCell(navigator, last)) { ForceMoveTo(instance, last, navigator, ref flipEmote); break; } } return moved; }
/// <summary> /// Tries to move a Duplicant to a more sensible location when entombed or falling. /// </summary> /// <param name="layer">The location history of the Duplicant.</param> /// <param name="navigator">The Duplicant to check.</param> /// <param name="instance">The fall monitor to update if successful.</param> /// <returns>true if the Duplicant was successfully moved away, or false otherwise.</returns> private static bool TryEscape(LocationHistoryTransitionLayer layer, Navigator navigator, FallMonitor.Instance instance, ref bool flipEmote) { bool moved = false; for (int i = 0; i < LocationHistoryTransitionLayer.TRACK_CELLS; i++) { int last = layer.VisitedCells[i]; if (Grid.IsValidCell(last) && IsValidNavCell(navigator, last)) { PUtil.LogDebug("{0} is in trouble, trying to escape to {1:D}".F(navigator. gameObject?.name, last)); ForceMoveTo(instance, last, navigator, ref flipEmote); // Prevents a loop back and forth between two cells in the history layer.Reset(); break; } } return(moved); }
/// <summary> /// Applied after AttemptInitialRecovery runs. Sets the variables depending on whether or not the dupe is falling. /// </summary> /// <param name="__instance">The current FallMonitor.Instance.</param> /// <param name="__navigator">The current FallMonitor.Instance.navigator.</param> internal static void Postfix(FallMonitor.Instance __instance, Navigator ___navigator) { //First disable completly if FallDamageDifficulty == None if (DangerousWorldOptions.Instance.FallDamageDifficultyOption == FallDamageDifficulty.None) { return; } if (__instance.gameObject.HasTag(GameTags.Minion)) { #if DEBUG PUtil.LogDebug(("AttemptRecovery: {0} state: {1}").F(__instance.gameObject.name, __instance.GetCurrentState().name)); #endif if (!dupesDict.ContainsKey(__instance.gameObject)) { dupesDict.Add(__instance.gameObject, -1); } if (__instance.GetCurrentState() == __instance.sm.falling_pre) { #if DEBUG PUtil.LogDebug(("AttemptRecovery: {0} is falling").F(__instance.gameObject.name)); #endif dupesDict[__instance.gameObject] = Grid.PosToCell((KMonoBehaviour)___navigator); #if DEBUG PUtil.LogDebug(("falling from: {0}").F(dupesDict[__instance.gameObject])); #endif } else { #if DEBUG PUtil.LogDebug(("AttemptRecovery: {0} recovered").F(__instance.gameObject.name)); #endif dupesDict[__instance.gameObject] = -1; } } }
/// <summary> /// Applied after TryEntombedEscape. Whether or not the dupe manages to escape, if he was falling we apply the same damage. /// </summary> /// <param name="__instance">The current FallMonitor.Instance.</param> /// <param name="__navigator">The current FallMonitor.Instance.navigator.</param> internal static void Postfix(FallMonitor.Instance __instance, Navigator ___navigator) { //First disable completly if FallDamageDifficulty == None if (DangerousWorldOptions.Instance.FallDamageDifficultyOption == FallDamageDifficulty.None) { return; } if (__instance.gameObject.HasTag(GameTags.Minion)) { if (!dupesDict.ContainsKey(__instance.gameObject)) { #if DEBUG PUtil.LogError("Unknown dupe tryEntombedEscape"); #endif dupesDict.Add(__instance.gameObject, -1); } #if DEBUG PUtil.LogDebug(("TryEntombedEscape. Dupe: {0}, falled to {1}").F(__instance.gameObject.name, Grid.PosToCell((KMonoBehaviour)___navigator))); #endif if (dupesDict[__instance.gameObject] >= 0) { Grid.CellToXY(dupesDict[__instance.gameObject], out int xFrom, out int yFrom); Grid.CellToXY(Grid.PosToCell((KMonoBehaviour)___navigator), out int xTo, out int yTo); int dist = yFrom - yTo; Health dupeHealth = __instance.gameObject.GetComponent <Health>(); if (dupeHealth != null) { if (dist > DangerousWorldOptions.Instance.fallOptions.damageHeightLimit) { #if DEBUG PUtil.LogDebug(("Fall damage. Difficulty: {0}, deathEnable: {1}, deathHeight: {2}").F(DangerousWorldOptions.Instance.FallDamageDifficultyOption.ToString(), DangerousWorldOptions.Instance.fallOptions.deathEnabled, DangerousWorldOptions.Instance.fallOptions.deathHeightLimit)); #endif if (DangerousWorldOptions.Instance.fallOptions.deathEnabled && dist > DangerousWorldOptions.Instance.fallOptions.deathHeightLimit) { __instance.gameObject.GetSMI <DeathMonitor.Instance>().Kill(Db.Get().Deaths.Slain); //TODO: use own death type } else { dupeHealth.Damage((dist - DangerousWorldOptions.Instance.fallOptions.damageHeightLimit) * 10f / DangerousWorldOptions.Instance.fallOptions.damageDivider); //TODO: make it use own death type if (DangerousWorldOptions.Instance.fallOptions.crippleEnabled && (dist > DangerousWorldOptions.Instance.fallOptions.crippleHeightLimit) && (UnityEngine.Random.Range((int)0, (int)100) <= dist * 10)) { #if DEBUG PUtil.LogDebug(("Dupe: {0}, hasEffect Crippled: {1}").F(__instance.gameObject.name, __instance.gameObject.AddOrGet <Effects>().HasEffect("DangerousWorlCrippled"))); #endif if (!__instance.gameObject.AddOrGet <Effects>().HasEffect("DangerousWorldCrippled")) { __instance.gameObject.AddOrGet <Effects>().Add("DangerousWorldCrippled", true); } #if DEBUG PUtil.LogDebug(("Dupe: {0}, hasEffect Crippled: {1}").F(__instance.gameObject.name, __instance.gameObject.AddOrGet <Effects>().HasEffect("DangerousWorlCrippled"))); #endif } } } } dupesDict[__instance.gameObject] = -1; } } }