Add announcements

This commit is contained in:
Sofia 2026-07-24 02:13:21 +03:00
parent 2f54365cea
commit a1f9895207
32 changed files with 428 additions and 3 deletions

View File

@ -2,5 +2,6 @@
- [LaserFast and LaserWithRecovery](godot/raw_assets/sfx/) - [LaserFast and LaserWithRecovery](godot/raw_assets/sfx/)
- [Hit](godot/raw_assets/sfx/Hit.mp3) - [Hit](godot/raw_assets/sfx/Hit.mp3)
- [Footstep sounds on Tree Trunk](godot/raw_assets/sfx/footsteps/) via [zapsplat](https://www.zapsplat.com/sound-effect-packs/footsteps-on-fallen-tree-trunk) - [Footstep sounds on Tree Trunk](godot/raw_assets/sfx/footsteps/) via [zapsplat](https://www.zapsplat.com/sound-effect-packs/footsteps-on-fallen-tree-trunk)
- [Announcer sounds](godot/raw_assets/sfx/announcer/) by [TripleSnail](https://opengameart.org/content/announcer-kill-sounds)
- [Soldier model and rig](godot/raw_assets/Soldier/) by [Casti_131](https://opengameart.org/content/low-poly-soldier-with-weapons) - [Soldier model and rig](godot/raw_assets/Soldier/) by [Casti_131](https://opengameart.org/content/low-poly-soldier-with-weapons)
- [Torment Textures](godot/maps/textures) (`str_*` files) by [Bradley D. (strideh.itch.io)](https://strideh.itch.io) - [Torment Textures](godot/maps/textures) (`str_*` files) by [Bradley D. (strideh.itch.io)](https://strideh.itch.io)

View File

@ -22,6 +22,7 @@ CommandLinePanelGlobal="*uid://bsb6asp5o35w3"
GameManagerGlobal="*uid://bii6018k3ip3t" GameManagerGlobal="*uid://bii6018k3ip3t"
CliParserGlobal="*uid://c52o7xa0fnr53" CliParserGlobal="*uid://c52o7xa0fnr53"
GameSettingsGlobal="*uid://bjr15hjiojecw" GameSettingsGlobal="*uid://bjr15hjiojecw"
AnnouncerGlobal="*uid://5gey4i80mmsa"
[display] [display]

Binary file not shown.

View File

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://cmx868xfm5tr"
path="res://.godot/imported/Carnage.wav-0aa25ce2dbaf67c412d878ecccaa2a43.sample"
[deps]
source_file="res://raw_assets/sfx/announcer/Carnage.wav"
dest_files=["res://.godot/imported/Carnage.wav-0aa25ce2dbaf67c412d878ecccaa2a43.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=2

Binary file not shown.

View File

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://bwv7hydyynxo3"
path="res://.godot/imported/Dominating.wav-9094379de14f4d99f4362076b03e9be9.sample"
[deps]
source_file="res://raw_assets/sfx/announcer/Dominating.wav"
dest_files=["res://.godot/imported/Dominating.wav-9094379de14f4d99f4362076b03e9be9.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=2

Binary file not shown.

View File

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://b5816xcsj2ueh"
path="res://.godot/imported/DoubleKill.wav-1dae3420408145da22058102a5b9489c.sample"
[deps]
source_file="res://raw_assets/sfx/announcer/DoubleKill.wav"
dest_files=["res://.godot/imported/DoubleKill.wav-1dae3420408145da22058102a5b9489c.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=2

Binary file not shown.

View File

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://bg8wugv6ij0pj"
path="res://.godot/imported/FirstBlood.wav-3a1ab2cafcafce472d28811db364622d.sample"
[deps]
source_file="res://raw_assets/sfx/announcer/FirstBlood.wav"
dest_files=["res://.godot/imported/FirstBlood.wav-3a1ab2cafcafce472d28811db364622d.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=2

Binary file not shown.

View File

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://celsg1h2xc85k"
path="res://.godot/imported/Godlike.wav-5ec4df26a22987b555e4f010565e2276.sample"
[deps]
source_file="res://raw_assets/sfx/announcer/Godlike.wav"
dest_files=["res://.godot/imported/Godlike.wav-5ec4df26a22987b555e4f010565e2276.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=2

Binary file not shown.

View File

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://cc5t6p6w468hi"
path="res://.godot/imported/KillingSpree.wav-448dff3e279f17fef54b67c2bf084908.sample"
[deps]
source_file="res://raw_assets/sfx/announcer/KillingSpree.wav"
dest_files=["res://.godot/imported/KillingSpree.wav-448dff3e279f17fef54b67c2bf084908.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=2

Binary file not shown.

View File

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://baml6py7t1ycd"
path="res://.godot/imported/Massacre.wav-26fd0dde29cf79df78d170f36281a8b1.sample"
[deps]
source_file="res://raw_assets/sfx/announcer/Massacre.wav"
dest_files=["res://.godot/imported/Massacre.wav-26fd0dde29cf79df78d170f36281a8b1.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=2

Binary file not shown.

View File

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://bjewmgxjuwsqo"
path="res://.godot/imported/Mayhem.wav-ea221444f4057bff04fd5e46ce2da957.sample"
[deps]
source_file="res://raw_assets/sfx/announcer/Mayhem.wav"
dest_files=["res://.godot/imported/Mayhem.wav-ea221444f4057bff04fd5e46ce2da957.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=2

Binary file not shown.

View File

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://dfxrsl808f6o0"
path="res://.godot/imported/MegaKill.wav-809778ac21e83a3da5eb84b1403e21a2.sample"
[deps]
source_file="res://raw_assets/sfx/announcer/MegaKill.wav"
dest_files=["res://.godot/imported/MegaKill.wav-809778ac21e83a3da5eb84b1403e21a2.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=2

Binary file not shown.

View File

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://4i6t6yh2j45t"
path="res://.godot/imported/Ownage.wav-7853435740c868c6707b6c2ef354df86.sample"
[deps]
source_file="res://raw_assets/sfx/announcer/Ownage.wav"
dest_files=["res://.godot/imported/Ownage.wav-7853435740c868c6707b6c2ef354df86.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=2

Binary file not shown.

View File

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://ccg5hd454nqpe"
path="res://.godot/imported/Rampage.wav-328d28840941970a1c0afd53cf5a6790.sample"
[deps]
source_file="res://raw_assets/sfx/announcer/Rampage.wav"
dest_files=["res://.godot/imported/Rampage.wav-328d28840941970a1c0afd53cf5a6790.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=2

Binary file not shown.

View File

@ -0,0 +1,24 @@
[remap]
importer="wav"
type="AudioStreamWAV"
uid="uid://co82glo13i8at"
path="res://.godot/imported/TripleKill.wav-5cd8a2f25db2a8430eba6089c5c04d0e.sample"
[deps]
source_file="res://raw_assets/sfx/announcer/TripleKill.wav"
dest_files=["res://.godot/imported/TripleKill.wav-5cd8a2f25db2a8430eba6089c5c04d0e.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=2

View File

@ -0,0 +1,15 @@
[gd_scene format=3 uid="uid://5gey4i80mmsa"]
[ext_resource type="AudioStream" uid="uid://bg8wugv6ij0pj" path="res://raw_assets/sfx/announcer/FirstBlood.wav" id="1_h17tq"]
[ext_resource type="AudioStream" uid="uid://b5816xcsj2ueh" path="res://raw_assets/sfx/announcer/DoubleKill.wav" id="2_6jrtu"]
[ext_resource type="AudioStream" uid="uid://co82glo13i8at" path="res://raw_assets/sfx/announcer/TripleKill.wav" id="3_6ob50"]
[ext_resource type="AudioStream" uid="uid://dfxrsl808f6o0" path="res://raw_assets/sfx/announcer/MegaKill.wav" id="4_0lbme"]
[node name="announcer" type="Announcer" unique_id=1062756235 node_paths=PackedStringArray("player")]
player = NodePath("audio_stream_player")
first_blood = ExtResource("1_h17tq")
double_kill = ExtResource("2_6jrtu")
triple_kill = ExtResource("3_6ob50")
mega_kill = ExtResource("4_0lbme")
[node name="audio_stream_player" type="AudioStreamPlayer" parent="." unique_id=1168314007]

62
rust/src/announcer.rs Normal file
View File

@ -0,0 +1,62 @@
use godot::{
classes::{AudioStream, AudioStreamPlayer},
prelude::*,
tools::get_autoload_by_name,
};
use serde::{Deserialize, Serialize};
pub const ANNOUCER_GLOBAL: &str = "AnnouncerGlobal";
#[derive(GodotClass)]
#[class(base=Node3D, init)]
pub struct Announcer {
#[export]
player: Option<Gd<AudioStreamPlayer>>,
#[export]
#[export_group(name = "Sounds")]
first_blood: Option<Gd<AudioStream>>,
#[export]
#[export_group(name = "Sounds")]
double_kill: Option<Gd<AudioStream>>,
#[export]
#[export_group(name = "Sounds")]
triple_kill: Option<Gd<AudioStream>>,
#[export]
#[export_group(name = "Sounds")]
mega_kill: Option<Gd<AudioStream>>,
base: Base<Node3D>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum Announcement {
FirstBlood,
DoubleKill,
TripleKill,
MegaKill,
}
impl Announcer {
pub fn singleton() -> Gd<Announcer> {
get_autoload_by_name::<Announcer>(ANNOUCER_GLOBAL)
}
pub fn announce(&mut self, announcement: Announcement) {
let sound = match announcement {
Announcement::FirstBlood => &self.first_blood,
Announcement::DoubleKill => &self.double_kill,
Announcement::TripleKill => &self.triple_kill,
Announcement::MegaKill => &self.mega_kill,
};
if let Some(sound) = sound
&& let Some(player) = &mut self.player
{
player.set_stream(sound);
player.play();
}
}
}

View File

@ -4,6 +4,10 @@ use godot::{classes::CharacterBody3D, prelude::*, tools::get_autoload_by_name};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
announcer::{
Announcement::{self, FirstBlood},
Announcer,
},
ball::Ball, ball::Ball,
map::Map, map::Map,
map_resource::MapResource, map_resource::MapResource,
@ -282,6 +286,13 @@ impl INode for Game {
} }
} }
} }
for player in &mut self.players {
player.killing_spree_cd -= delta;
if player.killing_spree_cd <= 0. {
player.killing_spree = 0;
}
}
} }
} }
@ -483,6 +494,8 @@ impl Game {
kills: 0, kills: 0,
deaths: 0, deaths: 0,
}, },
killing_spree: 0,
killing_spree_cd: 0.,
character: None, character: None,
}); });
self.run_deferred(move |s| s.signals().on_new_player().emit(id)); self.run_deferred(move |s| s.signals().on_new_player().emit(id));
@ -839,15 +852,48 @@ impl Game {
// Handle for when a player is killed, run both on client and server // Handle for when a player is killed, run both on client and server
pub fn on_kill(&mut self, source: DamageSource, player_id: u16) { pub fn on_kill(&mut self, source: DamageSource, player_id: u16) {
if let DamageSource::Player(source_id) = source let total_prev_kills: u16 = self.players.iter().map(|p| p.data.kills).sum();
let killing_spree = if let DamageSource::Player(source_id) = source
&& let Some(source_player) = self.find_player_mut(source_id) && let Some(source_player) = self.find_player_mut(source_id)
{ {
source_player.data.kills += 1; source_player.data.kills += 1;
} source_player.killing_spree += 1;
source_player.killing_spree_cd = 10.;
source_player.killing_spree
} else {
0
};
if let Some(player) = self.find_player_mut(player_id) { if let Some(player) = self.find_player_mut(player_id) {
player.data.deaths += 1; player.data.deaths += 1;
} }
self.run_deferred(move |s| s.signals().on_player_killed().emit(player_id)); self.run_deferred(move |s| {
s.signals().on_player_killed().emit(player_id);
if let Some(peer) = &mut NetworkManager::singleton().bind_mut().peer
&& let PeerKind::Server(peer, _) = peer
{
if total_prev_kills == 0 {
s.on_announcement(Announcement::FirstBlood);
peer.broadcast_reliable(Package::Announcement(FirstBlood));
}
let announcement = match killing_spree {
2 => Some(Announcement::DoubleKill),
3 => Some(Announcement::TripleKill),
4 => Some(Announcement::MegaKill),
_ => None,
};
if let Some(announcement) = announcement {
s.on_announcement(announcement);
peer.broadcast_reliable(Package::Announcement(announcement));
}
}
});
}
pub fn on_announcement(&mut self, announcement: Announcement) {
Announcer::singleton().bind_mut().announce(announcement);
} }
// Handle taking damage, from server to client // Handle taking damage, from server to client
@ -894,6 +940,8 @@ pub struct Player {
pub connection_addr: SocketAddr, pub connection_addr: SocketAddr,
pub data: NetPlayer, pub data: NetPlayer,
pub character: Option<DynGd<CharacterBody3D, dyn IPlayer + 'static>>, pub character: Option<DynGd<CharacterBody3D, dyn IPlayer + 'static>>,
pub killing_spree: u16,
pub killing_spree_cd: f64,
} }
impl Player { impl Player {
@ -1009,6 +1057,8 @@ impl From<NetPlayer> for Player {
connection_addr: SocketAddr::from(([0, 0, 0, 0], 0)), connection_addr: SocketAddr::from(([0, 0, 0, 0], 0)),
data: value, data: value,
character: None, character: None,
killing_spree: 0,
killing_spree_cd: 0.,
} }
} }
} }

View File

@ -1,5 +1,6 @@
use godot::prelude::*; use godot::prelude::*;
pub mod announcer;
pub mod ball; pub mod ball;
pub mod cli_parser; pub mod cli_parser;
pub mod game_manager; pub mod game_manager;

View File

@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize};
use teanet::{Peer, PeerConfig, PeerMessage}; use teanet::{Peer, PeerConfig, PeerMessage};
use crate::{ use crate::{
announcer::Announcement,
game_manager::{Game, GameManager, GameOption, GameOptionValue, NetPlayer}, game_manager::{Game, GameManager, GameOption, GameOptionValue, NetPlayer},
game_settings::GameSettingsManager, game_settings::GameSettingsManager,
net::{net_stats::Stats, util::NetVector3}, net::{net_stats::Stats, util::NetVector3},
@ -310,6 +311,7 @@ pub enum Package {
DespawnBall, DespawnBall,
SwapWeapon(u16, WeaponType), SwapWeapon(u16, WeaponType),
Goal(Vec<u8>), Goal(Vec<u8>),
Announcement(Announcement),
// End-of-Game packages // End-of-Game packages
FinishGame, FinishGame,

View File

@ -200,6 +200,11 @@ impl NetworkManager {
game.bind_mut().finish_game(); game.bind_mut().finish_game();
} }
} }
Package::Announcement(announcement) => {
if let Some(game) = &mut Game::singleton() {
game.bind_mut().on_announcement(announcement);
}
}
_ => {} _ => {}
}, },
}, },