Zmiany
This commit is contained in:
parent
0c9f812cfe
commit
b5338a7564
@ -1,4 +1,4 @@
|
||||
[gd_resource type="Resource" script_class="ItemAffixDatabase" load_steps=14 format=3]
|
||||
[gd_resource type="Resource" script_class="ItemAffixDatabase" load_steps=15 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scripts/items/item_affix_database.gd" id="1_database"]
|
||||
[ext_resource type="Script" path="res://scripts/items/item_affix_definition.gd" id="2_type"]
|
||||
@ -15,6 +15,8 @@
|
||||
|
||||
[ext_resource type="Resource" path="res://resources/items/affixes/missile_cooldown.tres" id="13_affix"]
|
||||
|
||||
[ext_resource type="Resource" path="res://resources/items/affixes/shield_on_damage.tres" id="14_affix"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_database")
|
||||
affixes = Array[ExtResource("2_type")]([ExtResource("3_affix"), ExtResource("4_affix"), ExtResource("5_affix"), ExtResource("6_affix"), ExtResource("7_affix"), ExtResource("8_affix"), ExtResource("9_affix"), ExtResource("10_affix"), ExtResource("11_affix"), ExtResource("12_affix"), ExtResource("13_affix")])
|
||||
affixes = Array[ExtResource("2_type")]([ExtResource("3_affix"), ExtResource("4_affix"), ExtResource("5_affix"), ExtResource("6_affix"), ExtResource("7_affix"), ExtResource("8_affix"), ExtResource("9_affix"), ExtResource("10_affix"), ExtResource("11_affix"), ExtResource("12_affix"), ExtResource("13_affix"), ExtResource("14_affix")])
|
||||
|
||||
14
resources/items/affixes/shield_on_damage.tres
Normal file
14
resources/items/affixes/shield_on_damage.tres
Normal file
@ -0,0 +1,14 @@
|
||||
[gd_resource type="Resource" script_class="ItemAffixDefinition" load_steps=2 format=3]
|
||||
[ext_resource type="Script" path="res://scripts/items/item_affix_definition.gd" id="1_affix"]
|
||||
[resource]
|
||||
script = ExtResource("1_affix")
|
||||
affix_id = "shield_on_damage"
|
||||
display_name = "Odzysk tarcz z obrażeń"
|
||||
description = "Globalnie odnawia tarcze o procent faktycznie zadanych obrażeń każdą bronią."
|
||||
stat_key = "shield_on_damage_percent"
|
||||
unit = "%"
|
||||
category_mask = 63
|
||||
minimum_value_level_1 = 1.0
|
||||
maximum_value_level_1 = 1.0
|
||||
minimum_value_level_1000 = 5.0
|
||||
maximum_value_level_1000 = 5.0
|
||||
@ -9,14 +9,14 @@ display_name = "Działko plazmowe"
|
||||
category = 0
|
||||
description = "Szybkostrzelna broń krótkiego zasięgu."
|
||||
primary_stat = "damage"
|
||||
primary_value = 15.0
|
||||
primary_value = 12.0
|
||||
stat_suffix = " DMG"
|
||||
weapon_kind = 0
|
||||
fire_interval = 0.2857
|
||||
projectile_speed = 720.0
|
||||
weapon_range = 350.0
|
||||
energy_cost = 4.0
|
||||
damage_lower_roll_min = 10
|
||||
damage_lower_roll_max = 14
|
||||
damage_lower_roll_min = 8
|
||||
damage_lower_roll_max = 11
|
||||
damage_spread_roll_min = 2
|
||||
damage_spread_roll_max = 4
|
||||
damage_spread_roll_max = 3
|
||||
|
||||
@ -59,9 +59,9 @@ script = ExtResource("1_schematic")
|
||||
|
||||
[node name="Weapon1" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")]
|
||||
layout_mode = 0
|
||||
offset_left = 44.0
|
||||
offset_left = 0.0
|
||||
offset_top = 22.0
|
||||
offset_right = 134.0
|
||||
offset_right = 90.0
|
||||
offset_bottom = 66.0
|
||||
slot_kind = "weapon"
|
||||
slot_index = 0
|
||||
@ -69,9 +69,9 @@ overlay_slot = true
|
||||
|
||||
[node name="Weapon2" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")]
|
||||
layout_mode = 0
|
||||
offset_left = 172.0
|
||||
offset_left = 216.0
|
||||
offset_top = 22.0
|
||||
offset_right = 262.0
|
||||
offset_right = 306.0
|
||||
offset_bottom = 66.0
|
||||
slot_kind = "weapon"
|
||||
slot_index = 1
|
||||
@ -80,9 +80,9 @@ overlay_slot = true
|
||||
[node name="Missile" parent="Contents/EquipmentLayout" instance=ExtResource("2_slot")]
|
||||
layout_mode = 0
|
||||
offset_left = 108.0
|
||||
offset_top = 260.0
|
||||
offset_top = 22.0
|
||||
offset_right = 198.0
|
||||
offset_bottom = 304.0
|
||||
offset_bottom = 66.0
|
||||
slot_kind = "weapon"
|
||||
slot_index = 2
|
||||
overlay_slot = true
|
||||
|
||||
@ -57,7 +57,7 @@ theme_override_constants/separation = 12
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_font_sizes/font_size = 11
|
||||
text = "AMUNICJA: 300"
|
||||
text = "AMUNICJA: 50"
|
||||
|
||||
[node name="CraftButton" type="Button" parent="Contents/Ammunition"]
|
||||
layout_mode = 2
|
||||
|
||||
@ -75,10 +75,10 @@ func add_rewards(xp_amount: int, credit_amount: int) -> void:
|
||||
learning_points += BALANCE.LEARNING_POINTS_PER_LEVEL
|
||||
emit_changed()
|
||||
|
||||
func reward_enemy(system_level: int, tier: int, xp_multiplier: float, credit_multiplier: float) -> Vector2i:
|
||||
func reward_enemy(system_level: int, tier: int, xp_multiplier: float, credit_multiplier: float, grant_credits: bool = true) -> Vector2i:
|
||||
var index := clampi(tier, 0, 2)
|
||||
var scale_factor := 1.0 + 0.05 * (clampi(system_level, 1, 1000) - 1)
|
||||
var xp := roundi(BALANCE.KILL_XP[index] * scale_factor * maxf(0.0, xp_multiplier))
|
||||
var money := roundi(BALANCE.KILL_CREDITS[index] * scale_factor * maxf(0.0, credit_multiplier))
|
||||
add_rewards(xp, money)
|
||||
add_rewards(xp, money if grant_credits else 0)
|
||||
return Vector2i(xp, money)
|
||||
|
||||
@ -6,6 +6,7 @@ const PLASMA_PROJECTILE_SCENE := preload("res://scenes/game/enemy_plasma_project
|
||||
const WEAPON_AUDIO_SCRIPT := preload("res://scripts/game/weapon_audio.gd")
|
||||
const ITEM_GENERATOR := preload("res://scripts/items/item_generator.gd")
|
||||
const LOOT_PICKUP := preload("res://scripts/items/loot_pickup.gd")
|
||||
const LOOT_OFFSET := Vector2(0, 36)
|
||||
const LEVEL_BALANCE := preload("res://scripts/levels/level_balance.gd")
|
||||
|
||||
@export_enum("Normal", "Elite", "Boss") var loot_tier: int = 0
|
||||
@ -99,7 +100,12 @@ func _fire_plasma(direction: Vector2) -> void:
|
||||
WEAPON_AUDIO_SCRIPT.play(get_parent(), global_position, ItemDefinition.WeaponKind.PLASMA, -4.0)
|
||||
|
||||
func take_weapon_hit(amount: float, critical: bool) -> void:
|
||||
var actual_damage := minf(maxf(0.0, amount), maxf(0.0, hull))
|
||||
if is_queued_for_deletion():
|
||||
actual_damage = 0.0
|
||||
take_damage(amount, critical)
|
||||
if is_instance_valid(_player):
|
||||
_player.call("restore_shields_from_damage", actual_damage)
|
||||
|
||||
func take_damage(amount: float, critical: bool = false) -> void:
|
||||
if is_queued_for_deletion() or hull <= 0.0:
|
||||
@ -114,7 +120,13 @@ func take_damage(amount: float, critical: bool = false) -> void:
|
||||
|
||||
func _reward_captain() -> void:
|
||||
if is_instance_valid(_player):
|
||||
GameState.captain.reward_enemy(GameState.system_level, loot_tier, float(_player.get("experience_multiplier")), float(_player.get("credits_multiplier")))
|
||||
var reward := GameState.captain.reward_enemy(GameState.system_level, loot_tier, float(_player.get("experience_multiplier")), float(_player.get("credits_multiplier")), false)
|
||||
if reward.y > 0:
|
||||
var credits := Node2D.new()
|
||||
credits.set_script(preload("res://scripts/items/credit_pickup.gd"))
|
||||
credits.set("amount", reward.y)
|
||||
credits.position = position - LOOT_OFFSET
|
||||
get_parent().call_deferred("add_child", credits)
|
||||
|
||||
func _drop_loot() -> void:
|
||||
var chance := 0.65 if loot_tier == 0 else (0.9 if loot_tier == 1 else 1.0)
|
||||
@ -123,7 +135,7 @@ func _drop_loot() -> void:
|
||||
var pickup := Node2D.new()
|
||||
pickup.set_script(LOOT_PICKUP)
|
||||
pickup.set("item", ITEM_GENERATOR.generate(GameState.system_level, loot_tier))
|
||||
pickup.position = position
|
||||
pickup.position = position + LOOT_OFFSET
|
||||
get_parent().call_deferred("add_child", pickup)
|
||||
|
||||
func _spawn_destruction_effect() -> void:
|
||||
|
||||
@ -133,6 +133,7 @@ Redukcja obrażeń: %.1f%%
|
||||
Regeneracja energii: %.1f pkt/s (%+.1f)
|
||||
Ładowanie solarne: +%.1f pkt/s
|
||||
Regeneracja tarcz: %.1f pkt/s (%+.1f)
|
||||
Odzysk tarcz z obrażeń: %.1f%%
|
||||
Maks. kadłub: %.1f (%+.1f pkt)
|
||||
Maks. tarcze: %.1f (%+.1f pkt)
|
||||
Maks. energia: %.1f (%+.1f pkt)
|
||||
@ -151,6 +152,7 @@ Zdobywane kredyty: %+.1f%%""" % [
|
||||
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("max_hull")), float(player.get("max_hull")) - float(player.get("base_max_hull")),
|
||||
float(player.get("max_shields")), float(player.get("max_shields")) - float(player.get("base_max_shields")),
|
||||
float(player.get("max_energy")), float(player.get("max_energy")) - float(player.get("base_max_energy")),
|
||||
|
||||
@ -36,6 +36,7 @@ var shield_regeneration := 0.0
|
||||
var damage_multiplier := 1.0
|
||||
var experience_multiplier := 1.0
|
||||
var credits_multiplier := 1.0
|
||||
var shield_on_damage_percent := 0.0
|
||||
var critical_chance_bonus := 0.0
|
||||
var critical_damage_bonus := 0.0
|
||||
@export var solar_charge_radius := 550.0
|
||||
@ -73,6 +74,7 @@ func apply_equipment_bonuses(bonuses: Dictionary) -> void:
|
||||
damage_multiplier = 1.0 + float(bonuses.get("damage_percent", 0.0)) / 100.0
|
||||
experience_multiplier = 1.0 + float(bonuses.get("experience_percent", 0.0)) / 100.0
|
||||
credits_multiplier = 1.0 + float(bonuses.get("credits_percent", 0.0)) / 100.0
|
||||
shield_on_damage_percent = maxf(0.0, float(bonuses.get("shield_on_damage_percent", 0.0)))
|
||||
critical_chance_bonus = float(bonuses.get("critical_chance_percent", 0.0)) / 100.0
|
||||
critical_damage_bonus = float(bonuses.get("critical_damage_percent", 0.0)) / 100.0
|
||||
energy_regeneration = base_energy_regeneration + float(bonuses.get("energy_regeneration", 0.0))
|
||||
@ -148,6 +150,12 @@ func take_damage(amount: float) -> void:
|
||||
if hull <= 0.0:
|
||||
_destroy_ship()
|
||||
|
||||
func restore_shields_from_damage(actual_damage: float) -> void:
|
||||
if _is_destroyed or actual_damage <= 0.0 or shield_on_damage_percent <= 0.0:
|
||||
return
|
||||
shields = minf(max_shields, shields + actual_damage * shield_on_damage_percent / 100.0)
|
||||
_update_shield_visual()
|
||||
|
||||
func get_evasion_chance() -> float:
|
||||
return GameState.captain.get_skill_bonus("piloting")
|
||||
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
extends Node2D
|
||||
|
||||
const FONT := preload("res://assets/fonts/font2.ttf")
|
||||
var player: CharacterBody2D
|
||||
var _display_ratio := 1.0
|
||||
var _display_shield_ratio := 1.0
|
||||
var _display_hull_ratio := 1.0
|
||||
var _background: StyleBoxFlat
|
||||
|
||||
func _ready() -> void:
|
||||
@ -13,6 +14,8 @@ func _ready() -> void:
|
||||
reset_physics_interpolation()
|
||||
z_index = 24
|
||||
_display_ratio = _energy_ratio()
|
||||
_display_shield_ratio = _shield_ratio()
|
||||
_display_hull_ratio = _hull_ratio()
|
||||
_background = StyleBoxFlat.new()
|
||||
_background.bg_color = Color("101925")
|
||||
_background.border_color = Color("456277")
|
||||
@ -33,11 +36,25 @@ func _process(delta: float) -> void:
|
||||
_display_ratio = lerpf(_display_ratio, target, 1.0 - exp(-20.0 * delta))
|
||||
if absf(_display_ratio - target) < 0.001:
|
||||
_display_ratio = target
|
||||
var shield_target := _shield_ratio()
|
||||
_display_shield_ratio = lerpf(_display_shield_ratio, shield_target, 1.0 - exp(-20.0 * delta))
|
||||
if absf(_display_shield_ratio - shield_target) < 0.001:
|
||||
_display_shield_ratio = shield_target
|
||||
var hull_target := _hull_ratio()
|
||||
_display_hull_ratio = lerpf(_display_hull_ratio, hull_target, 1.0 - exp(-20.0 * delta))
|
||||
if absf(_display_hull_ratio - hull_target) < 0.001:
|
||||
_display_hull_ratio = hull_target
|
||||
queue_redraw()
|
||||
|
||||
func _energy_ratio() -> float:
|
||||
return clampf(float(player.get("energy")) / maxf(1.0, float(player.get("max_energy"))), 0.0, 1.0)
|
||||
|
||||
func _shield_ratio() -> float:
|
||||
return clampf(float(player.get("shields")) / maxf(1.0, float(player.get("max_shields"))), 0.0, 1.0)
|
||||
|
||||
func _hull_ratio() -> float:
|
||||
return clampf(float(player.get("hull")) / maxf(1.0, float(player.get("max_hull"))), 0.0, 1.0)
|
||||
|
||||
func _draw() -> void:
|
||||
if not is_instance_valid(player):
|
||||
return
|
||||
@ -45,8 +62,9 @@ func _draw() -> void:
|
||||
var bar_position := Vector2(-44, 48)
|
||||
draw_style_box(_background, Rect2(bar_position - Vector2(2, 2), Vector2(92, 10)))
|
||||
draw_rect(Rect2(bar_position, Vector2(88 * _display_ratio, 6)), color)
|
||||
var charging := float(player.call("get_solar_regeneration")) > 0.0 and _energy_ratio() < 1.0
|
||||
var caption := "ENERGIA %d%%" % roundi(_energy_ratio() * 100)
|
||||
draw_string(FONT, bar_position + Vector2(0, 22), caption, HORIZONTAL_ALIGNMENT_CENTER, 88, 10, color)
|
||||
if charging:
|
||||
draw_string(FONT, bar_position + Vector2(-18, 36), "ŁADOWANIE SOLARNE", HORIZONTAL_ALIGNMENT_CENTER, 124, 9, Color("71e3ff"))
|
||||
var shield_bar_position := bar_position + Vector2(0, 14)
|
||||
draw_style_box(_background, Rect2(shield_bar_position - Vector2(2, 2), Vector2(92, 10)))
|
||||
draw_rect(Rect2(shield_bar_position, Vector2(88 * _display_shield_ratio, 6)), Color.WHITE)
|
||||
var hull_bar_position := bar_position + Vector2(0, 28)
|
||||
draw_style_box(_background, Rect2(hull_bar_position - Vector2(2, 2), Vector2(92, 10)))
|
||||
draw_rect(Rect2(hull_bar_position, Vector2(88 * _display_hull_ratio, 6)), Color("ef5967"))
|
||||
|
||||
36
scripts/items/credit_pickup.gd
Normal file
36
scripts/items/credit_pickup.gd
Normal file
@ -0,0 +1,36 @@
|
||||
extends Node2D
|
||||
|
||||
const PICKUP_RADIUS := 48.0
|
||||
const ATTRACTION_RADIUS := 150.0
|
||||
const POPUP := preload("res://scenes/game/damage_popup.tscn")
|
||||
var amount: int = 0
|
||||
var _player: CharacterBody2D
|
||||
var _collected := false
|
||||
|
||||
func _ready() -> void:
|
||||
add_to_group("credit_pickups")
|
||||
_player = get_parent().get_node_or_null("PlayerShip") as CharacterBody2D
|
||||
z_index = 18
|
||||
|
||||
func _draw() -> void:
|
||||
draw_circle(Vector2.ZERO, 13.0, Color("46351b"))
|
||||
draw_arc(Vector2.ZERO, 13.0, 0, TAU, 32, Color("ffd56a"), 2.0, true)
|
||||
draw_arc(Vector2.ZERO, 6.0, PI * 0.3, PI * 1.7, 20, Color("ffd56a"), 2.0, true)
|
||||
draw_line(Vector2(-2, -8), Vector2(-2, 8), Color("ffd56a"), 1.5, true)
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if _collected or not is_instance_valid(_player) or bool(_player.get("_is_destroyed")):
|
||||
return
|
||||
var distance := global_position.distance_to(_player.global_position)
|
||||
if distance <= PICKUP_RADIUS:
|
||||
_collected = true
|
||||
GameState.captain.add_rewards(0, amount)
|
||||
var popup := POPUP.instantiate() as Label
|
||||
popup.text = "+%d KREDYTÓW" % amount
|
||||
popup.set("text_color", Color("ffd56a"))
|
||||
popup.position = _player.position + Vector2(-48, -60)
|
||||
get_parent().add_child(popup)
|
||||
set_physics_process(false)
|
||||
queue_free()
|
||||
elif distance <= ATTRACTION_RADIUS:
|
||||
global_position = global_position.move_toward(_player.global_position, 180.0 * delta)
|
||||
1
scripts/items/credit_pickup.gd.uid
Normal file
1
scripts/items/credit_pickup.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bgi4icgkhw4ax
|
||||
@ -129,4 +129,4 @@ func get_tooltip_text() -> String:
|
||||
return "\n".join(lines)
|
||||
|
||||
static func stat_name(key: String) -> String:
|
||||
return str({"damage": "Obrażenia", "max_hull": "Kadłub", "max_shields": "Tarcze", "max_energy": "Energia", "max_speed": "Prędkość", "energy_regeneration": "Regeneracja energii", "shield_regeneration": "Regeneracja tarcz", "hull_percent": "Kadłub", "shield_percent": "Tarcze", "damage_percent": "Obrażenia broni", "speed_percent": "Prędkość statku", "experience_percent": "Doświadczenie (EXP)", "credits_percent": "Kredyty", "critical_chance_percent": "Szansa na trafienie krytyczne", "critical_damage_percent": "Obrażenia krytyczne"}.get(key, key))
|
||||
return str({"shield_on_damage_percent": "Odzysk tarcz z obrażeń", "damage": "Obrażenia", "max_hull": "Kadłub", "max_shields": "Tarcze", "max_energy": "Energia", "max_speed": "Prędkość", "energy_regeneration": "Regeneracja energii", "shield_regeneration": "Regeneracja tarcz", "hull_percent": "Kadłub", "shield_percent": "Tarcze", "damage_percent": "Obrażenia broni", "speed_percent": "Prędkość statku", "experience_percent": "Doświadczenie (EXP)", "credits_percent": "Kredyty", "critical_chance_percent": "Szansa na trafienie krytyczne", "critical_damage_percent": "Obrażenia krytyczne"}.get(key, key))
|
||||
|
||||
@ -12,7 +12,7 @@ const MISSILE_CRAFT_PARTS_COST := 50.0
|
||||
const MISSILE_BUY_BASE_COST := 10
|
||||
const MISSILE_DEFINITIONS := [preload("res://resources/items/missile_launcher.tres"), preload("res://resources/items/unguided_missile.tres")]
|
||||
const MODULE_SLOT_COUNT := 6
|
||||
const STARTING_AMMUNITION := 300
|
||||
const STARTING_AMMUNITION := 50
|
||||
const AMMUNITION_CRAFT_AMOUNT := 100
|
||||
const AMMUNITION_CRAFT_PARTS_COST := 50.0
|
||||
const AMMUNITION_BUY_AMOUNT := 100
|
||||
@ -215,7 +215,7 @@ func _recalculate_ship_stats() -> void:
|
||||
for item in weapon_slots + module_slots:
|
||||
if item == null or item.definition == null:
|
||||
continue
|
||||
for stat_key in ["max_hull", "max_shields", "max_energy", "max_speed", "energy_regeneration", "shield_regeneration", "hull_percent", "shield_percent", "damage_percent", "speed_percent", "experience_percent", "credits_percent", "critical_chance_percent", "critical_damage_percent"]:
|
||||
for stat_key in ["max_hull", "max_shields", "max_energy", "max_speed", "energy_regeneration", "shield_regeneration", "hull_percent", "shield_percent", "damage_percent", "speed_percent", "experience_percent", "credits_percent", "critical_chance_percent", "critical_damage_percent", "shield_on_damage_percent"]:
|
||||
bonuses[stat_key] = float(bonuses.get(stat_key, 0.0)) + item.get_stat_total(stat_key)
|
||||
ship.call("apply_equipment_bonuses", bonuses)
|
||||
|
||||
|
||||
@ -51,6 +51,8 @@ func _ready() -> void:
|
||||
enemy.call("take_weapon_hit", 1000000.0, true)
|
||||
enemy.call("take_damage", 1000000.0)
|
||||
assert(profile.level == 2 and profile.experience == 130 and profile.learning_points == 1)
|
||||
assert(profile.credits == 0)
|
||||
await _collect_credits(game.get_node("PlayerShip") as CharacterBody2D)
|
||||
assert(profile.credits == 180)
|
||||
var panel := game.get_node("HUD/HUDRoot/CaptainPanel")
|
||||
panel.get_node("Contents/Skills/gunner/Train_gunner").emit_signal("pressed")
|
||||
@ -117,6 +119,7 @@ func _ready() -> void:
|
||||
assert(player.get_node("ShipInventory").call("get_item", "weapon", 0) == weapon)
|
||||
for enemy in get_tree().get_nodes_in_group("enemies"):
|
||||
enemy.call("take_damage", 1000000.0)
|
||||
await _collect_credits(player)
|
||||
assert(profile.level == 4 and profile.experience == 68 and profile.credits == 452)
|
||||
var planet: Node2D = game.get("protected_planet")
|
||||
player.position = planet.position
|
||||
@ -131,12 +134,20 @@ func _ready() -> void:
|
||||
game = get_tree().current_scene as Node2D
|
||||
_freeze(game)
|
||||
assert(GameState.captain == profile and GameState.system_level == 2)
|
||||
player = game.get_node("PlayerShip") as CharacterBody2D
|
||||
assert(profile.get_skill_rank("gunner") == 1 and profile.get_skill_rank("sharp_eye") == 20)
|
||||
var enemy := get_tree().get_nodes_in_group("enemies")[0] as CharacterBody2D
|
||||
enemy.call("take_damage", 1000000.0)
|
||||
await _collect_credits(player)
|
||||
assert(profile.level == 5 and profile.experience == 15 and profile.credits == 641)
|
||||
print("CAPTAIN_SYSTEM_TEST: PASS (XP, multiple levels, learning UI, rewards once, dodge, reduction, critical addons, projectile metadata, death and next system)")
|
||||
|
||||
func _collect_credits(player: CharacterBody2D) -> void:
|
||||
await get_tree().process_frame
|
||||
for pickup in get_tree().get_nodes_in_group("credit_pickups"):
|
||||
player.global_position = pickup.global_position
|
||||
pickup.call("_physics_process", 0.0)
|
||||
|
||||
func _test_skill_balance_and_reset() -> void:
|
||||
assert(is_equal_approx(BALANCE.skill_bonus("mechanic", 1), 0.5 / 11.0))
|
||||
assert(is_equal_approx(BALANCE.skill_bonus("mechanic", 5), 1.0 / 6.0))
|
||||
|
||||
51
tests/items/credit_pickup_test.gd
Normal file
51
tests/items/credit_pickup_test.gd
Normal file
@ -0,0 +1,51 @@
|
||||
extends Node2D
|
||||
|
||||
const PLAYER := preload("res://scenes/game/player_ship.tscn")
|
||||
const ENEMY := preload("res://scenes/game/enemy_ship.tscn")
|
||||
const BALANCE := preload("res://scripts/captain/captain_balance.gd")
|
||||
|
||||
func _ready() -> void:
|
||||
GameState.start_new_run()
|
||||
GameState.system_level = 4
|
||||
var ship := PLAYER.instantiate() as CharacterBody2D
|
||||
ship.name = "PlayerShip"
|
||||
add_child(ship)
|
||||
ship.set_physics_process(false)
|
||||
ship.get_node("WeaponController").set_physics_process(false)
|
||||
ship.set("credits_multiplier", 1.5)
|
||||
var inventory := ship.get_node("ShipInventory") as ShipInventory
|
||||
assert(inventory.ammunition == 50)
|
||||
var enemy := ENEMY.instantiate() as CharacterBody2D
|
||||
enemy.position = Vector2(700, 0)
|
||||
add_child(enemy)
|
||||
enemy.set_physics_process(false)
|
||||
enemy.set("_player", ship)
|
||||
enemy.call("apply_level_settings", 4, 2)
|
||||
var expected := roundi(BALANCE.KILL_CREDITS[2] * 1.15 * 1.5)
|
||||
enemy.call("take_damage", enemy.get("max_hull"))
|
||||
assert(GameState.captain.credits == 0)
|
||||
assert(GameState.captain.level > 1 or GameState.captain.experience > 0)
|
||||
await get_tree().process_frame
|
||||
var pickups := get_tree().get_nodes_in_group("credit_pickups")
|
||||
assert(pickups.size() == 1)
|
||||
var pickup := pickups[0] as Node2D
|
||||
pickup.set_physics_process(false)
|
||||
assert(pickup.get("amount") == expected)
|
||||
pickup.call("_physics_process", 0.1)
|
||||
assert(GameState.captain.credits == 0)
|
||||
# Credits can be collected even when cargo is full.
|
||||
var item := ItemInstance.new()
|
||||
item.definition = preload("res://resources/items/plasma_cannon.tres")
|
||||
inventory.cargo.fill(item)
|
||||
ship.position = pickup.position
|
||||
pickup.call("_physics_process", 0.1)
|
||||
assert(GameState.captain.credits == expected)
|
||||
pickup.call("_physics_process", 0.1)
|
||||
assert(GameState.captain.credits == expected)
|
||||
var found_popup := false
|
||||
for child in get_children():
|
||||
if child is Label and child.text == "+%d KREDYTÓW" % expected:
|
||||
found_popup = true
|
||||
assert(found_popup)
|
||||
print("CREDIT_PICKUP_TEST: PASS (drop, deferred credits, XP, gear bonus, proximity, full cargo, popup, single collection)")
|
||||
get_tree().quit()
|
||||
1
tests/items/credit_pickup_test.gd.uid
Normal file
1
tests/items/credit_pickup_test.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dcwgf8tg8tncy
|
||||
4
tests/items/credit_pickup_test.tscn
Normal file
4
tests/items/credit_pickup_test.tscn
Normal file
@ -0,0 +1,4 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
[ext_resource type="Script" path="res://tests/items/credit_pickup_test.gd" id="1"]
|
||||
[node name="CreditPickupTest" type="Node2D"]
|
||||
script = ExtResource("1")
|
||||
99
tests/items/machine_gun_shield_recovery_test.gd
Normal file
99
tests/items/machine_gun_shield_recovery_test.gd
Normal file
@ -0,0 +1,99 @@
|
||||
extends Node2D
|
||||
|
||||
const PLAYER := preload("res://scenes/game/player_ship.tscn")
|
||||
const ENEMY := preload("res://scenes/game/enemy_ship.tscn")
|
||||
|
||||
class AimedGun:
|
||||
extends "res://scripts/game/weapon_controller.gd"
|
||||
func _aim_direction_from(_origin: Vector2) -> Vector2:
|
||||
return Vector2.RIGHT
|
||||
|
||||
func _ready() -> void:
|
||||
get_tree().create_timer(20.0).timeout.connect(func() -> void:
|
||||
push_error("MACHINE_GUN_SHIELD_RECOVERY_TEST: timed out")
|
||||
Input.action_release("ship_fire_primary")
|
||||
get_tree().quit(1)
|
||||
)
|
||||
seed(12345)
|
||||
GameState.start_new_run()
|
||||
var ship := PLAYER.instantiate() as CharacterBody2D
|
||||
ship.name = "PlayerShip"
|
||||
add_child(ship)
|
||||
ship.set_physics_process(false)
|
||||
ship.get_node("WeaponController").set_physics_process(false)
|
||||
ship.set("shields", 0.0)
|
||||
ship.set("energy", 0.0)
|
||||
var inventory := ship.get_node("ShipInventory") as ShipInventory
|
||||
var gun := ItemInstance.new()
|
||||
gun.definition = preload("res://resources/items/machine_gun.tres")
|
||||
assert(inventory.add_item(gun))
|
||||
assert(inventory.move_item("cargo", 0, "weapon", 0))
|
||||
var module := ItemInstance.new()
|
||||
module.definition = preload("res://resources/items/engine_extension.tres")
|
||||
var addon := ItemStatModifier.new()
|
||||
addon.affix = preload("res://resources/items/affixes/shield_on_damage.tres")
|
||||
addon.stat_key = addon.affix.stat_key
|
||||
addon.value = addon.affix.roll_value(1)
|
||||
module.additional_modifiers.append(addon)
|
||||
assert(inventory.add_item(module))
|
||||
assert(inventory.move_item("cargo", inventory.cargo.find(module), "module", 0))
|
||||
assert(ship.get("shield_on_damage_percent") == 1.0)
|
||||
var indicator := Node2D.new()
|
||||
indicator.set_script(preload("res://scripts/game/ship_energy_indicator.gd"))
|
||||
indicator.set("player", ship)
|
||||
ship.add_child(indicator)
|
||||
var enemy := ENEMY.instantiate() as CharacterBody2D
|
||||
enemy.position = Vector2(240, -28)
|
||||
add_child(enemy)
|
||||
enemy.set_physics_process(false)
|
||||
enemy.set("_player", ship)
|
||||
enemy.set("max_hull", 10000.0)
|
||||
enemy.set("hull", 10000.0)
|
||||
var controller := AimedGun.new()
|
||||
ship.add_child(controller)
|
||||
await get_tree().physics_frame
|
||||
await get_tree().physics_frame
|
||||
# Fire a real projectile with the smallest level-1 damage: 2 DMG.
|
||||
gun.damage_min = 2.0
|
||||
gun.damage_max = 2.0
|
||||
Input.action_press("ship_fire_primary")
|
||||
for frame in 60:
|
||||
await get_tree().physics_frame
|
||||
if inventory.ammunition < 50:
|
||||
break
|
||||
Input.action_release("ship_fire_primary")
|
||||
assert(inventory.ammunition == 49)
|
||||
for frame in 60:
|
||||
await get_tree().physics_frame
|
||||
if enemy.get("hull") < 10000.0:
|
||||
break
|
||||
assert(is_equal_approx(enemy.get("hull"), 9998.0))
|
||||
assert(is_equal_approx(ship.get("shields"), 0.02))
|
||||
print("MACHINE_GUN_RECOVERY: single physical hit: 2 DMG -> +%.4f shields" % ship.get("shields"))
|
||||
# Restore the normal starter roll (3–5 DMG), then empty the remaining magazine.
|
||||
gun.damage_min = -1.0
|
||||
gun.damage_max = -1.0
|
||||
Input.action_press("ship_fire_primary")
|
||||
for frame in 600:
|
||||
await get_tree().physics_frame
|
||||
if inventory.ammunition == 0:
|
||||
break
|
||||
Input.action_release("ship_fire_primary")
|
||||
assert(inventory.ammunition == 0)
|
||||
for frame in 45:
|
||||
await get_tree().physics_frame
|
||||
var actual_damage := 10000.0 - float(enemy.get("hull"))
|
||||
var recovered := float(ship.get("shields"))
|
||||
assert(is_equal_approx(recovered, actual_damage * 0.01))
|
||||
assert(recovered > 1.0 and ship.get("energy") == 0.0)
|
||||
indicator.call("_process", 1.0)
|
||||
assert(is_equal_approx(indicator.get("_display_shield_ratio"), recovered / float(ship.get("max_shields"))))
|
||||
print("MACHINE_GUN_RECOVERY: 50 physical hits, %.0f total DMG -> +%.4f shields; white bar: %.4f%% (%.3f px)" % [actual_damage, recovered, recovered / float(ship.get("max_shields")) * 100.0, recovered / float(ship.get("max_shields")) * 88.0])
|
||||
# Empty ammo cannot keep firing or generate any further shields.
|
||||
Input.action_press("ship_fire_primary")
|
||||
for frame in 30:
|
||||
await get_tree().physics_frame
|
||||
Input.action_release("ship_fire_primary")
|
||||
assert(is_equal_approx(ship.get("shields"), recovered))
|
||||
print("MACHINE_GUN_SHIELD_RECOVERY_TEST: PASS (real firing input, projectile flight, collisions, 2-DMG fractions, accumulated recovery, ammo consumption, zero energy, shield bar, empty ammo)")
|
||||
get_tree().quit()
|
||||
4
tests/items/machine_gun_shield_recovery_test.tscn
Normal file
4
tests/items/machine_gun_shield_recovery_test.tscn
Normal file
@ -0,0 +1,4 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
[ext_resource type="Script" path="res://tests/items/machine_gun_shield_recovery_test.gd" id="1"]
|
||||
[node name="MachineGunShieldRecoveryTest" type="Node2D"]
|
||||
script = ExtResource("1")
|
||||
76
tests/items/shield_on_damage_test.gd
Normal file
76
tests/items/shield_on_damage_test.gd
Normal file
@ -0,0 +1,76 @@
|
||||
extends Node2D
|
||||
|
||||
const PLAYER := preload("res://scenes/game/player_ship.tscn")
|
||||
const ENEMY := preload("res://scenes/game/enemy_ship.tscn")
|
||||
const PROJECTILE := preload("res://scenes/game/weapon_projectile.tscn")
|
||||
|
||||
func _ready() -> void:
|
||||
GameState.start_new_run()
|
||||
var ship := PLAYER.instantiate() as CharacterBody2D
|
||||
ship.name = "PlayerShip"
|
||||
add_child(ship)
|
||||
ship.set_physics_process(false)
|
||||
var weapons := ship.get_node("WeaponController")
|
||||
weapons.set_physics_process(false)
|
||||
var inventory := ship.get_node("ShipInventory") as ShipInventory
|
||||
var modifier := ItemStatModifier.new()
|
||||
modifier.stat_key = "shield_on_damage_percent"
|
||||
modifier.value = 1.0
|
||||
var module := ItemInstance.new()
|
||||
module.definition = preload("res://resources/items/engine_extension.tres")
|
||||
module.additional_modifiers.append(modifier)
|
||||
assert(inventory.add_item(module))
|
||||
assert(inventory.move_item("cargo", 0, "module", 0))
|
||||
assert(ship.get("shield_on_damage_percent") == 1.0)
|
||||
ship.set("shields", 0.0)
|
||||
var enemy := _enemy(ship, 100.0)
|
||||
# A gun without the addon still benefits from the global module stat.
|
||||
weapons.call("_apply_damage", enemy, 20.0, false)
|
||||
assert(is_equal_approx(ship.get("shields"), 0.2))
|
||||
var projectile := PROJECTILE.instantiate() as Area2D
|
||||
projectile.set("damage", 10.0)
|
||||
projectile.set("is_critical", true)
|
||||
add_child(projectile)
|
||||
projectile.set_physics_process(false)
|
||||
projectile.call("_apply_damage_once", enemy)
|
||||
projectile.call("_apply_damage_once", enemy)
|
||||
assert(is_equal_approx(ship.get("shields"), 0.3))
|
||||
enemy.call("take_weapon_hit", 1000.0, true)
|
||||
assert(is_equal_approx(ship.get("shields"), 1.0)) # Only remaining 70 HP.
|
||||
enemy.call("take_weapon_hit", 1000.0, true)
|
||||
assert(is_equal_approx(ship.get("shields"), 1.0))
|
||||
# Addons on weapons and modules sum globally.
|
||||
inventory.weapon_slots[0].additional_modifiers.append(modifier)
|
||||
inventory.call("_recalculate_ship_stats")
|
||||
assert(ship.get("shield_on_damage_percent") == 2.0)
|
||||
enemy = _enemy(ship, 100.0)
|
||||
enemy.call("take_damage", 10.0)
|
||||
assert(is_equal_approx(ship.get("shields"), 1.0))
|
||||
enemy.call("take_weapon_hit", -5.0, false)
|
||||
assert(is_equal_approx(ship.get("shields"), 1.0))
|
||||
enemy.call("take_weapon_hit", 50.0, false)
|
||||
assert(is_equal_approx(ship.get("shields"), 2.0))
|
||||
ship.set("shields", ship.get("max_shields") - 0.1)
|
||||
enemy.call("take_weapon_hit", 10.0, false)
|
||||
assert(ship.get("shields") == ship.get("max_shields"))
|
||||
assert(inventory.move_item("module", 0, "cargo", 0))
|
||||
inventory.weapon_slots[0].additional_modifiers.clear()
|
||||
inventory.call("_recalculate_ship_stats")
|
||||
ship.set("shields", 0.0)
|
||||
enemy.call("take_weapon_hit", 10.0, false)
|
||||
assert(ship.get("shields") == 0.0)
|
||||
var affix := load("res://resources/items/affixes/shield_on_damage.tres") as ItemAffixDefinition
|
||||
assert(affix.roll_value(1) == 1.0 and affix.roll_value(1000) == 5.0)
|
||||
assert(affix.can_roll(module.definition, 1))
|
||||
assert(affix.can_roll(inventory.weapon_slots[0].definition, 1))
|
||||
print("SHIELD_ON_DAMAGE_TEST: PASS (global addon, laser/projectile hits, stacking, actual HP, overkill, duplicate hits, caps, unequipping, scaling)")
|
||||
get_tree().quit()
|
||||
|
||||
func _enemy(ship: CharacterBody2D, hp: float) -> CharacterBody2D:
|
||||
var enemy := ENEMY.instantiate() as CharacterBody2D
|
||||
enemy.position = Vector2(700, 0)
|
||||
add_child(enemy)
|
||||
enemy.set_physics_process(false)
|
||||
enemy.set("_player", ship)
|
||||
enemy.set("hull", hp)
|
||||
return enemy
|
||||
1
tests/items/shield_on_damage_test.gd.uid
Normal file
1
tests/items/shield_on_damage_test.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://fcx7ivtsrkcp
|
||||
4
tests/items/shield_on_damage_test.tscn
Normal file
4
tests/items/shield_on_damage_test.tscn
Normal file
@ -0,0 +1,4 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
[ext_resource type="Script" path="res://tests/items/shield_on_damage_test.gd" id="1"]
|
||||
[node name="ShieldOnDamageTest" type="Node2D"]
|
||||
script = ExtResource("1")
|
||||
@ -77,7 +77,7 @@ func _ready() -> void:
|
||||
player.add_child(probe)
|
||||
probe.set_physics_process(false)
|
||||
var inventory := player.get_node("ShipInventory") as ShipInventory
|
||||
assert(inventory.ammunition == 300)
|
||||
assert(inventory.ammunition == ShipInventory.STARTING_AMMUNITION)
|
||||
var machine_gun := ItemInstance.new()
|
||||
machine_gun.definition = preload("res://resources/items/machine_gun.tres")
|
||||
player.set("energy", 0.0)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user