Initial project sync
This commit is contained in:
@@ -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")
|
||||
Reference in New Issue
Block a user