Skip to content
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@
tree-sitter-rust
*-E

# default world locations
backups/
worlds/

# local config files
server.toml
31 changes: 31 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ serde-big-array = "0.5.1"
chrono = "0.4.43"
rayon = "1.10.0"
clap = { version = "4.5.54", features = ["derive"] }
toml = "0.9.11"

[patch.crates-io]
# TODO: Remove patch once egui requirement is more flexible.
Expand Down
47 changes: 47 additions & 0 deletions src/server/config/commands.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use crate::prelude::*;
use std::path::Path;

#[derive(Debug, Subcommand)]
pub enum ConfigCommands {
#[command(about = "Initialize config file with default settings")]
Init,
#[command(about = "Show currently applied configuration")]
Show,
#[command(about = "Show defaults")]
Defaults,
}

pub fn perform_command(commands: &ConfigCommands) {
match commands {
ConfigCommands::Init => {
if Path::new(&CONFIG_PATH).is_file() {
eprintln!("Config file is already initialized at '{CONFIG_PATH}'.");
eprintln!("If you want to reinitialize it with defaults, remove it:");
eprintln!("rm '{CONFIG_PATH}'");
} else {
let config = Config::default();
let config_str =
toml::to_string(&config).expect("Default config should be serializable");

if let Err(err) = std::fs::write(CONFIG_PATH, config_str) {
eprintln!("Error writing to file: {err}");
} else {
println!("Initialized config file '{CONFIG_PATH}'");
}
}
}
ConfigCommands::Show => {
println!(
"{}",
toml::to_string(&*CONFIG).expect("Loaded config should always be serializable")
);
}
ConfigCommands::Defaults => {
println!(
"{}",
toml::to_string(&Config::default())
.expect("Default config should always be serializable")
);
}
}
}
49 changes: 49 additions & 0 deletions src/server/config/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use std::sync::LazyLock;

pub mod commands;

pub const CONFIG_PATH: &str = "server.toml";

pub static CONFIG: LazyLock<Config> = LazyLock::new(|| {
#[cfg(test)]
{
Config::default()
}

#[cfg(not(test))]
{
use std::path::PathBuf;

match std::fs::read_to_string(PathBuf::from(CONFIG_PATH)) {
Ok(string) => match toml::from_str(&string) {
Ok(config) => config,
Err(err) => {
use std::process;

eprintln!("Could not parse config file at '{CONFIG_PATH}'");
eprintln!("Err: {err}");
process::exit(1);
}
},
Err(err) => {
eprintln!(
"Could not read config file at '{CONFIG_PATH}', proceeding with defaults"
);
eprintln!("Err: {err}");
Config::default()
}
}
}
});

use serde::Deserialize;
use serde::Serialize;

use crate::prelude::*;

#[derive(Serialize, Deserialize, Default)]
#[serde(default, deny_unknown_fields)]
pub struct Config {
pub world: terrain_config::WorldConfig,
pub generator: terrain_config::TerrainGeneratorParams,
}
27 changes: 20 additions & 7 deletions src/server/main.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
pub mod chat;
pub mod config;
pub mod networking;
pub mod player;
pub mod prelude;
pub mod terrain;

use bevy::app::TerminalCtrlCHandlerPlugin;
use clap::Parser;

#[cfg(feature = "egui_layer")]
use bevy::DefaultPlugins;
Expand All @@ -22,7 +22,15 @@ use crate::prelude::*;
#[command(long_about = None)]
struct Cli {
#[command(subcommand)]
world_commands: terrain_commands::WorldCommands,
commands: MainCommands,
}

#[derive(Debug, Subcommand)]
enum MainCommands {
#[command(flatten)]
World(terrain_commands::WorldCommands),
#[command(subcommand, about = "Actions regarding server configuration")]
Config(config_commands::ConfigCommands),
}

fn main() {
Expand All @@ -44,13 +52,18 @@ fn main() {
}

let args = Cli::parse();
match terrain::TerrainPlugin::from_command(args.world_commands) {
Ok(terrain_plugin) => app.add_plugins(terrain_plugin),
Err(error) => {
eprintln!("Error: {}", error);
match args.commands {
MainCommands::World(world_commands) => {
match terrain::TerrainPlugin::from_command(world_commands) {
Ok(terrain_plugin) => app.add_plugins(terrain_plugin),
Err(error) => return eprintln!("Error: {}", error),
};
}
MainCommands::Config(config_commands) => {
config_commands::perform_command(&config_commands);
return;
}
};
}

app.add_plugins(player::PlayerPlugin);
app.add_plugins(networking::NetworkingPlugin);
Expand Down
5 changes: 5 additions & 0 deletions src/server/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub use rayon::iter::IntoParallelIterator;
pub use rayon::iter::IntoParallelRefMutIterator;
pub use rayon::iter::ParallelIterator;

pub use clap::{Parser, Subcommand};
pub use lib::*;
pub use noise::NoiseFn;
pub use noise::Perlin;
Expand All @@ -42,6 +43,7 @@ pub use crate::player::resources as player_resources;
pub use crate::player::systems as player_systems;

pub use crate::terrain::commands as terrain_commands;
pub use crate::terrain::config as terrain_config;
pub use crate::terrain::events as terrain_events;
pub use crate::terrain::resources as terrain_resources;
pub use crate::terrain::systems as terrain_systems;
Expand All @@ -50,3 +52,6 @@ pub use crate::terrain::util as terrain_util;
pub use crate::chat::events as chat_events;
pub use crate::chat::resources as chat_resources;
pub use crate::chat::systems as chat_systems;

pub use crate::config::commands as config_commands;
pub use crate::config::{Config, CONFIG, CONFIG_PATH};
5 changes: 2 additions & 3 deletions src/server/terrain/commands.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use clap::Subcommand;
use rand::RngCore;

use crate::prelude::*;
use crate::terrain::TerrainPlugin;
use rand::RngCore;

#[derive(Debug, Subcommand)]
pub enum WorldCommands {
Expand Down
Loading