This commit is contained in:
Tomasz Boruc 2026-10-11 21:39:53 +02:00
parent a3963fe36e
commit ffb63bfbdf
10 changed files with 152 additions and 2 deletions

View File

@ -0,0 +1,14 @@
[gd_resource type="Resource" script_class="ItemDefinition" load_steps=4 format=3]
[ext_resource type="Script" path="res://scripts/items/item_definition.gd" id="1"]
[ext_resource type="Script" path="res://scripts/abilities/scanner_ability.gd" id="2"]
[sub_resource type="Resource" id="Ability"]
script = ExtResource("2")
display_name = "Skaner"
description = "Przez 8 sekund pokazuje kierunki do wrogów, asteroid, wraków i planet z antycznymi znaleziskami. Wymaga i zużywa 50% maksymalnej energii."
[resource]
script = ExtResource("1")
item_id = "scanner"
display_name = "Skaner"
category = 5
description = "Aktywne rozszerzenie eksploracyjne. Zamontuj w slocie modułu i aktywuj przypisanym klawiszem 1–6."
active_ability = SubResource("Ability")

View File

@ -0,0 +1,24 @@
extends ActiveAbility
const SCAN_DURATION := 8.0
const OVERLAY := preload("res://scripts/ui/scanner_overlay.gd")
func unavailable_reason(ship: CharacterBody2D) -> String:
if ship.has_node("ScannerOverlay"):
return "Skaner jest aktywny"
return "Wymagane 50% energii" if float(ship.get("energy")) < float(ship.get("max_energy")) * 0.5 else ""
func activate(ship: CharacterBody2D) -> void:
if not unavailable_reason(ship).is_empty():
return
ship.set("energy", float(ship.get("energy")) - float(ship.get("max_energy")) * 0.5)
var layer := CanvasLayer.new()
layer.name = "ScannerOverlay"
layer.layer = 8
ship.add_child(layer)
var overlay := Control.new()
overlay.set_script(OVERLAY)
overlay.set("player", ship)
overlay.set("remaining", SCAN_DURATION)
layer.add_child(overlay)
overlay.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)

View File

@ -0,0 +1 @@
uid://dhayld6xbljao

View File

@ -14,6 +14,7 @@ const DEFINITIONS := [
preload("res://resources/items/afterburner.tres"),
preload("res://resources/items/shield_charge.tres"),
preload("res://resources/items/repair_drones.tres"),
preload("res://resources/items/scanner.tres"),
]
const AFFIX_DATABASE := preload("res://resources/items/affix_database.tres")

View File

@ -8,7 +8,7 @@ const CARGO_SIZE := 20
const WEAPON_SLOT_COUNT := 3
const MISSILE_SLOT := 2
const STARTING_MISSILE_AMMUNITION := 2
const MISSILE_CRAFT_PARTS_COST := 20.0
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
@ -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", "shield_on_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"]:
bonuses[stat_key] = float(bonuses.get(stat_key, 0.0)) + item.get_stat_total(stat_key)
ship.call("apply_equipment_bonuses", bonuses)

View File

@ -0,0 +1,58 @@
extends Control
const FONT := preload("res://assets/fonts/font2.ttf")
var player: CharacterBody2D
var remaining := 8.0
var targets: Array[Node2D] = []
func _ready() -> void:
mouse_filter = Control.MOUSE_FILTER_IGNORE
for group in ["enemies", "exploration_pois"]:
for target in get_tree().get_nodes_in_group(group):
if player.get_parent().is_ancestor_of(target):
targets.append(target)
func _process(delta: float) -> void:
remaining -= delta
if remaining <= 0 or not is_instance_valid(player) or bool(player.get("_is_destroyed")):
get_parent().queue_free()
return
queue_redraw()
func _draw() -> void:
if not is_instance_valid(player):
return
var bounds := Rect2(Vector2(45, 100), Vector2(maxf(100, size.x - 390), maxf(100, size.y - 210)))
var origin := get_viewport().get_canvas_transform() * player.global_position
var anchor := Vector2(clampf(origin.x, bounds.position.x, bounds.end.x), clampf(origin.y, bounds.position.y, bounds.end.y))
for target in targets:
if not is_instance_valid(target) or target.is_queued_for_deletion():
continue
var color := Color("ff7777")
var caption := "WRÓG"
if target.is_in_group("enemies"):
if float(target.get("hull")) <= 0:
continue
else:
match str(target.get("kind")):
"asteroid":
color = Color("a6c8e0")
caption = "AST"
"wreck":
color = Color("ffd166")
caption = "WRAK"
"ruin":
color = Color("d2a4ff")
caption = "?"
var screen := get_viewport().get_canvas_transform() * target.global_position
var direction := (screen - anchor).normalized()
var point := screen
if not bounds.has_point(screen):
var dx := (bounds.end.x - anchor.x) if direction.x > 0 else (anchor.x - bounds.position.x)
var dy := (bounds.end.y - anchor.y) if direction.y > 0 else (anchor.y - bounds.position.y)
var distance := minf(dx / maxf(absf(direction.x), 0.001), dy / maxf(absf(direction.y), 0.001))
point = anchor + direction * distance
var side := Vector2(-direction.y, direction.x)
draw_colored_polygon(PackedVector2Array([point + direction * 10, point - direction * 6 + side * 6, point - direction * 6 - side * 6]), color)
draw_string(FONT, point + Vector2(-18, 25), caption, HORIZONTAL_ALIGNMENT_LEFT, -1, 11, color)
draw_string(FONT, Vector2(32, 82), "SKANER %.1f s · czerwony: wrogowie · błękitny: asteroidy · złoty: wraki · fioletowy: antyczne planety" % maxf(0, remaining), HORIZONTAL_ALIGNMENT_LEFT, -1, 13, Color("c9e8ff"))

View File

@ -0,0 +1 @@
uid://bx7eud4hxp4jh

View File

@ -0,0 +1,46 @@
extends Node2D
const GENERATOR := preload("res://scripts/items/item_generator.gd")
func _ready() -> void:
GameState.start_new_run()
var ship := preload("res://scenes/game/player_ship.tscn").instantiate() as CharacterBody2D
ship.name = "PlayerShip"
add_child(ship)
ship.set_physics_process(false)
ship.get_node("WeaponController").set_physics_process(false)
var inventory := ship.get_node("ShipInventory") as ShipInventory
var scanner := GENERATOR.generate_for_definition(preload("res://resources/items/scanner.tres"), 5)
assert(scanner.get_addon_count() > 0)
assert(inventory.add_item(scanner))
assert(inventory.move_item("cargo", inventory.cargo.find(scanner), "module", 0))
var enemy := preload("res://scenes/game/enemy_ship.tscn").instantiate() as CharacterBody2D
enemy.position = Vector2(900, 0)
add_child(enemy)
enemy.set_physics_process(false)
for kind in ["asteroid", "wreck", "ruin"]:
var poi := StaticBody2D.new()
poi.set_script(preload("res://scripts/levels/exploration_poi.gd"))
poi.set("kind", kind)
poi.set("player", ship)
poi.position = Vector2(1500, 100)
add_child(poi)
poi.set_physics_process(false)
var maximum := float(ship.get("max_energy"))
ship.set("energy", maximum * 0.49)
assert(not ship.activate_ability(0))
ship.set("energy", maximum * 0.8)
assert(ship.activate_ability(0))
assert(is_equal_approx(ship.get("energy"), maximum * 0.3))
var layer := ship.get_node("ScannerOverlay") as CanvasLayer
var overlay := layer.get_child(0) as Control
assert(overlay.get("targets").size() == 4)
assert(overlay.mouse_filter == Control.MOUSE_FILTER_IGNORE)
assert(not ship.activate_ability(0))
await get_tree().process_frame
overlay.call("_process", 8.1)
await get_tree().process_frame
assert(not ship.has_node("ScannerOverlay"))
ship.set("energy", maximum * 0.5)
assert(ship.activate_ability(0))
assert(is_zero_approx(ship.get("energy")))
print("SCANNER_TEST: PASS loot addons, module activation, 50% max energy cost, four target types, repeat prevention, 8s expiry, reactivation")
get_tree().quit()

View File

@ -0,0 +1 @@
uid://c1a6tu0q5xiut

View File

@ -0,0 +1,4 @@
[gd_scene load_steps=2 format=3]
[ext_resource type="Script" path="res://tests/abilities/scanner_test.gd" id="1"]
[node name="ScannerTest" type="Node2D"]
script = ExtResource("1")