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
+105
View File
@@ -0,0 +1,105 @@
class_name DamageEmitter
extends Area2D
@export var damage := 10
@export var hit_type: StringName = &"normal"
@export var base_knockback := Vector2(120.0, 304.056)
var action_context: Resource
var judgement_context: Dictionary = {}
## Weak-state minion attacks only deal damage; interrupt authority snapshots at
## action start so phase changes mid-swing do not rewrite the hit.
var attacker_interrupts := true
var _default_position := Vector2.ZERO
var _default_shape_size := Vector2.ZERO
# With per-frame hit windows (FrameCollisionDriver) the same receiver can
# enter the area more than once within one swing; each swing hits once.
var _already_hit_ids: Dictionary = {}
func _ready() -> void:
_default_position = position
var shape_node := get_node_or_null("CollisionShape2D") as CollisionShape2D
if shape_node != null and shape_node.shape is RectangleShape2D:
_default_shape_size = (shape_node.shape as RectangleShape2D).size
area_entered.connect(_on_area_entered)
func configure_hit(action: Resource, judgement: Dictionary) -> void:
action_context = action
judgement_context = judgement.duplicate()
attacker_interrupts = _owner_interrupt_authority()
_already_hit_ids.clear()
if action != null:
hit_type = StringName(str(action.get("hit_type")))
_update_hitbox_geometry(action)
monitoring = true
func clear_hit() -> void:
monitoring = false
action_context = null
judgement_context = {}
attacker_interrupts = true
_already_hit_ids.clear()
func _owner_interrupt_authority() -> bool:
var owner := get_parent()
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 has_already_hit(receiver: Node) -> bool:
return receiver != null and _already_hit_ids.has(receiver.get_instance_id())
func _update_hitbox_geometry(action: Resource) -> void:
if action == null:
position = _default_position
return
var range := float(action.get("range"))
if range <= 0.0:
position = _default_position
return
var shape_node := get_node_or_null("CollisionShape2D") as CollisionShape2D
if shape_node != null and shape_node.shape is RectangleShape2D:
var rectangle := shape_node.shape as RectangleShape2D
var height := _default_shape_size.y if _default_shape_size != Vector2.ZERO else rectangle.size.y
rectangle.size = Vector2(maxf(8.0, range), maxf(8.0, height))
var owner := get_parent()
var direction := 1.0
if owner != null:
var heading = owner.get("heading")
if heading is Vector2 and absf((heading as Vector2).x) > 0.0:
direction = -1.0 if (heading as Vector2).x < 0.0 else 1.0
var base_y := _default_position.y
position = Vector2(direction * (range * 0.5 + 10.0), base_y)
func _on_area_entered(receiver: Area2D) -> void:
if receiver.is_in_group("damage_receivers"):
if _already_hit_ids.has(receiver.get_instance_id()):
return
_already_hit_ids[receiver.get_instance_id()] = true
var combat := _combat_manager_or_null()
if combat != null and combat.has_method("resolve_hit"):
var result: Dictionary = combat.call("resolve_hit", self, receiver)
_event_bus().emit_signal("damage_dealt", receiver, int(result.get("damage", 0)), hit_type)
func _event_bus() -> Node:
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
func _combat_manager_or_null() -> Node:
if not is_inside_tree():
return null
return get_tree().root.get_node_or_null("CombatManager")