feat: add persistent-world station game
Bevy ECS workspace with three crates:
- game_core: rsn data definitions, components, Tiled map loader, protocol
- game_server: authoritative simulation, Rune scripting, persistence,
dedicated server binary
- game_client: rendering, UI and input; embeds the server for
singleplayer and hosting
Items, recipes, buildings and skills are defined in assets/data, behavior
in Rune scripts under assets/scripts, and maps are made in Tiled.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 17:44:13 +00:00
|
|
|
//! Game client.
|
|
|
|
|
//!
|
|
|
|
|
//! ```text
|
|
|
|
|
//! game singleplayer (persistent local world)
|
|
|
|
|
//! game --host [PORT] singleplayer world that friends can join
|
|
|
|
|
//! game --connect ADDR[:PORT] join a dedicated server or host
|
|
|
|
|
//! ```
|
2026-10-18 18:47:43 +00:00
|
|
|
//! Common options: `--name NAME`, `--save NAME`, `--map MAP`.
|
feat: add persistent-world station game
Bevy ECS workspace with three crates:
- game_core: rsn data definitions, components, Tiled map loader, protocol
- game_server: authoritative simulation, Rune scripting, persistence,
dedicated server binary
- game_client: rendering, UI and input; embeds the server for
singleplayer and hosting
Items, recipes, buildings and skills are defined in assets/data, behavior
in Rune scripts under assets/scripts, and maps are made in Tiled.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 17:44:13 +00:00
|
|
|
|
|
|
|
|
// Release builds on Windows run without a console window.
|
|
|
|
|
#![cfg_attr(all(windows, not(debug_assertions)), windows_subsystem = "windows")]
|
|
|
|
|
|
|
|
|
|
mod input;
|
|
|
|
|
mod net;
|
|
|
|
|
mod render;
|
2026-10-18 18:47:43 +00:00
|
|
|
mod saves;
|
feat: add persistent-world station game
Bevy ECS workspace with three crates:
- game_core: rsn data definitions, components, Tiled map loader, protocol
- game_server: authoritative simulation, Rune scripting, persistence,
dedicated server binary
- game_client: rendering, UI and input; embeds the server for
singleplayer and hosting
Items, recipes, buildings and skills are defined in assets/data, behavior
in Rune scripts under assets/scripts, and maps are made in Tiled.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 17:44:13 +00:00
|
|
|
mod ui;
|
|
|
|
|
|
2026-10-18 18:47:43 +00:00
|
|
|
use std::net::SocketAddr;
|
feat: add persistent-world station game
Bevy ECS workspace with three crates:
- game_core: rsn data definitions, components, Tiled map loader, protocol
- game_server: authoritative simulation, Rune scripting, persistence,
dedicated server binary
- game_client: rendering, UI and input; embeds the server for
singleplayer and hosting
Items, recipes, buildings and skills are defined in assets/data, behavior
in Rune scripts under assets/scripts, and maps are made in Tiled.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 17:44:13 +00:00
|
|
|
|
|
|
|
|
use bevy::{prelude::*, window::WindowResolution};
|
|
|
|
|
use bevy_replicon::prelude::*;
|
|
|
|
|
use bevy_replicon_renet::RepliconRenetPlugins;
|
|
|
|
|
use game_core::CorePlugin;
|
|
|
|
|
use game_server::{GameServerPlugin, NetworkServerPlugin, ServerSettings};
|
|
|
|
|
|
|
|
|
|
/// How this client runs.
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
|
pub enum Mode {
|
|
|
|
|
/// Server logic runs in-process without networking.
|
|
|
|
|
Singleplayer,
|
|
|
|
|
/// Server logic runs in-process and accepts remote players.
|
|
|
|
|
Host { port: u16 },
|
|
|
|
|
/// Pure client connected to a remote server.
|
|
|
|
|
Join { addr: SocketAddr },
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The local player's login name, used to find our character among replicated ones.
|
|
|
|
|
#[derive(Resource, Clone, Debug)]
|
|
|
|
|
pub struct LocalPlayer {
|
|
|
|
|
pub name: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Resource, Clone, Debug)]
|
|
|
|
|
pub struct ClientMode(pub Mode);
|
|
|
|
|
|
|
|
|
|
fn main() -> AppExit {
|
|
|
|
|
let (mode, name, settings) = parse_args();
|
|
|
|
|
let mut app = App::new();
|
|
|
|
|
let assets = game_core::assets_dir();
|
|
|
|
|
app.add_plugins((
|
|
|
|
|
DefaultPlugins
|
|
|
|
|
.set(AssetPlugin {
|
|
|
|
|
file_path: assets.to_string_lossy().into_owned(),
|
|
|
|
|
..default()
|
|
|
|
|
})
|
|
|
|
|
.set(ImagePlugin::default_nearest())
|
|
|
|
|
.set(WindowPlugin {
|
|
|
|
|
primary_window: Some(Window {
|
|
|
|
|
title: format!("Station - {name}"),
|
|
|
|
|
resolution: WindowResolution::new(1280, 800),
|
|
|
|
|
..default()
|
|
|
|
|
}),
|
|
|
|
|
..default()
|
|
|
|
|
}),
|
|
|
|
|
RepliconPlugins,
|
|
|
|
|
RepliconRenetPlugins,
|
|
|
|
|
CorePlugin,
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
match &mode {
|
|
|
|
|
Mode::Singleplayer => {
|
|
|
|
|
app.add_plugins(GameServerPlugin { settings });
|
|
|
|
|
}
|
|
|
|
|
Mode::Host { port } => {
|
|
|
|
|
app.add_plugins((
|
|
|
|
|
GameServerPlugin { settings },
|
|
|
|
|
NetworkServerPlugin { port: *port },
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
Mode::Join { .. } => {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
app.insert_resource(LocalPlayer { name })
|
|
|
|
|
.insert_resource(ClientMode(mode))
|
|
|
|
|
.insert_resource(ClearColor(Color::srgb(0.02, 0.02, 0.05)))
|
|
|
|
|
.add_plugins((
|
|
|
|
|
net::NetPlugin,
|
|
|
|
|
render::RenderPlugin,
|
|
|
|
|
input::InputPlugin,
|
|
|
|
|
ui::UiPlugin,
|
|
|
|
|
))
|
|
|
|
|
.run()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_args() -> (Mode, String, ServerSettings) {
|
|
|
|
|
let mut mode = Mode::Singleplayer;
|
|
|
|
|
let mut name = std::env::var("USER")
|
|
|
|
|
.ok()
|
|
|
|
|
.filter(|n| game_server::players::valid_name(n))
|
|
|
|
|
.unwrap_or_else(|| "Player".to_owned());
|
|
|
|
|
let mut settings = ServerSettings {
|
2026-10-18 18:47:43 +00:00
|
|
|
storage: Some(saves::storage("singleplayer")),
|
feat: add persistent-world station game
Bevy ECS workspace with three crates:
- game_core: rsn data definitions, components, Tiled map loader, protocol
- game_server: authoritative simulation, Rune scripting, persistence,
dedicated server binary
- game_client: rendering, UI and input; embeds the server for
singleplayer and hosting
Items, recipes, buildings and skills are defined in assets/data, behavior
in Rune scripts under assets/scripts, and maps are made in Tiled.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 17:44:13 +00:00
|
|
|
..default()
|
|
|
|
|
};
|
|
|
|
|
let mut args = std::env::args().skip(1).peekable();
|
|
|
|
|
while let Some(arg) = args.next() {
|
|
|
|
|
let mut value = |what: &str| {
|
|
|
|
|
args.next()
|
|
|
|
|
.unwrap_or_else(|| usage(&format!("{arg} needs {what}")))
|
|
|
|
|
};
|
|
|
|
|
match arg.as_str() {
|
|
|
|
|
"--host" => {
|
|
|
|
|
let port = match args.peek() {
|
|
|
|
|
Some(p) if !p.starts_with("--") => args
|
|
|
|
|
.next()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.parse()
|
|
|
|
|
.unwrap_or_else(|_| usage("invalid port")),
|
|
|
|
|
_ => game_core::DEFAULT_PORT,
|
|
|
|
|
};
|
|
|
|
|
mode = Mode::Host { port };
|
|
|
|
|
}
|
|
|
|
|
"--connect" => {
|
|
|
|
|
let addr = value("an address");
|
|
|
|
|
let addr = if addr.contains(':') {
|
|
|
|
|
addr
|
|
|
|
|
} else {
|
|
|
|
|
format!("{addr}:{}", game_core::DEFAULT_PORT)
|
|
|
|
|
};
|
|
|
|
|
let addr = std::net::ToSocketAddrs::to_socket_addrs(&addr)
|
|
|
|
|
.ok()
|
|
|
|
|
.and_then(|mut a| a.next())
|
|
|
|
|
.unwrap_or_else(|| usage(&format!("can't resolve `{addr}`")));
|
|
|
|
|
mode = Mode::Join { addr };
|
|
|
|
|
}
|
|
|
|
|
"--name" => name = value("a name"),
|
2026-10-18 18:47:43 +00:00
|
|
|
"--save" => settings.storage = Some(saves::storage(&value("a save name"))),
|
|
|
|
|
"--no-save" => settings.storage = None,
|
feat: add persistent-world station game
Bevy ECS workspace with three crates:
- game_core: rsn data definitions, components, Tiled map loader, protocol
- game_server: authoritative simulation, Rune scripting, persistence,
dedicated server binary
- game_client: rendering, UI and input; embeds the server for
singleplayer and hosting
Items, recipes, buildings and skills are defined in assets/data, behavior
in Rune scripts under assets/scripts, and maps are made in Tiled.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 17:44:13 +00:00
|
|
|
"--map" => settings.map = value("a map path"),
|
|
|
|
|
"-h" | "--help" => usage(""),
|
|
|
|
|
other => usage(&format!("unknown argument `{other}`")),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !game_server::players::valid_name(&name) {
|
|
|
|
|
usage("names may only contain letters, digits, spaces, `_` and `-` (max 24)");
|
|
|
|
|
}
|
|
|
|
|
(mode, name, settings)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn usage(error: &str) -> ! {
|
|
|
|
|
if !error.is_empty() {
|
|
|
|
|
eprintln!("error: {error}\n");
|
|
|
|
|
}
|
|
|
|
|
eprintln!(
|
2026-10-18 18:47:43 +00:00
|
|
|
"usage: game [--host [PORT] | --connect ADDR[:PORT]] [--name NAME] [--save NAME | --no-save] [--map MAP]\n\n\
|
|
|
|
|
(no mode) singleplayer, world saved as `singleplayer`\n\
|
feat: add persistent-world station game
Bevy ECS workspace with three crates:
- game_core: rsn data definitions, components, Tiled map loader, protocol
- game_server: authoritative simulation, Rune scripting, persistence,
dedicated server binary
- game_client: rendering, UI and input; embeds the server for
singleplayer and hosting
Items, recipes, buildings and skills are defined in assets/data, behavior
in Rune scripts under assets/scripts, and maps are made in Tiled.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 17:44:13 +00:00
|
|
|
--host play your world and let others join (default port {port})\n\
|
|
|
|
|
--connect join a server (default port {port})\n\
|
|
|
|
|
--name character name; characters persist per world\n\
|
2026-10-18 18:47:43 +00:00
|
|
|
--save save game for singleplayer/host, stored in {saves}\n\
|
feat: add persistent-world station game
Bevy ECS workspace with three crates:
- game_core: rsn data definitions, components, Tiled map loader, protocol
- game_server: authoritative simulation, Rune scripting, persistence,
dedicated server binary
- game_client: rendering, UI and input; embeds the server for
singleplayer and hosting
Items, recipes, buildings and skills are defined in assets/data, behavior
in Rune scripts under assets/scripts, and maps are made in Tiled.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 17:44:13 +00:00
|
|
|
--map Tiled map for new worlds (default {map})",
|
|
|
|
|
port = game_core::DEFAULT_PORT,
|
|
|
|
|
map = game_core::DEFAULT_MAP,
|
2026-10-18 18:47:43 +00:00
|
|
|
saves = saves::saves_dir().display(),
|
feat: add persistent-world station game
Bevy ECS workspace with three crates:
- game_core: rsn data definitions, components, Tiled map loader, protocol
- game_server: authoritative simulation, Rune scripting, persistence,
dedicated server binary
- game_client: rendering, UI and input; embeds the server for
singleplayer and hosting
Items, recipes, buildings and skills are defined in assets/data, behavior
in Rune scripts under assets/scripts, and maps are made in Tiled.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 17:44:13 +00:00
|
|
|
);
|
|
|
|
|
std::process::exit(if error.is_empty() { 0 } else { 2 })
|
|
|
|
|
}
|