use crate::{Game, Server};
use sdk_raw::tf2::duels::find_duel_job_vtables;
use sdk_raw::util;
use std::ffi::c_void;
use std::marker::PhantomData;
use std::ptr::NonNull;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DuelJob {
#[doc(alias("CGC_GameServer_Duel_Request", "k_EMsgGC_Duel_Request"))]
Request,
#[doc(alias("CGC_GameServer_Duel_Response", "k_EMsgGC_Duel_Response"))]
Response,
}
impl DuelJob {
pub const ALL: [Self; 2] = [Self::Request, Self::Response];
}
#[derive(Debug, Clone, Copy)]
pub struct DuelJobVtable<'s> {
pub job: DuelJob,
vtable: NonNull<*mut c_void>,
_scope: PhantomData<&'s Server<'s>>,
}
impl DuelJobVtable<'_> {
pub const fn as_ptr(self) -> NonNull<*mut c_void> {
self.vtable
}
}
#[derive(Debug, thiserror::Error)]
pub enum DuelJobVtableError {
#[error("duel jobs 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 duel job {0:?}")]
NotFound(DuelJob),
}
impl From<util::Error> for DuelJobVtableError {
fn from(error: util::Error) -> Self {
match error {
util::Error::InvalidImage => Self::InvalidImage,
util::Error::Io(error) => Self::Image(error),
}
}
}
pub fn duel_job_vtables(server: Server<'_>) -> Result<[DuelJobVtable<'_>; 2], DuelJobVtableError> {
if server.game() != Game::TeamFortress2 {
return Err(DuelJobVtableError::WrongGame);
}
let [request, response] =
unsafe { find_duel_job_vtables(server.game_server_factory().as_raw()) }?;
let vtable = |job: DuelJob, vtable: Option<NonNull<*mut c_void>>| {
vtable
.map(|vtable| DuelJobVtable {
job,
vtable,
_scope: PhantomData,
})
.ok_or(DuelJobVtableError::NotFound(job))
};
Ok([
vtable(DuelJob::Request, request)?,
vtable(DuelJob::Response, response)?,
])
}