/ GUNRPG.Core / VirtualPet / OpponentDifficulty.cs
OpponentDifficulty.cs
  1  namespace GUNRPG.Core.VirtualPet;
  2  
  3  /// <summary>
  4  /// Static utility class for computing opponent difficulty based on level differences and proficiencies.
  5  /// Provides deterministic difficulty scaling for mission calculations with no dependencies on PetState or combat logic.
  6  /// </summary>
  7  public static class OpponentDifficulty
  8  {
  9      // ========================================
 10      // Tuning Constants
 11      // ========================================
 12  
 13      /// <summary>
 14      /// XP required per level in the square-root progression curve.
 15      /// Higher values make leveling slower.
 16      /// </summary>
 17      private const long XpPerLevel = 1000L;
 18  
 19      /// <summary>
 20      /// Base difficulty when opponents are evenly matched (same level, same proficiencies).
 21      /// </summary>
 22      private const float BaseDifficulty = 50f;
 23  
 24      /// <summary>
 25      /// Difficulty adjustment per level difference.
 26      /// Positive when opponent is higher level, negative when lower.
 27      /// </summary>
 28      private const float DifficultyPerLevel = 10f;
 29  
 30      /// <summary>
 31      /// Maximum difficulty contribution from weapon proficiency delta (±15).
 32      /// Applied when proficiency difference is ±100.
 33      /// </summary>
 34      private const float MaxWeaponProficiencyImpact = 15f;
 35  
 36      /// <summary>
 37      /// Maximum difficulty contribution from general proficiency delta (±10).
 38      /// Applied when proficiency difference is ±100.
 39      /// </summary>
 40      private const float MaxGeneralProficiencyImpact = 10f;
 41  
 42      /// <summary>
 43      /// Minimum difficulty value (prevents difficulty from going too low).
 44      /// </summary>
 45      private const float MinDifficulty = 10f;
 46  
 47      /// <summary>
 48      /// Maximum difficulty value (prevents difficulty from going too high).
 49      /// </summary>
 50      private const float MaxDifficulty = 100f;
 51  
 52      // ========================================
 53      // Public Methods
 54      // ========================================
 55  
 56      /// <summary>
 57      /// Computes the difficulty rating of an opponent based on level difference (simple version).
 58      /// </summary>
 59      /// <param name="opponentLevel">The level of the opponent.</param>
 60      /// <param name="playerLevel">The level of the player/operator.</param>
 61      /// <returns>
 62      /// A difficulty rating between 10 and 100, where:
 63      /// - 50 represents an evenly matched opponent (same level)
 64      /// - Each level difference adjusts difficulty by 10 points
 65      /// - Higher opponent levels increase difficulty
 66      /// - Lower opponent levels decrease difficulty
 67      /// - Result is clamped to the range [10, 100]
 68      /// </returns>
 69      /// <remarks>
 70      /// This is a pure utility function with no dependencies on PetState or combat logic.
 71      /// The difficulty value can be used as input to mission calculations.
 72      /// 
 73      /// Examples:
 74      /// - Player level 5 vs Opponent level 5: Difficulty = 50 (evenly matched)
 75      /// - Player level 5 vs Opponent level 8: Difficulty = 80 (opponent 3 levels higher)
 76      /// - Player level 8 vs Opponent level 5: Difficulty = 20 (opponent 3 levels lower)
 77      /// - Player level 1 vs Opponent level 20: Difficulty = 100 (clamped at maximum)
 78      /// </remarks>
 79      public static float Compute(int opponentLevel, int playerLevel)
 80      {
 81          // Calculate level difference (positive when opponent is stronger)
 82          int levelDelta = opponentLevel - playerLevel;
 83  
 84          // Base difficulty is 50 (evenly matched)
 85          // Each level of difference adjusts by 10 points
 86          float difficulty = BaseDifficulty + (levelDelta * DifficultyPerLevel);
 87  
 88          // Clamp result between 10 and 100
 89          return Math.Clamp(difficulty, MinDifficulty, MaxDifficulty);
 90      }
 91  
 92      /// <summary>
 93      /// Computes the difficulty rating of an opponent based on XP, weapon proficiency, and general proficiency.
 94      /// </summary>
 95      /// <param name="opponentXp">Experience points of the opponent.</param>
 96      /// <param name="playerXp">Experience points of the player/operator.</param>
 97      /// <param name="opponentWeaponProficiency">Opponent's weapon proficiency (0-100).</param>
 98      /// <param name="opponentGeneralProficiency">Opponent's general combat proficiency (0-100).</param>
 99      /// <param name="playerWeaponProficiency">Player's weapon proficiency (0-100).</param>
100      /// <param name="playerGeneralProficiency">Player's general combat proficiency (0-100).</param>
101      /// <returns>
102      /// A difficulty rating between 10 and 100, where:
103      /// - Base difficulty starts at 50 (evenly matched)
104      /// - Level difference (from XP) contributes ±10 per level
105      /// - Weapon proficiency difference contributes up to ±15
106      /// - General proficiency difference contributes up to ±10
107      /// - Result is clamped to the range [10, 100]
108      /// </returns>
109      /// <remarks>
110      /// This method provides more nuanced difficulty calculation by considering:
111      /// 1. Experience-based level differences (derived via square-root curve)
112      /// 2. Weapon-specific skill differences
113      /// 3. General combat capability differences
114      /// 
115      /// Proficiency modifiers are scaled linearly and have smaller impact than level differences,
116      /// ensuring that experience remains the primary factor in difficulty assessment.
117      /// 
118      /// Examples:
119      /// - Equal XP, equal proficiencies: Difficulty = 50
120      /// - Opponent 9 levels higher (81k vs 0 XP): Difficulty increases by 90 (clamped to 100)
121      /// - Opponent with +50 weapon prof, +50 general prof: Difficulty increases by ~12.5
122      /// </remarks>
123      public static float Compute(
124          long opponentXp,
125          long playerXp,
126          float opponentWeaponProficiency,
127          float opponentGeneralProficiency,
128          float playerWeaponProficiency,
129          float playerGeneralProficiency)
130      {
131          // Derive levels from XP using square-root curve
132          int opponentLevel = ComputeLevelFromXp(opponentXp);
133          int playerLevel = ComputeLevelFromXp(playerXp);
134  
135          // Calculate level difference contribution
136          int levelDelta = opponentLevel - playerLevel;
137          float difficulty = BaseDifficulty + (levelDelta * DifficultyPerLevel);
138  
139          // Calculate weapon proficiency contribution
140          // Delta in range [-100, +100], scaled to max impact of ±15
141          float weaponProfDelta = opponentWeaponProficiency - playerWeaponProficiency;
142          float weaponImpact = (weaponProfDelta / 100f) * MaxWeaponProficiencyImpact;
143          difficulty += weaponImpact;
144  
145          // Calculate general proficiency contribution
146          // Delta in range [-100, +100], scaled to max impact of ±10
147          float generalProfDelta = opponentGeneralProficiency - playerGeneralProficiency;
148          float generalImpact = (generalProfDelta / 100f) * MaxGeneralProficiencyImpact;
149          difficulty += generalImpact;
150  
151          // Clamp final result to valid range
152          return Math.Clamp(difficulty, MinDifficulty, MaxDifficulty);
153      }
154  
155      /// <summary>
156      /// Computes the level from experience points using a square-root progression curve.
157      /// </summary>
158      /// <param name="xp">Experience points (must be non-negative).</param>
159      /// <returns>
160      /// The level derived from XP, where:
161      /// - Level 0 requires 0 XP
162      /// - Level 1 requires 1,000 XP
163      /// - Level 2 requires 4,000 XP
164      /// - Level 3 requires 9,000 XP
165      /// - Level increases with the square root of XP
166      /// - Result is always >= 0
167      /// </returns>
168      /// <remarks>
169      /// Uses the formula: Level = floor(sqrt(xp / XpPerLevel))
170      /// 
171      /// This creates a progression where:
172      /// - Early levels are relatively quick to achieve
173      /// - Later levels require increasingly more XP
174      /// - The curve is smooth and continuous
175      /// 
176      /// Example progression with XpPerLevel = 1000:
177      /// - 0 XP → Level 0
178      /// - 1,000 XP → Level 1
179      /// - 4,000 XP → Level 2
180      /// - 9,000 XP → Level 3
181      /// - 16,000 XP → Level 4
182      /// - 25,000 XP → Level 5
183      /// </remarks>
184      public static int ComputeLevelFromXp(long xp)
185      {
186          // Handle negative XP gracefully (shouldn't happen, but be defensive)
187          if (xp < 0)
188          {
189              return 0;
190          }
191  
192          // Apply square-root curve: Level = floor(sqrt(xp / XpPerLevel))
193          double scaledXp = (double)xp / XpPerLevel;
194          int level = (int)Math.Floor(Math.Sqrt(scaledXp));
195  
196          // Ensure level is non-negative
197          return Math.Max(0, level);
198      }
199  }