rimlike/scenes/entities/rock.gd
megaproxy 725d3fb701 Rock: replace cluster-piece tiles with standalone single-tile boulders
The earlier coords (24,7), (28,7), (12,13) in FG_Grasslands_Spring.png are
autotile interior pieces — clipping their 16×16 windows produces sprites that
visibly continue beyond the tile edge, betraying the autotile cluster they
were cut from.

Replace with six clean single-tile boulders from the rock band (x=16..29 at
y=3 and y=5). Each has a green margin on all four edges, so they read as
proper standalone rocks. Two color families (brown + gray) × three sizes
(round / peaked / squat) gives more visual variety than before too.

Multi-tile big-boulder formations at (22..23, 3..4) brown and (30..31, 3..4)
gray are noted in the comment for a future BigRock entity.

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

157 lines
6 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). These coords pick standalone single-tile boulders from
## the rock band (x=16..29, y=3 and y=5) — chosen because each has a clean
## green margin on all four sides, so they read as separate small rocks rather
## than chunks of a tiled cluster. The earlier (24,7)/(28,7)/(12,13) coords
## were autotile interior pieces; replaced 2026-05-12 after the user flagged
## the obvious tile-cluster artifacts on small rocks.
##
## Multi-tile cluster formations live at (22..23, 3..4) brown and (30..31, 3..4)
## gray — those are reserved for the upcoming BigRock entity (2×2 boulder).
const _ROCK_TEX: Texture2D = preload("res://art/tiles/FG_Grasslands_Spring.png")
const _ROCK_VARIANT_COORDS: Array[Vector2i] = [
Vector2i(16, 3), # brown round, medium
Vector2i(20, 3), # brown peaked, smaller
Vector2i(16, 5), # brown squat, low
Vector2i(24, 3), # gray round, medium
Vector2i(28, 3), # gray peaked, smaller
Vector2i(24, 5), # gray squat, low
]
# ── 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
)