class_name ContactFxSpawner extends Node const OneShotFxScript := preload("res://scenes/combat/one_shot_fx.gd") const CONTACT_SPECS := { &"basic_1": {"path": "res://assets/art/characters/player/01_basic_attack_i/enemy_contact_fx.png", "hframes": 7, "vframes": 1, "first": 0, "last": 6, "fps": 20.0}, &"basic_2": {"path": "res://assets/art/characters/player/02_basic_attack_ii/enemy_contact_fx.png", "hframes": 7, "vframes": 1, "first": 0, "last": 6, "fps": 20.0}, &"basic_3": {"path": "res://assets/art/characters/player/03_basic_attack_iii/enemy_contact_fx.png", "hframes": 7, "vframes": 1, "first": 0, "last": 6, "fps": 20.0}, &"finisher": {"path": "res://assets/art/characters/player/05_combo_finisher/enemy_contact_fx.png", "hframes": 7, "vframes": 1, "first": 0, "last": 6, "fps": 20.0}, &"projectile": {"path": "res://assets/art/characters/player/11_blade_wave/projectile_enemy_contact.png", "hframes": 5, "vframes": 1, "first": 0, "last": 4, "fps": 20.0, "scale": 0.7}, } func _ready() -> void: var bus := _event_bus_or_null() if bus != null and bus.has_signal("hit_confirmed") and not bus.is_connected("hit_confirmed", _on_hit_confirmed): bus.connect("hit_confirmed", _on_hit_confirmed) func _exit_tree() -> void: var bus := _event_bus_or_null() if bus != null and bus.has_signal("hit_confirmed") and bus.is_connected("hit_confirmed", _on_hit_confirmed): bus.disconnect("hit_confirmed", _on_hit_confirmed) func _on_hit_confirmed(result: Dictionary) -> void: if int(result.get("damage", 0)) <= 0: return var emitter := result.get("emitter", null) as Area2D var receiver := result.get("receiver", null) as Area2D if emitter == null or receiver == null or not is_instance_valid(emitter) or not is_instance_valid(receiver): return if not _is_our_hit(emitter): return var target := receiver.get_parent() as Node2D if target == null: return var spec: Dictionary = CONTACT_SPECS.get(_spec_key_for(result), {}) if spec.is_empty(): return var config := spec.duplicate() var from := result.get("from", target.global_position) as Vector2 config["flip_h"] = from.x > target.global_position.x OneShotFxScript.spawn(target, target.global_position + Vector2(0.0, -70.0), config) func _is_our_hit(emitter: Area2D) -> bool: var actor := get_parent() if actor != null and emitter.get_parent() == actor: return true return emitter.is_in_group("player_projectiles") func _spec_key_for(result: Dictionary) -> StringName: if StringName(str(result.get("hit_type", &""))) == &"projectile": return &"projectile" var action: Resource = result.get("action", null) as Resource if action == null: return &"basic_1" var action_id := str(action.get("id")) if action_id.begins_with("ground_attack") and action_id.ends_with("_2"): return &"basic_2" if action_id.begins_with("ground_attack") and action_id.ends_with("_3"): return &"basic_3" if action_id.begins_with("combo_finisher") or action_id.begins_with("ground_smash"): return &"finisher" return &"basic_1" func _event_bus_or_null() -> Node: if not is_inside_tree(): return null return get_tree().root.get_node_or_null("EventBus")