Definitions for decor items and placed decor pieces. DecorDef describes properties for tank ornaments and provides a static catalog of predefined decor; DecorPiece is a runtime data object holding a placed piece position, velocity, held state, and convenience accessors to its Def and IsMoving state.
namespace NoChillquarium;
/// <summary>
/// Placeable tank ornaments. Sizes are tank pixels — props should dwarf fish
/// (goldfish ≈ 52×36). GroundSink buries the sprite base into gravel so plants
/// don't look like floating islands.
/// </summary>
public sealed class DecorDef
{
public string Id { get; init; }
public string Name { get; init; }
public string Note { get; init; }
public double Cost { get; init; }
public float Width { get; init; }
public float Height { get; init; }
public string CssClass { get; init; }
public string SpritePath { get; init; }
public float ComfortBonus { get; init; }
public float ChaosOnPlace { get; init; }
/// <summary>How many px of the sprite bottom sink into the gravel band.</summary>
public float GroundSink { get; init; }
public bool HasSprite => SpriteCatalog.Has( Id );
public string SpriteId => Id;
public static readonly DecorDef Plant = new()
{
Id = "plant_green",
Name = "Plastic Plant",
Note = "chill greenery",
Cost = 12,
Width = 140,
Height = 320,
CssClass = "decor-plant",
SpritePath = "plant_green",
ComfortBonus = 0.05f,
// Dirt mound in the art must sit inside the gravel strip
GroundSink = 48f
};
public static readonly DecorDef Treasure = new()
{
Id = "treasure_chest",
Name = "Treasure Chest",
Note = "bubbles optional",
Cost = 28,
// Art ~91×96 — keep a solid grab box without a huge empty frame.
Width = 140,
Height = 120,
CssClass = "decor-chest",
SpritePath = "treasure_chest",
ComfortBonus = 0.08f,
GroundSink = 10f
};
public static readonly DecorDef BubbleWand = new()
{
Id = "bubble_wand",
Name = "Bubble Wand",
Note = "party mode",
Cost = 18,
Width = 90,
Height = 340,
CssClass = "decor-bubbler",
SpritePath = "bubble_wand",
ComfortBonus = 0.04f,
GroundSink = 16f
};
public static readonly DecorDef FortyOz = new()
{
Id = "forty_oz",
Name = "40oz Bottle",
Note = "no-chill centerpiece",
Cost = 22,
// Art is 96×96 — tall-ish tank prop, not a skyscraper hitbox.
Width = 96,
Height = 160,
CssClass = "decor-forty",
SpritePath = "forty_oz",
ComfortBonus = 0.03f,
ChaosOnPlace = 2f,
GroundSink = 12f
};
public static readonly DecorDef Handcuffs = new()
{
Id = "handcuffs_skeleton",
Name = "Cuffs + Bone Hand",
Note = "don't ask",
Cost = 35,
Width = 170,
Height = 120,
CssClass = "decor-cuffs",
SpritePath = "handcuffs_skeleton",
ComfortBonus = 0.02f,
ChaosOnPlace = 4f,
GroundSink = 12f
};
public static readonly DecorDef Boot = new()
{
Id = "old_boot",
Name = "Old Boot",
Note = "classic dumpster chic",
Cost = 10,
// Source art is 96×96 — keep hitbox tight so grab/throw feels right.
Width = 128,
Height = 128,
CssClass = "decor-boot",
SpritePath = "old_boot",
ComfortBonus = 0.02f,
ChaosOnPlace = 1f,
GroundSink = 10f
};
public static readonly DecorDef Meds = new()
{
Id = "medicine_bottles",
Name = "Medicine Bottles",
Note = "vibes only (for now)",
Cost = 16,
Width = 120,
Height = 120,
CssClass = "decor-meds",
SpritePath = "medicine_bottles",
ComfortBonus = 0.06f,
GroundSink = 10f
};
public static readonly DecorDef DogeStatue = new()
{
Id = "doge_statue",
Name = "Doge Statue",
Note = "much worship",
Cost = 50,
Width = 180,
Height = 240,
CssClass = "decor-doge",
SpritePath = "doge_statue",
ComfortBonus = 0.15f,
GroundSink = 14f
};
public static readonly DecorDef M80Crate = new()
{
Id = "m80_crate",
Name = "M80 Crate",
Note = "decorative. mostly.",
Cost = 30,
Width = 180,
Height = 140,
CssClass = "decor-m80crate",
SpritePath = "m80_crate",
ComfortBonus = 0.01f,
ChaosOnPlace = 3f,
GroundSink = 12f
};
public static IReadOnlyList<DecorDef> Catalog { get; } = new[]
{
Plant,
Treasure,
BubbleWand,
FortyOz,
Handcuffs,
Boot,
Meds,
DogeStatue,
M80Crate
};
public static DecorDef Find( string id ) =>
Catalog.FirstOrDefault( d => d.Id == id ) ?? Plant;
}
public sealed class DecorPiece
{
public string DefId { get; set; }
public float X { get; set; }
public float Y { get; set; }
/// <summary>Throw / tumble velocity (tank px/s). Zeroed when resting on gravel.</summary>
public float Vx { get; set; }
public float Vy { get; set; }
/// <summary>True while the player is dragging this piece.</summary>
public bool Held { get; set; }
/// <summary>Seconds after release where floor stick is relaxed so throws launch.</summary>
public float ThrowGrace { get; set; }
public DecorDef Def => DecorDef.Find( DefId );
public bool IsMoving =>
Held || ThrowGrace > 0f || MathF.Abs( Vx ) > 2f || MathF.Abs( Vy ) > 2f;
}