source_sdk_2013 0.1.0-alpha.8

Higher-level bindings for the Source SDK.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
//! Console commands and variables implemented in Rust.
//!
//! A [`ConsoleCommand`] is laid out as the engine's `ConCommand`, so the
//! engine lists, finds, and runs it like any command a game module declares.
//! A [`ConsoleVariable`] is laid out as its `ConVar` likewise. Registering
//! either takes it pinned and `'static`, since the engine keeps its address.
//!
//! # Who runs a command
//!
//! [`Invoker`] tells a handler where an invocation came from:
//!
//! - [`Invoker::Server`]: the server console, rcon, a config file,
//!   [`ValveEngine::server_command`], or other server-side code. The engine
//!   delivers these through the command's `Dispatch`.
//! - [`Invoker::Client`]: a string command a connected client sent. The
//!   engine only dispatches a client's command directly when the command is
//!   flagged `FCVAR_GAMEDLL` or is on its short list of engine client commands,
//!   and it cannot tell which client ran it then. Commands from this module
//!   never report that flag, and [`validate_name`] rejects the listed names, so
//!   the engine hands a client's command to `IServerGameClients::ClientCommand`,
//!   together with the client's edict, instead. A plugin hooks that method and passes each call to
//!   [`route_client_command`], which runs the command if it is one of ours and
//!   clients may run it.
//!
//! Attribution therefore follows the path an invocation took, so nested
//! commands and commands run during level changes are never attributed to the
//! wrong invoker. [`CommandAccess`] decides which invokers a command accepts.
//!
//! [`ValveEngine::server_command`]: crate::interfaces::ValveEngine::server_command

mod args;
mod error;
mod object;
mod registrar;
mod route;
mod variable;

#[cfg(test)]
#[path = "../tests/commands.rs"]
mod tests;

use crate::NotThreadSafe;
use crate::edicts::Edict;
use crate::server::{InterfaceError, Server};
use sdk_raw::commands as raw;
use std::borrow::Cow;
use std::ffi::{CStr, CString, c_int};
use std::fmt::Display;
use std::marker::PhantomData;

pub use args::{ArgError, CommandArgs};

pub use error::{
	CommandBaseKind, InvalidCommandName, RegisterCommandError, RegisterCommandErrorKind,
	UnregisterCommandError, UnregisterCommandErrorKind, validate_name,
};

pub use object::ConsoleCommand;
pub use registrar::{CommandRegistrar, UnlinksBeforeUnload};
pub use route::{ClientRoute, route_client_command};
pub use source_sdk_2013_declmacros::{commands, convars};
pub use variable::ConsoleVariable;

pub(crate) use route::drop_payload;

/// A plain function handler, which keeps `ConsoleCommand<CommandFn>` nameable
/// in a `static`.
pub type CommandFn = for<'a, 'd> fn(&'a CommandContext<'d>) -> CommandResult;

/// What a handler returns.
pub type CommandResult = Result<(), CommandError>;

/// The client that ran a command.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Client<'d> {
	edict: Edict<'d>,
}

impl<'d> Client<'d> {
	/// The edict of the client's player.
	pub const fn edict(self) -> Edict<'d> {
		self.edict
	}

	/// The client's slot, one less than its edict's index.
	pub fn slot(self) -> c_int {
		self.edict.index() - 1
	}
}

/// Narrows the clients a command lets run it, such as to a plugin's admins:
/// see [`ConsoleCommand::clients`].
///
/// Clients it refuses are told the command is unknown, as for any command a
/// client cannot run. Server-side invokers are never filtered.
#[derive(Debug, Clone, Copy)]
pub struct ClientFilter {
	/// Who the filter lets through, in a few words, for listings such as a
	/// plugin's help command: `"admins"`.
	pub name: &'static str,

	/// Whether the client may run the command. It is asked before each of the
	/// client's invocations, and by [`ConsoleCommand::allows`].
	pub accepts: for<'d> fn(Server<'d>, Client<'d>) -> bool,
}

/// Who may run a command.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum CommandAccess {
	/// Server-side invokers only (see [`Invoker::Server`]). Clients that run the
	/// command are told it is unknown, as for any command a client cannot run.
	#[default]
	Server,

	/// Connected clients only. Server-side invokers are told the command is for
	/// players.
	///
	/// Clients' commands reach the game only if no command of ours takes them.
	/// The game also handles some client commands by name without registering
	/// them, such as TF2's `build` and `jointeam`, which the registry cannot
	/// report as taken; a client-runnable command with such a name replaces the
	/// game's handling of it, except for the invocations its handler leaves
	/// [unhandled](CommandError::Unhandled).
	Clients,

	/// Server-side invokers and connected clients. As for
	/// [`Clients`](Self::Clients), clients' invocations never reach the game.
	Everyone,
}

impl CommandAccess {
	/// Whether connected clients may run the command, all of them unless a
	/// [`ClientFilter`] narrows them.
	pub const fn allows_clients(self) -> bool {
		matches!(self, Self::Clients | Self::Everyone)
	}

	/// Whether server-side invokers may run the command.
	pub const fn allows_server(self) -> bool {
		matches!(self, Self::Server | Self::Everyone)
	}
}

/// One invocation of a command, passed to its handler.
pub struct CommandContext<'d> {
	server: Server<'d>,
	args: CommandArgs<'d>,
	invoker: Invoker<'d>,
	name: &'static CStr,
	_not_thread_safe: NotThreadSafe,
}

impl<'d> CommandContext<'d> {
	/// The arguments, copied from the engine before the handler runs, so
	/// commands run in the meantime cannot change them.
	pub const fn args(&self) -> CommandArgs<'d> {
		self.args
	}

	/// Where the invocation came from.
	pub const fn invoker(&self) -> Invoker<'d> {
		self.invoker
	}

	/// The name the command was registered under.
	///
	/// [`CommandArgs::name`] is the name as typed, whose case may differ.
	pub const fn name(&self) -> &'static CStr {
		self.name
	}

	/// Prints a line to whoever ran the command: the server console, which
	/// rcon also receives, or the client's console.
	///
	/// NUL characters, which C strings cannot hold, are dropped. Only a reply
	/// to a client can fail, if the engine does not export `IVEngineServer` at
	/// the version the bindings expect.
	pub fn reply(&self, message: impl Display) -> Result<(), InterfaceError> {
		let line = line_from(message);

		match self.invoker {
			Invoker::Server => self.server.console_print(&line),

			Invoker::Client(client) => self
				.server
				.valve_engine()?
				.client_print(client.edict, &line),
		}

		Ok(())
	}

	/// The server, scoped to this invocation.
	pub const fn server(&self) -> Server<'d> {
		self.server
	}
}

impl<'d> CommandContext<'d> {
	pub(crate) const fn new(
		server: Server<'d>,
		args: CommandArgs<'d>,
		invoker: Invoker<'d>,
		name: &'static CStr,
	) -> Self {
		Self {
			server,
			args,
			invoker,
			name,
			_not_thread_safe: PhantomData,
		}
	}
}

impl std::fmt::Debug for CommandContext<'_> {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		f.debug_struct("CommandContext")
			.field("name", &self.name)
			.field("args", &self.args)
			.field("invoker", &self.invoker)
			.finish_non_exhaustive()
	}
}

/// Why an invocation failed. The invoker is shown the command's name and this
/// error's message.
#[derive(Debug, thiserror::Error)]
pub enum CommandError {
	/// The arguments did not match the command's usage, shown as given.
	#[error("usage: {0}")]
	Usage(Cow<'static, str>),

	/// The invoker may not do what it asked.
	#[error("{0}")]
	Denied(Cow<'static, str>),

	/// An argument is missing or cannot be read as requested, as
	/// [`CommandArgs::get_str`] and [`CommandArgs::parse`] report.
	#[error(transparent)]
	Argument(#[from] ArgError),

	/// A module, the engine or the game server, does not export an interface
	/// the handler needs at the version the bindings expect.
	#[error(transparent)]
	Interface(#[from] InterfaceError),

	/// Any other error, shown by its message.
	#[error(transparent)]
	Other(Box<dyn std::error::Error>),

	/// The handler leaves the invocation to whoever would have handled it had
	/// the command not been registered, and nothing is printed.
	///
	/// A client's invocation goes on to the game, so a client-runnable command
	/// named after one the game handles itself, such as TF2's `jointeam`, can
	/// take over only some invocations. A server-side invocation has nothing
	/// to go on to, so it just ends.
	#[error("the command was left unhandled")]
	Unhandled,
}

impl CommandError {
	/// A [`Denied`](Self::Denied) error, which shows `reason`.
	pub fn denied(reason: impl Into<Cow<'static, str>>) -> Self {
		Self::Denied(reason.into())
	}

	/// Boxes any error as [`Other`](Self::Other), for use with
	/// [`Result::map_err`].
	pub fn other(error: impl std::error::Error + 'static) -> Self {
		Self::Other(Box::new(error))
	}

	/// A [`Usage`](Self::Usage) error, which shows `usage` after `usage: `.
	pub fn usage(usage: impl Into<Cow<'static, str>>) -> Self {
		Self::Usage(usage.into())
	}
}

bitflags::bitflags! {
	/// The `FCVAR_*` flags a command or variable may carry, from
	/// `public/tier1/iconvar.h`.
	///
	/// Flags read from the engine, such as [`ConVar::flags`], keep every bit,
	/// including those without a constant here. Every bit counts as a known
	/// flag, so [`all`](Self::all) sets all 32, and `!` and
	/// [`from_bits_truncate`](Self::from_bits_truncate) keep unnamed bits too.
	///
	/// A [`ConsoleCommand`] is never registered with
	/// [`GAME_DLL`](Self::GAME_DLL), even if its flags contain it: the engine
	/// would then dispatch clients' invocations without saying who ran them
	/// (see the [module documentation](self)). A [`ConsoleVariable`] keeps it,
	/// since the engine only dispatches commands that way.
	///
	/// [`ConVar::flags`]: crate::interfaces::cvar::ConVar::flags
	#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
	pub struct CommandFlags: c_int {
		/// `FCVAR_ARCHIVE`: a variable the engine saves when it writes its
		/// configuration.
		#[doc(alias("FCVAR_ARCHIVE"))]
		const ARCHIVE = raw::FCVAR_ARCHIVE;

		/// `FCVAR_CHEAT`: runnable only while `sv_cheats` is set. The engine
		/// checks this for server-side invokers, and [`route_client_command`]
		/// for clients.
		#[doc(alias("FCVAR_CHEAT"))]
		const CHEAT = raw::FCVAR_CHEAT;

		/// `FCVAR_CLIENTCMD_CAN_EXECUTE`: a client command or variable that the
		/// client library's `IVEngineClient::ClientCmd` may run, which it
		/// refuses otherwise. `ClientCmd_Unrestricted` runs either.
		#[doc(alias("FCVAR_CLIENTCMD_CAN_EXECUTE"))]
		const CLIENT_CMD_CAN_EXECUTE = raw::FCVAR_CLIENTCMD_CAN_EXECUTE;

		/// `FCVAR_CLIENTDLL`: declared by the client library, which shares a
		/// listen server's registry.
		#[doc(alias("FCVAR_CLIENTDLL"))]
		const CLIENT_DLL = raw::FCVAR_CLIENTDLL;

		/// `FCVAR_DEMO`: a variable recorded when a demo recording starts.
		#[doc(alias("FCVAR_DEMO"))]
		const DEMO = raw::FCVAR_DEMO;

		/// `FCVAR_DEVELOPMENTONLY`: hidden from the console in released builds
		/// of the engine.
		#[doc(alias("FCVAR_DEVELOPMENTONLY"))]
		const DEVELOPMENT_ONLY = raw::FCVAR_DEVELOPMENTONLY;

		/// `FCVAR_DONTRECORD`: left out of demo recordings.
		#[doc(alias("FCVAR_DONTRECORD"))]
		const DONT_RECORD = raw::FCVAR_DONTRECORD;

		/// `FCVAR_GAMEDLL`: declared by the game server library.
		///
		/// A [`ConsoleCommand`] registered from Rust never carries or reports
		/// it, while a [`ConsoleVariable`] keeps it.
		#[doc(alias("FCVAR_GAMEDLL"))]
		const GAME_DLL = raw::FCVAR_GAMEDLL;

		/// `FCVAR_HIDDEN`: left out of `find`, `cvarlist`, and completion.
		#[doc(alias("FCVAR_HIDDEN"))]
		const HIDDEN = raw::FCVAR_HIDDEN;

		/// `FCVAR_NEVER_AS_STRING`: a variable whose string is never updated
		/// from its value, so it keeps the one it was declared with.
		#[doc(alias("FCVAR_NEVER_AS_STRING"))]
		const NEVER_AS_STRING = raw::FCVAR_NEVER_AS_STRING;

		/// `FCVAR_NONE`: no flags, the default.
		#[doc(alias("FCVAR_NONE"))]
		const NONE = raw::FCVAR_NONE;

		/// `FCVAR_NOT_CONNECTED`: a client variable that clients cannot change
		/// while connected to a server, unless the game's
		/// `CGameRules::IsConnectedUserInfoChangeAllowed` allows it.
		#[doc(alias("FCVAR_NOT_CONNECTED"))]
		const NOT_CONNECTED = raw::FCVAR_NOT_CONNECTED;

		/// `FCVAR_NOTIFY`: changes to a variable are announced to players and
		/// written to the server log.
		#[doc(alias("FCVAR_NOTIFY"))]
		const NOTIFY = raw::FCVAR_NOTIFY;

		/// `FCVAR_PRINTABLEONLY`: a variable whose string may only contain
		/// printable characters, such as a player's name. The variable's own
		/// `SetValue` does not check this, so [`ConVar::set_string`] does not
		/// either.
		///
		/// [`ConVar::set_string`]: crate::interfaces::cvar::ConVar::set_string
		#[doc(alias("FCVAR_PRINTABLEONLY"))]
		const PRINTABLE_ONLY = raw::FCVAR_PRINTABLEONLY;

		/// `FCVAR_PROTECTED`: a server variable whose value is withheld, such
		/// as a password. Queries of the server's rules learn only whether it
		/// is set.
		#[doc(alias("FCVAR_PROTECTED"))]
		const PROTECTED = raw::FCVAR_PROTECTED;

		/// `FCVAR_REPLICATED`: a variable whose server value is sent to every
		/// client, whose own copy follows it.
		#[doc(alias("FCVAR_REPLICATED"))]
		const REPLICATED = raw::FCVAR_REPLICATED;

		/// `FCVAR_SERVER_CAN_EXECUTE`: a client command clients run when the
		/// server sends it to them, such as through
		/// `IVEngineServer::ClientCommand`.
		#[doc(alias("FCVAR_SERVER_CAN_EXECUTE"))]
		const SERVER_CAN_EXECUTE = raw::FCVAR_SERVER_CAN_EXECUTE;

		/// `FCVAR_SERVER_CANNOT_QUERY`: a client variable whose value clients
		/// refuse to report to the server, as
		/// [`PluginHelpers::start_query_cvar_value`] asks them to.
		///
		/// [`PluginHelpers::start_query_cvar_value`]: crate::interfaces::PluginHelpers::start_query_cvar_value
		#[doc(alias("FCVAR_SERVER_CANNOT_QUERY"))]
		const SERVER_CANNOT_QUERY = raw::FCVAR_SERVER_CANNOT_QUERY;

		/// `FCVAR_SPONLY`: meant for single-player games; clients connected to
		/// a multiplayer server cannot change a variable marked with it.
		#[doc(alias("FCVAR_SPONLY"))]
		const SP_ONLY = raw::FCVAR_SPONLY;

		/// `FCVAR_UNLOGGED`: changes to a variable are not written to the
		/// server log, even if it is marked [`NOTIFY`](Self::NOTIFY).
		#[doc(alias("FCVAR_UNLOGGED"))]
		const UNLOGGED = raw::FCVAR_UNLOGGED;

		/// `FCVAR_USERINFO`: a client variable whose value clients send to the
		/// server, which [`ValveEngine::client_convar_value`] reads.
		///
		/// [`ValveEngine::client_convar_value`]: crate::interfaces::ValveEngine::client_convar_value
		#[doc(alias("FCVAR_USERINFO"))]
		const USERINFO = raw::FCVAR_USERINFO;

		// Bits without a constant here, which the engine may set.
		const _ = !0;
	}
}

/// Runs a console command.
///
/// Commands can run inside one another, for example when a handler makes a
/// client run a command, so state behind `&self` belongs in a `Cell` or
/// `RefCell` whose borrows are not held across calls into the engine.
///
/// A panic is caught, logged to the server console, and reported to the
/// invoker as a failure. It is never an unload, so a client cannot unload the
/// plugin by making a handler panic.
pub trait CommandHandler: 'static {
	/// Runs one invocation. An error is shown to the invoker, except
	/// [`CommandError::Unhandled`], which passes the invocation on.
	fn dispatch(&self, command: &CommandContext<'_>) -> CommandResult;
}

impl<F> CommandHandler for F
where
	F: Fn(&CommandContext<'_>) -> CommandResult + 'static,
{
	fn dispatch(&self, command: &CommandContext<'_>) -> CommandResult {
		self(command)
	}
}

/// Where an invocation came from.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Invoker<'d> {
	/// Not a client's string command: the console, rcon, a config file,
	/// `ServerCommand`, a map's `point_servercommand`, or server-side code
	/// dispatching the command directly.
	Server,

	/// A string command from a connected client, including one server code
	/// made the client run.
	Client(Client<'d>),
}

/// Formats a message as a terminated line for the engine's print functions.
fn line_from(message: impl Display) -> CString {
	let mut bytes = message.to_string().into_bytes();

	bytes.retain(|&byte| byte != 0);
	bytes.push(b'\n');

	// SAFETY: Every NUL was removed.
	unsafe { CString::from_vec_unchecked(bytes) }
}