public static bool Prefix(Character __instance, Vector3 _direction) { var self = __instance; // only use this hook for local players. return orig everything else if (self.IsAI || !self.IsPhotonPlayerLocal) { return(true); } float staminaCost = (float)CombatOverhaul.config.GetValue(Settings.Custom_Dodge_Cost); // if dodge cancelling is NOT enabled, just do a normal dodge check. if (!(bool)CombatOverhaul.config.GetValue(Settings.Dodge_Cancelling)) { if (At.GetField(self, "m_currentlyChargingAttack") is bool m_currentlyChargingAttack && At.GetField(self, "m_preparingToSleep") is bool m_preparingToSleep && At.GetField(self, "m_nextIsLocomotion") is bool m_nextIsLocomotion && At.GetField(self, "m_dodgeAllowedInAction") is int m_dodgeAllowedInAction) { if (self.Stats.MovementSpeed > 0f && !m_preparingToSleep && (!self.LocomotionAction || m_currentlyChargingAttack) && (m_nextIsLocomotion || m_dodgeAllowedInAction > 0)) { if (!self.Dodging) { Instance.SendDodge(self, staminaCost, _direction); } return(false); } } } else // cancelling enabled. check if we should allow the dodge { if (Instance.PlayerLastHitTimes.ContainsKey(self.UID) && Time.time - Instance.PlayerLastHitTimes[self.UID] < (float)CombatOverhaul.config.GetValue(Settings.Dodge_DelayAfterHit)) { // Debug.Log("Player has hit within the last few seconds. Dodge not allowed!"); return(false); } Character.HurtType hurtType = (Character.HurtType)At.GetField(self, "m_hurtType"); // manual fix (game sometimes does not reset HurtType to NONE when animation ends. float timeout = (float)CombatOverhaul.config.GetValue(Settings.Dodge_DelayAfterStagger); if (hurtType == Character.HurtType.Knockdown) { timeout = (float)CombatOverhaul.config.GetValue(Settings.Dodge_DelayAfterKD); } if ((float)At.GetField(self, "m_timeOfLastStabilityHit") is float lasthit && Time.time - lasthit > timeout) { hurtType = Character.HurtType.NONE; At.SetField(self, "m_hurtType", hurtType); } // if we're not currently dodging or staggered, force an animation cancel dodge (provided we have enough stamina). if (!self.Dodging && hurtType == Character.HurtType.NONE) { Instance.SendDodge(self, staminaCost, _direction); } // send a fix to force m_dodging to false after a short delay. // this is a fix for if the player dodges while airborne, the game wont reset their m_dodging to true when they land. Instance.StartCoroutine(Instance.DodgeLateFix(self)); } return(false); }
// actual function to set an enemy's stats private void SetEnemyMods(ModConfig _config, CharacterStats _stats, Character m_character) { if (m_character == null || !m_character.IsAI || m_character.Faction == Character.Factions.Player) { //Debug.Log("trying to set stats for a null character, or a non-AI character"); return; } if (FixedEnemies.Contains(m_character.UID)) { // Debug.Log("Fixed enemies already contains " + m_character.Name); return; } var m_staminaUseModiifers = new Stat(1f); m_staminaUseModiifers.AddMultiplierStack(new StatStack("MyStat", -0.9f)); if ((bool)_config.GetValue(Settings.Enemy_Balancing)) { string stackSource = "CombatOverhaul"; if (!PhotonNetwork.isNonMasterClientInRoom) { // set health modifier var healthTag = TagSourceManager.Instance.GetTag("77"); // 77 = max health var healthStack = new StatStack(stackSource, (float)_config.GetValue(Settings.Enemy_Health) - 1); _stats.RemoveStatStack(healthTag, stackSource, true); _stats.AddStatStack(healthTag, healthStack, true); At.SetValue(_stats.CurrentHealth * (float)_config.GetValue(Settings.Enemy_Health), typeof(CharacterStats), _stats, "m_health"); } // set impact resistance var impactTag = TagSourceManager.Instance.GetTag("84"); // 84 = impact res _stats.RemoveStatStack(impactTag, stackSource, false); var impactStack = new StatStack(stackSource, (float)_config.GetValue(Settings.Enemy_ImpactRes)); _stats.AddStatStack(impactTag, impactStack, false); // damage bonus var damageTag = TagSourceManager.Instance.GetTag("96"); // 96 = all damage bonus _stats.RemoveStatStack(damageTag, stackSource, true); var damageStack = new StatStack(stackSource, (float)_config.GetValue(Settings.Enemy_Damages) * 0.01f); _stats.AddStatStack(damageTag, damageStack, true); // impact modifier var impactModifier = At.GetValue(typeof(CharacterStats), _stats, "m_impactModifier") as Stat; impactModifier.RemoveStack(stackSource, true); impactModifier.AddStack(new StatStack(stackSource, (float)_config.GetValue(Settings.Enemy_ImpactDmg) * 0.01f), true); for (int i = 0; i < 6; i++) { // damage resistance (Capped at 99, unless already 100) float currentRes = m_character.Stats.GetDamageResistance((DamageType.Types)i); if (currentRes < 100) { var valueToSet = (float)_config.GetValue(Settings.Enemy_Resistances); if (currentRes + valueToSet >= 99) { valueToSet = 99 - currentRes; } int tag = 113 + i; // 113 to 118 = damage resistance stats var damageResTag = TagSourceManager.Instance.GetTag(tag.ToString()); _stats.RemoveStatStack(damageResTag, stackSource, true); var resStack = new StatStack(stackSource, valueToSet); _stats.AddStatStack(damageResTag, resStack, false); } } } if ((bool)_config.GetValue(Settings.All_Enemies_Allied)) { m_character.ChangeFaction(Character.Factions.Bandits); m_character.TargetingSystem.AlliedToSameFaction = true; Character.Factions[] targets = new Character.Factions[] { Character.Factions.Player }; At.SetValue(targets, typeof(TargetingSystem), m_character.TargetingSystem, "TargetableFactions"); // fix skills foreach (var uid in m_character.Inventory.SkillKnowledge.GetLearnedActiveSkillUIDs()) { if (ItemManager.Instance.GetItem(uid) is Skill skill) { foreach (Shooter shooter in skill.GetComponentsInChildren <Shooter>()) { shooter.Setup(targets, shooter.transform.parent); } } } } FixedEnemies.Add(m_character.UID); }
// called from RPC manager public void SetSyncInfo(bool modsEnabled, bool enemiesAllied, bool customStats, float healthModifier, float damageModifier, float impactRes, float damageRes, float impactDmg) { m_currentHostUID = CharacterManager.Instance.GetWorldHostCharacter()?.UID; //Debug.Log("Received sync from host uid: " + m_currentHostUID); this.m_currentSyncInfos = new ModConfig { ModName = "CombatOverhaul_Sync", SettingsVersion = 1.0, Settings = new List <BBSetting> { new BoolSetting { Name = Settings.All_Enemies_Allied, m_value = enemiesAllied, DefaultValue = false, }, new BoolSetting { Name = Settings.Enemy_Balancing, m_value = customStats }, new FloatSetting { Name = Settings.Enemy_Health, m_value = healthModifier }, new FloatSetting { Name = Settings.Enemy_Damages, m_value = damageModifier }, new FloatSetting { Name = Settings.Enemy_Resistances, m_value = damageRes }, new FloatSetting { Name = Settings.Enemy_ImpactRes, m_value = impactRes }, new FloatSetting { Name = Settings.Enemy_ImpactDmg, m_value = impactDmg }, } }; // manually fix the settings dictionary since we are not using ModConfig.Register() var dict = new Dictionary <string, BBSetting>(); foreach (var setting in m_currentSyncInfos.Settings) { dict.Add(setting.Name, setting); } At.SetValue(dict, typeof(ModConfig), m_currentSyncInfos, "m_Settings"); if (modsEnabled) { //Debug.Log("Updating all current characters"); foreach (Character c in CharacterManager.Instance.Characters.Values.Where(x => x.IsAI)) { SetEnemyMods(m_currentSyncInfos, c.Stats, c); } } }
public static bool Prefix(Character __instance, float _knockValue, float _angle, bool _block, Character _dealerChar) { var self = __instance; var _base = self as Photon.MonoBehaviour; if (At.GetValue(typeof(Character), self, "m_impactImmune") is bool m_impactImmune && At.GetValue(typeof(Character), self, "m_shieldStability") is float m_shieldStability && At.GetValue(typeof(Character), self, "m_stability") is float m_stability && At.GetValue(typeof(Character), self, "m_knockbackCount") is float m_knockbackCount && At.GetValue(typeof(Character), self, "m_knockHurtAllowed") is bool m_knockHurtAllowed && At.GetValue(typeof(Character), self, "m_currentlyChargingAttack") is bool m_currentlyChargingAttack && At.GetValue(typeof(Character), self, "m_animator") is Animator m_animator) { // Begin actual stability hit function var hit = _knockValue; if (hit < 0) { hit = 0; } if (!m_impactImmune && hit > 0f) { //Debug.Log("--------- " + self.Name + " ---------"); // check stagger immunity dictionary (custom) float lastStagger = -1; if (Instance.LastStaggerTimes.ContainsKey(self.UID)) { lastStagger = Instance.LastStaggerTimes[self.UID]; } // if you run out of stamina and get hit, you will always get staggered. (unchanged, except to reflect custom stagger threshold) if (self.Stats.CurrentStamina < 1f) { float hitToStagger = m_shieldStability + m_stability - (100 - (float)CombatOverhaul.config.GetValue(Settings.Stagger_Threshold)); if (hit < hitToStagger) { hit = hitToStagger; } //Debug.LogError("Stamina autostagger called! hitToStagger: " + hitToStagger + ", hit: " + hit); } At.SetValue(Time.time, typeof(Character), self, "m_timeOfLastStabilityHit"); // Debug.Log("Set " + Time.time + " as character's last stability hit"); if (self.CharacterCamera != null && hit > 0f) { self.CharacterCamera.Hit(hit * 6f); } // check shield stability if blocking (unchanged) if (_block && m_shieldStability > 0f) { if (hit > m_shieldStability) { var num2 = m_stability - (hit - m_shieldStability); At.SetValue(num2, typeof(Character), self, "m_stability"); m_stability = num2; } var num3 = Mathf.Clamp(m_shieldStability - hit, 0f, 50f); At.SetValue(num3, typeof(Character), self, "m_shieldStability"); m_shieldStability = num3; } // check non-blocking stability (unchanged) else { var num2 = Mathf.Clamp(m_stability - hit, 0f, 100f); At.SetValue(num2, typeof(Character), self, "m_stability"); m_stability = num2; } // if hit takes us below knockdown threshold, or if AI auto-knockdown stagger count was reached... if (m_stability <= (float)CombatOverhaul.config.GetValue(Settings.Knockdown_Threshold) || (self.IsAI && m_knockbackCount >= (float)CombatOverhaul.config.GetValue(Settings.Enemy_AutoKD_Count))) { //Debug.LogError("Knockdown! Hit Value: " + _knockValue + ", current stability: " + m_stability); if ((!self.IsAI && _base.photonView.isMine) || (self.IsAI && (_dealerChar == null || _dealerChar.photonView.isMine))) { _base.photonView.RPC("SendKnock", PhotonTargets.All, new object[] { true, m_stability }); } else { At.Call(self, "Knock", new object[] { true }); } At.SetValue(0f, typeof(Character), self, "m_stability"); m_stability = 0f; if (self.IsPhotonPlayerLocal) { self.BlockInput(false); } } // else if hit is a stagger... else if (m_stability <= (float)CombatOverhaul.config.GetValue(Settings.Stagger_Threshold) && (Time.time - lastStagger > (float)CombatOverhaul.config.GetValue(Settings.Stagger_Immunity_Period))) { // Debug.LogWarning("Stagger! Hit Value: " + _knockValue + ", current stability: " + m_stability); // update Stagger Immunity dictionary if (!Instance.LastStaggerTimes.ContainsKey(self.UID)) { Instance.LastStaggerTimes.Add(self.UID, Time.time); } else { Instance.LastStaggerTimes[self.UID] = Time.time; } if ((!self.IsAI && _base.photonView.isMine) || (self.IsAI && (_dealerChar == null || _dealerChar.photonView.isMine))) { _base.photonView.RPC("SendKnock", PhotonTargets.All, new object[] { false, m_stability }); } else { At.Call(self, "Knock", new object[] { false }); } if (self.IsPhotonPlayerLocal && _block) { self.BlockInput(false); } } // else if we are not blocking... else if (!_block) { // Debug.Log("Value: " + _knockValue + ", new stability: " + m_stability); if (m_knockHurtAllowed) { At.SetValue(Character.HurtType.Hurt, typeof(Character), self, "m_hurtType"); if (m_currentlyChargingAttack) { self.CancelCharging(); } m_animator.SetTrigger("Knockhurt"); _base.StopCoroutine("KnockhurtRoutine"); MethodInfo _knockhurtRoutine = self.GetType().GetMethod("KnockhurtRoutine", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); IEnumerator _knockEnum = (IEnumerator)_knockhurtRoutine.Invoke(self, new object[] { hit }); _base.StartCoroutine(_knockEnum); } if (m_stability <= (float)CombatOverhaul.config.GetValue(Settings.Stagger_Immunity_Period)) { // Debug.LogError(self.Name + " would have staggered. Current delta: " + (Time.time - lastStagger)); } } else // hit was blocked and no stagger { Instance.StaggerAttacker(self, m_animator, _dealerChar); } m_animator.SetInteger("KnockAngle", (int)_angle); self.StabilityHitCall?.Invoke(); } else if (!m_impactImmune && _block) // hit dealt 0 impact and was blocked { Instance.StaggerAttacker(self, m_animator, _dealerChar); } } return(false); }
public static void Postfix(Character __instance, Vector3 _direction, bool ___m_pendingDeath, ref int ___m_dodgeAllowedInAction) { if (!CombatTweaksMod.Dodge_Cancelling.Value) { return; } if (!__instance.IsPhotonPlayerLocal || __instance.IsAI || __instance.Dodging) { return; } if (___m_pendingDeath) { return; } // check player has enough stamina if (!(bool)At.Invoke(__instance, "HasEnoughStamina", (float)__instance.DodgeStamCost)) { return; } if (PlayerLastHitTimes.ContainsKey(__instance.UID) && Time.time - PlayerLastHitTimes[__instance.UID] < CombatTweaksMod.Dodge_DelayAfterPlayerHits.Value) { // Debug.Log("Player has hit within the last few seconds. Dodge not allowed!"); return; } Character.HurtType hurtType = (Character.HurtType)At.GetField(__instance, "m_hurtType"); // manual fix (game sometimes does not reset HurtType to NONE when animation ends. float timeout; if (hurtType == Character.HurtType.Knockdown) { timeout = CombatTweaksMod.Dodge_DelayAfterKnockdown.Value; } else { timeout = CombatTweaksMod.Dodge_DelayAfterStagger.Value; } if ((float)At.GetField(__instance, "m_timeOfLastStabilityHit") is float lasthit && Time.time - lasthit > timeout) { hurtType = Character.HurtType.NONE; At.SetField(__instance, "m_hurtType", hurtType); } // if we're not currently staggered, force an animation cancel dodge (provided we have enough stamina). if (hurtType == Character.HurtType.NONE) { //SendDodge(__instance, __instance.DodgeStamCost, _direction); __instance.Stats.UseStamina(TagSourceManager.Dodge, __instance.DodgeStamCost); ___m_dodgeAllowedInAction = 0; if (__instance.CharacterCamera && __instance.CharacterCamera.InZoomMode) { __instance.SetZoomMode(false); } __instance.ForceCancel(false, true); __instance.ResetCastType(); __instance.photonView.RPC("SendDodgeTriggerTrivial", PhotonTargets.All, new object[] { _direction }); At.Invoke(__instance, "ActionPerformed", true); __instance.Invoke("ResetDodgeTrigger", 0.5f); } // send a fix to force m_dodging to false after a short delay. // this is a fix for if the player dodges while airborne, the game wont reset their m_dodging to true when they land. CombatTweaksMod.Instance.StartCoroutine(DodgeLateFix(__instance)); }
public static bool Prefix(Character __instance) { var self = __instance; if (At.GetValue(typeof(Character), self, "m_stability") is float m_stability && At.GetValue(typeof(Character), self, "m_timeOfLastStabilityHit") is float m_timeOfLastStabilityHit && At.GetValue(typeof(Character), self, "m_shieldStability") is float m_shieldStability && At.GetValue(typeof(Character), self, "m_knockbackCount") is float m_knockbackCount) { String m_nameLocKey = (String)At.GetValue(typeof(Character), self, "m_nameLocKey"); String m_name = (String)At.GetValue(typeof(Character), self, "m_name"); // ----------- original method, unchanged other than to reflect custom values ------------- if ((bool)CombatOverhaul.config.GetValue(Settings.No_Stability_Regen_When_Blocking) && self.Blocking) // no stability regen while blocking! otherwise too op { return(false); } float num = Time.time - m_timeOfLastStabilityHit; if (num > (float)CombatOverhaul.config.GetValue(Settings.Stability_Regen_Delay)) { if (m_stability < 100f && !(bool)CombatOverhaul.config.GetValue(Settings.Poise)) { var num2 = Mathf.Clamp(m_stability + (self.StabilityRegen * (float)CombatOverhaul.config.GetValue(Settings.Stability_Regen_Speed)) * Time.deltaTime, 0f, 100f); At.SetValue(num2, typeof(Character), self, "m_stability"); } else if (m_shieldStability < 50f) { var num2 = Mathf.Clamp(m_shieldStability + self.StabilityRegen * Time.deltaTime, 0f, 50f); At.SetValue(num2, typeof(Character), self, "m_shieldStability"); } if (num > (float)CombatOverhaul.config.GetValue(Settings.Enemy_AutoKD_Reset_Time)) { bool flag = m_knockbackCount > 0; var num2 = Mathf.Clamp(m_knockbackCount - Time.deltaTime, 0f, 4f); if ((bool)CombatOverhaul.config.GetValue(Settings.BossPoise)) { if ((int)EnemyClass.getEnemyLevel(self) < (int)EnemyLevel.MINIBOSS) { At.SetValue(num2, typeof(Character), self, "m_knockbackCount"); } } else { At.SetValue(num2, typeof(Character), self, "m_knockbackCount"); } if (flag && num2 <= 0) { // Debug.Log("Resetting AI stagger count for " + self.Name); } } } if ((bool)CombatOverhaul.config.GetValue(Settings.Poise)) { if (num > (float)CombatOverhaul.config.GetValue(Settings.PoiseResetTime)) { bool resetStability = false; if ((bool)CombatOverhaul.config.GetValue(Settings.BossPoise)) { //Minibosses do NOT regenerate stamina. if ((int)EnemyClass.getEnemyLevel(self) < (int)EnemyLevel.MINIBOSS) { resetStability = true; } } else { resetStability = true; } //Initially for readability, allowing display of stability hurt on a stagger actually meant (even badly) coordinated strikes knocked down too much. if (resetStability /*&& num > 0.25*/) { At.SetValue(100f, typeof(Character), self, "m_stability"); } } } } return(false); }