Appearance sets

This commit is contained in:
gamer147
2026-04-09 07:58:58 -04:00
parent b807e9897d
commit 6b46d1c274
23 changed files with 445 additions and 81 deletions

View File

@@ -22,7 +22,7 @@ func create_proposal(attacker: DeployedUnit, defender: DeployedUnit, distance: i
func _filter_tactics(deployed: DeployedUnit, distance: int) -> Array[CombatTactic]:
var valid: Array[CombatTactic] = []
for tactic in deployed.tactics:
if tactic.tactic_range and tactic.tactic_range.is_valid_range(distance, deployed.unit):
if tactic.tactic_range and tactic.tactic_range.is_valid_range(distance, deployed.current_stats):
valid.append(tactic)
return valid
@@ -35,27 +35,29 @@ func _find_default_attack(tactics: Array[CombatTactic]) -> CombatTactic:
func _snapshot(deployed: DeployedUnit, opponent: DeployedUnit, available: Array[CombatTactic], selected: CombatTactic, opponent_selected: CombatTactic) -> CombatProposal.CombatantStats:
var current := deployed.current_stats
var opp_current := opponent.current_stats
var stats := CombatProposal.CombatantStats.new()
stats.deployed = deployed
stats.max_hp = deployed.unit.stats.max_hp
stats.hp = deployed.current_stats.current_hp
stats.sp = deployed.current_stats.current_sp
stats.spd = deployed.unit.stats.spd
stats.max_hp = current.max_hp
stats.hp = current.current_hp
stats.sp = current.current_sp
stats.spd = current.spd
stats.available_tactics = available
stats.selected_tactic = selected
if selected and selected.deals_damage():
var offensive: Dictionary = selected.get_offensive_stats(deployed.unit)
var offensive: Dictionary = selected.get_offensive_stats(current)
stats.atk = offensive["atk"]
stats.hit = offensive["hit"] - opponent.unit.stats.eva
stats.hit = offensive["hit"] - opp_current.eva
else:
stats.atk = 0
stats.hit = 0
if opponent_selected and opponent_selected.deals_damage():
stats.def = opponent_selected.get_relevant_defense(deployed.unit)
stats.def = opponent_selected.get_relevant_defense(current)
else:
stats.def = deployed.unit.stats.phys_def
stats.def = current.phys_def
return stats
@@ -74,18 +76,18 @@ func update_tactic(proposal: CombatProposal, is_attacker: bool, tactic: CombatTa
# Recalculate this side's offensive stats
if tactic and tactic.deals_damage():
var offensive: Dictionary = tactic.get_offensive_stats(self_stats.deployed.unit)
var offensive: Dictionary = tactic.get_offensive_stats(self_stats.deployed.current_stats)
self_stats.atk = offensive["atk"]
self_stats.hit = offensive["hit"] - opp_stats.deployed.unit.stats.eva
self_stats.hit = offensive["hit"] - opp_stats.deployed.current_stats.eva
else:
self_stats.atk = 0
self_stats.hit = 0
# Recalculate opponent's def based on this side's new tactic
if tactic and tactic.deals_damage():
opp_stats.def = tactic.get_relevant_defense(opp_stats.deployed.unit)
opp_stats.def = tactic.get_relevant_defense(opp_stats.deployed.current_stats)
else:
opp_stats.def = opp_stats.deployed.unit.stats.phys_def
opp_stats.def = opp_stats.deployed.current_stats.phys_def
func select_ai_tactic(deployed: DeployedUnit, opponent: DeployedUnit, available_tactics: Array[CombatTactic]) -> CombatTactic:
@@ -95,8 +97,8 @@ func select_ai_tactic(deployed: DeployedUnit, opponent: DeployedUnit, available_
for tactic in available_tactics:
if not tactic.deals_damage():
continue
var offensive: Dictionary = tactic.get_offensive_stats(deployed.unit)
var defense: int = tactic.get_relevant_defense(opponent.unit)
var offensive: Dictionary = tactic.get_offensive_stats(deployed.current_stats)
var defense: int = tactic.get_relevant_defense(opponent.current_stats)
var damage := maxi(offensive["atk"] - defense, 0)
if damage > best_damage:
best_damage = damage

View File

@@ -3,11 +3,11 @@ class_name CombatTactic extends Resource
@export var tactic_name: String = ""
@export var tactic_range: CombatTacticRange
func get_offensive_stats(_unit: Unit) -> Variant:
func get_offensive_stats(_stats: DeployedUnitStats) -> Variant:
return null
func get_relevant_defense(unit: Unit) -> int:
return unit.stats.phys_def
func get_relevant_defense(stats: DeployedUnitStats) -> int:
return stats.phys_def
func deals_damage() -> bool:
return false

View File

@@ -1,5 +1,5 @@
# resources/resource_definitions/any_combat_tactic_range.gd
class_name AnyCombatTacticRange extends CombatTacticRange
func is_valid_range(_distance: int, _unit: Unit) -> bool:
func is_valid_range(_distance: int, _stats: DeployedUnitStats) -> bool:
return true

View File

@@ -1,5 +1,5 @@
# resources/resource_definitions/combat_tactic_range.gd
class_name CombatTacticRange extends Resource
func is_valid_range(_distance: int, _unit: Unit) -> bool:
func is_valid_range(_distance: int, _stats: DeployedUnitStats) -> bool:
return false

View File

@@ -3,5 +3,5 @@ class_name FixedCombatTacticRange extends CombatTacticRange
@export var tactic_range: int = 1
func is_valid_range(distance: int, unit: Unit) -> bool:
func is_valid_range(distance: int, _stats: DeployedUnitStats) -> bool:
return distance <= tactic_range

View File

@@ -1,5 +1,5 @@
# resources/resource_definitions/unit_matching_combat_tactic_range.gd
class_name UnitMatchingCombatTacticRange extends CombatTacticRange
func is_valid_range(distance: int, unit: Unit) -> bool:
return distance <= unit.stats.atk_range
func is_valid_range(distance: int, stats: DeployedUnitStats) -> bool:
return distance <= stats.atk_range

View File

@@ -1,10 +1,10 @@
class_name AttackCombatTactic extends CombatTactic
func get_offensive_stats(unit: Unit) -> Variant:
return {"atk": unit.stats.phys_atk, "hit": unit.stats.hit}
func get_offensive_stats(stats: DeployedUnitStats) -> Variant:
return {"atk": stats.phys_atk, "hit": stats.hit}
func get_relevant_defense(unit: Unit) -> int:
return unit.stats.phys_def
func get_relevant_defense(stats: DeployedUnitStats) -> int:
return stats.phys_def
func deals_damage() -> bool:
return true

View File

@@ -1,10 +1,10 @@
class_name DefendCombatTactic extends CombatTactic
func get_offensive_stats(_unit: Unit) -> Variant:
func get_offensive_stats(_stats: DeployedUnitStats) -> Variant:
return null
func get_relevant_defense(unit: Unit) -> int:
return unit.stats.phys_def
func get_relevant_defense(stats: DeployedUnitStats) -> int:
return stats.phys_def
func deals_damage() -> bool:
return false

View File

@@ -59,7 +59,7 @@ func _on_unit_died(deployed: DeployedUnit) -> void:
func _process(_delta: float) -> void:
if _selected_unit and is_instance_valid(_selected_unit):
hp_bar.max_value = _selected_unit.unit.stats.max_hp
hp_bar.max_value = _selected_unit.current_stats.max_hp
hp_bar.value = _selected_unit.current_stats.current_hp
func _unhandled_input(event: InputEvent) -> void:
@@ -72,7 +72,7 @@ func _on_unit_selected_changed(deployed: DeployedUnit, selected: bool) -> void:
if selected:
_selected_unit = deployed
name_label.text = deployed.unit.info.name
hp_bar.max_value = deployed.unit.stats.max_hp
hp_bar.max_value = deployed.current_stats.max_hp
hp_bar.value = deployed.current_stats.current_hp
unit_panel.visible = true
else:

View File

@@ -8,6 +8,9 @@ var current_stats: DeployedUnitStats
var tactics: Array[CombatTactic] = []
var state: UnitState = UnitState.ALIVE
var _sprite: AnimatedSprite2D
var _previous_position: Vector2
signal unit_selected_changed(deployed: DeployedUnit, selected: bool)
signal unit_allegiance_changed(deployed: DeployedUnit, allegiance: UnitAllegiance)
signal unit_died(deployed: DeployedUnit)
@@ -17,6 +20,35 @@ func _ready() -> void:
tactics = unit.tactics.duplicate()
_append_builtin_tactics()
unit_allegiance_changed.emit(self, unit.allegiance)
_previous_position = position
_setup_appearance()
func _setup_appearance() -> void:
_sprite = get_node_or_null("AnimatedSprite2D") as AnimatedSprite2D
if not _sprite:
return
var sprite_frames: SpriteFrames = unit.appearance.deployed_sprite_sheet if unit.appearance else null
if not sprite_frames:
return
_sprite.sprite_frames = sprite_frames
_sprite.play("idle")
func _physics_process(_delta: float) -> void:
if not _sprite or not _sprite.sprite_frames:
return
var delta_pos := position - _previous_position
_previous_position = position
if delta_pos.length_squared() < 0.01:
if _sprite.animation != &"idle":
_sprite.play("idle")
return
var anim: StringName
if absf(delta_pos.x) >= absf(delta_pos.y):
anim = &"right" if delta_pos.x > 0 else &"left"
else:
anim = &"down" if delta_pos.y > 0 else &"up"
if _sprite.animation != anim:
_sprite.play(anim)
func _append_builtin_tactics() -> void:
var attack := AttackCombatTactic.new()

View File

@@ -5,6 +5,35 @@ class_name DeployedUnitStats extends Resource
@export var current_sp: int
@export var current_fs: int
# Passthrough accessors. Future buff/debuff layers can override these
# without mutating the underlying UnitStats template.
var max_hp: int:
get: return unit_stats.max_hp
var max_sp: int:
get: return unit_stats.max_sp
var max_fs: int:
get: return unit_stats.max_fs
var phys_atk: int:
get: return unit_stats.phys_atk
var phys_def: int:
get: return unit_stats.phys_def
var magic_atk: int:
get: return unit_stats.magic_atk
var magic_def: int:
get: return unit_stats.magic_def
var hit: int:
get: return unit_stats.hit
var atk_range: int:
get: return unit_stats.atk_range
var spd: int:
get: return unit_stats.spd
var eva: int:
get: return unit_stats.eva
var lck: int:
get: return unit_stats.lck
var mov: int:
get: return unit_stats.mov
static func from_unit_stats(source: UnitStats) -> DeployedUnitStats:
var stats := DeployedUnitStats.new()
stats.unit_stats = source

View File

@@ -4,3 +4,4 @@ class_name Unit extends Resource
@export var info: UnitInfo
@export var allegiance: UnitAllegiance
@export var tactics: Array[CombatTactic] = []
@export var appearance: UnitAppearance

View File

@@ -0,0 +1,19 @@
class_name UnitAppearance extends Resource
@export var default_appearance_key: String = "default"
@export var current_appearance_key: String
@export var appearance_sets: Dictionary[String, UnitAppearanceSet]
var deployed_sprite_sheet: SpriteFrames:
get:
var key: String = current_appearance_key
if(key == null or key == ""):
key = default_appearance_key
if(!appearance_sets.has(key)):
return null
var appearance_set: UnitAppearanceSet = appearance_sets[key]
var sprite_value = appearance_set.deployed_sprite_sheet
if(sprite_value == null):
sprite_value = appearance_sets[default_appearance_key].deployed_sprite_sheet
return sprite_value

View File

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

View File

@@ -0,0 +1,3 @@
class_name UnitAppearanceSet extends Resource
@export var deployed_sprite_sheet: SpriteFrames

View File

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