diff --git a/addons/event_audio/EventAudio.cs b/addons/event_audio/EventAudio.cs
new file mode 100644
index 0000000..e52d9f0
--- /dev/null
+++ b/addons/event_audio/EventAudio.cs
@@ -0,0 +1,55 @@
+using Godot;
+
+public partial class EventAudio : Node {
+ static private EventAudio _instance;
+ private GodotObject _eventAudio;
+ private Callable _play3DMethod;
+ private Callable _play2DMethod;
+ private Callable _stopMethod;
+
+ // Simple 'handle' class for managing Godot audio emitters.
+ public class EventAudioEmitter {
+ public GodotObject Emitter;
+
+ public EventAudioEmitter(GodotObject emitter) {
+ Emitter = emitter;
+ }
+ }
+
+ public override void _Ready() {
+ base._Ready();
+ _eventAudio = GetParent().GetNode("EventAudio");
+ _play3DMethod = _eventAudio.Get("play_3d").AsCallable();
+ _play2DMethod = _eventAudio.Get("play_2d").AsCallable();
+ _stopMethod = _eventAudio.Get("stop").AsCallable();
+ _instance = this;
+ }
+
+ public static EventAudio Instance {
+ get {
+ return _instance;
+ }
+ }
+
+ public EventAudioEmitter Play2D(string trigger, Node3D source) {
+ var result = _play2DMethod.Call(trigger, source);
+ var godotEmitter = result.AsGodotObject();
+ if (godotEmitter != null) {
+ return new EventAudioEmitter(godotEmitter);
+ }
+ return null;
+ }
+
+ public EventAudioEmitter Play3D(string trigger, Node3D source) {
+ var result = _play3DMethod.Call(trigger, source);
+ var godotEmitter = result.AsGodotObject();
+ if (godotEmitter != null) {
+ return new EventAudioEmitter(godotEmitter);
+ }
+ return null;
+ }
+
+ public void Stop(EventAudioEmitter emitter) {
+ _stopMethod.Call(emitter.Emitter);
+ }
+}
diff --git a/addons/event_audio/event_audio.gd b/addons/event_audio/event_audio.gd
new file mode 100644
index 0000000..1a82bfd
--- /dev/null
+++ b/addons/event_audio/event_audio.gd
@@ -0,0 +1,207 @@
+extends Node
+
+# Ideally this would be called just EventAudio, but that would class with the autoload
+class_name EventAudioAPI
+static var _separator := "+"
+static var instance : EventAudioAPI
+
+@export var log_lookups := false
+@export var log_deaths := false
+@export var log_registrations := false
+
+var _trigger_map: Dictionary
+var _rng: RandomNumberGenerator
+var _audio_banks: Array[EAEventBank]
+
+class AudioEmitter2D:
+ var source: Node2D
+ var player: AudioStreamPlayer2D
+ var event: EAEvent
+
+var _active_emitters_2d = Array()
+
+class AudioEmitter3D:
+ var source: Node3D
+ var player: AudioStreamPlayer3D
+ var event: EAEvent
+
+var _active_emitters_3d = Array()
+
+#---------------------------------------------------------
+# API
+#---------------------------------------------------------
+static func get_instance() -> EventAudioAPI:
+ return instance
+
+func play_2d(trigger: String, source: Node2D) -> AudioEmitter2D:
+ var event := _find_event_for_trigger(trigger)
+ if event == null:
+ return null
+
+ var stream_player = AudioStreamPlayer2D.new()
+ return _play_event(event, stream_player, source)
+
+func play_3d(trigger: String, source: Node3D) -> AudioEmitter3D:
+ var event := _find_event_for_trigger(trigger)
+ if event == null:
+ return null
+
+ var stream_player = AudioStreamPlayer3D.new()
+ return _play_event(event, stream_player, source)
+
+func stop(emitter):
+ if emitter.player != null:
+ emitter.player.stop()
+
+func register_event_bank(bank: EAEventBank):
+ if log_registrations:
+ print("Registering bank: " + bank.resource_path)
+ _audio_banks.append(bank)
+ _invalidate_trigger_map()
+
+func unregister_event_bank(bank: EAEventBank):
+ if log_registrations:
+ print("Unregistering bank: " + bank.resource_name)
+ var idx := _audio_banks.find(bank)
+ if idx >= 0:
+ _audio_banks.remove_at(idx)
+ _invalidate_trigger_map()
+
+static func init_player_from_playback_settings(rng, stream_player, settings: EAEventPlaybackSettings):
+ var min_pitch := min(settings.min_pitch, settings.max_pitch) as float
+ var max_pitch := max(settings.min_pitch, settings.max_pitch) as float
+ var pitch = rng.randf_range(min_pitch, max_pitch)
+ stream_player.pitch_scale = pitch
+ stream_player.volume_db = settings.volume_db
+
+ if stream_player is AudioStreamPlayer3D:
+ stream_player.unit_size = settings.unit_size
+ stream_player.max_db = settings.max_db
+ stream_player.panning_strength = settings.panning_strength
+
+ elif stream_player is AudioStreamPlayer2D:
+ stream_player.max_distance = settings.max_distance
+ stream_player.attenuation = settings.attenuation
+ stream_player.panning_strength = settings.panning_strength
+
+#---------------------------------------------------------
+func _init():
+ _rng = RandomNumberGenerator.new()
+
+func _process(_delta: float):
+ _active_emitters_2d = _process_active_audio(_active_emitters_2d)
+ _active_emitters_3d = _process_active_audio(_active_emitters_3d)
+
+func _process_active_audio(active_audio):
+ var new_active_audio := Array()
+
+ # TODO - find a better way of modifying the list of active audio emitters
+ for audio in active_audio:
+ var alive := true
+ if audio.player == null:
+ alive = false
+ elif not audio.player.playing:
+ audio.player.queue_free()
+ audio.player = null
+ alive = false
+ elif audio.source == null:
+ if audio.event.playback_settings.stop_when_source_dies:
+ audio.player.stop()
+ alive = false
+
+ # Update the position
+ if not audio.event.playback_settings.stationary and alive and audio.source != null:
+ audio.player.global_position = audio.source.global_position
+
+ if alive:
+ new_active_audio.append(audio)
+ else:
+ _log_death(audio.event.trigger_tags)
+ return new_active_audio
+
+func _enter_tree():
+ instance = self
+
+func _exit_tree():
+ instance = null
+
+#---------------------------------------------------------------------------------
+# Internals
+#---------------------------------------------------------------------------------
+func _play_event(event: EAEvent, stream_player, source: Node):
+ var stream := event.get_weighted_random_stream(_rng.randf())
+ stream_player.name = "AudioPlayback"
+ add_child(stream_player)
+ stream_player.stream = stream
+
+ EventAudioAPI.init_player_from_playback_settings(_rng, stream_player, event.playback_settings)
+
+ if source:
+ stream_player.global_position = source.global_position
+
+ stream_player.play()
+
+ if stream_player is AudioStreamPlayer2D:
+ var emitter := AudioEmitter2D.new()
+ emitter.player = stream_player
+ emitter.source = source
+ emitter.event = event
+ _active_emitters_2d.append(emitter)
+ return emitter
+ else:
+ var emitter = AudioEmitter3D.new()
+ emitter.player = stream_player
+ emitter.source = source
+ emitter.event = event
+ _active_emitters_3d.append(emitter)
+ return emitter
+
+
+func _invalidate_trigger_map():
+ _trigger_map = {}
+
+func _make_trigger_map():
+ _trigger_map = {}
+ for bank: EAEventBank in _audio_banks:
+ for entry in bank.entries:
+ var key = entry.trigger_tags
+ _trigger_map[key] = entry
+
+func _find_event_for_trigger(trigger: String) -> EAEvent:
+ if _trigger_map.size() == 0:
+ _make_trigger_map()
+
+ var current_trigger := trigger
+
+ while current_trigger != "":
+ _log_lookup(current_trigger)
+ var found_entry := _trigger_map.get(current_trigger) as EAEvent
+ if found_entry:
+ _log_found(found_entry.trigger_tags)
+ return found_entry
+ var tag_pos := current_trigger.rfind(_separator)
+ if tag_pos >= 0:
+ current_trigger = current_trigger.substr(0, tag_pos)
+ else:
+ current_trigger = ""
+ return null
+
+func _log_lookup(msg: String):
+ if log_lookups:
+ print("Trying " + msg)
+
+func _log_found(msg: String):
+ if log_lookups:
+ print("Found " + msg)
+
+func _log_bank_add(msg: String):
+ if log_registrations:
+ print("Registering Bank " + msg)
+
+func _log_bank_remove(msg: String):
+ if log_registrations:
+ print("Unregistering Bank " + msg)
+
+func _log_death(msg: String):
+ if log_deaths:
+ print("Killing " + msg)
diff --git a/addons/event_audio/example/ExampleEmitter3D.cs b/addons/event_audio/example/ExampleEmitter3D.cs
new file mode 100644
index 0000000..6886977
--- /dev/null
+++ b/addons/event_audio/example/ExampleEmitter3D.cs
@@ -0,0 +1,59 @@
+using Godot;
+
+
+public partial class ExampleEmitter3D : Node3D {
+ [Export]
+ float Speed = 1.0f;
+ [Export]
+ Node3D OrbitNode;
+ EventAudio.EventAudioEmitter _loopEmitter;
+ float _orbitRadius = 1.0f;
+
+ public override void _Ready() {
+ _orbitRadius = (GlobalPosition - OrbitNode.GlobalPosition).Length();
+ }
+
+ public override void _Process(double _delta) {
+ float orbitAngle = (Time.GetTicksMsec() / 1000.0f % Speed) * 2.0f * 3.14159f;
+ float offset_x = _orbitRadius * Mathf.Cos(orbitAngle);
+ float offset_y = _orbitRadius * Mathf.Sin(orbitAngle);
+
+ var new_position = OrbitNode.GlobalPosition;
+ new_position.X += offset_x;
+ new_position.Z += offset_y;
+
+ GlobalPosition = new_position;
+ }
+
+ public override void _Input(InputEvent ev_) {
+ if (!(ev_ is InputEventKey) || !ev_.IsPressed()) {
+ return;
+ }
+
+ var ev = ev_ as InputEventKey;
+ if (ev.Keycode == Key.Key1) {
+ EventAudio.Instance.Play3D("hit", this);
+ }
+
+ if (ev.Keycode == Key.Key2) {
+ EventAudio.Instance.Play3D("hit+large", this);
+ }
+
+ if (ev.Keycode == Key.Key3) {
+ EventAudio.Instance.Play3D("hit+nonexistent", this);
+ }
+
+ if (ev.Keycode == Key.Key4) {
+ EventAudio.Instance.Play3D("random_shoot", this);
+ }
+
+ if (ev.Keycode == Key.Key5) {
+ if (_loopEmitter != null) {
+ EventAudio.Instance.Stop(_loopEmitter);
+ _loopEmitter = null;
+ } else {
+ _loopEmitter = EventAudio.Instance.Play3D("loop", this);
+ }
+ }
+ }
+}
diff --git a/addons/event_audio/example/csharp_example_3D.tscn b/addons/event_audio/example/csharp_example_3D.tscn
new file mode 100644
index 0000000..a539958
--- /dev/null
+++ b/addons/event_audio/example/csharp_example_3D.tscn
@@ -0,0 +1,31 @@
+[gd_scene load_steps=6 format=3 uid="uid://fk1nqp2seuw6"]
+
+[ext_resource type="Script" path="res://addons/event_audio/src/ea_event_bank_mounter.gd" id="1_0y6i8"]
+[ext_resource type="Resource" uid="uid://cfxmacq8po281" path="res://addons/event_audio/example/example_audio_bank.tres" id="2_qo1e5"]
+[ext_resource type="Script" path="res://addons/event_audio/example/ExampleEmitter3D.cs" id="3_w1shi"]
+[ext_resource type="PackedScene" uid="uid://kjsaalyhmkc5" path="res://addons/event_audio/example/example_ui.tscn" id="4_e42vy"]
+
+[sub_resource type="SphereMesh" id="SphereMesh_3bdsu"]
+
+[node name="Example3d" type="Node3D"]
+
+[node name="Camera3D" type="Camera3D" parent="."]
+
+[node name="Node3D" type="Node3D" parent="." node_paths=PackedStringArray("OrbitNode")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -4.70263)
+script = ExtResource("3_w1shi")
+OrbitNode = NodePath("../Camera3D")
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="Node3D"]
+mesh = SubResource("SphereMesh_3bdsu")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 0.176947, 0.98422, 0, -0.98422, 0.176947, 0, 1.9958, 0)
+
+[node name="ExampleAudioBank" type="Node" parent="."]
+script = ExtResource("1_0y6i8")
+_audio_bank_resource = ExtResource("2_qo1e5")
+
+[node name="CanvasLayer" type="CanvasLayer" parent="."]
+
+[node name="ExampleUi" parent="CanvasLayer" instance=ExtResource("4_e42vy")]
diff --git a/addons/event_audio/example/example2D.tscn b/addons/event_audio/example/example2D.tscn
new file mode 100644
index 0000000..44bc846
--- /dev/null
+++ b/addons/event_audio/example/example2D.tscn
@@ -0,0 +1,33 @@
+[gd_scene load_steps=6 format=3 uid="uid://konnaekcsk2e"]
+
+[ext_resource type="Script" path="res://addons/event_audio/example/example_emitter_2d.gd" id="1_7u4pm"]
+[ext_resource type="Resource" uid="uid://cfxmacq8po281" path="res://addons/event_audio/example/example_audio_bank.tres" id="3_kb4ey"]
+[ext_resource type="Script" path="res://addons/event_audio/src/ea_event_bank_mounter.gd" id="3_q1afy"]
+[ext_resource type="PackedScene" uid="uid://kjsaalyhmkc5" path="res://addons/event_audio/example/example_ui.tscn" id="4_kisj8"]
+
+[sub_resource type="SphereMesh" id="SphereMesh_7scev"]
+radius = 10.0
+height = 20.0
+
+[node name="Example2d" type="Node2D"]
+
+[node name="Emitter" type="MeshInstance2D" parent="."]
+position = Vector2(2.08165e-12, 175.95)
+mesh = SubResource("SphereMesh_7scev")
+script = ExtResource("1_7u4pm")
+
+[node name="Camera2D" type="Camera2D" parent="."]
+position = Vector2(578, 324)
+
+[node name="AudioListener2D" type="AudioListener2D" parent="Camera2D"]
+current = true
+
+[node name="TestAudioBank" type="Node" parent="."]
+script = ExtResource("3_q1afy")
+_audio_bank_resource = ExtResource("3_kb4ey")
+
+[node name="AudioStreamPlayer2D" type="AudioStreamPlayer2D" parent="."]
+
+[node name="CanvasLayer" type="CanvasLayer" parent="."]
+
+[node name="ExampleUi" parent="CanvasLayer" instance=ExtResource("4_kisj8")]
diff --git a/addons/event_audio/example/example3D.tscn b/addons/event_audio/example/example3D.tscn
new file mode 100644
index 0000000..ed362d0
--- /dev/null
+++ b/addons/event_audio/example/example3D.tscn
@@ -0,0 +1,31 @@
+[gd_scene load_steps=6 format=3 uid="uid://b12tpedbx38u7"]
+
+[ext_resource type="Script" path="res://addons/event_audio/src/ea_event_bank_mounter.gd" id="1_ncilt"]
+[ext_resource type="Resource" uid="uid://cfxmacq8po281" path="res://addons/event_audio/example/example_audio_bank.tres" id="2_nj06b"]
+[ext_resource type="Script" path="res://addons/event_audio/example/example_emitter_3d.gd" id="3_wpepl"]
+[ext_resource type="PackedScene" uid="uid://kjsaalyhmkc5" path="res://addons/event_audio/example/example_ui.tscn" id="4_sw6y2"]
+
+[sub_resource type="SphereMesh" id="SphereMesh_3bdsu"]
+
+[node name="Example3d" type="Node3D"]
+
+[node name="Camera3D" type="Camera3D" parent="."]
+
+[node name="Node3D" type="Node3D" parent="." node_paths=PackedStringArray("OrbitNode")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -4.70263)
+script = ExtResource("3_wpepl")
+OrbitNode = NodePath("../Camera3D")
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="Node3D"]
+mesh = SubResource("SphereMesh_3bdsu")
+
+[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 0.176947, 0.98422, 0, -0.98422, 0.176947, 0, 1.9958, 0)
+
+[node name="TestAudioBank" type="Node" parent="."]
+script = ExtResource("1_ncilt")
+_audio_bank_resource = ExtResource("2_nj06b")
+
+[node name="CanvasLayer" type="CanvasLayer" parent="."]
+
+[node name="ExampleUi" parent="CanvasLayer" instance=ExtResource("4_sw6y2")]
diff --git a/addons/event_audio/example/example_audio_bank.tres b/addons/event_audio/example/example_audio_bank.tres
new file mode 100644
index 0000000..fc85f13
--- /dev/null
+++ b/addons/event_audio/example/example_audio_bank.tres
@@ -0,0 +1,94 @@
+[gd_resource type="Resource" script_class="EAEventBank" load_steps=17 format=3 uid="uid://cfxmacq8po281"]
+
+[ext_resource type="Script" path="res://addons/event_audio/src/ea_event.gd" id="1_fy8lm"]
+[ext_resource type="AudioStream" uid="uid://7ncmg0ox4nxr" path="res://addons/event_audio/example/sounds/loop.wav" id="2_fyotq"]
+[ext_resource type="Script" path="res://addons/event_audio/src/ea_event_bank.gd" id="2_rbntb"]
+[ext_resource type="Script" path="res://addons/event_audio/src/ea_event_playback_settings.gd" id="3_fy5gc"]
+[ext_resource type="AudioStream" uid="uid://byg26ypxea5qh" path="res://addons/event_audio/example/sounds/laser1.wav" id="3_nyuuq"]
+[ext_resource type="AudioStream" uid="uid://ftyi4ikfmbii" path="res://addons/event_audio/example/sounds/laser2.wav" id="4_ahbca"]
+[ext_resource type="AudioStream" uid="uid://cqo6bi2ygpwbu" path="res://addons/event_audio/example/sounds/shot1.wav" id="4_fpv34"]
+[ext_resource type="AudioStream" uid="uid://cultflwybpfo1" path="res://addons/event_audio/example/sounds/shot2.wav" id="5_1yf0v"]
+
+[sub_resource type="Resource" id="Resource_dd35s"]
+script = ExtResource("3_fy5gc")
+stop_when_source_dies = false
+stationary = true
+volume_db = 0.0
+min_pitch = 0.8
+max_pitch = 1.2
+panning_strength = 1.0
+attenuation = 1.0
+max_distance = 2000
+unit_size = 10.0
+max_db = 3.0
+
+[sub_resource type="Resource" id="Resource_lfo5e"]
+script = ExtResource("1_fy8lm")
+audio_streams = Array[AudioStream]([ExtResource("4_fpv34")])
+probability_weights = Array[float]([1.0])
+trigger_tags = "hit"
+playback_settings = SubResource("Resource_dd35s")
+
+[sub_resource type="Resource" id="Resource_j54ca"]
+script = ExtResource("3_fy5gc")
+stop_when_source_dies = false
+stationary = true
+volume_db = 0.0
+min_pitch = 0.9
+max_pitch = 1.2
+panning_strength = 1.0
+attenuation = 1.0
+max_distance = 2000
+unit_size = 10.0
+max_db = 3.0
+
+[sub_resource type="Resource" id="Resource_d2op2"]
+script = ExtResource("1_fy8lm")
+audio_streams = Array[AudioStream]([ExtResource("5_1yf0v")])
+probability_weights = Array[float]([1.0])
+trigger_tags = "hit+large"
+playback_settings = SubResource("Resource_j54ca")
+
+[sub_resource type="Resource" id="Resource_ljdcd"]
+script = ExtResource("3_fy5gc")
+stop_when_source_dies = false
+stationary = false
+volume_db = 0.0
+min_pitch = 0.8
+max_pitch = 1.2
+panning_strength = 2.0
+attenuation = 1.0
+max_distance = 2000
+unit_size = 10.0
+max_db = 3.0
+
+[sub_resource type="Resource" id="Resource_ps0cx"]
+script = ExtResource("1_fy8lm")
+audio_streams = Array[AudioStream]([ExtResource("2_fyotq")])
+probability_weights = Array[float]([1.0])
+trigger_tags = "loop"
+playback_settings = SubResource("Resource_ljdcd")
+
+[sub_resource type="Resource" id="Resource_1xrvu"]
+script = ExtResource("3_fy5gc")
+stop_when_source_dies = false
+stationary = true
+volume_db = 0.0
+min_pitch = 0.8
+max_pitch = 1.2
+panning_strength = 1.0
+attenuation = 1.0
+max_distance = 2000
+unit_size = 10.0
+max_db = 3.0
+
+[sub_resource type="Resource" id="Resource_xjlcv"]
+script = ExtResource("1_fy8lm")
+audio_streams = Array[AudioStream]([ExtResource("3_nyuuq"), ExtResource("4_ahbca")])
+probability_weights = Array[float]([1.0, 1.0])
+trigger_tags = "random_shoot"
+playback_settings = SubResource("Resource_1xrvu")
+
+[resource]
+script = ExtResource("2_rbntb")
+entries = Array[ExtResource("1_fy8lm")]([SubResource("Resource_lfo5e"), SubResource("Resource_d2op2"), SubResource("Resource_ps0cx"), SubResource("Resource_xjlcv")])
diff --git a/addons/event_audio/example/example_emitter_2d.gd b/addons/event_audio/example/example_emitter_2d.gd
new file mode 100644
index 0000000..2c13293
--- /dev/null
+++ b/addons/event_audio/example/example_emitter_2d.gd
@@ -0,0 +1,48 @@
+extends Node2D
+class_name ExampleEmitter2D
+
+@export var Speed := 1.0
+var _loop_emitter : EventAudioAPI.AudioEmitter2D
+
+func _init():
+ EventAudio.log_lookups = true
+ EventAudio.log_registrations = true
+ EventAudio.log_deaths = true
+
+func _process(delta: float):
+ var screen_width := get_viewport_rect().size.x
+
+ var step := Speed * screen_width * delta
+ var new_position := global_position
+ new_position.x += step
+ if new_position.x < 0:
+ new_position.x = 0
+ Speed = -Speed
+ elif new_position.x >= screen_width:
+ new_position.x = screen_width
+ Speed = -Speed
+
+ global_position = new_position
+
+func _input(event: InputEvent):
+ if not event is InputEventKey or not event.is_pressed():
+ return
+
+ if event.keycode == KEY_1:
+ EventAudio.play_2d("hit", self)
+
+ if event.keycode == KEY_2:
+ EventAudio.play_2d("hit+large", self)
+
+ if event.keycode == KEY_3:
+ EventAudio.play_2d("hit+nonexistent", self)
+
+ if event.keycode == KEY_4:
+ EventAudio.play_2d("random_shoot", self)
+
+ if event.keycode == KEY_5:
+ if _loop_emitter:
+ EventAudio.stop(_loop_emitter)
+ _loop_emitter = null
+ else:
+ _loop_emitter = EventAudio.play_2d("loop", self)
diff --git a/addons/event_audio/example/example_emitter_3d.gd b/addons/event_audio/example/example_emitter_3d.gd
new file mode 100644
index 0000000..a2b4dee
--- /dev/null
+++ b/addons/event_audio/example/example_emitter_3d.gd
@@ -0,0 +1,50 @@
+extends Node3D
+class_name ExampleEmitter3D
+
+@export var Speed := 1.0
+@export var OrbitNode : Node3D
+var _loop_emitter : EventAudioAPI.AudioEmitter3D
+var _orbit_radius := 1.0
+
+func _init():
+ EventAudio.log_lookups = true
+ EventAudio.log_registrations = true
+ EventAudio.log_deaths = true
+
+func _ready():
+ _orbit_radius = (global_position - OrbitNode.global_position).length()
+
+func _process(_delta: float):
+ var orbit_angle = fmod(Time.get_ticks_msec() / 1000.0, Speed) * 2 * PI
+
+ var offset_x = _orbit_radius * cos(orbit_angle)
+ var offset_y = _orbit_radius * sin(orbit_angle)
+
+ var new_position := OrbitNode.global_position
+ new_position.x += offset_x
+ new_position.z += offset_y
+
+ global_position = new_position
+
+func _input(event: InputEvent):
+ if not event is InputEventKey or not event.is_pressed():
+ return
+
+ if event.keycode == KEY_1:
+ EventAudio.play_3d("hit", self)
+
+ if event.keycode == KEY_2:
+ EventAudio.play_3d("hit+large", self)
+
+ if event.keycode == KEY_3:
+ EventAudio.play_3d("hit+nonexistent", self)
+
+ if event.keycode == KEY_4:
+ EventAudio.play_3d("random_shoot", self)
+
+ if event.keycode == KEY_5:
+ if _loop_emitter:
+ EventAudio.stop(_loop_emitter)
+ _loop_emitter = null
+ else:
+ _loop_emitter = EventAudio.play_3d("loop", self)
diff --git a/addons/event_audio/example/example_ui.tscn b/addons/event_audio/example/example_ui.tscn
new file mode 100644
index 0000000..de92999
--- /dev/null
+++ b/addons/event_audio/example/example_ui.tscn
@@ -0,0 +1,24 @@
+[gd_scene format=3 uid="uid://kjsaalyhmkc5"]
+
+[node name="ExampleUi" type="Control"]
+layout_mode = 3
+anchors_preset = 2
+anchor_top = 1.0
+anchor_bottom = 1.0
+grow_vertical = 0
+
+[node name="Label" type="Label" parent="."]
+layout_mode = 1
+anchors_preset = 2
+anchor_top = 1.0
+anchor_bottom = 1.0
+offset_top = -127.0
+offset_right = 196.0
+grow_vertical = 0
+text = "1 - Play \"hit\"
+2 - Play \"hit+large\"
+3 - Play \"hit+nonexistent\"
+4 - Play \"random_shoot\"
+5 - Toggle \"loop\""
+vertical_alignment = 2
+justification_flags = 161
diff --git a/addons/event_audio/example/sounds/laser1.wav b/addons/event_audio/example/sounds/laser1.wav
new file mode 100644
index 0000000..c7f18b8
Binary files /dev/null and b/addons/event_audio/example/sounds/laser1.wav differ
diff --git a/addons/event_audio/example/sounds/laser1.wav.import b/addons/event_audio/example/sounds/laser1.wav.import
new file mode 100644
index 0000000..8888780
--- /dev/null
+++ b/addons/event_audio/example/sounds/laser1.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://byg26ypxea5qh"
+path="res://.godot/imported/laser1.wav-7c7bf6a86d0428120783ce6ae72e8afb.sample"
+
+[deps]
+
+source_file="res://addons/event_audio/example/sounds/laser1.wav"
+dest_files=["res://.godot/imported/laser1.wav-7c7bf6a86d0428120783ce6ae72e8afb.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/addons/event_audio/example/sounds/laser2.wav b/addons/event_audio/example/sounds/laser2.wav
new file mode 100644
index 0000000..975a33f
Binary files /dev/null and b/addons/event_audio/example/sounds/laser2.wav differ
diff --git a/addons/event_audio/example/sounds/laser2.wav.import b/addons/event_audio/example/sounds/laser2.wav.import
new file mode 100644
index 0000000..cea3665
--- /dev/null
+++ b/addons/event_audio/example/sounds/laser2.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://ftyi4ikfmbii"
+path="res://.godot/imported/laser2.wav-396964f333f7fcbda54278f155b07608.sample"
+
+[deps]
+
+source_file="res://addons/event_audio/example/sounds/laser2.wav"
+dest_files=["res://.godot/imported/laser2.wav-396964f333f7fcbda54278f155b07608.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/addons/event_audio/example/sounds/loop.wav b/addons/event_audio/example/sounds/loop.wav
new file mode 100644
index 0000000..22d7e8b
Binary files /dev/null and b/addons/event_audio/example/sounds/loop.wav differ
diff --git a/addons/event_audio/example/sounds/loop.wav.import b/addons/event_audio/example/sounds/loop.wav.import
new file mode 100644
index 0000000..6fcea47
--- /dev/null
+++ b/addons/event_audio/example/sounds/loop.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://7ncmg0ox4nxr"
+path="res://.godot/imported/loop.wav-fa3b1ef3ac7c882255ced1130766deef.sample"
+
+[deps]
+
+source_file="res://addons/event_audio/example/sounds/loop.wav"
+dest_files=["res://.godot/imported/loop.wav-fa3b1ef3ac7c882255ced1130766deef.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=2
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/addons/event_audio/example/sounds/shot1.wav b/addons/event_audio/example/sounds/shot1.wav
new file mode 100644
index 0000000..5633e13
Binary files /dev/null and b/addons/event_audio/example/sounds/shot1.wav differ
diff --git a/addons/event_audio/example/sounds/shot1.wav.import b/addons/event_audio/example/sounds/shot1.wav.import
new file mode 100644
index 0000000..7b954db
--- /dev/null
+++ b/addons/event_audio/example/sounds/shot1.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://cqo6bi2ygpwbu"
+path="res://.godot/imported/shot1.wav-d93bbca9f7efde511f9d9572265cbb25.sample"
+
+[deps]
+
+source_file="res://addons/event_audio/example/sounds/shot1.wav"
+dest_files=["res://.godot/imported/shot1.wav-d93bbca9f7efde511f9d9572265cbb25.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/addons/event_audio/example/sounds/shot2.wav b/addons/event_audio/example/sounds/shot2.wav
new file mode 100644
index 0000000..a9e0a6c
Binary files /dev/null and b/addons/event_audio/example/sounds/shot2.wav differ
diff --git a/addons/event_audio/example/sounds/shot2.wav.import b/addons/event_audio/example/sounds/shot2.wav.import
new file mode 100644
index 0000000..9d62c65
--- /dev/null
+++ b/addons/event_audio/example/sounds/shot2.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://cultflwybpfo1"
+path="res://.godot/imported/shot2.wav-ac04745e5c74f182ec001473606b2120.sample"
+
+[deps]
+
+source_file="res://addons/event_audio/example/sounds/shot2.wav"
+dest_files=["res://.godot/imported/shot2.wav-ac04745e5c74f182ec001473606b2120.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=0
diff --git a/addons/event_audio/icon.png b/addons/event_audio/icon.png
new file mode 100644
index 0000000..667162a
Binary files /dev/null and b/addons/event_audio/icon.png differ
diff --git a/addons/event_audio/icon.png.import b/addons/event_audio/icon.png.import
new file mode 100644
index 0000000..091649d
--- /dev/null
+++ b/addons/event_audio/icon.png.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d3rrns80mmgqo"
+path="res://.godot/imported/icon.png-5ff47921d459c4c7814e206e0a2e0cc0.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/event_audio/icon.png"
+dest_files=["res://.godot/imported/icon.png-5ff47921d459c4c7814e206e0a2e0cc0.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/event_audio/icons/Add.svg b/addons/event_audio/icons/Add.svg
new file mode 100644
index 0000000..afad08a
--- /dev/null
+++ b/addons/event_audio/icons/Add.svg
@@ -0,0 +1 @@
+
diff --git a/addons/event_audio/icons/Add.svg.import b/addons/event_audio/icons/Add.svg.import
new file mode 100644
index 0000000..084813a
--- /dev/null
+++ b/addons/event_audio/icons/Add.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dqg7mfau2g7ck"
+path="res://.godot/imported/Add.svg-219bdd038475c83ffbebee883f646de0.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/event_audio/icons/Add.svg"
+dest_files=["res://.godot/imported/Add.svg-219bdd038475c83ffbebee883f646de0.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=2.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/event_audio/icons/Close.svg b/addons/event_audio/icons/Close.svg
new file mode 100644
index 0000000..be1c1dc
--- /dev/null
+++ b/addons/event_audio/icons/Close.svg
@@ -0,0 +1 @@
+
diff --git a/addons/event_audio/icons/Close.svg.import b/addons/event_audio/icons/Close.svg.import
new file mode 100644
index 0000000..90d5361
--- /dev/null
+++ b/addons/event_audio/icons/Close.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://pad8hlsq2shw"
+path="res://.godot/imported/Close.svg-326dec14804498c2ab2273a4a572c2e2.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/event_audio/icons/Close.svg"
+dest_files=["res://.godot/imported/Close.svg-326dec14804498c2ab2273a4a572c2e2.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=2.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/event_audio/icons/Play.svg b/addons/event_audio/icons/Play.svg
new file mode 100644
index 0000000..385d501
--- /dev/null
+++ b/addons/event_audio/icons/Play.svg
@@ -0,0 +1 @@
+
diff --git a/addons/event_audio/icons/Play.svg.import b/addons/event_audio/icons/Play.svg.import
new file mode 100644
index 0000000..f25fc07
--- /dev/null
+++ b/addons/event_audio/icons/Play.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://byclw0utbwbam"
+path="res://.godot/imported/Play.svg-33c5bae738cb342d56c12b8caaf4909d.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/event_audio/icons/Play.svg"
+dest_files=["res://.godot/imported/Play.svg-33c5bae738cb342d56c12b8caaf4909d.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=2.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/event_audio/icons/RandomNumberGenerator.svg b/addons/event_audio/icons/RandomNumberGenerator.svg
new file mode 100644
index 0000000..2071c44
--- /dev/null
+++ b/addons/event_audio/icons/RandomNumberGenerator.svg
@@ -0,0 +1 @@
+
diff --git a/addons/event_audio/icons/RandomNumberGenerator.svg.import b/addons/event_audio/icons/RandomNumberGenerator.svg.import
new file mode 100644
index 0000000..67456e0
--- /dev/null
+++ b/addons/event_audio/icons/RandomNumberGenerator.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dcbbwdp16tdns"
+path="res://.godot/imported/RandomNumberGenerator.svg-307a8ce8bcb247fd591f4766aea337ae.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/event_audio/icons/RandomNumberGenerator.svg"
+dest_files=["res://.godot/imported/RandomNumberGenerator.svg-307a8ce8bcb247fd591f4766aea337ae.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=2.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/event_audio/icons/Remove.svg b/addons/event_audio/icons/Remove.svg
new file mode 100644
index 0000000..eb8e244
--- /dev/null
+++ b/addons/event_audio/icons/Remove.svg
@@ -0,0 +1 @@
+
diff --git a/addons/event_audio/icons/Remove.svg.import b/addons/event_audio/icons/Remove.svg.import
new file mode 100644
index 0000000..2a72493
--- /dev/null
+++ b/addons/event_audio/icons/Remove.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://djgktanb5swk6"
+path="res://.godot/imported/Remove.svg-dad4f6b2d936a8b9536e5322302cb088.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/event_audio/icons/Remove.svg"
+dest_files=["res://.godot/imported/Remove.svg-dad4f6b2d936a8b9536e5322302cb088.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=2.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/event_audio/icons/Tools.svg b/addons/event_audio/icons/Tools.svg
new file mode 100644
index 0000000..1591a56
--- /dev/null
+++ b/addons/event_audio/icons/Tools.svg
@@ -0,0 +1 @@
+
diff --git a/addons/event_audio/icons/Tools.svg.import b/addons/event_audio/icons/Tools.svg.import
new file mode 100644
index 0000000..d9b22d1
--- /dev/null
+++ b/addons/event_audio/icons/Tools.svg.import
@@ -0,0 +1,37 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://i4qa40akvjaa"
+path="res://.godot/imported/Tools.svg-4c4437be5a640503622d98eb51e3bd18.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/event_audio/icons/Tools.svg"
+dest_files=["res://.godot/imported/Tools.svg-4c4437be5a640503622d98eb51e3bd18.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=2.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/event_audio/plugin.cfg b/addons/event_audio/plugin.cfg
new file mode 100644
index 0000000..6805417
--- /dev/null
+++ b/addons/event_audio/plugin.cfg
@@ -0,0 +1,6 @@
+[plugin]
+name="EventAudio"
+description=""
+author="Simon Carter"
+version="1.0.1"
+script="plugin.gd"
diff --git a/addons/event_audio/plugin.gd b/addons/event_audio/plugin.gd
new file mode 100644
index 0000000..177b962
--- /dev/null
+++ b/addons/event_audio/plugin.gd
@@ -0,0 +1,33 @@
+@tool
+extends EditorPlugin
+
+var _editor_plugin = preload("res://addons/event_audio/src/ea_event_bank_inspector.gd").new()
+var _runtime_scene = "res://addons/event_audio/scenes/event_audio_autoload.tscn"
+
+func _enter_tree():
+
+ var mono_supported = ClassDB.class_exists("CSharpScript")
+
+ if mono_supported:
+ print ("Mono supported")
+ else:
+ print ("Mono unsupported")
+
+ add_inspector_plugin(_editor_plugin)
+
+ if not ProjectSettings.has_setting("autoload/EventAudio"):
+ add_autoload_singleton("EventAudio", _runtime_scene)
+
+ if mono_supported:
+ if not ProjectSettings.has_setting("autoload/EventAudioCSharp"):
+ var _cs_script = "res://addons/event_audio/EventAudio.cs"
+ add_autoload_singleton("EventAudioCSharp", _cs_script)
+
+func _exit_tree():
+ if _editor_plugin != null:
+ remove_inspector_plugin(_editor_plugin)
+ if ProjectSettings.has_setting("autoload/EventAudio"):
+ remove_autoload_singleton("EventAudio")
+
+ if ProjectSettings.has_setting("autoload/EventAudioCSharp"):
+ remove_autoload_singleton("EventAudioCSharp")
diff --git a/addons/event_audio/scenes/bank_line.tscn b/addons/event_audio/scenes/bank_line.tscn
new file mode 100644
index 0000000..eb8a5fd
--- /dev/null
+++ b/addons/event_audio/scenes/bank_line.tscn
@@ -0,0 +1,68 @@
+[gd_scene load_steps=6 format=3 uid="uid://co2eglljfwoxo"]
+
+[ext_resource type="Texture2D" uid="uid://pad8hlsq2shw" path="res://addons/event_audio/icons/Close.svg" id="1_2kmlt"]
+[ext_resource type="Script" path="res://addons/event_audio/src/ea_event_edit_control.gd" id="1_melcq"]
+[ext_resource type="Texture2D" uid="uid://i4qa40akvjaa" path="res://addons/event_audio/icons/Tools.svg" id="2_31ihp"]
+[ext_resource type="Texture2D" uid="uid://dcbbwdp16tdns" path="res://addons/event_audio/icons/RandomNumberGenerator.svg" id="3_ikqib"]
+[ext_resource type="PackedScene" uid="uid://b84km51g3vs0n" path="res://addons/event_audio/scenes/bank_resource_line.tscn" id="5_nou6o"]
+
+[node name="TriggerLine" type="HBoxContainer" node_paths=PackedStringArray("delete_trigger_button", "play_random_button", "trigger_name_edit", "settings_button", "stream_settings_list")]
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_top = -38.0
+offset_bottom = -655.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+script = ExtResource("1_melcq")
+delete_trigger_button = NodePath("HBoxContainer/DeleteButton")
+play_random_button = NodePath("HBoxContainer/RandomButton")
+trigger_name_edit = NodePath("HBoxContainer/TriggerEdit")
+settings_button = NodePath("HBoxContainer/SettingsButton")
+stream_settings_list = NodePath("BoxContainer/ResourceList")
+
+[node name="HBoxContainer" type="HBoxContainer" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 0
+
+[node name="DeleteButton" type="Button" parent="HBoxContainer"]
+layout_mode = 2
+tooltip_text = "Delete this event."
+icon = ExtResource("1_2kmlt")
+
+[node name="TriggerEdit" type="LineEdit" parent="HBoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+tooltip_text = "The string that will trigger this audio event.
+Trigger strings are chopped right-to-left at the seperator character until a match is found."
+placeholder_text = "trigger string"
+
+[node name="SettingsButton" type="Button" parent="HBoxContainer"]
+custom_minimum_size = Vector2(16, 16)
+layout_mode = 2
+tooltip_text = "Playback settings."
+icon = ExtResource("2_31ihp")
+icon_alignment = 1
+
+[node name="RandomButton" type="Button" parent="HBoxContainer"]
+layout_mode = 2
+tooltip_text = "Trigger this audio. Plays back a random audio variant, taking into account the variant weights."
+icon = ExtResource("3_ikqib")
+
+[node name="BoxContainer" type="BoxContainer" parent="."]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="ResourceList" type="VBoxContainer" parent="BoxContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="ResourceLine" parent="BoxContainer/ResourceList" instance=ExtResource("5_nou6o")]
+visible = false
+layout_mode = 2
+
+[node name="ResourceLine2" parent="BoxContainer/ResourceList" instance=ExtResource("5_nou6o")]
+visible = false
+layout_mode = 2
diff --git a/addons/event_audio/scenes/bank_resource_line.tscn b/addons/event_audio/scenes/bank_resource_line.tscn
new file mode 100644
index 0000000..25e33dd
--- /dev/null
+++ b/addons/event_audio/scenes/bank_resource_line.tscn
@@ -0,0 +1,76 @@
+[gd_scene load_steps=5 format=3 uid="uid://b84km51g3vs0n"]
+
+[ext_resource type="Script" path="res://addons/event_audio/src/ea_stream_edit_control.gd" id="1_fwtai"]
+[ext_resource type="Texture2D" uid="uid://byclw0utbwbam" path="res://addons/event_audio/icons/Play.svg" id="2_jis6r"]
+[ext_resource type="Texture2D" uid="uid://dqg7mfau2g7ck" path="res://addons/event_audio/icons/Add.svg" id="3_aknch"]
+[ext_resource type="Texture2D" uid="uid://djgktanb5swk6" path="res://addons/event_audio/icons/Remove.svg" id="4_il77p"]
+
+[node name="ResourceLine" type="HBoxContainer" node_paths=PackedStringArray("delete_event_button", "add_stream_button", "play_button", "audio_label")]
+offset_right = 328.0
+offset_bottom = 40.0
+size_flags_horizontal = 3
+size_flags_vertical = 3
+script = ExtResource("1_fwtai")
+delete_event_button = NodePath("DeleteButton")
+add_stream_button = NodePath("AddButton")
+play_button = NodePath("PlayButton")
+audio_label = NodePath("ResourcePicker/ResourceLabel")
+
+[node name="PlayButton" type="Button" parent="."]
+layout_mode = 2
+tooltip_text = "Play this audio variant."
+icon = ExtResource("2_jis6r")
+
+[node name="WeightSliderContainer" type="FlowContainer" parent="."]
+custom_minimum_size = Vector2(64, 0)
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 0.5
+tooltip_text = "The weighted probabily this variant will play.
+Only used when there is more than one variant."
+alignment = 1
+
+[node name="ResourcePicker" type="Control" parent="."]
+clip_contents = true
+custom_minimum_size = Vector2(128, 0)
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="ResourcePickerContainer" type="BoxContainer" parent="ResourcePicker"]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+alignment = 1
+vertical = true
+
+[node name="ResourceLabel" type="Label" parent="ResourcePicker"]
+visible = false
+layout_mode = 1
+anchors_preset = -1
+anchor_left = 0.234
+anchor_right = 0.703
+anchor_bottom = 1.0
+offset_left = 0.0479984
+offset_right = 0.0159988
+grow_horizontal = 2
+grow_vertical = 2
+horizontal_alignment = 1
+vertical_alignment = 1
+clip_text = true
+text_overrun_behavior = 1
+
+[node name="AddButton" type="Button" parent="."]
+layout_mode = 2
+size_flags_vertical = 3
+tooltip_text = "Add an audio variant to the trigger."
+icon = ExtResource("3_aknch")
+
+[node name="DeleteButton" type="Button" parent="."]
+layout_mode = 2
+size_flags_vertical = 3
+tooltip_text = "Delete this variant."
+icon = ExtResource("4_il77p")
diff --git a/addons/event_audio/scenes/event_audio_autoload.tscn b/addons/event_audio/scenes/event_audio_autoload.tscn
new file mode 100644
index 0000000..ca0d93f
--- /dev/null
+++ b/addons/event_audio/scenes/event_audio_autoload.tscn
@@ -0,0 +1,8 @@
+[gd_scene load_steps=2 format=3 uid="uid://be741v81fj3xc"]
+
+[ext_resource type="Script" path="res://addons/event_audio/event_audio.gd" id="1_75kvd"]
+
+[node name="EventAudio" type="Node"]
+script = ExtResource("1_75kvd")
+
+[node name="EventAudioAutoload" type="Node" parent="."]
diff --git a/addons/event_audio/src/ea_editor_tools.gd b/addons/event_audio/src/ea_editor_tools.gd
new file mode 100644
index 0000000..23346a5
--- /dev/null
+++ b/addons/event_audio/src/ea_editor_tools.gd
@@ -0,0 +1,201 @@
+class_name EAEditorTools
+static var _editor_stream_player : AudioStreamPlayer
+static var _global_rng : RandomNumberGenerator
+
+static func get_global_rng() -> RandomNumberGenerator:
+ if _global_rng == null:
+ _global_rng = RandomNumberGenerator.new()
+ return _global_rng
+
+static func play_sound(entry: EAEvent, stream: AudioStream):
+ var main_screen = EditorInterface.get_editor_main_screen()
+ var audio = _editor_stream_player
+ if audio == null:
+ audio = AudioStreamPlayer.new()
+ audio.name = "_EditorAudio"
+ main_screen.add_child(audio)
+ _editor_stream_player = audio
+
+ EventAudioAPI.init_player_from_playback_settings(get_global_rng(), audio, entry.playback_settings)
+
+ audio.stop()
+ audio.stream = stream
+ audio.play()
+
+static func stop_sound():
+ var audio := _editor_stream_player
+ if audio != null:
+ print("Stopping audio")
+ audio.stop()
+ audio.stream = null
+
+static func make_property_panel(obj: Object, title: String, excludes : Dictionary, change_callback : Callable) -> Container:
+ var panel := PanelContainer.new()
+ panel.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ panel.modulate = Color(0.8, 0.8, 1)
+
+ var property_container := VBoxContainer.new()
+ property_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ panel.add_child(property_container)
+
+ if title != "":
+ var title_label := Label.new()
+ title_label.text = title
+ title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
+ property_container.add_child(title_label)
+
+ for prop in obj.get_property_list():
+ if not prop.name in excludes:
+ var val = obj.get(prop.name)
+ var property_control := _make_property_control(prop, val, change_callback.bind(obj, prop.name))
+ if property_control != null:
+ property_container.add_child(property_control)
+
+ return panel
+
+static func _make_property_control(prop, initial_value, update_callback: Callable) -> Control:
+ # print(prop)
+ var control : Control
+
+ if prop.usage & PROPERTY_USAGE_STORAGE:
+ control = HBoxContainer.new()
+ control.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ var text_label = Label.new()
+ text_label.text = _property_name_to_display_name(prop.name)
+
+ text_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ control.add_child(text_label)
+
+ var prop_type: Variant.Type = prop["type"]
+ var prop_range : Dictionary = _parse_range(prop)
+
+ match prop_type:
+ TYPE_BOOL:
+ var prop_editor = CheckBox.new()
+ prop_editor.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ if initial_value != null:
+ prop_editor.button_pressed = initial_value
+
+ prop_editor.toggled.connect(update_callback)
+ control.add_child(prop_editor)
+
+ TYPE_INT, TYPE_FLOAT:
+ var prop_editor := EditorSpinSlider.new()
+ var default_step = null
+ if prop_range.has("step"):
+ default_step = prop_range["step"]
+
+ # For floats, show a normal input box if the step has been set to 1.
+ # For ints, show a normal input box if the step has been set to 1, or left out.
+ # Otherwise show a slider
+ if prop_type == TYPE_FLOAT:
+ if default_step == null:
+ prop_editor.hide_slider = false
+ prop_editor.step = 0.0001
+ elif default_step == 1:
+ prop_editor.step = 1
+ else:
+ prop_editor.step = default_step
+ prop_editor.hide_slider = false
+ else:
+ if default_step == null or default_step == 1:
+ prop_editor.step = 1
+ else:
+ prop_editor.step = default_step
+ prop_editor.hide_slider = false
+
+
+ prop_editor.allow_lesser = prop_range.has("or_lesser")
+ prop_editor.allow_greater = prop_range.has("or_greater")
+
+ if prop_range.has("min"):
+ prop_editor.min_value = prop_range["min"]
+
+ if prop_range.has("max"):
+ prop_editor.max_value = prop_range["max"]
+
+ prop_editor.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ if initial_value != null:
+ prop_editor.value = initial_value
+
+ prop_editor.value_changed.connect(update_callback)
+ control.add_child(prop_editor)
+
+ TYPE_STRING, TYPE_STRING_NAME, TYPE_NODE_PATH:
+ var prop_editor = LineEdit.new()
+ prop_editor.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ if initial_value != null:
+ prop_editor.text = initial_value
+
+ prop_editor.text_changed.connect(update_callback)
+ control.add_child(prop_editor)
+
+ _:
+ pass
+ elif prop.usage & PROPERTY_USAGE_GROUP:
+ var group_label := Label.new()
+ group_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
+ group_label.text = prop.name
+ control = group_label
+
+ return control
+
+static func _parse_range(prop) -> Dictionary:
+ if prop["hint"] != PROPERTY_HINT_RANGE:
+ return {}
+
+ var prop_range := {}
+ var parts := prop["hint_string"].split(",") as PackedStringArray
+ var is_float : bool = prop["type"] == TYPE_FLOAT
+
+ if parts.size() > 0:
+ if is_float:
+ prop_range["min"] = parts[0].to_float()
+ else:
+ prop_range["min"] = parts[0].to_int()
+
+ if parts.size() > 1:
+ if is_float:
+ prop_range["max"] = parts[1].to_float()
+ else:
+ prop_range["max"] = parts[1].to_int()
+
+ if parts.size() > 2:
+ if is_float:
+ prop_range["step"] = parts[2].to_float()
+ else:
+ prop_range["step"] = parts[2].to_int()
+
+ if parts.size() > 3:
+ match parts[3]:
+ "or_lesser":
+ prop_range["or_lesser"] = true
+ "or_greater":
+ prop_range["or_greater"] = true
+
+ if parts.size() > 4:
+ match parts[4]:
+ "or_lesser":
+ prop_range["or_lesser"] = true
+ "or_greater":
+ prop_range["or_greater"] = true
+ _:
+ pass
+
+ return prop_range
+
+
+static func _property_name_to_display_name(name: String):
+ var name_snake := name.to_snake_case()
+ var parts := name_snake.split("_")
+ var display_name := ""
+
+ for c1 : int in parts.size():
+ var part := parts[c1]
+ display_name += part.to_pascal_case()
+ if c1 < parts.size() - 1:
+ display_name += " "
+
+ return display_name
+
+
diff --git a/addons/event_audio/src/ea_event.gd b/addons/event_audio/src/ea_event.gd
new file mode 100644
index 0000000..08a0c61
--- /dev/null
+++ b/addons/event_audio/src/ea_event.gd
@@ -0,0 +1,40 @@
+@tool
+extends Resource
+class_name EAEvent
+
+@export var audio_streams : Array[AudioStream] = []
+@export var probability_weights : Array[float] = []
+@export var trigger_tags: String = ""
+@export var playback_settings: EAEventPlaybackSettings
+
+func _init():
+ if audio_streams.size() == 0:
+ audio_streams.push_front(null)
+ probability_weights.push_front(1.0)
+ playback_settings = EAEventPlaybackSettings.new()
+
+func add_stream(index: int):
+ audio_streams.insert(index+1, null)
+ probability_weights.insert(index+1, 1.0)
+
+func remove_stream(index: int):
+ audio_streams.remove_at(index)
+ probability_weights.remove_at(index)
+
+func get_weighted_random_stream(random: float) -> AudioStream:
+ var total_weight := 0.0
+ for w : float in probability_weights:
+ total_weight = total_weight + w
+
+ var r := random * total_weight
+ var num_entries := probability_weights.size()
+ var weight_count := 0.0
+ for i in num_entries:
+ if i + 1 == num_entries:
+ return audio_streams[i]
+ elif r <= weight_count + probability_weights[i]:
+ return audio_streams[i]
+ weight_count += probability_weights[i]
+ return null
+
+
diff --git a/addons/event_audio/src/ea_event_bank.gd b/addons/event_audio/src/ea_event_bank.gd
new file mode 100644
index 0000000..382a73e
--- /dev/null
+++ b/addons/event_audio/src/ea_event_bank.gd
@@ -0,0 +1,27 @@
+@tool
+extends Resource
+class_name EAEventBank
+
+@export var entries: Array[EAEvent]
+
+func add_entry():
+ entries.insert(0, EAEvent.new())
+
+func delete_entry(entry: EAEvent):
+ var entry_idx := entries.find(entry)
+ if entry_idx >= 0:
+ entries.remove_at(entry_idx)
+
+func find_entry_with_trigger(trigger: String) -> EAEvent:
+ for entry: EAEvent in entries:
+ if entry.trigger_tags == trigger:
+ return entry
+
+ return null
+
+func _sort_func(a: EAEvent, b: EAEvent) -> bool:
+ return a.trigger_tags < b.trigger_tags
+
+func sort_by_trigger():
+ entries.sort_custom(_sort_func)
+
diff --git a/addons/event_audio/src/ea_event_bank_inspector.gd b/addons/event_audio/src/ea_event_bank_inspector.gd
new file mode 100644
index 0000000..bfabe25
--- /dev/null
+++ b/addons/event_audio/src/ea_event_bank_inspector.gd
@@ -0,0 +1,11 @@
+@tool
+extends EditorInspectorPlugin
+
+func _can_handle(object):
+ return object is EAEventBank
+
+func _parse_property(_object, _type, name, _hint_type, _hint_string, _usage_flags, _wide):
+ if name == "entries":
+ add_property_editor(name, EAEventBankProperty.new())
+ return true
+ return false
diff --git a/addons/event_audio/src/ea_event_bank_mounter.gd b/addons/event_audio/src/ea_event_bank_mounter.gd
new file mode 100644
index 0000000..1482a47
--- /dev/null
+++ b/addons/event_audio/src/ea_event_bank_mounter.gd
@@ -0,0 +1,13 @@
+extends Node
+class_name EAEventBankMounter
+@export var _audio_bank_resource : EAEventBank
+# @export var audioBankResources: Array[EAEventBank]
+
+func _enter_tree():
+ var player := EventAudio.instance
+ player.register_event_bank(_audio_bank_resource)
+
+func _exit_tree():
+ var player := EventAudio.instance
+ player.unregister_event_bank(_audio_bank_resource)
+
diff --git a/addons/event_audio/src/ea_event_bank_property.gd b/addons/event_audio/src/ea_event_bank_property.gd
new file mode 100644
index 0000000..1c4388a
--- /dev/null
+++ b/addons/event_audio/src/ea_event_bank_property.gd
@@ -0,0 +1,109 @@
+@tool
+extends EditorProperty
+
+class_name EAEventBankProperty
+
+var _audio_bank_line_scene := preload("res://addons/event_audio/scenes/bank_line.tscn")
+var _audio_bank_resource_line_scene := preload("res://addons/event_audio/scenes/bank_resource_line.tscn")
+
+var _resource: EAEventBank
+var _property_name: StringName
+var _entries: Array[EAEvent]
+var _root_container := VBoxContainer.new()
+var _focus_on_trigger : String = ""
+
+#----------------------------------------------
+# Godot call to update rendering
+func _update_property():
+ # print("updating property")
+ var open_settings := {}
+
+ for control : Node in _root_container.get_children():
+ if control is EAEventEditControl and control.is_settings_open():
+ open_settings[control.get_event()] = true
+
+ _root_container.remove_child(control)
+ control.queue_free()
+
+ _make_lines(open_settings)
+
+ # Search for the trigger we were looking at previously.
+ if _focus_on_trigger == "":
+ return
+
+ await get_tree().process_frame
+
+ # Find the trigger we were editing previously and select it.
+ for control : Control in _root_container.get_children():
+ if control is not EAEventEditControl:
+ continue
+ var line = control as EAEventEditControl
+ if line.trigger_name_edit.text == _focus_on_trigger:
+ EditorInterface.get_inspector().ensure_control_visible(line.trigger_name_edit)
+ break
+
+ _focus_on_trigger = ""
+
+func _enter_tree():
+ _property_name = get_edited_property()
+ _resource = get_edited_object() as EAEventBank
+ _entries = _resource.entries
+
+ _root_container.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+
+ _make_lines()
+ add_child(_root_container)
+ set_bottom_editor(_root_container)
+
+func _exit_tree() -> void:
+ EAEditorTools.stop_sound()
+
+func sort_bank():
+ _resource.sort_by_trigger()
+
+func signal_entry_changed(update_ui: bool):
+ emit_changed(_property_name, _entries, "", not update_ui)
+
+func set_focus_on_trigger(trigger: String):
+ _focus_on_trigger = trigger
+
+func delete_event(event: EAEvent):
+ _resource.delete_entry(event)
+ signal_entry_changed(true)
+
+#--------------------------------------
+func _make_lines(setting_to_restore = {}):
+ var add_button := Button.new()
+ add_button.text = "Add Entry"
+ add_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ add_button.pressed.connect(_on_add_entry_button_clicked)
+ _root_container.add_child(add_button)
+
+ var stop_button := Button.new()
+ stop_button.text = "Stop playback"
+ stop_button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ stop_button.pressed.connect(_on_stop_button_clicked)
+ _root_container.add_child(stop_button)
+
+ for entry : EAEvent in _resource.entries:
+ var settings_open := setting_to_restore.has(entry)
+ _makeEntryLine(entry, settings_open)
+
+func _makeEntryLine(entry: EAEvent, settings_open: bool):
+ var line := _audio_bank_line_scene.instantiate() as EAEventEditControl
+ _root_container.add_child(line)
+ line.create(self, entry, settings_open)
+
+ for c1: int in entry.audio_streams.size():
+ var resource_line := _audio_bank_resource_line_scene.instantiate() as EAStreamEditControl
+ line.stream_settings_list.add_child(resource_line)
+ resource_line.create(self, entry, c1, entry.audio_streams.size() == 1)
+
+func _on_add_entry_button_clicked():
+ _resource.add_entry()
+ signal_entry_changed(true)
+
+func _on_stop_button_clicked():
+ EAEditorTools.stop_sound()
+
+
diff --git a/addons/event_audio/src/ea_event_edit_control.gd b/addons/event_audio/src/ea_event_edit_control.gd
new file mode 100644
index 0000000..c90018e
--- /dev/null
+++ b/addons/event_audio/src/ea_event_edit_control.gd
@@ -0,0 +1,91 @@
+@tool
+extends Container
+class_name EAEventEditControl
+
+@export var delete_trigger_button : BaseButton
+@export var play_random_button : BaseButton
+@export var trigger_name_edit : LineEdit
+@export var settings_button : BaseButton
+@export var stream_settings_list : Container
+var _exclude_props = {"resource_local_to_scene": true, "Resource": true, "resource_name": true, "script": true}
+var _settings_panel : Control
+var _event : EAEvent
+var _bank_inspector : EAEventBankProperty
+
+func create(bank_inspector: EAEventBankProperty, event: EAEvent, open_settings: bool) -> void:
+ _event = event
+ _bank_inspector = bank_inspector
+
+ delete_trigger_button.pressed.connect(_on_delete_entry_button_clicked)
+ trigger_name_edit.set_text(_event.trigger_tags)
+ trigger_name_edit.text_submitted.connect(_on_trigger_submitted)
+ trigger_name_edit.text_changed.connect(_on_trigger_changed)
+ settings_button.pressed.connect(_on_settings_button_clicked)
+ play_random_button.pressed.connect(_on_play_random_button_clicked)
+
+ if open_settings:
+ var panel := _make_settings_panel()
+ self.add_sibling(panel)
+ _settings_panel = panel
+
+func is_settings_open():
+ return _settings_panel != null
+
+func get_event():
+ return _event
+
+func _on_settings_button_clicked():
+ _toggle_settings_panel()
+
+func _on_settings_entry_changed(val, settings: EAEventPlaybackSettings, member: StringName):
+ settings.set(member, val)
+ _bank_inspector.signal_entry_changed(false)
+
+func _on_delete_entry_button_clicked():
+ _bank_inspector.delete_event(_event)
+
+func _check_trigger_name(entry: EAEvent, trigger: String) -> bool:
+ var bank_entry := _bank_inspector._resource.find_entry_with_trigger(trigger)
+
+ if bank_entry and bank_entry != entry:
+ return false
+
+ return true
+
+func _on_trigger_changed(trigger: String):
+ # If the trigger isn't valid, show an error color
+ if _check_trigger_name(_event, trigger):
+ trigger_name_edit.modulate = Color.WHITE
+ else:
+ trigger_name_edit.modulate = Color.RED
+
+func _on_trigger_submitted(trigger: String):
+ if _check_trigger_name(_event, trigger):
+ _event.trigger_tags = trigger
+ trigger_name_edit.release_focus()
+
+ # Re-sort the bank - this may trigger a UI update, so make sure we focus on the right control
+ _bank_inspector.set_focus_on_trigger(trigger)
+ _bank_inspector.sort_bank()
+ _bank_inspector.signal_entry_changed(true)
+
+func _on_play_random_button_clicked():
+ var roll := EAEditorTools.get_global_rng().randf_range(0, 1.0)
+ var stream := _event.get_weighted_random_stream(roll)
+ if stream:
+ EAEditorTools.play_sound(_event, stream)
+
+func _toggle_settings_panel():
+ if _settings_panel:
+ _settings_panel.get_parent().remove_child(_settings_panel)
+ _settings_panel.queue_free()
+ _settings_panel = null
+ else:
+ var panel := _make_settings_panel()
+ _settings_panel = panel
+ self.add_sibling(panel)
+
+func _make_settings_panel() -> Container:
+ return EAEditorTools.make_property_panel(
+ _event.playback_settings, "Playback Settings", _exclude_props, _on_settings_entry_changed)
+
diff --git a/addons/event_audio/src/ea_event_playback_settings.gd b/addons/event_audio/src/ea_event_playback_settings.gd
new file mode 100644
index 0000000..d882b11
--- /dev/null
+++ b/addons/event_audio/src/ea_event_playback_settings.gd
@@ -0,0 +1,22 @@
+extends Resource
+
+class_name EAEventPlaybackSettings
+
+## The factor for the attenuation effect. Higher values make the sound audible over a larger distance.
+@export_group("Behaviour")
+@export var stop_when_source_dies := false
+@export var stationary := false
+
+@export_group("Shared Playback")
+@export_range(-10.0, 10.0, 0.1) var volume_db := 0.0
+@export_range(0.1, 2.0, 0.1) var min_pitch := 1.0
+@export_range(0.1, 2.0, 0.1) var max_pitch := 1.0
+@export_range(0.0, 3.0, 0.1) var panning_strength := 1.0
+
+@export_group("2D Playback")
+@export_range(0.0, 3.0, 0.1) var attenuation := 1.0
+@export_range(0, 9999999) var max_distance := 2000
+
+@export_group("3D Playback")
+@export_range(0, 1000) var unit_size := 10.0
+@export_range(-10.0, 10.0, 0.1) var max_db := 3.0
diff --git a/addons/event_audio/src/ea_stream_edit_control.gd b/addons/event_audio/src/ea_stream_edit_control.gd
new file mode 100644
index 0000000..80bd7e7
--- /dev/null
+++ b/addons/event_audio/src/ea_stream_edit_control.gd
@@ -0,0 +1,131 @@
+@tool
+extends Container
+class_name EAStreamEditControl
+
+@export var delete_event_button : Button
+@export var add_stream_button : Button
+@export var play_button : Button
+@export var audio_label : Label
+var weight_editor : EditorSpinSlider
+var audio_selector : EditorResourcePicker
+
+var _stream_id : int
+var _event : EAEvent
+var _bank_inspector : EAEventBankProperty
+
+func create(bank_inspector: EAEventBankProperty, event: EAEvent, stream_id: int, primary: bool) -> void:
+ # When this is the primary resource, we don't want to delete it.
+ # add_stream_button.visible = true
+ _stream_id = stream_id
+ _event = event
+ _bank_inspector = bank_inspector
+
+ if primary:
+ delete_event_button.disabled = true
+ else:
+ delete_event_button.disabled = false
+
+ play_button.pressed.connect(_on_play_button_clicked)
+
+ audio_selector.resource_changed.connect(_on_stream_changed)
+ audio_selector.edited_resource = _event.audio_streams[_stream_id]
+
+ add_stream_button.pressed.connect(_on_add_resource_button_clicked)
+ delete_event_button.pressed.connect(_on_delete_resource_button_clicked)
+
+ weight_editor.value_changed.connect(_on_stream_weight_changed)
+ weight_editor.value = _event.probability_weights[_stream_id]
+ _make_audio_picker_pretty()
+
+func _ready() -> void:
+ get_node("WeightSliderContainer").add_child(weight_editor)
+ get_node("ResourcePicker/ResourcePickerContainer").add_child(audio_selector)
+ # if _event:
+ # audio_selector.edited_resource = _event.audio_streams[_stream_id]
+
+ _make_audio_picker_pretty()
+
+func _init() -> void:
+ weight_editor = EditorSpinSlider.new()
+ weight_editor.label = "weight"
+ weight_editor.step = 0.05
+ weight_editor.hide_slider = false
+ weight_editor.allow_lesser = false
+ weight_editor.allow_greater = false
+ weight_editor.min_value = 0
+ weight_editor.max_value = 1.0
+ weight_editor.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+
+ audio_selector = EditorResourcePicker.new()
+ audio_selector.size_flags_horizontal = Control.SIZE_EXPAND_FILL
+ audio_selector.base_type = "AudioStream"
+ # audio_selector.edited_resource = _event.audio_streams[_stream_id]
+
+ audio_selector.resource_changed.connect(_on_resource_changed)
+ audio_selector.resource_selected.connect(_on_resource_clicked)
+
+
+func _on_resource_clicked(res: Resource, _inspect: bool):
+ # When the resource picker is clicked, visit the resource.
+ # Do it the next frame, however, in case the controls are being updated.
+ if res != null:
+ await get_tree().process_frame
+ EditorInterface.edit_resource(res)
+
+func _on_resource_changed(_res: Resource):
+ # When the resource is changed, update the prettification of the resource picker.
+ _make_audio_picker_pretty()
+
+# The default resource picker has several drawbacks when used in a custom inspector.
+# - It draws double-height, to show an audio preview; even when there is no preview.
+# - When audio is assigned it stops showing the path name.
+# This function 'fixes' that, somewhat hackily.
+func _make_audio_picker_pretty():
+ # In certain circumstances, show a custom label with the resources file path.
+ # Otherwise, hide it and rely on the default control.
+ var res := audio_selector.edited_resource
+
+ if res == null:
+ # If no resource, show the default button text
+ audio_label.visible = false
+ else:
+ # Otherwise, try to show the file name
+ if FileAccess.file_exists(res.resource_path):
+ audio_label.text = res.resource_path.get_file()
+ audio_label.visible = true
+ else:
+ audio_label.text = res.get_class()
+ # TODO - can't tell when the button is rendering at the moment, so this shows on top, sometimes.
+ # Until we can, make the label invisible
+ audio_label.visible = false
+
+ # This is the only way we found to get the ResourcePicker smaller.
+ # It's not ideal, as it depends on the internals of the resource picker.
+ # Search for the texture rect that shows the preview and detach it.
+ var children := audio_selector.get_children()
+ while not children.is_empty():
+ var child := children.pop_back() as Node
+ children.append_array(child.get_children())
+ if child is TextureRect:
+ child.get_parent().remove_child(child)
+
+func _on_play_button_clicked():
+ var stream := _event.audio_streams[_stream_id]
+ if stream:
+ EAEditorTools.play_sound(_event, stream)
+
+func _on_stream_weight_changed(val):
+ _event.probability_weights[_stream_id] = val
+ _bank_inspector.signal_entry_changed(false)
+
+func _on_add_resource_button_clicked():
+ _event.add_stream(_stream_id)
+ _bank_inspector.signal_entry_changed(true)
+
+func _on_delete_resource_button_clicked():
+ _event.remove_stream(_stream_id)
+ _bank_inspector.signal_entry_changed(true)
+
+func _on_stream_changed(res: Resource):
+ _event.audio_streams[_stream_id] = res as AudioStream
+ _bank_inspector.signal_entry_changed(false)
diff --git a/main.gd b/main.gd
index cfdb5e8..9f07500 100644
--- a/main.gd
+++ b/main.gd
@@ -7,6 +7,7 @@ extends Control
@onready var trueview: int = 0
# variables for nodes
+@onready var sound: Node2D = $Sounds
@onready var roll_dice: TextureButton = $Roll/Roll_dice
@onready var psyline_other: Control = $Psy_line_other
@onready var psyline_self: Control = $Psy_line_self
@@ -15,10 +16,11 @@ extends Control
@onready var keep_psy: CheckBox = $Roll/Keep_psy
@onready var keep_traits: CheckBox = $Roll/Keep_traits
@onready var language_choice: MenuButton = $Language
+@onready var soundplayer: EAEventBankMounter = $SoundPlayer
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
- lets_roll(seed)
+ lets_roll(seed, 0)
roll_dice.pressed.connect(_on_roll_dice_pressed)
language_choice.get_popup().connect("id_pressed", _on_menu_language_selected)
@@ -35,13 +37,17 @@ func _process(delta: float) -> void:
func _on_roll_dice_pressed() -> void:
lets_roll(seed)
-func lets_roll(chosen_seed: int) -> void:
+func lets_roll(chosen_seed: int, noise: bool = 1) -> void:
# Random psy profiles
var possible_psy: int = $Psy_line_other.resources.size()
var random = RandomNumberGenerator.new()
random.seed = chosen_seed
+ # Play sound if wanted
+ if noise:
+ EventAudio.play_2d("DICEROLL", sound)
+
# Randomize psychology
if not keep_psy.button_pressed:
random.randomize()
diff --git a/main.tscn b/main.tscn
index 9f71c7a..b67b4ac 100644
--- a/main.tscn
+++ b/main.tscn
@@ -1,10 +1,12 @@
-[gd_scene load_steps=6 format=3 uid="uid://d14wihi8i46og"]
+[gd_scene load_steps=8 format=3 uid="uid://d14wihi8i46og"]
[ext_resource type="PackedScene" uid="uid://bff864odl7vlj" path="res://psyline.tscn" id="1_03r8p"]
[ext_resource type="Script" path="res://main.gd" id="1_gqole"]
[ext_resource type="Theme" uid="uid://qsom67gcgupx" path="res://themes/base.tres" id="2_vc4v2"]
[ext_resource type="PackedScene" uid="uid://cl5wswke7jxpi" path="res://traits.tscn" id="3_t3gol"]
[ext_resource type="Texture2D" uid="uid://b7fg7g42642p" path="res://textures/dices.png" id="5_rsth0"]
+[ext_resource type="Script" path="res://addons/event_audio/src/ea_event_bank_mounter.gd" id="6_im5e0"]
+[ext_resource type="Resource" uid="uid://c51i5hutfhrsx" path="res://sounds/eventbanks/UI_sounds.tres" id="7_6knbw"]
[node name="Main" type="Control"]
layout_mode = 3
@@ -239,3 +241,9 @@ item_count = 2
popup/item_0/text = "FRENCH"
popup/item_1/text = "ENGLISH"
popup/item_1/id = 1
+
+[node name="SoundPlayer" type="Node" parent="."]
+script = ExtResource("6_im5e0")
+_audio_bank_resource = ExtResource("7_6knbw")
+
+[node name="Sounds" type="Node2D" parent="."]
diff --git a/project.godot b/project.godot
index 42a7cd8..e79dd72 100644
--- a/project.godot
+++ b/project.godot
@@ -15,6 +15,14 @@ run/main_scene="res://main.tscn"
config/features=PackedStringArray("4.3", "Forward Plus")
config/icon="res://textures/icon.png"
+[autoload]
+
+EventAudio="*res://addons/event_audio/scenes/event_audio_autoload.tscn"
+
+[editor_plugins]
+
+enabled=PackedStringArray("res://addons/event_audio/plugin.cfg")
+
[input]
ui_accept={
diff --git a/sounds/dieShuffle1.ogg b/sounds/assets/dieShuffle1.ogg
similarity index 100%
rename from sounds/dieShuffle1.ogg
rename to sounds/assets/dieShuffle1.ogg
diff --git a/sounds/assets/dieShuffle1.ogg.import b/sounds/assets/dieShuffle1.ogg.import
new file mode 100644
index 0000000..c77412e
--- /dev/null
+++ b/sounds/assets/dieShuffle1.ogg.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="oggvorbisstr"
+type="AudioStreamOggVorbis"
+uid="uid://bbsb62p7ci00d"
+path="res://.godot/imported/dieShuffle1.ogg-dcb2d2e1f13163ad0879571ecc9e036c.oggvorbisstr"
+
+[deps]
+
+source_file="res://sounds/assets/dieShuffle1.ogg"
+dest_files=["res://.godot/imported/dieShuffle1.ogg-dcb2d2e1f13163ad0879571ecc9e036c.oggvorbisstr"]
+
+[params]
+
+loop=false
+loop_offset=0
+bpm=0
+beat_count=0
+bar_beats=4
diff --git a/sounds/dieShuffle1.ogg.import b/sounds/dieShuffle1.ogg.import
deleted file mode 100644
index f5bd4ab..0000000
--- a/sounds/dieShuffle1.ogg.import
+++ /dev/null
@@ -1,19 +0,0 @@
-[remap]
-
-importer="oggvorbisstr"
-type="AudioStreamOggVorbis"
-uid="uid://bbsb62p7ci00d"
-path="res://.godot/imported/dieShuffle1.ogg-839321e52515a7254293b5f786bc71b8.oggvorbisstr"
-
-[deps]
-
-source_file="res://sounds/dieShuffle1.ogg"
-dest_files=["res://.godot/imported/dieShuffle1.ogg-839321e52515a7254293b5f786bc71b8.oggvorbisstr"]
-
-[params]
-
-loop=false
-loop_offset=0
-bpm=0
-beat_count=0
-bar_beats=4
diff --git a/sounds/eventbanks/UI_sounds.tres b/sounds/eventbanks/UI_sounds.tres
new file mode 100644
index 0000000..307c4e0
--- /dev/null
+++ b/sounds/eventbanks/UI_sounds.tres
@@ -0,0 +1,30 @@
+[gd_resource type="Resource" script_class="EAEventBank" load_steps=7 format=3 uid="uid://c51i5hutfhrsx"]
+
+[ext_resource type="Script" path="res://addons/event_audio/src/ea_event.gd" id="1_kbnqw"]
+[ext_resource type="Script" path="res://addons/event_audio/src/ea_event_bank.gd" id="2_jlkrc"]
+[ext_resource type="AudioStream" uid="uid://bbsb62p7ci00d" path="res://sounds/assets/dieShuffle1.ogg" id="2_uyd46"]
+[ext_resource type="Script" path="res://addons/event_audio/src/ea_event_playback_settings.gd" id="3_dcij5"]
+
+[sub_resource type="Resource" id="Resource_odclp"]
+script = ExtResource("3_dcij5")
+stop_when_source_dies = false
+stationary = false
+volume_db = 0.0
+min_pitch = 1.0
+max_pitch = 1.0
+panning_strength = 1.0
+attenuation = 1.0
+max_distance = 2000
+unit_size = 10.0
+max_db = 3.0
+
+[sub_resource type="Resource" id="Resource_5ogac"]
+script = ExtResource("1_kbnqw")
+audio_streams = Array[AudioStream]([ExtResource("2_uyd46")])
+probability_weights = Array[float]([1.0])
+trigger_tags = "DICEROLL"
+playback_settings = SubResource("Resource_odclp")
+
+[resource]
+script = ExtResource("2_jlkrc")
+entries = Array[ExtResource("1_kbnqw")]([SubResource("Resource_5ogac")])