diff --git a/controllers/LaserController.gd b/controllers/LaserController.gd index f017811..3470864 100644 --- a/controllers/LaserController.gd +++ b/controllers/LaserController.gd @@ -11,9 +11,6 @@ extends "res://controllers/AimedFleetAbilityController.gd" # while they aim it. const BEAM_EFFECT = preload("res://vfx/LaserBeamEffect.gd") -# #201: the beam borrows the fire stick's aim helper, so it is loaded here -# for its static assist rather than for anything a laser hull carries. -const WEAPON_COMPONENT = preload("res://vessels/VesselWeaponComponent.gd") const BEAM_COLOR := BEAM_EFFECT.BEAM_COLOR const TARGET_COLOR := Color(0.6, 0.95, 1.0) const BEAM_HALF_WIDTH := BEAM_EFFECT.BEAM_HALF_WIDTH @@ -54,25 +51,7 @@ func _aim_max_range() -> float: func _calculate_aim_target(drag: Vector2, origin: Vector2) -> Vector2: # A beam always reaches full range; dragging chooses its direction only. var direction: Vector2 = drag.normalized() if drag.length() > 0.001 else Vector2.UP - return _assisted_target(origin, origin + direction * _aim_max_range()) - - -# The mouse path (#80) names a world point directly instead of dragging one -# into existence. It has a real cursor, so it is assisted from that rather -# than from a virtual one at the end of the beam - otherwise the crosshair -# would be the only aim in the game the helper does not touch. -func fire_at(world_target: Vector2) -> bool: - return super(_assisted_target(_aim_origin(), world_target)) - - -# #201: ordinary fire's aim helper, applied to the beam. It answers with a -# PLACE, not a heading - the same thing the fire stick resolves and every -# hull then converges on - so a drag that ends near an enemy, or merely -# inside the assist cone, passes through that enemy instead of a few degrees -# past it. The beam still runs on to its full range from there. -func _assisted_target(origin: Vector2, point: Vector2) -> Vector2: - return WEAPON_COMPONENT.assisted_point(get_tree(), origin, point, - _aim_max_range()) + return origin + direction * _aim_max_range() func _consume_shot(component: Node) -> bool: @@ -83,12 +62,7 @@ func _perform_shot(ship: Node2D, component: Node) -> void: # The cooldown was already spent by _consume_shot() above, so this fires # the effect directly rather than going through fire_ability() and # spending it twice. - # Aimed from the HULL, not from the fleet centre. aim_target is a place - # now that the assist can move it onto an enemy (#201), and ordinary fire - # converges on such a place by letting each hull find its own heading to - # it - a shared heading measured at the centroid would walk this beam off - # the very target the assist just picked, by the ship's formation offset. - var direction: Vector2 = aim_target - ship.global_position + var direction: Vector2 = aim_target - _aim_origin() direction = direction.normalized() if direction.length() > 0.001 else Vector2.UP var effect: Node2D = BEAM_EFFECT.fire(get_tree().root, ship.global_position, direction, component.beam_range, int(component.damage)) diff --git a/core/GameState.gd b/core/GameState.gd index 2e63272..d95961b 100644 --- a/core/GameState.gd +++ b/core/GameState.gd @@ -190,17 +190,11 @@ var research_points: int = 0 # place to set it - assigning dev_mode anywhere, tests included, reaches # world generation. var dev_unlimited_resources: bool = false -# #202: every research and production price answers zero while this is on. -# A separate switch from the one above rather than a stronger version of it - -# unlimited resources tests whether a price can be PAID, and this tests what -# happens when there is no price at all, which is a different play-test. -var dev_free_costs: bool = false var dev_mode: bool = false: set(value): dev_mode = value if not value: dev_unlimited_resources = false - dev_free_costs = false WORLD_GEN.dev_planets_enabled = value # Explored-chunk history for the world map's fog-of-war reveal. Lives @@ -682,7 +676,7 @@ func prerequisite_met(id: String) -> bool: # ever compared two nodes by price - keeps working on a plain number. func skill_costs(id: String) -> Dictionary: var node: Dictionary = skill_node(id) - if node.is_empty() or costs_are_free(): + if node.is_empty(): return {} var costs: Dictionary = node.get("costs", {}) if not costs.is_empty(): @@ -698,7 +692,7 @@ func can_unlock(id: String, payment_position: Variant = null) -> bool: func skill_research_point_cost(id: String) -> int: - return 0 if costs_are_free() else int(skill_node(id).get("research_points", 0)) + return int(skill_node(id).get("research_points", 0)) func unlock(id: String, payment_position: Variant = null) -> bool: @@ -798,7 +792,6 @@ func reset_new_game() -> void: boost_depleted_notice = false research_points = 0 dev_unlimited_resources = false - dev_free_costs = false dev_mode = false explored_chunks.clear() scanner_target.clear() @@ -961,21 +954,6 @@ func set_dev_unlimited_resources(enabled: bool) -> bool: return true -func set_dev_free_costs(enabled: bool) -> bool: - if enabled and not dev_mode: - return false - dev_free_costs = enabled - return true - - -# Asked by every price below, and by the menus that display them. The dev_mode -# term is not redundant: the setter refuses outside a dev game and the dev_mode -# setter clears the flag, so this is the third guard rather than the first, and -# it is the one that holds if a save or a test sets the field directly. -func costs_are_free() -> bool: - return dev_mode and dev_free_costs - - func _refill_dev_resources() -> void: for resource_type in resource_types(): if bank_inventory.amount(resource_type) < DEV_UNLIMITED_AMOUNT: @@ -1074,7 +1052,7 @@ func find_building(id: int) -> Dictionary: # there is one question with one answer whatever is being bought. func building_costs(type: String) -> Dictionary: var definition: Resource = building_definition(type) - if definition == null or costs_are_free(): + if definition == null: return {} if not definition.build_resource_costs.is_empty(): return definition.build_resource_costs.duplicate() @@ -1516,10 +1494,7 @@ func unlock_vessel(vessel: String, payment_position: Variant = null) -> bool: if definition == null or definition.unlock_id == "" or vessel_is_unlocked(vessel) or not has_ship_lab(): return false var costs: Dictionary = research_costs(definition.unlock_id) - # Through the accessor, not off the definition: this used to be the one - # place a research price was read straight from its file, which #202's - # free-costs switch would have left charging full price here alone. - var points_needed: int = research_point_cost(definition.unlock_id) + var points_needed: int = int(definition.research_point_cost) if research_points < points_needed or not spend_resources(costs, payment_position): return false if points_needed > 0: @@ -1565,7 +1540,7 @@ func upgrade_maxed(id: String) -> bool: func upgrade_cost(id: String) -> int: var d: Dictionary = upgrade_def(id) - if d.is_empty() or costs_are_free(): + if d.is_empty(): return 0 return int(d.cost) * (upgrade_level(id) + 1) @@ -1656,7 +1631,7 @@ func product_cost(product: String) -> int: func product_costs(product: String) -> Dictionary: var definition: Resource = vessel_definition(product) - if definition == null or costs_are_free(): + if definition == null: return {} if not definition.build_resource_costs.is_empty(): return definition.build_resource_costs.duplicate() @@ -2357,8 +2332,6 @@ func research_cost(id: String) -> int: func research_point_cost(id: String) -> int: - if costs_are_free(): - return 0 var unlock_definition: Resource = vessel_for_unlock(id) if unlock_definition != null: return int(unlock_definition.research_point_cost) @@ -2367,8 +2340,6 @@ func research_point_cost(id: String) -> int: func research_costs(id: String) -> Dictionary: - if costs_are_free(): - return {} var unlock_definition: Resource = vessel_for_unlock(id) if unlock_definition != null: if not unlock_definition.unlock_resource_costs.is_empty(): diff --git a/tests/test_dev_menu.gd b/tests/test_dev_menu.gd index 94a3dec..ad09482 100644 --- a/tests/test_dev_menu.gd +++ b/tests/test_dev_menu.gd @@ -5,7 +5,6 @@ extends SceneTree # the live fleet, not only GameState bookkeeping. const VESSEL_DEFINITIONS = preload("res://vessels/VesselDefinitions.gd") -const BUILDING_DEFINITIONS = preload("res://buildings/BuildingDefinitions.gd") var state: Node var scene: Node @@ -74,39 +73,6 @@ func _process(delta: float) -> bool: return _fail("unlimited mode did not replenish research points") print("skill unlock and unlimited resources operate on persistent GameState") - # --- #202: every research and production price reads zero ------- - # Unlimited resources goes off first: a bottomless bank and a free - # price are indistinguishable while both are on, and it is the - # price this checks. - menu.unlimited_button.pressed.emit() - if state.dev_unlimited_resources: - return _fail("the unlimited-resources toggle did not turn back off") - var charged: int = _priced_things() - if charged == 0: - return _fail("nothing in the game carries a price to make free") - menu.free_costs_button.pressed.emit() - if not state.dev_free_costs: - return _fail("the free-costs toggle did not turn on") - if _priced_things() != 0: - return _fail("%d prices survived the free-costs toggle" % _priced_things()) - # The accessors are what every purchase, queue and price label - # reads, so an empty bank must now afford anything. - for resource_type in state.resource_types(): - state.bank_inventory.set_amount(resource_type, 0) - state.fleet_inventory.set_amount(resource_type, 0) - state.research_points = 0 - for definition in BUILDING_DEFINITIONS.all(): - if not state.can_afford_resources(state.building_costs(String(definition.definition_id))): - return _fail("a broke fleet could not afford the free %s" % definition.definition_id) - print("free costs zeroed every research and production price") - - menu.free_costs_button.pressed.emit() - if state.dev_free_costs: - return _fail("the free-costs toggle did not turn back off") - if _priced_things() != charged: - return _fail("turning free costs off did not restore every price") - print("prices come back exactly as they were when the toggle goes off") - var wave_before: int = state.wave_number menu.wave_button.pressed.emit() if menu.visible: @@ -115,7 +81,7 @@ func _process(delta: float) -> bool: return _fail("the dev menu did not retain the immediate wave tool") state.reset_new_game() - if state.dev_mode or state.dev_unlimited_resources or state.dev_free_costs: + if state.dev_mode or state.dev_unlimited_resources: return _fail("dev cheats leaked into a normal new game") print("TEST_DEV_MENU: PASS") quit(0) @@ -123,30 +89,6 @@ func _process(delta: float) -> bool: return false -# How many prices in the whole game are currently non-zero: skills, vessel -# unlocks and upgrades on the research side, buildings and vessel production -# on the other. Counted rather than sampled so a price added later is covered -# here without anyone remembering to add it. -func _priced_things() -> int: - var total: int = 0 - for node in GameState.SKILL_TREE: - var id: String = String(node.id) - total += state.skill_costs(id).size() + state.skill_research_point_cost(id) - for definition in BUILDING_DEFINITIONS.all(): - total += state.building_costs(String(definition.definition_id)).size() - for definition in VESSEL_DEFINITIONS.all(): - total += state.product_costs(String(definition.definition_id)).size() - var unlock_id: String = String(definition.unlock_id) - if unlock_id != "": - total += state.research_costs(unlock_id).size() \ - + state.research_point_cost(unlock_id) - for upgrade in definition.upgrades: - var upgrade_id: String = String(upgrade.upgrade_id) - total += state.upgrade_cost(upgrade_id) \ - + state.research_point_cost(upgrade_id) - return total - - func _fail(message: String) -> bool: print("TEST_DEV_MENU: FAIL (%s)" % message) quit(1) diff --git a/tests/test_gas_bomber.gd b/tests/test_gas_bomber.gd index 6af6ee3..7412779 100644 --- a/tests/test_gas_bomber.gd +++ b/tests/test_gas_bomber.gd @@ -144,12 +144,6 @@ func _process(delta: float) -> bool: main._handle_touch(release) if not skill.projectile_active: return _fail_now("release did not throw a gas bomb") - # #201 doubled the blast. Read off the bomb actually in flight, so - # a constant that stops reaching the effect fails here rather than - # passing on the strength of its own declaration. - if int(skill.projectiles[0].damage) != 6: - return _fail_now("gas bomb carries %d damage, expected the doubled 6" \ - % int(skill.projectiles[0].damage)) elapsed = 0.0 stage = 1 1: diff --git a/tests/test_laser_vessel.gd b/tests/test_laser_vessel.gd index 5dd6239..3177010 100644 --- a/tests/test_laser_vessel.gd +++ b/tests/test_laser_vessel.gd @@ -99,12 +99,6 @@ func _process(delta: float) -> bool: return _fail_now("no laser ship is ready right after spawning") if absf(first_shooter.get_component("laser").beam_range - 2900.0) > 0.1: return _fail_now("laser ship beam range was not doubled to 2900") - # #201 tripled the beam's damage. Read off the COMPONENT rather - # than off the definition, so a stat that stops reaching the hull - # fails here instead of passing on the strength of its own file. - if first_shooter.get_component("laser").damage != 6: - return _fail_now("laser damage is %d, expected the tripled 6" \ - % first_shooter.get_component("laser").damage) var press := InputEventScreenTouch.new() press.index = 7 @@ -209,37 +203,6 @@ func _process(delta: float) -> bool: if absf(main.get_node("Camera2D").zoom.x - original_zoom) > 0.03: return _fail_now("camera did not restore its pre-aim zoom") print("camera zoom restored") - - # --- E. #201: the beam gets ordinary fire's aim helper ---------- - # Checked on the resolution itself rather than by firing again: - # both hulls are still cooling down, and what the ticket asks for - # is exactly that a drag resolves onto a nearby enemy. - for spent in on_line_enemies + [off_line_enemy, beyond_range_enemy]: - if is_instance_valid(spent): - spent.free() - var origin: Vector2 = skill._aim_origin() - var in_cone: Node = load("res://enemies/Enemy.tscn").instantiate() - in_cone.position = origin + Vector2(800.0, 0.0).rotated(deg_to_rad(20.0)) - main.add_child(in_cone) - in_cone.speed = 0.0 - # Nearer than the one in the cone, and deliberately outside it: - # the assist must not treat "closest" as "aimed at". - var out_of_cone: Node = load("res://enemies/Enemy.tscn").instantiate() - out_of_cone.position = origin + Vector2(600.0, 0.0).rotated(deg_to_rad(60.0)) - main.add_child(out_of_cone) - out_of_cone.speed = 0.0 - - var assisted: Vector2 = skill._calculate_aim_target(Vector2(300.0, 0.0), origin) - if assisted.distance_to(in_cone.global_position) > 1.0: - return _fail_now("a drag past an enemy inside the assist cone did not snap onto it") - print("aiming snapped onto the enemy inside the assist cone") - - in_cone.free() - var unassisted: Vector2 = skill._calculate_aim_target(Vector2(300.0, 0.0), origin) - if unassisted.distance_to(origin + Vector2(2900.0, 0.0)) > 1.0: - return _fail_now("the aim bent toward an enemy outside the assist cone") - print("an enemy outside the cone left the aim where it was pointed") - print("TEST_LASER_VESSEL: PASS") quit(0) return true diff --git a/ui/DevMenuButton.gd b/ui/DevMenuButton.gd index 22dc7f8..ae77001 100644 --- a/ui/DevMenuButton.gd +++ b/ui/DevMenuButton.gd @@ -14,7 +14,6 @@ func _ready() -> void: menu.fleet_remove_requested.connect(_on_fleet_remove_requested) menu.unlock_skills_requested.connect(_on_unlock_skills_requested) menu.unlimited_resources_requested.connect(_on_unlimited_resources_requested) - menu.free_costs_requested.connect(_on_free_costs_requested) menu.wave_requested.connect(_on_wave_requested) visible = false @@ -71,13 +70,6 @@ func _on_unlimited_resources_requested(enabled: bool) -> void: menu.set_status("Unlimited resources %s" % ("enabled" if enabled else "disabled")) -func _on_free_costs_requested(enabled: bool) -> void: - if not GameState.set_dev_free_costs(enabled): - return - menu.set_status("Research and production costs %s" \ - % ("are now free" if enabled else "are charged again")) - - func _on_wave_requested() -> void: GameState.force_wave_now() menu.close_menu() diff --git a/ui/menus/DevMenu.gd b/ui/menus/DevMenu.gd index 24c412b..e96b43a 100644 --- a/ui/menus/DevMenu.gd +++ b/ui/menus/DevMenu.gd @@ -4,7 +4,6 @@ signal fleet_add_requested(vessel_type: String) signal fleet_remove_requested(vessel_type: String) signal unlock_skills_requested signal unlimited_resources_requested(enabled: bool) -signal free_costs_requested(enabled: bool) signal wave_requested const ICON_BUTTON = preload("res://ui/IconButton.gd") @@ -17,10 +16,8 @@ const DESIGN_SIZE := Vector2(720.0, 1280.0) @onready var fleet_rows: VBoxContainer = $Content/FleetRows @onready var unlock_button: IconButton = $Content/ToolButtons/UnlockSkills @onready var unlimited_button: IconButton = $Content/ToolButtons/UnlimitedResources -@onready var free_costs_button: IconButton = $Content/ToolButtons/FreeCosts @onready var wave_button: IconButton = $Content/ToolButtons/StartWave @onready var unlimited_label: Label = $Content/ToolLabels/UnlimitedLabel -@onready var free_costs_label: Label = $Content/ToolLabels/FreeCostsLabel @onready var status: Label = $Content/StatusLabel var fleet: Node = null @@ -34,8 +31,6 @@ func _ready() -> void: unlock_button.pressed.connect(func(): unlock_skills_requested.emit()) unlimited_button.pressed.connect(func(): unlimited_resources_requested.emit(not GameState.dev_unlimited_resources)) - free_costs_button.pressed.connect(func(): - free_costs_requested.emit(not GameState.dev_free_costs)) wave_button.pressed.connect(func(): wave_requested.emit()) _layout_content() @@ -85,11 +80,8 @@ func refresh() -> void: (controls.remove as IconButton).enabled = count > 0 and total > 1 unlock_button.highlighted = _all_skills_unlocked() unlimited_button.highlighted = GameState.dev_unlimited_resources - unlimited_label.text = "UNLIMITED\nRESOURCES\n%s" \ + unlimited_label.text = "UNLIMITED RESOURCES\n%s" \ % ("ON" if GameState.dev_unlimited_resources else "OFF") - free_costs_button.highlighted = GameState.dev_free_costs - free_costs_label.text = "FREE\nCOSTS\n%s" \ - % ("ON" if GameState.dev_free_costs else "OFF") func _all_skills_unlocked() -> bool: diff --git a/ui/menus/DevMenu.tscn b/ui/menus/DevMenu.tscn index 860b1d9..e1e72c5 100644 --- a/ui/menus/DevMenu.tscn +++ b/ui/menus/DevMenu.tscn @@ -105,11 +105,11 @@ horizontal_alignment = 1 [node name="ToolButtons" type="HBoxContainer" parent="Content"] anchor_right = 1.0 -offset_left = 24.0 +offset_left = 70.0 offset_top = 896.0 -offset_right = -24.0 +offset_right = -70.0 offset_bottom = 1012.0 -theme_override_constants/separation = 52 +theme_override_constants/separation = 56 alignment = 1 [node name="UnlockSkills" type="Control" parent="Content/ToolButtons"] @@ -126,13 +126,6 @@ script = ExtResource("icon_button") icon = 29 glow_color = Color(1, 0.78, 0.25, 1) -[node name="FreeCosts" type="Control" parent="Content/ToolButtons"] -custom_minimum_size = Vector2(116, 116) -layout_mode = 2 -script = ExtResource("icon_button") -icon = 28 -glow_color = Color(0.55, 0.75, 1, 1) - [node name="StartWave" type="Control" parent="Content/ToolButtons"] custom_minimum_size = Vector2(116, 116) layout_mode = 2 @@ -142,15 +135,15 @@ glow_color = Color(1, 0.45, 0.2, 1) [node name="ToolLabels" type="HBoxContainer" parent="Content"] anchor_right = 1.0 -offset_left = 24.0 +offset_left = 28.0 offset_top = 1018.0 -offset_right = -24.0 -offset_bottom = 1114.0 +offset_right = -28.0 +offset_bottom = 1098.0 theme_override_constants/separation = 8 alignment = 1 [node name="UnlockLabel" type="Label" parent="Content/ToolLabels"] -custom_minimum_size = Vector2(160, 92) +custom_minimum_size = Vector2(210, 76) layout_mode = 2 theme_override_font_sizes/font_size = 20 text = "UNLOCK ALL\nSKILLS" @@ -158,23 +151,15 @@ horizontal_alignment = 1 vertical_alignment = 1 [node name="UnlimitedLabel" type="Label" parent="Content/ToolLabels"] -custom_minimum_size = Vector2(160, 92) +custom_minimum_size = Vector2(210, 76) layout_mode = 2 theme_override_font_sizes/font_size = 20 -text = "UNLIMITED\nRESOURCES\nOFF" -horizontal_alignment = 1 -vertical_alignment = 1 - -[node name="FreeCostsLabel" type="Label" parent="Content/ToolLabels"] -custom_minimum_size = Vector2(160, 92) -layout_mode = 2 -theme_override_font_sizes/font_size = 20 -text = "FREE\nCOSTS\nOFF" +text = "UNLIMITED RESOURCES\nOFF" horizontal_alignment = 1 vertical_alignment = 1 [node name="WaveLabel" type="Label" parent="Content/ToolLabels"] -custom_minimum_size = Vector2(160, 92) +custom_minimum_size = Vector2(210, 76) layout_mode = 2 theme_override_font_sizes/font_size = 20 text = "START ENEMY\nWAVE" diff --git a/vessels/LaserVesselDefinition.tres b/vessels/LaserVesselDefinition.tres index f76690d..820b6cb 100644 --- a/vessels/LaserVesselDefinition.tres +++ b/vessels/LaserVesselDefinition.tres @@ -18,7 +18,7 @@ hull_points = PackedVector2Array(0, -26, -9, 14, -4, 10, 4, 10, 9, 14) fleet_eligible = true capabilities = PackedStringArray("laser") component_scenes = Array[PackedScene]([ExtResource("2")]) -base_stats = {"max_speed": 660.0, "acceleration": 1890.0, "delivery_speed": 180.0, "damage": 6.0, "beam_range": 2900.0, "beam_cooldown": 4.0} +base_stats = {"max_speed": 660.0, "acceleration": 1890.0, "delivery_speed": 180.0, "damage": 2.0, "beam_range": 2900.0, "beam_cooldown": 4.0} minimum_stats = {"beam_cooldown": 1.5} upgrades = Array[Resource]([ExtResource("3"), ExtResource("4"), ExtResource("5")]) unlock_id = "unlock_laser" diff --git a/vessels/VesselGasBomberComponent.gd b/vessels/VesselGasBomberComponent.gd index 1d02f3c..d9a0ede 100644 --- a/vessels/VesselGasBomberComponent.gd +++ b/vessels/VesselGasBomberComponent.gd @@ -14,9 +14,7 @@ func _init() -> void: const BOMB_EFFECT = preload("res://vfx/GasBombEffect.gd") const GAS_PER_SHOT: int = 5 const MAX_RANGE: float = 950.0 -# #201: doubled. The bomb is the fleet's only area weapon and one slow, -# expensive throw was worth less than a couple of seconds of ordinary fire. -const BLAST_DAMAGE: int = 6 +const BLAST_DAMAGE: int = 3 const SHOT_COOLDOWN: float = 5.0 diff --git a/vessels/VesselLaserComponent.gd b/vessels/VesselLaserComponent.gd index a34541b..553357d 100644 --- a/vessels/VesselLaserComponent.gd +++ b/vessels/VesselLaserComponent.gd @@ -2,7 +2,7 @@ class_name VesselLaserComponent extends "res://vessels/VesselCooldownFillComponent.gd" var capability_id := "laser" -var damage: int = 6 +var damage: int = 2 var beam_range: float = 2900.0 const READY_COLOR := Color(0.3, 0.9, 1.0)