Randoloot/scripts/captain/captain_progress.gd
2026-10-16 20:24:12 +02:00

181 lines
6.4 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

extends Resource
class_name CaptainProgress
const BALANCE := preload("res://scripts/captain/captain_balance.gd")
const SKILLS := preload("res://scripts/captain/captain_skills.gd")
const ACTIVE_SKILL_SLOTS := 5
@export var level: int = 1
@export var experience: int = 0
@export var credits: int = 0
@export var parts: float = 0.0
@export var minerals: Dictionary = {"uranium": 0, "titanium": 0, "iron": 0, "aluminium": 0, "sulfur": 0}
@export var learning_points: int = 0
@export var skills: Dictionary = {}
@export var equipped_skills: Array[String] = ["", "", "", "", ""]
var skill_offers: Array[String] = []
var skill_cooldowns: Dictionary = {}
var skill_durations: Dictionary = {}
var _ammo_credit: Dictionary = {}
var _skill_rng := RandomNumberGenerator.new()
func _init() -> void:
_skill_rng.randomize()
func get_xp_required() -> int:
return BALANCE.xp_required(level)
func get_skill_rank(skill: String) -> int:
return clampi(int(skills.get(skill, 0)), 0, SKILLS.MAX_RANK) if SKILLS.DATA.has(skill) else 0
func get_skill_bonus(skill: String) -> float:
return SKILLS.value(skill, get_skill_rank(skill))
func normalize_skill_ranks() -> void:
for skill in skills.keys():
if not SKILLS.DATA.has(skill):
skills.erase(skill)
else:
skills[skill] = get_skill_rank(skill)
normalize_equipped_skills()
func normalize_equipped_skills() -> void:
equipped_skills.resize(ACTIVE_SKILL_SLOTS)
var seen: Array[String] = []
for index in ACTIVE_SKILL_SLOTS:
var skill := equipped_skills[index]
if not SKILLS.DATA.has(skill) or not bool(SKILLS.DATA[skill]["active"]) or get_skill_rank(skill) <= 0 or skill in seen:
equipped_skills[index] = ""
else:
seen.append(skill)
func equip_skill(slot: int, skill: String) -> bool:
if slot < 0 or slot >= ACTIVE_SKILL_SLOTS:
return false
if not skill.is_empty() and (not SKILLS.DATA.has(skill) or not bool(SKILLS.DATA[skill]["active"]) or get_skill_rank(skill) <= 0):
return false
normalize_equipped_skills()
# Moving a skill between slots never resets its cooldown or active duration.
var previous_slot := equipped_skills.find(skill) if not skill.is_empty() else -1
if previous_slot >= 0:
equipped_skills[previous_slot] = ""
equipped_skills[slot] = skill
emit_changed()
return true
func get_spent_learning_points() -> int:
var spent := 0
for skill in SKILLS.IDS:
spent += get_skill_rank(skill)
return spent
func get_skill_reset_cost() -> int:
return BALANCE.SKILL_RESET_COST_PER_LEVEL * maxi(1, level)
func can_reset_skills() -> bool:
return false # Skills belong to the entire run; choices cannot be rerolled at a shop.
func reset_skills() -> bool:
return false
func can_train(skill: String) -> bool:
return learning_points > 0 and skill in skill_offers and get_skill_rank(skill) < SKILLS.MAX_RANK
func train_skill(skill: String) -> bool:
if not can_train(skill):
return false
skills[skill] = get_skill_rank(skill) + 1
if get_skill_rank(skill) == 1 and bool(SKILLS.DATA[skill]["active"]):
normalize_equipped_skills()
var empty := equipped_skills.find("")
if empty >= 0:
equipped_skills[empty] = skill
learning_points -= 1
skill_offers.clear()
prepare_skill_offers()
emit_changed()
return true
func prepare_skill_offers() -> void:
if learning_points <= 0 or not skill_offers.is_empty():
return
var pool: Array[String] = []
for skill in SKILLS.IDS:
if get_skill_rank(skill) < SKILLS.MAX_RANK:
pool.append(skill)
if pool.is_empty():
learning_points = 0
return
while skill_offers.size() < 3 and not pool.is_empty():
var index := _skill_rng.randi_range(0, pool.size() - 1)
skill_offers.append(pool[index])
pool.remove_at(index)
func discounted_price(base_price: int) -> int:
return maxi(1, roundi(base_price * (1.0 - get_skill_bonus("business_shark"))))
func should_consume_ammunition(ammo_kind: String) -> bool:
# Accumulate fractions for an exact saving rate instead of a lucky streak.
var credit := float(_ammo_credit.get(ammo_kind, 0.0)) + get_skill_bonus("efficient_magazine")
var saved := credit >= 1.0 - 0.000001
_ammo_credit[ammo_kind] = maxf(0.0, credit - 1.0) if saved else credit
return not saved
func tick_skills(delta: float) -> void:
for skill in skill_cooldowns:
skill_cooldowns[skill] = maxf(0.0, float(skill_cooldowns[skill]) - delta)
for skill in skill_durations:
skill_durations[skill] = maxf(0.0, float(skill_durations[skill]) - delta)
func is_skill_active(skill: String) -> bool:
return float(skill_durations.get(skill, 0.0)) > 0.0
func energy_regeneration_multiplier() -> float:
var bonus := 1.0
for skill in ["reactor_overload", "power_redirect"]:
if is_skill_active(skill):
bonus += get_skill_bonus(skill)
return bonus
func skill_unavailable_reason(skill: String, ship: CharacterBody2D) -> String:
if not SKILLS.DATA.has(skill) or not bool(SKILLS.DATA[skill]["active"]) or get_skill_rank(skill) <= 0:
return "Umiejętność nie jest odblokowana"
if skill not in equipped_skills:
return "Przypisz umiejętność do slotu F1–F5 w panelu kapitana"
if bool(ship.get("_is_destroyed")) or bool(ship.get("is_docked")):
return "Wymagany aktywny lot"
if float(skill_cooldowns.get(skill, 0.0)) > 0.0:
return "Odnowienie: %.1f s" % float(skill_cooldowns[skill])
if skill == "power_redirect" and (float(ship.get("max_shields")) <= 0.0 or float(ship.get("shields")) < float(ship.get("max_shields")) * 0.25):
return "Wymagane min. 25% tarcz"
return ""
func activate_skill(skill: String, ship: CharacterBody2D) -> bool:
if not skill_unavailable_reason(skill, ship).is_empty():
return false
skill_cooldowns[skill] = SKILLS.COOLDOWN
skill_durations[skill] = SKILLS.DURATION
if skill == "power_redirect":
ship.set("shields", 0.0)
ship.call("_update_shield_visual")
emit_changed()
return true
func add_rewards(xp_amount: int, credit_amount: int) -> void:
experience += maxi(0, xp_amount)
credits += maxi(0, credit_amount)
while experience >= get_xp_required():
experience -= get_xp_required()
level += 1
learning_points += BALANCE.LEARNING_POINTS_PER_LEVEL
prepare_skill_offers()
emit_changed()
func reward_enemy(system_level: int, tier: int, xp_multiplier: float, credit_multiplier: float, grant_credits: bool = true) -> Vector2i:
var index := clampi(tier, 0, 2)
var scale_factor := 1.0 + 0.05 * (clampi(system_level, 1, 1000) - 1)
var xp := roundi(BALANCE.KILL_XP[index] * scale_factor * maxf(0.0, xp_multiplier))
var money := roundi(BALANCE.KILL_CREDITS[index] * scale_factor * maxf(0.0, credit_multiplier))
add_rewards(xp, money if grant_credits else 0)
return Vector2i(xp, money)