class_name ArtStyle extends RefCounted ## One look of the playing field. Draws every graphic that Art names, with ## code. This base class draws plain placeholders; each style overrides the ## drawing of every id. ## ## The entries of `state`, by id: ## - GROUND, SOFT_ROCK, HARD_ROCK, BEDROCK, DEAD_SOIL and LIVING_SOIL: ## `solid`, a TileShape mask of the neighbours that are rock or the edge of ## the map; `living`, for LIVING_SOIL, the mask of living neighbours; ## `variant`, from TileShape.variant ## - CRACKS: `hits`, 1 or more; `variant` ## - HIT: `age`, from 0 when the pickaxe lands to 1 when the burst is over; ## `variant` ## - FIGURE: `player`, the player's number or -1; `facing`, 1.0 to the ## right or -1.0 to the left; `step`, an angle that grows while the figure ## walks; `moving` ## - MARKER: `reachable` ## - MOSS: `variant`; `time`, in seconds ## - VINE: `points`, the path of the vine; `time` ## - FIBER_PLANT: `growth`, from 0 (a seed) to 1 (grown); `time` ## - GRAZER: `hop`, from 0 to 1 during one hop; `facing` ## - RUIN: `awake`, from 0 (asleep) to 1 (awake); `time` ## - STONE: nothing ## The name of the style on the command line and in the settings. The base ## class is no style to choose and has none. func key() -> StringName: return &"" ## The key of the style's name in the texts, or an empty text for the base ## class. func title_key() -> String: return "" ## Draws the graphic `id` into `rect` on `canvas`, while `canvas` draws. func draw(canvas: CanvasItem, id: StringName, rect: Rect2, state: Dictionary) -> void: match id: Art.GROUND: _ground(canvas, rect, state) Art.SOFT_ROCK: _soft_rock(canvas, rect, state) Art.HARD_ROCK: _hard_rock(canvas, rect, state) Art.BEDROCK: _bedrock(canvas, rect, state) Art.CRACKS: _cracks(canvas, rect, state) Art.HIT: _hit(canvas, rect, state) Art.FIGURE: _figure(canvas, rect, state) Art.MARKER: _marker(canvas, rect, state) Art.DEAD_SOIL: _dead_soil(canvas, rect, state) Art.LIVING_SOIL: _living_soil(canvas, rect, state) Art.MOSS: _moss(canvas, rect, state) Art.VINE: _vine(canvas, rect, state) Art.FIBER_PLANT: _fiber_plant(canvas, rect, state) Art.STONE: _stone(canvas, rect, state) Art.GRAZER: _grazer(canvas, rect, state) Art.RUIN: _ruin(canvas, rect, state) _: push_error("no drawing for the graphic %s" % id) ## The colour of the figure of `player`. Players keep their colour; -1, a ## player not in the world yet, gets the first. func player_color(player: int) -> Color: return Color(0.95, 0.85, 0.6) if player <= 0 else Color(0.55, 0.8, 0.95) func _ground(canvas: CanvasItem, rect: Rect2, _state: Dictionary) -> void: canvas.draw_rect(rect, Color(0.42, 0.36, 0.28)) func _soft_rock(canvas: CanvasItem, rect: Rect2, _state: Dictionary) -> void: canvas.draw_rect(rect, Color(0.56, 0.55, 0.52)) func _hard_rock(canvas: CanvasItem, rect: Rect2, _state: Dictionary) -> void: canvas.draw_rect(rect, Color(0.33, 0.33, 0.37)) func _bedrock(canvas: CanvasItem, rect: Rect2, _state: Dictionary) -> void: canvas.draw_rect(rect, Color(0.12, 0.12, 0.14)) func _cracks(canvas: CanvasItem, rect: Rect2, state: Dictionary) -> void: var hits: int = state.get("hits", 1) var inset := rect.grow(-3.0) for line in hits: var part := (line + 1.0) / (hits + 1.0) var from := Vector2(inset.position.x, inset.position.y + inset.size.y * part) var to := Vector2(inset.end.x, inset.position.y + inset.size.y * (1.0 - part)) canvas.draw_line(from, to, Color(0.1, 0.08, 0.06, 0.8), 1.0) func _hit(canvas: CanvasItem, rect: Rect2, state: Dictionary) -> void: var age: float = state.get("age", 0.0) for chip in 4: var direction := Vector2.RIGHT.rotated(TAU * chip / 4.0 + 0.4) var at := rect.get_center() + direction * rect.size.x * 0.6 * age canvas.draw_rect(Rect2(at - Vector2(1, 1), Vector2(2, 2)), Color(0.8, 0.8, 0.75, 1.0 - age)) func _figure(canvas: CanvasItem, rect: Rect2, state: Dictionary) -> void: var radius := rect.size.x * 0.3 canvas.draw_circle(rect.get_center(), radius + 1.0, Color(0.15, 0.1, 0.05)) canvas.draw_circle(rect.get_center(), radius, player_color(state.get("player", -1))) func _marker(canvas: CanvasItem, rect: Rect2, state: Dictionary) -> void: var color := Color(0.4, 1.0, 0.4) if state.get("reachable", false) else Color(1.0, 0.4, 0.4) canvas.draw_rect(rect, color, false, 1.0) func _dead_soil(canvas: CanvasItem, rect: Rect2, _state: Dictionary) -> void: canvas.draw_rect(rect, Color(0.45, 0.42, 0.38)) func _living_soil(canvas: CanvasItem, rect: Rect2, _state: Dictionary) -> void: canvas.draw_rect(rect, Color(0.35, 0.5, 0.25)) func _moss(canvas: CanvasItem, rect: Rect2, _state: Dictionary) -> void: canvas.draw_circle(rect.get_center(), rect.size.x * 0.3, Color(0.3, 0.65, 0.3)) func _vine(canvas: CanvasItem, _rect: Rect2, state: Dictionary) -> void: var points: PackedVector2Array = state.get("points", PackedVector2Array()) if points.size() >= 2: canvas.draw_polyline(points, Color(0.25, 0.6, 0.25), 2.0) func _fiber_plant(canvas: CanvasItem, rect: Rect2, state: Dictionary) -> void: var growth: float = state.get("growth", 1.0) var foot := Vector2(rect.get_center().x, rect.end.y) canvas.draw_line(foot, foot - Vector2(0, rect.size.y * growth), Color(0.4, 0.7, 0.3), 2.0) func _stone(canvas: CanvasItem, rect: Rect2, _state: Dictionary) -> void: canvas.draw_circle(rect.get_center(), rect.size.x * 0.4, Color(0.6, 0.6, 0.58)) func _grazer(canvas: CanvasItem, rect: Rect2, state: Dictionary) -> void: var lift := sin(PI * float(state.get("hop", 0.0))) * rect.size.y * 0.3 canvas.draw_circle(rect.get_center() - Vector2(0, lift), rect.size.x * 0.35, Color(0.92, 0.9, 0.85)) func _ruin(canvas: CanvasItem, rect: Rect2, state: Dictionary) -> void: canvas.draw_rect(rect, Color(0.5, 0.5, 0.48)) var awake: float = state.get("awake", 0.0) canvas.draw_circle(rect.get_center(), rect.size.x * 0.2, Color(1.0, 0.85, 0.3, awake)) # Helpers for the styles. ## A share from 0 to 1 that is the same for the same `variant` and `salt`. static func share(variant: int, salt: int) -> float: return float(TileShape.variant(Vector2i(variant & 0xffff, salt)) % 10007) / 10007.0 ## A gentle swing from -1 to 1, slow and different for every `variant`. static func sway(time: float, variant: int, speed := 1.3) -> float: return sin(time * speed + share(variant, 7) * TAU) ## The point at `part` of the way along `points`, from 0 to 1. static func along(points: PackedVector2Array, part: float) -> Vector2: var total := 0.0 for i in points.size() - 1: total += points[i].distance_to(points[i + 1]) var left := clampf(part, 0.0, 1.0) * total for i in points.size() - 1: var length := points[i].distance_to(points[i + 1]) if left <= length: return points[i].lerp(points[i + 1], left / length if length > 0.0 else 0.0) left -= length return points[points.size() - 1]