Files
TheTree_mock/scripts/ui/pixel_tower_art.gd
T
TakeshiandClaude Opus 5.5 0cfaec1626 P14-02: Add buildable Mana warp (A07)
2x2 FRONT warp at 3000G/30 materials/360 min, 15G/day and 1 Mana upkeep.
Every completed warp pair is linked by one path connector, so crossing is a
single step; a 3-Mana fare per rider is charged once in the step's tick.
Warps close while either end is unpowered, the reserve is empty or below one
fare, and blocked arrivals offer no step. Saves validate the derived links.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 10:45:28 +09:00

290 lines
15 KiB
GDScript

class_name PixelTowerArt
extends RefCounted
## Small, code-native room furnishings. Coordinates use the same pixel size as
## Kenney's 16 px sprites. This renderer never changes simulation state.
const Layout := preload("res://scripts/ui/room_presentation.gd")
const StandardRooms := preload("res://scripts/ui/standard_room_art.gd")
const InfirmaryRooms := preload("res://scripts/ui/infirmary_room_art.gd")
const INK := Color("34413b")
const WOOD := Color("8e6348")
const WOOD_LIGHT := Color("c39665")
const CREAM := Color("eee5c9")
const TEAL := Color("608d84")
static func tile(canvas: CanvasItem, texture: Texture2D, index: int, target: Rect2, tint := Color.WHITE) -> void:
if texture == null:
return
canvas.draw_texture_rect_region(texture, target, Rect2((index % 12) * 16, (index / 12) * 16, 16, 16), tint)
static func room(canvas: CanvasItem, rect: Rect2, id: StringName, pixel: float, dungeon: Texture2D, town: Texture2D, back: bool, decoration_seed := 0, presentation: Dictionary = {}, visual_level: int = 1) -> void:
var p := pixel
if id in [&"guest_stairs", &"service_stairs"]:
stairs(canvas, rect, p, id == &"service_stairs")
return
if id in [&"guest_corridor", &"service_corridor"]:
canvas.draw_rect(rect, Color("c6b999") if not back else Color("a6b6a0"))
canvas.draw_rect(Rect2(rect.position.x,rect.end.y-4*p,rect.size.x,3*p),WOOD_LIGHT)
canvas.draw_rect(Rect2(rect.position.x,rect.end.y-p,rect.size.x,p),INK)
canvas.draw_rect(Rect2(rect.position.x,rect.position.y,rect.size.x,p),WOOD)
if posmod(decoration_seed,3)==0:
window(canvas,Vector2(rect.get_center().x-3*p,rect.position.y+5*p),p)
return
var dark := id in [&"tavern", &"adult_private_room", &"massage_room"]
var wall := Color("d4c7a5") if not back else Color("acb9a5")
if dark:
wall = Color("897487")
canvas.draw_rect(rect, INK)
var inner := rect.grow(-p)
canvas.draw_rect(inner, wall)
# Plaster, dado rail and floor boards read as interiors, even at a distance.
canvas.draw_rect(Rect2(inner.position, Vector2(inner.size.x, p)), Color("f1e9cb"))
var floor_y := rect.end.y - 4.0 * p
canvas.draw_rect(Rect2(Vector2(inner.position.x, floor_y - 4.0 * p), Vector2(inner.size.x, 4.0 * p)), Color("b39b77") if not dark else Color("615563"))
canvas.draw_rect(Rect2(Vector2(inner.position.x, floor_y), Vector2(inner.size.x, 3.0 * p)), WOOD)
canvas.draw_line(Vector2(inner.position.x, floor_y), Vector2(inner.end.x, floor_y), WOOD_LIGHT, p)
for x in range(int(inner.position.x + 2 * p), int(inner.end.x), maxi(int(8 * p), 1)):
canvas.draw_rect(Rect2(x, floor_y + p, 5 * p, p), Color("a57852"))
var mid := rect.get_center().x
var w := rect.size.x
var top := rect.position.y
if id in [&"guest_elevator", &"service_elevator"]:
elevator_shaft(canvas, rect, p, back)
return
if id in [&"guest_corridor", &"service_corridor", &"staff_door"]:
if id == &"staff_door":
door(canvas, Vector2(mid-4*p, floor_y-14*p), p, Color("587f7b"))
else:
window(canvas, Vector2(mid-3*p, top+5*p), p)
return
# A window and small wall lamp leave the lower part clear for live actors.
if w >= 28*p:
window(canvas, Vector2(rect.position.x+5*p, top+5*p), p)
window(canvas, Vector2(rect.end.x-12*p, top+5*p), p)
else:
window(canvas, Vector2(mid-3*p, top+4*p), p)
var lamp_x := mid if w > 32*p else rect.end.x-4*p
canvas.draw_rect(Rect2(lamp_x-p, top+3*p, 2*p, 3*p), WOOD)
canvas.draw_rect(Rect2(lamp_x-2*p, top+5*p, 4*p, 2*p), Color("edc97c"))
if id == &"standard_room":
StandardRooms.wall_trim(canvas, rect, p, back, visual_level)
match id:
&"reception":
counter(canvas, Vector2(mid-9*p, floor_y-6*p), 18*p, p)
canvas.draw_rect(Rect2(mid-6*p, floor_y-8*p, 4*p, 2*p), CREAM)
canvas.draw_rect(Rect2(mid+4*p, floor_y-8*p, 2*p, 2*p), Color("d6ad52"))
&"standard_room":
for at: Vector2 in Layout.slots(rect, p, id):
var bed_at: Vector2 = at + Vector2(-7, -9)*p
bed(canvas, bed_at, p, Color("709b94") if not dark else Color("a37b91"), 13)
StandardRooms.bed_trim(canvas, bed_at, p, back, visual_level)
&"infirmary":
InfirmaryRooms.draw_room(canvas,rect,p,back,visual_level,dungeon)
&"premium_room", &"staff_dormitory", &"adult_private_room":
for at: Vector2 in Layout.slots(rect, p, id):
bed(canvas, at + Vector2(-7, -9)*p, p, Color("709b94") if not dark else Color("a37b91"), 13)
&"cafeteria", &"tavern":
for at: Vector2 in Layout.slots(rect, p, id):
table(canvas, at + Vector2(-2, -7)*p, p)
&"chapel":
chapel_furnishings(canvas, rect, p)
&"mana_warp":
# Original code-drawn transfer circle and rising motes; existing palette.
var ring := Vector2(mid, floor_y - 3.0 * p)
canvas.draw_circle(ring, 9.0 * p, INK)
canvas.draw_circle(ring, 8.0 * p, TEAL)
canvas.draw_circle(ring, 6.0 * p, Color("c1e0d4"))
canvas.draw_circle(ring, 4.0 * p, TEAL)
for i in 5:
var x := mid + (i - 2) * 4.0 * p
canvas.draw_rect(Rect2(x, floor_y - (9 + (i % 3) * 5) * p, p, 2.0 * p), CREAM)
&"public_bath":
canvas.draw_rect(Rect2(rect.position.x+4*p, floor_y-7*p, w-8*p, 7*p), CREAM)
canvas.draw_rect(Rect2(rect.position.x+6*p, floor_y-6*p, w-12*p, 4*p), Color("72b6b8"))
canvas.draw_line(Vector2(rect.position.x+8*p,floor_y-5*p), Vector2(rect.end.x-10*p,floor_y-5*p), Color("c1e0d4"), p)
&"kitchen":
counter(canvas, Vector2(rect.position.x+3*p, floor_y-7*p), w-6*p, p)
for x in range(int(rect.position.x+5*p), int(rect.end.x-9*p), int(12*p)):
canvas.draw_rect(Rect2(x, floor_y-9*p, 7*p, 2*p), Color("445652"))
canvas.draw_rect(Rect2(x+2*p, floor_y-11*p, 4*p, 3*p), Color("a2b2a3"))
&"warehouse":
for x in range(int(rect.position.x+2*p), int(rect.end.x-12*p), int(13*p)):
tile(canvas, dungeon, 63, Rect2(x, floor_y-12*p, 12*p, 12*p))
&"cleaning_room":
# Original code-drawn supply rack, mop and bucket; existing palette.
counter(canvas, Vector2(rect.position.x+3*p, floor_y-10*p), 10*p, p)
for shelf in 2:
canvas.draw_rect(Rect2(rect.position.x+4*p,floor_y-(12+3*shelf)*p,8*p,2*p),CREAM)
canvas.draw_rect(Rect2(mid+3*p,floor_y-16*p,p,14*p),WOOD_LIGHT)
canvas.draw_rect(Rect2(mid+p,floor_y-3*p,6*p,2*p),CREAM)
canvas.draw_rect(Rect2(rect.end.x-9*p,floor_y-6*p,5*p,5*p),TEAL)
canvas.draw_rect(Rect2(rect.end.x-8*p,floor_y-7*p,3*p,p),INK)
&"staff_waiting_room":
table(canvas, Vector2(mid-6*p, floor_y-7*p), p)
tile(canvas, dungeon, 89, Rect2(rect.position.x+2*p, floor_y-12*p, 12*p, 12*p))
&"security_station":
counter(canvas, Vector2(mid-8*p, floor_y-6*p), 16*p, p)
tile(canvas, dungeon, 105, Rect2(rect.position.x+3*p, floor_y-17*p, 12*p, 12*p))
&"massage_room":
bed(canvas, Vector2(mid-8*p, floor_y-9*p), p, Color("d3aa8f"))
_:
table(canvas, Vector2(mid-6*p, floor_y-7*p), p)
if not presentation.is_empty():
if rect.size.y >= 40*p and id != &"chapel":
var workers: Array = presentation.get(&"staff", [])
var columns := maxi(int(w / (16*p)), 1)
if not workers.is_empty():
canvas.draw_rect(Rect2(rect.position.x+p,top+24*p,w-2*p,p), WOOD_LIGHT)
for slot: int in mini(workers.size(), columns):
counter(canvas, Vector2(rect.position.x+(slot+0.5)*w/columns-6*p, top+20*p), 12*p, p)
var operation := int(presentation[&"operation"])
if operation != FacilityInstance.OperationState.OPERATING:
canvas.draw_rect(rect.grow(-p), Color(0.12, 0.16, 0.18, 0.42))
# A closed shutter differs in shape as well as colour from an open room.
for y: int in range(int(top+10*p), int(top+17*p), maxi(int(3*p), 1)):
canvas.draw_rect(Rect2(rect.position.x+2*p, y, w-4*p, p), WOOD)
if operation == FacilityInstance.OperationState.SUSPENDED:
canvas.draw_line(rect.position+Vector2(3,10)*p, rect.end-Vector2(3,5)*p, Color("ca9c62"), 2*p)
canvas.draw_line(Vector2(rect.end.x-3*p,top+10*p), Vector2(rect.position.x+3*p,rect.end.y-5*p), Color("ca9c62"), 2*p)
elif int(presentation[&"count"]) > 0:
canvas.draw_rect(Rect2(lamp_x-2*p, top+5*p, 4*p, 2*p), Color("ffe9a8"))
## The upper landing owns a short flight down to the floor below. Six square
## treads and a stepped handrail keep the shape legible at the 16 px room scale.
static func chapel_furnishings(canvas: CanvasItem, rect: Rect2, p: float) -> void:
# Two rows share the live visitor anchors. Keep the central aisle clear;
# the altar and world-tree glass occupy the otherwise unused upper wall.
var mid := rect.get_center().x
var top := rect.position.y
canvas.draw_rect(Rect2(mid-6*p,top+3*p,12*p,19*p), INK)
canvas.draw_rect(Rect2(mid-5*p,top+4*p,10*p,17*p), Color("6c8993"))
canvas.draw_rect(Rect2(mid-4*p,top+6*p,8*p,13*p), Color("adc5b4"))
canvas.draw_rect(Rect2(mid-p,top+9*p,2*p,10*p), WOOD)
for row in 3:
var width := float(row+1)*2*p
canvas.draw_rect(Rect2(mid-width/2,top+(6+row*3)*p,width,3*p), Color("52836c"))
canvas.draw_rect(Rect2(mid-p,top+5*p,2*p,p), Color("edc97c"))
var altar_y := top+28*p
counter(canvas,Vector2(mid-9*p,altar_y),18*p,p)
canvas.draw_rect(Rect2(mid-9*p,altar_y,18*p,2*p), CREAM)
canvas.draw_rect(Rect2(mid-2*p,altar_y+2*p,4*p,4*p), TEAL)
for direction in [-1,1]:
canvas.draw_rect(Rect2(mid+direction*6*p,altar_y-3*p,p,3*p), CREAM)
canvas.draw_rect(Rect2(mid+direction*6*p,altar_y-4*p,p,p), Color("edc97c"))
var rows: Dictionary = {}
for at: Vector2 in Layout.slots(rect,p,&"chapel"):
rows[at.y] = true
for y: float in rows:
for side in 2:
var x := rect.position.x+3*p if side==0 else mid+3*p
var width := rect.size.x/2-6*p
canvas.draw_rect(Rect2(x,y-8*p,width,5*p), WOOD)
canvas.draw_rect(Rect2(x,y-8*p,width,p), WOOD_LIGHT)
canvas.draw_rect(Rect2(x-p,y-3*p,width+2*p,2*p), WOOD_LIGHT)
canvas.draw_rect(Rect2(x,y-p,p,p), INK)
canvas.draw_rect(Rect2(x+width-p,y-p,p,p), INK)
static func stairs(canvas: CanvasItem, rect: Rect2, p: float, staff_only: bool, compact := false) -> void:
if not compact:
canvas.draw_rect(rect, INK)
canvas.draw_rect(rect.grow(-p), Color("c7c6a6") if not staff_only else Color("a8b6aa"))
canvas.draw_rect(Rect2(rect.position.x, rect.end.y-4*p, rect.size.x, 3*p), WOOD)
canvas.draw_rect(Rect2(rect.position.x, rect.end.y-p, rect.size.x, p), INK)
var at := Vector2(rect.get_center().x-6*p, rect.end.y-5*p)
var tread := Color("d8bf89") if not staff_only else Color("aec0bf")
var rise := Color("8a7155") if not staff_only else Color("617f82")
for step: int in range(6):
var top := at + Vector2(step*2, -step*2)*p
canvas.draw_rect(Rect2(top, Vector2(2, 2+step*2)*p), rise)
canvas.draw_rect(Rect2(top, Vector2(3, 1)*p), tread)
canvas.draw_rect(Rect2(top+Vector2(0,-5)*p, Vector2(2,1)*p), INK)
if step % 2 == 0:
canvas.draw_rect(Rect2(top+Vector2(0,-4)*p, Vector2(1,4)*p), WOOD if not staff_only else TEAL)
canvas.draw_rect(Rect2(at+Vector2(10,-11)*p, Vector2(3,2)*p), tread)
if staff_only and not compact:
canvas.draw_rect(Rect2(rect.position+Vector2(2,2)*p, Vector2(3,3)*p), TEAL)
canvas.draw_rect(Rect2(rect.position+Vector2(3,3)*p, Vector2(1,1)*p), CREAM)
## Static shaft rails remain when a car is stopped or the transport service is
## unavailable. The car itself is drawn once from its authoritative trip state.
static func elevator_shaft(canvas: CanvasItem, rect: Rect2, p: float, back: bool) -> void:
canvas.draw_rect(rect, INK)
canvas.draw_rect(rect.grow(-p), Color("3e514e") if not back else Color("404f63"))
canvas.draw_rect(Rect2(rect.position.x+2*p, rect.position.y, 2*p, rect.size.y), Color("83958a"))
canvas.draw_rect(Rect2(rect.end.x-4*p, rect.position.y, 2*p, rect.size.y), Color("83958a"))
canvas.draw_rect(Rect2(rect.get_center().x-p, rect.position.y, 2*p, rect.size.y), Color("283a39"))
for row: int in range(2):
var floor_y := rect.position.y + float(row+1)*rect.size.y*0.5
canvas.draw_rect(Rect2(rect.position.x, floor_y-4*p, rect.size.x, 3*p), Color("586c62") if not back else Color("526177"))
canvas.draw_rect(Rect2(rect.position.x, floor_y-p, rect.size.x, p), INK)
canvas.draw_rect(Rect2(rect.position.x+p, floor_y-7*p, p, 2*p), Color("dcc08b"))
canvas.draw_rect(Rect2(rect.end.x-2*p, floor_y-7*p, p, 2*p), Color("dcc08b"))
static func elevator_cabin(canvas: CanvasItem, rect: Rect2, p: float, back: bool, operational: bool) -> void:
canvas.draw_rect(rect, INK)
canvas.draw_rect(rect.grow(-p), Color("9daa8d") if not back else Color("879eaa"))
canvas.draw_rect(Rect2(rect.position+Vector2(2,3)*p,rect.size-Vector2(4,5)*p),Color("556e62") if not back else Color("4d6472"))
canvas.draw_rect(Rect2(rect.position.x+p,rect.end.y-3*p,rect.size.x-2*p,2*p),WOOD_LIGHT)
canvas.draw_rect(Rect2(rect.position.x+2*p,rect.position.y+p,rect.size.x-4*p,p),CREAM)
canvas.draw_rect(Rect2(rect.get_center().x-p,rect.position.y-p,2*p,p),Color("e2bd6c") if operational else Color("ba7157"))
static func elevator_gate(canvas: CanvasItem, rect: Rect2, p: float, doors_open: bool) -> void:
# The cutaway keeps passengers visible through the iron gate during travel.
var rail := Color("bfbb9a")
canvas.draw_rect(Rect2(rect.position.x,rect.position.y,2*p,rect.size.y),rail)
canvas.draw_rect(Rect2(rect.end.x-2*p,rect.position.y,2*p,rect.size.y),rail)
if doors_open:
return
var gate_y := rect.end.y-7*p
canvas.draw_rect(Rect2(rect.position.x+2*p,gate_y,rect.size.x-4*p,p),rail)
for x: int in range(int(rect.position.x+5*p),int(rect.end.x-3*p),maxi(int(6*p),1)):
canvas.draw_rect(Rect2(x,rect.position.y+4*p,p,rect.size.y-7*p),Color("a9aa91",0.8))
static func window(canvas: CanvasItem, at: Vector2, p: float) -> void:
canvas.draw_rect(Rect2(at, Vector2(7,9)*p), WOOD)
canvas.draw_rect(Rect2(at+Vector2.ONE*p, Vector2(5,6)*p), Color("9ab9b3"))
canvas.draw_rect(Rect2(at+Vector2(2,1)*p, Vector2(2,2)*p), Color("d5e3d0"))
canvas.draw_rect(Rect2(at+Vector2(3,1)*p, Vector2(1,6)*p), WOOD_LIGHT)
canvas.draw_rect(Rect2(at+Vector2(1,4)*p, Vector2(5,1)*p), WOOD_LIGHT)
canvas.draw_rect(Rect2(at+Vector2(-1,8)*p, Vector2(9,1)*p), WOOD_LIGHT)
static func door(canvas: CanvasItem, at: Vector2, p: float, color: Color) -> void:
canvas.draw_rect(Rect2(at, Vector2(8,14)*p), INK)
canvas.draw_rect(Rect2(at+Vector2.ONE*p, Vector2(6,13)*p), color)
canvas.draw_rect(Rect2(at+Vector2(5,8)*p, Vector2.ONE*p), Color("f2ca81"))
static func bed(canvas: CanvasItem, at: Vector2, p: float, cover: Color, width := 17) -> void:
canvas.draw_rect(Rect2(at+Vector2(0,2)*p, Vector2(width,7)*p), WOOD)
canvas.draw_rect(Rect2(at+Vector2(1,2)*p, Vector2(width-2,4)*p), CREAM)
canvas.draw_rect(Rect2(at+Vector2(5,2)*p, Vector2(width-6,5)*p), cover)
canvas.draw_rect(Rect2(at, Vector2(1,10)*p), WOOD)
canvas.draw_rect(Rect2(at+Vector2(width-1,5)*p, Vector2(1,5)*p), WOOD)
canvas.draw_rect(Rect2(at+Vector2(2,2)*p, Vector2(3,2)*p), Color("fff0d6"))
static func counter(canvas: CanvasItem, at: Vector2, width: float, p: float) -> void:
canvas.draw_rect(Rect2(at, Vector2(width, 6*p)), WOOD)
canvas.draw_rect(Rect2(at, Vector2(width, p)), WOOD_LIGHT)
canvas.draw_rect(Rect2(at+Vector2(2,2)*p, Vector2(width-4*p, 3*p)), Color("a67c53"))
static func table(canvas: CanvasItem, at: Vector2, p: float) -> void:
canvas.draw_rect(Rect2(at+Vector2(2,1)*p, Vector2(9,2)*p), WOOD_LIGHT)
canvas.draw_rect(Rect2(at+Vector2(3,3)*p, Vector2(1,4)*p), WOOD)
canvas.draw_rect(Rect2(at+Vector2(9,3)*p, Vector2(1,4)*p), WOOD)
canvas.draw_rect(Rect2(at+Vector2(0,3)*p, Vector2(2,4)*p), TEAL)
canvas.draw_rect(Rect2(at+Vector2(12,3)*p, Vector2(2,4)*p), TEAL)
canvas.draw_rect(Rect2(at+Vector2(5,0)*p, Vector2(3,1)*p), CREAM)