use crate::{Game, Server};
use sdk_raw::tf2::airblast::{REFERENCE_CLASS, find_airblast_vtables};
use sdk_raw::util;
use std::ffi::c_void;
use std::marker::PhantomData;
use std::ptr::NonNull;
#[derive(Debug, thiserror::Error)]
pub enum AirblastVtableError {
#[error("airblast hooks require Team Fortress 2")]
WrongGame,
#[error("the game module could not be inspected")]
Image(#[from] std::io::Error),
#[error("the game module has an unsupported executable image")]
InvalidImage,
#[error("no unique primary vtable found for TF2's weapon class `{0}`")]
NotFound(&'static str),
}
impl From<util::Error> for AirblastVtableError {
fn from(error: util::Error) -> Self {
match error {
util::Error::InvalidImage => Self::InvalidImage,
util::Error::Io(error) => Self::Image(error),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct AirblastVtables<'s> {
weapons: [NonNull<*mut c_void>; 2],
reference: NonNull<*mut c_void>,
_scope: PhantomData<&'s Server<'s>>,
}
impl AirblastVtables<'_> {
pub const fn reference(self) -> NonNull<*mut c_void> {
self.reference
}
pub const fn weapons(self) -> [NonNull<*mut c_void>; 2] {
self.weapons
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AirblastWeapon {
#[doc(alias("CTFFlameThrower", "tf_weapon_flamethrower"))]
FlameThrower,
#[doc(alias("CTFWeaponFlameBall", "tf_weapon_rocketlauncher_fireball"))]
DragonsFury,
}
impl AirblastWeapon {
pub const ALL: [Self; 2] = [Self::FlameThrower, Self::DragonsFury];
pub const fn class(self) -> &'static str {
match self {
Self::FlameThrower => sdk_raw::tf2::airblast::FLAME_THROWER_CLASS,
Self::DragonsFury => sdk_raw::tf2::airblast::DRAGONS_FURY_CLASS,
}
}
}
pub fn airblast_vtables(server: Server<'_>) -> Result<AirblastVtables<'_>, AirblastVtableError> {
if server.game() != Game::TeamFortress2 {
return Err(AirblastVtableError::WrongGame);
}
let [flame_thrower, dragons_fury, reference] =
unsafe { find_airblast_vtables(server.game_server_factory().as_raw()) }?;
let found = |vtable: Option<NonNull<*mut c_void>>, class| {
vtable.ok_or(AirblastVtableError::NotFound(class))
};
Ok(AirblastVtables {
weapons: [
found(flame_thrower, AirblastWeapon::FlameThrower.class())?,
found(dragons_fury, AirblastWeapon::DragonsFury.class())?,
],
reference: found(reference, REFERENCE_CLASS)?,
_scope: PhantomData,
})
}