using System; using UnityEngine; [CreateAssetMenu(fileName = "NewLevelRuleConfig", menuName = "Bansonic/Gameplay/Level Rule Config")] public class LevelRuleConfig_SO : ScriptableObject { [Header("Match")] public int songID = 0; public string songName = string.Empty; [Tooltip("-1 means any difficulty.")] public int difficulty = -1; [Header("Support Track")] public bool useLeastNoteTrackAsSupportTrack = false; [Tooltip("Used when useLeastNoteTrackAsSupportTrack is false. 0-4. -1 disables support-track logic.")] public int supportTrackIndex = -1; public int maxSupportCharges = 3; public int adjacentSkillCastsPerSupportCharge = 3; public float adjacentSkillCastWindow = 5f; [Header("Echo")] public bool enableAdjacentSkillEcho = false; public float echoWindow = 1.5f; public int echoThreshold = 3; public int maxEchoStacks = 4; public float echoResistancePerStack = 0.06f; public float echoResistanceDuration = 4f; public int echoManaPenalty = 0; [Header("Conditional Rewards")] public int allAlliesAliveScoreOnEnemyDeath = 0; public bool awardAllAlliesAliveScorePerEnemy = false; [Tooltip("At settlement: average living HP percent * this value is added as team score.")] public int finalAverageHpPercentScore = 0; [Header("Stages")] public LevelRuleStage[] stages = Array.Empty(); public bool Matches(SongData song, int currentDifficulty) { if (song == null) return false; bool idMatches = songID != 0 && song.songID == songID; bool nameMatches = !string.IsNullOrWhiteSpace(songName) && string.Equals(song.songName, songName, StringComparison.OrdinalIgnoreCase); if (!idMatches && !nameMatches) return false; return difficulty < 0 || difficulty == currentDifficulty; } } [Serializable] public class LevelRuleStage { [Tooltip("-1 means match by current stage index. If enemyID > 0, enemy ID also has to match.")] public int stageIndex = -1; public int enemyID = 0; [Tooltip("If > 0, this enemy is held at 1 HP before this song time.")] public float minimumDeathSongTime = -1f; public bool clearEchoOnSpawn = true; public bool clearSupportChargesOnSpawn = false; public LevelRuleAction[] onSpawn = Array.Empty(); public LevelRuleAction[] onManaFull = Array.Empty(); public LevelRuleAction[] onDeath = Array.Empty(); public LevelRuleAction[] onDamaged = Array.Empty(); public LevelRuleHoldAction[] onHoldStart = Array.Empty(); public LevelRuleHoldAction[] onHoldComplete = Array.Empty(); public LevelRuleHoldAction[] onHoldBreak = Array.Empty(); public LevelRuleHpPhase[] hpPhases = Array.Empty(); public bool Matches(int currentStageIndex, EnemyData_SO data) { if (stageIndex >= 0 && stageIndex != currentStageIndex) return false; if (enemyID > 0 && (data == null || data.enemyID != enemyID)) return false; return stageIndex >= 0 || enemyID > 0; } } [Serializable] public class LevelRuleHpPhase { [Range(0f, 1f)] public float hpPercentThreshold = 0.5f; public bool triggerOnce = true; public LevelRuleAction[] actions = Array.Empty(); } [Serializable] public class LevelRuleHoldAction { public LevelRuleAction[] actions = Array.Empty(); } [Serializable] public class LevelRuleAction { public string actionId = string.Empty; public LevelRuleActionType actionType = LevelRuleActionType.None; public LevelRuleTarget target = LevelRuleTarget.CurrentEnemy; public float amount = 0f; public bool amountScalesByDifficultyLevel = false; public bool amountIsPercentOfMaxHp = false; public float duration = 0f; public int maxTriggersPerEnemy = 0; public bool onlyIfAllAlliesAlive = false; public bool requireSupportCharge = false; public int scoreReward = 0; } public enum LevelRuleActionType { None, Damage, Heal, ManaDelta, AttackDelta, ResistanceDeltaTimed, HealReceivedMultiplierTimed, AddTeamScore, DamageCurrentEnemyByOwnAttack, SpendSupportChargeForEnemyVulnerability, ClearEnemyMana } public enum LevelRuleTarget { CurrentEnemy, AllLivingAllies, TriggerTrackAlly, HighestCurrentManaAlly, HighestAttackAlly, HighestRecentHitTrackAlly, HighestRecentSkillCastAlly }