Distinct icons for wheat / corn / potato / strawberry harvests

Wheat + corn both produce TYPE_GRAIN; potato + strawberry both produce
TYPE_VEGETABLE. Until now they rendered identically (yellow stalks for
both grains, green-leafed root for both vegetables) since shape was
driven by item_type alone.

Added an Item.subtype field that carries the origin crop_kind through
harvest. draw_item_shape dispatches on subtype FIRST then falls back
to item_type — so storage filters (which match on item_type) still
treat wheat+corn as one Grain category and potato+strawberry as one
Vegetable category, but the visuals are now distinct.

New procedural shapes:
- wheat: 3 yellow stalks with grain-heads (same as existing grain)
- corn: yellow cob with kernel dots wrapped in green husk leaves
- potato: 2 brown overlapping lumps with sprout-eye dots
- strawberry: red heart-shape body with green calyx + yellow seeds

Crop.on_harvest_tick assigns subtype = crop_kind on spawn.
SaveSystem._spawn_item now round-trips subtype through saves.
Pawn carry indicator + Item._draw both pass subtype to draw_item_shape.
This commit is contained in:
megaproxy 2026-05-16 15:52:31 +01:00
parent c7f97e2c7a
commit 413054157a
4 changed files with 97 additions and 3 deletions

View file

@ -407,6 +407,8 @@ func _spawn_item(world_scene: Node, d: Dictionary) -> void:
Vector2i(int(d.get("tile_x", 0)), int(d.get("tile_y", 0)))
)
ent.quality = int(d.get("quality", 1)) as Item.Quality
ent.subtype = StringName(d.get("subtype", ""))
ent.queue_redraw()
func _spawn_wall(world_scene: Node, d: Dictionary) -> void:

View file

@ -109,6 +109,11 @@ func on_harvest_tick() -> void:
var it: Item = ITEM_SCENE.instantiate()
get_parent().add_child(it)
it.setup(item_type, 1, tile)
# Carry the crop_kind through as a visual subtype so wheat / corn /
# potato / strawberry each render distinctly, while item_type stays
# generic (TYPE_GRAIN / TYPE_VEGETABLE) for stockpile filter purposes.
it.subtype = crop_kind
it.queue_redraw()
stage = Stage.TILLED
stage_progress = 0
_refresh_sprite_region()

View file

@ -84,6 +84,13 @@ enum Quality { SHODDY, NORMAL, EXCELLENT, MASTERWORK, LEGENDARY }
@export var stack_size: int = 1
@export var quality: Quality = Quality.NORMAL
## Visual subtype within the broader item_type. Lets multiple harvested produce
## share one storage-filter category (TYPE_GRAIN matches both wheat AND corn)
## while still rendering distinctly. Empty string = use item_type's default
## shape. Set by the spawning code (Crop.on_harvest_tick assigns "wheat"/"corn"
## /"potato"/"strawberry" based on crop_kind).
@export var subtype: StringName = &""
var tile: Vector2i = Vector2i.ZERO
## When true the on-floor visual is suppressed; the carrying pawn renders the
@ -154,6 +161,7 @@ func to_dict() -> Dictionary:
return {
"class_id": &"item",
"type": String(item_type),
"subtype": String(subtype),
"stack_size": stack_size,
"tile_x": tile.x,
"tile_y": tile.y,
@ -167,6 +175,7 @@ func to_dict() -> Dictionary:
static func from_dict(d: Dictionary) -> Dictionary:
return {
"type": StringName(d.get("type", "wood")),
"subtype": StringName(d.get("subtype", "")),
"stack_size": int(d.get("stack_size", 1)),
"tile_x": int(d.get("tile_x", 0)),
"tile_y": int(d.get("tile_y", 0)),
@ -187,8 +196,86 @@ static func from_dict(d: Dictionary) -> Dictionary:
## etc. Atlas-backed types (wood / stone / plank / iron_ore / gold) also have
## a shape here so the carry indicator works for them — the on-floor visual
## still uses the bundle icon via the Sprite2D child path.
static func draw_item_shape(target: CanvasItem, t: StringName) -> bool:
static func draw_item_shape(target: CanvasItem, t: StringName, sub: StringName = &"") -> bool:
var dark := Color(0.10, 0.07, 0.05, 0.85) # shared outline
# Subtype dispatch — lets wheat / corn / potato / strawberry render
# distinctly even though they share TYPE_GRAIN or TYPE_VEGETABLE for
# storage-filter purposes. Falls through to the type dispatch if subtype
# is unrecognised so existing items don't blank out.
match sub:
&"wheat":
# Yellow stalks with grain heads — same as default TYPE_GRAIN shape.
var stalk := Color(0.85, 0.70, 0.20)
var tie := Color(0.55, 0.35, 0.10)
target.draw_rect(Rect2(-4.0, -6.0, 1.5, 12.0), stalk)
target.draw_rect(Rect2(-0.75, -6.0, 1.5, 12.0), stalk)
target.draw_rect(Rect2(2.5, -6.0, 1.5, 12.0), stalk)
target.draw_circle(Vector2(-3.2, -5.0), 1.2, Color(0.95, 0.78, 0.25))
target.draw_circle(Vector2(0.0, -5.5), 1.2, Color(0.95, 0.78, 0.25))
target.draw_circle(Vector2(3.2, -5.0), 1.2, Color(0.95, 0.78, 0.25))
target.draw_rect(Rect2(-5.0, 0.0, 10.0, 2.0), tie)
return true
&"corn":
# Corn cob — yellow body with rows of kernel dots + green husk.
var husk := Color(0.30, 0.65, 0.20)
var cob := Color(0.95, 0.82, 0.30)
var kernel_dark := Color(0.70, 0.55, 0.15)
# Husk leaves splayed out at the top.
var husk_l: PackedVector2Array = PackedVector2Array([Vector2(-4.0, -5.0), Vector2(-2.0, -2.0), Vector2(-5.0, -1.0)])
var husk_r: PackedVector2Array = PackedVector2Array([Vector2(4.0, -5.0), Vector2(2.0, -2.0), Vector2(5.0, -1.0)])
target.draw_colored_polygon(husk_l, husk)
target.draw_colored_polygon(husk_r, husk)
# Cob body — vertical oval/rect with rounded corners.
target.draw_rect(Rect2(-3.0, -4.0, 6.0, 10.0), cob)
target.draw_circle(Vector2(0.0, 5.5), 3.0, cob)
# Kernel dots — 2 columns × 3 rows for texture.
for y in [-2.0, 0.0, 2.0, 4.0]:
target.draw_rect(Rect2(-2.0, y, 1.5, 1.5), kernel_dark)
target.draw_rect(Rect2(0.5, y, 1.5, 1.5), kernel_dark)
# Cob outline (open at top because of husk).
target.draw_arc(Vector2(0.0, 5.5), 3.0, 0.0, PI, 12, dark, 1.0)
return true
&"potato":
# Two brown lumps with sprout-eye dots — pile of potatoes.
var skin := Color(0.62, 0.45, 0.25)
var skin_dark := Color(0.42, 0.28, 0.15)
var eye := Color(0.25, 0.15, 0.05)
# Two overlapping potato ovals.
target.draw_circle(Vector2(-2.0, 1.0), 4.0, skin)
target.draw_circle(Vector2(2.5, -0.5), 3.5, skin)
# Outline.
target.draw_arc(Vector2(-2.0, 1.0), 4.0, 0.0, TAU, 16, skin_dark, 1.0)
target.draw_arc(Vector2(2.5, -0.5), 3.5, 0.0, TAU, 12, skin_dark, 1.0)
# Eye dots.
target.draw_circle(Vector2(-3.0, 0.0), 0.7, eye)
target.draw_circle(Vector2(-0.5, 2.0), 0.6, eye)
target.draw_circle(Vector2(3.5, -1.5), 0.7, eye)
return true
&"strawberry":
# Classic red strawberry — heart-shape body with green calyx on top
# and tiny yellow seed dots scattered on the surface.
var berry := Color(0.88, 0.18, 0.20)
var berry_dark := Color(0.62, 0.10, 0.10)
var leaf := Color(0.25, 0.60, 0.20)
var seed := Color(0.95, 0.85, 0.30)
# Body — wider top tapering to a point at the bottom.
var body: PackedVector2Array = PackedVector2Array([
Vector2(-5.0, -1.0), Vector2(-3.5, -3.0), Vector2(3.5, -3.0),
Vector2(5.0, -1.0), Vector2(3.0, 4.0), Vector2(0.0, 6.0), Vector2(-3.0, 4.0),
])
target.draw_colored_polygon(body, berry)
# Body outline.
target.draw_polyline(body + PackedVector2Array([body[0]]), berry_dark, 1.0)
# Green calyx (leaves) on top.
target.draw_rect(Rect2(-3.0, -5.0, 6.0, 2.0), leaf)
var leaf_top: PackedVector2Array = PackedVector2Array([Vector2(-2.0, -5.0), Vector2(0.0, -7.0), Vector2(2.0, -5.0)])
target.draw_colored_polygon(leaf_top, leaf)
# Seed speckles.
target.draw_circle(Vector2(-2.0, 1.0), 0.5, seed)
target.draw_circle(Vector2(2.0, 1.0), 0.5, seed)
target.draw_circle(Vector2(0.0, 3.0), 0.5, seed)
return true
# Fall through to type dispatch.
match t:
TYPE_BREAD:
var crust := Color(0.62, 0.40, 0.18)
@ -375,7 +462,7 @@ func _draw() -> void:
var square := Rect2(Vector2(-half, -half), Vector2(half * 2, half * 2))
if not has_sprite:
if not Item.draw_item_shape(self, item_type):
if not Item.draw_item_shape(self, item_type, subtype):
var hue := float(item_type.hash() % 360) / 360.0
var fill := Color.from_hsv(hue, 0.6, 0.85)
draw_rect(square, fill)

View file

@ -1184,7 +1184,7 @@ func _draw() -> void:
var ci_center := Vector2(7.0, -12.0)
var ci_scale := 0.55
draw_set_transform(ci_center, 0.0, Vector2(ci_scale, ci_scale))
if not Item.draw_item_shape(self, carried_item.item_type):
if not Item.draw_item_shape(self, carried_item.item_type, carried_item.subtype):
var ci_hue := float(carried_item.item_type.hash() % 360) / 360.0
var ci_color := Color.from_hsv(ci_hue, 0.6, 0.85)
draw_rect(Rect2(-6, -6, 12, 12), ci_color)