Preserve approved pixel tower prototype as development baseline
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
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 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) -> void:
|
||||
var p := pixel
|
||||
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"]:
|
||||
canvas.draw_rect(inner, Color("455855") if not back else Color("506277"))
|
||||
canvas.draw_rect(Rect2(mid - p, top + p, 2 * p, rect.size.y - 2 * p), Color("243c3c"))
|
||||
canvas.draw_rect(Rect2(inner.position.x + 2*p, top + 4*p, inner.size.x - 4*p, inner.size.y - 6*p), Color("84978b"))
|
||||
canvas.draw_line(Vector2(mid, top+4*p), Vector2(mid, rect.end.y-3*p), INK, p)
|
||||
canvas.draw_rect(Rect2(mid-2*p, top+2*p, 4*p, p), Color("f1c774"))
|
||||
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"))
|
||||
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", &"adult_private_room":
|
||||
bed(canvas, Vector2(rect.position.x+2*p, floor_y-9*p), p, Color("709b94") if not dark else Color("a37b91"),13)
|
||||
if w >= 28*p:
|
||||
bed(canvas, Vector2(rect.end.x-15*p, floor_y-9*p), p, Color("709b94") if not dark else Color("a37b91"),13)
|
||||
&"cafeteria", &"tavern":
|
||||
for x in range(int(rect.position.x+5*p), int(rect.end.x-13*p), int(20*p)):
|
||||
table(canvas, Vector2(x, floor_y-7*p), p)
|
||||
if rect.size.y > 28*p:
|
||||
counter(canvas, Vector2(rect.position.x+7*p, top+13*p), w-14*p, p)
|
||||
for x in range(int(rect.position.x+10*p), int(rect.end.x-8*p), int(12*p)):
|
||||
canvas.draw_rect(Rect2(x, top+11*p, 3*p, 2*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))
|
||||
&"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)
|
||||
|
||||
|
||||
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)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cq3ssdj06ljk0
|
||||
@@ -0,0 +1,141 @@
|
||||
extends RefCounted
|
||||
|
||||
## Reproducible, disposable viewing scenario. All buildings, employees, inventory,
|
||||
## and jobs go through Runtime commands; visitors come from production arrivals.
|
||||
## The normal new-game state and the player's saves are never changed by this file.
|
||||
|
||||
var failure := ""
|
||||
var facility_ids: Dictionary = {}
|
||||
var _runtime: Variant
|
||||
var _clock: Variant
|
||||
var _tree: SceneTree
|
||||
var _sequence := 0
|
||||
|
||||
|
||||
func prepare(runtime: Variant, clock: Variant, tree: SceneTree) -> bool:
|
||||
_runtime = runtime
|
||||
_clock = clock
|
||||
_tree = tree
|
||||
_clock.set_paused(true)
|
||||
var operations: Dictionary = _runtime.enable_production_operations()
|
||||
if not bool(operations.get(&"ok", false)):
|
||||
return _fail("Could not enable production operations: %s" % operations)
|
||||
for row: Array in [
|
||||
[&"reception", Vector3i(-8, 0, 0)],
|
||||
[&"cafeteria", Vector3i(2, 0, 0)],
|
||||
[&"kitchen", Vector3i(2, 0, 1)],
|
||||
]:
|
||||
if not _build(row[0], row[1]):
|
||||
return false
|
||||
if not await advance(180):
|
||||
return false
|
||||
for facility: Dictionary in _runtime.construction_service.get_facilities():
|
||||
facility_ids[facility[&"definition_id"]] = facility[&"instance_id"]
|
||||
# Four rooms sit on the completed reception and dining structure. Corridor
|
||||
# cells are ordinary supported construction; unfinished elevator topology is
|
||||
# deliberately excluded from this scenario.
|
||||
for x: int in [-8, -6, 2, 4]:
|
||||
if not _build(&"standard_room", Vector3i(x, 2, 0)):
|
||||
return false
|
||||
for x: int in range(-4, 2):
|
||||
if not _build(&"guest_corridor", Vector3i(x, 1, 0)):
|
||||
return false
|
||||
if not await advance(60):
|
||||
return false
|
||||
for x: int in range(-4, 2):
|
||||
if not _build(&"guest_corridor", Vector3i(x, 2, 0)):
|
||||
return false
|
||||
if not await advance(10):
|
||||
return false
|
||||
# This walkway follows the upper side of the rooms. Its authoritative Path
|
||||
# projection allows real guests and workers to move between the three levels.
|
||||
for x: int in range(-8, 6):
|
||||
if not _build(&"guest_corridor", Vector3i(x, 3, 0)):
|
||||
return false
|
||||
if not await advance(10):
|
||||
return false
|
||||
for row: Array in [
|
||||
[&"staff_akane", facility_ids[&"reception"], Vector3i(-8, 0, 0)],
|
||||
[&"staff_chika", facility_ids[&"cafeteria"], Vector3i(2, 0, 0)],
|
||||
[&"staff_iris", facility_ids[&"cafeteria"], Vector3i(4, 0, 0)],
|
||||
[&"staff_eri", &"", Vector3i(0, 0, 0)],
|
||||
]:
|
||||
if not _command(&"hire_staff_candidate", {
|
||||
&"candidate_id": row[0], &"facility_id": row[1], &"current_cell": row[2],
|
||||
}):
|
||||
return false
|
||||
# A small explicitly seeded pantry is part of the disposable scenario; its
|
||||
# stacks are real and get consumed by the same service pipeline as gameplay.
|
||||
var cafeteria: Variant = _runtime.facility_service.get_instance(facility_ids[&"cafeteria"])
|
||||
if not _command(&"add_inventory_stack", {
|
||||
&"owner_inventory_id": cafeteria.local_inventory_id,
|
||||
&"item_id": &"ingredient/basic_meal", &"quantity": 32,
|
||||
}):
|
||||
return false
|
||||
if not await advance(1):
|
||||
return false
|
||||
# The cleaner actually accepts, walks to, and completes these queued tasks.
|
||||
for index: int in 6:
|
||||
var target := Vector3i(-8 if index % 2 == 0 else 5, 3, 0)
|
||||
if not _command(&"create_staff_task", {
|
||||
&"task_id": StringName("pixel_view.clean.%02d" % index),
|
||||
&"task_type": &"clean", &"priority": 70 - index,
|
||||
&"options": {
|
||||
&"target_entity_id": facility_ids[&"reception"] if index % 2 == 0 else facility_ids[&"cafeteria"],
|
||||
&"target_cell": target, &"required_role_tags": [&"cleaner"],
|
||||
&"task_tags": [&"cleaning"], &"estimated_minutes": 8,
|
||||
},
|
||||
}):
|
||||
return false
|
||||
return await advance(1)
|
||||
|
||||
|
||||
func advance(minutes: int) -> bool:
|
||||
for index: int in minutes:
|
||||
var before: int = _clock.absolute_minute
|
||||
_clock.advance_minutes(1)
|
||||
if _clock.absolute_minute != before + 1:
|
||||
return _fail("Scenario stopped at %s: %s" % [_clock.formatted_time(), _runtime.get_operations_report()])
|
||||
var report: Dictionary = _runtime.get_operations_report()
|
||||
if bool(report.get(&"runtime_failure_latched", false)):
|
||||
return _fail("Operations failed: %s" % report)
|
||||
if index % 30 == 29:
|
||||
await _tree.process_frame
|
||||
return true
|
||||
|
||||
|
||||
func describe() -> Dictionary:
|
||||
var facilities: Array[Dictionary] = []
|
||||
for facility: Variant in _runtime.facility_service.get_instances():
|
||||
facilities.append({
|
||||
"definition": String(facility.definition_id), "state": int(facility.state),
|
||||
"origin": [facility.origin.x, facility.origin.y, facility.origin.z],
|
||||
})
|
||||
var actors: Array[Dictionary] = []
|
||||
for guest: Variant in _runtime.guest_service.get_guests():
|
||||
actors.append({"id": String(guest.guest_id), "kind": "guest", "state": int(guest.state),
|
||||
"cell": [guest.current_cell.x, guest.current_cell.y, guest.current_cell.z]})
|
||||
for staff: Variant in _runtime.staff_service.get_staff_members():
|
||||
actors.append({"id": String(staff.staff_id), "kind": "staff", "state": int(staff.state),
|
||||
"cell": [staff.current_cell.x, staff.current_cell.y, staff.current_cell.z]})
|
||||
return {"time": _clock.formatted_time(), "minute": _clock.absolute_minute,
|
||||
"facilities": facilities, "actors": actors, "pantry_seed_meals": 32}
|
||||
|
||||
|
||||
func _build(definition_id: StringName, cell: Vector3i) -> bool:
|
||||
return _command(&"build_facility", {&"definition_id": definition_id, &"origin": cell})
|
||||
|
||||
|
||||
func _command(type: StringName, payload: Dictionary) -> bool:
|
||||
_sequence += 1
|
||||
var result: Variant = _runtime.submit_command(type, payload, &"player", &"", 50, -1,
|
||||
StringName("pixel_view.command.%04d" % _sequence))
|
||||
if not result.is_success():
|
||||
return _fail("%s failed for %s: %s %s" % [type, payload, result.message, result.details])
|
||||
return true
|
||||
|
||||
|
||||
func _fail(message: String) -> bool:
|
||||
failure = message
|
||||
push_error(message)
|
||||
return false
|
||||
@@ -0,0 +1 @@
|
||||
uid://l58jbi52mqdl
|
||||
@@ -0,0 +1,90 @@
|
||||
class_name VisualThemeProfile
|
||||
extends Resource
|
||||
|
||||
## Replaceable visual-only configuration. Gameplay code must not branch on
|
||||
## this resource, so changing art never changes simulation or save data.
|
||||
|
||||
@export_group("Pixel Art")
|
||||
@export var pixel_dungeon_tiles: Texture2D
|
||||
@export var pixel_town_tiles: Texture2D
|
||||
|
||||
@export_group("World Art")
|
||||
@export var front_world_background: Texture2D
|
||||
@export var back_world_background: Texture2D
|
||||
@export_range(0.0, 1.0, 0.01) var world_art_opacity := 0.94
|
||||
@export_range(0.0, 1.0, 0.01) var grid_overlay_opacity := 0.24
|
||||
|
||||
@export_group("Interface Icons")
|
||||
@export var facility_icons: Dictionary = {}
|
||||
@export var navigation_icons: Dictionary = {}
|
||||
@export var resource_icons: Dictionary = {}
|
||||
@export var control_icons: Dictionary = {}
|
||||
@export var guest_actor_markers: Dictionary = {}
|
||||
@export var staff_actor_markers: Dictionary = {}
|
||||
|
||||
@export_group("Interface Skins")
|
||||
@export var ui_skin_textures: Dictionary = {}
|
||||
|
||||
@export_group("Interface Palette")
|
||||
@export var window_background := Color("24170f")
|
||||
@export var dark_panel := Color("6b3e1d")
|
||||
@export var dark_panel_alt := Color("513018")
|
||||
@export var light_panel := Color("f4e3bd")
|
||||
@export var border_gold := Color("d5a552")
|
||||
@export var button_normal := Color("75451f")
|
||||
@export var button_hover := Color("8d5628")
|
||||
@export var button_pressed := Color("496528")
|
||||
@export var button_disabled := Color("574334")
|
||||
@export var text_light := Color("fff0c8")
|
||||
@export var text_dark := Color("55371e")
|
||||
@export var success := Color("4f8a45")
|
||||
@export var warning := Color("d39a2d")
|
||||
@export var danger := Color("c84632")
|
||||
|
||||
|
||||
func world_background_for_layer(layer: int) -> Texture2D:
|
||||
return front_world_background if layer == 0 else back_world_background
|
||||
|
||||
|
||||
func facility_icon(definition_id: StringName) -> Texture2D:
|
||||
return _icon_from(facility_icons, definition_id)
|
||||
|
||||
|
||||
func navigation_icon(mode: StringName) -> Texture2D:
|
||||
return _icon_from(navigation_icons, mode)
|
||||
|
||||
|
||||
func resource_icon(resource_name: StringName) -> Texture2D:
|
||||
return _icon_from(resource_icons, resource_name)
|
||||
|
||||
|
||||
func control_icon(control_name: StringName) -> Texture2D:
|
||||
return _icon_from(control_icons, control_name)
|
||||
|
||||
|
||||
func guest_actor_marker(guest_type_id: StringName) -> Texture2D:
|
||||
var direct := _icon_from(guest_actor_markers, guest_type_id)
|
||||
if direct != null:
|
||||
return direct
|
||||
var type_text := String(guest_type_id)
|
||||
for raw_key: Variant in guest_actor_markers:
|
||||
var class_id := String(raw_key)
|
||||
if class_id == "default":
|
||||
continue
|
||||
if type_text == class_id or type_text.begins_with(class_id + "_"):
|
||||
return guest_actor_markers[raw_key] as Texture2D
|
||||
return _icon_from(guest_actor_markers, &"default")
|
||||
|
||||
|
||||
func staff_actor_marker(role_id: StringName) -> Texture2D:
|
||||
var marker := _icon_from(staff_actor_markers, role_id)
|
||||
return marker if marker != null else _icon_from(staff_actor_markers, &"default")
|
||||
|
||||
|
||||
func ui_skin(skin_id: StringName) -> Texture2D:
|
||||
return _icon_from(ui_skin_textures, skin_id)
|
||||
|
||||
|
||||
func _icon_from(collection: Dictionary, key: StringName) -> Texture2D:
|
||||
var value: Variant = collection.get(key, collection.get(String(key), null))
|
||||
return value as Texture2D
|
||||
@@ -0,0 +1 @@
|
||||
uid://bmcsg0aclydnb
|
||||
Reference in New Issue
Block a user