use crate::NotThreadSafe;
use crate::commands::{CommandBaseKind, CommandFlags};
use sdk_raw::util::cstr::{borrow_cstr, copy_cstr};
use sdk_raw::vcall;
use std::ffi::{CStr, CString, c_int};
use std::iter::FusedIterator;
use std::marker::PhantomData;
use std::ptr::NonNull;
const MAX_LISTED: usize = 1 << 16;
interface! {
#[doc(alias("ICvar"))]
pub struct Cvar(sys::ICvar) = Engine sdk_raw::interfaces::cvar::VERSION;
}
#[doc(alias("ConCommandBase"))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CommandBase<'s> {
raw: NonNull<sys::ConCommandBase>,
kind: CommandBaseKind,
_scope: PhantomData<&'s ()>,
_not_thread_safe: NotThreadSafe,
}
impl<'s> CommandBase<'s> {
pub const fn as_ptr(self) -> *mut sys::ConCommandBase {
self.raw.as_ptr()
}
pub const fn as_var(self) -> Option<ConVar<'s>> {
match self.kind {
CommandBaseKind::Command => None,
CommandBaseKind::Variable => Some(unsafe { ConVar::from_raw(self.raw.cast()) }),
}
}
#[doc(alias("GetFlags", "IsFlagSet"))]
pub fn flags(self) -> CommandFlags {
if let Some(var) = self.as_var() {
return var.flags();
}
CommandFlags::from_bits_retain(unsafe { (&raw const (*self.as_ptr()).m_nFlags).read() })
}
#[doc(alias("IsCommand"))]
pub const fn kind(self) -> CommandBaseKind {
self.kind
}
#[doc(alias("GetName"))]
pub fn name(self) -> &'s CStr {
unsafe { borrow_cstr((&raw const (*self.as_ptr()).m_pszName).read()) }.unwrap_or_default()
}
}
#[derive(Debug, Clone)]
pub struct CommandBases<'s> {
listed: std::vec::IntoIter<CommandBase<'s>>,
}
impl DoubleEndedIterator for CommandBases<'_> {
fn next_back(&mut self) -> Option<Self::Item> {
self.listed.next_back()
}
}
impl ExactSizeIterator for CommandBases<'_> {}
impl FusedIterator for CommandBases<'_> {}
impl<'s> Iterator for CommandBases<'s> {
type Item = CommandBase<'s>;
fn next(&mut self) -> Option<Self::Item> {
self.listed.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.listed.size_hint()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ConVar<'s> {
raw: NonNull<sys::ConVar>,
_scope: PhantomData<&'s ()>,
_not_thread_safe: NotThreadSafe,
}
impl<'s> ConVar<'s> {
pub(crate) const unsafe fn from_raw(raw: NonNull<sys::ConVar>) -> Self {
Self {
raw,
_scope: PhantomData,
_not_thread_safe: PhantomData,
}
}
pub const fn as_ptr(self) -> *mut sys::ConVar {
self.raw.as_ptr()
}
#[doc(alias("GetDefault"))]
pub fn default_string(self) -> CString {
unsafe { copy_cstr((&raw const (*self.parent()).m_pszDefaultValue).read()) }
.unwrap_or_default()
}
#[doc(alias("GetFlags", "IsFlagSet"))]
pub fn flags(self) -> CommandFlags {
CommandFlags::from_bits_retain(unsafe {
(&raw const (*self.parent())._base.m_nFlags).read()
})
}
#[doc(alias("GetFloat"))]
pub fn float(self) -> f32 {
unsafe { (&raw const (*self.parent()).m_fValue).read() }
}
#[doc(alias("GetInt"))]
pub fn int(self) -> c_int {
unsafe { (&raw const (*self.parent()).m_nValue).read() }
}
fn interface(self) -> *mut sys::IConVar {
unsafe { &raw mut (*self.as_ptr())._base_1 }
}
pub fn is_default(self) -> bool {
let parent = self.parent();
unsafe {
let current = borrow_cstr((&raw const (*parent).m_pszString).read());
let default = borrow_cstr((&raw const (*parent).m_pszDefaultValue).read());
current.unwrap_or_default() == default.unwrap_or_default()
}
}
#[doc(alias("GetName"))]
pub fn name(self) -> &'s CStr {
unsafe { borrow_cstr((&raw const (*self.as_ptr())._base.m_pszName).read()) }
.unwrap_or_default()
}
fn parent(self) -> *mut sys::ConVar {
let parent = unsafe { (&raw const (*self.as_ptr()).m_pParent).read() };
if parent.is_null() {
self.as_ptr()
} else {
parent
}
}
#[doc(alias("SetValue"))]
pub fn set_float(self, value: f32) {
unsafe { vcall!(self.interface() => IConVar_SetValue1(value)) };
}
#[doc(alias("SetValue"))]
pub fn set_int(self, value: c_int) {
unsafe { vcall!(self.interface() => IConVar_SetValue2(value)) };
}
#[doc(alias("SetValue"))]
pub fn set_string(self, value: &CStr) {
unsafe { vcall!(self.interface() => IConVar_SetValue(value.as_ptr())) };
}
pub fn set_string_quietly(self, value: &CStr) {
let notify = CommandFlags::NOTIFY.bits();
let flags = unsafe { &raw mut (*self.parent())._base.m_nFlags };
let announced = unsafe { flags.read() } & notify;
unsafe { flags.write(flags.read() & !notify) };
self.set_string(value);
unsafe { flags.write(flags.read() | announced) };
}
#[doc(alias("GetString"))]
pub fn string(self) -> CString {
unsafe { copy_cstr((&raw const (*self.parent()).m_pszString).read()) }.unwrap_or_default()
}
}
impl<'s> Cvar<'s> {
#[doc(alias("AllocateDLLIdentifier"))]
pub(crate) fn allocate_dll_identifier(self) -> sys::CVarDLLIdentifier_t {
unsafe { vcall!(self.as_ptr() => ICvar_AllocateDLLIdentifier()) }
}
#[doc(alias("CallGlobalChangeCallbacks"))]
pub(crate) unsafe fn call_global_change_callbacks(
self,
var: NonNull<sys::ConVar>,
old_value: &CStr,
old_float: f32,
) {
unsafe {
vcall!(self.as_ptr() => ICvar_CallGlobalChangeCallbacks(
var.as_ptr(),
old_value.as_ptr(),
old_float,
))
};
}
#[doc(alias("GetCommands", "GetNext"))]
pub fn command_bases(self) -> CommandBases<'s> {
let mut listed = Vec::new();
let mut next = unsafe { vcall!(self.as_ptr() => ICvar_GetCommands()) };
while let Some(base) = NonNull::new(next)
&& listed.len() < MAX_LISTED
{
let is_command = unsafe { vcall!(base.as_ptr() => ConCommandBase_IsCommand()) };
next = unsafe { (&raw const (*base.as_ptr()).m_pNext).read() };
listed.push(CommandBase {
raw: base,
kind: if is_command {
CommandBaseKind::Command
} else {
CommandBaseKind::Variable
},
_scope: PhantomData,
_not_thread_safe: PhantomData,
});
}
CommandBases {
listed: listed.into_iter(),
}
}
#[doc(alias("ConsolePrintf"))]
pub(crate) fn console_printf(self, message: &CStr) {
unsafe { vcall!(self.as_ptr() => ICvar_ConsolePrintf(c"%s".as_ptr(), message.as_ptr())) };
}
#[doc(alias("FindCommandBase"))]
pub(crate) fn find_command_base(self, name: &CStr) -> Option<NonNull<sys::ConCommandBase>> {
NonNull::new(unsafe { vcall!(self.as_ptr() => ICvar_FindCommandBase(name.as_ptr())) })
}
#[doc(alias("FindVar"))]
pub fn find_var(self, name: &CStr) -> Option<ConVar<'s>> {
let var = NonNull::new(unsafe { vcall!(self.as_ptr() => ICvar_FindVar(name.as_ptr())) })?;
Some(unsafe { ConVar::from_raw(var) })
}
#[doc(alias("RegisterConCommand"))]
pub(crate) unsafe fn register_con_command(self, command: NonNull<sys::ConCommandBase>) {
unsafe { vcall!(self.as_ptr() => ICvar_RegisterConCommand(command.as_ptr())) };
}
#[doc(alias("UnregisterConCommand"))]
pub(crate) unsafe fn unregister_con_command(self, command: NonNull<sys::ConCommandBase>) {
unsafe { vcall!(self.as_ptr() => ICvar_UnregisterConCommand(command.as_ptr())) };
}
#[doc(alias("GetCommands"))]
pub fn vars(self) -> impl DoubleEndedIterator<Item = ConVar<'s>> + FusedIterator {
self.command_bases().filter_map(CommandBase::as_var)
}
}