Spending/Research/Improvements/TypedImprovements.cs
using System;

namespace PlanetMeat;

public class IntEffectImprovement( string ident, string category, int effectBase, int effectPerRank, int effectMax = -1 ) : BaseEffectImprovement<int>( ident, category, effectBase, effectPerRank, effectMax )
{
	public override int GetEffectAt( int rank ) => EffectBase.AddCapped( EffectPerRank.MultiplyCapped( rank ) );
	public override bool HasCap => EffectMax >= 0;
}

public class FloatEffectImprovement : BaseEffectImprovement<float>
{
	public static bool TestCap( float current, float cap ) => current + 0.001f < cap;

	public const int DEFAULT_DECIMAL_PLACES = 2;
	public int DecimalPlaces { get; set; } = DEFAULT_DECIMAL_PLACES;

	public override float GetEffectAt( int rank ) => EffectBase.AddCapped( EffectPerRank.MultiplyCapped( rank ) );
	public override bool HasCap => EffectMax > -0.1f;
	protected override bool IsUnderCap => TestCap(CurrentEffect, EffectMax);

	public FloatEffectImprovement( string ident, string category, float effectBase, float effectPerRank, float effectMax = -1.0f ) : base( ident, category, effectBase, effectPerRank, effectMax )
	{
		DecimalPlaces = Math.Max( effectBase.CountDecimalPlaces(), effectPerRank.CountDecimalPlaces() );
	}

	public override string GetDisplayedEffectAt( int rank ) => GetEffectAt( rank ).ToNString( DecimalPlaces );
}

public class PercentageEffectImprovement : FloatEffectImprovement
{
	public PercentageEffectImprovement( string ident, string category, float effectBase, float effectPerRank, float effectMax = -1.0f ) : base( ident, category, effectBase, effectPerRank, effectMax )
	{
		DecimalPlaces = Math.Max( 0, DecimalPlaces - 2 );
	}

	public override string GetDisplayedEffectAt( int rank ) => GetDisplayedPercentage( GetEffectAt( rank ), DecimalPlaces );

	public static string GetDisplayedPercentage( float frac, int decimalPaces = DEFAULT_DECIMAL_PLACES ) => frac.ToPercentage().ToNString( decimalPaces ) + "%";
}


public class DoubleEffectImprovement( string ident, string category, double effectBase, double effectPerRank, double effectMax = -1 ) : BaseEffectImprovement<double>( ident, category, effectBase, effectPerRank, effectMax )
{
	public override double GetEffectAt( int rank ) => EffectBase.AddCapped( EffectPerRank.MultiplyCapped( rank ) );
	public override bool HasCap => EffectMax >= -0.1;
}

public class PowLogZeroedEffectImprovement( string ident, string category, double effectBase, double effectPerRank, double effectMax = -1 ) : DoubleEffectImprovement( ident, category, effectBase, effectPerRank, effectMax )
{
	public override double GetEffectAt( int rank ) => EffectBase.AddCapped( EffectPerRank.MultiplyCapped( Progressions.PowLogZeroed( rank ) ) );
}