Show buildings on the world map, and extractors on their planets
Forgejo #11: buildings the player has placed now appear on the world map. They are placed freely in space rather than tied to a chunk, so they get their own pass in WorldMapContent._draw() rather than riding along with the chunk markers. Each type has its own shape and colour, mirroring Building.gd's constants so a marker reads as the same building you built. Forgejo #9: a planet with an extractor built on it now shows it, in both places you look at a planet - an amber derrick on its outline in space, and a matching badge on its world-map marker. The extractor lives on PlanetSurface.tscn, a scene you are not looking at from space, so an already-mined planet was otherwise indistinguishable from an untouched one until you landed on it. The positioning steps are factored out (building_markers(), chunk_has_extractor_badge(), extractor_derrick_tip()) so headless tests can assert on them - --headless has no GPU and draws no pixels, so there is nothing to check in _draw() itself. Both features were also rendered for real via tests/screenshot.sh and looked at. extractor_derrick_tip() exists because the first version of the "derrick is not buried inside the planet" check measured the furthest of all four derrick points, which passes a derrick pointing straight into the ground: the base corners sit a half-width off the rim vertex and clear the outline on their own. Mutation-testing caught it. screenshot.sh now clears a stale /tmp/.X99-lock. A run killed mid-screenshot leaves it behind and Xvfb then refuses :99, which resurfaces later as Godot's misleading "X11 Display is not available". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
b641fa5891
commit
8dd5d7b78f
13 changed files with 472 additions and 0 deletions
56
Planet.gd
56
Planet.gd
|
|
@ -20,6 +20,20 @@ extends Node2D
|
|||
|
||||
var shape_points: PackedVector2Array = PackedVector2Array()
|
||||
|
||||
# An extractor is built down on this planet's SURFACE, in a different scene
|
||||
# the player is no longer looking at, so from space nothing distinguished a
|
||||
# planet already being mined from an untouched one - you had to land to find
|
||||
# out. This is a derrick planted on the rim: the same amber the world map
|
||||
# uses for its extractor badge, so the two read as the same fact.
|
||||
const EXTRACTOR_MARKER_COLOR: Color = Color(1.0, 0.85, 0.3)
|
||||
const DERRICK_HEIGHT: float = 44.0
|
||||
const DERRICK_HALF_WIDTH: float = 16.0
|
||||
|
||||
# Resolved once in _ready() rather than polled: building an extractor happens
|
||||
# on PlanetSurface.tscn, and coming back to space rebuilds Main.tscn from
|
||||
# scratch, so a Planet node never outlives a change to its own answer.
|
||||
var shows_extractor: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("planets")
|
||||
|
|
@ -32,8 +46,50 @@ func _ready() -> void:
|
|||
var angle: float = (float(i) / point_count) * TAU
|
||||
var radius: float = rng.randf_range(70.0, 100.0)
|
||||
shape_points.append(Vector2(cos(angle), sin(angle)) * radius)
|
||||
shows_extractor = GameState.has_extractor_at(planet_name)
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func extractor_derrick_anchor() -> Vector2:
|
||||
# The rim vertex the derrick stands on: the shape's own topmost point,
|
||||
# rather than a fixed radius, so it sits ON the outline whatever lumpy
|
||||
# shape the seed drew instead of floating off it or sinking inside it.
|
||||
var base: Vector2 = Vector2(0.0, -100.0)
|
||||
for p in shape_points:
|
||||
if p.y < base.y:
|
||||
base = p
|
||||
return base
|
||||
|
||||
|
||||
func extractor_derrick_tip() -> Vector2:
|
||||
# Exposed separately from the polygon because "does the derrick clear the
|
||||
# planet's outline" is a question about the TIP alone. The base corners
|
||||
# sit a half-width off to either side, so they are sqrt(|base|^2 + w^2)
|
||||
# from the centre and can clear the rim on their own even when the tower
|
||||
# points straight into the ground.
|
||||
var base: Vector2 = extractor_derrick_anchor()
|
||||
var out: Vector2 = base.normalized()
|
||||
if out == Vector2.ZERO:
|
||||
out = Vector2.UP
|
||||
return base + out * DERRICK_HEIGHT
|
||||
|
||||
|
||||
func extractor_derrick_points() -> PackedVector2Array:
|
||||
var base: Vector2 = extractor_derrick_anchor()
|
||||
var out: Vector2 = base.normalized()
|
||||
if out == Vector2.ZERO:
|
||||
out = Vector2.UP
|
||||
var side: Vector2 = Vector2(-out.y, out.x)
|
||||
var tip: Vector2 = extractor_derrick_tip()
|
||||
return PackedVector2Array([
|
||||
base - side * DERRICK_HALF_WIDTH,
|
||||
tip - side * (DERRICK_HALF_WIDTH * 0.35),
|
||||
tip + side * (DERRICK_HALF_WIDTH * 0.35),
|
||||
base + side * DERRICK_HALF_WIDTH,
|
||||
])
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
NeonDraw.draw_glowing_polygon(self, shape_points, Color(0.6, 0.55, 0.5), 1.0)
|
||||
if shows_extractor:
|
||||
NeonDraw.draw_glowing_polygon(self, extractor_derrick_points(), EXTRACTOR_MARKER_COLOR, 1.0)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,23 @@ extends Node2D
|
|||
|
||||
const WORLD_TO_LOCAL: float = 0.04
|
||||
|
||||
# Mirrors Building.gd's own MOTHERSHIP_COLOR/CRAFTER_COLOR/COMMAND_COLOR so a
|
||||
# marker on the map reads as "the same building" at a glance. Duplicated
|
||||
# rather than cross-referenced - the same colour is already independently
|
||||
# duplicated between Ship.gd, Fleet.gd and Building.gd's HULL_COLOR, so this
|
||||
# follows the codebase's existing convention rather than inventing a new one.
|
||||
const MOTHERSHIP_MARKER_COLOR: Color = Color(0.45, 0.9, 0.55)
|
||||
const CRAFTER_MARKER_COLOR: Color = Color(1.0, 0.72, 0.25)
|
||||
const COMMAND_MARKER_COLOR: Color = Color(0.55, 0.6, 1.0)
|
||||
|
||||
# A structure built on a planet's surface (see GameState.extractors) is not
|
||||
# itself a map marker - it is a small badge on the existing planet marker,
|
||||
# in a colour distinct from both the planets and the space buildings so it
|
||||
# reads as "something is mined here" rather than another building type.
|
||||
const EXTRACTOR_BADGE_COLOR: Color = Color(1.0, 0.85, 0.3)
|
||||
const EXTRACTOR_BADGE_OFFSET: Vector2 = Vector2(8.0, -8.0)
|
||||
const EXTRACTOR_BADGE_RADIUS: float = 5.0
|
||||
|
||||
@onready var tracker: Node2D = get_node("../../../ExplorationTracker")
|
||||
@onready var fleet: Node2D = get_node("../../../Fleet")
|
||||
|
||||
|
|
@ -11,6 +28,59 @@ func _process(_delta: float) -> void:
|
|||
queue_redraw()
|
||||
|
||||
|
||||
# Local-space marker data for every player-placed building (GameState.
|
||||
# buildings), one entry per building with its colour already resolved. Kept
|
||||
# as a separate, callable step (rather than inlined in _draw()) so a
|
||||
# headless test - which has no GPU and cannot check drawn pixels - can
|
||||
# verify a built building actually produces a marker.
|
||||
func building_markers() -> Array:
|
||||
var markers: Array = []
|
||||
for b in GameState.buildings:
|
||||
markers.append({
|
||||
"position": b.position * WORLD_TO_LOCAL,
|
||||
"type": b.type,
|
||||
"color": _building_marker_color(b.type),
|
||||
})
|
||||
return markers
|
||||
|
||||
|
||||
func _building_marker_color(type: String) -> Color:
|
||||
match type:
|
||||
GameState.BUILDING_MOTHERSHIP:
|
||||
return MOTHERSHIP_MARKER_COLOR
|
||||
GameState.BUILDING_SHIP_CRAFTER:
|
||||
return CRAFTER_MARKER_COLOR
|
||||
_:
|
||||
return COMMAND_MARKER_COLOR
|
||||
|
||||
|
||||
# Whether the planet in this (explored) chunk has an extractor on it, per
|
||||
# GameState.has_extractor_at(). A separate predicate for the same
|
||||
# GPU-less-testability reason as building_markers().
|
||||
func chunk_has_extractor_badge(chunk: Vector2i) -> bool:
|
||||
if not WorldGen.has_planet_at_chunk(chunk):
|
||||
return false
|
||||
return GameState.has_extractor_at(WorldGen.planet_data_at_chunk(chunk).name)
|
||||
|
||||
|
||||
func _regular_polygon(center: Vector2, radius: float, sides: int, phase: float = 0.0) -> PackedVector2Array:
|
||||
var pts: PackedVector2Array = PackedVector2Array()
|
||||
for i in range(sides):
|
||||
var a: float = (float(i) / float(sides)) * TAU + phase
|
||||
pts.append(center + Vector2(cos(a), sin(a)) * radius)
|
||||
return pts
|
||||
|
||||
|
||||
# Filled shape plus a thin white outline - the same construction the fleet
|
||||
# marker below already uses, just factored out since buildings now need it
|
||||
# three times over (once per type) and the extractor badge needs it too.
|
||||
func _draw_marker_polygon(pts: PackedVector2Array, color: Color) -> void:
|
||||
draw_colored_polygon(pts, color)
|
||||
var closed: PackedVector2Array = pts.duplicate()
|
||||
closed.append(pts[0])
|
||||
draw_polyline(closed, Color(1, 1, 1), 1.5, true)
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
var chunk_size_local: float = WorldGen.CHUNK_SIZE * WORLD_TO_LOCAL
|
||||
|
||||
|
|
@ -32,6 +102,22 @@ func _draw() -> void:
|
|||
draw_circle(planet_local, 5.0, Color(0.7, 0.65, 0.55))
|
||||
draw_arc(planet_local, 5.0, 0.0, TAU, 16, Color(1.0, 1.0, 1.0, 0.9), 1.0, true)
|
||||
|
||||
if chunk_has_extractor_badge(chunk):
|
||||
var badge_pts: PackedVector2Array = _regular_polygon(
|
||||
planet_local + EXTRACTOR_BADGE_OFFSET, EXTRACTOR_BADGE_RADIUS, 4, PI / 4.0)
|
||||
_draw_marker_polygon(badge_pts, EXTRACTOR_BADGE_COLOR)
|
||||
|
||||
# Buildings are placed freely in space rather than tied to a chunk, so
|
||||
# they get their own pass rather than living inside the chunk loop above.
|
||||
for marker in building_markers():
|
||||
match marker.type:
|
||||
GameState.BUILDING_MOTHERSHIP:
|
||||
_draw_marker_polygon(_regular_polygon(marker.position, 8.0, 6, PI / 6.0), marker.color)
|
||||
GameState.BUILDING_SHIP_CRAFTER:
|
||||
_draw_marker_polygon(_regular_polygon(marker.position, 7.0, 4, 0.0), marker.color)
|
||||
_:
|
||||
_draw_marker_polygon(_regular_polygon(marker.position, 6.0, 4, PI / 4.0), marker.color)
|
||||
|
||||
var fleet_local: Vector2 = fleet.get_centroid() * WORLD_TO_LOCAL
|
||||
var marker_points: PackedVector2Array = PackedVector2Array([
|
||||
fleet_local + Vector2(0, -10),
|
||||
|
|
|
|||
45
tests/_marker_preview.gd
Normal file
45
tests/_marker_preview.gd
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
extends Node2D
|
||||
|
||||
# Preview, not a test (underscore prefix keeps it out of run_tests.sh).
|
||||
# Renders the world map with buildings placed and an extractor built, so the
|
||||
# issue #11 building markers and the issue #9 extractor badge can actually be
|
||||
# LOOKED at. --headless draws no pixels, so no headless test can tell whether
|
||||
# a marker is visible - see tests/screenshot.sh.
|
||||
#
|
||||
# bash tests/screenshot.sh res://tests/_marker_preview.tscn /tmp/map.png 20 720x1280
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
GameState.buildings.clear()
|
||||
GameState.extractors.clear()
|
||||
GameState.explored_chunks.clear()
|
||||
|
||||
# Near the origin, where the fleet starts, so everything lands on screen.
|
||||
GameState.add_building(GameState.BUILDING_MOTHERSHIP, Vector2(900.0, -700.0))
|
||||
GameState.add_building(GameState.BUILDING_SHIP_CRAFTER, Vector2(-1100.0, 300.0))
|
||||
GameState.add_building(GameState.BUILDING_COMMAND_CENTER, Vector2(400.0, 1200.0))
|
||||
|
||||
# Reveal a patch of map, and put an extractor on whichever planet is
|
||||
# nearest the origin so its badge is in frame too.
|
||||
var best: Vector2i = Vector2i.ZERO
|
||||
var best_distance: int = 1 << 30
|
||||
for x in range(-6, 7):
|
||||
for y in range(-6, 7):
|
||||
var chunk: Vector2i = Vector2i(x, y)
|
||||
GameState.explored_chunks[chunk] = true
|
||||
if WorldGen.has_planet_at_chunk(chunk):
|
||||
var d: int = absi(x) + absi(y)
|
||||
if d < best_distance:
|
||||
best_distance = d
|
||||
best = chunk
|
||||
if best_distance < (1 << 30):
|
||||
var data: Dictionary = WorldGen.planet_data_at_chunk(best)
|
||||
GameState.add_extractor(data.name, WorldGen.chunk_origin(best) + data.local_pos)
|
||||
print("extractor on '%s' at chunk %s" % [data.name, best])
|
||||
|
||||
var main: Node = load("res://Main.tscn").instantiate()
|
||||
add_child(main)
|
||||
# open_map(), not `visible = true` - opening also recentres the view on
|
||||
# the fleet, and without that the map is drawn from its own origin in the
|
||||
# corner of the screen with everything near the fleet cropped off.
|
||||
main.get_node("UI/WorldMap").open_map()
|
||||
1
tests/_marker_preview.gd.uid
Normal file
1
tests/_marker_preview.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://blbow3jq36fdw
|
||||
6
tests/_marker_preview.tscn
Normal file
6
tests/_marker_preview.tscn
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tests/_marker_preview.gd" id="1"]
|
||||
|
||||
[node name="MarkerPreview" type="Node2D"]
|
||||
script = ExtResource("1")
|
||||
25
tests/_planet_derrick_preview.gd
Normal file
25
tests/_planet_derrick_preview.gd
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
extends Node2D
|
||||
|
||||
# Preview, not a test. Two planets side by side in SPACE - the left one has
|
||||
# an extractor built on it, the right one does not - so the issue #9 derrick
|
||||
# can be compared against a plain planet and judged as actually visible.
|
||||
#
|
||||
# bash tests/screenshot.sh res://tests/_planet_derrick_preview.tscn /tmp/derrick.png 15 1280x720
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
GameState.extractors.clear()
|
||||
GameState.buildings.clear()
|
||||
GameState.add_extractor("Mined World", Vector2.ZERO)
|
||||
|
||||
_spawn("Mined World", Vector2(380.0, 380.0), 12345)
|
||||
_spawn("Untouched World", Vector2(900.0, 380.0), 4242)
|
||||
|
||||
|
||||
func _spawn(planet_name: String, at: Vector2, shape_seed: int) -> void:
|
||||
var planet: Node2D = load("res://Planet.tscn").instantiate()
|
||||
planet.planet_name = planet_name
|
||||
planet.shape_seed = shape_seed
|
||||
planet.position = at
|
||||
add_child(planet)
|
||||
print("%s: derrick=%s" % [planet_name, planet.shows_extractor])
|
||||
1
tests/_planet_derrick_preview.gd.uid
Normal file
1
tests/_planet_derrick_preview.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://dl4abkye58bd6
|
||||
6
tests/_planet_derrick_preview.tscn
Normal file
6
tests/_planet_derrick_preview.tscn
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://tests/_planet_derrick_preview.gd" id="1"]
|
||||
|
||||
[node name="PlanetDerrickPreview" type="Node2D"]
|
||||
script = ExtResource("1")
|
||||
|
|
@ -28,6 +28,13 @@ WAIT_FRAMES="${3:-60}"
|
|||
RESOLUTION="${4:-1280x720}"
|
||||
|
||||
proot-distro login ubuntu -- bash -c "
|
||||
# A killed run (or a session that ended mid-screenshot) leaves /tmp/.X99-lock
|
||||
# behind, and Xvfb then refuses to claim :99 - which surfaces much later and
|
||||
# far less obviously as Godot's 'X11 Display is not available'. Nothing else
|
||||
# uses :99 here, so a lock with no live Xvfb behind it is always stale.
|
||||
if ! pgrep -x Xvfb >/dev/null; then
|
||||
rm -f /tmp/.X99-lock /tmp/.X11-unix/X99
|
||||
fi
|
||||
Xvfb :99 -screen 0 ${RESOLUTION}x24 &
|
||||
XVFB_PID=\$!
|
||||
sleep 2
|
||||
|
|
|
|||
99
tests/test_map_building_markers.gd
Normal file
99
tests/test_map_building_markers.gd
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
extends SceneTree
|
||||
|
||||
# Headless gameplay test: run with
|
||||
# godot --headless -s res://tests/test_map_building_markers.gd
|
||||
# Forgejo issue #11: a building the player has placed must appear on the
|
||||
# world map. Without it the map showed explored chunks, planets and the
|
||||
# fleet, but nothing the player had actually built - so there was no way to
|
||||
# find your own base again once you had flown away from it.
|
||||
#
|
||||
# Buildings are not tied to a chunk (they are placed freely in space), so
|
||||
# they cannot ride along with the chunk markers and need their own pass.
|
||||
#
|
||||
# --headless has no GPU and draws no pixels, so this cannot assert on what
|
||||
# was rendered. It asserts on building_markers(), the step that turns
|
||||
# GameState.buildings into map-local marker data, which is the part that
|
||||
# _draw() then simply stamps out.
|
||||
#
|
||||
# Verifies:
|
||||
# 1. no buildings -> no markers (so a passing count is not just "always 3")
|
||||
# 2. every placed building produces exactly one marker
|
||||
# 3. markers land at the building's world position scaled into map space
|
||||
# 4. the three building types are told apart by colour, rather than all
|
||||
# being drawn as the same anonymous dot
|
||||
|
||||
var scene: Node
|
||||
var game_state: Node
|
||||
var map_content: Node2D
|
||||
var stage: int = 0
|
||||
|
||||
const MOTHERSHIP_AT: Vector2 = Vector2(2000.0, -1200.0)
|
||||
const CRAFTER_AT: Vector2 = Vector2(-800.0, 400.0)
|
||||
const COMMAND_AT: Vector2 = Vector2(150.0, 3000.0)
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
game_state = root.get_node("GameState")
|
||||
game_state.buildings.clear()
|
||||
game_state.extractors.clear()
|
||||
scene = load("res://Main.tscn").instantiate()
|
||||
root.add_child(scene)
|
||||
map_content = scene.get_node("UI/WorldMap/MapContent")
|
||||
|
||||
|
||||
func _process(_delta: float) -> bool:
|
||||
match stage:
|
||||
0:
|
||||
# Nothing built yet: the map must show no building markers at all.
|
||||
var empty: Array = map_content.building_markers()
|
||||
if not empty.is_empty():
|
||||
print("TEST_MAP_BUILDING_MARKERS: FAIL (%d markers with nothing built)" % empty.size())
|
||||
quit(1)
|
||||
return true
|
||||
|
||||
game_state.add_building(GameState.BUILDING_MOTHERSHIP, MOTHERSHIP_AT)
|
||||
game_state.add_building(GameState.BUILDING_SHIP_CRAFTER, CRAFTER_AT)
|
||||
game_state.add_building(GameState.BUILDING_COMMAND_CENTER, COMMAND_AT)
|
||||
stage = 1
|
||||
return false
|
||||
1:
|
||||
var markers: Array = map_content.building_markers()
|
||||
print("markers=%d for %d buildings" % [markers.size(), game_state.buildings.size()])
|
||||
if markers.size() != 3:
|
||||
print("TEST_MAP_BUILDING_MARKERS: FAIL (expected one marker per building)")
|
||||
quit(1)
|
||||
return true
|
||||
|
||||
var scale: float = map_content.WORLD_TO_LOCAL
|
||||
var expected: Dictionary = {
|
||||
GameState.BUILDING_MOTHERSHIP: MOTHERSHIP_AT * scale,
|
||||
GameState.BUILDING_SHIP_CRAFTER: CRAFTER_AT * scale,
|
||||
GameState.BUILDING_COMMAND_CENTER: COMMAND_AT * scale,
|
||||
}
|
||||
var colours: Array = []
|
||||
for m in markers:
|
||||
if not expected.has(m.type):
|
||||
print("TEST_MAP_BUILDING_MARKERS: FAIL (unexpected marker type %s)" % m.type)
|
||||
quit(1)
|
||||
return true
|
||||
var want: Vector2 = expected[m.type]
|
||||
if m.position.distance_to(want) > 0.001:
|
||||
print("%s marker at %s, expected %s" % [m.type, m.position, want])
|
||||
print("TEST_MAP_BUILDING_MARKERS: FAIL (marker is not where the building is)")
|
||||
quit(1)
|
||||
return true
|
||||
colours.append(m.color)
|
||||
|
||||
# Three buildings drawn in one indistinguishable colour would
|
||||
# satisfy every check above and still be useless to read.
|
||||
if colours[0] == colours[1] or colours[1] == colours[2] or colours[0] == colours[2]:
|
||||
print("colours=%s" % [colours])
|
||||
print("TEST_MAP_BUILDING_MARKERS: FAIL (building types are not visually distinct)")
|
||||
quit(1)
|
||||
return true
|
||||
|
||||
print("all 3 markers positioned correctly, colours distinct: %s" % [colours])
|
||||
print("TEST_MAP_BUILDING_MARKERS: PASS")
|
||||
quit(0)
|
||||
return true
|
||||
return false
|
||||
1
tests/test_map_building_markers.gd.uid
Normal file
1
tests/test_map_building_markers.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bx3bce8ix4rbg
|
||||
138
tests/test_planet_extractor_marker.gd
Normal file
138
tests/test_planet_extractor_marker.gd
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
extends SceneTree
|
||||
|
||||
# Headless gameplay test: run with
|
||||
# godot --headless -s res://tests/test_planet_extractor_marker.gd
|
||||
# Forgejo issue #9: a planet with an extractor built on it must show that
|
||||
# something is built there. The extractor lives on PlanetSurface.tscn, a
|
||||
# scene the player is not looking at from space, so an already-mined planet
|
||||
# was indistinguishable from an untouched one - you had to land to find out.
|
||||
#
|
||||
# Covered in both places the player looks at a planet:
|
||||
# - in space, as a derrick on the planet's own outline (Planet.gd)
|
||||
# - on the world map, as a badge on the planet marker (WorldMapContent.gd)
|
||||
#
|
||||
# --headless has no GPU, so this asserts on the state and geometry that
|
||||
# _draw() consumes, not on pixels. tests/screenshot.sh covers whether it is
|
||||
# actually visible.
|
||||
#
|
||||
# Verifies:
|
||||
# 1. a planet with no extractor shows neither derrick nor badge (so a pass
|
||||
# cannot just mean "always on")
|
||||
# 2. building one turns both on, keyed by planet NAME - the same key
|
||||
# GameState.has_extractor_at uses
|
||||
# 3. the derrick sits outside the planet's outline rather than buried in
|
||||
# it, which is the difference between visible and not
|
||||
|
||||
var game_state: Node
|
||||
var scene: Node
|
||||
var map_content: Node2D
|
||||
var planet_chunk: Vector2i
|
||||
var planet_name: String = ""
|
||||
var stage: int = 0
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
game_state = root.get_node("GameState")
|
||||
game_state.buildings.clear()
|
||||
game_state.extractors.clear()
|
||||
|
||||
var found: bool = false
|
||||
for x in range(-15, 15):
|
||||
for y in range(-15, 15):
|
||||
var chunk: Vector2i = Vector2i(x, y)
|
||||
if WorldGen.has_planet_at_chunk(chunk):
|
||||
planet_chunk = chunk
|
||||
planet_name = WorldGen.planet_data_at_chunk(chunk).name
|
||||
found = true
|
||||
break
|
||||
if found:
|
||||
break
|
||||
if not found:
|
||||
print("TEST_PLANET_EXTRACTOR_MARKER: FAIL (no planet chunk to test with)")
|
||||
quit(1)
|
||||
return
|
||||
|
||||
scene = load("res://Main.tscn").instantiate()
|
||||
root.add_child(scene)
|
||||
map_content = scene.get_node("UI/WorldMap/MapContent")
|
||||
|
||||
|
||||
func _spawn_planet() -> Node2D:
|
||||
var planet: Node2D = load("res://Planet.tscn").instantiate()
|
||||
planet.planet_name = planet_name
|
||||
planet.shape_seed = 12345
|
||||
scene.add_child(planet)
|
||||
return planet
|
||||
|
||||
|
||||
func _process(_delta: float) -> bool:
|
||||
match stage:
|
||||
0:
|
||||
# --- nothing built there yet --------------------------------
|
||||
var clean: Node2D = _spawn_planet()
|
||||
if clean.shows_extractor:
|
||||
print("TEST_PLANET_EXTRACTOR_MARKER: FAIL (derrick on a planet with no extractor)")
|
||||
quit(1)
|
||||
return true
|
||||
if map_content.chunk_has_extractor_badge(planet_chunk):
|
||||
print("TEST_PLANET_EXTRACTOR_MARKER: FAIL (map badge with no extractor)")
|
||||
quit(1)
|
||||
return true
|
||||
clean.queue_free()
|
||||
print("no extractor -> no derrick, no badge (planet '%s')" % planet_name)
|
||||
|
||||
game_state.add_extractor(planet_name, WorldGen.chunk_origin(planet_chunk))
|
||||
stage = 1
|
||||
return false
|
||||
1:
|
||||
# --- extractor built ----------------------------------------
|
||||
var mined: Node2D = _spawn_planet()
|
||||
if not mined.shows_extractor:
|
||||
print("TEST_PLANET_EXTRACTOR_MARKER: FAIL (no derrick after building an extractor)")
|
||||
quit(1)
|
||||
return true
|
||||
if not map_content.chunk_has_extractor_badge(planet_chunk):
|
||||
print("TEST_PLANET_EXTRACTOR_MARKER: FAIL (no map badge after building an extractor)")
|
||||
quit(1)
|
||||
return true
|
||||
|
||||
# The derrick has to clear the outline to be seen at all. Compare
|
||||
# its tip against the planet's own radius in that direction, not
|
||||
# against a constant - the outline is a lumpy seeded shape.
|
||||
var derrick: PackedVector2Array = mined.extractor_derrick_points()
|
||||
if derrick.size() < 3:
|
||||
print("TEST_PLANET_EXTRACTOR_MARKER: FAIL (derrick has no shape)")
|
||||
quit(1)
|
||||
return true
|
||||
# Measure the TIP, not the whole polygon. The base corners sit a
|
||||
# half-width to either side of the rim vertex, so they are
|
||||
# sqrt(|base|^2 + w^2) from the centre and clear the rim on their
|
||||
# own - a max over every point called a derrick pointing straight
|
||||
# into the ground "visible", which is exactly the bug this check
|
||||
# exists to catch.
|
||||
var furthest: float = mined.extractor_derrick_tip().length()
|
||||
var rim: float = 0.0
|
||||
for p in mined.shape_points:
|
||||
rim = maxf(rim, p.length())
|
||||
print("derrick tip reaches %.1f, planet outline reaches %.1f" % [furthest, rim])
|
||||
if furthest <= rim:
|
||||
print("TEST_PLANET_EXTRACTOR_MARKER: FAIL (derrick buried inside the planet)")
|
||||
quit(1)
|
||||
return true
|
||||
|
||||
# A different planet must NOT inherit the marker: the flag is keyed
|
||||
# by name, and getting that wrong would light up every planet.
|
||||
var other: Node2D = load("res://Planet.tscn").instantiate()
|
||||
other.planet_name = planet_name + " II"
|
||||
other.shape_seed = 999
|
||||
scene.add_child(other)
|
||||
if other.shows_extractor:
|
||||
print("TEST_PLANET_EXTRACTOR_MARKER: FAIL (marker leaked onto a different planet)")
|
||||
quit(1)
|
||||
return true
|
||||
|
||||
print("extractor -> derrick + badge, and only on the right planet")
|
||||
print("TEST_PLANET_EXTRACTOR_MARKER: PASS")
|
||||
quit(0)
|
||||
return true
|
||||
return false
|
||||
1
tests/test_planet_extractor_marker.gd.uid
Normal file
1
tests/test_planet_extractor_marker.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://dcr7qb1yoewcy
|
||||
Loading…
Reference in a new issue