Expand description
cheats is a shell backend for games. Basically, it helps you invoke code with a provided string line.
The library is not yet production-ready. It has a very simple implementation of developer console and might lack some features you might desire.
§Shell Grammar
There are many conventions about how to cheat. Grand Theft Auto series receive sequential keypresses and invokes functionality. Age of Empires II has a simple textbox to invoke a cheat but its cheats do not have any arguments.
In this library, cheats, the developer console of Valve games such as Half-Life, Counter-Strike, Portal, Left 4 Dead, has been an inspiration and it is implemented in such a way.
// this is a comment
cl_hello // without arg
cl_hello Eray # with arg
§How to Use
First, you need to initialize a Shell instance.
use cheats::Shell;
let mut shell = Shell::new();
§Registering/Unregistering A Code
In order to register a cheat code, first, you need to define what it will do upon invoking. There is a Invokable trait that you can implement to do that.
use std::io::Write;
use cheats::code::Invokable;
struct ClHello; // An empty struct to implement.
impl Invokable for ClHello {
fn invoke(
&self,
args: &str, // args if given, can be an empty string
mut stdout: Box<&mut dyn Write>, // stdout to write
mut stderr: Box<&mut dyn Write>, // stderr to write
) {
match args.is_empty() { // is `args` empty?
true => { // if so...
// write to `stderr` to inform that args were empty
// you do not have to do that, it is given for demonstration purposes
stderr
.write(b"Args are empty.")
.expect("Could not write to stderr.");
// since no args were given, write "Hello, world!" to `stdout`
stdout
.write(b"Hello, world!")
.expect("Could not write to stdout.");
}
false => { // if not...
// build a message saying the name
let msg: String = format!("Hello, {}!", args);
// write `msg` to `stdout`
stdout
.write(msg.as_bytes()) // mind `as_bytes`
.expect("Could not write to stdout.");
}
}
}
}
Mind Invokable receives args
as plain &str
. Parsing of
arguments is not handled by this library. Also, no args will result in an empty &str
.
Now that you have an Invokable, you can register it:
// we need to box our Invokable struct
// that's because `register` method on `Shell` requires `Box<Invokable>`
let invokable = Box::new(ClHello);
shell.register("cl_hello", invokable).expect("Could not register the code.");
register
method returns ShellError in case:
- ShellError::CodeAlreadyExists: the code with same name is
already registered, in this case,
"cl_hello"
- ShellError::CodeError: the initialization of code fails due to having an invalid name
You can also unregister an existing code:
shell.unregister("cl_hello").expect("Could not unregister the code.");
unregister
method returns ShellError::CodeDoesNotExist if,
well, the code with given name is not registered before.
§Filtering Codes
Naturally, you’d like to filter code names as the user types to your shell.
Shell instance has a method named filter_names
to help
you filter codes. You need:
query
: A query to filter code names.starts_with
: Iftrue
, filters code names usingstarts_with
, else usescontains
.
// assuming you have `cl_hello`, `sv_foo`, `sv_foobar`
let sv_codes: Vec<&str> = shell.filter_names("sv", true).collect();
assert_eq!(sv_codes, ["sv_foo", "sv_foobar"]);
let foo_codes: Vec<&str> = shell.filter_names("foo", false).collect();
assert_eq!(foo_codes, ["sv_foo", "sv_foobar"]),
While, in this case, the Vec
of code names are ordered, it might not be in larger
examples. In this case, you can sort a Vec
by using sort
on it.
let sv_codes: Vec<&str> = shell.filter_names("sv", true).collect();
sv_codes.sort();
Note that filter_names
method actually returns an
Iterator, which, then,
you can collect
into a Vec<&str>
.
§Running Script
You can run a cheat code line by doing:
shell.run("cl_hello").expect("Could not run the code.");
shell.run("cl_hello Eray").expect("Could not run the code.");
Running a single line is cool but consider loading a script in runtime. You can
pass a file content to run
method. An example:
use std::fs;
// read the file
// it does not have to have .script extension, this is just an example
let content: String = fs::read_to_string("path/to/file.script")
.expect("Could not read the file.");
// convert from String to &str
let content_str: &content[..];
// run
shell.run(content_str).expect("Could not run the code.");
§Reading Output
Of course, a shell is nothing without output. Shell has two attributes:
- stdout: Standard output.
- stderr: Standard output for errors.
These attributes are actually a custom ReadWrite trait objects,
which means you can read
from or write
to them.
You usually would like to write to these channels while in Invokable
trait’s invoke
method because these are referenced in there so that you can use them.
You can read from stdout
or stderr
as below:
// you can do the same with `stderr`
let output: String = {
let ref mut stdout = shell.stdout; // take a reference to stdout
let mut stdout_bytes: Vec<u8> = vec![]; // create a vector buffer for bytes
stdout
.read_to_end(&mut stdout_bytes) // read until the end
.expect("Could not read stdout.");
String::from_iter(stdout_bytes.into_iter().map(|b| b as char)) // map u8 bytes to char
};
Modules§
Structs§
- Shell
- A shell for a game.