diff --git a/scenes/entities/item.gd b/scenes/entities/item.gd index e54cdce..0ae136d 100644 --- a/scenes/entities/item.gd +++ b/scenes/entities/item.gd @@ -176,20 +176,193 @@ static func from_dict(d: Dictionary) -> Dictionary: # ── render ──────────────────────────────────────────────────────────────────── +## Procedural shape per item type. All shapes draw inside a -6..+6 box so the +## quality border (half=6) wraps the result. Returns true if the type was +## handled; false sends the caller to the hue-hashed fallback. +## +## Shape language is loose silhouette + a category-appropriate colour: bread = +## brown loaf, grain = wheat-amber stalks, vegetable = green disc, meal = bowl, +## etc. Better than rendering 18 different magenta-pink squares; later art pass +## can lift these to atlas crops where the bundle has a fitting icon. +func _draw_item_shape(t: StringName) -> bool: + var dark := Color(0.10, 0.07, 0.05, 0.85) # shared outline + match t: + TYPE_BREAD: + # Two stacked brown loaves with a slash on the upper crust. + var crust := Color(0.62, 0.40, 0.18) + var glaze := Color(0.82, 0.58, 0.30) + draw_rect(Rect2(-6.0, -1.0, 12.0, 5.0), crust) + draw_rect(Rect2(-5.0, -5.0, 10.0, 4.0), glaze) + draw_line(Vector2(-3.0, -4.0), Vector2(0.0, -2.0), dark, 1.0) + draw_line(Vector2(0.0, -4.0), Vector2(3.0, -2.0), dark, 1.0) + draw_rect(Rect2(-6.0, -5.0, 12.0, 9.0), dark, false, 1.0) + return true + TYPE_GRAIN: + # Three vertical wheat stalks tied in the middle. + var stalk := Color(0.85, 0.70, 0.20) + var tie := Color(0.55, 0.35, 0.10) + draw_rect(Rect2(-4.0, -6.0, 1.5, 12.0), stalk) + draw_rect(Rect2(-0.75, -6.0, 1.5, 12.0), stalk) + draw_rect(Rect2(2.5, -6.0, 1.5, 12.0), stalk) + # Grain heads (small dots near top). + draw_circle(Vector2(-3.2, -5.0), 1.2, Color(0.95, 0.78, 0.25)) + draw_circle(Vector2(0.0, -5.5), 1.2, Color(0.95, 0.78, 0.25)) + draw_circle(Vector2(3.2, -5.0), 1.2, Color(0.95, 0.78, 0.25)) + draw_rect(Rect2(-5.0, 0.0, 10.0, 2.0), tie) + return true + TYPE_FLOUR: + # Cream-white sack with a darker drawstring at the neck. + var sack := Color(0.93, 0.90, 0.78) + var shadow := Color(0.78, 0.74, 0.60) + draw_rect(Rect2(-5.0, -3.0, 10.0, 8.0), sack) + draw_rect(Rect2(3.0, -3.0, 2.0, 8.0), shadow) + draw_rect(Rect2(-4.0, -5.0, 8.0, 2.0), Color(0.45, 0.30, 0.10)) # drawstring band + draw_circle(Vector2(0.0, -5.5), 1.0, sack) # gathered top puff + draw_rect(Rect2(-5.0, -6.0, 10.0, 11.0), dark, false, 1.0) + return true + TYPE_VEGETABLE: + # Green-leafed root vegetable (think turnip). + var leaf := Color(0.25, 0.65, 0.20) + var root := Color(0.92, 0.88, 0.70) + draw_circle(Vector2(0.0, 1.0), 5.0, root) + draw_arc(Vector2(0.0, 1.0), 5.0, 0.0, TAU, 16, dark, 1.0) + # Leaves + draw_rect(Rect2(-3.0, -5.0, 2.0, 3.0), leaf) + draw_rect(Rect2(-1.0, -6.0, 2.0, 4.0), leaf) + draw_rect(Rect2( 1.0, -5.0, 2.0, 3.0), leaf) + return true + TYPE_MEAL: + # Wooden bowl with a steamy meal heap. + var bowl := Color(0.50, 0.30, 0.12) + var bowl_rim := Color(0.30, 0.18, 0.08) + var food := Color(0.85, 0.55, 0.20) + # Bowl as a half-disc. + draw_circle(Vector2(0.0, 1.0), 6.0, bowl) + draw_rect(Rect2(-6.0, -5.0, 12.0, 6.0), Color(0, 0, 0, 0)) # cover top half (no-op, rely on z) + draw_arc(Vector2(0.0, 1.0), 6.0, 0.0, PI, 16, bowl_rim, 1.0) + draw_line(Vector2(-6.0, 1.0), Vector2(6.0, 1.0), bowl_rim, 1.0) + # Food mound on top. + draw_circle(Vector2(0.0, 0.0), 3.5, food) + # Two steam wisps. + draw_line(Vector2(-2.0, -5.0), Vector2(-1.0, -3.0), Color(0.9, 0.9, 0.9, 0.7), 1.0) + draw_line(Vector2(2.0, -5.0), Vector2(1.0, -3.0), Color(0.9, 0.9, 0.9, 0.7), 1.0) + return true + TYPE_MEAT: + # Raw red steak with a pale fat marbling line. + var meat := Color(0.78, 0.20, 0.20) + var fat := Color(0.95, 0.85, 0.70) + draw_rect(Rect2(-5.0, -3.0, 10.0, 7.0), meat) + draw_line(Vector2(-5.0, 0.0), Vector2(5.0, 0.5), fat, 1.0) + draw_rect(Rect2(-5.0, -3.0, 10.0, 7.0), dark, false, 1.0) + return true + TYPE_CLOTH: + # Folded cloth bolt — light blue with horizontal pleats. + var cloth := Color(0.50, 0.65, 0.85) + var pleat := Color(0.32, 0.42, 0.58) + draw_rect(Rect2(-5.0, -4.0, 10.0, 8.0), cloth) + draw_line(Vector2(-5.0, -1.5), Vector2(5.0, -1.5), pleat, 1.0) + draw_line(Vector2(-5.0, 1.5), Vector2(5.0, 1.5), pleat, 1.0) + draw_rect(Rect2(-5.0, -4.0, 10.0, 8.0), dark, false, 1.0) + return true + TYPE_MEDICINE: + # White phial with a red cross — medieval-ish but reads instantly. + var phial := Color(0.95, 0.95, 0.95) + var cross := Color(0.80, 0.15, 0.15) + draw_rect(Rect2(-4.0, -5.0, 8.0, 10.0), phial) + draw_rect(Rect2(-1.0, -3.0, 2.0, 6.0), cross) + draw_rect(Rect2(-3.0, -1.0, 6.0, 2.0), cross) + draw_rect(Rect2(-4.0, -5.0, 8.0, 10.0), dark, false, 1.0) + return true + TYPE_TOOL: + # Brown-handled hammer. + var handle := Color(0.50, 0.30, 0.12) + var head := Color(0.45, 0.45, 0.48) + draw_rect(Rect2(-1.0, -2.0, 2.0, 8.0), handle) + draw_rect(Rect2(-5.0, -5.0, 10.0, 4.0), head) + draw_rect(Rect2(-5.0, -5.0, 10.0, 4.0), dark, false, 1.0) + return true + TYPE_WEAPON: + # Sword: triangular blade + brown grip + crossguard. + var blade := Color(0.78, 0.80, 0.85) + var guard := Color(0.45, 0.30, 0.10) + # Blade (pointing up) + var pts: PackedVector2Array = PackedVector2Array([Vector2(0.0, -6.0), Vector2(2.5, 1.0), Vector2(-2.5, 1.0)]) + draw_colored_polygon(pts, blade) + # Crossguard + draw_rect(Rect2(-4.0, 1.0, 8.0, 2.0), guard) + # Grip + draw_rect(Rect2(-1.0, 3.0, 2.0, 3.0), guard) + return true + TYPE_ARMOR: + # Helmet silhouette — rounded grey dome with a nose-guard slit. + var steel := Color(0.65, 0.65, 0.70) + var visor := Color(0.20, 0.20, 0.25) + draw_circle(Vector2(0.0, 0.0), 5.5, steel) + draw_rect(Rect2(-1.0, -2.0, 2.0, 4.0), visor) + draw_rect(Rect2(-6.0, 4.0, 12.0, 2.0), steel) + draw_arc(Vector2(0.0, 0.0), 5.5, 0.0, TAU, 16, dark, 1.0) + return true + TYPE_STONE_BLOCK: + # Cleaned-up stone brick: pale grey rect with a chiseled corner. + var stone := Color(0.62, 0.60, 0.58) + var stone_hi := Color(0.78, 0.76, 0.72) + draw_rect(Rect2(-6.0, -4.0, 12.0, 8.0), stone) + draw_rect(Rect2(-6.0, -4.0, 12.0, 2.0), stone_hi) + draw_line(Vector2(-6.0, 0.0), Vector2(6.0, 0.0), Color(0.42, 0.40, 0.38), 1.0) + draw_rect(Rect2(-6.0, -4.0, 12.0, 8.0), dark, false, 1.0) + return true + TYPE_COPPER_ORE: + # Copper chunks — warm brown with bright highlights. + var copper := Color(0.65, 0.35, 0.18) + var hi := Color(0.92, 0.55, 0.20) + draw_circle(Vector2(-2.0, 1.0), 3.5, copper) + draw_circle(Vector2(2.0, -1.0), 2.8, copper) + draw_circle(Vector2(-2.0, 1.0), 1.5, hi) + draw_circle(Vector2(2.0, -1.0), 1.0, hi) + return true + TYPE_SILVER: + # Silver nugget — cool grey with a white highlight. + var silver := Color(0.78, 0.80, 0.85) + var hi := Color(0.98, 0.98, 1.00) + draw_circle(Vector2(-2.0, 1.0), 3.5, silver) + draw_circle(Vector2(2.0, -1.0), 2.8, silver) + draw_circle(Vector2(-2.0, 0.5), 1.2, hi) + return true + TYPE_ASH: + # Grey pile with faint smoke wisps. + var ash := Color(0.55, 0.55, 0.55) + var ash_hi := Color(0.78, 0.78, 0.78) + # Heap (low triangle) + var pts: PackedVector2Array = PackedVector2Array([Vector2(-6.0, 4.0), Vector2(6.0, 4.0), Vector2(0.0, -2.0)]) + draw_colored_polygon(pts, ash) + draw_line(Vector2(-3.0, 1.0), Vector2(3.0, 1.0), ash_hi, 1.0) + # Smoke wisps + draw_line(Vector2(-1.0, -3.0), Vector2(0.0, -5.0), Color(0.85, 0.85, 0.85, 0.6), 1.0) + draw_line(Vector2(1.5, -3.0), Vector2(2.5, -5.0), Color(0.85, 0.85, 0.85, 0.6), 1.0) + return true + _: + return false + + func _draw() -> void: - # Two render paths: types in _ITEM_SPRITES are painted by the Sprite2D child - # (built in setup()) — _draw() then only adds the quality border + stack - # count badge on top. Other types still use the procedural hue square so - # stockpile filtering remains visually unique while we expand the sprite set. + # Three render paths: + # 1. Atlas sprite (_ITEM_SPRITES): a Sprite2D child paints the icon; + # _draw() adds quality border + stack badge on top. + # 2. Procedural shape (_draw_item_shape returns true): bread/grain/ + # vegetable/meal/flour/meat/cloth/medicine/etc. get a recognisable + # silhouette in their category colour. + # 3. Unknown fallback: hue-hashed coloured square. Should be unreachable + # once every ALL_TYPES entry is handled above — kept for safety. var has_sprite: bool = _ITEM_SPRITES.has(item_type) var half: int = 6 if not has_sprite else 8 # border hugs the 16×16 sprite var square := Rect2(Vector2(-half, -half), Vector2(half * 2, half * 2)) if not has_sprite: - var hue := float(item_type.hash() % 360) / 360.0 - var fill := Color.from_hsv(hue, 0.6, 0.85) - draw_rect(square, fill) - draw_rect(square, Color(0.0, 0.0, 0.0, 0.75), false, 1.0) + if not _draw_item_shape(item_type): + var hue := float(item_type.hash() % 360) / 360.0 + var fill := Color.from_hsv(hue, 0.6, 0.85) + draw_rect(square, fill) + draw_rect(square, Color(0.0, 0.0, 0.0, 0.75), false, 1.0) # Quality border — drawn over the dark outline (or sprite), colour per quality tier. # NORMAL has no extra border.