using System; using EntityStates; using JetBrains.Annotations; using UnityEngine; using UnityEngine.Serialization; namespace RoR2.Skills; [CreateAssetMenu(menuName = "RoR2/SkillDef/Generic")] public class SkillDef : ScriptableObject { public class BaseSkillInstanceData { } [Tooltip("The name of the skill. This is mainly for purposes of identification in the inspector and currently has no direct effect.")] [Header("Skill Identifier")] public string skillName = ""; [Tooltip("The language token with the name of this skill.")] [Header("User-Facing Info")] public string skillNameToken = ""; [Tooltip("The language token with the description of this skill.")] public string skillDescriptionToken = ""; [Tooltip("Extra tooltips when hovered over in character select. Currently only used in that area!")] public string[] keywordTokens; [Tooltip("The icon to display for this skill.")] [ShowThumbnail] public Sprite icon; [Header("State Machine Parameters")] [Tooltip("The state machine this skill operates upon.")] public string activationStateMachineName; [Tooltip("The state to enter when this skill is activated.")] public SerializableEntityStateType activationState; [Tooltip("The priority of this skill.")] public InterruptPriority interruptPriority = InterruptPriority.Skill; [Tooltip("How long it takes for this skill to recharge after being used.")] [Header("Stock and Cooldown")] public float baseRechargeInterval = 1f; [Tooltip("Maximum number of charges this skill can carry.")] public int baseMaxStock = 1; [Tooltip("How much stock to restore on a recharge.")] public int rechargeStock = 1; [Tooltip("How much stock is required to activate this skill.")] public int requiredStock = 1; [Tooltip("How much stock to deduct when the skill is activated.")] public int stockToConsume = 1; [Tooltip("Makes the skill restock interval divided by attack speed if checked.")] public bool attackSpeedBuffsRestockSpeed; [Tooltip("Increases the efficacy of attack speed on restock time.")] [Range(1f, 10f)] public float attackSpeedBuffsRestockSpeed_Multiplier = 1f; [Tooltip("Whether or not it resets any progress on cooldowns.")] [Header("Optional Parameters, Stock")] [FormerlySerializedAs("isBullets")] public bool resetCooldownTimerOnUse; [Tooltip("Whether or not to fully restock this skill when it's assigned.")] public bool fullRestockOnAssign = true; [Tooltip("Whether or not this skill can hold past it's maximum stock.")] public bool dontAllowPastMaxStocks; [Tooltip("Whether or not the cooldown waits until it leaves the set state")] public bool beginSkillCooldownOnSkillEnd; [Tooltip("Whether or not activating the skill forces off sprinting.")] [FormerlySerializedAs("noSprint")] [Header("Optional Parameters, Sprinting")] public bool cancelSprintingOnActivation = true; [FormerlySerializedAs("mobilitySkill")] [Tooltip("Whether or not this skill is considered 'mobility'. Currently just forces sprint.")] public bool forceSprintDuringState; [Tooltip("Whether or not sprinting sets the skill's state to be reset.")] public bool canceledFromSprinting; [Header("Optional Parameters, Misc")] [Tooltip("Whether or not this is considered a combat skill.")] public bool isCombatSkill = true; [Tooltip("The skill can't be activated if the key is held.")] public bool mustKeyPress; [Tooltip("If true, CharacterBody handles LuminiousShot buffs. If false, the skill must handle it.")] public bool autoHandleLuminousShot = true; [Tooltip("If true, the SkillIcon will not show this skill's stock count.")] public bool hideStockCount; [Obsolete("Accessing UnityEngine.Object.Name causes allocations on read. Look up the name from the catalog instead. If absolutely necessary to perform direct access, cast to ScriptableObject first.")] public new string name => null; public int skillIndex { get; set; } public virtual BaseSkillInstanceData OnAssigned([NotNull] GenericSkill skillSlot) { return null; } public virtual void OnUnassigned([NotNull] GenericSkill skillSlot) { } public virtual Sprite GetCurrentIcon([NotNull] GenericSkill skillSlot) { return icon; } public virtual string GetCurrentNameToken([NotNull] GenericSkill skillSlot) { return skillNameToken; } public virtual string GetCurrentDescriptionToken([NotNull] GenericSkill skillSlot) { return skillDescriptionToken; } protected bool HasRequiredStockAndDelay([NotNull] GenericSkill skillSlot) { return skillSlot.stock >= requiredStock; } public virtual bool CanExecute([NotNull] GenericSkill skillSlot) { if (HasRequiredStockAndDelay(skillSlot) && IsReady(skillSlot) && (bool)skillSlot.stateMachine && !skillSlot.stateMachine.HasPendingState()) { return skillSlot.stateMachine.CanInterruptState(interruptPriority); } return false; } public virtual bool IsReady([NotNull] GenericSkill skillSlot) { return HasRequiredStockAndDelay(skillSlot); } protected virtual EntityState InstantiateNextState([NotNull] GenericSkill skillSlot) { EntityState entityState = EntityStateCatalog.InstantiateState(ref activationState); if (entityState is ISkillState skillState) { skillState.activatorSkillSlot = skillSlot; } return entityState; } public virtual void OnExecute([NotNull] GenericSkill skillSlot) { skillSlot.stateMachine.SetInterruptState(InstantiateNextState(skillSlot), interruptPriority); if (cancelSprintingOnActivation) { skillSlot.characterBody.isSprinting = false; } skillSlot.stock -= stockToConsume; if (resetCooldownTimerOnUse) { skillSlot.rechargeStopwatch = 0f; } if ((bool)skillSlot.characterBody) { skillSlot.characterBody.OnSkillActivated(skillSlot); } } public virtual void OnFixedUpdate([NotNull] GenericSkill skillSlot, float deltaTime) { skillSlot.RunRecharge(deltaTime); if (((canceledFromSprinting && skillSlot.characterBody.isSprinting) || forceSprintDuringState) && skillSlot.stateMachine.state.GetType() == activationState.stateType) { if (canceledFromSprinting && skillSlot.characterBody.isSprinting) { skillSlot.stateMachine.SetNextStateToMain(); } if (forceSprintDuringState) { skillSlot.characterBody.isSprinting = true; } } } public bool IsAlreadyInState([NotNull] GenericSkill skillSlot) { return skillSlot?.stateMachine.state.GetType() == activationState.stateType; } public virtual int GetMaxStock([NotNull] GenericSkill skillSlot) { return baseMaxStock; } public virtual int GetRechargeStock([NotNull] GenericSkill skillSlot) { return rechargeStock; } public virtual float GetRechargeInterval([NotNull] GenericSkill skillSlot) { if (attackSpeedBuffsRestockSpeed) { float num = skillSlot.characterBody.attackSpeed - skillSlot.characterBody.baseAttackSpeed; num *= attackSpeedBuffsRestockSpeed_Multiplier; num += 1f; if (num < 0.5f) { num = 0.5f; } return baseRechargeInterval / num; } return baseRechargeInterval; } }