spaceship/buildings/BuildingField.gd

78 lines
2.7 KiB
GDScript3
Raw Permalink Normal View History

extends Node2D
# Rebuilds every player-placed structure from GameState when the space
# scene loads, and answers "is the fleet standing on one?".
#
# Buildings live in GameState rather than in the scene because Main.tscn is
# torn down and recreated on every trip to a planet's surface - anything
# kept only as a node would be lost on the first landing.
@onready var fleet: Node2D = get_node("../Fleet")
#84: move 215 root files into a folder hierarchy Everything lived in one flat directory, so a diff gave no clue what area it touched and finding the file you wanted meant knowing its exact name. Twelve folders now, grouped by what a thing IS rather than what type it is - a vessel's definition, its component scenes and its upgrade resources sit together, because that is the set you read when you change one of them. buildings core controllers enemies logistics planets resources shaders surface ui vessels vfx world 332 files moved and every res:// reference rewritten - 306 files touched, including the autoload and main-scene paths in project.godot. Nothing renamed: this is purely where files sit, so the diff is a pile of pure moves and no behaviour is buried in it. ui_theme.tres, icon.svg and web_shell.html stay at the root, where Godot and the export preset expect them. Verified three ways, because a suite alone would not have caught all of it: the full 117 pass, the web export builds clean, and the game renders. The export is why that matters. The FIRST export after the move failed with "Attempt to open script 'res://GameState.gd'" - the old path - while project.godot on disk plainly said res://core/GameState.gd. That is this project's documented Android FUSE mtime trap: Godot served a cached copy of a file whose content had changed. A `touch` fixed it. Nothing in the test suite would have found that, because the suite runs from a copy on a native filesystem - the one place the bug cannot happen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 19:44:14 +02:00
var building_scene: PackedScene = preload("res://buildings/Building.tscn")
var spawned: Array = []
# Nothing happens here per frame any more. Arriving at the mothership used to
# bank the fleet's hold automatically (#14), edge-triggered so that ore
# withdrawn while parked was not instantly taken back. #24 removes it
# entirely: unloading is now an explicit move in the transfer menu, in both
# directions and at every building, so an automatic one-way exception at the
# mothership was the odd case out - and it silently overrode a player who had
# just loaded the hold in order to carry ore somewhere else.
func _ready() -> void:
# #99: the record is the truth, so the node follows it out. Connected
# here rather than in Building.gd because the field owns `spawned` and
# would otherwise be left holding a freed node.
if not GameState.building_destroyed.is_connected(_on_building_destroyed):
GameState.building_destroyed.connect(_on_building_destroyed)
rebuild_all()
func _on_building_destroyed(building_id: int, _type: String) -> void:
var kept: Array = []
for b in spawned:
if not is_instance_valid(b):
continue
if int(b.building_id) == building_id:
BuildingExplosionVFX.spawn(get_tree().root, b.global_position)
#146: shots, deaths and collapses are heard Three events get a sound: a shot leaving a gun, a vessel dying, a structure coming down. The sounds are SYNTHESISED rather than downloaded - audio/make_sfx.py generates all three from scratch, and is committed alongside them. The ticket asked for free sources and the good ones are in its comments (Kenney's CC0 packs, OpenGameArt's CC0 sets, Freesound filtered to CC0), but a generated sound has no licence to track, no attribution to keep, and no binary in the repository whose provenance nobody can check a year from now. It also makes the sounds DATA: too sharp a shot is a number in that file and a re-run, not a hunt through a library. Dropping in a pack file later means replacing a .wav and nothing else. A voice POOL, not a player, because these overlap constantly - a fleet firing is several shots inside one shot's length. Twelve voices, and when they are all busy the one furthest through its own sound is recycled, which is the one with the least left to lose. Parented to the tree root, for the reason that matters: every one of these sounds is started by something that is being freed on the same frame. A dying ship cannot own the sound of its own death. Levels are per sound rather than shared - a shot fires constantly and a death happens once, so equal volumes are not proportionate ones - and shots get a little pitch spread so a stream of them does not flatten into one machine-gun note. The deaths get none: a wobbling explosion sounds broken rather than varied. The test goes at the failures a play-through would hide for weeks: firing three times the pool size must leave exactly the pool's worth sounding and no leaked voices, and the ship-death case is checked from a node that really has been freed on that frame.
2026-08-29 13:05:47 +02:00
Sfx.play(get_tree(), Sfx.BUILDING_DEATH)
b.queue_free()
continue
kept.append(b)
spawned = kept
func rebuild_all() -> void:
for b in spawned:
if is_instance_valid(b):
b.queue_free()
spawned.clear()
for record in GameState.buildings:
_spawn(record)
func place(type: String, at_position: Vector2) -> void:
GameState.add_building(type, at_position)
_spawn(GameState.buildings[-1])
func get_building_fleet_is_on() -> Node2D:
var centre: Vector2 = fleet.get_centroid()
for b in spawned:
if is_instance_valid(b) and b.position.distance_to(centre) <= b.interact_radius:
return b
return null
func _spawn(record: Dictionary) -> void:
var b: Node2D = building_scene.instantiate()
b.position = record.position
b.definition_id = record.type
b.building_id = int(record.get("id", 0))
# Deferred for the same reason as the other field spawners: this can run
# from inside a parent's _ready() chain, where a direct add_child() is
# rejected while the tree is still setting up children.
get_parent().add_child.call_deferred(b)
spawned.append(b)