Initial project sync

This commit is contained in:
wxm
2026-07-06 22:55:14 -07:00
commit 8cca00d0da
1066 changed files with 58585 additions and 0 deletions
+149
View File
@@ -0,0 +1,149 @@
class_name ActionExecutor
extends Node
const StatResolverScript := preload("res://scripts/resolvers/stat_resolver.gd")
signal action_executed(action: Resource, judgement: StringName)
signal action_failed(action: Resource, reason: StringName)
@export var energy_component_path: NodePath
@export var damage_emitter_path: NodePath
@onready var _energy_component: Node = get_node_or_null(energy_component_path)
@onready var _damage_emitter: Node = get_node_or_null(damage_emitter_path)
var last_failure_reason: StringName = &""
func execute(action: Resource, judgement: StringName, _stat_provider: Variant = null) -> bool:
last_failure_reason = &""
if action == null:
_fail(action, &"missing_action")
return false
if _action_requires_damage_emitter(action) and _damage_emitter == null:
_fail(action, &"missing_damage_emitter")
return false
if _action_requires_damage_emitter(action) and _damage_emitter != null and _damage_emitter.has_method("configure_hit"):
_damage_emitter.configure_hit(action, {"label": str(judgement)})
elif _damage_emitter != null and _damage_emitter.has_method("clear_hit"):
_damage_emitter.clear_hit()
if _action_spawns_projectile(action):
_request_projectile(action, judgement)
var reward := int(round(_resolve_reward(action, {"label": str(judgement)}, _stat_provider)))
if reward != 0 and _energy_component != null:
_energy_component.change(reward)
action_executed.emit(action, judgement)
return true
func finish_action() -> void:
if _damage_emitter != null and _damage_emitter.has_method("clear_hit"):
_damage_emitter.clear_hit()
elif _damage_emitter != null:
_damage_emitter.monitoring = false
func _fail(action: Resource, reason: StringName) -> void:
last_failure_reason = reason
action_failed.emit(action, reason)
func _action_requires_damage_emitter(action: Resource) -> bool:
if action == null:
return false
return StringName(str(action.get("hit_type"))) == &"melee"
func _action_spawns_projectile(action: Resource) -> bool:
return action != null and StringName(str(action.get("hit_type"))) == &"projectile"
func _resolve_cost(action: Resource, stat_provider: Variant) -> float:
var combat := _combat_manager_or_null()
if combat != null and combat.has_method("resolve_cost"):
return float(combat.call("resolve_cost", action, {}, stat_provider))
return float(action.get("base_cost"))
func _resolve_reward(action: Resource, judgement: Dictionary, stat_provider: Variant) -> float:
var combat := _combat_manager_or_null()
if combat != null and combat.has_method("resolve_reward"):
return float(combat.call("resolve_reward", action, judgement, stat_provider))
return StatResolverScript.resolve_reward(action, judgement, stat_provider)
func _request_projectile(action: Resource, judgement: StringName = &"perfect") -> void:
var owner := get_parent()
var context := {
"team": _projectile_team(action),
"action": action,
"judgement": {"label": str(judgement)},
"range": float(action.get("range")) if action != null else 0.0,
"attacker_interrupts": _owner_interrupt_authority(owner),
"source_actor": owner,
}
# Projectile base damage belongs to the shooter (player/minion/boss), not
# the projectile scene default.
if _damage_emitter != null:
context["base_damage"] = int(_damage_emitter.get("damage"))
if owner != null and owner.has_method("projectile_requests_for_action"):
var requests = owner.call("projectile_requests_for_action", action)
if requests is Array and not requests.is_empty():
for request: Variant in requests:
if request is Dictionary:
_emit_projectile_request(
request.get("projectile_scene", null) as PackedScene,
request.get("spawn_position", Vector2.ZERO) as Vector2,
request.get("direction", Vector2.RIGHT) as Vector2,
context
)
return
var spawn_position := Vector2.ZERO
var direction := Vector2.RIGHT
if owner is Node2D:
spawn_position = (owner as Node2D).global_position
var heading = owner.get("heading")
if heading is Vector2 and heading != Vector2.ZERO:
direction = heading
if owner != null and owner.has_method("projectile_spawn_position"):
spawn_position = owner.call("projectile_spawn_position", action)
if owner != null and owner.has_method("projectile_direction"):
direction = owner.call("projectile_direction", action)
_emit_projectile_request(null, spawn_position, direction, context)
func _owner_interrupt_authority(owner: Node) -> bool:
if owner != null and owner.has_method("is_strong_in_current_time_phase"):
return bool(owner.call("is_strong_in_current_time_phase"))
return true
func _projectile_team(action: Resource) -> StringName:
if action != null and action.get("action_tags") is Array:
for tag: Variant in action.get("action_tags"):
if StringName(str(tag)) == &"enemy":
return &"enemy"
return &"player"
func _emit_projectile_request(projectile_scene: PackedScene, spawn_position: Vector2, direction: Vector2, context: Dictionary = {}) -> void:
var bus := _event_bus_or_null()
if bus != null and bus.has_signal("projectile_requested"):
bus.emit_signal("projectile_requested", projectile_scene, spawn_position, direction, context)
func _combat_manager_or_null() -> Node:
if not is_inside_tree():
return null
return get_tree().root.get_node_or_null("CombatManager")
func _event_bus_or_null() -> Node:
if not is_inside_tree():
return null
var root := get_tree().root
var bus := root.get_node_or_null("EventBus")
if bus == null:
bus = load("res://autoload/event_bus.gd").new()
bus.name = "EventBus"
root.add_child(bus)
return bus