Zmiany w brocniah i dodanie ebsorcji
This commit is contained in:
parent
24a58b60db
commit
f12ed2f67e
1
assets/icons/gamma.svg
Normal file
1
assets/icons/gamma.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96"><defs><radialGradient id="g"><stop stop-color="#edffd0"/><stop offset=".5" stop-color="#80ff45"/><stop offset="1" stop-color="#174823"/></radialGradient></defs><path d="M27 80V47L34 24H62L69 47V80Z" fill="#293e46" stroke="#95b7ad" stroke-width="3"/><path d="M38 60V18H58V60" fill="#11241a" stroke="#59d44c" stroke-width="3"/><circle cx="48" cy="25" r="17" fill="url(#g)"/><ellipse cx="48" cy="25" rx="25" ry="10" fill="none" stroke="#b7ff72" stroke-width="2" transform="rotate(-35 48 25)"/><circle cx="65" cy="10" r="3" fill="#e0ffc3"/><path d="M34 68H62M34 74H62" stroke="#7eff4f" stroke-width="3"/></svg>
|
||||||
|
After Width: | Height: | Size: 689 B |
43
assets/icons/gamma.svg.import
Normal file
43
assets/icons/gamma.svg.import
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://b30nivtfvxw31"
|
||||||
|
path="res://.godot/imported/gamma.svg-877dc4511099b181b72b5bbafe97b7fd.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://assets/icons/gamma.svg"
|
||||||
|
dest_files=["res://.godot/imported/gamma.svg-877dc4511099b181b72b5bbafe97b7fd.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
svg/scale=1.0
|
||||||
|
editor/scale_with_editor_scale=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
@ -1,4 +1,4 @@
|
|||||||
[gd_resource type="Resource" script_class="ItemAffixDatabase" load_steps=15 format=3]
|
[gd_resource type="Resource" script_class="ItemAffixDatabase" load_steps=18 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_database.gd" id="1_database"]
|
||||||
[ext_resource type="Script" path="res://scripts/items/item_affix_definition.gd" id="2_type"]
|
[ext_resource type="Script" path="res://scripts/items/item_affix_definition.gd" id="2_type"]
|
||||||
@ -17,6 +17,10 @@
|
|||||||
|
|
||||||
[ext_resource type="Resource" path="res://resources/items/affixes/shield_on_damage.tres" id="14_affix"]
|
[ext_resource type="Resource" path="res://resources/items/affixes/shield_on_damage.tres" id="14_affix"]
|
||||||
|
|
||||||
|
[ext_resource type="Resource" path="res://resources/items/affixes/kinetic_absorption.tres" id="kinetic"]
|
||||||
|
[ext_resource type="Resource" path="res://resources/items/affixes/energy_absorption.tres" id="energy"]
|
||||||
|
[ext_resource type="Resource" path="res://resources/items/affixes/radiation_absorption.tres" id="radiation"]
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1_database")
|
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"), ExtResource("14_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"), ExtResource("kinetic"), ExtResource("energy"), ExtResource("radiation")])
|
||||||
|
|||||||
13
resources/items/affixes/energy_absorption.tres
Normal file
13
resources/items/affixes/energy_absorption.tres
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[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"]
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
affix_id = "energy_absorption"
|
||||||
|
display_name = "Absorpcja obrażeń energetycznych"
|
||||||
|
stat_key = "energy_absorption"
|
||||||
|
description = "Niweluje obrażenia tego typu przed tarczą i pancerzem. Łączny limit statku: 60%."
|
||||||
|
category_mask = 95
|
||||||
|
minimum_value_level_1 = 1.0
|
||||||
|
maximum_value_level_1 = 1.0
|
||||||
|
minimum_value_level_1000 = 10.0
|
||||||
|
maximum_value_level_1000 = 15.0
|
||||||
13
resources/items/affixes/kinetic_absorption.tres
Normal file
13
resources/items/affixes/kinetic_absorption.tres
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[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"]
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
affix_id = "kinetic_absorption"
|
||||||
|
display_name = "Absorpcja obrażeń kinetycznych"
|
||||||
|
stat_key = "kinetic_absorption"
|
||||||
|
description = "Niweluje obrażenia tego typu przed tarczą i pancerzem. Łączny limit statku: 60%."
|
||||||
|
category_mask = 95
|
||||||
|
minimum_value_level_1 = 1.0
|
||||||
|
maximum_value_level_1 = 1.0
|
||||||
|
minimum_value_level_1000 = 10.0
|
||||||
|
maximum_value_level_1000 = 15.0
|
||||||
13
resources/items/affixes/radiation_absorption.tres
Normal file
13
resources/items/affixes/radiation_absorption.tres
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[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"]
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
affix_id = "radiation_absorption"
|
||||||
|
display_name = "Absorpcja obrażeń radiacyjnych"
|
||||||
|
stat_key = "radiation_absorption"
|
||||||
|
description = "Niweluje obrażenia tego typu przed tarczą i pancerzem. Łączny limit statku: 60%."
|
||||||
|
category_mask = 95
|
||||||
|
minimum_value_level_1 = 1.0
|
||||||
|
maximum_value_level_1 = 1.0
|
||||||
|
minimum_value_level_1000 = 10.0
|
||||||
|
maximum_value_level_1000 = 15.0
|
||||||
21
resources/items/gamma_cannon.tres
Normal file
21
resources/items/gamma_cannon.tres
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[gd_resource type="Resource" script_class="ItemDefinition" load_steps=3 format=3]
|
||||||
|
[ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1"]
|
||||||
|
[ext_resource type="Texture2D" path="res://assets/icons/gamma.svg" id="icon"]
|
||||||
|
[resource]
|
||||||
|
script = ExtResource("1")
|
||||||
|
icon = ExtResource("icon")
|
||||||
|
item_id = "gamma_cannon"
|
||||||
|
display_name = "Działko gamma"
|
||||||
|
category = 0
|
||||||
|
description = "Radiacyjna kula. Napromieniowanie: 3 s, 25% obrażeń trafienia/s. Kolejne trafienia odświeżają czas. Zużywa napromieniowaną amunicję."
|
||||||
|
primary_stat = "damage"
|
||||||
|
primary_value = 15.0
|
||||||
|
stat_suffix = " DMG"
|
||||||
|
weapon_kind = 4
|
||||||
|
fire_interval = 0.8
|
||||||
|
projectile_speed = 430.0
|
||||||
|
weapon_range = 800.0
|
||||||
|
damage_lower_roll_min = 10
|
||||||
|
damage_lower_roll_max = 14
|
||||||
|
damage_spread_roll_min = 4
|
||||||
|
damage_spread_roll_max = 8
|
||||||
@ -7,7 +7,7 @@ const BALANCE := preload("res://scripts/captain/captain_balance.gd")
|
|||||||
@export var experience: int = 0
|
@export var experience: int = 0
|
||||||
@export var credits: int = 0
|
@export var credits: int = 0
|
||||||
@export var parts: float = 0.0
|
@export var parts: float = 0.0
|
||||||
@export var minerals: Dictionary = {"titanium": 0, "iron": 0, "aluminium": 0}
|
@export var minerals: Dictionary = {"uranium": 0, "titanium": 0, "iron": 0, "aluminium": 0}
|
||||||
@export var learning_points: int = 0
|
@export var learning_points: int = 0
|
||||||
@export var skills: Dictionary = {
|
@export var skills: Dictionary = {
|
||||||
"piloting": 0, "gunner": 0, "mechanic": 0, "sharp_eye": 0,
|
"piloting": 0, "gunner": 0, "mechanic": 0, "sharp_eye": 0,
|
||||||
|
|||||||
28
scripts/game/damage_types.gd
Normal file
28
scripts/game/damage_types.gd
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
extends RefCounted
|
||||||
|
class_name DamageTypes
|
||||||
|
|
||||||
|
enum Type { KINETIC, ENERGY, RADIATION }
|
||||||
|
const KEYS := ["kinetic_absorption", "energy_absorption", "radiation_absorption"]
|
||||||
|
const NAMES := ["Kinetyczne", "Energetyczne", "Radiacyjne"]
|
||||||
|
|
||||||
|
static func for_weapon(kind: int) -> int:
|
||||||
|
if kind == ItemDefinition.WeaponKind.GAMMA:
|
||||||
|
return Type.RADIATION
|
||||||
|
if kind in [ItemDefinition.WeaponKind.LASER, ItemDefinition.WeaponKind.PLASMA]:
|
||||||
|
return Type.ENERGY
|
||||||
|
return Type.KINETIC
|
||||||
|
|
||||||
|
static func capped(values: Vector3) -> Vector3:
|
||||||
|
values = values.max(Vector3.ZERO)
|
||||||
|
var total := values.x + values.y + values.z
|
||||||
|
return values * (60.0 / total) if total > 60.0 else values
|
||||||
|
|
||||||
|
static func enemy_profile(level: int, seed_value: int) -> Dictionary:
|
||||||
|
var rng := RandomNumberGenerator.new()
|
||||||
|
rng.seed = seed_value
|
||||||
|
var weights := Vector3(rng.randf_range(0.1, 1.0), rng.randf_range(0.1, 1.0), rng.randf_range(0.1, 1.0))
|
||||||
|
var total := lerpf(2.0, 60.0, pow(float(clampi(level, 1, 1000) - 1) / 999.0, 1.25))
|
||||||
|
return {"kind": rng.randi_range(0, 4), "absorption": weights * total / (weights.x + weights.y + weights.z)}
|
||||||
|
|
||||||
|
static func describe(values: Vector3) -> String:
|
||||||
|
return "Absorpcja: K %.1f%% · E %.1f%% · R %.1f%%" % [values.x, values.y, values.z]
|
||||||
1
scripts/game/damage_types.gd.uid
Normal file
1
scripts/game/damage_types.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://cumj03bmuxvlr
|
||||||
@ -46,5 +46,7 @@ func _draw() -> void:
|
|||||||
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"))
|
||||||
|
draw_rect(Rect2(-width * 0.5, 5.0, width, 3.0), Color("142a3d"))
|
||||||
|
draw_rect(Rect2(-width * 0.5, 5.0, width * clampf(enemy.shields / maxf(1.0, enemy.max_shields), 0.0, 1.0), 3.0), Color("67cfff"))
|
||||||
var color := _tier_color()
|
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)
|
||||||
|
|||||||
@ -49,5 +49,8 @@ func _hit_target(target: Variant) -> void:
|
|||||||
get_parent().add_child(effect)
|
get_parent().add_child(effect)
|
||||||
effect.call("configure", false, Color(1.0, 0.32, 0.38))
|
effect.call("configure", false, Color(1.0, 0.32, 0.38))
|
||||||
if target is Node and target.has_method("take_damage"):
|
if target is Node and target.has_method("take_damage"):
|
||||||
|
if target is CharacterBody2D:
|
||||||
|
target.call("take_damage", damage, false, DamageTypes.Type.ENERGY)
|
||||||
|
else:
|
||||||
target.call("take_damage", damage)
|
target.call("take_damage", damage)
|
||||||
queue_free()
|
queue_free()
|
||||||
|
|||||||
@ -2,7 +2,6 @@ extends CharacterBody2D
|
|||||||
|
|
||||||
const DAMAGE_POPUP_SCENE := preload("res://scenes/game/damage_popup.tscn")
|
const DAMAGE_POPUP_SCENE := preload("res://scenes/game/damage_popup.tscn")
|
||||||
const IMPACT_EFFECT_SCENE := preload("res://scenes/game/impact_effect.tscn")
|
const IMPACT_EFFECT_SCENE := preload("res://scenes/game/impact_effect.tscn")
|
||||||
const PLASMA_PROJECTILE_SCENE := preload("res://scenes/game/enemy_plasma_projectile.tscn")
|
|
||||||
const WEAPON_AUDIO_SCRIPT := preload("res://scripts/game/weapon_audio.gd")
|
const WEAPON_AUDIO_SCRIPT := preload("res://scripts/game/weapon_audio.gd")
|
||||||
const ITEM_GENERATOR := preload("res://scripts/items/item_generator.gd")
|
const ITEM_GENERATOR := preload("res://scripts/items/item_generator.gd")
|
||||||
const LOOT_PICKUP := preload("res://scripts/items/loot_pickup.gd")
|
const LOOT_PICKUP := preload("res://scripts/items/loot_pickup.gd")
|
||||||
@ -20,6 +19,12 @@ const LEVEL_BALANCE := preload("res://scripts/levels/level_balance.gd")
|
|||||||
@export var weapon_cooldown := 1.5
|
@export var weapon_cooldown := 1.5
|
||||||
@export var weapon_damage := 8.0
|
@export var weapon_damage := 8.0
|
||||||
|
|
||||||
|
var max_shields := 20.0
|
||||||
|
var shields := 20.0
|
||||||
|
var absorption := Vector3.ZERO
|
||||||
|
var weapon_kind := ItemDefinition.WeaponKind.PLASMA
|
||||||
|
var _shield_visual: Node2D
|
||||||
|
|
||||||
var hull := 50.0
|
var hull := 50.0
|
||||||
var _player: CharacterBody2D
|
var _player: CharacterBody2D
|
||||||
var _activated := false
|
var _activated := false
|
||||||
@ -29,6 +34,11 @@ var _weapon_timer := 0.0
|
|||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
hull = max_hull
|
hull = max_hull
|
||||||
_sprite = $Sprite2D
|
_sprite = $Sprite2D
|
||||||
|
_shield_visual = Node2D.new()
|
||||||
|
_shield_visual.set_script(preload("res://scripts/game/shield_visual.gd"))
|
||||||
|
_shield_visual.scale = Vector2.ONE * get_visual_size() / 64.0
|
||||||
|
add_child(_shield_visual)
|
||||||
|
_shield_visual.set_shield_values(shields, max_shields)
|
||||||
add_to_group("enemies")
|
add_to_group("enemies")
|
||||||
_weapon_timer = randf_range(0.2, weapon_cooldown)
|
_weapon_timer = randf_range(0.2, weapon_cooldown)
|
||||||
|
|
||||||
@ -42,6 +52,11 @@ func apply_level_settings(level: int, tier: int) -> void:
|
|||||||
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 * combat_multiplier
|
max_hull = 50.0 * power * hull_multiplier * combat_multiplier
|
||||||
hull = max_hull
|
hull = max_hull
|
||||||
|
max_shields = max_hull * 0.4
|
||||||
|
shields = max_shields
|
||||||
|
var profile := DamageTypes.enemy_profile(level, GameState.system_seed)
|
||||||
|
weapon_kind = int(profile["kind"])
|
||||||
|
absorption = profile["absorption"]
|
||||||
weapon_damage = 8.0 * power * damage_bonus * combat_multiplier
|
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
|
||||||
@ -50,6 +65,9 @@ func apply_level_settings(level: int, tier: int) -> void:
|
|||||||
weapon_range = 680.0 * boss_multiplier
|
weapon_range = 680.0 * boss_multiplier
|
||||||
weapon_cooldown = maxf(0.55, 1.5 / (1.0 + 0.001 * (clampi(level, 1, 1000) - 1))) / boss_multiplier
|
weapon_cooldown = maxf(0.55, 1.5 / (1.0 + 0.001 * (clampi(level, 1, 1000) - 1))) / boss_multiplier
|
||||||
$CollisionShape2D.scale = Vector2.ONE * boss_multiplier
|
$CollisionShape2D.scale = Vector2.ONE * boss_multiplier
|
||||||
|
if is_instance_valid(_shield_visual):
|
||||||
|
_shield_visual.scale = Vector2.ONE * get_visual_size() / 64.0
|
||||||
|
_shield_visual.set_shield_values(shields, max_shields)
|
||||||
|
|
||||||
func configure(player: CharacterBody2D, ship_texture: Texture2D) -> void:
|
func configure(player: CharacterBody2D, ship_texture: Texture2D) -> void:
|
||||||
_player = player
|
_player = player
|
||||||
@ -98,31 +116,63 @@ func _check_player_collision() -> void:
|
|||||||
_player.call("register_enemy_collision", global_position)
|
_player.call("register_enemy_collision", global_position)
|
||||||
|
|
||||||
func _fire_plasma(direction: Vector2) -> void:
|
func _fire_plasma(direction: Vector2) -> void:
|
||||||
var projectile := PLASMA_PROJECTILE_SCENE.instantiate() as Area2D
|
var muzzle := global_position + direction * (get_visual_size() * 0.5 + 8.0)
|
||||||
projectile.call("configure", global_position + direction * (get_visual_size() * 0.5), direction, weapon_damage)
|
if weapon_kind == ItemDefinition.WeaponKind.LASER:
|
||||||
|
var endpoint := muzzle + direction * weapon_range
|
||||||
|
var query := PhysicsRayQueryParameters2D.create(muzzle, endpoint, 3, [get_rid()])
|
||||||
|
var hit := get_world_2d().direct_space_state.intersect_ray(query)
|
||||||
|
if not hit.is_empty():
|
||||||
|
endpoint = hit["position"]
|
||||||
|
var target: Node = hit.get("collider")
|
||||||
|
if target is CharacterBody2D and target.has_method("take_damage"):
|
||||||
|
target.take_damage(weapon_damage, false, DamageTypes.Type.ENERGY)
|
||||||
|
var beam := Line2D.new()
|
||||||
|
beam.set_script(preload("res://scripts/game/laser_beam_visual.gd"))
|
||||||
|
beam.width = 4.0
|
||||||
|
beam.default_color = Color(1.0, 0.3, 0.3)
|
||||||
|
beam.add_point(muzzle)
|
||||||
|
beam.add_point(endpoint)
|
||||||
|
get_parent().add_child(beam)
|
||||||
|
get_tree().create_timer(0.1).timeout.connect(beam.queue_free)
|
||||||
|
else:
|
||||||
|
var projectile := preload("res://scenes/game/weapon_projectile.tscn").instantiate()
|
||||||
|
projectile.enemy_shot = true
|
||||||
|
projectile.collision_mask = 3
|
||||||
|
projectile.missile_homing = false
|
||||||
|
var speed := 430.0 if weapon_kind == ItemDefinition.WeaponKind.GAMMA else (950.0 if weapon_kind == ItemDefinition.WeaponKind.MACHINE_GUN else 600.0)
|
||||||
|
projectile.configure(muzzle, direction, weapon_kind, speed, weapon_damage, weapon_range)
|
||||||
get_parent().add_child(projectile)
|
get_parent().add_child(projectile)
|
||||||
# Relative to the Plasma bus: enemy shots are 4 dB quieter than player shots.
|
WEAPON_AUDIO_SCRIPT.play(get_parent(), global_position, weapon_kind, -4.0)
|
||||||
WEAPON_AUDIO_SCRIPT.play(get_parent(), global_position, ItemDefinition.WeaponKind.PLASMA, -4.0)
|
|
||||||
|
|
||||||
func take_weapon_hit(amount: float, critical: bool) -> void:
|
func take_weapon_hit(amount: float, critical: bool, damage_type: int = DamageTypes.Type.KINETIC) -> 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:
|
if is_queued_for_deletion() or hull <= 0.0:
|
||||||
return
|
return
|
||||||
hull = maxf(0.0, hull - maxf(0.0, amount))
|
var before := hull + shields
|
||||||
_show_damage_popup(amount, critical)
|
take_damage(amount, critical, damage_type)
|
||||||
|
if is_instance_valid(_player):
|
||||||
|
_player.call("restore_shields_from_damage", maxf(0.0, before - hull - shields))
|
||||||
|
|
||||||
|
func take_damage(amount: float, critical: bool = false, damage_type: int = DamageTypes.Type.KINETIC) -> void:
|
||||||
|
if is_queued_for_deletion() or hull <= 0.0:
|
||||||
|
return
|
||||||
|
var actual := maxf(0.0, amount) * (1.0 - absorption[damage_type] / 100.0)
|
||||||
|
var shield_damage := minf(shields, actual)
|
||||||
|
shields -= shield_damage
|
||||||
|
hull = maxf(0.0, hull - actual + shield_damage)
|
||||||
|
if is_instance_valid(_shield_visual):
|
||||||
|
_shield_visual.set_shield_values(shields, max_shields)
|
||||||
|
if shield_damage > 0.0:
|
||||||
|
_shield_visual.flash_hit()
|
||||||
|
_show_damage_popup(actual, critical)
|
||||||
if hull <= 0.0:
|
if hull <= 0.0:
|
||||||
_reward_captain()
|
_reward_captain()
|
||||||
_drop_loot()
|
_drop_loot()
|
||||||
_spawn_destruction_effect()
|
_spawn_destruction_effect()
|
||||||
queue_free()
|
queue_free()
|
||||||
|
|
||||||
|
func get_weapon_dps() -> float:
|
||||||
|
return weapon_damage / maxf(0.001, weapon_cooldown) * (1.0 + 0.25 * minf(3.0, weapon_cooldown) if weapon_kind == ItemDefinition.WeaponKind.GAMMA else 1.0)
|
||||||
|
|
||||||
func _reward_captain() -> void:
|
func _reward_captain() -> void:
|
||||||
if is_instance_valid(_player):
|
if is_instance_valid(_player):
|
||||||
var reward := GameState.captain.reward_enemy(GameState.system_level, loot_tier, float(_player.get("experience_multiplier")), float(_player.get("credits_multiplier")), false)
|
var reward := GameState.captain.reward_enemy(GameState.system_level, loot_tier, float(_player.get("experience_multiplier")), float(_player.get("credits_multiplier")), false)
|
||||||
|
|||||||
@ -40,6 +40,10 @@ func _ready() -> void:
|
|||||||
# The display font has unusually tall vertical metrics; keep debug rows compact.
|
# The display font has unusually tall vertical metrics; keep debug rows compact.
|
||||||
debug_info.add_theme_constant_override("line_spacing", mini(0, 13 - ceili(debug_info.get_theme_font("font").get_height(10))))
|
debug_info.add_theme_constant_override("line_spacing", mini(0, 13 - ceili(debug_info.get_theme_font("font").get_height(10))))
|
||||||
_create_sidebar()
|
_create_sidebar()
|
||||||
|
var enemy_panel := Control.new()
|
||||||
|
enemy_panel.name = "EnemyDrawer"
|
||||||
|
enemy_panel.set_script(preload("res://scripts/ui/enemy_drawer.gd"))
|
||||||
|
$HUDRoot.add_child(enemy_panel)
|
||||||
_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
|
||||||
@ -154,7 +158,7 @@ func _layout_sidebar() -> void:
|
|||||||
_sidebar.scale = Vector2.ONE * EQUIPMENT_UI_SCALE
|
_sidebar.scale = Vector2.ONE * EQUIPMENT_UI_SCALE
|
||||||
_sidebar.size = Vector2(maxf(SIDEBAR_WIDTH, _sidebar.get_combined_minimum_size().x), minf(_sidebar.get_node("Scroll/Contents").get_combined_minimum_size().y + 16.0, maxf(100.0, ($HUDRoot.size.y - 64.0) / EQUIPMENT_UI_SCALE)))
|
_sidebar.size = Vector2(maxf(SIDEBAR_WIDTH, _sidebar.get_combined_minimum_size().x), minf(_sidebar.get_node("Scroll/Contents").get_combined_minimum_size().y + 16.0, maxf(100.0, ($HUDRoot.size.y - 64.0) / EQUIPMENT_UI_SCALE)))
|
||||||
_cockpit.scale = Vector2.ONE * 0.85
|
_cockpit.scale = Vector2.ONE * 0.85
|
||||||
_cockpit.size = Vector2(minf(640.0, ($HUDRoot.size.x - 24) / 0.85), 126)
|
_cockpit.size = Vector2(minf(640.0, ($HUDRoot.size.x - 24) / 0.85), 170)
|
||||||
_cockpit.position = Vector2(($HUDRoot.size.x - _cockpit.size.x * 0.85) * 0.5, $HUDRoot.size.y - _cockpit.size.y * 0.85 - 12)
|
_cockpit.position = Vector2(($HUDRoot.size.x - _cockpit.size.x * 0.85) * 0.5, $HUDRoot.size.y - _cockpit.size.y * 0.85 - 12)
|
||||||
_layout_debug()
|
_layout_debug()
|
||||||
_sidebar.position = Vector2($HUDRoot.size.x - _sidebar.size.x * EQUIPMENT_UI_SCALE - 8 if _sidebar_open else $HUDRoot.size.x, 44)
|
_sidebar.position = Vector2($HUDRoot.size.x - _sidebar.size.x * EQUIPMENT_UI_SCALE - 8 if _sidebar_open else $HUDRoot.size.x, 44)
|
||||||
|
|||||||
43
scripts/game/radiation_status.gd
Normal file
43
scripts/game/radiation_status.gd
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
var remaining := 0.0
|
||||||
|
var tick_damage := 0.0
|
||||||
|
var credited_to_player := false
|
||||||
|
var _tick_elapsed := 0.0
|
||||||
|
var _base_color := Color.WHITE
|
||||||
|
var _sprite: Sprite2D
|
||||||
|
|
||||||
|
static func apply(target: Node, damage_per_second: float, player_hit: bool) -> void:
|
||||||
|
if not target.has_method("take_damage") or not target is CharacterBody2D:
|
||||||
|
return
|
||||||
|
var status := target.get_node_or_null("RadiationStatus")
|
||||||
|
if status == null:
|
||||||
|
status = Node.new()
|
||||||
|
status.set_script(load("res://scripts/game/radiation_status.gd"))
|
||||||
|
status.name = "RadiationStatus"
|
||||||
|
target.add_child(status)
|
||||||
|
status.remaining = 3.0
|
||||||
|
status.tick_damage = maxf(status.tick_damage, damage_per_second)
|
||||||
|
status.credited_to_player = player_hit
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
_sprite = get_parent().get_node_or_null("Sprite2D")
|
||||||
|
if _sprite != null:
|
||||||
|
_base_color = _sprite.modulate
|
||||||
|
|
||||||
|
func _physics_process(delta: float) -> void:
|
||||||
|
var step := minf(delta, remaining)
|
||||||
|
remaining = maxf(0.0, remaining - delta)
|
||||||
|
var target := get_parent()
|
||||||
|
if _sprite != null:
|
||||||
|
_sprite.modulate = Color(0.3, 1.0, 0.35) if remaining > 0.0 else _base_color
|
||||||
|
_tick_elapsed += step
|
||||||
|
if _tick_elapsed >= 0.5 or (remaining <= 0.0 and _tick_elapsed > 0.0):
|
||||||
|
step = _tick_elapsed
|
||||||
|
_tick_elapsed = 0.0
|
||||||
|
if credited_to_player and target.has_method("take_weapon_hit"):
|
||||||
|
target.take_weapon_hit(tick_damage * step, false, DamageTypes.Type.RADIATION)
|
||||||
|
else:
|
||||||
|
target.take_damage(tick_damage * step, false, DamageTypes.Type.RADIATION)
|
||||||
|
if remaining <= 0.0:
|
||||||
|
tick_damage = 0.0
|
||||||
1
scripts/game/radiation_status.gd.uid
Normal file
1
scripts/game/radiation_status.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://drtwspjwle1py
|
||||||
@ -35,6 +35,7 @@ var shields := 100.0
|
|||||||
var energy := 100.0
|
var energy := 100.0
|
||||||
var energy_regeneration := 5.0
|
var energy_regeneration := 5.0
|
||||||
var shield_regeneration := 0.0
|
var shield_regeneration := 0.0
|
||||||
|
var absorption := Vector3.ZERO
|
||||||
var damage_multiplier := 1.0
|
var damage_multiplier := 1.0
|
||||||
var experience_multiplier := 1.0
|
var experience_multiplier := 1.0
|
||||||
var credits_multiplier := 1.0
|
var credits_multiplier := 1.0
|
||||||
@ -69,6 +70,7 @@ func _enter_tree() -> void:
|
|||||||
base_max_speed = float(chassis["speed"])
|
base_max_speed = float(chassis["speed"])
|
||||||
|
|
||||||
func apply_equipment_bonuses(bonuses: Dictionary) -> void:
|
func apply_equipment_bonuses(bonuses: Dictionary) -> void:
|
||||||
|
absorption = DamageTypes.capped(Vector3(float(bonuses.get("kinetic_absorption", 0.0)), float(bonuses.get("energy_absorption", 0.0)), float(bonuses.get("radiation_absorption", 0.0))))
|
||||||
max_hull = (base_max_hull + float(bonuses.get("max_hull", 0.0))) * (1.0 + float(bonuses.get("hull_percent", 0.0)) / 100.0)
|
max_hull = (base_max_hull + float(bonuses.get("max_hull", 0.0))) * (1.0 + float(bonuses.get("hull_percent", 0.0)) / 100.0)
|
||||||
max_shields = (base_max_shields + float(bonuses.get("max_shields", 0.0))) * (1.0 + float(bonuses.get("shield_percent", 0.0)) / 100.0)
|
max_shields = (base_max_shields + float(bonuses.get("max_shields", 0.0))) * (1.0 + float(bonuses.get("shield_percent", 0.0)) / 100.0)
|
||||||
scanner_range = base_scanner_range + float(bonuses.get("scanner_range", 0.0))
|
scanner_range = base_scanner_range + float(bonuses.get("scanner_range", 0.0))
|
||||||
@ -134,10 +136,10 @@ func _ensure_input_action(action_name: StringName, physical_key: Key) -> void:
|
|||||||
if not InputMap.action_has_event(action_name, event):
|
if not InputMap.action_has_event(action_name, event):
|
||||||
InputMap.action_add_event(action_name, event)
|
InputMap.action_add_event(action_name, event)
|
||||||
|
|
||||||
func take_damage(amount: float) -> void:
|
func take_damage(amount: float, _critical: bool = false, damage_type: int = DamageTypes.Type.KINETIC) -> void:
|
||||||
if _is_destroyed or is_docked or amount <= 0.0:
|
if _is_destroyed or is_docked or amount <= 0.0:
|
||||||
return
|
return
|
||||||
var hit := COMBAT_ROLLS.incoming_hit(amount, get_evasion_chance(), get_damage_reduction())
|
var hit := COMBAT_ROLLS.incoming_hit(amount * (1.0 - absorption[damage_type] / 100.0), get_evasion_chance(), get_damage_reduction())
|
||||||
if hit["evaded"]:
|
if hit["evaded"]:
|
||||||
var flash := Node2D.new()
|
var flash := Node2D.new()
|
||||||
flash.set_script(COMBAT_FLASH)
|
flash.set_script(COMBAT_FLASH)
|
||||||
|
|||||||
@ -2,6 +2,7 @@ extends RefCounted
|
|||||||
class_name WeaponAudio
|
class_name WeaponAudio
|
||||||
|
|
||||||
const SOUND_PATHS := {
|
const SOUND_PATHS := {
|
||||||
|
ItemDefinition.WeaponKind.GAMMA: "res://assets/sounds/effects/plasma.mp3",
|
||||||
ItemDefinition.WeaponKind.PLASMA: "res://assets/sounds/effects/plasma.mp3",
|
ItemDefinition.WeaponKind.PLASMA: "res://assets/sounds/effects/plasma.mp3",
|
||||||
ItemDefinition.WeaponKind.MACHINE_GUN: "res://assets/sounds/effects/machine_gun.mp3",
|
ItemDefinition.WeaponKind.MACHINE_GUN: "res://assets/sounds/effects/machine_gun.mp3",
|
||||||
ItemDefinition.WeaponKind.LASER: "res://assets/sounds/effects/laser.mp3",
|
ItemDefinition.WeaponKind.LASER: "res://assets/sounds/effects/laser.mp3",
|
||||||
@ -11,6 +12,7 @@ const SOUND_PATHS := {
|
|||||||
static var _streams: Dictionary = {}
|
static var _streams: Dictionary = {}
|
||||||
|
|
||||||
const BUS_NAMES := {
|
const BUS_NAMES := {
|
||||||
|
ItemDefinition.WeaponKind.GAMMA: &"Plasma",
|
||||||
ItemDefinition.WeaponKind.PLASMA: &"Plasma",
|
ItemDefinition.WeaponKind.PLASMA: &"Plasma",
|
||||||
ItemDefinition.WeaponKind.MACHINE_GUN: &"MachineGun",
|
ItemDefinition.WeaponKind.MACHINE_GUN: &"MachineGun",
|
||||||
ItemDefinition.WeaponKind.LASER: &"Laser",
|
ItemDefinition.WeaponKind.LASER: &"Laser",
|
||||||
|
|||||||
@ -205,6 +205,8 @@ func _muzzle_position() -> Vector2:
|
|||||||
func _consume_shot_resource(item: ItemInstance) -> bool:
|
func _consume_shot_resource(item: ItemInstance) -> bool:
|
||||||
if item.definition.weapon_kind == ItemDefinition.WeaponKind.MACHINE_GUN:
|
if item.definition.weapon_kind == ItemDefinition.WeaponKind.MACHINE_GUN:
|
||||||
return inventory.consume_ammunition()
|
return inventory.consume_ammunition()
|
||||||
|
if item.definition.weapon_kind == ItemDefinition.WeaponKind.GAMMA:
|
||||||
|
return inventory.consume_irradiated_ammunition()
|
||||||
return _consume_energy(item.get_energy_cost())
|
return _consume_energy(item.get_energy_cost())
|
||||||
|
|
||||||
func _consume_energy(amount: float) -> bool:
|
func _consume_energy(amount: float) -> bool:
|
||||||
@ -216,7 +218,7 @@ func _consume_energy(amount: float) -> bool:
|
|||||||
|
|
||||||
func _apply_damage(target: Variant, damage: float, critical: bool = false) -> void:
|
func _apply_damage(target: Variant, damage: float, critical: bool = false) -> void:
|
||||||
if target is Node and target.has_method("take_weapon_hit"):
|
if target is Node and target.has_method("take_weapon_hit"):
|
||||||
target.call("take_weapon_hit", damage, critical)
|
target.call("take_weapon_hit", damage, critical, DamageTypes.Type.ENERGY)
|
||||||
elif target is Node and target.has_method("take_damage"):
|
elif target is Node and target.has_method("take_damage"):
|
||||||
target.call("take_damage", damage)
|
target.call("take_damage", damage)
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ const IMPACT_EFFECT_SCENE := preload("res://scenes/game/impact_effect.tscn")
|
|||||||
const PLASMA_TEXTURE := preload("res://assets/other/bullet.png")
|
const PLASMA_TEXTURE := preload("res://assets/other/bullet.png")
|
||||||
const PLASMA_VISUAL_WIDTH := 24.0
|
const PLASMA_VISUAL_WIDTH := 24.0
|
||||||
|
|
||||||
|
var enemy_shot := false
|
||||||
var direction := Vector2.UP
|
var direction := Vector2.UP
|
||||||
var projectile_speed := 600.0
|
var projectile_speed := 600.0
|
||||||
var damage := 1.0
|
var damage := 1.0
|
||||||
@ -124,6 +125,9 @@ func _physics_process(delta: float) -> void:
|
|||||||
queue_free()
|
queue_free()
|
||||||
|
|
||||||
func _process(delta: float) -> void:
|
func _process(delta: float) -> void:
|
||||||
|
if weapon_kind == ItemDefinition.WeaponKind.GAMMA:
|
||||||
|
_engine_time += delta
|
||||||
|
queue_redraw()
|
||||||
if weapon_kind == ItemDefinition.WeaponKind.MISSILE:
|
if weapon_kind == ItemDefinition.WeaponKind.MISSILE:
|
||||||
_engine_time += delta
|
_engine_time += delta
|
||||||
_smoke_emission_timer += delta
|
_smoke_emission_timer += delta
|
||||||
@ -157,6 +161,8 @@ func _update_smoke_puffs(delta: float) -> void:
|
|||||||
func _find_enemy_in_acquisition_range() -> Node2D:
|
func _find_enemy_in_acquisition_range() -> Node2D:
|
||||||
var nearest_enemy: Node2D
|
var nearest_enemy: Node2D
|
||||||
var nearest_distance_squared := target_acquisition_range * target_acquisition_range
|
var nearest_distance_squared := target_acquisition_range * target_acquisition_range
|
||||||
|
if enemy_shot:
|
||||||
|
return null
|
||||||
for candidate in get_tree().get_nodes_in_group("enemies"):
|
for candidate in get_tree().get_nodes_in_group("enemies"):
|
||||||
if not candidate is Node2D or candidate.is_queued_for_deletion():
|
if not candidate is Node2D or candidate.is_queued_for_deletion():
|
||||||
continue
|
continue
|
||||||
@ -170,6 +176,15 @@ func _draw() -> void:
|
|||||||
if weapon_kind == ItemDefinition.WeaponKind.MISSILE:
|
if weapon_kind == ItemDefinition.WeaponKind.MISSILE:
|
||||||
_draw_missile_smoke()
|
_draw_missile_smoke()
|
||||||
match weapon_kind:
|
match weapon_kind:
|
||||||
|
ItemDefinition.WeaponKind.GAMMA:
|
||||||
|
var radius := 9.0 + sin(_engine_time * 9.0) * 2.0
|
||||||
|
draw_circle(Vector2.ZERO, radius * 1.8, Color(0.3, 1.0, 0.1, 0.15))
|
||||||
|
draw_circle(Vector2.ZERO, radius, Color(0.35, 0.95, 0.15, 0.8))
|
||||||
|
draw_circle(Vector2.ZERO, radius * 0.5, Color(0.86, 1.0, 0.6))
|
||||||
|
for index in 8:
|
||||||
|
var angle := _engine_time * 5.0 + index * TAU / 8.0
|
||||||
|
var orbit := Vector2(cos(angle), sin(angle) * 0.65) * (radius + 6.0)
|
||||||
|
draw_circle(orbit, 2.0, Color(0.7, 1.0, 0.4))
|
||||||
ItemDefinition.WeaponKind.PLASMA:
|
ItemDefinition.WeaponKind.PLASMA:
|
||||||
var texture_size := PLASMA_TEXTURE.get_size() * (PLASMA_VISUAL_WIDTH / PLASMA_TEXTURE.get_width())
|
var texture_size := PLASMA_TEXTURE.get_size() * (PLASMA_VISUAL_WIDTH / PLASMA_TEXTURE.get_width())
|
||||||
# The source image points left; projectiles travel along their local +X.
|
# The source image points left; projectiles travel along their local +X.
|
||||||
@ -249,6 +264,9 @@ func _spawn_impact_effect(is_explosion: bool) -> void:
|
|||||||
effect.global_position = global_position
|
effect.global_position = global_position
|
||||||
get_parent().add_child(effect)
|
get_parent().add_child(effect)
|
||||||
var effect_color := Color(1.0, 0.48, 0.22) if is_explosion else Color(1.0, 0.78, 0.38)
|
var effect_color := Color(1.0, 0.48, 0.22) if is_explosion else Color(1.0, 0.78, 0.38)
|
||||||
|
if weapon_kind == ItemDefinition.WeaponKind.GAMMA:
|
||||||
|
effect_color = Color(0.4, 1.0, 0.15)
|
||||||
|
is_explosion = true
|
||||||
effect.call("configure", is_explosion, effect_color)
|
effect.call("configure", is_explosion, effect_color)
|
||||||
|
|
||||||
func _explode_at_target(impact_target: Node) -> void:
|
func _explode_at_target(impact_target: Node) -> void:
|
||||||
@ -258,7 +276,7 @@ func _explode_at_target(impact_target: Node) -> void:
|
|||||||
var query := PhysicsShapeQueryParameters2D.new()
|
var query := PhysicsShapeQueryParameters2D.new()
|
||||||
query.shape = explosion_shape
|
query.shape = explosion_shape
|
||||||
query.transform = Transform2D(0.0, global_position)
|
query.transform = Transform2D(0.0, global_position)
|
||||||
query.collision_mask = 4 # Enemy physics layer 3.
|
query.collision_mask = 2 if enemy_shot else 4
|
||||||
for result in get_world_2d().direct_space_state.intersect_shape(query, 32):
|
for result in get_world_2d().direct_space_state.intersect_shape(query, 32):
|
||||||
_apply_damage_once(result.get("collider"))
|
_apply_damage_once(result.get("collider"))
|
||||||
|
|
||||||
@ -270,6 +288,11 @@ func _apply_damage_once(target: Variant) -> void:
|
|||||||
return
|
return
|
||||||
_already_hit[instance_id] = true
|
_already_hit[instance_id] = true
|
||||||
if target.has_method("take_weapon_hit"):
|
if target.has_method("take_weapon_hit"):
|
||||||
target.call("take_weapon_hit", damage, is_critical)
|
target.call("take_weapon_hit", damage, is_critical, DamageTypes.for_weapon(weapon_kind))
|
||||||
elif target.has_method("take_damage"):
|
elif target.has_method("take_damage"):
|
||||||
|
if target is CharacterBody2D:
|
||||||
|
target.call("take_damage", damage, is_critical, DamageTypes.for_weapon(weapon_kind))
|
||||||
|
else:
|
||||||
target.call("take_damage", damage)
|
target.call("take_damage", damage)
|
||||||
|
if weapon_kind == ItemDefinition.WeaponKind.GAMMA:
|
||||||
|
preload("res://scripts/game/radiation_status.gd").apply(target, damage * 0.25, not enemy_shot)
|
||||||
|
|||||||
@ -2,7 +2,10 @@ extends Resource
|
|||||||
class_name ItemDefinition
|
class_name ItemDefinition
|
||||||
|
|
||||||
enum Category { WEAPON, SHIELD, ARMOR, REACTOR, ENGINE, ACTIVE_EXTENSION, COMPUTER }
|
enum Category { WEAPON, SHIELD, ARMOR, REACTOR, ENGINE, ACTIVE_EXTENSION, COMPUTER }
|
||||||
enum WeaponKind { PLASMA, MACHINE_GUN, LASER, MISSILE }
|
enum WeaponKind { PLASMA, MACHINE_GUN, LASER, MISSILE, GAMMA }
|
||||||
|
|
||||||
|
func get_damage_type() -> int:
|
||||||
|
return DamageTypes.for_weapon(weapon_kind)
|
||||||
|
|
||||||
@export var item_id: String = ""
|
@export var item_id: String = ""
|
||||||
@export var display_name: String = ""
|
@export var display_name: String = ""
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
extends RefCounted
|
extends RefCounted
|
||||||
|
|
||||||
const DEFINITIONS := [
|
const DEFINITIONS := [
|
||||||
|
preload("res://resources/items/gamma_cannon.tres"),
|
||||||
preload("res://resources/items/plasma_cannon.tres"),
|
preload("res://resources/items/plasma_cannon.tres"),
|
||||||
preload("res://resources/items/beam_laser.tres"),
|
preload("res://resources/items/beam_laser.tres"),
|
||||||
preload("res://resources/items/machine_gun.tres"),
|
preload("res://resources/items/machine_gun.tres"),
|
||||||
|
|||||||
@ -55,7 +55,7 @@ func roll_damage() -> float:
|
|||||||
func get_energy_cost() -> float:
|
func get_energy_cost() -> float:
|
||||||
if definition == null or definition.category != ItemDefinition.Category.WEAPON:
|
if definition == null or definition.category != ItemDefinition.Category.WEAPON:
|
||||||
return 0.0
|
return 0.0
|
||||||
if definition.weapon_kind in [ItemDefinition.WeaponKind.MACHINE_GUN, ItemDefinition.WeaponKind.MISSILE]:
|
if definition.weapon_kind in [ItemDefinition.WeaponKind.MACHINE_GUN, ItemDefinition.WeaponKind.MISSILE, ItemDefinition.WeaponKind.GAMMA]:
|
||||||
return 0.0
|
return 0.0
|
||||||
# Compare this weapon with its starter damage. Captain/global gear buffs are free.
|
# Compare this weapon with its starter damage. Captain/global gear buffs are free.
|
||||||
var base_lower := roundf((definition.damage_lower_roll_min + definition.damage_lower_roll_max) * 0.5)
|
var base_lower := roundf((definition.damage_lower_roll_min + definition.damage_lower_roll_max) * 0.5)
|
||||||
@ -112,9 +112,12 @@ func get_tooltip_text() -> String:
|
|||||||
if definition.energy_regeneration_bonus > 0.0:
|
if definition.energy_regeneration_bonus > 0.0:
|
||||||
lines.append("Regeneracja generatora: +%.1f /s" % ((energy_regeneration_override if energy_regeneration_override >= 0.0 else definition.energy_regeneration_bonus) * ENERGY_REGENERATION_BONUS_MULTIPLIER))
|
lines.append("Regeneracja generatora: +%.1f /s" % ((energy_regeneration_override if energy_regeneration_override >= 0.0 else definition.energy_regeneration_bonus) * ENERGY_REGENERATION_BONUS_MULTIPLIER))
|
||||||
if definition.category == ItemDefinition.Category.WEAPON:
|
if definition.category == ItemDefinition.Category.WEAPON:
|
||||||
|
lines.append("Typ obrażeń: %s" % DamageTypes.NAMES[definition.get_damage_type()])
|
||||||
lines.append("Każdy strzał / impuls losuje obrażenia z tego zakresu.")
|
lines.append("Każdy strzał / impuls losuje obrażenia z tego zakresu.")
|
||||||
if definition.weapon_kind == ItemDefinition.WeaponKind.MACHINE_GUN:
|
if definition.weapon_kind == ItemDefinition.WeaponKind.MACHINE_GUN:
|
||||||
lines.append("Zasięg: %.0f px • Amunicja: 1 / strzał" % definition.weapon_range)
|
lines.append("Zasięg: %.0f px • Amunicja: 1 / strzał" % definition.weapon_range)
|
||||||
|
elif definition.weapon_kind == ItemDefinition.WeaponKind.GAMMA:
|
||||||
|
lines.append("Zasięg: %.0f px • Napromieniowana amunicja: 1 / strzał" % definition.weapon_range)
|
||||||
elif definition.weapon_kind == ItemDefinition.WeaponKind.MISSILE:
|
elif definition.weapon_kind == ItemDefinition.WeaponKind.MISSILE:
|
||||||
lines.append("Amunicja: 1 rakieta / strzał • Cooldown bazowy: 5 s • Bez kosztu energii")
|
lines.append("Amunicja: 1 rakieta / strzał • Cooldown bazowy: 5 s • Bez kosztu energii")
|
||||||
else:
|
else:
|
||||||
@ -129,4 +132,4 @@ func get_tooltip_text() -> String:
|
|||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
static func stat_name(key: String) -> String:
|
static func stat_name(key: String) -> String:
|
||||||
return str({"missile_cooldown_reduction": "Skrócenie cooldownu rakiet", "scanner_range": "Zasięg skanera", "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))
|
return str({"kinetic_absorption": "Absorpcja kinetyczna", "energy_absorption": "Absorpcja energetyczna", "radiation_absorption": "Absorpcja radiacyjna", "missile_cooldown_reduction": "Skrócenie cooldownu rakiet", "scanner_range": "Zasięg skanera", "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))
|
||||||
|
|||||||
@ -20,6 +20,8 @@ const AMMUNITION_CRAFT_PARTS_COST := 50.0
|
|||||||
const AMMUNITION_BUY_AMOUNT := 100
|
const AMMUNITION_BUY_AMOUNT := 100
|
||||||
const AMMUNITION_BUY_BASE_COST := 20
|
const AMMUNITION_BUY_BASE_COST := 20
|
||||||
|
|
||||||
|
var irradiated_ammunition := 0
|
||||||
|
|
||||||
var ammunition: int = STARTING_AMMUNITION
|
var ammunition: int = STARTING_AMMUNITION
|
||||||
var missile_ammunition: int = STARTING_MISSILE_AMMUNITION
|
var missile_ammunition: int = STARTING_MISSILE_AMMUNITION
|
||||||
|
|
||||||
@ -45,6 +47,7 @@ func _ready() -> void:
|
|||||||
_restore_modules(GameState.restart_inventory)
|
_restore_modules(GameState.restart_inventory)
|
||||||
ammunition = int(GameState.restart_inventory.get("ammunition", STARTING_AMMUNITION))
|
ammunition = int(GameState.restart_inventory.get("ammunition", STARTING_AMMUNITION))
|
||||||
missile_ammunition = int(GameState.restart_inventory.get("missile_ammunition", STARTING_MISSILE_AMMUNITION))
|
missile_ammunition = int(GameState.restart_inventory.get("missile_ammunition", STARTING_MISSILE_AMMUNITION))
|
||||||
|
irradiated_ammunition = int(GameState.restart_inventory.get("irradiated_ammunition", 0))
|
||||||
GameState.restart_inventory.clear()
|
GameState.restart_inventory.clear()
|
||||||
_remove_extension_addons()
|
_remove_extension_addons()
|
||||||
_recalculate_ship_stats()
|
_recalculate_ship_stats()
|
||||||
@ -64,6 +67,7 @@ func preserve_for_restart() -> void:
|
|||||||
"weapon": weapon_slots.duplicate(),
|
"weapon": weapon_slots.duplicate(),
|
||||||
"module": module_slots.duplicate(),
|
"module": module_slots.duplicate(),
|
||||||
"extension": extension_slots.duplicate(),
|
"extension": extension_slots.duplicate(),
|
||||||
|
"irradiated_ammunition": irradiated_ammunition,
|
||||||
"ammunition": ammunition,
|
"ammunition": ammunition,
|
||||||
"missile_ammunition": missile_ammunition,
|
"missile_ammunition": missile_ammunition,
|
||||||
}
|
}
|
||||||
@ -231,7 +235,7 @@ func _recalculate_ship_stats() -> void:
|
|||||||
for item in weapon_slots + module_slots:
|
for item in weapon_slots + module_slots:
|
||||||
if item == null or item.definition == null:
|
if item == null or item.definition == null:
|
||||||
continue
|
continue
|
||||||
for stat_key in ["shield_on_damage_percent", "scanner_range", "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 ["kinetic_absorption", "energy_absorption", "radiation_absorption", "shield_on_damage_percent", "scanner_range", "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"]:
|
||||||
bonuses[stat_key] = float(bonuses.get(stat_key, 0.0)) + item.get_stat_total(stat_key)
|
bonuses[stat_key] = float(bonuses.get(stat_key, 0.0)) + item.get_stat_total(stat_key)
|
||||||
ship.call("apply_equipment_bonuses", bonuses)
|
ship.call("apply_equipment_bonuses", bonuses)
|
||||||
|
|
||||||
@ -288,7 +292,7 @@ func _comparison_bonuses(incoming: ItemInstance = null, outgoing: ItemInstance =
|
|||||||
return bonuses
|
return bonuses
|
||||||
|
|
||||||
func _add_comparison_bonuses(bonuses: Dictionary, item: ItemInstance) -> void:
|
func _add_comparison_bonuses(bonuses: Dictionary, item: ItemInstance) -> void:
|
||||||
for key in ["max_hull", "max_shields", "max_energy", "max_speed", "scanner_range", "hull_percent", "shield_percent", "speed_percent", "damage_percent", "critical_damage_percent", "critical_chance_percent", "shield_on_damage_percent", "energy_regeneration", "shield_regeneration", "missile_cooldown_reduction", "experience_percent", "credits_percent"]:
|
for key in ["kinetic_absorption", "energy_absorption", "radiation_absorption", "max_hull", "max_shields", "max_energy", "max_speed", "scanner_range", "hull_percent", "shield_percent", "speed_percent", "damage_percent", "critical_damage_percent", "critical_chance_percent", "shield_on_damage_percent", "energy_regeneration", "shield_regeneration", "missile_cooldown_reduction", "experience_percent", "credits_percent"]:
|
||||||
bonuses[key] = float(bonuses.get(key, 0.0)) + item.get_stat_total(key)
|
bonuses[key] = float(bonuses.get(key, 0.0)) + item.get_stat_total(key)
|
||||||
|
|
||||||
func _comparison_stats(bonuses: Dictionary) -> Dictionary:
|
func _comparison_stats(bonuses: Dictionary) -> Dictionary:
|
||||||
@ -299,9 +303,12 @@ func _comparison_stats(bonuses: Dictionary) -> Dictionary:
|
|||||||
for data in [["max_energy", "base_max_energy", "Energia", "pkt"], ["scanner_range", "base_scanner_range", "Zasięg skanera", "px"], ["energy_regeneration", "base_energy_regeneration", "Regeneracja energii", "pkt/s"], ["shield_regeneration", "base_shield_regeneration", "Regeneracja tarcz", "pkt/s"]]:
|
for data in [["max_energy", "base_max_energy", "Energia", "pkt"], ["scanner_range", "base_scanner_range", "Zasięg skanera", "px"], ["energy_regeneration", "base_energy_regeneration", "Regeneracja energii", "pkt/s"], ["shield_regeneration", "base_shield_regeneration", "Regeneracja tarcz", "pkt/s"]]:
|
||||||
stats[data[0]] = {"value": float(ship.get(data[1])) + float(bonuses.get(data[0], 0.0)), "name": data[2], "unit": data[3]}
|
stats[data[0]] = {"value": float(ship.get(data[1])) + float(bonuses.get(data[0], 0.0)), "name": data[2], "unit": data[3]}
|
||||||
# These stats are bonuses in their own right, not rewards or hit scenarios.
|
# These stats are bonuses in their own right, not rewards or hit scenarios.
|
||||||
for data in [["damage_percent", "Obrażenia broni"], ["critical_damage_percent", "Obrażenia krytyczne"], ["critical_chance_percent", "Szansa na krytyk"], ["shield_on_damage_percent", "Odzysk tarcz z obrażeń"], ["experience_percent", "Doświadczenie"], ["credits_percent", "Kredyty"]]:
|
for data in [["kinetic_absorption", "Absorpcja kinetyczna"], ["energy_absorption", "Absorpcja energetyczna"], ["radiation_absorption", "Absorpcja radiacyjna"], ["damage_percent", "Obrażenia broni"], ["critical_damage_percent", "Obrażenia krytyczne"], ["critical_chance_percent", "Szansa na krytyk"], ["shield_on_damage_percent", "Odzysk tarcz z obrażeń"], ["experience_percent", "Doświadczenie"], ["credits_percent", "Kredyty"]]:
|
||||||
stats[data[0]] = {"value": float(bonuses.get(data[0], 0.0)), "name": data[1], "unit": "%"}
|
stats[data[0]] = {"value": float(bonuses.get(data[0], 0.0)), "name": data[1], "unit": "%"}
|
||||||
stats["missile_cooldown_reduction"] = {"value": float(bonuses.get("missile_cooldown_reduction", 0.0)), "name": "Skrócenie cooldownu rakiet", "unit": "s"}
|
stats["missile_cooldown_reduction"] = {"value": float(bonuses.get("missile_cooldown_reduction", 0.0)), "name": "Skrócenie cooldownu rakiet", "unit": "s"}
|
||||||
|
var absorption_values := DamageTypes.capped(Vector3(float(bonuses.get("kinetic_absorption", 0.0)), float(bonuses.get("energy_absorption", 0.0)), float(bonuses.get("radiation_absorption", 0.0))))
|
||||||
|
for index in 3:
|
||||||
|
stats[DamageTypes.KEYS[index]]["value"] = absorption_values[index]
|
||||||
return stats
|
return stats
|
||||||
|
|
||||||
func get_weapon_comparison(item: ItemInstance, slot_index: int) -> Dictionary:
|
func get_weapon_comparison(item: ItemInstance, slot_index: int) -> Dictionary:
|
||||||
@ -334,3 +341,45 @@ func _weapon_dps(item: ItemInstance, bonuses: Dictionary) -> float:
|
|||||||
if item.definition.weapon_kind == ItemDefinition.WeaponKind.MISSILE:
|
if item.definition.weapon_kind == ItemDefinition.WeaponKind.MISSILE:
|
||||||
interval = maxf(1.0, 5.0 - float(bonuses.get("missile_cooldown_reduction", 0.0)))
|
interval = maxf(1.0, 5.0 - float(bonuses.get("missile_cooldown_reduction", 0.0)))
|
||||||
return item.get_stat_total("damage") * damage_factor * (1.0 + chance * (critical_factor - 1.0)) / maxf(0.001, interval)
|
return item.get_stat_total("damage") * damage_factor * (1.0 + chance * (critical_factor - 1.0)) / maxf(0.001, interval)
|
||||||
|
|
||||||
|
func consume_irradiated_ammunition() -> bool:
|
||||||
|
if irradiated_ammunition <= 0:
|
||||||
|
return false
|
||||||
|
irradiated_ammunition -= 1
|
||||||
|
ammunition_changed.emit()
|
||||||
|
return true
|
||||||
|
|
||||||
|
func can_craft_irradiated_ammunition() -> bool:
|
||||||
|
return int(GameState.captain.minerals.get("uranium", 0)) >= 1 and GameState.captain.parts >= 10.0
|
||||||
|
|
||||||
|
func craft_irradiated_ammunition() -> bool:
|
||||||
|
if not can_craft_irradiated_ammunition():
|
||||||
|
return false
|
||||||
|
GameState.captain.minerals["uranium"] -= 1
|
||||||
|
GameState.captain.parts -= 10.0
|
||||||
|
irradiated_ammunition += 10
|
||||||
|
GameState.captain.emit_changed()
|
||||||
|
ammunition_changed.emit()
|
||||||
|
return true
|
||||||
|
|
||||||
|
func can_buy_irradiated_ammunition() -> bool:
|
||||||
|
return GameState.captain.credits >= 15 * maxi(1, GameState.system_level)
|
||||||
|
|
||||||
|
func buy_irradiated_ammunition() -> bool:
|
||||||
|
if not can_buy_irradiated_ammunition():
|
||||||
|
return false
|
||||||
|
GameState.captain.credits -= 15 * maxi(1, GameState.system_level)
|
||||||
|
irradiated_ammunition += 10
|
||||||
|
GameState.captain.emit_changed()
|
||||||
|
ammunition_changed.emit()
|
||||||
|
return true
|
||||||
|
|
||||||
|
func get_total_dps() -> float:
|
||||||
|
var total := 0.0
|
||||||
|
var bonuses := _comparison_bonuses()
|
||||||
|
for item in weapon_slots:
|
||||||
|
var value := _weapon_dps(item, bonuses)
|
||||||
|
if item != null and item.definition.weapon_kind == ItemDefinition.WeaponKind.GAMMA:
|
||||||
|
value *= 1.0 + 0.25 * minf(3.0, item.definition.fire_interval)
|
||||||
|
total += value
|
||||||
|
return total
|
||||||
|
|||||||
@ -22,7 +22,7 @@ var _asteroid_spin := 0.0
|
|||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
add_to_group("exploration_pois")
|
add_to_group("exploration_pois")
|
||||||
_rng.seed = reward_seed
|
_rng.seed = reward_seed
|
||||||
_mineral = ["iron", "aluminium", "titanium"][_rng.randi_range(0, 2)]
|
_mineral = ["iron", "aluminium", "titanium", "uranium"][_rng.randi_range(0, 3)]
|
||||||
hull = 20.0 * reward_scale()
|
hull = 20.0 * reward_scale()
|
||||||
collision_layer = 4 if kind == "asteroid" else 0
|
collision_layer = 4 if kind == "asteroid" else 0
|
||||||
collision_mask = 0
|
collision_mask = 0
|
||||||
@ -79,7 +79,7 @@ func _update_label(near: bool) -> void:
|
|||||||
if kind == "ruin":
|
if kind == "ruin":
|
||||||
_label.text = "?"
|
_label.text = "?"
|
||||||
elif kind == "asteroid":
|
elif kind == "asteroid":
|
||||||
_label.text = "Asteroida · %s\nHP: %.0f" % [{"iron": "Żelazo", "aluminium": "Aluminium", "titanium": "Tytan"}[_mineral], hull]
|
_label.text = "Asteroida · %s\nHP: %.0f" % [{"iron": "Żelazo", "aluminium": "Aluminium", "titanium": "Tytan", "uranium": "Uran"}[_mineral], hull]
|
||||||
else:
|
else:
|
||||||
_label.text = ("Wrak statku" if kind == "wreck" else "Antyczne znalezisko") + ("\n[E] Przeszukaj" if near else "\nPodleć, aby przeszukać")
|
_label.text = ("Wrak statku" if kind == "wreck" else "Antyczne znalezisko") + ("\n[E] Przeszukaj" if near else "\nPodleć, aby przeszukać")
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ func _physics_process(_delta: float) -> void:
|
|||||||
if near and (kind == "ruin" or (kind == "wreck" and Input.is_physical_key_pressed(KEY_E))):
|
if near and (kind == "ruin" or (kind == "wreck" and Input.is_physical_key_pressed(KEY_E))):
|
||||||
claim()
|
claim()
|
||||||
|
|
||||||
func take_weapon_hit(amount: float, _critical: bool = false) -> void:
|
func take_weapon_hit(amount: float, _critical: bool = false, _damage_type: int = 0) -> void:
|
||||||
take_damage(amount)
|
take_damage(amount)
|
||||||
|
|
||||||
func take_damage(amount: float) -> void:
|
func take_damage(amount: float) -> void:
|
||||||
@ -114,7 +114,7 @@ func claim() -> bool:
|
|||||||
if kind == "asteroid":
|
if kind == "asteroid":
|
||||||
var amount := maxi(1, roundi(_rng.randi_range(1, 3) * scale))
|
var amount := maxi(1, roundi(_rng.randi_range(1, 3) * scale))
|
||||||
GameState.captain.minerals[_mineral] = int(GameState.captain.minerals.get(_mineral, 0)) + amount
|
GameState.captain.minerals[_mineral] = int(GameState.captain.minerals.get(_mineral, 0)) + amount
|
||||||
summary = "+%d %s" % [amount, {"iron": "żelaza", "aluminium": "aluminium", "titanium": "tytanu"}[_mineral]]
|
summary = "+%d %s" % [amount, {"iron": "żelaza", "aluminium": "aluminium", "titanium": "tytanu", "uranium": "uranu"}[_mineral]]
|
||||||
elif kind == "ruin":
|
elif kind == "ruin":
|
||||||
var xp := roundi(_rng.randi_range(10, 20) * scale)
|
var xp := roundi(_rng.randi_range(10, 20) * scale)
|
||||||
GameState.captain.add_rewards(xp, 0)
|
GameState.captain.add_rewards(xp, 0)
|
||||||
|
|||||||
60
scripts/ui/enemy_drawer.gd
Normal file
60
scripts/ui/enemy_drawer.gd
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
extends Control
|
||||||
|
|
||||||
|
var _panel: PanelContainer
|
||||||
|
var _label: Label
|
||||||
|
var _toggle: Button
|
||||||
|
var _open := false
|
||||||
|
var _tween: Tween
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
set_anchors_and_offsets_preset(Control.PRESET_TOP_LEFT)
|
||||||
|
_panel = PanelContainer.new()
|
||||||
|
var style := StyleBoxFlat.new()
|
||||||
|
style.bg_color = Color("0b1520f5")
|
||||||
|
style.border_color = Color("a54e61")
|
||||||
|
style.set_border_width_all(1)
|
||||||
|
style.set_content_margin_all(12)
|
||||||
|
_panel.add_theme_stylebox_override("panel", style)
|
||||||
|
add_child(_panel)
|
||||||
|
_label = Label.new()
|
||||||
|
_label.add_theme_font_size_override("font_size", 12)
|
||||||
|
_panel.add_child(_label)
|
||||||
|
_toggle = Button.new()
|
||||||
|
_toggle.text = "WRÓG ▾"
|
||||||
|
_toggle.focus_mode = Control.FOCUS_NONE
|
||||||
|
add_child(_toggle)
|
||||||
|
_toggle.pressed.connect(_toggle_drawer)
|
||||||
|
_open = bool(GameState.hud_panel_state.get("enemy_open", false))
|
||||||
|
get_viewport().size_changed.connect(_layout)
|
||||||
|
_layout()
|
||||||
|
|
||||||
|
func _layout() -> void:
|
||||||
|
position.x = (get_viewport_rect().size.x - 390.0) * 0.5
|
||||||
|
_panel.size = Vector2(390, 150)
|
||||||
|
_panel.position.y = 42.0 if _open else -160.0
|
||||||
|
_toggle.position = Vector2(130, 12)
|
||||||
|
_toggle.size = Vector2(130, 28)
|
||||||
|
|
||||||
|
func _toggle_drawer() -> void:
|
||||||
|
_open = not _open
|
||||||
|
GameState.hud_panel_state["enemy_open"] = _open
|
||||||
|
_toggle.text = "WRÓG ▴" if _open else "WRÓG ▾"
|
||||||
|
if _tween != null:
|
||||||
|
_tween.kill()
|
||||||
|
_tween = create_tween()
|
||||||
|
_tween.tween_property(_panel, "position:y", 42.0 if _open else -160.0, 0.25).set_trans(Tween.TRANS_CUBIC)
|
||||||
|
|
||||||
|
func _process(_delta: float) -> void:
|
||||||
|
var enemy: Node = null
|
||||||
|
for candidate in get_tree().get_nodes_in_group("enemies"):
|
||||||
|
if not candidate.is_queued_for_deletion() and (enemy == null or int(candidate.loot_tier) < int(enemy.loot_tier)):
|
||||||
|
enemy = candidate
|
||||||
|
var profile := DamageTypes.enemy_profile(GameState.system_level, GameState.system_seed)
|
||||||
|
var kind := int(profile["kind"])
|
||||||
|
var names := ["Działko plazmowe", "Działko maszynowe", "Laser", "Rakiety", "Działko gamma"]
|
||||||
|
_label.text = "WRÓG · UKŁAD %d\n%s · %s\n%s" % [GameState.system_level, names[kind], DamageTypes.NAMES[DamageTypes.for_weapon(kind)], DamageTypes.describe(profile["absorption"])]
|
||||||
|
if enemy != null:
|
||||||
|
_label.text += "\nDPS: %.1f (%s)\nTarcze: %.0f / %.0f · bez regeneracji" % [enemy.get_weapon_dps(), ["zwykły", "elita", "boss"][enemy.loot_tier], enemy.shields, enemy.max_shields]
|
||||||
|
else:
|
||||||
|
_label.text += "\nUkład oczyszczony"
|
||||||
1
scripts/ui/enemy_drawer.gd.uid
Normal file
1
scripts/ui/enemy_drawer.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://d0ejbqbvdb2kq
|
||||||
@ -32,6 +32,8 @@ func _draw() -> void:
|
|||||||
closed.append(outline[0])
|
closed.append(outline[0])
|
||||||
draw_polyline(closed, Color("376779"), 1.5, true)
|
draw_polyline(closed, Color("376779"), 1.5, true)
|
||||||
draw_line(Vector2(28, 2), Vector2(size.x - 28, 2), CYAN, 2, true)
|
draw_line(Vector2(28, 2), Vector2(size.x - 28, 2), CYAN, 2, true)
|
||||||
|
_text("DPS: %.1f · GAMMA: %d" % [_inventory.get_total_dps(), _inventory.irradiated_ammunition], Vector2(20, 143), size.x - 40, 12, CYAN)
|
||||||
|
_text(DamageTypes.describe(player.get("absorption")), Vector2(20, 162), size.x - 40, 10, Color("a9d69a"))
|
||||||
var cell := (size.x - 40) / 3.0
|
var cell := (size.x - 40) / 3.0
|
||||||
var values := [_inventory.missile_ammunition, _inventory.ammunition, int(game.call("get_remaining_enemies"))]
|
var values := [_inventory.missile_ammunition, _inventory.ammunition, int(game.call("get_remaining_enemies"))]
|
||||||
var captions := ["RAKIETY", "AMUNICJA", "WROGOWIE"]
|
var captions := ["RAKIETY", "AMUNICJA", "WROGOWIE"]
|
||||||
|
|||||||
@ -14,7 +14,16 @@ var planet: Node2D
|
|||||||
var _open_tween: Tween
|
var _open_tween: Tween
|
||||||
var _missile_button: Button
|
var _missile_button: Button
|
||||||
|
|
||||||
|
var _gamma_buy: Button
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
_gamma_buy = Button.new()
|
||||||
|
_gamma_buy.name = "BuyIrradiatedAmmunition"
|
||||||
|
_gamma_buy.custom_minimum_size.y = 36
|
||||||
|
_gamma_buy.add_theme_font_size_override("font_size", 12)
|
||||||
|
_gamma_buy.focus_mode = Control.FOCUS_NONE
|
||||||
|
$Center/Panel/Contents.add_child(_gamma_buy)
|
||||||
|
_gamma_buy.pressed.connect(func() -> void: _inventory.buy_irradiated_ammunition())
|
||||||
next_button.pressed.connect(func() -> void: next_level_requested.emit())
|
next_button.pressed.connect(func() -> void: next_level_requested.emit())
|
||||||
buy_ammunition_button.pressed.connect(_buy_ammunition)
|
buy_ammunition_button.pressed.connect(_buy_ammunition)
|
||||||
reset_skills_button.pressed.connect(_reset_skills)
|
reset_skills_button.pressed.connect(_reset_skills)
|
||||||
@ -75,6 +84,9 @@ func _position_at_planet() -> void:
|
|||||||
position = Vector2(clampf(target.x, 8.0, maxf(8.0, screen_right - size.x)), clampf(target.y, 8.0, maxf(8.0, viewport_size.y - size.y - 150.0)))
|
position = Vector2(clampf(target.x, 8.0, maxf(8.0, screen_right - size.x)), clampf(target.y, 8.0, maxf(8.0, viewport_size.y - size.y - 150.0)))
|
||||||
|
|
||||||
func _refresh_ammunition_service() -> void:
|
func _refresh_ammunition_service() -> void:
|
||||||
|
if _gamma_buy != null:
|
||||||
|
_gamma_buy.text = "KUP 10 NAPROMIENIOWANEJ — %d KREDYTÓW" % (15 * maxi(1, GameState.system_level))
|
||||||
|
_gamma_buy.disabled = _inventory == null or not _inventory.can_buy_irradiated_ammunition()
|
||||||
_missile_button.disabled = _inventory == null or not _inventory.can_buy_missile()
|
_missile_button.disabled = _inventory == null or not _inventory.can_buy_missile()
|
||||||
_missile_button.text = "KUP +1 RAKIETĘ — %d KREDYTÓW" % (_inventory.get_missile_buy_cost() if _inventory else ShipInventory.MISSILE_BUY_BASE_COST)
|
_missile_button.text = "KUP +1 RAKIETĘ — %d KREDYTÓW" % (_inventory.get_missile_buy_cost() if _inventory else ShipInventory.MISSILE_BUY_BASE_COST)
|
||||||
reset_skills_button.disabled = not GameState.captain.can_reset_skills()
|
reset_skills_button.disabled = not GameState.captain.can_reset_skills()
|
||||||
|
|||||||
@ -7,6 +7,7 @@ var _details: VBoxContainer
|
|||||||
var _parts: Label
|
var _parts: Label
|
||||||
var _stock: Label
|
var _stock: Label
|
||||||
var _ammo: Button
|
var _ammo: Button
|
||||||
|
var _gamma: Button
|
||||||
var _missile: Button
|
var _missile: Button
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
@ -36,6 +37,8 @@ func _ready() -> void:
|
|||||||
_missile = _button("CraftMissile", "+1 RAKIETA · %.0f CZĘŚCI" % ShipInventory.MISSILE_CRAFT_PARTS_COST)
|
_missile = _button("CraftMissile", "+1 RAKIETA · %.0f CZĘŚCI" % ShipInventory.MISSILE_CRAFT_PARTS_COST)
|
||||||
_missile.tooltip_text = "Tworzy rakietę do dowolnej wyrzutni."
|
_missile.tooltip_text = "Tworzy rakietę do dowolnej wyrzutni."
|
||||||
_missile.pressed.connect(func() -> void: _inventory.craft_missile())
|
_missile.pressed.connect(func() -> void: _inventory.craft_missile())
|
||||||
|
_gamma = _button("CraftGamma", "+10 NAPROMIENIOWANEJ · 1 URAN + 10 CZĘŚCI")
|
||||||
|
_gamma.pressed.connect(func() -> void: _inventory.craft_irradiated_ammunition())
|
||||||
_details.hide()
|
_details.hide()
|
||||||
if bool(GameState.hud_panel_state.get("crafting_expanded", false)):
|
if bool(GameState.hud_panel_state.get("crafting_expanded", false)):
|
||||||
_toggle_expanded()
|
_toggle_expanded()
|
||||||
@ -70,4 +73,6 @@ func _refresh() -> void:
|
|||||||
_parts.text = "DOSTĘPNE CZĘŚCI: %.1f" % GameState.captain.parts
|
_parts.text = "DOSTĘPNE CZĘŚCI: %.1f" % GameState.captain.parts
|
||||||
_stock.text = "AMUNICJA: %d / RAKIETY: %d" % [_inventory.ammunition, _inventory.missile_ammunition]
|
_stock.text = "AMUNICJA: %d / RAKIETY: %d" % [_inventory.ammunition, _inventory.missile_ammunition]
|
||||||
_ammo.disabled = not _inventory.can_craft_ammunition()
|
_ammo.disabled = not _inventory.can_craft_ammunition()
|
||||||
|
_gamma.disabled = not _inventory.can_craft_irradiated_ammunition()
|
||||||
|
_stock.text += "\nNAPROMIENIOWANA: %d · URAN: %d" % [_inventory.irradiated_ammunition, int(GameState.captain.minerals.get("uranium", 0))]
|
||||||
_missile.disabled = not _inventory.can_craft_missile()
|
_missile.disabled = not _inventory.can_craft_missile()
|
||||||
|
|||||||
@ -38,6 +38,7 @@ func _refresh_title() -> void:
|
|||||||
if _inventory:
|
if _inventory:
|
||||||
title_label.text = "MAGAZYN (%d/%d) · Części: %.1f" % [_inventory.get_cargo_used(), CARGO_SIZE, GameState.captain.parts]
|
title_label.text = "MAGAZYN (%d/%d) · Części: %.1f" % [_inventory.get_cargo_used(), CARGO_SIZE, GameState.captain.parts]
|
||||||
ammunition_label.text = "AMUNICJA: %d · RAKIETY: %d" % [_inventory.ammunition, _inventory.get_missile_count()]
|
ammunition_label.text = "AMUNICJA: %d · RAKIETY: %d" % [_inventory.ammunition, _inventory.get_missile_count()]
|
||||||
|
ammunition_label.text += " · GAMMA: %d" % _inventory.irradiated_ammunition
|
||||||
|
|
||||||
func toggle_drawer() -> void:
|
func toggle_drawer() -> void:
|
||||||
_set_drawer_open(not _drawer_open)
|
_set_drawer_open(not _drawer_open)
|
||||||
|
|||||||
@ -10,7 +10,7 @@ const BALANCE := preload("res://scripts/captain/captain_balance.gd")
|
|||||||
class DamageReceiver:
|
class DamageReceiver:
|
||||||
extends Node2D
|
extends Node2D
|
||||||
var hits: Array[Dictionary] = []
|
var hits: Array[Dictionary] = []
|
||||||
func take_weapon_hit(amount: float, critical: bool) -> void:
|
func take_weapon_hit(amount: float, critical: bool, _damage_type: int = 0) -> void:
|
||||||
hits.append({"damage": amount, "critical": critical})
|
hits.append({"damage": amount, "critical": critical})
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
|||||||
@ -4,12 +4,15 @@ const GENERATOR := preload("res://scripts/items/item_generator.gd")
|
|||||||
const DATABASE := preload("res://resources/items/affix_database.tres")
|
const DATABASE := preload("res://resources/items/affix_database.tres")
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
assert(DATABASE.affixes.size() == 10)
|
assert(DATABASE.affixes.size() == 15)
|
||||||
for level in [1, 50, 1000]:
|
for level in [1, 50, 1000]:
|
||||||
for tier in [0, 1, 2]:
|
for tier in [0, 1, 2]:
|
||||||
for sample in 50:
|
for sample in 50:
|
||||||
var item := GENERATOR.generate(level, tier)
|
var item := GENERATOR.generate(level, tier)
|
||||||
assert(item.item_level == level)
|
assert(item.item_level == level)
|
||||||
|
if item.definition.category == ItemDefinition.Category.ACTIVE_EXTENSION:
|
||||||
|
assert(item.additional_modifiers.is_empty())
|
||||||
|
continue
|
||||||
assert(item.additional_modifiers.size() >= 1 and item.additional_modifiers.size() <= 5)
|
assert(item.additional_modifiers.size() >= 1 and item.additional_modifiers.size() <= 5)
|
||||||
if tier == 2:
|
if tier == 2:
|
||||||
assert(item.additional_modifiers.size() >= 4)
|
assert(item.additional_modifiers.size() >= 4)
|
||||||
@ -18,9 +21,9 @@ func _ready() -> void:
|
|||||||
assert(modifier.affix.can_roll(item.definition, level))
|
assert(modifier.affix.can_roll(item.definition, level))
|
||||||
assert(modifier.affix.affix_id not in ids)
|
assert(modifier.affix.affix_id not in ids)
|
||||||
ids.append(modifier.affix.affix_id)
|
ids.append(modifier.affix.affix_id)
|
||||||
var progression := float(level - 1) / 999.0
|
var bounds := modifier.affix.get_value_range(level)
|
||||||
assert(modifier.value >= lerpf(modifier.affix.minimum_value_level_1, modifier.affix.minimum_value_level_1000, progression))
|
assert(modifier.value >= bounds.x - 0.0001)
|
||||||
assert(modifier.value <= lerpf(modifier.affix.maximum_value_level_1, modifier.affix.maximum_value_level_1000, progression))
|
assert(modifier.value <= bounds.y + 0.0001)
|
||||||
assert(item.get_tooltip_text().contains(item.additional_modifiers[0].affix.display_name))
|
assert(item.get_tooltip_text().contains(item.additional_modifiers[0].affix.display_name))
|
||||||
|
|
||||||
var weapon := ItemDefinition.new()
|
var weapon := ItemDefinition.new()
|
||||||
|
|||||||
@ -22,7 +22,7 @@ func _ready() -> void:
|
|||||||
enemy.set("_player", ship)
|
enemy.set("_player", ship)
|
||||||
enemy.call("apply_level_settings", 4, 2)
|
enemy.call("apply_level_settings", 4, 2)
|
||||||
var expected := roundi(BALANCE.KILL_CREDITS[2] * 1.15 * 1.5)
|
var expected := roundi(BALANCE.KILL_CREDITS[2] * 1.15 * 1.5)
|
||||||
enemy.call("take_damage", enemy.get("max_hull"))
|
enemy.call("take_damage", 10.0 * (float(enemy.get("max_hull")) + float(enemy.get("max_shields"))))
|
||||||
assert(GameState.captain.credits == 0)
|
assert(GameState.captain.credits == 0)
|
||||||
assert(GameState.captain.level > 1 or GameState.captain.experience > 0)
|
assert(GameState.captain.level > 1 or GameState.captain.experience > 0)
|
||||||
await get_tree().process_frame
|
await get_tree().process_frame
|
||||||
|
|||||||
84
tests/items/damage_types_test.gd
Normal file
84
tests/items/damage_types_test.gd
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
extends Node2D
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
GameState.start_new_run()
|
||||||
|
GameState.system_seed = 12345
|
||||||
|
assert(DamageTypes.for_weapon(ItemDefinition.WeaponKind.MACHINE_GUN) == DamageTypes.Type.KINETIC)
|
||||||
|
assert(DamageTypes.for_weapon(ItemDefinition.WeaponKind.MISSILE) == DamageTypes.Type.KINETIC)
|
||||||
|
assert(DamageTypes.for_weapon(ItemDefinition.WeaponKind.PLASMA) == DamageTypes.Type.ENERGY)
|
||||||
|
assert(DamageTypes.for_weapon(ItemDefinition.WeaponKind.LASER) == DamageTypes.Type.ENERGY)
|
||||||
|
assert(DamageTypes.for_weapon(ItemDefinition.WeaponKind.GAMMA) == DamageTypes.Type.RADIATION)
|
||||||
|
var capped := DamageTypes.capped(Vector3(40, 40, 40))
|
||||||
|
assert(capped.is_equal_approx(Vector3(20, 20, 20)))
|
||||||
|
for level in [1, 500, 1000]:
|
||||||
|
var profile := DamageTypes.enemy_profile(level, 12345)
|
||||||
|
assert(profile == DamageTypes.enemy_profile(level, 12345))
|
||||||
|
var values: Vector3 = profile["absorption"]
|
||||||
|
assert(values.x + values.y + values.z <= 60.001)
|
||||||
|
if level == 1:
|
||||||
|
assert(is_equal_approx(values.x + values.y + values.z, 2.0))
|
||||||
|
var ship := preload("res://scenes/game/player_ship.tscn").instantiate()
|
||||||
|
add_child(ship)
|
||||||
|
ship.set_physics_process(false)
|
||||||
|
ship.get_node("WeaponController").set_physics_process(false)
|
||||||
|
var inventory: ShipInventory = ship.get_node("ShipInventory")
|
||||||
|
var module := ItemInstance.new()
|
||||||
|
module.definition = preload("res://resources/items/hull_plating.tres")
|
||||||
|
var modifier := ItemStatModifier.new()
|
||||||
|
modifier.stat_key = "kinetic_absorption"
|
||||||
|
modifier.value = 20.0
|
||||||
|
module.additional_modifiers.append(modifier)
|
||||||
|
assert(inventory.add_item(module))
|
||||||
|
assert(inventory.move_item("cargo", inventory.cargo.find(module), "module", 1))
|
||||||
|
assert(is_equal_approx(ship.absorption.x, 20.0))
|
||||||
|
ship.shields = 100.0
|
||||||
|
ship.take_damage(10.0, false, DamageTypes.Type.KINETIC)
|
||||||
|
assert(is_equal_approx(ship.shields, 92.0))
|
||||||
|
assert(inventory.move_item("module", 1, "cargo", inventory.cargo.find(null)))
|
||||||
|
assert(ship.absorption == Vector3.ZERO)
|
||||||
|
GameState.captain.parts = 10
|
||||||
|
GameState.captain.minerals["uranium"] = 1
|
||||||
|
assert(inventory.craft_irradiated_ammunition())
|
||||||
|
assert(inventory.irradiated_ammunition == 10)
|
||||||
|
assert(not inventory.craft_irradiated_ammunition())
|
||||||
|
assert(inventory.consume_irradiated_ammunition())
|
||||||
|
GameState.captain.credits = 15
|
||||||
|
assert(inventory.buy_irradiated_ammunition())
|
||||||
|
assert(inventory.irradiated_ammunition == 19)
|
||||||
|
inventory.preserve_for_restart()
|
||||||
|
assert(GameState.restart_inventory["irradiated_ammunition"] == 19)
|
||||||
|
var enemy := preload("res://scenes/game/enemy_ship.tscn").instantiate()
|
||||||
|
enemy.apply_level_settings(1, 0)
|
||||||
|
add_child(enemy)
|
||||||
|
enemy.set_physics_process(false)
|
||||||
|
enemy.absorption = Vector3(20, 0, 0)
|
||||||
|
enemy.shields = 10
|
||||||
|
enemy.hull = 100
|
||||||
|
enemy.take_damage(20, false, DamageTypes.Type.KINETIC)
|
||||||
|
assert(is_equal_approx(enemy.shields, 0))
|
||||||
|
assert(is_equal_approx(enemy.hull, 94))
|
||||||
|
enemy.take_damage(10, false, DamageTypes.Type.ENERGY)
|
||||||
|
assert(is_equal_approx(enemy.hull, 84))
|
||||||
|
var projectile := preload("res://scenes/game/weapon_projectile.tscn").instantiate()
|
||||||
|
projectile.weapon_kind = ItemDefinition.WeaponKind.GAMMA
|
||||||
|
projectile.damage = 8
|
||||||
|
add_child(projectile)
|
||||||
|
projectile.set_physics_process(false)
|
||||||
|
projectile._apply_damage_once(enemy)
|
||||||
|
assert(is_equal_approx(enemy.hull, 76))
|
||||||
|
var status: Node = enemy.get_node("RadiationStatus")
|
||||||
|
status.set_physics_process(false)
|
||||||
|
for index in 6:
|
||||||
|
status._physics_process(0.5)
|
||||||
|
assert(is_equal_approx(enemy.hull, 70))
|
||||||
|
assert(is_equal_approx(status.remaining, 0))
|
||||||
|
assert(enemy.get_node("Sprite2D").modulate == Color.WHITE)
|
||||||
|
for kind in 5:
|
||||||
|
enemy.weapon_kind = kind
|
||||||
|
enemy._fire_plasma(Vector2.RIGHT)
|
||||||
|
for key in ["kinetic", "energy", "radiation"]:
|
||||||
|
var affix: ItemAffixDefinition = load("res://resources/items/affixes/%s_absorption.tres" % key)
|
||||||
|
assert(is_equal_approx(affix.roll_value(1), 1.0))
|
||||||
|
assert(preload("res://resources/items/gamma_cannon.tres") in preload("res://scripts/items/item_generator.gd").DEFINITIONS)
|
||||||
|
print("DAMAGE_TYPES_TEST: PASS (types, caps, profiles, crafting, purchase, persistence, shield overflow, radiation duration)")
|
||||||
|
get_tree().quit()
|
||||||
1
tests/items/damage_types_test.gd.uid
Normal file
1
tests/items/damage_types_test.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://b2tol3o50xrk7
|
||||||
4
tests/items/damage_types_test.tscn
Normal file
4
tests/items/damage_types_test.tscn
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
[ext_resource type="Script" path="res://tests/items/damage_types_test.gd" id="1"]
|
||||||
|
[node name="DamageTypesTest" type="Node2D"]
|
||||||
|
script = ExtResource("1")
|
||||||
@ -49,6 +49,7 @@ func _ready() -> void:
|
|||||||
enemy.set("_player", ship)
|
enemy.set("_player", ship)
|
||||||
enemy.set("max_hull", 10000.0)
|
enemy.set("max_hull", 10000.0)
|
||||||
enemy.set("hull", 10000.0)
|
enemy.set("hull", 10000.0)
|
||||||
|
enemy.set("shields", 0.0)
|
||||||
var controller := AimedGun.new()
|
var controller := AimedGun.new()
|
||||||
ship.add_child(controller)
|
ship.add_child(controller)
|
||||||
await get_tree().physics_frame
|
await get_tree().physics_frame
|
||||||
|
|||||||
@ -73,4 +73,5 @@ func _enemy(ship: CharacterBody2D, hp: float) -> CharacterBody2D:
|
|||||||
enemy.set_physics_process(false)
|
enemy.set_physics_process(false)
|
||||||
enemy.set("_player", ship)
|
enemy.set("_player", ship)
|
||||||
enemy.set("hull", hp)
|
enemy.set("hull", hp)
|
||||||
|
enemy.set("shields", 0.0)
|
||||||
return enemy
|
return enemy
|
||||||
|
|||||||
@ -32,7 +32,7 @@ func _ready() -> void:
|
|||||||
addon.stat_key = "damage_percent"
|
addon.stat_key = "damage_percent"
|
||||||
addon.value = 10.0
|
addon.value = 10.0
|
||||||
stronger.additional_modifiers.append(addon)
|
stronger.additional_modifiers.append(addon)
|
||||||
if definition.weapon_kind in [ItemDefinition.WeaponKind.MACHINE_GUN, ItemDefinition.WeaponKind.MISSILE]:
|
if definition.weapon_kind in [ItemDefinition.WeaponKind.MACHINE_GUN, ItemDefinition.WeaponKind.MISSILE, ItemDefinition.WeaponKind.GAMMA]:
|
||||||
assert(stronger.get_energy_cost() == 0.0)
|
assert(stronger.get_energy_cost() == 0.0)
|
||||||
else:
|
else:
|
||||||
assert(stronger.get_energy_cost() > cost_before_addon)
|
assert(stronger.get_energy_cost() > cost_before_addon)
|
||||||
|
|||||||
@ -210,6 +210,9 @@ func _assert_boss_and_health(game: Node2D) -> void:
|
|||||||
assert(boss.get_node("CollisionShape2D").scale == Vector2(2, 2))
|
assert(boss.get_node("CollisionShape2D").scale == Vector2(2, 2))
|
||||||
var bar: Node2D = boss.get_node("HealthBar")
|
var bar: Node2D = boss.get_node("HealthBar")
|
||||||
assert(is_equal_approx(bar.get_health_ratio(), 1.0))
|
assert(is_equal_approx(bar.get_health_ratio(), 1.0))
|
||||||
|
# Isolate the hull bar from the new shield and absorption layers.
|
||||||
|
boss.shields = 0.0
|
||||||
|
boss.absorption = Vector3.ZERO
|
||||||
boss.take_damage(boss.max_hull * 0.5)
|
boss.take_damage(boss.max_hull * 0.5)
|
||||||
assert(is_equal_approx(bar.get_health_ratio(), 0.5))
|
assert(is_equal_approx(bar.get_health_ratio(), 0.5))
|
||||||
boss.rotation = 1.2
|
boss.rotation = 1.2
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user