Initial commit — Station 0 vertical slice in progress

Includes full design documentation (ARCHITECTURE, DESIGN_NOTES, CONTENT_SPEC, PRODUCTION_NOTES)
and active Godot 4 / GDScript codebase. Core systems built: player, floor generation,
room system, enemy base, body part upgrades, run manager, save system, HUD, EventBus.
This commit is contained in:
Station 0 Dev
2026-03-04 12:33:05 -05:00
commit 342175f4c3
66 changed files with 4683 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
extends Resource
class_name BodyPartData
@export var slot: String = "" # "head" | "torso" | "left_arm" | "right_arm" | "legs"
@export var display_name: String = ""
# ---------------------------------------------------------------------------
# Stat modifiers — additive bonuses stacked across all equipped parts.
# Field names mirror the stat keys used in player_stats.gd.
# ---------------------------------------------------------------------------
@export var mod_speed: float = 0.0
@export var mod_max_health: float = 0.0
@export var mod_damage: float = 0.0
@export var mod_fire_rate: float = 0.0
@export var mod_range: float = 0.0
@export var mod_proj_speed: float = 0.0 # flat delta; e.g. -104 = -20% of base 520
# ---------------------------------------------------------------------------
# Scatter (left arm slot)
# mod_scatter_count > 0 enables multi-shot spread from this part
# ---------------------------------------------------------------------------
@export var mod_scatter_count: int = 0
@export var mod_scatter_spread_deg: float = 0.0
# ---------------------------------------------------------------------------
# Shield Projector (left arm slot)
# Active block on cooldown — absorbs one hit per activation
# ---------------------------------------------------------------------------
@export var shield_projector: bool = false
+11
View File
@@ -0,0 +1,11 @@
extends Area2D
var part: BodyPartData = null
func _ready() -> void:
body_entered.connect(_on_body_entered)
func _on_body_entered(body: Node) -> void:
if body.is_in_group("player") and part != null:
UpgradeManager.acquire_part(part)
queue_free()
+63
View File
@@ -0,0 +1,63 @@
extends Node
const SLOTS: Array[String] = ["head", "torso", "left_arm", "right_arm", "legs"]
var hub_credits: int = 0
# slot -> BodyPart resource path (empty string = default/base part)
var equipped_parts: Dictionary = {
"head": "",
"torso": "",
"left_arm": "",
"right_arm": "",
"legs": "",
}
# All permanently acquired part resource paths
var acquired_part_paths: Array[String] = []
func equip_part(part: Resource) -> void:
var slot: String = part.slot
if slot not in SLOTS:
push_error("UpgradeManager: invalid slot '%s'" % slot)
return
equipped_parts[slot] = part.resource_path
EventBus.body_part_equipped.emit(part, slot)
func acquire_part(part: Resource) -> void:
if part.resource_path not in acquired_part_paths:
acquired_part_paths.append(part.resource_path)
EventBus.body_part_found.emit(part)
func get_equipped_part(slot: String) -> Resource:
var path: String = equipped_parts.get(slot, "")
if path != "":
return load(path)
return null
func get_stat_modifier(stat: String) -> float:
var total := 0.0
for slot in SLOTS:
var part := get_equipped_part(slot)
if part == null:
continue
match stat:
"speed": total += part.mod_speed
"max_health": total += part.mod_max_health
"damage": total += part.mod_damage
"fire_rate": total += part.mod_fire_rate
"range": total += part.mod_range
"proj_speed": total += part.mod_proj_speed
return total
func get_save_data() -> Dictionary:
return {
"equipped": equipped_parts.duplicate(),
"acquired": acquired_part_paths.duplicate(),
}
func load_save_data(data: Dictionary) -> void:
var saved_equipped: Dictionary = data.get("equipped", {})
for slot in SLOTS:
equipped_parts[slot] = saved_equipped.get(slot, "")
acquired_part_paths = data.get("acquired", [])