143 lines
7.2 KiB
GDScript
143 lines
7.2 KiB
GDScript
class_name CatalogCheck
|
|
extends RefCounted
|
|
|
|
# Forgejo #173: "in the web export version the fleet can not store any
|
|
# resources."
|
|
#
|
|
# That symptom has a history in this project, and it is not a web bug. Opening
|
|
# the project in the Godot editor re-saves .tres files, and the editor's
|
|
# writer DROPS properties it considers default-valued. An empty-looking
|
|
# PackedStringArray is the usual casualty, and it has now cost this project
|
|
# three times:
|
|
#
|
|
# GasBurnerBuildingDefinition lost `capabilities` and `collected_outputs`,
|
|
# which took the burner's whole conversion with it.
|
|
#
|
|
# InterceptorVesselDefinition lost `capabilities`, `hull_points`,
|
|
# `build_time`, `icon` and `glow_color`. The starting vessel kept its
|
|
# cargo_capacity of 20 and stopped claiming the "cargo" capability, which
|
|
# is the one thing fleet_cargo_capacity() gates on - so the hold was zero
|
|
# and nothing could be mined or carried.
|
|
#
|
|
# The second one is #173, word for word. A build exported from a tree in that
|
|
# state has a fleet that cannot store any resources, and NOTHING SAYS SO: the
|
|
# resource still loads, it is just quietly less than it was.
|
|
#
|
|
# test_definitions_intact caught it in the suite. What it could not do is
|
|
# catch it in a build, and an export is taken from the working tree at
|
|
# whatever moment the editor last touched it - which is exactly when the
|
|
# damage is done and exactly where nobody is running tests.
|
|
#
|
|
# So the same check now runs at STARTUP, in every build (see GameState._ready)
|
|
# and in the test, from this one function. On the web its push_error lands in
|
|
# the on-page error log that web_shell.html mirrors from the console, which is
|
|
# the only place a phone can read an error at all. A player still cannot mine,
|
|
# but the reason is on the screen instead of being reasoned about for an
|
|
# evening.
|
|
#
|
|
# It asserts the SHAPE of the catalog rather than any one value: every
|
|
# definition must carry the things that make it what it is, and two facts that
|
|
# have to agree - a hold and the capability that admits to it - must agree.
|
|
|
|
|
|
# Everything wrong with the catalog, in the order found. Empty is the healthy
|
|
# answer, which is what both callers check.
|
|
static func faults() -> PackedStringArray:
|
|
var found: PackedStringArray = PackedStringArray()
|
|
_check_vessels(found)
|
|
_check_buildings(found)
|
|
_check_planets(found)
|
|
return found
|
|
|
|
|
|
# get() answers null for a property a definition's script never declared, and
|
|
# null has no is_empty(). These keep a missing field from looking like an empty
|
|
# one - and from taking the rest of the sweep down with it.
|
|
static func _text(definition: Resource, field: String) -> String:
|
|
var value: Variant = definition.get(field)
|
|
return "" if value == null else str(value)
|
|
|
|
|
|
static func _list(definition: Resource, field: String) -> Array:
|
|
var value: Variant = definition.get(field)
|
|
if value == null:
|
|
return []
|
|
return Array(value.keys() if value is Dictionary else value)
|
|
|
|
|
|
static func _check_vessels(found: PackedStringArray) -> void:
|
|
for definition in VesselDefinitions.all():
|
|
var id: String = String(definition.definition_id)
|
|
# --- whole ------------------------------------------------------
|
|
if String(definition.display_name).strip_edges() == "":
|
|
found.append("vessel %s has no display name" % id)
|
|
if definition.base_stats.is_empty():
|
|
found.append("vessel %s has no stats at all" % id)
|
|
# Only the hulls that actually join the fleet: a repair drone is a
|
|
# logistics unit driven by GameState, with nothing bolted to it.
|
|
if definition.fleet_eligible and definition.component_scenes.is_empty():
|
|
found.append("vessel %s flies in the fleet with no components - an empty hull" % id)
|
|
if definition.hull_points.is_empty():
|
|
found.append("vessel %s has no hull points - it would draw as the fallback triangle" % id)
|
|
# --- cargo: the #173 case ---------------------------------------
|
|
# The hold and the capability are two recordings of one fact, and
|
|
# fleet_cargo_capacity() reads only the second. They must agree.
|
|
var carries: bool = float(definition.base_stats.get("cargo_capacity", 0.0)) > 0.0
|
|
var claims: bool = "cargo" in definition.capabilities
|
|
if carries != claims:
|
|
found.append("vessel %s: cargo_capacity %s but capabilities %s - a hold nothing can fill, or the reverse" \
|
|
% [id, definition.base_stats.get("cargo_capacity", 0.0), definition.capabilities])
|
|
if "weapon" in definition.capabilities \
|
|
and float(definition.base_stats.get("fire_range", 0.0)) <= 0.0:
|
|
found.append("vessel %s claims a weapon and has no range" % id)
|
|
|
|
|
|
static func _check_buildings(found: PackedStringArray) -> void:
|
|
for definition in BuildingDefinitions.all():
|
|
var id: String = String(definition.definition_id)
|
|
# --- whole ------------------------------------------------------
|
|
if String(definition.display_name).strip_edges() == "":
|
|
found.append("building %s has no display name" % id)
|
|
if String(definition.description).strip_edges() == "":
|
|
found.append("building %s has no description - its info popup would be blank" % id)
|
|
if int(definition.cost) <= 0:
|
|
found.append("building %s is free" % id)
|
|
if int(definition.max_hp) <= 0:
|
|
found.append("building %s has no hit points" % id)
|
|
# Space-placed structures only: an extractor is a marker on a planet
|
|
# rather than a thing standing in the world, and has no scene.
|
|
if definition.placement_mode == "space" and definition.visual_scene == null:
|
|
found.append("building %s stands in space with nothing to draw" % id)
|
|
if definition.visual_scene != null:
|
|
var visual: Node = definition.visual_scene.instantiate()
|
|
var provider: Node = visual.get_node_or_null("ActionProvider")
|
|
if provider != null and Array(provider.actions).is_empty():
|
|
found.append("building %s has an action provider with no actions" % id)
|
|
visual.free()
|
|
for field in ["menu_button_name", "menu_label_name", "menu_cost_name"]:
|
|
if str(definition.get(field)).strip_edges() == "":
|
|
found.append("building %s has no %s - the build menu could not place it" % [id, field])
|
|
if definition.capabilities.is_empty():
|
|
found.append("building %s declares no capabilities - it would do nothing at all" % id)
|
|
# --- converters -------------------------------------------------
|
|
# These live on the LOGISTICS profile, a sub-resource of the
|
|
# definition, not on the definition itself. Asking the definition for
|
|
# them answers null and the whole check quietly never fires - which is
|
|
# how the first draft of this passed while the gas burner sat there
|
|
# with its collected_outputs missing.
|
|
var logistics: Resource = definition.logistics
|
|
if logistics == null or _text(logistics, "conversion_input") == "":
|
|
continue
|
|
if _text(logistics, "conversion_output") == "":
|
|
found.append("building %s converts something into nothing" % id)
|
|
if _list(logistics, "collected_outputs").is_empty():
|
|
found.append("building %s converts but lists no collected outputs - what it makes would never be fetched" % id)
|
|
if _list(logistics, "input_buffers").is_empty():
|
|
found.append("building %s converts with no input buffer" % id)
|
|
|
|
|
|
static func _check_planets(found: PackedStringArray) -> void:
|
|
for definition in PlanetDefinitions.DEFINITIONS.values():
|
|
var id: String = String(definition.definition_id)
|
|
if definition.names.is_empty():
|
|
found.append("planet %s has no generated names" % id)
|