using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using Unity.Netcode; using UnityEngine; using UnityEngine.AI; namespace ProjectI.Enemies { public enum EnemyDirectorTier { Normal, Elite } public enum EnemySelectionMode { SingleType, Template, Procedural } [CreateAssetMenu(menuName = "ProjectI/Enemies/Director (provisional tuning)")] public sealed class EnemyDirectorDefinition : ScriptableObject { [Serializable] public struct Eligibility { [Min(0)] public int minimumStage, maximumStage; [Min(0)] public double minimumMinutes, maximumMinutes, minimumDifficulty, maximumDifficulty; [Range(0, 4)] public int minimumParticipants, maximumParticipants; public bool Valid => minimumStage >= 0 && maximumStage >= 0 && Ordered(minimumStage, maximumStage) && Ordered(minimumMinutes, maximumMinutes) && Ordered(minimumDifficulty, maximumDifficulty) && minimumParticipants >= 0 && minimumParticipants <= 4 && maximumParticipants <= 4 && Ordered(minimumParticipants, maximumParticipants); private static bool Ordered(double min, double max) => Finite(min) && Finite(max) && min >= 0 && max >= 0 && (max == 0 || max >= min); public bool Allows(int stage, double minutes, double difficulty, int participants) => Valid && Within(stage, minimumStage, maximumStage) && Within(minutes, minimumMinutes, maximumMinutes) && Within(difficulty, minimumDifficulty, maximumDifficulty) && Within(participants, minimumParticipants, maximumParticipants); private static bool Within(double value, double min, double max) => value >= min && (max == 0 || value <= max); } [Serializable] public sealed class Card { public string id, typeId; public GameObject prefab; public EnemyDirectorTier tier; [Tooltip("SingleType normal packs only: one roll replaces at most one member, using eligible active-profile elites and complete-group credit. Zero disables upgrades. Saving conditions on expensive final outcomes: any positive chance can make an upgraded-only target eligible. Ignored on elite cards.")] [Range(0, 1)] public double singlePackEliteChance; public double cost = 10; [Tooltip("Weight for direct card selection. On elite cards this also weights eligible elite replacement variants; it is separate from the normal card's pack upgrade chance.")] public double weight = 1; public Eligibility eligibility; } [Serializable] public sealed class Template { public string id; public EnemyCompositionDefinition composition; public double weight = 1; public Eligibility eligibility; } [Serializable] public sealed class Profile { public string id = "exploration"; public double baseIncome = 10, bankCeiling = 100, initialBalance = 20, purchaseInterval = 4; public double savingChance = .25, savingSeconds = 12; public double memberStagger = .4, pendingLifetime = 20, arrivalSeconds = 1.25; [Range(1, 16)] public int admissionLimit = 16; [Range(1, 5)] public int singleMinimum = 1, singleMaximum = 5, proceduralMinimum = 2, proceduralMaximum = 5; public double singleWeight = 1, templateWeight = 1, proceduralWeight = 1; public string[] cardIds = Array.Empty(), templateIds = Array.Empty(); } [Serializable] public struct HardExclusion { public string firstType, secondType; } [Serializable] public struct SoftModifier { public string triggerType, candidateType; public double multiplier; } public Card[] cards = Array.Empty(); public Template[] templates = Array.Empty