Skip to content

Commit

Permalink
Add basic exec & echo commands to the client
Browse files Browse the repository at this point in the history
  • Loading branch information
Jupeyy committed Jan 9, 2025
1 parent 89fb1e4 commit d5bf5f4
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 58 deletions.
69 changes: 60 additions & 9 deletions game/client-console/src/console/local_console.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::{
collections::HashMap,
net::{SocketAddr, ToSocketAddrs},
ops::Range,
path::PathBuf,
rc::Rc,
};

Expand Down Expand Up @@ -46,6 +47,12 @@ pub enum LocalConsoleEvent {
// The bind was added to the player's profile
was_player_profile: bool,
},
Exec {
file_path: PathBuf,
},
Echo {
text: String,
},
/// Switch to an dummy or the main player
ChangeDummy {
dummy_index: Option<usize>,
Expand Down Expand Up @@ -84,13 +91,17 @@ impl super::console::ConsoleEvents<LocalConsoleEvent> for LocalConsoleEvents {
}
}

pub type LocalConsole = ConsoleRender<LocalConsoleEvent, ()>;
pub type LocalConsole = ConsoleRender<LocalConsoleEvent, Rc<RefCell<ParserCache>>>;

#[derive(Debug, Default)]
pub struct LocalConsoleBuilder {}

impl LocalConsoleBuilder {
fn register_commands(console_events: LocalConsoleEvents, list: &mut Vec<ConsoleEntry>) {
fn register_commands(
console_events: LocalConsoleEvents,
list: &mut Vec<ConsoleEntry>,
parser_cache: Rc<RefCell<ParserCache>>,
) {
list.push(ConsoleEntry::Cmd(ConsoleEntryCmd {
name: "push".into(),
usage: "push <var>".into(),
Expand Down Expand Up @@ -517,8 +528,7 @@ impl LocalConsoleBuilder {
}
// bind for player
let events = console_events.clone();
let cache_shared = Rc::new(RefCell::new(ParserCache::default()));
let cache = cache_shared.clone();
let cache = parser_cache.clone();
let keys_arg_cmd = keys_arg.clone();
list.push(ConsoleEntry::Cmd(ConsoleEntryCmd {
name: "bind".into(),
Expand Down Expand Up @@ -550,7 +560,7 @@ impl LocalConsoleBuilder {
let actions_map = gen_local_player_action_hash_map();
let actions_map_rev = gen_local_player_action_hash_map_rev();
let events = console_events.clone();
let cache = cache_shared.clone();
let cache = parser_cache.clone();
let keys_arg_cmd = keys_arg.clone();
list.push(ConsoleEntry::Cmd(ConsoleEntryCmd {
name: "bind_dummy".into(),
Expand Down Expand Up @@ -582,7 +592,7 @@ impl LocalConsoleBuilder {

let keys_arg_cmd = keys_arg.clone();
// unbind for player
let cache = cache_shared.clone();
let cache = parser_cache.clone();
let events = console_events.clone();
list.push(ConsoleEntry::Cmd(ConsoleEntryCmd {
name: "unbind".into(),
Expand All @@ -604,7 +614,7 @@ impl LocalConsoleBuilder {
allows_partial_cmds: false,
}));
let keys_arg_cmd = keys_arg.clone();
let cache = cache_shared.clone();
let cache = parser_cache.clone();
let events = console_events.clone();
list.push(ConsoleEntry::Cmd(ConsoleEntryCmd {
name: "unbind_dummy".into(),
Expand All @@ -627,6 +637,46 @@ impl LocalConsoleBuilder {
allows_partial_cmds: false,
}));

let console_events_cmd = console_events.clone();
list.push(ConsoleEntry::Cmd(ConsoleEntryCmd {
name: "exec".into(),
usage: "exec <file_path>".into(),
description: "Executes a file of command lines.".into(),
cmd: Rc::new(move |_, _, path| {
let Syn::Text(file_path_str) = &path[0].0 else {
panic!("Command parser returned a non requested command arg");
};
let file_path: PathBuf = file_path_str.into();
console_events_cmd.push(LocalConsoleEvent::Exec { file_path });
Ok("".into())
}),
args: vec![CommandArg {
ty: CommandArgType::Text,
user_ty: None,
}],
allows_partial_cmds: false,
}));

let console_events_cmd = console_events.clone();
list.push(ConsoleEntry::Cmd(ConsoleEntryCmd {
name: "echo".into(),
usage: "echo <text>".into(),
description: "Echos text to the console and a client component.".into(),
cmd: Rc::new(move |_, _, path| {
let Syn::Text(text) = &path[0].0 else {
panic!("Command parser returned a non requested command arg");
};

console_events_cmd.push(LocalConsoleEvent::Echo { text: text.clone() });
Ok(format!("Echo: {text}"))
}),
args: vec![CommandArg {
ty: CommandArgType::Text,
user_ty: None,
}],
allows_partial_cmds: false,
}));

let console_events_cmd = console_events.clone();
list.push(ConsoleEntry::Cmd(ConsoleEntryCmd {
name: "connect".into(),
Expand Down Expand Up @@ -754,13 +804,14 @@ impl LocalConsoleBuilder {
"".into(),
Default::default(),
);
Self::register_commands(console_events.clone(), &mut entries);
let parser_cache = Rc::new(RefCell::new(ParserCache::default()));
Self::register_commands(console_events.clone(), &mut entries, parser_cache.clone());
ConsoleRender::new(
creator,
entries,
Box::new(console_events),
Color32::from_rgba_unmultiplied(0, 0, 0, 150),
(),
parser_cache,
)
}
}
51 changes: 25 additions & 26 deletions game/client-ui/src/console/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,14 +498,14 @@ pub fn try_apply_config_val(
})
}

/// Returns `false` if the command was considered partially or fully failed.
pub fn run_command(
cmd: CommandTypeRef<'_>,
entries: &[ConsoleEntry],
config_engine: &mut ConfigEngine,
config_game: &mut ConfigGame,
msgs: &mut String,
can_change_config: bool,
) {
) -> anyhow::Result<String, String> {
if let Some(entry_cmd) = entries
.iter()
.filter_map(|e| match e {
Expand All @@ -528,14 +528,8 @@ pub fn run_command(
{
let cmd = cmd.unwrap_full_or_partial_cmd_ref();
match (entry_cmd.cmd)(config_engine, config_game, &cmd.args) {
Ok(msg) => {
if !msg.is_empty() {
msgs.push_str(&format!("{msg}\n"));
}
}
Err(err) => {
msgs.push_str(&format!("Parsing error: {}\n", err));
}
Ok(msg) => Ok(msg),
Err(err) => Err(format!("Parsing error: {}\n", err)),
}
} else {
let Some((args, cmd_text)) = (match cmd {
Expand All @@ -548,7 +542,7 @@ pub fn run_command(
}
}
}) else {
return;
return Err(format!("Invalid argument: {:?}", cmd));
};

let set_val = syn_vec_to_config_val(&args);
Expand All @@ -557,10 +551,6 @@ pub fn run_command(
match try_apply_config_val(cmd_text, &args, config_engine, config_game) {
Ok(cur_val) => {
if set_val.is_some() {
msgs.push_str(&format!(
"Updated value for \"{}\": {}\n",
cmd_text, cur_val
));
if let Some(var) = entries
.iter()
.filter_map(|cmd| {
Expand All @@ -574,37 +564,46 @@ pub fn run_command(
{
(var.on_set)(cmd_text);
}
Ok(format!("Updated value for \"{}\": {}\n", cmd_text, cur_val))
} else {
msgs.push_str(&format!(
"Current value for \"{}\": {}\n",
cmd_text, cur_val
));
Ok(format!("Current value for \"{}\": {}\n", cmd_text, cur_val))
}
}
Err(err) => {
msgs.push_str(&err);
}
Err(err) => Err(err),
}
} else {
Err(format!("Failed to apply command: {:?}", cmd))
}
}
}

/// Returns `false` if at least one command failed.
pub fn run_commands(
cmds: &CommandsTyped,
entries: &[ConsoleEntry],
config_engine: &mut ConfigEngine,
config_game: &mut ConfigGame,
msgs: &mut String,
can_change_config: bool,
) {
) -> bool {
let mut res = true;
for cmd in cmds {
run_command(
let msg = match run_command(
cmd.as_ref(),
entries,
config_engine,
config_game,
msgs,
can_change_config,
);
) {
Ok(msg) => msg,
Err(msg) => {
res = false;
msg
}
};
if !msg.is_empty() {
msgs.push_str(&format!("{msg}\n"));
}
}
res
}
2 changes: 1 addition & 1 deletion game/game-server/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ impl Server {
) -> ReponsesAndCmds {
let mut remaining_cmds = Vec::default();
let mut responses = Vec::default();
for line in lines {
for line in lines.into_iter().filter(|l| !l.is_empty()) {
let cmds =
command_parser::parser::parse(&line, &rcon_chain.parser, &mut Default::default());

Expand Down
76 changes: 74 additions & 2 deletions src/client/client.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use std::{
borrow::Borrow, cell::RefCell, net::SocketAddr, num::NonZeroUsize, rc::Rc, sync::Arc,
time::Duration,
borrow::Borrow, cell::RefCell, net::SocketAddr, num::NonZeroUsize, path::PathBuf, rc::Rc,
sync::Arc, time::Duration,
};

use anyhow::anyhow;
use base::{
benchmark::Benchmark,
linked_hash_map_view::FxLinkedHashMap,
Expand Down Expand Up @@ -40,12 +41,14 @@ use client_render_game::render_game::{
RenderGameCreateOptions, RenderGameForPlayer, RenderGameInput, RenderGameInterface,
RenderGameSettings, RenderModTy, RenderPlayerCameraMode,
};
use client_types::console::entries_to_parser;
use client_ui::{
chat::user_data::{ChatEvent, ChatMode},
connect::{
page::ConnectingUi,
user_data::{ConnectMode, ConnectModes},
},
console::utils::run_commands,
events::{UiEvent, UiEvents},
ingame_menu::{
account_info::AccountInfo,
Expand Down Expand Up @@ -1958,6 +1961,71 @@ impl ClientNativeImpl {
.unwrap();
}

fn handle_exec(&mut self, file_path: PathBuf) {
let fs = self.io.fs.clone();
let cmds_file = match self
.io
.rt
.spawn(async move {
fs.read_file(&file_path)
.await
.map_err(|err| {
anyhow!(
"failed to read config file: {file_path:?} in {:?}: {err}",
fs.get_save_path()
)
})
.and_then(|file| {
String::from_utf8(file).map_err(|err| {
anyhow!(
"failed to read config file: {file_path:?} in {:?}: {err}",
fs.get_save_path()
)
})
})
})
.get_storage()
{
Ok(cmds_file) => cmds_file,
Err(err) => {
self.notifications
.add_err(err.to_string(), Duration::from_secs(10));
return;
}
};

let mut cmds_succeeded = true;
let parser_entries = entries_to_parser(&self.local_console.entries);
for line in cmds_file.lines().filter(|l| !l.is_empty()) {
let cmds = command_parser::parser::parse(
line,
&parser_entries,
&mut self.local_console.user.borrow_mut(),
);
let mut res = String::default();
let cur_cmds_succeeded = run_commands(
&cmds,
&self.local_console.entries,
&mut self.config.engine,
&mut self.config.game,
&mut res,
true,
);
log::debug!("{}", res);
if !cur_cmds_succeeded {
self.console_logs.push_str(&res);
}
cmds_succeeded &= cur_cmds_succeeded;
}
if !cmds_succeeded {
self.notifications.add_err(
"At least one command failed to be executed, \
see local console for more info.",
Duration::from_secs(5),
);
}
}

fn handle_console_events(
&mut self,
native: &mut dyn NativeImpl,
Expand Down Expand Up @@ -2031,6 +2099,10 @@ impl ClientNativeImpl {
}
}
}
LocalConsoleEvent::Exec { file_path } => self.handle_exec(file_path),
LocalConsoleEvent::Echo { text } => {
self.notifications.add_info(text, Duration::from_secs(2));
}
LocalConsoleEvent::ChangeDummy { dummy_index } => {
if let Game::Active(game) = &mut self.game {
if let Some(dummy_index) = dummy_index {
Expand Down
Loading

0 comments on commit d5bf5f4

Please sign in to comment.