BuyReveal.cs
namespace NoChillquarium;
/// <summary>Pack-open phases for a shop fish buy. Fast on purpose.</summary>
public enum BuyRevealPhase
{
Off = 0,
/// <summary>Card back drops in.</summary>
Deal = 1,
/// <summary>Paper flaps peel. Tease gold on every card.</summary>
Unfold = 2,
/// <summary>Fish visible, rarity still hidden.</summary>
Show = 3,
/// <summary>Rarity stamp slams.</summary>
Stamp = 4,
/// <summary>Hold, then auto-dismiss (commons fast; jackpots linger).</summary>
Hold = 5
}
/// <summary>
/// Chillquarium-style buy juice: spend snaps, card unfolds with ticks,
/// rarity stamp is the slot-stop. Shop packs stay clickable so you mash.
/// </summary>
public static class BuyReveal
{
static BuyRevealPhase _phase;
static FishSpecies _species;
static FishRarity _rarity;
static float _time;
static float _stampAt;
static float _holdFor;
static int _lastTick = -1;
static int _version;
static bool _bannered;
public static int Version => _version;
public static bool IsActive => _phase != BuyRevealPhase.Off;
public static BuyRevealPhase Phase => _phase;
public static FishSpecies Species => _species;
public static FishRarity Rarity => _rarity;
public static string SpeciesName => _species?.Name ?? "Fish";
public static string RarityLabel => FishRarityInfo.Label( _rarity );
public static string RarityCss => FishRarityInfo.CssClass( _rarity );
public static string SpriteId => _species?.SpriteId ?? "";
public static bool HasSprite => _species?.HasSprite == true;
public static string Color => _species?.Color ?? "#80c090";
public static bool FishVisible => (int)_phase >= (int)BuyRevealPhase.Show;
public static bool Revealed => (int)_phase >= (int)BuyRevealPhase.Stamp;
public static bool IsJackpot => _rarity >= FishRarity.Legendary;
public static bool IsSpicy => _rarity >= FishRarity.PaintedRare;
/// <summary>Gold flicker during deal/unfold — every card might be the one.</summary>
public static bool Teasing =>
_phase is BuyRevealPhase.Deal or BuyRevealPhase.Unfold or BuyRevealPhase.Show;
public static string Hint
{
get
{
if ( !IsActive )
return "Pick a pack";
if ( !Revealed )
return "click · skip";
if ( IsJackpot )
return "click · keep it";
if ( IsSpicy )
return "click · nice";
return "click · next";
}
}
public static string CardClass
{
get
{
var phase = _phase.ToString().ToLowerInvariant();
var spicy = IsSpicy && Revealed ? " spicy" : "";
var jack = IsJackpot && Revealed ? " jackpot" : "";
var tease = Teasing ? " tease" : "";
var shown = FishVisible ? " fish-on" : "";
var stamp = Revealed ? " revealed" : "";
return $"buy-card {RarityCss} phase-{phase}{tease}{shown}{stamp}{spicy}{jack}";
}
}
public static void Clear()
{
_phase = BuyRevealPhase.Off;
_species = null;
_rarity = FishRarity.Common;
_time = 0f;
_stampAt = 0f;
_holdFor = 0f;
_lastTick = -1;
_bannered = false;
_version++;
}
/// <summary>Start a pack-open. Commons snap over an in-flight common; jackpots still yield.</summary>
public static void Begin( FishSpecies species, FishRarity rarity )
{
if ( species is null )
return;
_species = species;
_rarity = rarity;
_time = 0f;
_lastTick = -1;
_bannered = false;
_phase = BuyRevealPhase.Deal;
// Variable delay = the slot linger. Subtle per tier so rares feel "heavier".
var tier = (int)rarity;
_stampAt = 0.20f + tier * 0.055f;
_holdFor = rarity switch
{
FishRarity.Common => 0.18f,
FishRarity.Uncommon => 0.30f,
FishRarity.Rare => 0.52f,
FishRarity.PaintedRare => 0.85f,
FishRarity.Legendary => 1.7f,
_ => 2.3f
};
_version++;
PlayTick( 0 );
}
/// <summary>Click the card: skip to stamp, or dismiss if already shown.</summary>
public static void Click()
{
if ( !IsActive )
return;
if ( !Revealed )
{
SkipToStamp();
return;
}
Dismiss();
}
/// <summary>Shop closed — stamp if we were mid-flip so the SFX still lands, then dump.</summary>
public static void OnShopClosed()
{
if ( !IsActive )
return;
if ( !Revealed && _rarity >= FishRarity.Rare )
SkipToStamp();
Dismiss();
}
public static void Tick( float dt )
{
if ( !IsActive )
return;
if ( dt <= 0f )
return;
if ( dt > 0.05f )
dt = 0.05f;
_time += dt;
var next = ResolvePhase( _time );
if ( next != _phase )
{
_phase = next;
var tick = (int)_phase - 1; // Deal=0 … Stamp=3
if ( tick >= 0 && tick <= 3 && tick > _lastTick )
{
_lastTick = tick;
PlayTick( tick );
}
if ( _phase == BuyRevealPhase.Stamp )
OnStamped();
_version++;
}
if ( _phase == BuyRevealPhase.Hold && _time >= _stampAt + _holdFor )
Dismiss();
}
static BuyRevealPhase ResolvePhase( float t )
{
var unfoldAt = _stampAt * 0.28f;
var showAt = _stampAt * 0.58f;
if ( t < unfoldAt )
return BuyRevealPhase.Deal;
if ( t < showAt )
return BuyRevealPhase.Unfold;
if ( t < _stampAt )
return BuyRevealPhase.Show;
if ( t < _stampAt + 0.08f )
return BuyRevealPhase.Stamp;
return BuyRevealPhase.Hold;
}
static void SkipToStamp()
{
if ( Revealed )
return;
_time = _stampAt;
_phase = BuyRevealPhase.Stamp;
_lastTick = 3;
PlayTick( 3 );
OnStamped();
_version++;
}
static void OnStamped()
{
if ( _bannered )
return;
_bannered = true;
var name = SpeciesName;
var tag = FishRarityInfo.Label( _rarity );
Shop.SetLastMessage( _rarity == FishRarity.Common ? $"{name}." : $"{tag} {name}!" );
PixelFx.BurstRarity( PixelFxLayer.Shop, 94f, 118f, _rarity );
PixelFx.BurstRarity( PixelFxLayer.Tank, TankSim.LastSpawnX, TankSim.LastSpawnY, _rarity );
if ( _rarity >= FishRarity.Legendary )
{
BoomFeel.SoftPunch( _rarity >= FishRarity.Mythical ? 0.85f : 0.65f );
TankSim.ShowBanner( $"{tag} {name}!! Keep or sell huge." );
GrandmaReact.OnKeeper( _rarity );
}
else if ( _rarity >= FishRarity.Rare )
{
BoomFeel.SoftPunch( _rarity >= FishRarity.PaintedRare ? 0.5f : 0.35f );
TankSim.ShowBanner( $"{tag} {name}! Feed it up." );
if ( _rarity >= FishRarity.PaintedRare )
GrandmaReact.OnKeeper( _rarity );
}
else if ( _rarity >= FishRarity.Uncommon )
TankSim.ShowBanner( $"{tag} {name}." );
}
static void Dismiss()
{
if ( !IsActive )
return;
_phase = BuyRevealPhase.Off;
_species = null;
_version++;
}
static void PlayTick( int step ) =>
GameAudio.PlayBuyTick( step, _rarity );
}