diff --git a/Cirno.sln.DotSettings.user b/Cirno.sln.DotSettings.user
index c8698013..a96154b9 100644
--- a/Cirno.sln.DotSettings.user
+++ b/Cirno.sln.DotSettings.user
@@ -19,6 +19,7 @@
ForceIncluded
ForceIncluded
ForceIncluded
+ ForceIncluded
ForceIncluded
ForceIncluded
ForceIncluded
\ No newline at end of file
diff --git a/Resources/Bullets/Explosion.tres b/Resources/Bullets/Explosion.tres
index 0601af65..257b79bd 100644
--- a/Resources/Bullets/Explosion.tres
+++ b/Resources/Bullets/Explosion.tres
@@ -9,8 +9,11 @@ BulletScene = ExtResource("1_bca33")
BulletSpeed = 0.0
Direction = Vector2(1, 0)
BulletDamage = 8.0
-LifeTime = 1.0
+LifeTime = 0.4
DestroyOnCollision = false
Owner = 0
DamageType = 4
+Controllable = false
+Grazeable = false
+GrazeValue = 1.0
TimeModifiers = null
diff --git a/Resources/bus_layout.tres b/Resources/bus_layout.tres
index 7b6617f6..8e3ee784 100644
--- a/Resources/bus_layout.tres
+++ b/Resources/bus_layout.tres
@@ -1,7 +1,8 @@
[gd_resource type="AudioBusLayout" load_steps=2 format=3 uid="uid://clblu7fse2pjy"]
-[sub_resource type="AudioEffectLimiter" id="AudioEffectLimiter_0f13h"]
-resource_name = "Limiter"
+[sub_resource type="AudioEffectHardLimiter" id="AudioEffectHardLimiter_0f13h"]
+resource_name = "HardLimiter"
+ceiling_db = -10.0
[resource]
bus/1/name = &"Music"
@@ -16,7 +17,7 @@ bus/2/mute = false
bus/2/bypass_fx = false
bus/2/volume_db = 0.0
bus/2/send = &"Master"
-bus/2/effect/0/effect = SubResource("AudioEffectLimiter_0f13h")
+bus/2/effect/0/effect = SubResource("AudioEffectHardLimiter_0f13h")
bus/2/effect/0/enabled = true
bus/3/name = &"Voice"
bus/3/solo = false
diff --git a/Scenes/Weapons/Bullets/Autoclearing_Explosion_Bullet.tscn b/Scenes/Weapons/Bullets/Autoclearing_Explosion_Bullet.tscn
index f2181c18..75e0683b 100644
--- a/Scenes/Weapons/Bullets/Autoclearing_Explosion_Bullet.tscn
+++ b/Scenes/Weapons/Bullets/Autoclearing_Explosion_Bullet.tscn
@@ -6,6 +6,7 @@
[node name="AutoclearingExplosionBullet" type="Node2D"]
script = ExtResource("1_5c773")
+Timeout = 0.4
BulletResource = ExtResource("2_d2d24")
EmitOnStart = true
EmitCoolDown = 10.0
diff --git a/Scenes/Weapons/Bullets/explosion.tscn b/Scenes/Weapons/Bullets/explosion.tscn
index 82900ef8..602909a0 100644
--- a/Scenes/Weapons/Bullets/explosion.tscn
+++ b/Scenes/Weapons/Bullets/explosion.tscn
@@ -1,8 +1,9 @@
-[gd_scene load_steps=5 format=3 uid="uid://h11o0et1y54v"]
+[gd_scene load_steps=6 format=3 uid="uid://h11o0et1y54v"]
[ext_resource type="Script" uid="uid://dsa4b75hdig8p" path="res://Scripts/Bullet.cs" id="1_f0epf"]
[ext_resource type="SpriteFrames" uid="uid://lh1q76788ixw" path="res://Resources/Sprites/explosion_proc_1.tres" id="2_wng0j"]
[ext_resource type="AudioStream" uid="uid://ds84e0m5l4i5d" path="res://SFX/404752__owlstorm__retro-video-game-sfx-explode-3.wav" id="3_wng0j"]
+[ext_resource type="Script" uid="uid://dwnqgkuj6bgay" path="res://Scripts/Misc/LimitedAudioPlayer.cs" id="4_0imfi"]
[sub_resource type="CircleShape2D" id="CircleShape2D_jxptd"]
radius = 28.0179
@@ -35,6 +36,10 @@ autoplay = true
max_distance = 300.0
bus = &"Effects"
area_mask = 8
+script = ExtResource("4_0imfi")
+AudioName = &"EXPLOSION"
+ReparentOnCreation = true
+Duration = 0.55
[connection signal="area_entered" from="." to="." method="_on_area_entered"]
[connection signal="body_entered" from="." to="." method="_on_body_entered"]
diff --git a/Scenes/Weapons/bullet.tscn b/Scenes/Weapons/bullet.tscn
index ddff4a62..a44c8dba 100644
--- a/Scenes/Weapons/bullet.tscn
+++ b/Scenes/Weapons/bullet.tscn
@@ -1,8 +1,9 @@
-[gd_scene load_steps=5 format=3 uid="uid://b1qnfiuokpvsr"]
+[gd_scene load_steps=6 format=3 uid="uid://b1qnfiuokpvsr"]
[ext_resource type="Texture2D" uid="uid://cybpmpb0d8yva" path="res://Sprites/Projectile.png" id="1_2eu87"]
[ext_resource type="Script" uid="uid://dsa4b75hdig8p" path="res://Scripts/Bullet.cs" id="1_jvxw3"]
[ext_resource type="AudioStream" uid="uid://cjg8r7bthkfsy" path="res://SFX/Laser_shoot 11.wav" id="3_8bitv"]
+[ext_resource type="Script" uid="uid://dwnqgkuj6bgay" path="res://Scripts/Misc/LimitedAudioPlayer.cs" id="4_jxgah"]
[sub_resource type="CircleShape2D" id="CircleShape2D_jxptd"]
radius = 2.23607
@@ -30,6 +31,8 @@ stream = ExtResource("3_8bitv")
autoplay = true
bus = &"Effects"
area_mask = 8
+script = ExtResource("4_jxgah")
+AudioName = &"ICE_BULLET"
[connection signal="area_entered" from="." to="." method="_on_area_entered"]
[connection signal="body_entered" from="." to="." method="_on_body_entered"]
diff --git a/Scripts/GameManager.cs b/Scripts/GameManager.cs
index 2e8cca70..e0a7af3b 100644
--- a/Scripts/GameManager.cs
+++ b/Scripts/GameManager.cs
@@ -4,6 +4,7 @@ using System.Linq;
using System.Threading.Tasks;
using Cirno.Scripts;
using Cirno.Scripts.Components.FSM;
+using Cirno.Scripts.Misc;
using Cirno.Scripts.Resources;
using Godot.Collections;
using Cirno.Scripts.Utils;
@@ -60,6 +61,8 @@ public partial class GameManager : Node2D
public delegate void PlayerRespawnedEventHandler();
public Vector2 LastCheckpointPosition { get; set; }
+
+ private AudioManager _audioManager;
[Export]
public Node2D PlayerParentNode { get; set; }
@@ -76,6 +79,9 @@ public partial class GameManager : Node2D
GlobalState.Instance.SaveGame();
}
+ _audioManager = new AudioManager();
+ this.AddChild(_audioManager);
+
_hud = GetNodeOrNull("HUD");
if (_hud == null) GD.Print("No HUD in scene.");
diff --git a/Scripts/Misc/AudioManager.cs b/Scripts/Misc/AudioManager.cs
new file mode 100644
index 00000000..c78d8b18
--- /dev/null
+++ b/Scripts/Misc/AudioManager.cs
@@ -0,0 +1,52 @@
+using System.Collections.Generic;
+using Godot;
+
+namespace Cirno.Scripts.Misc;
+
+public partial class AudioManager : Node2D
+{
+ public static AudioManager Instance { get; private set; }
+
+ [Export]
+ public int ConcurrentSounds { get; set; } = 3;
+
+ private Dictionary _audioDict = new Dictionary();
+
+ public override void _Ready()
+ {
+ Instance = this;
+ }
+
+ public bool CanPlay(string audioName)
+ {
+ var item = _audioDict.TryGetValue(audioName, out int amount);
+ if (item) return amount < ConcurrentSounds;
+ _audioDict.Add(audioName, 0);
+ return true;
+ }
+
+ public bool Play(string audioName)
+ {
+ if (!CanPlay(audioName)) return false;
+ if (!_audioDict.ContainsKey(audioName))
+ {
+ _audioDict.Add(audioName, 0);
+ }
+ _audioDict[audioName] += 1;
+
+ return true;
+ }
+
+ public void Stop(string audioName)
+ {
+ if (_audioDict.ContainsKey(audioName))
+ {
+ _audioDict[audioName] -= 1;
+
+ if (_audioDict[audioName] < 0)
+ {
+ _audioDict[audioName] = 0;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Scripts/Misc/AudioManager.cs.uid b/Scripts/Misc/AudioManager.cs.uid
new file mode 100644
index 00000000..87d7052b
--- /dev/null
+++ b/Scripts/Misc/AudioManager.cs.uid
@@ -0,0 +1 @@
+uid://ci2px8dicc0xx
diff --git a/Scripts/Misc/LimitedAudioPlayer.cs b/Scripts/Misc/LimitedAudioPlayer.cs
new file mode 100644
index 00000000..4a861d47
--- /dev/null
+++ b/Scripts/Misc/LimitedAudioPlayer.cs
@@ -0,0 +1,82 @@
+using Godot;
+
+namespace Cirno.Scripts.Misc;
+
+public partial class LimitedAudioPlayer : AudioStreamPlayer2D
+{
+ [Export] public StringName AudioName { get; private set; }
+ [Export] public bool ReparentOnCreation { get; private set; } = false;
+ [Export] public float Duration { get; private set; } = -1;
+
+ private bool _finished = false;
+
+ private double _timer = 0;
+
+ public override void _Ready()
+ {
+ if (ReparentOnCreation)
+ {
+ CallDeferred(MethodName.ReparentDeferred);
+ }
+
+ if (Duration >= 0)
+ {
+
+ }
+
+ // Check if it can play
+ if (IsAutoplayEnabled())
+ {
+ TryPlay();
+ }
+
+ this.Finished += OnFinished;
+ }
+
+ public override void _Process(double delta)
+ {
+ if (Duration >= 0 && IsPlaying())
+ {
+ _timer += delta;
+
+ if (_timer >= Duration)
+ {
+ this.Stop();
+ }
+ }
+ }
+
+ private void ReparentDeferred()
+ {
+ this.Reparent(GameManager.Instance.BulletsContainer);
+ }
+
+ public override void _ExitTree()
+ {
+ this.Finished -= OnFinished;
+
+ if (!_finished)
+ {
+ AudioManager.Instance.Stop(AudioName);
+ }
+ }
+
+ public void TryPlay()
+ {
+ if (!AudioManager.Instance.Play(AudioName))
+ {
+ this.SetVolumeDb(-100);
+ return;
+ }
+ else
+ {
+ this.SetVolumeDb(0);
+ }
+ }
+
+ private void OnFinished()
+ {
+ AudioManager.Instance.Stop(AudioName);
+ _finished = true;
+ }
+}
\ No newline at end of file
diff --git a/Scripts/Misc/LimitedAudioPlayer.cs.uid b/Scripts/Misc/LimitedAudioPlayer.cs.uid
new file mode 100644
index 00000000..dc568ff5
--- /dev/null
+++ b/Scripts/Misc/LimitedAudioPlayer.cs.uid
@@ -0,0 +1 @@
+uid://dwnqgkuj6bgay