private IEnumerator StopBlockingCoroutine(Character character) { yield return(new WaitForSeconds(0.05f)); // 50ms wait (1 or 2 frames) At.Invoke(character, "StopBlocking"); At.SetField(character, "m_blockDesired", false); }
private void SendDodge(Character self, float staminaCost, Vector3 _direction) { float f = (float)At.GetField(self.Stats, "m_stamina"); if (f >= staminaCost) { //At.SetValue(f - staminaCost, typeof(CharacterStats), self.Stats, "m_stamina"); self.Stats.UseStamina(TagSourceManager.Dodge, staminaCost); At.SetField(self, "m_dodgeAllowedInAction", 0); if (self.CharacterCamera && self.CharacterCamera.InZoomMode) { self.SetZoomMode(false); } self.ForceCancel(false, true); self.ResetCastType(); self.photonView.RPC("SendDodgeTriggerTrivial", PhotonTargets.All, new object[] { _direction }); At.Invoke(self, "ActionPerformed", false); self.Invoke("ResetDodgeTrigger", 0.5f); } }
private IEnumerator DodgeLateFix(Character character) { yield return(new WaitForSeconds(0.25f)); while (!character.NextIsLocomotion) { yield return(null); } At.SetField(character, "m_dodging", false); }
public static bool Prefix(Character __instance, int _type, int _id = 0) { var self = __instance; if (self.IsLocalPlayer && (bool)CombatOverhaul.config.GetValue(Settings.Attack_Cancels_Blocking) && !self.IsAI && self.Blocking) { Instance.StartCoroutine(Instance.StopBlockingCoroutine(self)); At.Invoke(self, "StopBlocking"); At.SetField(self, "m_blockDesired", false); } return(true); }
public static bool Prefix(Character __instance, int _type, int _id = 0) { var self = __instance; if (self.IsLocalPlayer && CombatTweaksMod.Blocking_CancelledByAttack.Value && !self.IsAI && self.Blocking) { CombatTweaksMod.Instance.StartCoroutine(StopBlockingCoroutine(self)); At.Invoke(self, "StopBlocking"); At.SetField(self, "m_blockDesired", false); } return(true); }
public static bool Prefix(CharacterStats __instance) { var self = __instance; if (At.GetField(self, "m_timeOfLastStamUse") is float timeOfLast && Time.time - timeOfLast > (float)CombatOverhaul.config.GetValue(Settings.Stamina_Regen_Delay) && At.GetField(self, "m_stamina") is float m_stamina && At.GetField(self, "m_character") is Character character && !character.Blocking) { float regen = (float)CombatOverhaul.config.GetValue(Settings.Extra_Stamina_Regen) * Time.deltaTime; float newStamina = Mathf.Clamp(m_stamina + regen, 0, self.ActiveMaxStamina); At.SetField(self, "m_stamina", newStamina); } return(true); }
public static bool Prefix(Character __instance, Vector3 _direction) { var self = __instance; if (!(bool)CombatOverhaul.config.GetValue(Settings.Custom_Bag_Burden)) { return(true); } if (self.CurrentWeapon) { if (self.HasDodgeDirection) { self.Animator.SetFloat("DodgeBlend", !self.DodgeRestricted ? 0.0f : Instance.GetDodgeRestriction(self)); } } self.Animator.SetTrigger("Dodge"); if (self.CurrentlyChargingAttack) { //self.SendCancelCharging(); At.Invoke(self, "SendCancelCharging"); } // get sound player with null coalescing operator (At.GetField(self, "m_dodgeSoundPlayer") as SoundPlayer)?.Play(false); //self.m_dodging = true; At.SetField(self, "m_dodging", true); //self.StopBlocking(); At.Invoke(self, "StopBlocking"); // null coalescing OnDodgeEvent invoke self.OnDodgeEvent?.Invoke(); if (At.GetField(self, "m_characterSoundManager") is CharacterSoundManager charSounds) { Global.AudioManager.PlaySoundAtPosition(charSounds.GetDodgeSound(), self.transform, 0f, 1f, 1f, 1f, 1f); } self.SendMessage("DodgeTrigger", _direction, SendMessageOptions.DontRequireReceiver); return(false); }
private void StaggerAttacker(Character self, Animator m_animator, Character _dealerChar) { // Debug.Log(self.Name + " blocked the attack. Shield stability: " + m_shieldStability); At.SetField(self, "m_hurtType", Character.HurtType.NONE); if (self.InLocomotion) { m_animator.SetTrigger("BlockHit"); } if ((bool)CombatOverhaul.config.GetValue(Settings.Blocking_Staggers_Attacker)) { // Debug.Log("autoknocking " + _dealerChar.Name); if (_dealerChar.CurrentWeapon.Type != Weapon.WeaponType.Bow) { _dealerChar.AutoKnock(false, new Vector3(0, 0, 0)); } } }
public static bool Prefix(Character __instance) { var self = __instance; if (At.GetField(self, "m_stability") is float m_stability && At.GetField(self, "m_timeOfLastStabilityHit") is float m_timeOfLastStabilityHit && At.GetField(self, "m_shieldStability") is float m_shieldStability && At.GetField(self, "m_knockbackCount") is float m_knockbackCount) { // ----------- 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) { var num2 = Mathf.Clamp(m_stability + (self.StabilityRegen * (float)CombatOverhaul.config.GetValue(Settings.Stability_Regen_Speed)) * Time.deltaTime, 0f, 100f); At.SetField(self, "m_stability", num2); } else if (m_shieldStability < 50f) { var num2 = Mathf.Clamp(m_shieldStability + self.StabilityRegen * Time.deltaTime, 0f, 50f); At.SetField(self, "m_shieldStability", num2); } 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); At.SetField(self, "m_knockbackCount", num2); if (flag && num2 <= 0) { // Debug.Log("Resetting AI stagger count for " + self.Name); } } } } return(false); }
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); }
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, float _knockValue, float _angle, bool _block, Character _dealerChar) { var self = __instance; var _base = self as Photon.MonoBehaviour; if (At.GetField(self, "m_impactImmune") is bool m_impactImmune && At.GetField(self, "m_shieldStability") is float m_shieldStability && At.GetField(self, "m_stability") is float m_stability && At.GetField(self, "m_knockbackCount") is float m_knockbackCount && At.GetField(self, "m_knockHurtAllowed") is bool m_knockHurtAllowed && At.GetField(self, "m_currentlyChargingAttack") is bool m_currentlyChargingAttack && At.GetField(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.SetField(self, "m_timeOfLastStabilityHit", Time.time); // 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.SetField(self, "m_stability", num2); m_stability = num2; } var num3 = Mathf.Clamp(m_shieldStability - hit, 0f, 50f); At.SetField(self, "m_shieldStability", num3); m_shieldStability = num3; } // check non-blocking stability (unchanged) else { var num2 = Mathf.Clamp(m_stability - hit, 0f, 100f); At.SetField(self, "m_stability", num2); 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.Invoke(self, "Knock", true); } At.SetField(self, "m_stability", 0f); 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.Invoke(self, "Knock", true); } 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.SetField(self, "m_hurtType", Character.HurtType.Hurt); if (m_currentlyChargingAttack) { self.CancelCharging(); } m_animator.SetTrigger("Knockhurt"); _base.StopCoroutine("KnockhurtRoutine"); MethodInfo _knockhurtRoutine = self.GetType().GetMethod("KnockhurtRoutine", BindingFlags.NonPublic | 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); }
// 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.SetField(_stats, "m_health", _stats.CurrentHealth * (float)_config.GetValue(Settings.Enemy_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.GetField(_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.SetField(m_character.TargetingSystem, "TargetableFactions", targets); // 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.SetField(m_currentSyncInfos, "m_Settings", dict); 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); } } }