Complete lab, quest, garrison, and energy tickets
This commit is contained in:
parent
d413d1da9f
commit
0f7edff15a
20 changed files with 238 additions and 45 deletions
|
|
@ -12,7 +12,7 @@ input_buffers = {
|
|||
collected_outputs = PackedStringArray("energy")
|
||||
conversion_input = "gas"
|
||||
conversion_output = "energy"
|
||||
conversion_rate = 1.0
|
||||
conversion_rate = 0.2
|
||||
conversion_yield = 2
|
||||
|
||||
[resource]
|
||||
|
|
|
|||
|
|
@ -143,6 +143,8 @@ func _process(delta: float) -> void:
|
|||
build_menu.refresh_option_states(_planet_under_fleet())
|
||||
if craft_menu.visible:
|
||||
craft_menu.refresh()
|
||||
if lab_menu.visible:
|
||||
_refresh_lab()
|
||||
if transfer_menu.visible:
|
||||
transfer_menu.refresh()
|
||||
if world_map.visible or _menu_is_open():
|
||||
|
|
@ -592,6 +594,8 @@ func _on_lab_buy() -> void:
|
|||
|
||||
|
||||
func _refresh_lab() -> void:
|
||||
lab_menu.set_status("")
|
||||
lab_menu.hide_research_progress()
|
||||
var definitions: Array = VESSEL_DEFINITIONS.all()
|
||||
lab_menu.render_vessels(definitions, lab_vessel)
|
||||
lab_menu.vessel_heading.text = "%s research · %d RP" % [
|
||||
|
|
@ -622,13 +626,16 @@ func _refresh_lab() -> void:
|
|||
or not GameState.upgrade_maxed(selected.id))
|
||||
if not queued.is_empty():
|
||||
var queued_points: int = GameState.research_point_cost(String(queued.id))
|
||||
var progress: float = float(queued.elapsed) \
|
||||
/ maxf(GameState.lab_research_time(), 0.001)
|
||||
if not bool(queued.started) and GameState.research_points < queued_points:
|
||||
lab_menu.set_status("Need %d research point%s (have %d)" % [queued_points,
|
||||
"" if queued_points == 1 else "s", GameState.research_points])
|
||||
lab_menu.show_research_progress(
|
||||
"Need %d research point%s (have %d)" % [queued_points,
|
||||
"" if queued_points == 1 else "s", GameState.research_points], progress)
|
||||
else:
|
||||
lab_menu.set_status("%s %d%%" % ["Researching" if bool(queued.started)
|
||||
else "Waiting for resources", int(100.0 * float(queued.elapsed)
|
||||
/ maxf(GameState.lab_research_time(), 0.001))])
|
||||
lab_menu.show_research_progress(
|
||||
"Researching" if bool(queued.started) else "Waiting for resources",
|
||||
progress)
|
||||
|
||||
|
||||
func _on_transfer_pressed() -> void:
|
||||
|
|
|
|||
26
core/CombatTarget.gd
Normal file
26
core/CombatTarget.gd
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
class_name CombatTarget
|
||||
extends RefCounted
|
||||
|
||||
# One opt-in contract for anything a player weapon may deliberately attack.
|
||||
# Aim assist, autonomous garrisons and projectile impacts all consume the same
|
||||
# group, so a new target composition does not need a concrete-type branch in
|
||||
# each combat system.
|
||||
const GROUP: StringName = &"player_weapon_targets"
|
||||
const PRIORITY_META: StringName = &"player_weapon_target_priority"
|
||||
|
||||
# A garrison protects its post before mining nearby debris. Distances still
|
||||
# decide between targets at the same priority.
|
||||
const RESOURCE_PRIORITY: int = 0
|
||||
const DANGER_PRIORITY: int = 100
|
||||
|
||||
|
||||
static func register(node: Node, priority: int = RESOURCE_PRIORITY) -> void:
|
||||
if node == null:
|
||||
return
|
||||
node.add_to_group(GROUP)
|
||||
node.set_meta(PRIORITY_META, priority)
|
||||
|
||||
|
||||
static func priority(node: Node) -> int:
|
||||
return int(node.get_meta(PRIORITY_META, RESOURCE_PRIORITY)) if node != null \
|
||||
else RESOURCE_PRIORITY
|
||||
1
core/CombatTarget.gd.uid
Normal file
1
core/CombatTarget.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://pgp5qd13xgro
|
||||
|
|
@ -242,6 +242,14 @@ const CHAIN: Array = [
|
|||
"done": "Three kinds of world surveyed. Not one of them holds any sign of the convoy.",
|
||||
"important": true,
|
||||
},
|
||||
{
|
||||
"id": "propulser",
|
||||
"title": "A LONGER REACH",
|
||||
"objective": "Build an Energy Propulser vessel",
|
||||
"goal": {"kind": GOAL_FLEET_VESSEL, "vessel": "distorder", "count": 1},
|
||||
"brief": "The trail may run far beyond this cluster. Unlock an Energy Propulser at the ship lab, build one at the yard, and crew it. Its drive will let the whole fleet search faster.",
|
||||
"done": "The Energy Propulser is in formation. We can cast the search wider now.",
|
||||
},
|
||||
{
|
||||
"id": "trail",
|
||||
"title": "THE TRAIL",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
extends Area2D
|
||||
|
||||
const COMBAT_TARGET = preload("res://core/CombatTarget.gd")
|
||||
|
||||
signal destroyed
|
||||
|
||||
const ENEMY_DEFINITIONS = preload("res://enemies/EnemyDefinitions.gd")
|
||||
|
|
@ -30,10 +32,10 @@ var max_hp: int
|
|||
|
||||
func _ready() -> void:
|
||||
add_to_group("enemies")
|
||||
# Weapons discover anything the player may deliberately aim at through one
|
||||
# compositional contract. Asteroids implement the same contract without the
|
||||
# aim-assist code having to know either concrete type (#181).
|
||||
add_to_group("player_aim_targets")
|
||||
# Weapons discover anything the player may deliberately attack through one
|
||||
# compositional contract. Asteroids implement the same contract without
|
||||
# combat systems having to know either concrete type (#181, #186).
|
||||
COMBAT_TARGET.register(self, COMBAT_TARGET.DANGER_PRIORITY)
|
||||
definition = ENEMY_DEFINITIONS.get_definition(enemy_id)
|
||||
speed = definition.speed
|
||||
acceleration = definition.acceleration
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
extends Node2D
|
||||
|
||||
# Preview, not a test - the ship lab at the real 720x1280 viewport, with a
|
||||
# vessel selected and its upgrades listed.
|
||||
# vessel selected, its upgrades listed, and active research in progress.
|
||||
#
|
||||
# bash tests/screenshot.sh res://tests/_lab_menu_preview.tscn /tmp/lab.png 25 720x1280
|
||||
|
||||
|
|
@ -13,10 +13,21 @@ func _ready() -> void:
|
|||
GameState.bank_inventory.set_amount(GameState.RESOURCE_ORE, 310)
|
||||
GameState.fleet_inventory.set_amount(GameState.RESOURCE_ORE, 47)
|
||||
GameState.research_points = 4
|
||||
GameState.build_time_scale = 1.0
|
||||
var lab_id: int = GameState.add_building(GameState.BUILDING_SHIP_LAB,
|
||||
Vector2.ZERO)
|
||||
GameState.queue_lab_research(lab_id, "int_rate")
|
||||
GameState.deposit_resource_to_building(lab_id, GameState.RESOURCE_ORE,
|
||||
GameState.upgrade_cost("int_rate"))
|
||||
GameState.call("_advance_lab_research", GameState.lab_research_time() * 0.42)
|
||||
# Freeze the model so a slow software-rendered capture cannot finish the
|
||||
# research before it takes the frame this preview exists to show.
|
||||
GameState.set_process(false)
|
||||
var main: Node = load("res://world/Main.tscn").instantiate()
|
||||
add_child(main)
|
||||
main.get_node("EnemySpawner").set_process(false)
|
||||
main.get_node("AsteroidField").set_process(false)
|
||||
var build: Node = main.get_node("BuildController")
|
||||
build.lab_menu.visible = true
|
||||
build.lab_building_id = lab_id
|
||||
build.lab_menu.open_for(lab_id)
|
||||
build._refresh_lab()
|
||||
|
|
|
|||
1
tests/test_dialog_releases_joysticks.gd.uid
Normal file
1
tests/test_dialog_releases_joysticks.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://qgrnenhp5sqh
|
||||
|
|
@ -53,6 +53,8 @@ func _initialize() -> void:
|
|||
var logistics: Resource = game_state.building_logistics_profile(game_state.find_building(burner_id))
|
||||
conversion_rate = logistics.conversion_rate
|
||||
conversion_yield = logistics.conversion_yield
|
||||
if not is_equal_approx(conversion_rate, 0.2):
|
||||
_fail("burner conversion rate is %.2f/s, expected the reduced 0.20/s" % conversion_rate)
|
||||
|
||||
|
||||
func _process(delta: float) -> bool:
|
||||
|
|
@ -72,13 +74,13 @@ func _process(delta: float) -> bool:
|
|||
print("energy is generic: in resource_types(), own definition, own colour %s" % energy_color)
|
||||
|
||||
# --- B. produces energy from local gas at the configured rate -
|
||||
if elapsed < 2.5:
|
||||
if elapsed < 5.5:
|
||||
return false
|
||||
var gas_left: int = game_state.building_resource(burner_id, GameState.RESOURCE_GAS)
|
||||
var energy_made: int = game_state.building_resource(burner_id, GameState.RESOURCE_ENERGY)
|
||||
var gas_burned: int = 20 - gas_left
|
||||
if gas_burned < 2:
|
||||
return _fail("burner only burned %d gas in 2.5s at rate %.1f/s" % [gas_burned, conversion_rate])
|
||||
if gas_burned < 1:
|
||||
return _fail("burner burned no gas in 5.5s at rate %.2f/s" % conversion_rate)
|
||||
if energy_made != gas_burned * conversion_yield:
|
||||
return _fail("burner made %d energy for %d gas burned, expected %dx" % [
|
||||
energy_made, gas_burned, conversion_yield
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ func _process(delta: float) -> bool:
|
|||
return false
|
||||
1:
|
||||
# --- B. running, it converts ----------------------------------
|
||||
if elapsed < 1.5:
|
||||
if elapsed < 5.5:
|
||||
return false
|
||||
if _stock(GameState.RESOURCE_ENERGY) <= 0:
|
||||
return _fail("a running burner made no energy in %.1fs" % elapsed)
|
||||
|
|
@ -119,7 +119,7 @@ func _process(delta: float) -> bool:
|
|||
elapsed = 0.0
|
||||
return false
|
||||
3:
|
||||
if elapsed < 1.5:
|
||||
if elapsed < 5.5:
|
||||
return false
|
||||
if _stock(GameState.RESOURCE_ENERGY) <= energy_at_pause:
|
||||
return _fail("a resumed burner made no further energy")
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ extends SceneTree
|
|||
# E. it goes back to patrolling once the enemy is gone
|
||||
# F. losing a garrison vessel frees the berth, so it gets crewed again
|
||||
# G. enemies fight back - a garrison vessel is a target like any other
|
||||
# H. #186 - with no hostile nearby, the same weapon-target contract lets a
|
||||
# garrison shoot asteroids for ore
|
||||
|
||||
var game_state: Node
|
||||
var main: Node
|
||||
|
|
@ -30,6 +32,7 @@ var outpost_id: int = 0
|
|||
var guard: Node2D
|
||||
var component: Node
|
||||
var enemy: Node2D
|
||||
var asteroid: Node2D
|
||||
var stage: int = 0
|
||||
var elapsed: float = 0.0
|
||||
var orbit_min: float = INF
|
||||
|
|
@ -231,14 +234,40 @@ func _process(delta: float) -> bool:
|
|||
return _fail("the garrison is still fighting an enemy that is gone")
|
||||
print("stood down and went back to its patrol")
|
||||
|
||||
# --- H. asteroids are valid lower-priority targets -----------
|
||||
asteroid = load("res://world/Asteroid.tscn").instantiate()
|
||||
asteroid.position = Vector2(300.0, 0.0)
|
||||
asteroid.velocity = Vector2.ZERO
|
||||
main.add_child(asteroid)
|
||||
asteroid.hp = 100000
|
||||
dummy_hp_before = asteroid.hp
|
||||
elapsed = 0.0
|
||||
stage = 6
|
||||
return false
|
||||
6:
|
||||
if elapsed < 2.5:
|
||||
return false
|
||||
if not component.is_engaging():
|
||||
return _fail("an asteroid beside the outpost was not engaged")
|
||||
if asteroid.hp >= dummy_hp_before:
|
||||
return _fail("the garrison engaged an asteroid but never shot it")
|
||||
print("garrison shot a nearby asteroid through the shared target contract")
|
||||
asteroid.queue_free()
|
||||
elapsed = 0.0
|
||||
stage = 7
|
||||
return false
|
||||
7:
|
||||
if elapsed < 0.5:
|
||||
return false
|
||||
|
||||
# --- F. a lost vessel frees its berth ------------------------
|
||||
if game_state.garrison_crew(outpost_id).size() != 1:
|
||||
return _fail("the crew is not 1 before the kill")
|
||||
guard.take_damage(1)
|
||||
elapsed = 0.0
|
||||
stage = 6
|
||||
stage = 8
|
||||
return false
|
||||
6:
|
||||
8:
|
||||
if elapsed < 0.3:
|
||||
return false
|
||||
if not game_state.garrison_crew(outpost_id).is_empty():
|
||||
|
|
|
|||
|
|
@ -133,6 +133,23 @@ func _catalogue_cases() -> bool:
|
|||
return _fail("the quest after the mothership is %s, not exploration" % after.id)
|
||||
print("the mothership answers with distance, and exploration follows it")
|
||||
|
||||
# #185: the final wide search has a propulsion lesson immediately before
|
||||
# it. The goal uses the stable vessel id while the player-facing copy uses
|
||||
# the renamed Energy Propulser name.
|
||||
var trail: Dictionary = QUESTS.by_id("trail")
|
||||
var trail_at: int = QUESTS.CHAIN.find(trail)
|
||||
if trail_at <= 0:
|
||||
return _fail("the Chart 150 quest is missing or opens the chain")
|
||||
var before_trail: Dictionary = QUESTS.at(trail_at - 1)
|
||||
if String(before_trail.id) != "propulser" \
|
||||
or String(before_trail.goal.kind) != QUESTS.GOAL_FLEET_VESSEL \
|
||||
or String(before_trail.goal.vessel) != GameState.VESSEL_DISTORDER:
|
||||
return _fail("Chart 150 is preceded by %s, not the Energy Propulser vessel quest" \
|
||||
% before_trail.get("id", "nothing"))
|
||||
if not "Energy Propulser" in String(before_trail.objective):
|
||||
return _fail("the propulsion quest does not name the Energy Propulser to the player")
|
||||
print("the Energy Propulser vessel quest leads directly into Chart 150")
|
||||
|
||||
# --- R. #145: every structure quest can actually be finished ---------
|
||||
# The extractor quest could not be. Extractors are placed onto a planet
|
||||
# from space and live in their own array, not in `buildings`, so the
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ var scene: Node
|
|||
var fleet: Node
|
||||
var field: Node
|
||||
var controller: Node
|
||||
var lab_id: int = 0
|
||||
var crafter_id: int = 0
|
||||
var stage: int = 0
|
||||
var elapsed: float = 0.0
|
||||
|
|
@ -83,6 +84,7 @@ func _process(delta: float) -> bool:
|
|||
game_state.unlocked = ["shipyards", "vessel_science", "coordination", "wing", "squadron"]
|
||||
game_state.research_points = 10
|
||||
field.place(GameState.BUILDING_SHIP_LAB, Vector2(-700.0, 0.0))
|
||||
lab_id = int(game_state.buildings[-1].id)
|
||||
field.place(GameState.BUILDING_SHIP_CRAFTER, Vector2(700.0, 0.0))
|
||||
crafter_id = int(game_state.buildings[-1].id)
|
||||
elapsed = 0.0
|
||||
|
|
@ -130,6 +132,29 @@ func _process(delta: float) -> bool:
|
|||
return true
|
||||
print("lab cards are vessel-specific and selectable")
|
||||
|
||||
# #184: a queued research uses the bar as its changing value. The
|
||||
# status names the state but never repeats it as a percentage.
|
||||
var lab_progress: ProgressBar = controller.get_node(
|
||||
"../UI/LabMenu/ResearchProgress")
|
||||
var lab_status: Label = controller.get_node("../UI/LabMenu/StatusLabel")
|
||||
controller.lab_building_id = lab_id
|
||||
if not game_state.queue_lab_research(lab_id, "int_rate"):
|
||||
return _fail("could not queue research for the progress-bar check")
|
||||
controller.call("_refresh_lab")
|
||||
if not lab_progress.visible or lab_progress.show_percentage:
|
||||
return _fail("pending research did not show a percentage-free progress bar")
|
||||
if "%" in lab_status.text:
|
||||
return _fail("pending research still prints a percentage: '%s'" % lab_status.text)
|
||||
game_state.deposit_resource_to_building(lab_id, GameState.RESOURCE_ORE,
|
||||
game_state.upgrade_cost("int_rate"))
|
||||
game_state.call("_advance_lab_research", game_state.lab_research_time() * 0.25)
|
||||
controller.call("_refresh_lab")
|
||||
if lab_progress.value < 0.2 or lab_progress.value > 0.3 \
|
||||
or lab_status.text != "Researching":
|
||||
return _fail("research bar did not track active work (value %.2f, status '%s')" \
|
||||
% [lab_progress.value, lab_status.text])
|
||||
print("pending lab research is shown by a live progress bar, without percentage text")
|
||||
|
||||
# --- 3. buying raises the level and charges ------------------
|
||||
var before_rocks: int = game_state.rocks
|
||||
var first_cost: int = game_state.upgrade_cost("int_damage")
|
||||
|
|
@ -225,3 +250,9 @@ func _process(delta: float) -> bool:
|
|||
quit(0)
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _fail(message: String) -> bool:
|
||||
print("TEST_SHIP_LAB: FAIL (%s)" % message)
|
||||
quit(1)
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ const VESSEL_MARGIN := 24.0
|
|||
@onready var vessel_heading: Label = $VesselLabel
|
||||
@onready var detail: Label = $DetailLabel
|
||||
@onready var status: Label = $StatusLabel
|
||||
@onready var research_progress: ProgressBar = $ResearchProgress
|
||||
@onready var resource_bar: Control = $Bar
|
||||
|
||||
var building_id: int = 0
|
||||
|
|
@ -96,6 +97,16 @@ func set_status(message: String) -> void:
|
|||
status.text = message
|
||||
|
||||
|
||||
func show_research_progress(message: String, ratio: float) -> void:
|
||||
status.text = message
|
||||
research_progress.value = clampf(ratio, 0.0, 1.0)
|
||||
research_progress.visible = true
|
||||
|
||||
|
||||
func hide_research_progress() -> void:
|
||||
research_progress.visible = false
|
||||
|
||||
|
||||
func _build_price_tags() -> void:
|
||||
for i in range(upgrade_labels.size()):
|
||||
var label: Control = upgrade_labels[i]
|
||||
|
|
|
|||
|
|
@ -1,9 +1,28 @@
|
|||
[gd_scene load_steps=4 format=3]
|
||||
[gd_scene load_steps=6 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://ui/IconButton.gd" id="10"]
|
||||
[ext_resource type="Script" path="res://ui/ResourceBar.gd" id="24"]
|
||||
[ext_resource type="Script" path="res://ui/menus/LabMenu.gd" id="menu_script"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_progress_background"]
|
||||
bg_color = Color(0.025, 0.045, 0.09, 0.92)
|
||||
border_width_left = 2
|
||||
border_width_top = 2
|
||||
border_width_right = 2
|
||||
border_width_bottom = 2
|
||||
border_color = Color(0.18, 0.48, 0.62, 0.9)
|
||||
corner_radius_top_left = 8
|
||||
corner_radius_top_right = 8
|
||||
corner_radius_bottom_right = 8
|
||||
corner_radius_bottom_left = 8
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_progress_fill"]
|
||||
bg_color = Color(0.3, 0.9, 1, 0.88)
|
||||
corner_radius_top_left = 6
|
||||
corner_radius_top_right = 6
|
||||
corner_radius_bottom_right = 6
|
||||
corner_radius_bottom_left = 6
|
||||
|
||||
[node name="LabMenu" type="Control" groups=["modal_menus"]]
|
||||
visible = false
|
||||
mouse_filter = 2
|
||||
|
|
@ -283,12 +302,29 @@ anchor_bottom = 1.0
|
|||
offset_left = 32
|
||||
offset_top = -174
|
||||
offset_right = -32
|
||||
offset_bottom = -120
|
||||
offset_bottom = -128
|
||||
theme_override_font_sizes/font_size = 30
|
||||
autowrap_mode = 2
|
||||
horizontal_alignment = 1
|
||||
text = ""
|
||||
|
||||
[node name="ResearchProgress" type="ProgressBar" parent="."]
|
||||
visible = false
|
||||
mouse_filter = 2
|
||||
anchor_left = 0.5
|
||||
anchor_top = 1.0
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 1.0
|
||||
offset_left = -280
|
||||
offset_top = -116
|
||||
offset_right = 280
|
||||
offset_bottom = -82
|
||||
theme_override_styles/background = SubResource("StyleBoxFlat_progress_background")
|
||||
theme_override_styles/fill = SubResource("StyleBoxFlat_progress_fill")
|
||||
max_value = 1.0
|
||||
step = 0.001
|
||||
show_percentage = false
|
||||
|
||||
[node name="NextButton" type="Control" parent="."]
|
||||
visible = false
|
||||
anchor_left = 0.5
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
extends Area2D
|
||||
|
||||
const COMBAT_TARGET = preload("res://core/CombatTarget.gd")
|
||||
|
||||
# #86: faster. The bolt used to crawl far enough behind a moving target that
|
||||
# leading a shot was guesswork rather than skill.
|
||||
# #155: was 850, which took 1.1s to cross the gun's own range - long enough
|
||||
|
|
@ -54,9 +56,9 @@ func _physics_process(delta: float) -> void:
|
|||
|
||||
|
||||
func _on_area_entered(area: Area2D) -> void:
|
||||
# Asteroids answer take_damage() exactly as enemies do, so both are hit by
|
||||
# the same branch rather than by a second special case.
|
||||
if area.is_in_group("enemies") or area.is_in_group("asteroids"):
|
||||
# Anything that opted into the weapon-target contract answers take_damage(),
|
||||
# so adding a new target composition needs no projectile type check.
|
||||
if area.is_in_group(COMBAT_TARGET.GROUP):
|
||||
area.take_damage(damage)
|
||||
queue_free()
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ extends Area2D
|
|||
signal destroyed
|
||||
|
||||
const VESSEL_DEFINITIONS = preload("res://vessels/VesselDefinitions.gd")
|
||||
const COMBAT_TARGET = preload("res://core/CombatTarget.gd")
|
||||
|
||||
@export var max_speed: float = 460.0
|
||||
@export var acceleration: float = 450.0
|
||||
|
|
@ -200,17 +201,14 @@ func _breakaway_direction() -> Vector2:
|
|||
|
||||
|
||||
func _on_area_entered(area: Area2D) -> void:
|
||||
if not (area.is_in_group("enemies") or area.is_in_group("asteroids")):
|
||||
if not area.is_in_group(COMBAT_TARGET.GROUP):
|
||||
return
|
||||
# The enemy is told about the impact BEFORE this ship resolves its own
|
||||
# damage, because resolving it can free this node - and a ship that dies
|
||||
# to a ramming chaser must still take the chaser with it. Whether the
|
||||
# impact actually destroys the enemy is the enemy's decision (#82).
|
||||
if area.is_in_group("enemies") and area.has_method("collide_with_player_vessel"):
|
||||
# A target is told about the impact BEFORE this ship resolves its own
|
||||
# damage, because resolving it can free this node. Whether the impact does
|
||||
# anything to the target is part of that target's composition (#82).
|
||||
if area.has_method("collide_with_player_vessel"):
|
||||
area.collide_with_player_vessel()
|
||||
var absorbed: bool = take_damage(1)
|
||||
if absorbed and area.is_in_group("asteroids") and is_instance_valid(area):
|
||||
area.queue_free()
|
||||
take_damage(1)
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
class_name VesselGarrisonComponent
|
||||
extends Node
|
||||
|
||||
const COMBAT_TARGET = preload("res://core/CombatTarget.gd")
|
||||
|
||||
# #105 (#97g): "When a vessel is assigned to an outpost, it navigates around
|
||||
# the outpost by itself, attacking nearby ennemies if any are detected."
|
||||
#
|
||||
|
|
@ -66,7 +68,7 @@ func is_engaging() -> bool:
|
|||
|
||||
func tick(delta: float) -> void:
|
||||
var ship: Node2D = get_parent()
|
||||
var target: Node2D = _nearest_enemy(ship)
|
||||
var target: Node2D = _nearest_target(ship)
|
||||
_engaging = target != null
|
||||
if target == null:
|
||||
_patrol(ship, delta)
|
||||
|
|
@ -176,20 +178,27 @@ func _hold_fire(ship: Node2D) -> void:
|
|||
weapon.set_fire_direction(Vector2.ZERO)
|
||||
|
||||
|
||||
func _nearest_enemy(ship: Node2D) -> Node2D:
|
||||
func _nearest_target(ship: Node2D) -> Node2D:
|
||||
var nearest: Node2D = null
|
||||
var best: float = detection_range
|
||||
for enemy in get_tree().get_nodes_in_group("enemies"):
|
||||
if not is_instance_valid(enemy) or enemy.is_queued_for_deletion():
|
||||
var best_distance: float = INF
|
||||
var best_priority: int = -1
|
||||
for target_node in get_tree().get_nodes_in_group(COMBAT_TARGET.GROUP):
|
||||
if not is_instance_valid(target_node) or not (target_node is Node2D) \
|
||||
or target_node.is_queued_for_deletion():
|
||||
continue
|
||||
var at: Vector2 = (enemy as Node2D).global_position
|
||||
var target: Node2D = target_node as Node2D
|
||||
var at: Vector2 = target.global_position
|
||||
# Judged from the POST, not from the vessel: otherwise a garrison
|
||||
# that had drifted out to its leash would see further still, and
|
||||
# could be walked away from its outpost one enemy at a time.
|
||||
if station.distance_to(at) > leash_range + engage_distance:
|
||||
continue
|
||||
var d: float = ship.global_position.distance_to(at)
|
||||
if d < best:
|
||||
best = d
|
||||
nearest = enemy
|
||||
if d > detection_range:
|
||||
continue
|
||||
var priority: int = COMBAT_TARGET.priority(target)
|
||||
if priority > best_priority or (priority == best_priority and d < best_distance):
|
||||
best_priority = priority
|
||||
best_distance = d
|
||||
nearest = target
|
||||
return nearest
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ var fire_timer: float = 0.0
|
|||
var fire_target: Vector2 = Vector2.ZERO
|
||||
var has_fire_target: bool = false
|
||||
var projectile_scene: PackedScene = preload("res://vessels/Projectile.tscn")
|
||||
const COMBAT_TARGET = preload("res://core/CombatTarget.gd")
|
||||
# By path, not by class name - see the note at the top of MuzzleSparkVFX.
|
||||
const MUZZLE_SPARK = preload("res://vfx/MuzzleSparkVFX.gd")
|
||||
|
||||
|
|
@ -58,7 +59,6 @@ const ASSIST_CONE_DEGREES: float = 28.0
|
|||
# smaller than the directional cone at ordinary weapon range: it catches a
|
||||
# pointer placed beside something without making the whole screen magnetic.
|
||||
const ASSIST_CURSOR_RADIUS: float = 180.0
|
||||
const ASSIST_TARGET_GROUP: StringName = &"player_aim_targets"
|
||||
|
||||
|
||||
# The concrete point an aim should stick to. A target can qualify in two ways:
|
||||
|
|
@ -81,7 +81,7 @@ static func assisted_point(tree: SceneTree, origin: Vector2, point: Vector2,
|
|||
var cursor_distance: float = INF
|
||||
var direction_pick: Node2D = null
|
||||
var direction_distance: float = INF
|
||||
for target_node in tree.get_nodes_in_group(ASSIST_TARGET_GROUP):
|
||||
for target_node in tree.get_nodes_in_group(COMBAT_TARGET.GROUP):
|
||||
if not is_instance_valid(target_node) or not (target_node is Node2D) \
|
||||
or target_node.is_queued_for_deletion():
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
extends Area2D
|
||||
|
||||
const COMBAT_TARGET = preload("res://core/CombatTarget.gd")
|
||||
|
||||
# A drifting rock that can be shot apart for the ore inside it.
|
||||
#
|
||||
# Asteroids used to be pure hazard: they could only ever cost you a ship. Now
|
||||
|
|
@ -32,7 +34,7 @@ var shape_points: PackedVector2Array = PackedVector2Array()
|
|||
func _ready() -> void:
|
||||
randomize()
|
||||
add_to_group("asteroids")
|
||||
add_to_group("player_aim_targets")
|
||||
COMBAT_TARGET.register(self, COMBAT_TARGET.RESOURCE_PRIORITY)
|
||||
area_entered.connect(_on_area_entered)
|
||||
|
||||
var point_count: int = 7
|
||||
|
|
|
|||
Loading…
Reference in a new issue