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>
290 lines
15 KiB
GDScript
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)
|