Agenda/Difficulty.cs
using System;
namespace PlanetMeat;
public sealed class Difficulty : GameObjectSystem<Difficulty>
{
private class Multiplier( string challenge, float step )
{
private float _mult = float.NaN;
public float Mult => float.IsNaN( _mult ) ? BakeMultiplier() : _mult;
public string ChallengeIdent { get; private set; } = challenge;
public void UpdateMultiplier()
{
if ( !float.IsNaN( _mult ) )
BakeMultiplier();
}
private float BakeMultiplier()
{
return _mult = MathF.Pow( step, Current.GetChallengeLevel( ChallengeIdent ) );
}
}
private int HighestRank { get; set; } = 0;
private Dictionary<string, int> ChallengeLevels { get; } = [];
private readonly Dictionary<string, Multiplier> challengeMults = new()
{
["EnemyHp"] = new( "enemy_hp", 1.3f ),
["EnemyDmg"] = new( "enemy_dmg", 1.3f ),
["EnemySpeed"] = new( "enemy_speed", 1.15f ),
["GroupCount"] = new( "group_count", 1.15f ),
["GroupDelay"] = new( "group_delay", 0.85f ),
["GroupSize"] = new( "group_size", 1.2f ),
["GroupSpread"] = new( "group_spread", 1.3f ),
};
private readonly HashSet<string> challengeFlags = [];
public Difficulty( Scene scene ) : base( scene )
{
if ( !Scene.IsEditor )
{
foreach ( var c in ResourceLibrary.GetAll<Challenge>() )
{
var rank = c.Rank;
if ( rank > HighestRank )
HighestRank = rank;
}
}
}
public void ResetChallenges()
{
challengeFlags.Clear();
ChallengeLevels.Clear();
foreach ( var m in challengeMults.Values )
m.UpdateMultiplier();
}
public static Challenge GetChallengeResource( string ident ) => ResourceLibrary.Get<Challenge>( "challenges/" + ident + ".chalng" );
public int GetChallengeLevel( string ident ) => ChallengeLevels.GetValueOrDefault( ident, 0 );
public static int GetChallengeMax( string ident ) => GetChallengeResource( ident ).MaxLevel;
public bool HasChallenge( string ident ) => ChallengeLevels.ContainsKey( ident );
public float GetMult( string id ) => challengeMults[id].Mult;
public void AddChallenge( Challenge challenge )
{
var ident = challenge.Ident;
ChallengeLevels[ident] = GetChallengeLevel( ident ) + 1;
foreach ( var f in challenge.AddedFlags )
AddFlag( f );
foreach ( var m in challengeMults.Values.Where( ( v ) => v.ChallengeIdent == ident ) )
m.UpdateMultiplier();
}
private void AddFlag( string flag )
{
if ( challengeFlags.Add( flag ) )
{
/* flag-specific behavior
switch ( flag )
{
default:
break;
}
*/
}
}
private bool IsChallengeAvailable( Challenge c )
{
if ( !c.RequiredFlags.IsSubsetOf( challengeFlags ) )
return false;
var ident = c.Ident;
var level = GetChallengeLevel( ident );
var maxLevel = c.MaxLevel;
return maxLevel < 0 || level < maxLevel;
}
private int GetEffectiveRank( Challenge c )
{
var level = GetChallengeLevel( c.Ident );
var maxLevel = c.MaxLevel;
return (maxLevel >= 0 && level < maxLevel) ? (c.Rank + level) : c.Rank;
}
private bool IsChallengeOfferedAtRank( Challenge c, int rank ) => GetEffectiveRank( c ) <= rank;
private List<Challenge> GetChoicesAtRank( int rank, int minChoices )
{
var available = ResourceLibrary.GetAll<Challenge>().Where( IsChallengeAvailable );
var offers = available.Where( ( c ) => IsChallengeOfferedAtRank( c, rank ) ).ToList();
while ( offers.Count < minChoices && rank <= HighestRank )
{
offers.AddRange( available.Except( offers ).Where( ( c ) => IsChallengeOfferedAtRank( c, rank ) ) );
rank++;
}
return offers;
}
public List<Challenge> CreateOffers( int rank, int numChoices = 3 )
{
var choices = GetChoicesAtRank( rank, numChoices );
choices.Shuffle( Game.Random );
/* DBG test specific challenges
choices.Insert( 0, GetChallengeResource( "add_morsel" ) );
choices.Insert( 0, GetChallengeResource( "add_feast" ) );
choices.Insert( 0, GetChallengeResource( "hydra" ) );
//*/
if ( choices.Count > 0 )
return choices[..numChoices];
return [];
}
}