#[cfg(test)]
#[path = "../../tests/tf2/objectives/round_timer.rs"]
mod tests;
use super::{Objective, ObjectiveError};
use crate::Server;
use crate::entities::Entity;
use crate::entities::outputs::OutputAction;
use crate::inputs::InputValue;
use crate::tf2::scoreboard::ScoringTeam;
use sdk_raw::players::TEAM_UNASSIGNED;
use sdk_raw::tf2::objectives as raw;
use std::ffi::{CStr, CString, c_int};
#[doc(alias("tf_logic_koth", "CKothLogic"))]
#[derive(Debug, Clone, Copy)]
pub struct KothLogic<'s>(Objective<'s>);
impl<'s> KothLogic<'s> {
pub fn new(server: Server<'s>, entity: Entity<'s>) -> Result<Self, ObjectiveError> {
Objective::new(server, entity, c"CKothLogic", "tf_logic_koth").map(Self)
}
#[doc(alias("AddRedTimer", "AddBlueTimer"))]
pub fn add_timer(self, team: ScoringTeam, seconds: c_int) -> Result<(), ObjectiveError> {
let input = match team {
ScoringTeam::Red => c"AddRedTimer",
ScoringTeam::Blue => c"AddBlueTimer",
};
self.0.input(input, InputValue::Int(seconds))
}
pub fn entity(self) -> Entity<'s> {
self.0.entity()
}
#[doc(alias("m_nTimerInitialLength", "timer_length"))]
pub fn initial_timer_length(self) -> Result<c_int, ObjectiveError> {
self.0.int_field(c"CKothLogic", c"m_nTimerInitialLength")
}
#[doc(alias("SetRedTimer", "SetBlueTimer"))]
pub fn set_timer(self, team: ScoringTeam, seconds: c_int) -> Result<(), ObjectiveError> {
let input = match team {
ScoringTeam::Red => c"SetRedTimer",
ScoringTeam::Blue => c"SetBlueTimer",
};
self.0.input(input, InputValue::Int(seconds))
}
pub fn timer(self, team: ScoringTeam) -> Result<Option<RoundTimer<'s>>, ObjectiveError> {
let name = match team {
ScoringTeam::Red => raw::KOTH_RED_TIMER_NAME,
ScoringTeam::Blue => raw::KOTH_BLUE_TIMER_NAME,
};
super::find_by_name(self.0.server(), name, RoundTimer::new)
}
#[doc(alias("m_nTimeToUnlockPoint", "unlock_point"))]
pub fn unlock_delay(self) -> Result<c_int, ObjectiveError> {
self.0.int_field(c"CKothLogic", c"m_nTimeToUnlockPoint")
}
}
#[doc(alias("team_round_timer", "CTeamRoundTimer"))]
#[derive(Debug, Clone, Copy)]
pub struct RoundTimer<'s>(Objective<'s>);
impl<'s> RoundTimer<'s> {
pub fn new(server: Server<'s>, entity: Entity<'s>) -> Result<Self, ObjectiveError> {
Objective::new(server, entity, c"CTeamRoundTimer", "team_round_timer").map(Self)
}
#[doc(alias("AddTeamTime"))]
pub fn add_team_time(self, team: ScoringTeam, seconds: c_int) -> Result<(), ObjectiveError> {
let value = CString::new(format!("{} {seconds}", team.to_raw()))
.expect("integers format without NUL bytes");
self.0.input(c"AddTeamTime", InputValue::String(&value))
}
#[doc(alias("AddTime"))]
pub fn add_time(self, seconds: c_int) -> Result<(), ObjectiveError> {
self.0.input(c"AddTime", InputValue::Int(seconds))
}
#[doc(alias("m_bAutoCountdown", "auto_countdown"))]
pub fn announces_countdown(self) -> Result<bool, ObjectiveError> {
self.0.flag(c"m_bAutoCountdown")
}
#[doc(alias("Disable"))]
pub fn disable(self) -> Result<(), ObjectiveError> {
self.0.input(c"Disable", InputValue::Void)
}
#[doc(alias("Enable"))]
pub fn enable(self) -> Result<(), ObjectiveError> {
self.0.input(c"Enable", InputValue::Void)
}
#[doc(alias("m_flTimerEndTime"))]
pub fn end_time(self) -> Result<f32, ObjectiveError> {
self.0.get(c"m_flTimerEndTime")
}
pub fn entity(self) -> Entity<'s> {
self.0.entity()
}
#[doc(alias("GetTimerMaxLength"))]
pub fn hud_length(self) -> Result<c_int, ObjectiveError> {
if self.state()? == TimerState::Setup {
return self.setup_length();
}
match self.max_length()? {
Some(max) => Ok(max),
None => self.length(),
}
}
#[doc(alias("m_nTimerInitialLength", "timer_length"))]
pub fn initial_length(self) -> Result<c_int, ObjectiveError> {
self.0.get(c"m_nTimerInitialLength")
}
#[doc(alias("m_bInCaptureWatchState"))]
pub fn is_capture_watching(self) -> Result<bool, ObjectiveError> {
self.0.flag(c"m_bInCaptureWatchState")
}
#[doc(alias("m_bIsDisabled", "StartDisabled"))]
pub fn is_disabled(self) -> Result<bool, ObjectiveError> {
self.0.flag(c"m_bIsDisabled")
}
#[doc(alias("m_bTimerPaused"))]
pub fn is_paused(self) -> Result<bool, ObjectiveError> {
self.0.flag(c"m_bTimerPaused")
}
#[doc(alias("m_bStopWatchTimer"))]
pub fn is_stopwatch(self) -> Result<bool, ObjectiveError> {
self.0.flag(c"m_bStopWatchTimer")
}
#[doc(alias("m_nTimerLength"))]
pub fn length(self) -> Result<c_int, ObjectiveError> {
self.0.get(c"m_nTimerLength")
}
#[doc(alias("m_nTimerMaxLength"))]
pub fn max_length(self) -> Result<Option<c_int>, ObjectiveError> {
let max: c_int = self.0.get(c"m_nTimerMaxLength")?;
Ok((max > 0).then_some(max))
}
pub fn output_actions(self, output: RoundTimerOutput) -> Option<Vec<OutputAction>> {
self.0.entity().output_actions(output.name())
}
#[doc(alias("Pause"))]
pub fn pause(self) -> Result<(), ObjectiveError> {
self.0.input(c"Pause", InputValue::Void)
}
#[doc(alias("Restart"))]
pub fn restart(self) -> Result<(), ObjectiveError> {
self.0.input(c"Restart", InputValue::Void)
}
#[doc(alias("Resume"))]
pub fn resume(self) -> Result<(), ObjectiveError> {
self.0.input(c"Resume", InputValue::Void)
}
#[doc(alias("AutoCountdown"))]
pub fn set_announces_countdown(self, announces: bool) -> Result<(), ObjectiveError> {
self.0
.input(c"AutoCountdown", InputValue::Int(c_int::from(announces)))
}
#[doc(alias("SetMaxTime"))]
pub fn set_max_length(self, max: Option<c_int>) -> Result<(), ObjectiveError> {
let max = max.filter(|max| *max > 0).unwrap_or(0);
self.0.input(c"SetMaxTime", InputValue::Int(max))
}
#[doc(alias("SetSetupTime"))]
pub fn set_setup_length(self, seconds: c_int) -> Result<(), ObjectiveError> {
self.0.input(c"SetSetupTime", InputValue::Int(seconds))
}
#[doc(alias("ShowInHUD"))]
pub fn set_shown_in_hud(self, shown: bool) -> Result<(), ObjectiveError> {
self.0
.input(c"ShowInHUD", InputValue::Int(c_int::from(shown)))
}
#[doc(alias("SetTime"))]
pub fn set_time(self, seconds: c_int) -> Result<(), ObjectiveError> {
self.0.input(c"SetTime", InputValue::Int(seconds))
}
#[doc(alias("m_nSetupTimeLength"))]
pub fn setup_length(self) -> Result<c_int, ObjectiveError> {
self.0.get(c"m_nSetupTimeLength")
}
#[doc(alias("m_bShowInHUD", "show_in_hud"))]
pub fn shows_in_hud(self) -> Result<bool, ObjectiveError> {
self.0.flag(c"m_bShowInHUD")
}
#[doc(alias("m_bShowTimeRemaining", "show_time_remaining"))]
pub fn shows_time_remaining(self) -> Result<bool, ObjectiveError> {
self.0.flag(c"m_bShowTimeRemaining")
}
#[doc(alias("m_nState"))]
pub fn state(self) -> Result<TimerState, ObjectiveError> {
let state: c_int = self.0.get(c"m_nState")?;
TimerState::from_raw(state).ok_or(ObjectiveError::UnknownValue {
name: c"m_nState",
value: state,
})
}
#[doc(alias("GetTimeRemaining"))]
pub fn time_remaining(self) -> Result<f32, ObjectiveError> {
let remaining = if self.is_stopwatch()? && self.is_capture_watching()? {
self.0.get(c"m_flTotalTime")?
} else if self.is_paused()? {
self.0.get(c"m_flTimeRemaining")?
} else {
self.end_time()? - self.0.current_time()?
};
Ok(remaining.max(0.0))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RoundTimerOutput {
#[doc(alias("OnRoundStart"))]
RoundStart,
#[doc(alias("OnFinished"))]
Finished,
#[doc(alias("OnSetupStart"))]
SetupStart,
#[doc(alias("OnSetupFinished"))]
SetupFinished,
#[doc(alias("On5MinRemain"))]
FiveMinutesLeft,
#[doc(alias("On4MinRemain"))]
FourMinutesLeft,
#[doc(alias("On3MinRemain"))]
ThreeMinutesLeft,
#[doc(alias("On2MinRemain"))]
TwoMinutesLeft,
#[doc(alias("On1MinRemain"))]
OneMinuteLeft,
#[doc(alias("On30SecRemain"))]
ThirtySecondsLeft,
#[doc(alias("On10SecRemain"))]
TenSecondsLeft,
#[doc(alias("On5SecRemain"))]
FiveSecondsLeft,
#[doc(alias("On4SecRemain"))]
FourSecondsLeft,
#[doc(alias("On3SecRemain"))]
ThreeSecondsLeft,
#[doc(alias("On2SecRemain"))]
TwoSecondsLeft,
#[doc(alias("On1SecRemain"))]
OneSecondLeft,
}
impl RoundTimerOutput {
pub const fn name(self) -> &'static CStr {
match self {
Self::RoundStart => c"OnRoundStart",
Self::Finished => c"OnFinished",
Self::SetupStart => c"OnSetupStart",
Self::SetupFinished => c"OnSetupFinished",
Self::FiveMinutesLeft => c"On5MinRemain",
Self::FourMinutesLeft => c"On4MinRemain",
Self::ThreeMinutesLeft => c"On3MinRemain",
Self::TwoMinutesLeft => c"On2MinRemain",
Self::OneMinuteLeft => c"On1MinRemain",
Self::ThirtySecondsLeft => c"On30SecRemain",
Self::TenSecondsLeft => c"On10SecRemain",
Self::FiveSecondsLeft => c"On5SecRemain",
Self::FourSecondsLeft => c"On4SecRemain",
Self::ThreeSecondsLeft => c"On3SecRemain",
Self::TwoSecondsLeft => c"On2SecRemain",
Self::OneSecondLeft => c"On1SecRemain",
}
}
}
#[doc(alias("game_round_win", "CTeamplayRoundWin"))]
#[derive(Debug, Clone, Copy)]
pub struct RoundWin<'s>(Objective<'s>);
impl<'s> RoundWin<'s> {
pub fn new(server: Server<'s>, entity: Entity<'s>) -> Result<Self, ObjectiveError> {
Objective::new(server, entity, c"CTeamplayRoundWin", "game_round_win").map(Self)
}
pub fn entity(self) -> Entity<'s> {
self.0.entity()
}
#[doc(alias("m_bForceMapReset", "force_map_reset"))]
pub fn resets_map(self) -> Result<bool, ObjectiveError> {
self.0.bool_field(c"CTeamplayRoundWin", c"m_bForceMapReset")
}
pub fn set_resets_map(self, resets: bool) -> Result<(), ObjectiveError> {
self.0
.set_key_value(c"force_map_reset", if resets { c"1" } else { c"0" })
}
pub fn set_switches_teams(self, switches: bool) -> Result<(), ObjectiveError> {
self.0
.set_key_value(c"switch_teams", if switches { c"1" } else { c"0" })
}
#[doc(alias("SetTeam"))]
pub fn set_team(self, team: Option<ScoringTeam>) -> Result<(), ObjectiveError> {
let team = team.map_or(TEAM_UNASSIGNED, ScoringTeam::to_raw);
self.0.input(c"SetTeam", InputValue::Int(team))
}
pub fn set_win_reason(self, reason: WinReason) -> Result<(), ObjectiveError> {
let value =
CString::new(reason.to_raw().to_string()).expect("integers format without NUL bytes");
self.0.set_key_value(c"win_reason", &value)
}
#[doc(alias("m_bSwitchTeamsOnWin", "switch_teams"))]
pub fn switches_teams(self) -> Result<bool, ObjectiveError> {
self.0
.bool_field(c"CTeamplayRoundWin", c"m_bSwitchTeamsOnWin")
}
#[doc(alias("m_iTeamNum", "TeamNum"))]
pub fn team(self) -> Result<Option<ScoringTeam>, ObjectiveError> {
Ok(ScoringTeam::from_raw(
self.0.int_field(c"CBaseEntity", c"m_iTeamNum")?,
))
}
#[doc(alias("RoundWin"))]
pub fn win(self) -> Result<(), ObjectiveError> {
self.0.check_round_end()?;
self.0.input(c"RoundWin", InputValue::Void)
}
#[doc(alias("m_iWinReason"))]
pub fn win_reason(self) -> Result<WinReason, ObjectiveError> {
let reason = self.0.int_field(c"CTeamplayRoundWin", c"m_iWinReason")?;
WinReason::from_raw(reason).ok_or(ObjectiveError::UnknownValue {
name: c"m_iWinReason",
value: reason,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TimerState {
#[doc(alias("RT_STATE_SETUP"))]
Setup,
#[doc(alias("RT_STATE_NORMAL"))]
Normal,
}
impl TimerState {
pub const fn from_raw(raw: c_int) -> Option<Self> {
match raw {
raw::RT_STATE_SETUP => Some(Self::Setup),
raw::RT_STATE_NORMAL => Some(Self::Normal),
_ => None,
}
}
pub const fn to_raw(self) -> c_int {
match self {
Self::Setup => raw::RT_STATE_SETUP,
Self::Normal => raw::RT_STATE_NORMAL,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum WinReason {
#[doc(alias("WINREASON_NONE"))]
None,
#[doc(alias("WINREASON_ALL_POINTS_CAPTURED"))]
AllPointsCaptured,
#[doc(alias("WINREASON_OPPONENTS_DEAD"))]
OpponentsDead,
#[doc(alias("WINREASON_FLAG_CAPTURE_LIMIT"))]
FlagCaptureLimit,
#[doc(alias("WINREASON_DEFEND_UNTIL_TIME_LIMIT"))]
DefendedUntilTimeLimit,
#[doc(alias("WINREASON_STALEMATE"))]
Stalemate,
#[doc(alias("WINREASON_TIMELIMIT"))]
TimeLimit,
#[doc(alias("WINREASON_WINLIMIT"))]
WinLimit,
#[doc(alias("WINREASON_WINDIFFLIMIT"))]
WinDifferenceLimit,
#[doc(alias("WINREASON_RD_REACTOR_CAPTURED"))]
ReactorCaptured,
#[doc(alias("WINREASON_RD_CORES_COLLECTED"))]
CoresCollected,
#[doc(alias("WINREASON_RD_REACTOR_RETURNED"))]
ReactorReturned,
#[doc(alias("WINREASON_PD_POINTS"))]
PlayerDestructionPoints,
#[doc(alias("WINREASON_SCORED"))]
Scored,
#[doc(alias("WINREASON_STOPWATCH_WATCHING_ROUNDS"))]
StopwatchWatchingRounds,
#[doc(alias("WINREASON_STOPWATCH_WATCHING_FINAL_ROUND"))]
StopwatchWatchingFinalRound,
#[doc(alias("WINREASON_STOPWATCH_PLAYING_ROUNDS"))]
StopwatchPlayingRounds,
}
impl WinReason {
pub const ALL: [Self; 17] = [
Self::None,
Self::AllPointsCaptured,
Self::OpponentsDead,
Self::FlagCaptureLimit,
Self::DefendedUntilTimeLimit,
Self::Stalemate,
Self::TimeLimit,
Self::WinLimit,
Self::WinDifferenceLimit,
Self::ReactorCaptured,
Self::CoresCollected,
Self::ReactorReturned,
Self::PlayerDestructionPoints,
Self::Scored,
Self::StopwatchWatchingRounds,
Self::StopwatchWatchingFinalRound,
Self::StopwatchPlayingRounds,
];
pub const fn from_raw(raw: c_int) -> Option<Self> {
Some(match raw {
raw::WINREASON_NONE => Self::None,
raw::WINREASON_ALL_POINTS_CAPTURED => Self::AllPointsCaptured,
raw::WINREASON_OPPONENTS_DEAD => Self::OpponentsDead,
raw::WINREASON_FLAG_CAPTURE_LIMIT => Self::FlagCaptureLimit,
raw::WINREASON_DEFEND_UNTIL_TIME_LIMIT => Self::DefendedUntilTimeLimit,
raw::WINREASON_STALEMATE => Self::Stalemate,
raw::WINREASON_TIMELIMIT => Self::TimeLimit,
raw::WINREASON_WINLIMIT => Self::WinLimit,
raw::WINREASON_WINDIFFLIMIT => Self::WinDifferenceLimit,
raw::WINREASON_RD_REACTOR_CAPTURED => Self::ReactorCaptured,
raw::WINREASON_RD_CORES_COLLECTED => Self::CoresCollected,
raw::WINREASON_RD_REACTOR_RETURNED => Self::ReactorReturned,
raw::WINREASON_PD_POINTS => Self::PlayerDestructionPoints,
raw::WINREASON_SCORED => Self::Scored,
raw::WINREASON_STOPWATCH_WATCHING_ROUNDS => Self::StopwatchWatchingRounds,
raw::WINREASON_STOPWATCH_WATCHING_FINAL_ROUND => Self::StopwatchWatchingFinalRound,
raw::WINREASON_STOPWATCH_PLAYING_ROUNDS => Self::StopwatchPlayingRounds,
_ => return None,
})
}
pub const fn to_raw(self) -> c_int {
match self {
Self::None => raw::WINREASON_NONE,
Self::AllPointsCaptured => raw::WINREASON_ALL_POINTS_CAPTURED,
Self::OpponentsDead => raw::WINREASON_OPPONENTS_DEAD,
Self::FlagCaptureLimit => raw::WINREASON_FLAG_CAPTURE_LIMIT,
Self::DefendedUntilTimeLimit => raw::WINREASON_DEFEND_UNTIL_TIME_LIMIT,
Self::Stalemate => raw::WINREASON_STALEMATE,
Self::TimeLimit => raw::WINREASON_TIMELIMIT,
Self::WinLimit => raw::WINREASON_WINLIMIT,
Self::WinDifferenceLimit => raw::WINREASON_WINDIFFLIMIT,
Self::ReactorCaptured => raw::WINREASON_RD_REACTOR_CAPTURED,
Self::CoresCollected => raw::WINREASON_RD_CORES_COLLECTED,
Self::ReactorReturned => raw::WINREASON_RD_REACTOR_RETURNED,
Self::PlayerDestructionPoints => raw::WINREASON_PD_POINTS,
Self::Scored => raw::WINREASON_SCORED,
Self::StopwatchWatchingRounds => raw::WINREASON_STOPWATCH_WATCHING_ROUNDS,
Self::StopwatchWatchingFinalRound => raw::WINREASON_STOPWATCH_WATCHING_FINAL_ROUND,
Self::StopwatchPlayingRounds => raw::WINREASON_STOPWATCH_PLAYING_ROUNDS,
}
}
}