Preserve approved pixel tower prototype as development baseline

This commit is contained in:
Codex
2026-09-09 07:51:16 +09:00
commit 423fd6dd2e
915 changed files with 95193 additions and 0 deletions
+114
View File
@@ -0,0 +1,114 @@
class_name FacilityCatalog
extends RefCounted
## Canonical MVP order from GAME_SPECIFICATION section 7.6.
##
## DefinitionRegistry deliberately sorts its query results by stable ID. This
## list instead preserves the design-table order for tools that present the
## baseline catalog to designers or players.
const BASELINE_FACILITY_IDS: Array[StringName] = [
&"reception",
&"standard_room",
&"cafeteria",
&"kitchen",
&"public_bath",
&"warehouse",
&"staff_waiting_room",
&"security_station",
&"tavern",
&"massage_room",
&"guest_elevator",
&"service_elevator",
&"staff_door",
&"guest_corridor",
&"service_corridor",
&"adult_private_room",
]
const FACILITY_PATHS: Array[String] = [
"res://data/facilities/reception.tres",
"res://data/facilities/standard_room.tres",
"res://data/facilities/cafeteria.tres",
"res://data/facilities/kitchen.tres",
"res://data/facilities/public_bath.tres",
"res://data/facilities/warehouse.tres",
"res://data/facilities/staff_waiting_room.tres",
"res://data/facilities/security_station.tres",
"res://data/facilities/tavern.tres",
"res://data/facilities/massage_room.tres",
"res://data/facilities/guest_elevator.tres",
"res://data/facilities/service_elevator.tres",
"res://data/facilities/staff_door.tres",
"res://data/facilities/guest_corridor.tres",
"res://data/facilities/service_corridor.tres",
"res://data/facilities/adult_private_room.tres",
]
## Optional post-MVP expansion definitions. They live below a subdirectory so
## DefinitionRegistry's baseline directory scan remains exactly compatible with
## existing saves and the sixteen-facility acceptance fixture.
const EXPANSION_FACILITY_IDS: Array[StringName] = [
&"premium_room",
&"tea_lounge",
&"general_store",
&"clinic",
&"guild_counter",
&"underground_guest_room",
&"private_bath",
&"secret_tavern",
&"black_market",
&"vip_private_salon",
&"laundry",
&"cold_storage",
&"mana_control_room",
&"maintenance_workshop",
&"staff_dormitory",
&"waste_processing",
]
const EXPANSION_FACILITY_PATHS: Array[String] = [
"res://data/facilities/expansion/premium_room.tres",
"res://data/facilities/expansion/tea_lounge.tres",
"res://data/facilities/expansion/general_store.tres",
"res://data/facilities/expansion/clinic.tres",
"res://data/facilities/expansion/guild_counter.tres",
"res://data/facilities/expansion/underground_guest_room.tres",
"res://data/facilities/expansion/private_bath.tres",
"res://data/facilities/expansion/secret_tavern.tres",
"res://data/facilities/expansion/black_market.tres",
"res://data/facilities/expansion/vip_private_salon.tres",
"res://data/facilities/expansion/laundry.tres",
"res://data/facilities/expansion/cold_storage.tres",
"res://data/facilities/expansion/mana_control_room.tres",
"res://data/facilities/expansion/maintenance_workshop.tres",
"res://data/facilities/expansion/staff_dormitory.tres",
"res://data/facilities/expansion/waste_processing.tres",
]
static func load_all() -> Array[FacilityDefinition]:
var result: Array[FacilityDefinition] = []
for path in FACILITY_PATHS:
var definition := load(path) as FacilityDefinition
if definition != null:
result.append(definition)
return result
static func load_expansion() -> Array[FacilityDefinition]:
var result: Array[FacilityDefinition] = []
for path in EXPANSION_FACILITY_PATHS:
var definition := load(path) as FacilityDefinition
if definition != null:
result.append(definition)
return result
static func load_available(include_expansion := false) -> Array[FacilityDefinition]:
var result := load_all()
if include_expansion:
result.append_array(load_expansion())
return result
static func by_id(facility_id: StringName) -> FacilityDefinition:
for definition in load_available(true):
if definition.id == facility_id:
return definition
return null
+1
View File
@@ -0,0 +1 @@
uid://c61tnpw8yjyx7
+143
View File
@@ -0,0 +1,143 @@
class_name FacilityDefinition
extends Resource
const LEGACY_SCHEMA_VERSION := 1
const DETAILED_SCHEMA_VERSION := 2
enum MapZone {
ANY,
ABOVE_GROUND,
BOUNDARY,
UNDERGROUND,
}
enum ViewLayer {
FRONT,
BACK,
}
@export_group("Identity")
@export_range(LEGACY_SCHEMA_VERSION, DETAILED_SCHEMA_VERSION, 1) var definition_schema_version := LEGACY_SCHEMA_VERSION
@export var id: StringName
@export var display_name := ""
@export_multiline var description := ""
@export var category: StringName = &"uncategorized"
@export var service_types: Array[StringName] = []
@export var default_service_type: StringName = &""
@export var service_profiles: Dictionary = {}
@export_group("Placement")
@export var zone := MapZone.ANY
@export var layer := ViewLayer.FRONT
@export var footprint := Vector2i.ONE
@export var allowed_rotations: Array[int] = [0, 90, 180, 270]
@export var guest_access_offsets: Array[Vector2i] = []
@export var staff_access_offsets: Array[Vector2i] = []
@export var required_connection_tags: Array[StringName] = []
@export var preferred_adjacency_tags: Array[StringName] = []
@export var forbidden_adjacency_tags: Array[StringName] = []
@export_group("Construction")
@export var construction_cost := {&"gold": 100, &"materials": 1}
@export var build_minutes := 60
@export_range(1, 3, 1) var weight_per_cell := 1
@export var pass_through := false
@export_range(0, 100, 1) var demolition_refund_percent := 25
@export_range(1, 5, 1) var max_level := 3
@export var upgrade_costs: Array[Dictionary] = []
@export_group("Upkeep and Power")
@export var maintenance_gold := 0
@export var maintenance_mana := 0
@export_range(0, 100, 1) var power_priority := 50
@export_group("Staffing")
@export var staff_min := 0
@export var staff_max := 0
@export var required_roles: Array[StringName] = []
@export var primary_skill: StringName = &"service"
@export_group("Service")
@export var capacity := 0
@export var queue_capacity := 0
@export var base_service_minutes := 1
@export var base_quality := 50
@export var base_price := 0
@export var inventory_inputs: Dictionary = {}
@export var inventory_outputs: Dictionary = {}
@export var opening_schedule := {&"morning": true, &"day": true, &"night": true}
@export var policy_schema: Dictionary = {}
@export_group("Environment and Risk")
@export_range(0, 100, 1) var cleanliness_decay_per_service := 0
@export_range(0, 100, 1) var noise_per_service := 0
@export_range(-100, 100, 1) var beauty_effect := 0
@export_range(-100, 100, 1) var security_effect := 0
@export_range(-100, 100, 1) var concealment_effect := 0
@export_range(0, 100, 1) var heat_per_service := 0
@export var incident_weights: Dictionary = {}
@export_group("Audience and Content")
@export var allowed_guest_tags: Array[StringName] = []
@export var forbidden_guest_tags: Array[StringName] = []
@export var adult_content := false
@export var content_replacement_id: StringName = &""
@export_group("Progression")
@export_range(1, 7, 1) var unlock_rank := 1
@export var research_requirements: Array[StringName] = []
@export_group("Analytics")
@export var statistics_keys: Array[StringName] = []
@export_group("Classification")
@export var tags: Array[StringName] = []
func is_valid() -> bool:
return not id.is_empty() \
and not display_name.is_empty() \
and footprint.x > 0 \
and footprint.y > 0 \
and build_minutes > 0 \
and weight_per_cell in [1, 2, 3] \
and not allowed_rotations.is_empty() \
and maintenance_gold >= 0 \
and maintenance_mana >= 0 \
and staff_min >= 0 \
and staff_max >= staff_min \
and capacity >= 0 \
and queue_capacity >= 0 \
and base_service_minutes > 0 \
and base_price >= 0 \
and detailed_profile_is_valid()
func has_detailed_profile() -> bool:
return definition_schema_version == DETAILED_SCHEMA_VERSION
func detailed_profile_is_valid() -> bool:
if definition_schema_version == LEGACY_SCHEMA_VERSION:
return true
if definition_schema_version != DETAILED_SCHEMA_VERSION:
return false
if category.is_empty() or service_types.is_empty() or default_service_type.is_empty():
return false
if not service_types.has(default_service_type) or not service_profiles.has(default_service_type):
return false
if guest_access_offsets.is_empty() and staff_access_offsets.is_empty() and not pass_through:
return false
if demolition_refund_percent < 0 or demolition_refund_percent > 100:
return false
if max_level < 1 or max_level > 5 or upgrade_costs.size() != max_level - 1:
return false
if unlock_rank < 1 or unlock_rank > 7:
return false
for time_band: StringName in [&"morning", &"day", &"night"]:
if not opening_schedule.has(time_band) or not opening_schedule[time_band] is bool:
return false
if opening_schedule.size() != 3:
return false
if adult_content and (not tags.has(&"adult_only") or content_replacement_id.is_empty()):
return false
return true
+1
View File
@@ -0,0 +1 @@
uid://cpmiimqx2vb58
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
uid://nw4dttch8rt4