Game/DesertPumpGame.Extras.cs
namespace DesertPump;
/// <summary>
/// The parts that exist to bring you back tomorrow: backgrounds to spend on, a daily
/// bonus that rewards a streak, and the gusher - a rare click worth a lot of coins.
/// </summary>
public sealed partial class DesertPumpGame
{
// ---- backgrounds ------------------------------------------------------
readonly HashSet<string> ownedBackgrounds = new() { BackgroundStyle.DefaultId };
public string SelectedBackground { get; private set; } = BackgroundStyle.DefaultId;
public bool OwnsBackground( string id ) => ownedBackgrounds.Contains( id );
public bool CanBuyBackground( BackgroundStyle style ) =>
style is not null && !OwnsBackground( style.Id ) && Coins >= style.Cost;
public bool BuyBackground( BackgroundStyle style )
{
if ( style is null || OwnsBackground( style.Id ) )
return false;
if ( Coins < style.Cost )
{
Refuse( $"Need {Numbers.Short( style.Cost - Coins )} more coins" );
return false;
}
Coins -= style.Cost;
ownedBackgrounds.Add( style.Id );
SelectedBackground = style.Id;
Play( UpgradeSound );
Save();
return true;
}
public void SelectBackground( string id )
{
if ( !OwnsBackground( id ) )
return;
SelectedBackground = id;
Save();
}
// ---- daily bonus ------------------------------------------------------
/// <summary>Unix day number of the last claim.</summary>
long lastDailyDay;
public int DailyStreak { get; private set; }
static long Today => DateTimeOffset.UtcNow.ToUnixTimeSeconds() / 86400;
public bool CanClaimDaily => Today > lastDailyDay;
/// <summary>
/// The streak this claim would land on. Miss a day and it's back to 1, so the
/// banner has to work that out rather than just adding one to the old streak.
/// </summary>
public int ProjectedDailyStreak => Today == lastDailyDay + 1 ? DailyStreak + 1 : 1;
/// <summary>
/// Scales with where you are, so it stays worth coming back for at tier 12 - about
/// a minute of your current income, plus a bit more for each day of the streak.
/// </summary>
public double DailyReward
{
get
{
var perSecond = (CurrentPump.PerSecond + LitresPerClick) * PricePerLitre;
var streakBonus = 1d + Math.Min( DailyStreak, 6 ) * 0.25d;
return Math.Max( 100d, perSecond * 60d * streakBonus );
}
}
/// <summary>Returns the coins granted, or zero if it wasn't available.</summary>
public double ClaimDaily()
{
if ( !CanClaimDaily )
return 0;
// Consecutive days keep the streak, a gap resets it.
DailyStreak = Today == lastDailyDay + 1 ? DailyStreak + 1 : 1;
lastDailyDay = Today;
var reward = DailyReward;
AddCoins( reward );
Play( UpgradeSound );
Save();
return reward;
}
// ---- the gusher -------------------------------------------------------
/// <summary>Seconds between gusher appearances, randomised inside this band.</summary>
public const float GusherMinDelay = 75f;
public const float GusherMaxDelay = 200f;
/// <summary>How long it hangs around waiting to be clicked.</summary>
public const float GusherLifetime = 11f;
TimeSince timeSinceGusher;
float nextGusherAt = GusherMinDelay;
/// <summary>True while a gusher is on screen and clickable.</summary>
public bool GusherActive { get; private set; }
/// <summary>0-1 across the screen, so it doesn't appear in the same place twice.</summary>
public float GusherX { get; private set; }
public float GusherY { get; private set; }
/// <summary>Fired when a gusher is cashed in, with the coins won.</summary>
public Action<double> GusherClaimed { get; set; }
/// <summary>Worth a couple of minutes of income - enough to feel like a find.</summary>
public double GusherReward =>
Math.Max( 50d, (CurrentPump.PerSecond + LitresPerClick * 2d) * PricePerLitre * 120d );
void TickGusher()
{
if ( GusherActive )
{
if ( timeSinceGusher > GusherLifetime )
{
GusherActive = false;
ScheduleGusher();
}
return;
}
if ( timeSinceGusher < nextGusherAt )
return;
GusherActive = true;
GusherX = System.Random.Shared.NextSingle() * 0.7f + 0.15f;
GusherY = System.Random.Shared.NextSingle() * 0.4f + 0.15f;
timeSinceGusher = 0;
}
void ScheduleGusher()
{
timeSinceGusher = 0;
nextGusherAt = GusherMinDelay + System.Random.Shared.NextSingle() * (GusherMaxDelay - GusherMinDelay);
}
/// <summary>Cash in the gusher. Returns the coins won, or zero if there wasn't one.</summary>
public double ClaimGusher()
{
if ( !GusherActive )
return 0;
GusherActive = false;
ScheduleGusher();
var reward = GusherReward;
AddCoins( reward );
Play( SellSound );
GusherClaimed?.Invoke( reward );
// Saved like every other windfall. A gusher is worth two minutes of income and
// shows up every couple of minutes, so leaving it to the autosave meant quitting
// on the wrong second threw it away.
Save();
return reward;
}
/// <summary>Drop straight onto a pump. For balance testing, not for players.</summary>
public void JumpToPump( int index )
{
PumpLevel = Math.Clamp( index, 0, PumpModel.MaxIndex );
Water = Math.Min( Water, TankCapacity );
Save();
}
// ---- tier progress ----------------------------------------------------
/// <summary>
/// How far through the current tier's nine pumps, 0-1. Owning rank 9 is a finished
/// tier, so that reads full - counting from rank-1 left the bar at 89% next to a
/// label saying nothing was left to buy.
/// </summary>
public float TierProgress => CurrentPump.Rank / (float)PumpTier.PumpsPerTier;
/// <summary>Pumps left before the next tier opens up.</summary>
public int PumpsLeftInTier => PumpTier.PumpsPerTier - CurrentPump.Rank;
}