rimlike/scenes/entities/rock.gd
megaproxy 2de5130ae0 Visual pass 2: tree + rock + stone wall sprite swaps
Replaces three procedural _draw() entities with bundle sprites:

- Tree: was draw_rect trunk + draw_circle canopy. Now Sprite2D using
  FG_Tree_Spring.png (64x80, 4 variants picked deterministically from
  tile coord). Bottom-anchored so trunk base sits at tile bottom, canopy
  rises into the cell above; y_sort_enabled so canopies tuck behind
  pawns south of the trunk. Chop-progress notch overlay retained.
- Rock: was draw_colored_polygon hex. Now Sprite2D reading from the
  existing FG_Grasslands_Spring.png decoration atlas at three eyeballed
  coords (2 gray boulders, 1 brown rock pile). Variant deterministic
  per tile. Mine-progress crack overlay retained.
- Stone wall: was procedural top-band + front-band + mortar lines. Now
  Sprite2D from FG_Fortress.png at (1,1) — clean tan-stone brick fill.
  Bottom-anchored (offset.y=-8) so the 16x16 sprite spans y=-16..0,
  matching the procedural draw box exactly. Ghost state via modulate.a.
  Wood walls still use procedural _draw_wood_wall — no clean 16x16 wood
  tile found in the bundle yet (Pixel Crawler Walls.png is 32x32, would
  need crop+rescale).

Asset additions:
- art/sprites/FG_Tree_Spring.png (Tier 1, Grasslands pack)
- FG_Fortress.png and FG_Grasslands_Spring.png were already in art/tiles
  from earlier passes; this commit just consumes them from new sites.

Headless boots clean, runtime verified: trees look like chunky pixel-art
trees with root flare, rocks read as real boulders, cabin walls show
proper brick texture.

License: all ElvGames Humble bundle — commercial OK with credit.
Credit-string compilation still open.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 14:19:06 +01:00

148 lines
5.5 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## Rock entity — mineable by a pawn with a Mine job. Drops a stone Item node
## when mined out.
##
## Mirrors Tree's chopping model; stone is harder so MINE_TICKS is longer.
## A MineProvider (Opus, Phase 4) creates a Job whose INTERACT toil calls
## on_mine_tick() once per sim tick via JobRunner.
##
## World registration (World.register_rock / World.unregister_rock) is called
## here but the methods land in World during Opus integration.
class_name Rock extends Node2D
const TILE_SIZE_PX: int = 16
## Sim ticks to mine a rock at 1× speed (120 ticks = 6 sim seconds at 20 Hz).
## Stone is harder than wood — MINE_TICKS > Tree.CHOP_TICKS.
const MINE_TICKS: int = 120
## Stone Items dropped on a successful mine.
const STONE_DROPS_ON_MINE: int = 1
# ── state ─────────────────────────────────────────────────────────────────────
var tile: Vector2i = Vector2i.ZERO
## 0..MINE_TICKS. Advanced by on_mine_tick(); rock is mined when equal to MINE_TICKS.
var mine_progress: int = 0
# Preloaded scene for spawned stone items.
const ITEM_SCENE: PackedScene = preload("res://scenes/entities/item.tscn")
## Rock sprite atlas — re-uses the Grasslands tileset (already imported for the
## decoration overlay). The three coords below were eyeballed from the
## /tmp/rocks_review.png probe in the 2026-05-12 visual pass: two gray boulders
## and one brown rock for variety.
const _ROCK_TEX: Texture2D = preload("res://art/tiles/FG_Grasslands_Spring.png")
const _ROCK_VARIANT_COORDS: Array[Vector2i] = [
Vector2i(24, 7), # gray boulder A
Vector2i(28, 7), # gray boulder B
Vector2i(12, 13), # brown rock pile
]
# ── lifecycle ─────────────────────────────────────────────────────────────────
func _ready() -> void:
position = _tile_to_world(tile)
_build_sprite()
World.register_rock(self)
## Adds a Sprite2D child with one of the rock variants. Variant chosen
## deterministically from the tile coord so the same tile renders the same
## rock across boots and load/save.
func _build_sprite() -> void:
var sprite := Sprite2D.new()
sprite.name = "Sprite"
sprite.texture = _ROCK_TEX
sprite.region_enabled = true
var coord: Vector2i = _ROCK_VARIANT_COORDS[(tile.x * 31 + tile.y * 17) % _ROCK_VARIANT_COORDS.size()]
sprite.region_rect = Rect2(coord.x * TILE_SIZE_PX, coord.y * TILE_SIZE_PX, TILE_SIZE_PX, TILE_SIZE_PX)
sprite.centered = true
sprite.offset = Vector2.ZERO # 16×16 tile, sits centered on the tile
add_child(sprite)
func _exit_tree() -> void:
World.unregister_rock(self)
# ── public API ────────────────────────────────────────────────────────────────
## One-shot initialiser. Call after add_child() so _ready() already fired.
func setup(start_tile: Vector2i) -> void:
tile = start_tile
mine_progress = 0
position = _tile_to_world(tile)
queue_redraw()
Audit.log("rock", "spawned at %s" % tile)
## True when the rock hasn't been fully mined yet.
func is_mineable() -> bool:
return mine_progress < MINE_TICKS
## Called by the INTERACT toil in JobRunner once per sim tick while the pawn
## works this rock. Advances mine_progress and triggers mined() when complete.
func on_mine_tick() -> void:
if not is_mineable():
return
mine_progress += 1
queue_redraw()
if mine_progress >= MINE_TICKS:
mined()
## Drop stone Item(s) and free this node. Called automatically by on_mine_tick()
## but also accessible for scripted removal (debug, storyteller events).
func mined() -> void:
# Single drop lands on the rock's own tile.
var item: Item = ITEM_SCENE.instantiate()
get_parent().add_child(item)
item.setup(Item.TYPE_STONE, 1, tile)
Audit.log("rock", "mined at %s; %d stone drop" % [tile, STONE_DROPS_ON_MINE])
queue_free()
# ── save / load ───────────────────────────────────────────────────────────────
func to_dict() -> Dictionary:
return {
"class_id": &"rock",
"tile_x": tile.x,
"tile_y": tile.y,
"mine_progress": mine_progress,
}
static func from_dict(d: Dictionary) -> Dictionary:
return {
"tile_x": int(d.get("tile_x", 0)),
"tile_y": int(d.get("tile_y", 0)),
"mine_progress": int(d.get("mine_progress", 0)),
}
# ── render ────────────────────────────────────────────────────────────────────
func _draw() -> void:
# Rock body comes from the Sprite2D child (see _build_sprite).
# This _draw renders only the mine-progress crack overlaid on the sprite.
if mine_progress > 0:
var ratio := float(mine_progress) / float(MINE_TICKS)
var crack_len := ratio * 5.0
draw_line(
Vector2(-1.0, -2.0),
Vector2(-1.0 + crack_len, 1.0),
Color(0.15, 0.12, 0.10, 0.85),
1.5
)
# ── helpers ───────────────────────────────────────────────────────────────────
func _tile_to_world(t: Vector2i) -> Vector2:
return Vector2(
t.x * TILE_SIZE_PX + TILE_SIZE_PX / 2.0,
t.y * TILE_SIZE_PX + TILE_SIZE_PX / 2.0
)