source_sdk_2013 0.1.0-alpha.7

Higher-level bindings for the Source SDK.
Documentation
//! `IVEngineServer`, the engine's services for the game server.

use crate::edicts::Edict;
use crate::net::NetChannel;
use crate::players::UserId;
use sdk_raw::edicts::MAX_EDICTS;
use sdk_raw::players::ABSOLUTE_PLAYER_LIMIT;
use sdk_raw::tier0::MAX_PATH;
use sdk_raw::util::cstr::{copy_cstr, cstring_from_buffer};
use sdk_raw::vcall;
use std::ffi::{CStr, CString, c_char, c_int};
use std::ptr::{self, NonNull};

interface! {
	/// The engine's services for the game server (`IVEngineServer`).
	#[doc(alias("IVEngineServer"))]
	pub struct ValveEngine(sys::IVEngineServer) = Engine sdk_raw::interfaces::valve_engine::VERSION;
}

impl<'s> ValveEngine<'s> {
	/// The engine's change-tracking record for an edict.
	#[doc(alias("GetChangeAccessor"))]
	pub(crate) fn change_accessor(
		self,
		edict: Edict<'_>,
	) -> Option<NonNull<sys::IChangeInfoAccessor>> {
		// SAFETY: As for `user_id_of_edict`.
		NonNull::new(unsafe {
			vcall!(self.as_ptr() => IVEngineServer_GetChangeAccessor(edict.as_ptr()))
		})
	}

	/// Queues a change to another level, as the `changelevel` command does.
	///
	/// `landmark` is only used by single-player level transitions.
	#[doc(alias("ChangeLevel"))]
	pub fn change_level(self, map: &CStr, landmark: Option<&CStr>) {
		let landmark = landmark.map_or(ptr::null(), CStr::as_ptr);

		// SAFETY: `Server::new` guarantees the interface is live.
		unsafe { vcall!(self.as_ptr() => IVEngineServer_ChangeLevel(map.as_ptr(), landmark)) };
	}

	/// The value a client reported for one of its user settings, the console
	/// variables marked `FCVAR_USERINFO`, such as `name` or `cl_interp`.
	///
	/// The engine reports an unknown setting, or an edict that no connected
	/// client owns, as empty. Returns `None` if the engine returns null.
	#[doc(alias("GetClientConVarValue"))]
	pub fn client_convar_value(self, client: Edict<'_>, name: &CStr) -> Option<CString> {
		// SAFETY: As for `change_level`. The engine checks the index, and the
		// value is copied at once.
		unsafe {
			copy_cstr(
				vcall!(self.as_ptr() => IVEngineServer_GetClientConVarValue(client.index(), name.as_ptr())),
			)
		}
	}

	/// Prints a message to the console of the client owning an edict.
	#[doc(alias("ClientPrintf"))]
	pub fn client_print(self, client: Edict<'_>, message: &CStr) {
		// SAFETY: As for `change_level`, and the edict is live. The engine
		// ignores edicts that no connected client owns.
		unsafe {
			vcall!(self.as_ptr() => IVEngineServer_ClientPrintf(client.as_ptr(), message.as_ptr()))
		};
	}

	/// Offsets the crosshair of the client owning an edict, in degrees.
	#[doc(alias("CrosshairAngle"))]
	pub fn crosshair_angle(self, client: Edict<'_>, pitch: f32, yaw: f32) {
		// SAFETY: As for `set_view`.
		unsafe {
			vcall!(self.as_ptr() => IVEngineServer_CrosshairAngle(client.as_ptr(), pitch, yaw))
		};
	}

	/// Looks up the edict at an entity index.
	///
	/// Returns `None` if the index is outside the edict table or its slot is
	/// free. The engine keeps every player slot's edict in use, even while no
	/// client occupies it.
	#[doc(alias("PEntityOfEntIndex", "INDEXENT"))]
	pub fn edict_of_index(self, index: c_int) -> Option<Edict<'s>> {
		// The engine validates the index too, but an out-of-range index never
		// needs to reach it.
		if !(0..MAX_EDICTS).contains(&index) {
			return None;
		}

		// SAFETY: As for `change_level`.
		let edict = NonNull::new(unsafe {
			vcall!(self.as_ptr() => IVEngineServer_PEntityOfEntIndex(index))
		})?;

		// SAFETY: The engine returned an element of its edict table, which only
		// a level change reallocates, and no level changes during `'s`.
		Some(unsafe { Edict::from_raw(edict) })
	}

	/// Finds the edict of the player with a user ID.
	///
	/// Like the game's own `UTIL_PlayerByUserId`, this asks the engine which
	/// client owns the edict of each player slot. Returns `None` if no connected
	/// client has the user ID.
	///
	/// A client's edict resolves as soon as it connects, before its player
	/// entity spawns, and until its disconnection completes, so the edict may
	/// have no entity yet. A lookup scans up to [`ABSOLUTE_PLAYER_LIMIT`] slots.
	pub fn edict_of_user_id(self, user_id: UserId) -> Option<Edict<'s>> {
		(1..=ABSOLUTE_PLAYER_LIMIT)
			.filter_map(|index| self.edict_of_index(index))
			.find(|&edict| !edict.is_free() && self.user_id_of_edict(edict) == Some(user_id))
	}

	/// The number of edicts in use (`GetEntityCount`), one for each networked
	/// entity, of the [`MAX_EDICTS`] the edict table holds. Server-only
	/// entities take none.
	///
	/// The count is optimistic about how many more entities the engine can
	/// network: it reuses a freed edict only once a second has passed since it
	/// was freed, and stops the server with a fatal error when it needs an
	/// edict while the table holds none it can use.
	#[doc(alias("GetEntityCount"))]
	pub fn entity_count(self) -> c_int {
		// SAFETY: As for `change_level`.
		unsafe { vcall!(self.as_ptr() => IVEngineServer_GetEntityCount()) }
	}

	/// The path of the game directory, such as `.../tf`.
	#[doc(alias("GetGameDir"))]
	pub fn game_dir(self) -> CString {
		let mut buffer = [0 as c_char; MAX_PATH];

		// SAFETY: As for `change_level`, and the buffer length is passed.
		unsafe {
			vcall!(self.as_ptr() => IVEngineServer_GetGameDir(buffer.as_mut_ptr(), MAX_PATH as c_int))
		};

		cstring_from_buffer(&buffer)
	}

	/// Whether a map file exists and can be loaded, such as `maps/ctf_2fort.bsp`.
	#[doc(alias("IsMapValid"))]
	pub fn is_map_valid(self, file: &CStr) -> bool {
		// SAFETY: As for `change_level`.
		unsafe { vcall!(self.as_ptr() => IVEngineServer_IsMapValid(file.as_ptr())) != 0 }
	}

	/// Locks or unlocks the network string tables, and returns whether they
	/// were locked before.
	///
	/// While a level runs, the game unlocks the tables around code that adds
	/// strings, such as spawning a player, and then restores the state this
	/// returned, as the engine's interface asks of every caller.
	/// [`Self::with_unlocked_string_tables`] does both.
	#[doc(alias("LockNetworkStringTables"))]
	pub fn lock_network_string_tables(self, lock: bool) -> bool {
		// SAFETY: As for `change_level`. The game itself sets both states during
		// ordinary play, such as around a player's first spawn.
		unsafe { vcall!(self.as_ptr() => IVEngineServer_LockNetworkStringTables(lock)) }
	}

	/// Writes a line to the server log, as the `log` command does.
	#[doc(alias("LogPrint"))]
	pub fn log_print(self, message: &CStr) {
		// SAFETY: As for `change_level`.
		unsafe { vcall!(self.as_ptr() => IVEngineServer_LogPrint(message.as_ptr())) };
	}

	/// The net channel of the client owning an edict.
	///
	/// Returns `None` for an edict that no connected client owns, and for fake
	/// clients such as bots and SourceTV, which have no channel.
	#[doc(alias("GetPlayerNetInfo"))]
	pub fn net_channel(self, client: Edict<'_>) -> Option<NetChannel<'s>> {
		// SAFETY: As for `change_level`. The engine checks the index.
		let info = NonNull::new(unsafe {
			vcall!(self.as_ptr() => IVEngineServer_GetPlayerNetInfo(client.index()))
		})?;

		// SAFETY: The engine returns its client's `CNetChan`, whose only base is
		// `INetChannel`, which derives only from `INetChannelInfo`, so both are
		// at the same address. The engine frees a channel when its client
		// disconnects, which nothing safe causes during `'s`.
		Some(unsafe { NetChannel::from_raw(info.cast()) })
	}

	/// The network ID of the client owning an edict, such as a rendered Steam
	/// ID or `BOT`.
	///
	/// Returns `None` for an edict that no client owns, which covers the edict
	/// of every entity that is not a player. The edict of an empty player slot
	/// can still report an ID, such as `STEAM_ID_PENDING`.
	#[doc(alias("GetPlayerNetworkIDString"))]
	pub fn player_network_id(self, edict: Edict<'_>) -> Option<CString> {
		// SAFETY: As for `user_id_of_edict`. The engine renders the ID into a
		// buffer it reuses, so it is copied immediately.
		unsafe {
			copy_cstr(
				vcall!(self.as_ptr() => IVEngineServer_GetPlayerNetworkIDString(edict.as_ptr())),
			)
		}
	}

	/// Queues a command as though it were entered at the server console.
	///
	/// Commands are normally processed on the next frame. Include a command
	/// separator such as a trailing newline when required by the command parser.
	#[doc(alias("ServerCommand"))]
	pub fn server_command(self, command: &CStr) {
		// SAFETY: As for `change_level`.
		unsafe { vcall!(self.as_ptr() => IVEngineServer_ServerCommand(command.as_ptr())) };
	}

	/// Renders a client's view from another entity, such as a camera, or from
	/// its own player again.
	#[doc(alias("SetView"))]
	pub fn set_view(self, client: Edict<'_>, view: Edict<'_>) {
		// SAFETY: As for `change_level`, and both edicts are live. The engine
		// ignores edicts that no connected client owns.
		unsafe { vcall!(self.as_ptr() => IVEngineServer_SetView(client.as_ptr(), view.as_ptr())) };
	}

	/// The engine's per-frame record of changed network variables.
	#[doc(alias("GetSharedEdictChangeInfo"))]
	pub(crate) fn shared_edict_change_info(self) -> Option<NonNull<sys::CSharedEdictChangeInfo>> {
		// SAFETY: As for `change_level`.
		NonNull::new(unsafe { vcall!(self.as_ptr() => IVEngineServer_GetSharedEdictChangeInfo()) })
	}

	/// Returns the user ID of the player whose client owns an edict.
	///
	/// Returns `None` for an edict that no connected client owns, which covers
	/// the edict of every entity that is not a player.
	#[doc(alias("GetPlayerUserId"))]
	pub fn user_id_of_edict(self, edict: Edict<'_>) -> Option<UserId> {
		// The engine returns -1 for an edict none of its clients own, and can
		// return 0 for the edict of a client slot nobody occupies. Neither is a
		// user ID, and both mean no player owns the edict.
		//
		// SAFETY: As for `change_level`, and the edict is live.
		UserId::from_raw(unsafe {
			vcall!(self.as_ptr() => IVEngineServer_GetPlayerUserId(edict.as_ptr()))
		})
		.ok()
	}

	/// Runs `f` with the network string tables unlocked, then restores the lock
	/// state they had before, even if `f` panics.
	///
	/// Wrap additions made while a level runs, such as
	/// [`NetworkStringTable::add`], in this, as the game does around its own
	/// additions.
	///
	/// [`NetworkStringTable::add`]: crate::interfaces::network_string_tables::NetworkStringTable::add
	#[doc(alias("LockNetworkStringTables"))]
	pub fn with_unlocked_string_tables<R>(self, f: impl FnOnce() -> R) -> R {
		/// Restores the tables' previous lock state when dropped.
		struct Restore<'s> {
			engine: ValveEngine<'s>,
			locked: bool,
		}

		impl Drop for Restore<'_> {
			fn drop(&mut self) {
				self.engine.lock_network_string_tables(self.locked);
			}
		}

		let _restore = Restore {
			engine: self,
			locked: self.lock_network_string_tables(false),
		};

		f()
	}
}