Preserve production code, regression fixtures, audio assets and provenance, test infrastructure, and the original-completion plan and progress ledger. Final game and Windows acceptance remain pending; excluded local artifacts and native experiments are not part of this snapshot.
280 lines
15 KiB
GDScript
280 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)
|
|
&"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)
|