using HarmonyLib; using KFCommonUtilityLib.Scripts.Attributes; using KFCommonUtilityLib.Scripts.Utilities; using UnityEngine; using static ItemActionRanged; [TypeTarget(typeof(ItemActionRanged)), ActionDataTarget(typeof(RampUpData))] public class ActionModuleRampUp { public enum State { RampUp, Stable, RampDown } private readonly static int prepareHash = Animator.StringToHash("prepare"); private readonly static int prepareSpeedHash = Animator.StringToHash("prepareSpeed"); private readonly static int rampHash = Animator.StringToHash("ramp"); private readonly static int prepareRatioHash = Animator.StringToHash("prepareRatio"); private readonly static int rampRatioHash = Animator.StringToHash("rampRatio"); private readonly static int totalRatioHash = Animator.StringToHash("totalRatio"); [HarmonyPatch(nameof(ItemAction.OnHoldingUpdate)), MethodTargetPostfix] public void Postfix_OnHoldingUpdate(ItemActionData _actionData, RampUpData __customData, ItemActionRanged __instance) { var rangedData = _actionData as ItemActionDataRanged; __customData.originalDelay = rangedData.Delay; if (rangedData.invData.holdingEntity.isEntityRemote) return; bool aiming = rangedData.invData.holdingEntity.AimingGun; bool isRampUp = ((rangedData.bPressed && !rangedData.bReleased && __instance.notReloading(rangedData) && rangedData.curBurstCount < __instance.GetBurstCount(rangedData)) || (__customData.zoomPrepare && aiming)) && (__instance.InfiniteAmmo || _actionData.invData.itemValue.Meta > 0) && _actionData.invData.itemValue.PercentUsesLeft > 0; UpdateTick(__customData, _actionData, isRampUp); if (__customData.rampRatio > 0) { rangedData.Delay /= __customData.rampRatio >= 1f ? __customData.maxMultiplier : __customData.rampRatio * (__customData.maxMultiplier - 1f) + 1f; } } [HarmonyPatch(nameof(ItemAction.OnModificationsChanged)), MethodTargetPostfix] public void Postfix_OnModificationsChanged(ItemActionData _data, RampUpData __customData, ItemActionRanged __instance) { int actionIndex = __instance.ActionIndex; string originalValue = 1.ToString(); __instance.Properties.ParseString("RampMultiplier", ref originalValue); __customData.maxMultiplier = Mathf.Max(float.Parse(_data.invData.itemValue.GetPropertyOverrideForAction("RampMultiplier", originalValue, actionIndex)), 1); originalValue = 0.ToString(); __instance.Properties.ParseString("RampUpTime", ref originalValue); __customData.rampUpTime = float.Parse(_data.invData.itemValue.GetPropertyOverrideForAction("RampTime", originalValue, actionIndex)); originalValue = string.Empty; __instance.Properties.ParseString("RampUpSound", ref originalValue); __customData.rampUpSound = _data.invData.itemValue.GetPropertyOverrideForAction("RampStartSound", originalValue, actionIndex); originalValue = 0.ToString(); __instance.Properties.ParseString("RampDownTime", ref originalValue); __customData.rampDownTime = Mathf.Max(float.Parse(_data.invData.itemValue.GetPropertyOverrideForAction("RampTime", originalValue, actionIndex)), 0); originalValue = string.Empty; __instance.Properties.ParseString("RampDownSound", ref originalValue); __customData.rampDownSound = _data.invData.itemValue.GetPropertyOverrideForAction("RampStartSound", originalValue, actionIndex); originalValue = 0.ToString(); __instance.Properties.ParseString("PrepareTime", ref originalValue); __customData.prepareTime = float.Parse(_data.invData.itemValue.GetPropertyOverrideForAction("PrepareTime", originalValue, actionIndex)); __customData.prepareSpeed = float.Parse(originalValue) / __customData.prepareTime; originalValue = string.Empty; __instance.Properties.ParseString("PrepareSound", ref originalValue); __customData.prepareSound = _data.invData.itemValue.GetPropertyOverrideForAction("PrepareSound", originalValue, actionIndex); originalValue = false.ToString(); __instance.Properties.ParseString("PrepareOnAim", ref originalValue); __customData.zoomPrepare = bool.Parse(_data.invData.itemValue.GetPropertyOverrideForAction("PrepareOnAim", originalValue, actionIndex)); originalValue = string.Empty; __instance.Properties.ParseString("RampStableSound", ref originalValue); __customData.rampStableSound = _data.invData.itemValue.GetPropertyOverrideForAction("RampStableSound", originalValue, actionIndex); __customData.totalChargeTime = __customData.prepareTime + __customData.rampUpTime; __customData.rampDownTimeScale = __customData.rampDownTime > 0 ? (__customData.totalChargeTime) / __customData.rampDownTime : float.MaxValue; ResetAll(__customData, _data); } [HarmonyPatch(nameof(ItemAction.StopHolding)), MethodTargetPostfix] public void Postfix_StopHolding(RampUpData __customData, ItemActionData _data) { ResetAll(__customData, _data); } [HarmonyPatch(nameof(ItemAction.ExecuteAction)), MethodTargetPrefix] public bool Prefix_ExecuteAction(RampUpData __customData, ItemActionRanged __instance, ItemActionData _actionData, bool _bReleased) { ItemActionDataRanged rangedData = _actionData as ItemActionDataRanged; if (!_bReleased && (__instance.InfiniteAmmo || _actionData.invData.itemValue.Meta > 0) && _actionData.invData.itemValue.PercentUsesLeft > 0) { rangedData.bReleased = false; rangedData.bPressed = true; if (__customData.curTime < __customData.prepareTime) return false; } return true; } private void UpdateTick(RampUpData data, ItemActionData actionData, bool isRampUp) { float previousTime = data.curTime; float deltaTime = Time.time - data.lastTickTime; data.lastTickTime = Time.time; ref float curTime = ref data.curTime; ref State curState = ref data.curState; float totalChargeTime = data.totalChargeTime; switch (curState) { case State.RampUp: { curTime = Mathf.Max(curTime, 0); if (isRampUp) { actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(prepareHash, true, true); if (curTime < totalChargeTime) { curTime += deltaTime; } if (curTime >= data.prepareTime) { actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(rampHash, true, true); } if (curTime >= totalChargeTime) { //Log.Out($"change state from {curState} to stable"); actionData.invData.holdingEntity.PlayOneShot(data.rampStableSound); curState = State.Stable; } } else { //Log.Out($"change state from {curState} to ramp down"); actionData.invData.holdingEntity.StopOneShot(data.rampUpSound); actionData.invData.holdingEntity.PlayOneShot(data.rampDownSound); curState = State.RampDown; } break; } case State.RampDown: { curTime = Mathf.Min(curTime, totalChargeTime); if (!isRampUp) { actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(rampHash, false, true); if (curTime > 0) { curTime -= deltaTime * data.rampDownTimeScale; } if (curTime < data.prepareTime) { actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(prepareHash, false, true); } if (curTime <= 0) { //Log.Out($"change state from {curState} to stable"); //actionData.invData.holdingEntity.PlayOneShot(data.rampStableSound); curState = State.Stable; } } else { //Log.Out($"change state from {curState} to ramp up"); actionData.invData.holdingEntity.StopOneShot(data.rampDownSound); actionData.invData.holdingEntity.PlayOneShot(data.rampUpSound); curState = State.RampUp; } break; } case State.Stable: { if (isRampUp) { if (curTime < totalChargeTime) { //Log.Out($"change state from {curState} to ramp up"); actionData.invData.holdingEntity.StopOneShot(data.rampStableSound); actionData.invData.holdingEntity.StopOneShot(data.rampDownSound); actionData.invData.holdingEntity.PlayOneShot(data.rampUpSound); curState = State.RampUp; } else { actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(prepareHash, true, true); actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(rampHash, true, true); } } else { if (curTime > 0) { //Log.Out($"change state from {curState} to ramp down"); actionData.invData.holdingEntity.StopOneShot(data.rampStableSound); actionData.invData.holdingEntity.StopOneShot(data.rampUpSound); actionData.invData.holdingEntity.PlayOneShot(data.rampDownSound); curState = State.RampDown; } else { actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(prepareHash, false, true); actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(rampHash, false, true); } } break; } } //Log.Out($"turret burst fire rate {turret.burstFireRate} max {turret.burstFireRateMax} cur time {curTime} cur state {curState} is ramp up {isRampUp} turret: ison {turret.IsOn} has target {turret.hasTarget} state {turret.state}"); actionData.invData.holdingEntity.emodel.avatarController.UpdateFloat(prepareSpeedHash, data.prepareSpeed); if (curTime != previousTime) { actionData.invData.holdingEntity.emodel.avatarController.UpdateFloat(prepareRatioHash, data.prepareRatio = (data.prepareTime == 0 ? 1f : Mathf.Clamp01(curTime / data.prepareTime))); actionData.invData.holdingEntity.emodel.avatarController.UpdateFloat(rampRatioHash, data.rampRatio = (data.rampUpTime == 0 ? 1f : Mathf.Clamp01((curTime - data.prepareTime) / data.rampUpTime))); actionData.invData.holdingEntity.emodel.avatarController.UpdateFloat(totalRatioHash, data.totalRatio = (totalChargeTime == 0 ? 1f : Mathf.Clamp01(curTime / totalChargeTime))); } } private void ResetAll(RampUpData _rampData, ItemActionData _actionData) { _rampData.curTime = 0f; _rampData.lastTickTime = Time.time; _rampData.curState = State.Stable; _rampData.prepareRatio = 0f; _rampData.rampRatio = 0f; _rampData.totalRatio = 0f; ((ItemActionDataRanged)_actionData).Delay = _rampData.originalDelay; _actionData.invData.holdingEntity.StopOneShot(_rampData.prepareSound); _actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(prepareHash, false, true); _actionData.invData.holdingEntity.StopOneShot(_rampData.rampUpSound); _actionData.invData.holdingEntity.emodel.avatarController.UpdateBool(rampHash, false, true); //Log.Out("Reset all!"); } public class RampUpData { public float maxMultiplier = 1f; public string prepareSound = string.Empty; public float prepareSpeed = 1f; public float prepareTime = 0f; public string rampUpSound = string.Empty; public float rampUpTime = 0f; public float totalChargeTime = 0f; public string rampDownSound = string.Empty; public float rampDownTime = 0f; public float rampDownTimeScale = float.MaxValue; public string rampStableSound = string.Empty; public float originalDelay = 0f; public float curTime = 0f; public State curState = State.Stable; public float prepareRatio = 0f; public float rampRatio = 0f; public float totalRatio = 0f; public float lastTickTime = 0f; public bool zoomPrepare = false; public ActionModuleRampUp rampUpModule; public RampUpData(ItemInventoryData _invData, int _indexInEntityOfAction, ActionModuleRampUp _module) { rampUpModule = _module; } } }