#[cfg(test)]
#[path = "tests/soundscapes.rs"]
mod tests;
use crate::datatables::NetPropError;
use crate::entities::Entity;
use crate::interfaces::ServerTools;
use crate::server::{InterfaceError, Server};
use sdk_raw::soundscapes::{EnvSoundscapeLayout, NO_SOUNDSCAPE};
use sdk_raw::util::cstr::copy_cstr;
use std::ffi::{CStr, CString, c_int};
use std::num::NonZero;
#[doc(alias("audioparams_t", "m_audio"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PlayerSoundscape {
#[doc(alias("entIndex"))]
pub source: Option<SoundscapeId>,
#[doc(alias("soundscapeIndex"))]
pub index: SoundscapeIndex,
}
#[doc(alias(
"CEnvSoundscape",
"env_soundscape",
"env_soundscape_proxy",
"env_soundscape_triggerable"
))]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Soundscape<'s> {
entity: Entity<'s>,
layout: EnvSoundscapeLayout,
}
impl<'s> Soundscape<'s> {
pub fn new(entity: Entity<'s>) -> Option<Self> {
Some(Self {
entity,
layout: EnvSoundscapeLayout::find(entity.data_maps())?,
})
}
pub const fn entity(self) -> Entity<'s> {
self.entity
}
#[doc(alias("m_soundscapeEntityId"))]
pub fn id(self) -> Option<SoundscapeId> {
SoundscapeId::new(unsafe { self.layout.entity_id(self.entity.as_ptr()).read() })
}
#[doc(alias("m_soundscapeIndex"))]
pub fn index(self) -> SoundscapeIndex {
SoundscapeIndex(unsafe { self.layout.index(self.entity.as_ptr()).read() })
}
#[doc(alias("m_bDisabled"))]
pub fn is_enabled(self) -> bool {
unsafe { self.layout.disabled(self.entity.as_ptr()).read() == 0 }
}
#[doc(alias("m_soundscapeName"))]
pub fn name(self) -> Option<CString> {
unsafe { copy_cstr(self.layout.name(self.entity.as_ptr()).read().pszValue) }
}
#[doc(alias("m_soundscapeIndex"))]
pub fn set_index(self, index: SoundscapeIndex) {
unsafe { self.layout.index(self.entity.as_ptr()).write(index.0) };
}
}
#[derive(Debug, thiserror::Error)]
pub enum SoundscapeError {
#[error(transparent)]
Interface(#[from] InterfaceError),
#[error(transparent)]
NetProp(#[from] NetPropError),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SoundscapeId(NonZero<c_int>);
impl SoundscapeId {
const fn new(id: c_int) -> Option<Self> {
match NonZero::new(id) {
Some(id) if id.get() > 0 => Some(Self(id)),
_ => None,
}
}
pub const fn get(self) -> c_int {
self.0.get()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct SoundscapeIndex(c_int);
impl SoundscapeIndex {
pub const NONE: Self = Self(NO_SOUNDSCAPE);
pub const fn get(self) -> c_int {
self.0
}
pub const fn is_none(self) -> bool {
self.0 < 0
}
}
impl<'s> ServerTools<'s> {
pub fn spawn_soundscape(self, name: &CStr) -> Option<Soundscape<'s>> {
let entity = unsafe { self.create_entity_by_name(c"env_soundscape") }?;
self.set_key_value(entity, c"soundscape", name);
self.set_key_value(entity, c"StartDisabled", c"1");
unsafe { self.dispatch_spawn(entity) };
Soundscape::new(entity)
}
}
pub fn player_soundscape<'s>(
server: Server<'s>,
player: Entity<'s>,
) -> Result<PlayerSoundscape, SoundscapeError> {
let game = server.server_game_dll()?;
let index = game
.entity_net_prop(player, c"m_audio.soundscapeIndex")?
.get::<i32>(player)?;
let source = game
.entity_net_prop(player, c"m_audio.entIndex")?
.get::<i32>(player)?;
Ok(PlayerSoundscape {
source: SoundscapeId::new(source),
index: SoundscapeIndex(index),
})
}
pub fn set_player_soundscape<'s>(
server: Server<'s>,
player: Entity<'s>,
soundscape: PlayerSoundscape,
) -> Result<(), SoundscapeError> {
let game = server.server_game_dll()?;
let engine = server.valve_engine()?;
let index = game.entity_net_prop(player, c"m_audio.soundscapeIndex")?;
let source = game.entity_net_prop(player, c"m_audio.entIndex")?;
let source_id = soundscape.source.map_or(0, SoundscapeId::get);
unsafe {
index.set(engine, player, soundscape.index.get())?;
source.set(engine, player, source_id)?;
}
Ok(())
}