use crate::interfaces::GameClient;
use crate::net::cheats::{Begun, CheatsError, ClientCheats, LeaseId, Purpose};
use crate::players::UserId;
use crate::{Game, Server};
pub type LockResult = Result<LockState, LockError>;
#[doc(alias("m_bCheatsEverOn"))]
#[derive(Debug, Default)]
pub struct AchievementLock {
requests: Vec<(UserId, Request)>,
}
impl AchievementLock {
pub const fn new() -> Self {
Self {
requests: Vec::new(),
}
}
pub fn clear(&mut self) {
self.requests.clear();
}
pub fn forget(&mut self, user_id: UserId) -> bool {
let known = self.requests.len();
self.requests.retain(|&(id, _)| id != user_id);
self.requests.len() != known
}
pub fn lease(&self, user_id: UserId) -> Option<LeaseId> {
self.requests
.iter()
.find_map(|&(id, request)| match request {
Request::Lease(lease) if id == user_id => Some(lease),
_ => None,
})
}
pub fn lock(
&mut self,
cheats: &mut ClientCheats,
server: Server<'_>,
user_id: UserId,
) -> LockResult {
if server.game() != Game::TeamFortress2 {
return Err(LockError::UnsupportedGame);
}
match self.state(cheats, user_id) {
None | Some(LockState::Failed | LockState::ServerCheats) => {}
Some(state) => return Ok(state),
}
let (request, state) = match cheats.begin(server, user_id, Purpose::Observe)? {
Begun::Lease(lease) => (Request::Lease(lease), LockState::Pending),
Begun::ServerCheats => (Request::ServerCheats, LockState::ServerCheats),
};
self.forget(user_id);
self.requests.push((user_id, request));
Ok(state)
}
pub fn lock_all(
&mut self,
cheats: &mut ClientCheats,
server: Server<'_>,
) -> Result<Vec<(UserId, LockResult)>, LockError> {
if server.game() != Game::TeamFortress2 {
return Err(LockError::UnsupportedGame);
}
let user_ids: Vec<UserId> = server
.valve_engine()
.map_err(CheatsError::from)?
.game_server()
.ok_or(CheatsError::NoServer)?
.clients()
.filter(|client| client.is_connected() && !client.is_fake() && !client.is_hltv())
.filter_map(GameClient::user_id)
.collect();
Ok(user_ids
.into_iter()
.map(|user_id| (user_id, self.lock(cheats, server, user_id)))
.collect())
}
pub fn state(&self, cheats: &ClientCheats, user_id: UserId) -> Option<LockState> {
let client = cheats.client(user_id);
if client.is_some_and(|client| client.confirmed) {
return Some(LockState::Locked);
}
let request = self
.requests
.iter()
.find(|&&(id, _)| id == user_id)
.map(|&(_, request)| request);
match request {
Some(Request::Lease(lease)) if cheats.lease(lease).is_some() => {
Some(LockState::Pending)
}
_ if client.is_some_and(|client| client.refused) => Some(LockState::Refused),
Some(Request::Lease(_)) => Some(LockState::Failed),
Some(Request::ServerCheats) => Some(LockState::ServerCheats),
None => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum LockError {
#[error("achievement locks require Team Fortress 2")]
UnsupportedGame,
#[error(transparent)]
Cheats(#[from] CheatsError),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LockState {
Pending,
Locked,
ServerCheats,
Refused,
Failed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Request {
Lease(LeaseId),
ServerCheats,
}