Zmiany
This commit is contained in:
parent
a3963fe36e
commit
ffb63bfbdf
14
resources/items/scanner.tres
Normal file
14
resources/items/scanner.tres
Normal 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")
|
||||||
24
scripts/abilities/scanner_ability.gd
Normal file
24
scripts/abilities/scanner_ability.gd
Normal 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)
|
||||||
1
scripts/abilities/scanner_ability.gd.uid
Normal file
1
scripts/abilities/scanner_ability.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://dhayld6xbljao
|
||||||
@ -14,6 +14,7 @@ const DEFINITIONS := [
|
|||||||
preload("res://resources/items/afterburner.tres"),
|
preload("res://resources/items/afterburner.tres"),
|
||||||
preload("res://resources/items/shield_charge.tres"),
|
preload("res://resources/items/shield_charge.tres"),
|
||||||
preload("res://resources/items/repair_drones.tres"),
|
preload("res://resources/items/repair_drones.tres"),
|
||||||
|
preload("res://resources/items/scanner.tres"),
|
||||||
]
|
]
|
||||||
const AFFIX_DATABASE := preload("res://resources/items/affix_database.tres")
|
const AFFIX_DATABASE := preload("res://resources/items/affix_database.tres")
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,7 @@ const CARGO_SIZE := 20
|
|||||||
const WEAPON_SLOT_COUNT := 3
|
const WEAPON_SLOT_COUNT := 3
|
||||||
const MISSILE_SLOT := 2
|
const MISSILE_SLOT := 2
|
||||||
const STARTING_MISSILE_AMMUNITION := 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_BUY_BASE_COST := 10
|
||||||
const MISSILE_DEFINITIONS := [preload("res://resources/items/missile_launcher.tres"), preload("res://resources/items/unguided_missile.tres")]
|
const MISSILE_DEFINITIONS := [preload("res://resources/items/missile_launcher.tres"), preload("res://resources/items/unguided_missile.tres")]
|
||||||
const MODULE_SLOT_COUNT := 6
|
const MODULE_SLOT_COUNT := 6
|
||||||
@ -215,7 +215,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 ["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)
|
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)
|
||||||
|
|
||||||
|
|||||||
58
scripts/ui/scanner_overlay.gd
Normal file
58
scripts/ui/scanner_overlay.gd
Normal 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"))
|
||||||
1
scripts/ui/scanner_overlay.gd.uid
Normal file
1
scripts/ui/scanner_overlay.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://bx7eud4hxp4jh
|
||||||
46
tests/abilities/scanner_test.gd
Normal file
46
tests/abilities/scanner_test.gd
Normal 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()
|
||||||
1
tests/abilities/scanner_test.gd.uid
Normal file
1
tests/abilities/scanner_test.gd.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://c1a6tu0q5xiut
|
||||||
4
tests/abilities/scanner_test.tscn
Normal file
4
tests/abilities/scanner_test.tscn
Normal 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")
|
||||||
Loading…
x
Reference in New Issue
Block a user