Zmiany w bosach i balans
This commit is contained in:
parent
f14fa23aac
commit
8e90447486
@ -14,7 +14,7 @@ stat_suffix = " DMG"
|
|||||||
weapon_kind = 2
|
weapon_kind = 2
|
||||||
fire_interval = 0.2
|
fire_interval = 0.2
|
||||||
weapon_range = 800.0
|
weapon_range = 800.0
|
||||||
energy_cost = 2.0
|
energy_cost = 2.5
|
||||||
damage_lower_roll_min = 1
|
damage_lower_roll_min = 1
|
||||||
damage_lower_roll_max = 3
|
damage_lower_roll_max = 3
|
||||||
damage_spread_roll_min = 1
|
damage_spread_roll_min = 1
|
||||||
|
|||||||
@ -18,16 +18,16 @@ anchor_right = 1.0
|
|||||||
anchor_bottom = 1.0
|
anchor_bottom = 1.0
|
||||||
grow_horizontal = 2
|
grow_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
color = Color(0.018, 0.035, 0.065, 1)
|
color = Color(0.018, 0.035, 0.065, 0.97)
|
||||||
|
|
||||||
[node name="Layout" type="VBoxContainer" parent="."]
|
[node name="Layout" type="VBoxContainer" parent="."]
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
anchors_preset = 15
|
anchors_preset = 15
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
anchor_bottom = 1.0
|
anchor_bottom = 1.0
|
||||||
offset_left = 48.0
|
offset_left = 28.0
|
||||||
offset_top = 28.0
|
offset_top = 28.0
|
||||||
offset_right = -48.0
|
offset_right = -28.0
|
||||||
offset_bottom = -28.0
|
offset_bottom = -28.0
|
||||||
grow_horizontal = 2
|
grow_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
@ -42,7 +42,7 @@ text = "Wybierz statek"
|
|||||||
[node name="Instructions" type="Label" parent="Layout"]
|
[node name="Instructions" type="Label" parent="Layout"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_colors/font_color = Color(0.55, 0.7, 0.82, 1)
|
theme_override_colors/font_color = Color(0.55, 0.7, 0.82, 1)
|
||||||
text = "Wybierz jednostkę, którą rozpoczniesz nową grę."
|
text = "Kliknij statek, aby od razu rozpocząć grę."
|
||||||
|
|
||||||
[node name="ShipScroll" type="ScrollContainer" parent="Layout"]
|
[node name="ShipScroll" type="ScrollContainer" parent="Layout"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
@ -55,22 +55,15 @@ size_flags_horizontal = 3
|
|||||||
size_flags_vertical = 0
|
size_flags_vertical = 0
|
||||||
theme_override_constants/h_separation = 14
|
theme_override_constants/h_separation = 14
|
||||||
theme_override_constants/v_separation = 14
|
theme_override_constants/v_separation = 14
|
||||||
columns = 4
|
columns = 2
|
||||||
|
|
||||||
[node name="Footer" type="HBoxContainer" parent="Layout"]
|
[node name="Footer" type="HBoxContainer" parent="Layout"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_constants/separation = 20
|
theme_override_constants/separation = 20
|
||||||
|
|
||||||
[node name="SelectedLabel" type="Label" parent="Layout/Footer"]
|
[node name="CloseButton" type="Button" parent="Layout/Footer"]
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 3
|
|
||||||
vertical_alignment = 1
|
|
||||||
text = "Wybierz statek"
|
|
||||||
|
|
||||||
[node name="ContinueButton" type="Button" parent="Layout/Footer"]
|
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
custom_minimum_size = Vector2(220, 52)
|
custom_minimum_size = Vector2(220, 52)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_font_sizes/font_size = 20
|
theme_override_font_sizes/font_size = 20
|
||||||
text = "Rozpocznij grę"
|
text = "WRÓĆ"
|
||||||
|
|||||||
@ -1,30 +1,50 @@
|
|||||||
extends Node2D
|
extends Node2D
|
||||||
|
|
||||||
var enemy: CharacterBody2D
|
var enemy: CharacterBody2D
|
||||||
|
var _tier_label: Label
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
enemy = get_parent() as CharacterBody2D
|
enemy = get_parent() as CharacterBody2D
|
||||||
top_level = true
|
top_level = true
|
||||||
z_index = 30
|
z_index = 30
|
||||||
light_mask = 4
|
light_mask = 4
|
||||||
|
_tier_label = Label.new()
|
||||||
|
_tier_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
_tier_label.light_mask = 4
|
||||||
|
_tier_label.position = Vector2(-50, -23)
|
||||||
|
_tier_label.size = Vector2(100, 16)
|
||||||
|
_tier_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
_tier_label.add_theme_font_size_override("font_size", 11)
|
||||||
|
_tier_label.add_theme_color_override("font_outline_color", Color("08111d"))
|
||||||
|
_tier_label.add_theme_constant_override("outline_size", 3)
|
||||||
|
add_child(_tier_label)
|
||||||
_update_position()
|
_update_position()
|
||||||
|
_update_tier_label()
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
func _process(_delta: float) -> void:
|
||||||
_update_position()
|
_update_position()
|
||||||
|
_update_tier_label()
|
||||||
queue_redraw()
|
queue_redraw()
|
||||||
|
|
||||||
func _update_position() -> void:
|
func _update_position() -> void:
|
||||||
global_position = enemy.global_position + Vector2(0, -enemy.get_visual_size() * 0.5 - 12.0)
|
global_position = enemy.global_position + Vector2(0, -enemy.get_visual_size() * 0.5 - 12.0)
|
||||||
|
|
||||||
|
func _tier_color() -> Color:
|
||||||
|
return Color("ff76d6") if enemy.loot_tier == 2 else (Color("ffc785") if enemy.loot_tier == 1 else Color("ef5967"))
|
||||||
|
|
||||||
|
func _update_tier_label() -> void:
|
||||||
|
_tier_label.text = "BOSS" if enemy.loot_tier == 2 else ("ELITA" if enemy.loot_tier == 1 else "")
|
||||||
|
_tier_label.add_theme_color_override("font_color", _tier_color())
|
||||||
|
|
||||||
func get_health_ratio() -> float:
|
func get_health_ratio() -> float:
|
||||||
return clampf(enemy.hull / maxf(enemy.max_hull, 0.001), 0.0, 1.0)
|
return clampf(enemy.hull / maxf(enemy.max_hull, 0.001), 0.0, 1.0)
|
||||||
|
|
||||||
func _draw() -> void:
|
func _draw() -> void:
|
||||||
if not is_instance_valid(enemy) or enemy.hull <= 0.0:
|
if not is_instance_valid(enemy) or enemy.hull <= 0.0:
|
||||||
return
|
return
|
||||||
var width := 72.0 if enemy.loot_tier == 2 else 44.0
|
var width := 72.0 if enemy.loot_tier == 2 else (56.0 if enemy.loot_tier == 1 else 44.0)
|
||||||
var background := Rect2(-width * 0.5, -3.0, width, 6.0)
|
var background := Rect2(-width * 0.5, -3.0, width, 6.0)
|
||||||
draw_rect(background.grow(1.0), Color("08111dee"))
|
draw_rect(background.grow(1.0), Color("08111dee"))
|
||||||
draw_rect(background, Color("39232bee"))
|
draw_rect(background, Color("39232bee"))
|
||||||
var color := Color("ffac46") if enemy.loot_tier == 2 else Color("ef5967")
|
var color := _tier_color()
|
||||||
draw_rect(Rect2(background.position, Vector2(width * get_health_ratio(), 6.0)), color)
|
draw_rect(Rect2(background.position, Vector2(width * get_health_ratio(), 6.0)), color)
|
||||||
|
|||||||
@ -34,13 +34,15 @@ func _ready() -> void:
|
|||||||
|
|
||||||
func apply_level_settings(level: int, tier: int) -> void:
|
func apply_level_settings(level: int, tier: int) -> void:
|
||||||
loot_tier = tier
|
loot_tier = tier
|
||||||
|
$Sprite2D.modulate = get_tier_tint()
|
||||||
var power := LEVEL_BALANCE.power_scale(level)
|
var power := LEVEL_BALANCE.power_scale(level)
|
||||||
|
var combat_multiplier := 2.0 if tier == 2 else 1.25
|
||||||
var boss_multiplier := 2.0 if tier == 2 else 1.0
|
var boss_multiplier := 2.0 if tier == 2 else 1.0
|
||||||
var hull_multiplier := 4.0 if tier == 2 else (2.0 if tier == 1 else 1.0)
|
var hull_multiplier := 4.0 if tier == 2 else (2.0 if tier == 1 else 1.0)
|
||||||
var damage_bonus := 4.0 if tier == 2 else (1.3 if tier == 1 else 1.0)
|
var damage_bonus := 4.0 if tier == 2 else (1.3 if tier == 1 else 1.0)
|
||||||
max_hull = 50.0 * power * hull_multiplier
|
max_hull = 50.0 * power * hull_multiplier * combat_multiplier
|
||||||
hull = max_hull
|
hull = max_hull
|
||||||
weapon_damage = 8.0 * power * damage_bonus
|
weapon_damage = 8.0 * power * damage_bonus * combat_multiplier
|
||||||
approach_speed = 62.0 * (1.0 + 0.0005 * (clampi(level, 1, 1000) - 1)) * boss_multiplier
|
approach_speed = 62.0 * (1.0 + 0.0005 * (clampi(level, 1, 1000) - 1)) * boss_multiplier
|
||||||
approach_acceleration = 85.0 * boss_multiplier
|
approach_acceleration = 85.0 * boss_multiplier
|
||||||
turn_speed = 1.8 * boss_multiplier
|
turn_speed = 1.8 * boss_multiplier
|
||||||
@ -56,9 +58,12 @@ func configure(player: CharacterBody2D, ship_texture: Texture2D) -> void:
|
|||||||
var largest_dimension := maxf(ship_texture.get_width(), ship_texture.get_height())
|
var largest_dimension := maxf(ship_texture.get_width(), ship_texture.get_height())
|
||||||
var visual_size := get_visual_size()
|
var visual_size := get_visual_size()
|
||||||
_sprite.scale = Vector2.ONE * (visual_size / largest_dimension)
|
_sprite.scale = Vector2.ONE * (visual_size / largest_dimension)
|
||||||
_sprite.modulate = Color("ffc785") if loot_tier == 1 else Color.WHITE
|
_sprite.modulate = get_tier_tint()
|
||||||
preload("res://scripts/game/ship_shadow.gd").attach_to(_sprite)
|
preload("res://scripts/game/ship_shadow.gd").attach_to(_sprite)
|
||||||
|
|
||||||
|
func get_tier_tint() -> Color:
|
||||||
|
return Color("ffb3df") if loot_tier == 2 else (Color("ffc785") if loot_tier == 1 else Color.WHITE)
|
||||||
|
|
||||||
func get_visual_size() -> float:
|
func get_visual_size() -> float:
|
||||||
return 112.0 if loot_tier == 2 else (68.0 if loot_tier == 1 else 56.0)
|
return 112.0 if loot_tier == 2 else (68.0 if loot_tier == 1 else 56.0)
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,11 @@ var _ability_bar: HBoxContainer
|
|||||||
var _ability_ready_style: StyleBoxFlat
|
var _ability_ready_style: StyleBoxFlat
|
||||||
var _ability_blocked_style: StyleBoxFlat
|
var _ability_blocked_style: StyleBoxFlat
|
||||||
var _inventory_clip: Control
|
var _inventory_clip: Control
|
||||||
|
var _sidebar: PanelContainer
|
||||||
|
var _sidebar_toggle: Button
|
||||||
|
var _sidebar_open := false
|
||||||
|
var _sidebar_tween: Tween
|
||||||
|
const SIDEBAR_WIDTH := 344.0
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
var energy_indicator := Node2D.new()
|
var energy_indicator := Node2D.new()
|
||||||
@ -25,12 +30,7 @@ func _ready() -> void:
|
|||||||
feedback.set("player", player)
|
feedback.set("player", player)
|
||||||
$HUDRoot.add_child(feedback)
|
$HUDRoot.add_child(feedback)
|
||||||
debug_info.add_theme_font_size_override("font_size", 9)
|
debug_info.add_theme_font_size_override("font_size", 9)
|
||||||
# Establish anchor layout before applying offsets; absolute layout resets anchors.
|
_create_sidebar()
|
||||||
var equipment := $HUDRoot/ShipEquipmentPanel as Control
|
|
||||||
equipment.set("layout_mode", 1)
|
|
||||||
equipment.set_anchors_and_offsets_preset(Control.PRESET_TOP_RIGHT)
|
|
||||||
equipment.offset_left = -330.0
|
|
||||||
equipment.offset_right = -10.0
|
|
||||||
_inventory_clip = Control.new()
|
_inventory_clip = Control.new()
|
||||||
_inventory_clip.name = "InventoryDrawer"
|
_inventory_clip.name = "InventoryDrawer"
|
||||||
_inventory_clip.clip_contents = true
|
_inventory_clip.clip_contents = true
|
||||||
@ -45,67 +45,101 @@ func _ready() -> void:
|
|||||||
debug_toggle.pressed.connect(_toggle_debug)
|
debug_toggle.pressed.connect(_toggle_debug)
|
||||||
inventory_button.pressed.connect(_toggle_inventory)
|
inventory_button.pressed.connect(_toggle_inventory)
|
||||||
_update_debug_toggle()
|
_update_debug_toggle()
|
||||||
$HUDRoot/CaptainPanel.resized.connect(func() -> void: _layout_sidebar.call_deferred())
|
|
||||||
_layout_sidebar.call_deferred()
|
_layout_sidebar.call_deferred()
|
||||||
|
|
||||||
|
func _create_sidebar() -> void:
|
||||||
|
_sidebar = PanelContainer.new()
|
||||||
|
_sidebar.name = "CaptainShipDrawer"
|
||||||
|
var style := StyleBoxFlat.new()
|
||||||
|
style.bg_color = Color(0.018, 0.028, 0.043, 0.97)
|
||||||
|
style.border_color = Color("52677e")
|
||||||
|
style.set_border_width_all(1)
|
||||||
|
style.set_content_margin_all(8)
|
||||||
|
_sidebar.add_theme_stylebox_override("panel", style)
|
||||||
|
$HUDRoot.add_child(_sidebar)
|
||||||
|
var scroll := ScrollContainer.new()
|
||||||
|
scroll.name = "Scroll"
|
||||||
|
scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
|
||||||
|
_sidebar.add_child(scroll)
|
||||||
|
var contents := VBoxContainer.new()
|
||||||
|
contents.name = "Contents"
|
||||||
|
contents.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
contents.add_theme_constant_override("separation", 8)
|
||||||
|
scroll.add_child(contents)
|
||||||
|
var captain := $HUDRoot/CaptainPanel as PanelContainer
|
||||||
|
var equipment := $HUDRoot/ShipEquipmentPanel as PanelContainer
|
||||||
|
for panel in [captain, equipment]:
|
||||||
|
panel.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT)
|
||||||
|
panel.reparent(contents, false)
|
||||||
|
panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
panel.add_theme_stylebox_override("panel", StyleBoxEmpty.new())
|
||||||
|
if panel == captain:
|
||||||
|
contents.add_child(HSeparator.new())
|
||||||
|
_sidebar_toggle = Button.new()
|
||||||
|
_sidebar_toggle.name = "CaptainShipToggle"
|
||||||
|
_sidebar_toggle.text = "KAPITAN / STATEK ◀"
|
||||||
|
_sidebar_toggle.add_theme_font_size_override("font_size", 12)
|
||||||
|
_sidebar_toggle.focus_mode = Control.FOCUS_NONE
|
||||||
|
$HUDRoot.add_child(_sidebar_toggle)
|
||||||
|
_sidebar_toggle.set_anchors_and_offsets_preset(Control.PRESET_TOP_RIGHT)
|
||||||
|
_sidebar_toggle.offset_left = -210
|
||||||
|
_sidebar_toggle.offset_right = -8
|
||||||
|
_sidebar_toggle.offset_top = 8
|
||||||
|
_sidebar_toggle.offset_bottom = 38
|
||||||
|
_sidebar_toggle.pressed.connect(_toggle_sidebar)
|
||||||
|
_sidebar.hide()
|
||||||
|
|
||||||
|
func _toggle_sidebar() -> void:
|
||||||
|
_sidebar_open = not _sidebar_open
|
||||||
|
if _sidebar_tween != null and _sidebar_tween.is_running():
|
||||||
|
_sidebar_tween.kill()
|
||||||
|
_sidebar_toggle.text = "ZAMKNIJ ▶" if _sidebar_open else "KAPITAN / STATEK ◀"
|
||||||
|
if _sidebar_open:
|
||||||
|
_sidebar.show()
|
||||||
|
else:
|
||||||
|
close_inventory()
|
||||||
|
_sidebar_tween = create_tween()
|
||||||
|
_sidebar_tween.tween_property(_sidebar, "position:x", $HUDRoot.size.x - SIDEBAR_WIDTH - 8 if _sidebar_open else $HUDRoot.size.x, 0.25).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT if _sidebar_open else Tween.EASE_IN)
|
||||||
|
if not _sidebar_open:
|
||||||
|
_sidebar_tween.tween_callback(_sidebar.hide)
|
||||||
|
|
||||||
func _layout_sidebar() -> void:
|
func _layout_sidebar() -> void:
|
||||||
var captain_panel := $HUDRoot/CaptainPanel as Control
|
if _sidebar_tween != null and _sidebar_tween.is_running():
|
||||||
var equipment_panel := $HUDRoot/ShipEquipmentPanel as Control
|
_sidebar_tween.kill()
|
||||||
captain_panel.grow_vertical = Control.GROW_DIRECTION_END
|
_sidebar.size = Vector2(SIDEBAR_WIDTH, maxf(100.0, $HUDRoot.size.y - 52.0))
|
||||||
equipment_panel.grow_vertical = Control.GROW_DIRECTION_END
|
_sidebar.position = Vector2($HUDRoot.size.x - SIDEBAR_WIDTH - 8 if _sidebar_open else $HUDRoot.size.x, 44)
|
||||||
equipment_panel.position.y = captain_panel.position.y + captain_panel.size.y + 6.0
|
_sidebar.visible = _sidebar_open
|
||||||
equipment_panel.size.y = equipment_panel.get_combined_minimum_size().y
|
|
||||||
_inventory_clip.size = inventory_panel.size
|
_inventory_clip.size = inventory_panel.size
|
||||||
_inventory_clip.position = Vector2(equipment_panel.position.x - _inventory_clip.size.x - 8, equipment_panel.position.y)
|
_layout_inventory()
|
||||||
|
|
||||||
|
func _layout_inventory() -> void:
|
||||||
|
if _inventory_clip == null:
|
||||||
|
return
|
||||||
|
var equipment := _sidebar.get_node("Scroll/Contents/ShipEquipmentPanel") as Control
|
||||||
|
_inventory_clip.position = Vector2(_sidebar.position.x - _inventory_clip.size.x - 8, minf(equipment.global_position.y, maxf(8.0, $HUDRoot.size.y - inventory_panel.size.y - 8)))
|
||||||
|
|
||||||
func _process(_delta: float) -> void:
|
func _process(_delta: float) -> void:
|
||||||
|
_layout_inventory()
|
||||||
_update_ability_bar()
|
_update_ability_bar()
|
||||||
if not _debug_expanded:
|
if not _debug_expanded:
|
||||||
return
|
return
|
||||||
|
|
||||||
var ship_position := player.global_position
|
|
||||||
var ship_velocity := player.velocity
|
|
||||||
var angular_speed := float(player.get("angular_velocity"))
|
|
||||||
var game := get_parent()
|
var game := get_parent()
|
||||||
var enemy_count: int = game.call("get_remaining_enemies")
|
debug_info.text = """POZIOM UKŁADU: %d
|
||||||
var goal: Node2D = game.get("protected_planet")
|
|
||||||
var to_planet := goal.global_position - player.global_position
|
|
||||||
debug_info.text = """STEROWANIE
|
|
||||||
W: ciąg S: hamulec / wsteczny
|
|
||||||
Tab: mini boost (+100%% prędkości, 20 energii/s)
|
|
||||||
Q / E: strafe A / D: obrót
|
|
||||||
LPM / PPM: broń 1 / broń 2 · ŚPM: rakieta
|
|
||||||
1–6: aktywne rozszerzenia
|
|
||||||
|
|
||||||
UKŁAD: %d · PLANETY: %d
|
|
||||||
WROGOWIE: %d
|
WROGOWIE: %d
|
||||||
CEL: %s
|
|
||||||
PLANETA RASY: %.0f px · %.0f°
|
|
||||||
|
|
||||||
STATEK
|
|
||||||
KADŁUB: %d / %d
|
|
||||||
TARCZE: %d / %d
|
|
||||||
ENERGIA: %d / %d
|
|
||||||
REGEN E / T: %.1f / %.1f /s
|
|
||||||
|
|
||||||
RUCH
|
|
||||||
PRĘDKOŚĆ: %.1f px/s
|
PRĘDKOŚĆ: %.1f px/s
|
||||||
WEKTOR: [%.1f, %.1f] px/s
|
|
||||||
POZYCJA: [%.1f, %.1f]
|
AKTUALNA REGENERACJA
|
||||||
KIERUNEK: %.1f°
|
Energia: %.1f pkt/s
|
||||||
OBRÓT: %.2f rad/s""" % [
|
Tarcze: %.1f pkt/s
|
||||||
GameState.system_level, int(game.get("system_data")["planets"].size()), enemy_count,
|
|
||||||
"Odwiedź chronioną rasę" if bool(game.get("system_cleared")) else "Oczyść układ",
|
%s""" % [
|
||||||
to_planet.length(), wrapf(rad_to_deg(to_planet.angle()) + 90.0, 0.0, 360.0),
|
GameState.system_level,
|
||||||
roundi(float(player.get("hull"))), roundi(float(player.get("max_hull"))),
|
int(game.call("get_remaining_enemies")),
|
||||||
roundi(float(player.get("shields"))), roundi(float(player.get("max_shields"))),
|
player.velocity.length(),
|
||||||
roundi(float(player.get("energy"))), roundi(float(player.get("max_energy"))),
|
|
||||||
float(player.get("energy_regeneration")) + float(player.call("get_solar_regeneration")),
|
float(player.get("energy_regeneration")) + float(player.call("get_solar_regeneration")),
|
||||||
float(player.get("shield_regeneration")),
|
float(player.get("shield_regeneration")),
|
||||||
ship_velocity.length(), ship_velocity.x, ship_velocity.y,
|
_get_bonus_summary(),
|
||||||
ship_position.x, ship_position.y, rad_to_deg(player.rotation), angular_speed,
|
|
||||||
]
|
]
|
||||||
debug_info.text += "\n\n" + _get_bonus_summary()
|
|
||||||
|
|
||||||
func _toggle_debug() -> void:
|
func _toggle_debug() -> void:
|
||||||
_debug_expanded = not _debug_expanded
|
_debug_expanded = not _debug_expanded
|
||||||
@ -116,47 +150,48 @@ func _update_debug_toggle() -> void:
|
|||||||
debug_toggle.text = "DEBUG [−]" if _debug_expanded else "DEBUG [+]"
|
debug_toggle.text = "DEBUG [−]" if _debug_expanded else "DEBUG [+]"
|
||||||
|
|
||||||
func _toggle_inventory() -> void:
|
func _toggle_inventory() -> void:
|
||||||
|
if not _sidebar_open:
|
||||||
|
_toggle_sidebar()
|
||||||
inventory_panel.call("toggle_drawer")
|
inventory_panel.call("toggle_drawer")
|
||||||
|
|
||||||
func close_inventory() -> void:
|
func close_inventory() -> void:
|
||||||
inventory_panel.call("close_drawer", true)
|
inventory_panel.call("close_drawer", true)
|
||||||
|
|
||||||
func _get_bonus_summary() -> String:
|
func _get_bonus_summary() -> String:
|
||||||
# Read final ship values so gear and captain multipliers match actual combat.
|
var cooldown_bonus := 0.0
|
||||||
return """BONUSY ŁĄCZNE (MODUŁY + KAPITAN)
|
var inventory := player.get_node("ShipInventory") as ShipInventory
|
||||||
Obrażenia broni: %+.1f%% (×%.3f)
|
for item in inventory.weapon_slots + inventory.module_slots:
|
||||||
Szansa na krytyk: %.1f%%
|
if item != null:
|
||||||
Obrażenia krytyczne: ×%.2f (%+.1f%% bonusu)
|
cooldown_bonus += item.get_stat_total("missile_cooldown_reduction")
|
||||||
Szansa na unik: %.1f%%
|
return """BONUSY (ADDONY + KAPITAN)
|
||||||
Redukcja obrażeń: %.1f%%
|
Obrażenia broni: %+.1f%%
|
||||||
|
Szansa na krytyk: %+.1f%%
|
||||||
Regeneracja energii: %.1f pkt/s (%+.1f)
|
Obrażenia krytyczne: %+.1f%%
|
||||||
Ładowanie solarne: +%.1f pkt/s
|
Szansa na unik: %+.1f%%
|
||||||
Regeneracja tarcz: %.1f pkt/s (%+.1f)
|
Redukcja obrażeń: %+.1f%%
|
||||||
Odzysk tarcz z obrażeń: %.1f%%
|
Odzysk tarcz z obrażeń: %.1f%%
|
||||||
Maks. kadłub: %.1f (%+.1f pkt)
|
Regeneracja energii: %+.1f pkt/s
|
||||||
Maks. tarcze: %.1f (%+.1f pkt)
|
Regeneracja tarcz: %+.1f pkt/s
|
||||||
Maks. energia: %.1f (%+.1f pkt)
|
Kadłub: %+.1f pkt
|
||||||
Maks. prędkość: %.1f px/s (%+.1f%%)
|
Tarcze: %+.1f pkt
|
||||||
Zdobywane XP: %+.1f%%
|
Energia: %+.1f pkt
|
||||||
Zdobywane kredyty: %+.1f%%""" % [
|
Prędkość maks.: %+.1f%%
|
||||||
|
Skrócenie cooldownu rakiet: %.2f s
|
||||||
|
Doświadczenie: %+.1f%%
|
||||||
|
Kredyty: %+.1f%%""" % [
|
||||||
(float(player.call("get_weapon_damage_multiplier")) - 1.0) * 100.0,
|
(float(player.call("get_weapon_damage_multiplier")) - 1.0) * 100.0,
|
||||||
float(player.call("get_weapon_damage_multiplier")),
|
|
||||||
float(player.call("get_critical_chance")) * 100.0,
|
float(player.call("get_critical_chance")) * 100.0,
|
||||||
float(player.call("get_critical_multiplier")),
|
|
||||||
float(player.get("critical_damage_bonus")) * 100.0,
|
float(player.get("critical_damage_bonus")) * 100.0,
|
||||||
float(player.call("get_evasion_chance")) * 100.0,
|
float(player.call("get_evasion_chance")) * 100.0,
|
||||||
float(player.call("get_damage_reduction")) * 100.0,
|
float(player.call("get_damage_reduction")) * 100.0,
|
||||||
float(player.get("energy_regeneration")),
|
|
||||||
float(player.get("energy_regeneration")) - float(player.get("base_energy_regeneration")),
|
|
||||||
float(player.call("get_solar_regeneration")),
|
|
||||||
float(player.get("shield_regeneration")),
|
|
||||||
float(player.get("shield_regeneration")) - float(player.get("base_shield_regeneration")),
|
|
||||||
float(player.get("shield_on_damage_percent")),
|
float(player.get("shield_on_damage_percent")),
|
||||||
float(player.get("max_hull")), float(player.get("max_hull")) - float(player.get("base_max_hull")),
|
float(player.get("energy_regeneration")) - float(player.get("base_energy_regeneration")),
|
||||||
float(player.get("max_shields")), float(player.get("max_shields")) - float(player.get("base_max_shields")),
|
float(player.get("shield_regeneration")) - float(player.get("base_shield_regeneration")),
|
||||||
float(player.get("max_energy")), float(player.get("max_energy")) - float(player.get("base_max_energy")),
|
float(player.get("max_hull")) - float(player.get("base_max_hull")),
|
||||||
float(player.get("max_speed")), (float(player.get("max_speed")) / float(player.get("base_max_speed")) - 1.0) * 100.0,
|
float(player.get("max_shields")) - float(player.get("base_max_shields")),
|
||||||
|
float(player.get("max_energy")) - float(player.get("base_max_energy")),
|
||||||
|
(float(player.get("max_speed")) / float(player.get("base_max_speed")) - 1.0) * 100.0,
|
||||||
|
cooldown_bonus,
|
||||||
(float(player.get("experience_multiplier")) - 1.0) * 100.0,
|
(float(player.get("experience_multiplier")) - 1.0) * 100.0,
|
||||||
(float(player.get("credits_multiplier")) - 1.0) * 100.0,
|
(float(player.get("credits_multiplier")) - 1.0) * 100.0,
|
||||||
]
|
]
|
||||||
|
|||||||
@ -2,8 +2,7 @@ extends RefCounted
|
|||||||
|
|
||||||
const MAX_LEVEL := 1000
|
const MAX_LEVEL := 1000
|
||||||
const COUNT_POINTS := [Vector2(1, 5), Vector2(10, 10), Vector2(50, 18), Vector2(100, 23), Vector2(250, 29), Vector2(500, 34), Vector2(1000, 40)]
|
const COUNT_POINTS := [Vector2(1, 5), Vector2(10, 10), Vector2(50, 18), Vector2(100, 23), Vector2(250, 29), Vector2(500, 34), Vector2(1000, 40)]
|
||||||
# x = level, y = elite fraction. Each level has exactly one boss.
|
const ELITES_PER_LEVEL := 2
|
||||||
const TIER_POINTS := [Vector2(10, 0), Vector2(50, 0.15), Vector2(100, 0.25), Vector2(500, 0.45), Vector2(1000, 0.55)]
|
|
||||||
|
|
||||||
static func enemy_count(level: int) -> int:
|
static func enemy_count(level: int) -> int:
|
||||||
var clamped_level := clampi(level, 1, MAX_LEVEL)
|
var clamped_level := clampi(level, 1, MAX_LEVEL)
|
||||||
@ -16,19 +15,9 @@ static func enemy_count(level: int) -> int:
|
|||||||
|
|
||||||
static func enemy_tiers(level: int) -> Array[int]:
|
static func enemy_tiers(level: int) -> Array[int]:
|
||||||
var count := enemy_count(level)
|
var count := enemy_count(level)
|
||||||
var elite_fraction := 0.0
|
# One boss and two elites on every level, included in the total count.
|
||||||
for index in range(1, TIER_POINTS.size()):
|
|
||||||
var before: Vector2 = TIER_POINTS[index - 1]
|
|
||||||
var after: Vector2 = TIER_POINTS[index]
|
|
||||||
if level <= after.x:
|
|
||||||
var progress := clampf(inverse_lerp(before.x, after.x, level), 0.0, 1.0)
|
|
||||||
elite_fraction = lerpf(before.y, after.y, progress)
|
|
||||||
break
|
|
||||||
if level >= MAX_LEVEL:
|
|
||||||
elite_fraction = 0.55
|
|
||||||
# One boss on every level, included in the total enemy count.
|
|
||||||
var bosses := 1
|
var bosses := 1
|
||||||
var elites := mini(count - bosses, roundi(count * elite_fraction))
|
var elites := ELITES_PER_LEVEL
|
||||||
var tiers: Array[int] = [2]
|
var tiers: Array[int] = [2]
|
||||||
for elite in elites:
|
for elite in elites:
|
||||||
tiers.append(1)
|
tiers.append(1)
|
||||||
|
|||||||
@ -1,16 +1,65 @@
|
|||||||
extends Control
|
extends Control
|
||||||
|
|
||||||
const SHIP_SELECTION_SCENE := "res://scenes/ui/ship_selection.tscn"
|
const SHIP_SELECTION_SCENE := preload("res://scenes/ui/ship_selection.tscn")
|
||||||
|
const GAME_SCENE := "res://scenes/game/game.tscn"
|
||||||
|
|
||||||
@onready var start_button: Button = %StartButton
|
@onready var start_button: Button = %StartButton
|
||||||
@onready var exit_button: Button = %ExitButton
|
@onready var exit_button: Button = %ExitButton
|
||||||
|
var _ship_panel: Control
|
||||||
|
var _panel_open := false
|
||||||
|
var _launching := false
|
||||||
|
var _drawer_tween: Tween
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
start_button.pressed.connect(_start_game)
|
start_button.pressed.connect(_start_game)
|
||||||
exit_button.pressed.connect(_exit_game)
|
exit_button.pressed.connect(_exit_game)
|
||||||
|
_ship_panel = SHIP_SELECTION_SCENE.instantiate() as Control
|
||||||
|
_ship_panel.name = "ShipSelectionPanel"
|
||||||
|
_ship_panel.hide()
|
||||||
|
add_child(_ship_panel)
|
||||||
|
_ship_panel.set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT)
|
||||||
|
_ship_panel.connect("ship_selected", _launch_game)
|
||||||
|
_ship_panel.connect("close_requested", _close_ship_panel)
|
||||||
|
resized.connect(_layout_ship_panel)
|
||||||
|
_layout_ship_panel()
|
||||||
|
|
||||||
func _start_game() -> void:
|
func _start_game() -> void:
|
||||||
get_tree().change_scene_to_file(SHIP_SELECTION_SCENE)
|
if _panel_open or _launching:
|
||||||
|
return
|
||||||
|
if _drawer_tween != null and _drawer_tween.is_running():
|
||||||
|
_drawer_tween.kill()
|
||||||
|
_panel_open = true
|
||||||
|
start_button.disabled = true
|
||||||
|
_ship_panel.position.x = size.x
|
||||||
|
_ship_panel.show()
|
||||||
|
_drawer_tween = create_tween()
|
||||||
|
_drawer_tween.tween_property(_ship_panel, "position:x", size.x - _ship_panel.size.x, 0.3).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_OUT)
|
||||||
|
|
||||||
|
func _layout_ship_panel() -> void:
|
||||||
|
if _ship_panel == null:
|
||||||
|
return
|
||||||
|
if _drawer_tween != null and _drawer_tween.is_running():
|
||||||
|
_drawer_tween.kill()
|
||||||
|
_ship_panel.size = Vector2(minf(700.0, size.x * 0.55), size.y)
|
||||||
|
_ship_panel.position = Vector2(size.x - _ship_panel.size.x if _panel_open else size.x, 0)
|
||||||
|
|
||||||
|
func _close_ship_panel() -> void:
|
||||||
|
if not _panel_open or _launching:
|
||||||
|
return
|
||||||
|
_panel_open = false
|
||||||
|
start_button.disabled = false
|
||||||
|
if _drawer_tween != null and _drawer_tween.is_running():
|
||||||
|
_drawer_tween.kill()
|
||||||
|
_drawer_tween = create_tween()
|
||||||
|
_drawer_tween.tween_property(_ship_panel, "position:x", size.x, 0.2).set_trans(Tween.TRANS_CUBIC).set_ease(Tween.EASE_IN)
|
||||||
|
_drawer_tween.tween_callback(_ship_panel.hide)
|
||||||
|
|
||||||
|
func _launch_game() -> void:
|
||||||
|
if _launching:
|
||||||
|
return
|
||||||
|
_launching = true
|
||||||
|
GameState.start_new_run()
|
||||||
|
get_tree().change_scene_to_file(GAME_SCENE)
|
||||||
|
|
||||||
func _exit_game() -> void:
|
func _exit_game() -> void:
|
||||||
get_tree().quit()
|
get_tree().quit()
|
||||||
|
|||||||
@ -67,7 +67,8 @@ func _position_at_planet() -> void:
|
|||||||
var planet_radius := float(planet.get("radius")) * canvas.x.length()
|
var planet_radius := float(planet.get("radius")) * canvas.x.length()
|
||||||
size = panel.get_combined_minimum_size()
|
size = panel.get_combined_minimum_size()
|
||||||
var viewport_size := get_viewport_rect().size
|
var viewport_size := get_viewport_rect().size
|
||||||
var screen_right := viewport_size.x - 320.0
|
var sidebar := get_parent().get_node_or_null("CaptainShipDrawer") as Control
|
||||||
|
var screen_right := viewport_size.x - (sidebar.size.x + 8.0 if sidebar != null and sidebar.visible else 8.0)
|
||||||
var target := planet_screen + Vector2(planet_radius + 24.0, -size.y * 0.5)
|
var target := planet_screen + Vector2(planet_radius + 24.0, -size.y * 0.5)
|
||||||
if target.x + size.x > screen_right:
|
if target.x + size.x > screen_right:
|
||||||
target.x = planet_screen.x - planet_radius - 24.0 - size.x
|
target.x = planet_screen.x - planet_radius - 24.0 - size.x
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
extends Control
|
extends Control
|
||||||
|
|
||||||
const GAME_SCENE := "res://scenes/game/game.tscn"
|
signal ship_selected
|
||||||
|
signal close_requested
|
||||||
const CATALOG := preload("res://scripts/game/ship_catalog.gd")
|
const CATALOG := preload("res://scripts/game/ship_catalog.gd")
|
||||||
|
|
||||||
@onready var ship_grid: GridContainer = %ShipGrid
|
@onready var ship_grid: GridContainer = %ShipGrid
|
||||||
@onready var selected_label: Label = %SelectedLabel
|
@onready var close_button: Button = %CloseButton
|
||||||
@onready var continue_button: Button = %ContinueButton
|
|
||||||
|
|
||||||
var button_group := ButtonGroup.new()
|
var button_group := ButtonGroup.new()
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
button_group.allow_unpress = false
|
button_group.allow_unpress = false
|
||||||
continue_button.pressed.connect(_start_game)
|
close_button.pressed.connect(func() -> void: close_requested.emit())
|
||||||
_load_ships()
|
_load_ships()
|
||||||
|
|
||||||
func _load_ships() -> void:
|
func _load_ships() -> void:
|
||||||
@ -23,25 +23,35 @@ func _load_ships() -> void:
|
|||||||
continue
|
continue
|
||||||
var button := Button.new()
|
var button := Button.new()
|
||||||
button.name = "Ship%d" % int(chassis["number"])
|
button.name = "Ship%d" % int(chassis["number"])
|
||||||
button.custom_minimum_size = Vector2(250, 260)
|
button.custom_minimum_size = Vector2(250, 240)
|
||||||
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
button.toggle_mode = true
|
button.toggle_mode = true
|
||||||
button.button_group = button_group
|
button.button_group = button_group
|
||||||
|
var contents := VBoxContainer.new()
|
||||||
|
contents.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
button.add_child(contents)
|
||||||
|
contents.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||||
|
contents.offset_left = 12
|
||||||
|
contents.offset_right = -12
|
||||||
|
contents.offset_top = 12
|
||||||
|
contents.offset_bottom = -12
|
||||||
|
contents.add_theme_constant_override("separation", 12)
|
||||||
var preview := TextureRect.new()
|
var preview := TextureRect.new()
|
||||||
|
preview.custom_minimum_size.y = 100
|
||||||
preview.texture = texture
|
preview.texture = texture
|
||||||
preview.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
preview.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||||
preview.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
preview.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||||
preview.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
preview.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
button.add_child(preview)
|
contents.add_child(preview)
|
||||||
preview.set_anchors_and_offsets_preset(Control.PRESET_CENTER_TOP)
|
var description := Label.new()
|
||||||
preview.offset_left = -50.0
|
description.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
preview.offset_right = 50.0
|
description.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
preview.offset_top = 12.0
|
description.text = "%s\n%s\n\n%s" % [chassis["name"], chassis["role"], CATALOG.stats_text(chassis)]
|
||||||
preview.offset_bottom = 102.0
|
description.add_theme_font_size_override("font_size", 13)
|
||||||
button.text = "%s\n%s\n\n%s" % [chassis["name"], chassis["role"], CATALOG.stats_text(chassis)]
|
contents.add_child(description)
|
||||||
button.add_theme_font_size_override("font_size", 13)
|
button.add_theme_font_size_override("font_size", 13)
|
||||||
button.tooltip_text = "%s — %s\nStatystyki bazowe przed bonusami modułów.\n%s" % [chassis["name"], chassis["role"], CATALOG.stats_text(chassis)]
|
button.tooltip_text = "%s — %s\nStatystyki bazowe przed bonusami modułów.\n%s" % [chassis["name"], chassis["role"], CATALOG.stats_text(chassis)]
|
||||||
button.pressed.connect(_select_ship.bind(chassis, button))
|
button.pressed.connect(_choose_ship.bind(chassis, button))
|
||||||
ship_grid.add_child(button)
|
ship_grid.add_child(button)
|
||||||
if chassis["number"] == selected_ship["number"]:
|
if chassis["number"] == selected_ship["number"]:
|
||||||
_select_ship(chassis, button)
|
_select_ship(chassis, button)
|
||||||
@ -49,9 +59,12 @@ func _load_ships() -> void:
|
|||||||
func _select_ship(chassis: Dictionary, button: Button) -> void:
|
func _select_ship(chassis: Dictionary, button: Button) -> void:
|
||||||
GameState.selected_ship_path = CATALOG.texture_path(chassis)
|
GameState.selected_ship_path = CATALOG.texture_path(chassis)
|
||||||
button.button_pressed = true
|
button.button_pressed = true
|
||||||
selected_label.text = "Wybrano: %s · %s" % [chassis["name"], chassis["role"]]
|
|
||||||
continue_button.disabled = false
|
|
||||||
|
|
||||||
func _start_game() -> void:
|
func _choose_ship(chassis: Dictionary, button: Button) -> void:
|
||||||
GameState.start_new_run()
|
_select_ship(chassis, button)
|
||||||
get_tree().change_scene_to_file(GAME_SCENE)
|
ship_selected.emit()
|
||||||
|
|
||||||
|
func _unhandled_input(event: InputEvent) -> void:
|
||||||
|
if visible and event is InputEventKey and event.pressed and event.keycode == KEY_ESCAPE:
|
||||||
|
close_requested.emit()
|
||||||
|
get_viewport().set_input_as_handled()
|
||||||
|
|||||||
@ -54,7 +54,7 @@ func _ready() -> void:
|
|||||||
assert(profile.credits == 0)
|
assert(profile.credits == 0)
|
||||||
await _collect_credits(game.get_node("PlayerShip") as CharacterBody2D)
|
await _collect_credits(game.get_node("PlayerShip") as CharacterBody2D)
|
||||||
assert(profile.credits == 180)
|
assert(profile.credits == 180)
|
||||||
var panel := game.get_node("HUD/HUDRoot/CaptainPanel")
|
var panel := game.get_node("HUD/HUDRoot/CaptainShipDrawer/Scroll/Contents/CaptainPanel")
|
||||||
panel.get_node("Contents/Skills/gunner/Train_gunner").emit_signal("pressed")
|
panel.get_node("Contents/Skills/gunner/Train_gunner").emit_signal("pressed")
|
||||||
assert(profile.get_skill_rank("gunner") == 1 and profile.learning_points == 0)
|
assert(profile.get_skill_rank("gunner") == 1 and profile.learning_points == 0)
|
||||||
panel.get_node("Contents/Skills/piloting/Train_piloting").emit_signal("pressed")
|
panel.get_node("Contents/Skills/piloting/Train_piloting").emit_signal("pressed")
|
||||||
|
|||||||
52
tests/items/laser_energy_regeneration_test.gd
Normal file
52
tests/items/laser_energy_regeneration_test.gd
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
extends Node2D
|
||||||
|
|
||||||
|
const GENERATOR := preload("res://scripts/items/item_generator.gd")
|
||||||
|
const REGEN := preload("res://resources/items/affixes/energy_regeneration.tres")
|
||||||
|
|
||||||
|
class MeasuredLaser:
|
||||||
|
extends "res://scripts/game/weapon_controller.gd"
|
||||||
|
var shots := 0
|
||||||
|
func _consume_energy(amount: float) -> bool:
|
||||||
|
var consumed := super._consume_energy(amount)
|
||||||
|
if consumed:
|
||||||
|
shots += 1
|
||||||
|
return consumed
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
seed(54321)
|
||||||
|
GameState.start_new_run()
|
||||||
|
var ship := preload("res://scenes/game/player_ship.tscn").instantiate() as CharacterBody2D
|
||||||
|
add_child(ship)
|
||||||
|
ship.get_node("WeaponController").set_physics_process(false)
|
||||||
|
var inventory := ship.get_node("ShipInventory") as ShipInventory
|
||||||
|
var laser := GENERATOR.generate_for_definition(preload("res://resources/items/beam_laser.tres"), 5)
|
||||||
|
assert(inventory.add_item(laser))
|
||||||
|
assert(inventory.move_item("cargo", inventory.cargo.find(laser), "weapon", 0))
|
||||||
|
# Five separate level-5 regeneration affixes, on five ordinary hull modules.
|
||||||
|
for index in 5:
|
||||||
|
var module := ItemInstance.new()
|
||||||
|
module.definition = preload("res://resources/items/hull_plating.tres")
|
||||||
|
module.item_level = 5
|
||||||
|
var addon := ItemStatModifier.new()
|
||||||
|
addon.affix = REGEN
|
||||||
|
addon.stat_key = REGEN.stat_key
|
||||||
|
addon.value = REGEN.roll_value(5)
|
||||||
|
module.additional_modifiers.append(addon)
|
||||||
|
assert(inventory.add_item(module))
|
||||||
|
assert(inventory.move_item("cargo", inventory.cargo.find(module), "module", index))
|
||||||
|
print("REGEN_BONUS_%d: %.4f energy/s" % [index + 1, module.get_stat_total("energy_regeneration")])
|
||||||
|
var controller := MeasuredLaser.new()
|
||||||
|
ship.add_child(controller)
|
||||||
|
await get_tree().physics_frame
|
||||||
|
ship.set("energy", 80.0)
|
||||||
|
assert(ship.call("get_solar_regeneration") == 0.0)
|
||||||
|
print("LASER_SETUP: level=5 DMG=%s addons=%d cost=%.2f regen=%.4f" % [laser.get_damage_range_text(), laser.get_addon_count(), laser.get_energy_cost(), ship.get("energy_regeneration")])
|
||||||
|
Input.action_press("ship_fire_primary")
|
||||||
|
for frame in 600:
|
||||||
|
await get_tree().physics_frame
|
||||||
|
Input.action_release("ship_fire_primary")
|
||||||
|
var final_energy := float(ship.get("energy"))
|
||||||
|
assert(controller.shots > 0)
|
||||||
|
assert(final_energy < 80.0)
|
||||||
|
print("LASER_ENERGY_TEST: PASS duration=10s shots=%d initial=80 final=%.4f loss=%.4f" % [controller.shots, final_energy, 80.0 - final_energy])
|
||||||
|
get_tree().quit()
|
||||||
1
tests/items/laser_energy_regeneration_test.gd.uid
Normal file
1
tests/items/laser_energy_regeneration_test.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://c4qoktbpmppg6
|
||||||
6
tests/items/laser_energy_regeneration_test.tscn
Normal file
6
tests/items/laser_energy_regeneration_test.tscn
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://tests/items/laser_energy_regeneration_test.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="LaserEnergyRegenerationTest" type="Node2D"]
|
||||||
|
script = ExtResource("1")
|
||||||
1
tests/items/machine_gun_shield_recovery_test.gd.uid
Normal file
1
tests/items/machine_gun_shield_recovery_test.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://b7motgp3dyqv5
|
||||||
34
tests/levels/enemy_tiers_test.gd
Normal file
34
tests/levels/enemy_tiers_test.gd
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
const BALANCE := preload("res://scripts/levels/level_balance.gd")
|
||||||
|
const GENERATOR := preload("res://scripts/levels/system_generator.gd")
|
||||||
|
const ENEMY := preload("res://scenes/game/enemy_ship.tscn")
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
for level in range(1, 1001):
|
||||||
|
var tiers := BALANCE.enemy_tiers(level)
|
||||||
|
assert(tiers.count(1) == 2 and tiers.count(2) == 1)
|
||||||
|
assert(tiers.size() == BALANCE.enemy_count(level))
|
||||||
|
assert(tiers.count(0) >= 2)
|
||||||
|
for level in [1, 10, 100, 1000]:
|
||||||
|
var data: Dictionary = GENERATOR.generate(level, 314159)
|
||||||
|
var elites := 0
|
||||||
|
var bosses := 0
|
||||||
|
for entry in data["enemies"]:
|
||||||
|
elites += 1 if entry["tier"] == 1 else 0
|
||||||
|
bosses += 1 if entry["tier"] == 2 else 0
|
||||||
|
assert(elites == 2 and bosses == 1)
|
||||||
|
for tier in 3:
|
||||||
|
var enemy := ENEMY.instantiate() as CharacterBody2D
|
||||||
|
add_child(enemy)
|
||||||
|
enemy.set_physics_process(false)
|
||||||
|
enemy.call("apply_level_settings", 1, tier)
|
||||||
|
var bar := enemy.get_node("HealthBar")
|
||||||
|
bar.call("_process", 0.0)
|
||||||
|
var label := bar.get_child(0) as Label
|
||||||
|
assert(label.text == ["", "ELITA", "BOSS"][tier])
|
||||||
|
assert(label.light_mask == 4)
|
||||||
|
assert(enemy.get_node("Sprite2D").modulate == enemy.call("get_tier_tint"))
|
||||||
|
enemy.queue_free()
|
||||||
|
print("ENEMY_TIERS_TEST: PASS (1000 levels, generated encounters, exactly two elites/one boss, tint and labels)")
|
||||||
|
get_tree().quit()
|
||||||
1
tests/levels/enemy_tiers_test.gd.uid
Normal file
1
tests/levels/enemy_tiers_test.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://df4ca8t4gt0f
|
||||||
4
tests/levels/enemy_tiers_test.tscn
Normal file
4
tests/levels/enemy_tiers_test.tscn
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
[ext_resource type="Script" path="res://tests/levels/enemy_tiers_test.gd" id="1"]
|
||||||
|
[node name="EnemyTiersTest" type="Node"]
|
||||||
|
script = ExtResource("1")
|
||||||
@ -127,7 +127,7 @@ func _ready() -> void:
|
|||||||
_assert_full_resources(player)
|
_assert_full_resources(player)
|
||||||
var enemy := get_tree().get_nodes_in_group("enemies")[0] as CharacterBody2D
|
var enemy := get_tree().get_nodes_in_group("enemies")[0] as CharacterBody2D
|
||||||
enemy.call("apply_level_settings", 2, 2)
|
enemy.call("apply_level_settings", 2, 2)
|
||||||
assert(is_equal_approx(float(enemy.get("max_hull")), 210.0))
|
assert(is_equal_approx(float(enemy.get("max_hull")), 420.0))
|
||||||
enemy.call("take_damage", 100000.0)
|
enemy.call("take_damage", 100000.0)
|
||||||
await get_tree().process_frame
|
await get_tree().process_frame
|
||||||
await get_tree().process_frame
|
await get_tree().process_frame
|
||||||
@ -193,7 +193,7 @@ func _assert_boss_and_health(game: Node2D) -> void:
|
|||||||
normal = enemy
|
normal = enemy
|
||||||
assert(boss != null and normal != null)
|
assert(boss != null and normal != null)
|
||||||
for key in ["max_hull", "weapon_damage"]:
|
for key in ["max_hull", "weapon_damage"]:
|
||||||
assert(is_equal_approx(float(boss.get(key)), float(normal.get(key)) * 4.0))
|
assert(is_equal_approx(float(boss.get(key)), float(normal.get(key)) * 6.4))
|
||||||
for key in ["approach_speed", "approach_acceleration", "turn_speed", "activation_range", "weapon_range"]:
|
for key in ["approach_speed", "approach_acceleration", "turn_speed", "activation_range", "weapon_range"]:
|
||||||
assert(is_equal_approx(float(boss.get(key)), float(normal.get(key)) * 2.0))
|
assert(is_equal_approx(float(boss.get(key)), float(normal.get(key)) * 2.0))
|
||||||
assert(is_equal_approx(boss.weapon_cooldown, normal.weapon_cooldown * 0.5))
|
assert(is_equal_approx(boss.weapon_cooldown, normal.weapon_cooldown * 0.5))
|
||||||
|
|||||||
60
tests/ships/captain_ship_drawer_test.gd
Normal file
60
tests/ships/captain_ship_drawer_test.gd
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
const GAME := preload("res://scenes/game/game.tscn")
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
get_tree().create_timer(10.0).timeout.connect(func() -> void:
|
||||||
|
push_error("CAPTAIN_SHIP_DRAWER_TEST: timed out")
|
||||||
|
get_tree().quit(1)
|
||||||
|
)
|
||||||
|
GameState.start_new_run()
|
||||||
|
var game := GAME.instantiate() as Node2D
|
||||||
|
add_child(game)
|
||||||
|
await get_tree().process_frame
|
||||||
|
for enemy in get_tree().get_nodes_in_group("enemies"):
|
||||||
|
enemy.set_physics_process(false)
|
||||||
|
var ship := game.get_node("PlayerShip") as CharacterBody2D
|
||||||
|
ship.set_physics_process(false)
|
||||||
|
ship.get_node("WeaponController").set_physics_process(false)
|
||||||
|
var hud := game.get_node("HUD")
|
||||||
|
var root := hud.get_node("HUDRoot") as Control
|
||||||
|
var drawer := root.get_node("CaptainShipDrawer") as Control
|
||||||
|
var toggle := root.get_node("CaptainShipToggle") as Button
|
||||||
|
var captain := drawer.get_node("Scroll/Contents/CaptainPanel")
|
||||||
|
var equipment := drawer.get_node("Scroll/Contents/ShipEquipmentPanel")
|
||||||
|
assert(captain.get_parent() == equipment.get_parent())
|
||||||
|
assert(not drawer.visible)
|
||||||
|
toggle.pressed.emit()
|
||||||
|
await get_tree().create_timer(0.3).timeout
|
||||||
|
assert(drawer.visible and is_equal_approx(drawer.position.x + drawer.size.x, root.size.x - 8.0))
|
||||||
|
GameState.captain.learning_points = 1
|
||||||
|
GameState.captain.emit_changed()
|
||||||
|
captain.get_node("Contents/Skills/gunner/Train_gunner").pressed.emit()
|
||||||
|
assert(GameState.captain.get_skill_rank("gunner") == 1)
|
||||||
|
equipment.get_node("Contents/InventoryButton").pressed.emit()
|
||||||
|
await get_tree().create_timer(0.3).timeout
|
||||||
|
var inventory_panel: Control = hud.get("inventory_panel")
|
||||||
|
assert(inventory_panel.visible and inventory_panel.get("_drawer_open"))
|
||||||
|
var inventory := ship.get_node("ShipInventory") as ShipInventory
|
||||||
|
var item := ItemInstance.new()
|
||||||
|
item.definition = preload("res://resources/items/engine_extension.tres")
|
||||||
|
assert(inventory.add_item(item))
|
||||||
|
assert(inventory.move_item("cargo", inventory.cargo.find(item), "module", 0))
|
||||||
|
assert(ship.get("max_speed") > ship.get("base_max_speed"))
|
||||||
|
toggle.pressed.emit()
|
||||||
|
await get_tree().create_timer(0.3).timeout
|
||||||
|
assert(not drawer.visible and not inventory_panel.visible)
|
||||||
|
toggle.pressed.emit()
|
||||||
|
await get_tree().create_timer(0.3).timeout
|
||||||
|
assert(drawer.visible and not inventory_panel.visible)
|
||||||
|
var original_size := root.size
|
||||||
|
root.size = Vector2(root.size.x, 480)
|
||||||
|
hud.call("_layout_sidebar")
|
||||||
|
await get_tree().process_frame
|
||||||
|
await get_tree().process_frame
|
||||||
|
var scroll := drawer.get_node("Scroll") as ScrollContainer
|
||||||
|
assert(scroll.get_v_scroll_bar().visible)
|
||||||
|
root.size = original_size
|
||||||
|
hud.call("_layout_sidebar")
|
||||||
|
print("CAPTAIN_SHIP_DRAWER_TEST: PASS (merged panel, slide, close/reopen, skills, inventory, equipment stats)")
|
||||||
|
get_tree().quit()
|
||||||
1
tests/ships/captain_ship_drawer_test.gd.uid
Normal file
1
tests/ships/captain_ship_drawer_test.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://deox4ywvpn5mi
|
||||||
4
tests/ships/captain_ship_drawer_test.tscn
Normal file
4
tests/ships/captain_ship_drawer_test.tscn
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
[ext_resource type="Script" path="res://tests/ships/captain_ship_drawer_test.gd" id="1"]
|
||||||
|
[node name="CaptainShipDrawerTest" type="Node"]
|
||||||
|
script = ExtResource("1")
|
||||||
43
tests/ships/ship_selection_drawer_test.gd
Normal file
43
tests/ships/ship_selection_drawer_test.gd
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
const MENU := preload("res://scenes/ui/main_menu.tscn")
|
||||||
|
const CATALOG := preload("res://scripts/game/ship_catalog.gd")
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
get_tree().create_timer(10.0).timeout.connect(func() -> void:
|
||||||
|
push_error("SHIP_SELECTION_DRAWER_TEST: timed out")
|
||||||
|
get_tree().quit(1)
|
||||||
|
)
|
||||||
|
var menu := MENU.instantiate() as Control
|
||||||
|
get_tree().root.add_child.call_deferred(menu)
|
||||||
|
await get_tree().process_frame
|
||||||
|
await get_tree().process_frame
|
||||||
|
get_tree().current_scene = menu
|
||||||
|
var panel := menu.get_node("ShipSelectionPanel") as Control
|
||||||
|
assert(not panel.visible)
|
||||||
|
var start := menu.get_node("MenuLayout/StartButton") as Button
|
||||||
|
start.pressed.emit()
|
||||||
|
assert(get_tree().current_scene == menu and panel.visible)
|
||||||
|
assert(is_equal_approx(panel.position.x, menu.size.x))
|
||||||
|
await get_tree().create_timer(0.35).timeout
|
||||||
|
assert(is_equal_approx(panel.position.x + panel.size.x, menu.size.x))
|
||||||
|
var grid := panel.get_node("Layout/ShipScroll/ShipGrid") as GridContainer
|
||||||
|
assert(grid.columns == 2 and grid.get_child_count() == 4)
|
||||||
|
assert(not panel.has_node("Layout/Footer/ContinueButton"))
|
||||||
|
panel.get_node("Layout/Footer/CloseButton").pressed.emit()
|
||||||
|
await get_tree().create_timer(0.25).timeout
|
||||||
|
assert(not panel.visible and not start.disabled)
|
||||||
|
start.pressed.emit()
|
||||||
|
await get_tree().create_timer(0.35).timeout
|
||||||
|
GameState.captain.credits = 999
|
||||||
|
grid.get_node("Ship7").pressed.emit()
|
||||||
|
assert(GameState.selected_ship_path == CATALOG.texture_path(CATALOG.SHIPS[3]))
|
||||||
|
await get_tree().process_frame
|
||||||
|
await get_tree().process_frame
|
||||||
|
await get_tree().process_frame
|
||||||
|
var game := get_tree().current_scene
|
||||||
|
assert(game.scene_file_path == "res://scenes/game/game.tscn")
|
||||||
|
assert(GameState.captain.credits == 0 and GameState.system_level == 1)
|
||||||
|
assert(game.get_node("PlayerShip").get("max_energy") == 125.0)
|
||||||
|
print("SHIP_SELECTION_DRAWER_TEST: PASS (Start opens drawer in menu, animation, four ships, close/reopen, one-click launch, selected ship stats, fresh run)")
|
||||||
|
get_tree().quit()
|
||||||
1
tests/ships/ship_selection_drawer_test.gd.uid
Normal file
1
tests/ships/ship_selection_drawer_test.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://dsx5ej1byds5h
|
||||||
4
tests/ships/ship_selection_drawer_test.tscn
Normal file
4
tests/ships/ship_selection_drawer_test.tscn
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
[ext_resource type="Script" path="res://tests/ships/ship_selection_drawer_test.gd" id="1"]
|
||||||
|
[node name="ShipSelectionDrawerTest" type="Node"]
|
||||||
|
script = ExtResource("1")
|
||||||
@ -23,7 +23,10 @@ func _ready() -> void:
|
|||||||
assert(ship.get("max_speed") == chassis["speed"] and ship.get("energy_regeneration") == 5.0)
|
assert(ship.get("max_speed") == chassis["speed"] and ship.get("energy_regeneration") == 5.0)
|
||||||
assert(ship.get_node("Sprite2D").texture.resource_path == CATALOG.texture_path(chassis))
|
assert(ship.get_node("Sprite2D").texture.resource_path == CATALOG.texture_path(chassis))
|
||||||
var inventory: ShipInventory = ship.get_node("ShipInventory")
|
var inventory: ShipInventory = ship.get_node("ShipInventory")
|
||||||
assert(inventory.move_item("cargo", 2, "module", 0))
|
var shield := ItemInstance.new()
|
||||||
|
shield.definition = preload("res://resources/items/reinforced_shield.tres")
|
||||||
|
assert(inventory.add_item(shield))
|
||||||
|
assert(inventory.move_item("cargo", inventory.cargo.find(shield), "module", 0))
|
||||||
var equipped := inventory.get_item("module", 0)
|
var equipped := inventory.get_item("module", 0)
|
||||||
assert(ship.get("max_shields") == chassis["shields"] + equipped.get_stat_total("max_shields"))
|
assert(ship.get("max_shields") == chassis["shields"] + equipped.get_stat_total("max_shields"))
|
||||||
inventory.preserve_for_restart()
|
inventory.preserve_for_restart()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user