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
|
|
|
//! Login/logout and the mapping between network clients and characters.
|
|
|
|
|
|
feat!: start menu, file-based UI, machine windows and server access control
- Start menu with new game (optionally hosted), load/delete saves and join
server screens; CLI options skip it.
- Menus and HUD are built from rsn layouts in assets/ui with a shared theme,
bindings, conditional visibility and tooltips.
- Machine windows place slots and backgrounds from the machine's ui
definition; slots are filled from and emptied into the inventory.
- Chat with / commands, pause menu, clickable inventory, craft and build
menus.
- Server passwords, admins, whitelist, bans, kicks and admin commands from
the terminal or chat; changes are written to the config file. The hosting
player is always admin.
- Clients keep a player id in client.toml; characters belong to it.
BREAKING CHANGE: protocol version 3 adds the player id and password to
Login, and characters are keyed by player id instead of name, so characters
in existing worlds are not restored.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 20:29:05 +00:00
|
|
|
use std::collections::{BTreeMap, HashMap, VecDeque};
|
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::*;
|
|
|
|
|
use bevy_replicon::prelude::*;
|
|
|
|
|
use game_core::{
|
|
|
|
|
GameData,
|
|
|
|
|
components::*,
|
feat!: start menu, file-based UI, machine windows and server access control
- Start menu with new game (optionally hosted), load/delete saves and join
server screens; CLI options skip it.
- Menus and HUD are built from rsn layouts in assets/ui with a shared theme,
bindings, conditional visibility and tooltips.
- Machine windows place slots and backgrounds from the machine's ui
definition; slots are filled from and emptied into the inventory.
- Chat with / commands, pause menu, clickable inventory, craft and build
menus.
- Server passwords, admins, whitelist, bans, kicks and admin commands from
the terminal or chat; changes are written to the config file. The hosting
player is always admin.
- Clients keep a player id in client.toml; characters belong to it.
BREAKING CHANGE: protocol version 3 adds the player id and password to
Login, and characters are keyed by player id instead of name, so characters
in existing worlds are not restored.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 20:29:05 +00:00
|
|
|
protocol::{AdminStatus, Chat, GameLog, Login, Rejected, Welcome},
|
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 crate::{
|
feat!: start menu, file-based UI, machine windows and server access control
- Start menu with new game (optionally hosted), load/delete saves and join
server screens; CLI options skip it.
- Menus and HUD are built from rsn layouts in assets/ui with a shared theme,
bindings, conditional visibility and tooltips.
- Machine windows place slots and backgrounds from the machine's ui
definition; slots are filled from and emptied into the inventory.
- Chat with / commands, pause menu, clickable inventory, craft and build
menus.
- Server passwords, admins, whitelist, bans, kicks and admin commands from
the terminal or chat; changes are written to the config file. The hosting
player is always admin.
- Clients keep a player id in client.toml; characters belong to it.
BREAKING CHANGE: protocol version 3 adds the player id and password to
Login, and characters are keyed by player id instead of name, so characters
in existing worlds are not restored.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 20:29:05 +00:00
|
|
|
ServerSettings,
|
|
|
|
|
commands::{self, CommandSource},
|
|
|
|
|
config::ServerConfig,
|
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
|
|
|
movement::MoveIntent,
|
|
|
|
|
persistence::SavedCharacter,
|
|
|
|
|
world::{SpawnPoint, WorldLayout, find_free_tile},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// Which client controls a character. Server-only.
|
|
|
|
|
#[derive(Component, Clone, Copy, Debug)]
|
|
|
|
|
pub struct Controller(pub ClientId);
|
|
|
|
|
|
feat!: start menu, file-based UI, machine windows and server access control
- Start menu with new game (optionally hosted), load/delete saves and join
server screens; CLI options skip it.
- Menus and HUD are built from rsn layouts in assets/ui with a shared theme,
bindings, conditional visibility and tooltips.
- Machine windows place slots and backgrounds from the machine's ui
definition; slots are filled from and emptied into the inventory.
- Chat with / commands, pause menu, clickable inventory, craft and build
menus.
- Server passwords, admins, whitelist, bans, kicks and admin commands from
the terminal or chat; changes are written to the config file. The hosting
player is always admin.
- Clients keep a player id in client.toml; characters belong to it.
BREAKING CHANGE: protocol version 3 adds the player id and password to
Login, and characters are keyed by player id instead of name, so characters
in existing worlds are not restored.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 20:29:05 +00:00
|
|
|
/// The player owning a character (a UUID sent by the client). Server-only.
|
|
|
|
|
#[derive(Component, Clone, Debug, PartialEq, Eq)]
|
|
|
|
|
pub struct PlayerId(pub String);
|
|
|
|
|
|
|
|
|
|
/// Marks characters of players with admin rights. Server-only.
|
|
|
|
|
#[derive(Component, Clone, Copy, Debug)]
|
|
|
|
|
pub struct Admin;
|
|
|
|
|
|
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
|
|
|
/// Online characters by client.
|
|
|
|
|
#[derive(Resource, Default)]
|
|
|
|
|
pub struct Players {
|
|
|
|
|
pub by_client: HashMap<ClientId, Entity>,
|
|
|
|
|
}
|
|
|
|
|
|
feat!: start menu, file-based UI, machine windows and server access control
- Start menu with new game (optionally hosted), load/delete saves and join
server screens; CLI options skip it.
- Menus and HUD are built from rsn layouts in assets/ui with a shared theme,
bindings, conditional visibility and tooltips.
- Machine windows place slots and backgrounds from the machine's ui
definition; slots are filled from and emptied into the inventory.
- Chat with / commands, pause menu, clickable inventory, craft and build
menus.
- Server passwords, admins, whitelist, bans, kicks and admin commands from
the terminal or chat; changes are written to the config file. The hosting
player is always admin.
- Clients keep a player id in client.toml; characters belong to it.
BREAKING CHANGE: protocol version 3 adds the player id and password to
Login, and characters are keyed by player id instead of name, so characters
in existing worlds are not restored.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 20:29:05 +00:00
|
|
|
/// Characters that are currently offline, by player id. Persisted with the world.
|
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
|
|
|
#[derive(Resource, Default)]
|
|
|
|
|
pub struct CharacterStore(pub BTreeMap<String, SavedCharacter>);
|
|
|
|
|
|
|
|
|
|
pub const MAX_NAME_LEN: usize = 24;
|
|
|
|
|
|
|
|
|
|
pub fn tell(commands: &mut Commands, client: ClientId, text: impl Into<String>) {
|
|
|
|
|
commands.server_trigger(ToClients {
|
|
|
|
|
targets: SendTargets::Single(client),
|
|
|
|
|
message: GameLog { text: text.into() },
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn valid_name(name: &str) -> bool {
|
|
|
|
|
let len = name.chars().count();
|
|
|
|
|
(1..=MAX_NAME_LEN).contains(&len)
|
|
|
|
|
&& name.trim() == name
|
|
|
|
|
&& name
|
|
|
|
|
.chars()
|
|
|
|
|
.all(|c| c.is_alphanumeric() || c == ' ' || c == '_' || c == '-')
|
|
|
|
|
}
|
|
|
|
|
|
feat!: start menu, file-based UI, machine windows and server access control
- Start menu with new game (optionally hosted), load/delete saves and join
server screens; CLI options skip it.
- Menus and HUD are built from rsn layouts in assets/ui with a shared theme,
bindings, conditional visibility and tooltips.
- Machine windows place slots and backgrounds from the machine's ui
definition; slots are filled from and emptied into the inventory.
- Chat with / commands, pause menu, clickable inventory, craft and build
menus.
- Server passwords, admins, whitelist, bans, kicks and admin commands from
the terminal or chat; changes are written to the config file. The hosting
player is always admin.
- Clients keep a player id in client.toml; characters belong to it.
BREAKING CHANGE: protocol version 3 adds the player id and password to
Login, and characters are keyed by player id instead of name, so characters
in existing worlds are not restored.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 20:29:05 +00:00
|
|
|
/// Everything [`check_access`] needs to know about a login attempt.
|
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
|
|
|
pub struct AccessRequest<'a> {
|
|
|
|
|
pub player_id: &'a str,
|
|
|
|
|
/// Character name (the stored one for returning players).
|
|
|
|
|
pub name: &'a str,
|
|
|
|
|
pub password: &'a str,
|
|
|
|
|
/// Host of an embedded server or a local client; bypasses all restrictions.
|
|
|
|
|
pub owner: bool,
|
|
|
|
|
pub online: usize,
|
|
|
|
|
pub id_online: bool,
|
|
|
|
|
pub new_character: bool,
|
|
|
|
|
/// Another player's character already uses this name.
|
|
|
|
|
pub name_taken: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Whether a player may join. Pure so the rules can be tested in isolation.
|
|
|
|
|
pub fn check_access(config: &ServerConfig, req: &AccessRequest) -> Result<(), String> {
|
|
|
|
|
if uuid::Uuid::parse_str(req.player_id).is_err() {
|
|
|
|
|
return Err("Invalid player id.".into());
|
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
|
|
|
}
|
feat!: start menu, file-based UI, machine windows and server access control
- Start menu with new game (optionally hosted), load/delete saves and join
server screens; CLI options skip it.
- Menus and HUD are built from rsn layouts in assets/ui with a shared theme,
bindings, conditional visibility and tooltips.
- Machine windows place slots and backgrounds from the machine's ui
definition; slots are filled from and emptied into the inventory.
- Chat with / commands, pause menu, clickable inventory, craft and build
menus.
- Server passwords, admins, whitelist, bans, kicks and admin commands from
the terminal or chat; changes are written to the config file. The hosting
player is always admin.
- Clients keep a player id in client.toml; characters belong to it.
BREAKING CHANGE: protocol version 3 adds the player id and password to
Login, and characters are keyed by player id instead of name, so characters
in existing worlds are not restored.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 20:29:05 +00:00
|
|
|
let admin = req.owner || is_listed(&config.access.admins, req.player_id, req.name);
|
|
|
|
|
if !req.owner {
|
|
|
|
|
if !config.password.is_empty() && req.password != config.password {
|
|
|
|
|
return Err(if req.password.is_empty() {
|
|
|
|
|
"This server requires a password.".into()
|
|
|
|
|
} else {
|
|
|
|
|
"Wrong password.".into()
|
|
|
|
|
});
|
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
|
|
|
}
|
feat!: start menu, file-based UI, machine windows and server access control
- Start menu with new game (optionally hosted), load/delete saves and join
server screens; CLI options skip it.
- Menus and HUD are built from rsn layouts in assets/ui with a shared theme,
bindings, conditional visibility and tooltips.
- Machine windows place slots and backgrounds from the machine's ui
definition; slots are filled from and emptied into the inventory.
- Chat with / commands, pause menu, clickable inventory, craft and build
menus.
- Server passwords, admins, whitelist, bans, kicks and admin commands from
the terminal or chat; changes are written to the config file. The hosting
player is always admin.
- Clients keep a player id in client.toml; characters belong to it.
BREAKING CHANGE: protocol version 3 adds the player id and password to
Login, and characters are keyed by player id instead of name, so characters
in existing worlds are not restored.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 20:29:05 +00:00
|
|
|
if is_listed(&config.access.banned, req.player_id, req.name) {
|
|
|
|
|
return Err("You are banned from this server.".into());
|
|
|
|
|
}
|
|
|
|
|
if config.access.whitelist_enabled
|
|
|
|
|
&& !admin
|
|
|
|
|
&& !is_listed(&config.access.whitelist, req.player_id, req.name)
|
|
|
|
|
{
|
|
|
|
|
return Err("You are not on the whitelist.".into());
|
|
|
|
|
}
|
|
|
|
|
if req.online >= config.max_players && !admin {
|
|
|
|
|
return Err("The server is full.".into());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if req.id_online {
|
|
|
|
|
return Err("You are already logged in.".into());
|
|
|
|
|
}
|
|
|
|
|
if req.new_character {
|
|
|
|
|
if !valid_name(req.name) {
|
|
|
|
|
return Err(format!(
|
|
|
|
|
"Invalid character name. Use 1-{MAX_NAME_LEN} letters, digits, spaces, `_` or `-`."
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
if req.name_taken {
|
|
|
|
|
return Err("That name belongs to another player.".into());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Whether a ban/whitelist/admin entry matches a player id or (case-insensitively) a name.
|
|
|
|
|
pub fn is_listed(list: &[String], player_id: &str, name: &str) -> bool {
|
|
|
|
|
list.iter()
|
|
|
|
|
.any(|e| e == player_id || (!name.is_empty() && e.eq_ignore_ascii_case(name)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Whether a player has admin rights.
|
|
|
|
|
pub fn is_admin(
|
|
|
|
|
world_owner: Option<&str>,
|
|
|
|
|
config: &ServerConfig,
|
|
|
|
|
client: ClientId,
|
|
|
|
|
id: &str,
|
|
|
|
|
) -> bool {
|
|
|
|
|
client == ClientId::Server
|
|
|
|
|
|| world_owner == Some(id)
|
|
|
|
|
|| config.access.admins.iter().any(|a| a == id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Recent rejected logins, for admins.
|
|
|
|
|
#[derive(Resource, Default)]
|
|
|
|
|
pub struct RejectedLogins(pub VecDeque<String>);
|
|
|
|
|
|
|
|
|
|
const REJECTED_HISTORY: usize = 20;
|
|
|
|
|
|
|
|
|
|
/// Tells a client why, then disconnects it.
|
|
|
|
|
pub fn disconnect(commands: &mut Commands, client: ClientId, reason: impl Into<String>) {
|
|
|
|
|
commands.server_trigger(ToClients {
|
|
|
|
|
targets: SendTargets::Single(client),
|
|
|
|
|
message: Rejected {
|
|
|
|
|
reason: reason.into(),
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
if let ClientId::Client(entity) = client {
|
|
|
|
|
commands.write_message(DisconnectRequest { client: entity });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Sends a message to every online admin.
|
|
|
|
|
pub fn tell_admins(world: &mut World, text: &str) {
|
|
|
|
|
let mut q = world.query_filtered::<&Controller, With<Admin>>();
|
|
|
|
|
let admins: Vec<ClientId> = q.iter(world).map(|c| c.0).collect();
|
|
|
|
|
let mut commands = world.commands();
|
|
|
|
|
for client in admins {
|
|
|
|
|
tell(&mut commands, client, text);
|
|
|
|
|
}
|
|
|
|
|
world.flush();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Name of the character a player id owns in this world, online or stored.
|
|
|
|
|
pub fn character_name(world: &mut World, player_id: &str) -> Option<String> {
|
|
|
|
|
let mut q = world.query::<(&Character, &PlayerId)>();
|
|
|
|
|
if let Some((c, _)) = q.iter(world).find(|(_, id)| id.0 == player_id) {
|
|
|
|
|
return Some(c.name.clone());
|
|
|
|
|
}
|
|
|
|
|
world
|
|
|
|
|
.resource::<CharacterStore>()
|
|
|
|
|
.0
|
|
|
|
|
.get(player_id)
|
|
|
|
|
.map(|c| c.name.clone())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Player id owning a character name (case-insensitive), online or stored.
|
|
|
|
|
pub fn find_player_by_name(world: &mut World, name: &str) -> Option<String> {
|
|
|
|
|
let mut q = world.query::<(&Character, &PlayerId)>();
|
|
|
|
|
if let Some((_, id)) = q
|
|
|
|
|
.iter(world)
|
|
|
|
|
.find(|(c, _)| c.name.eq_ignore_ascii_case(name))
|
|
|
|
|
{
|
|
|
|
|
return Some(id.0.clone());
|
|
|
|
|
}
|
|
|
|
|
world
|
|
|
|
|
.resource::<CharacterStore>()
|
|
|
|
|
.0
|
|
|
|
|
.iter()
|
|
|
|
|
.find(|(_, c)| c.name.eq_ignore_ascii_case(name))
|
|
|
|
|
.map(|(id, _)| id.clone())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn on_login(login: On<FromClient<Login>>, mut commands: Commands) {
|
|
|
|
|
let client = login.client_id;
|
|
|
|
|
let login = login.message.clone();
|
|
|
|
|
commands.queue(move |world: &mut World| handle_login(world, client, login));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn handle_login(world: &mut World, client: ClientId, login: Login) {
|
|
|
|
|
if world.resource::<Players>().by_client.contains_key(&client) {
|
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
|
|
|
return;
|
|
|
|
|
}
|
feat!: start menu, file-based UI, machine windows and server access control
- Start menu with new game (optionally hosted), load/delete saves and join
server screens; CLI options skip it.
- Menus and HUD are built from rsn layouts in assets/ui with a shared theme,
bindings, conditional visibility and tooltips.
- Machine windows place slots and backgrounds from the machine's ui
definition; slots are filled from and emptied into the inventory.
- Chat with / commands, pause menu, clickable inventory, craft and build
menus.
- Server passwords, admins, whitelist, bans, kicks and admin commands from
the terminal or chat; changes are written to the config file. The hosting
player is always admin.
- Clients keep a player id in client.toml; characters belong to it.
BREAKING CHANGE: protocol version 3 adds the player id and password to
Login, and characters are keyed by player id instead of name, so characters
in existing worlds are not restored.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 20:29:05 +00:00
|
|
|
let player_id = login.player_id.trim().to_lowercase();
|
|
|
|
|
let stored_name = character_name(world, &player_id);
|
|
|
|
|
let new_character = stored_name.is_none();
|
|
|
|
|
let name = stored_name.unwrap_or_else(|| login.name.trim().to_owned());
|
|
|
|
|
let name_taken =
|
|
|
|
|
new_character && find_player_by_name(world, &name).is_some_and(|owner| owner != player_id);
|
|
|
|
|
let owner = world.resource::<ServerSettings>().owner.as_deref() == Some(player_id.as_str())
|
|
|
|
|
|| client == ClientId::Server;
|
|
|
|
|
let id_online = world
|
|
|
|
|
.query::<&PlayerId>()
|
|
|
|
|
.iter(world)
|
|
|
|
|
.any(|id| id.0 == player_id);
|
|
|
|
|
let request = AccessRequest {
|
|
|
|
|
player_id: &player_id,
|
|
|
|
|
name: &name,
|
|
|
|
|
password: &login.password,
|
|
|
|
|
owner,
|
|
|
|
|
online: world.resource::<Players>().by_client.len(),
|
|
|
|
|
id_online,
|
|
|
|
|
new_character,
|
|
|
|
|
name_taken,
|
|
|
|
|
};
|
|
|
|
|
if let Err(reason) = check_access(world.resource::<ServerConfig>(), &request) {
|
|
|
|
|
let entry = format!("{name} ({player_id}): {reason}");
|
|
|
|
|
warn!("rejected login of {entry}");
|
|
|
|
|
tell_admins(world, &format!("[admin] Rejected {entry}"));
|
|
|
|
|
let mut log = world.resource_mut::<RejectedLogins>();
|
|
|
|
|
if log.0.len() >= REJECTED_HISTORY {
|
|
|
|
|
log.0.pop_front();
|
|
|
|
|
}
|
|
|
|
|
log.0.push_back(entry);
|
|
|
|
|
disconnect(&mut world.commands(), client, reason);
|
|
|
|
|
world.flush();
|
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
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
feat!: start menu, file-based UI, machine windows and server access control
- Start menu with new game (optionally hosted), load/delete saves and join
server screens; CLI options skip it.
- Menus and HUD are built from rsn layouts in assets/ui with a shared theme,
bindings, conditional visibility and tooltips.
- Machine windows place slots and backgrounds from the machine's ui
definition; slots are filled from and emptied into the inventory.
- Chat with / commands, pause menu, clickable inventory, craft and build
menus.
- Server passwords, admins, whitelist, bans, kicks and admin commands from
the terminal or chat; changes are written to the config file. The hosting
player is always admin.
- Clients keep a player id in client.toml; characters belong to it.
BREAKING CHANGE: protocol version 3 adds the player id and password to
Login, and characters are keyed by player id instead of name, so characters
in existing worlds are not restored.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 20:29:05 +00:00
|
|
|
let saved = match world.resource_mut::<CharacterStore>().0.remove(&player_id) {
|
|
|
|
|
Some(saved) => saved,
|
|
|
|
|
None => {
|
|
|
|
|
let spawn = world.resource::<SpawnPoint>().0;
|
|
|
|
|
new_character_data(world.resource::<GameData>(), &name, spawn)
|
|
|
|
|
}
|
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
|
|
|
};
|
feat!: start menu, file-based UI, machine windows and server access control
- Start menu with new game (optionally hosted), load/delete saves and join
server screens; CLI options skip it.
- Menus and HUD are built from rsn layouts in assets/ui with a shared theme,
bindings, conditional visibility and tooltips.
- Machine windows place slots and backgrounds from the machine's ui
definition; slots are filled from and emptied into the inventory.
- Chat with / commands, pause menu, clickable inventory, craft and build
menus.
- Server passwords, admins, whitelist, bans, kicks and admin commands from
the terminal or chat; changes are written to the config file. The hosting
player is always admin.
- Clients keep a player id in client.toml; characters belong to it.
BREAKING CHANGE: protocol version 3 adds the player id and password to
Login, and characters are keyed by player id instead of name, so characters
in existing worlds are not restored.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 20:29:05 +00:00
|
|
|
let mut buildings =
|
|
|
|
|
world.query_filtered::<(&GridPos, &BuildingState), (With<Building>, Without<Character>)>();
|
|
|
|
|
let pos = find_free_tile(
|
|
|
|
|
saved.pos,
|
|
|
|
|
&world.resource::<WorldLayout>().0,
|
|
|
|
|
&buildings.query(world),
|
|
|
|
|
);
|
2026-10-18 21:15:16 +00:00
|
|
|
// The saved spot is only kept if the tile is still free.
|
|
|
|
|
let position = saved
|
|
|
|
|
.position
|
|
|
|
|
.filter(|p| p.tile() == pos)
|
|
|
|
|
.unwrap_or(Position::center_of(pos));
|
feat!: start menu, file-based UI, machine windows and server access control
- Start menu with new game (optionally hosted), load/delete saves and join
server screens; CLI options skip it.
- Menus and HUD are built from rsn layouts in assets/ui with a shared theme,
bindings, conditional visibility and tooltips.
- Machine windows place slots and backgrounds from the machine's ui
definition; slots are filled from and emptied into the inventory.
- Chat with / commands, pause menu, clickable inventory, craft and build
menus.
- Server passwords, admins, whitelist, bans, kicks and admin commands from
the terminal or chat; changes are written to the config file. The hosting
player is always admin.
- Clients keep a player id in client.toml; characters belong to it.
BREAKING CHANGE: protocol version 3 adds the player id and password to
Login, and characters are keyed by player id instead of name, so characters
in existing worlds are not restored.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 20:29:05 +00:00
|
|
|
let config = world.resource::<ServerConfig>().clone();
|
|
|
|
|
let owner_id = world.resource::<ServerSettings>().owner.clone();
|
|
|
|
|
let admin = is_admin(owner_id.as_deref(), &config, client, &player_id);
|
|
|
|
|
let mut entity = world.spawn((
|
|
|
|
|
Replicated,
|
|
|
|
|
Character { name: name.clone() },
|
|
|
|
|
pos,
|
2026-10-18 21:15:16 +00:00
|
|
|
position,
|
feat!: start menu, file-based UI, machine windows and server access control
- Start menu with new game (optionally hosted), load/delete saves and join
server screens; CLI options skip it.
- Menus and HUD are built from rsn layouts in assets/ui with a shared theme,
bindings, conditional visibility and tooltips.
- Machine windows place slots and backgrounds from the machine's ui
definition; slots are filled from and emptied into the inventory.
- Chat with / commands, pause menu, clickable inventory, craft and build
menus.
- Server passwords, admins, whitelist, bans, kicks and admin commands from
the terminal or chat; changes are written to the config file. The hosting
player is always admin.
- Clients keep a player id in client.toml; characters belong to it.
BREAKING CHANGE: protocol version 3 adds the player id and password to
Login, and characters are keyed by player id instead of name, so characters
in existing worlds are not restored.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 20:29:05 +00:00
|
|
|
Facing(saved.facing),
|
|
|
|
|
saved.vitals,
|
|
|
|
|
saved.inventory,
|
|
|
|
|
saved.skills,
|
|
|
|
|
Controller(client),
|
|
|
|
|
PlayerId(player_id.clone()),
|
|
|
|
|
MoveIntent::default(),
|
|
|
|
|
));
|
|
|
|
|
if admin {
|
|
|
|
|
entity.insert(Admin);
|
|
|
|
|
}
|
|
|
|
|
let entity = entity.id();
|
|
|
|
|
world
|
|
|
|
|
.resource_mut::<Players>()
|
|
|
|
|
.by_client
|
|
|
|
|
.insert(client, entity);
|
|
|
|
|
info!(
|
|
|
|
|
"{name} logged in (player id {player_id}, {client:?}{})",
|
|
|
|
|
if admin { ", admin" } else { "" }
|
|
|
|
|
);
|
|
|
|
|
let greeting = if new_character {
|
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
|
|
|
format!("Welcome to the station, {name}. Press H for help.")
|
feat!: start menu, file-based UI, machine windows and server access control
- Start menu with new game (optionally hosted), load/delete saves and join
server screens; CLI options skip it.
- Menus and HUD are built from rsn layouts in assets/ui with a shared theme,
bindings, conditional visibility and tooltips.
- Machine windows place slots and backgrounds from the machine's ui
definition; slots are filled from and emptied into the inventory.
- Chat with / commands, pause menu, clickable inventory, craft and build
menus.
- Server passwords, admins, whitelist, bans, kicks and admin commands from
the terminal or chat; changes are written to the config file. The hosting
player is always admin.
- Clients keep a player id in client.toml; characters belong to it.
BREAKING CHANGE: protocol version 3 adds the player id and password to
Login, and characters are keyed by player id instead of name, so characters
in existing worlds are not restored.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 20:29:05 +00:00
|
|
|
} else {
|
|
|
|
|
format!("Welcome back, {name}.")
|
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
|
|
|
};
|
feat!: start menu, file-based UI, machine windows and server access control
- Start menu with new game (optionally hosted), load/delete saves and join
server screens; CLI options skip it.
- Menus and HUD are built from rsn layouts in assets/ui with a shared theme,
bindings, conditional visibility and tooltips.
- Machine windows place slots and backgrounds from the machine's ui
definition; slots are filled from and emptied into the inventory.
- Chat with / commands, pause menu, clickable inventory, craft and build
menus.
- Server passwords, admins, whitelist, bans, kicks and admin commands from
the terminal or chat; changes are written to the config file. The hosting
player is always admin.
- Clients keep a player id in client.toml; characters belong to it.
BREAKING CHANGE: protocol version 3 adds the player id and password to
Login, and characters are keyed by player id instead of name, so characters
in existing worlds are not restored.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 20:29:05 +00:00
|
|
|
let mut commands = world.commands();
|
|
|
|
|
commands.server_trigger(ToClients {
|
|
|
|
|
targets: SendTargets::Single(client),
|
|
|
|
|
message: Welcome {
|
|
|
|
|
server_name: config.name.clone(),
|
|
|
|
|
motd: config.motd.clone(),
|
|
|
|
|
character: name,
|
|
|
|
|
admin,
|
|
|
|
|
},
|
|
|
|
|
});
|
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
|
|
|
tell(&mut commands, client, greeting);
|
feat!: start menu, file-based UI, machine windows and server access control
- Start menu with new game (optionally hosted), load/delete saves and join
server screens; CLI options skip it.
- Menus and HUD are built from rsn layouts in assets/ui with a shared theme,
bindings, conditional visibility and tooltips.
- Machine windows place slots and backgrounds from the machine's ui
definition; slots are filled from and emptied into the inventory.
- Chat with / commands, pause menu, clickable inventory, craft and build
menus.
- Server passwords, admins, whitelist, bans, kicks and admin commands from
the terminal or chat; changes are written to the config file. The hosting
player is always admin.
- Clients keep a player id in client.toml; characters belong to it.
BREAKING CHANGE: protocol version 3 adds the player id and password to
Login, and characters are keyed by player id instead of name, so characters
in existing worlds are not restored.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 20:29:05 +00:00
|
|
|
if !config.motd.is_empty() {
|
|
|
|
|
tell(&mut commands, client, config.motd);
|
|
|
|
|
}
|
|
|
|
|
world.flush();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Longest accepted chat message, in characters.
|
|
|
|
|
pub const MAX_CHAT_LEN: usize = 200;
|
|
|
|
|
|
|
|
|
|
/// Chat messages and `/commands` from players.
|
|
|
|
|
pub fn on_chat(chat: On<FromClient<Chat>>, mut commands: Commands) {
|
|
|
|
|
let client = chat.client_id;
|
|
|
|
|
let text: String = chat
|
|
|
|
|
.message
|
|
|
|
|
.text
|
|
|
|
|
.chars()
|
|
|
|
|
.filter(|c| !c.is_control())
|
|
|
|
|
.take(MAX_CHAT_LEN)
|
|
|
|
|
.collect();
|
|
|
|
|
commands.queue(move |world: &mut World| handle_chat(world, client, text.trim()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn handle_chat(world: &mut World, client: ClientId, text: &str) {
|
|
|
|
|
let Some(&entity) = world.resource::<Players>().by_client.get(&client) else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
if text.is_empty() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if let Some(command) = text.strip_prefix('/') {
|
|
|
|
|
let player_id = world.get::<PlayerId>(entity).map(|id| id.0.clone());
|
|
|
|
|
let owner = client == ClientId::Server
|
|
|
|
|
|| player_id
|
|
|
|
|
.is_some_and(|id| world.resource::<ServerSettings>().owner.as_deref() == Some(&id));
|
|
|
|
|
let source = CommandSource::Player {
|
|
|
|
|
client,
|
|
|
|
|
admin: world.get::<Admin>(entity).is_some(),
|
|
|
|
|
owner,
|
|
|
|
|
};
|
|
|
|
|
let name = world
|
|
|
|
|
.get::<Character>(entity)
|
|
|
|
|
.map(|c| c.name.clone())
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
info!("{name} ran `/{command}`");
|
|
|
|
|
let reply = commands::execute(world, source, command);
|
|
|
|
|
let mut cmds = world.commands();
|
|
|
|
|
for line in reply {
|
|
|
|
|
tell(&mut cmds, client, line);
|
|
|
|
|
}
|
|
|
|
|
world.flush();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let Some(name) = world.get::<Character>(entity).map(|c| c.name.clone()) else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
|
|
|
|
let line = format!("<{name}> {text}");
|
|
|
|
|
info!(target: "chat", "{line}");
|
|
|
|
|
world.commands().server_trigger(ToClients {
|
|
|
|
|
targets: SendTargets::All,
|
|
|
|
|
message: GameLog { text: line },
|
|
|
|
|
});
|
|
|
|
|
world.flush();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Applies access list changes to online players: kicks banned and no longer
|
|
|
|
|
/// whitelisted players and updates admin rights.
|
|
|
|
|
pub fn enforce_access(world: &mut World) {
|
|
|
|
|
let config = world.resource::<ServerConfig>().clone();
|
|
|
|
|
let owner = world.resource::<ServerSettings>().owner.clone();
|
|
|
|
|
let mut q = world.query::<(Entity, &Character, &PlayerId, &Controller, Has<Admin>)>();
|
|
|
|
|
let online: Vec<_> = q
|
|
|
|
|
.iter(world)
|
|
|
|
|
.map(|(e, c, id, ctrl, admin)| (e, c.name.clone(), id.0.clone(), ctrl.0, admin))
|
|
|
|
|
.collect();
|
|
|
|
|
for (entity, name, id, client, was_admin) in online {
|
|
|
|
|
let admin = is_admin(owner.as_deref(), &config, client, &id);
|
|
|
|
|
let privileged =
|
|
|
|
|
admin || client == ClientId::Server || owner.as_deref() == Some(id.as_str());
|
|
|
|
|
let reason = if privileged {
|
|
|
|
|
None
|
|
|
|
|
} else if is_listed(&config.access.banned, &id, &name) {
|
|
|
|
|
Some("You have been banned from this server.")
|
|
|
|
|
} else if config.access.whitelist_enabled
|
|
|
|
|
&& !is_listed(&config.access.whitelist, &id, &name)
|
|
|
|
|
{
|
|
|
|
|
Some("You are not on the whitelist.")
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
};
|
|
|
|
|
let mut commands = world.commands();
|
|
|
|
|
if let Some(reason) = reason {
|
|
|
|
|
info!("disconnecting {name}: {reason}");
|
|
|
|
|
disconnect(&mut commands, client, reason);
|
|
|
|
|
} else if admin != was_admin {
|
|
|
|
|
if admin {
|
|
|
|
|
commands.entity(entity).insert(Admin);
|
|
|
|
|
} else {
|
|
|
|
|
commands.entity(entity).remove::<Admin>();
|
|
|
|
|
}
|
|
|
|
|
commands.server_trigger(ToClients {
|
|
|
|
|
targets: SendTargets::Single(client),
|
|
|
|
|
message: AdminStatus { admin },
|
|
|
|
|
});
|
|
|
|
|
tell(
|
|
|
|
|
&mut commands,
|
|
|
|
|
client,
|
|
|
|
|
if admin {
|
|
|
|
|
"You are now an admin."
|
|
|
|
|
} else {
|
|
|
|
|
"You are no longer an admin."
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
world.flush();
|
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
|
feat!: start menu, file-based UI, machine windows and server access control
- Start menu with new game (optionally hosted), load/delete saves and join
server screens; CLI options skip it.
- Menus and HUD are built from rsn layouts in assets/ui with a shared theme,
bindings, conditional visibility and tooltips.
- Machine windows place slots and backgrounds from the machine's ui
definition; slots are filled from and emptied into the inventory.
- Chat with / commands, pause menu, clickable inventory, craft and build
menus.
- Server passwords, admins, whitelist, bans, kicks and admin commands from
the terminal or chat; changes are written to the config file. The hosting
player is always admin.
- Clients keep a player id in client.toml; characters belong to it.
BREAKING CHANGE: protocol version 3 adds the player id and password to
Login, and characters are keyed by player id instead of name, so characters
in existing worlds are not restored.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 20:29:05 +00:00
|
|
|
pub fn new_character_data(data: &GameData, name: &str, spawn: GridPos) -> SavedCharacter {
|
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
|
|
|
let mut inventory = Inventory::default();
|
|
|
|
|
if data.items.contains_key("apple") {
|
|
|
|
|
inventory.add("apple", 2, data);
|
|
|
|
|
}
|
|
|
|
|
SavedCharacter {
|
2026-10-18 18:47:43 +00:00
|
|
|
name: name.to_owned(),
|
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
|
|
|
pos: spawn,
|
2026-10-18 21:15:16 +00:00
|
|
|
position: 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
|
|
|
facing: Default::default(),
|
|
|
|
|
vitals: Vitals::default(),
|
|
|
|
|
inventory,
|
|
|
|
|
skills: Skills::default(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Stores the character of a disconnecting client and removes it from the world.
|
|
|
|
|
pub fn on_disconnect(
|
|
|
|
|
remove: On<Remove, ConnectedClient>,
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
mut players: ResMut<Players>,
|
|
|
|
|
mut store: ResMut<CharacterStore>,
|
feat!: start menu, file-based UI, machine windows and server access control
- Start menu with new game (optionally hosted), load/delete saves and join
server screens; CLI options skip it.
- Menus and HUD are built from rsn layouts in assets/ui with a shared theme,
bindings, conditional visibility and tooltips.
- Machine windows place slots and backgrounds from the machine's ui
definition; slots are filled from and emptied into the inventory.
- Chat with / commands, pause menu, clickable inventory, craft and build
menus.
- Server passwords, admins, whitelist, bans, kicks and admin commands from
the terminal or chat; changes are written to the config file. The hosting
player is always admin.
- Clients keep a player id in client.toml; characters belong to it.
BREAKING CHANGE: protocol version 3 adds the player id and password to
Login, and characters are keyed by player id instead of name, so characters
in existing worlds are not restored.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 20:29:05 +00:00
|
|
|
characters: Query<(
|
|
|
|
|
&Character,
|
|
|
|
|
&PlayerId,
|
|
|
|
|
&GridPos,
|
2026-10-18 21:15:16 +00:00
|
|
|
&Position,
|
feat!: start menu, file-based UI, machine windows and server access control
- Start menu with new game (optionally hosted), load/delete saves and join
server screens; CLI options skip it.
- Menus and HUD are built from rsn layouts in assets/ui with a shared theme,
bindings, conditional visibility and tooltips.
- Machine windows place slots and backgrounds from the machine's ui
definition; slots are filled from and emptied into the inventory.
- Chat with / commands, pause menu, clickable inventory, craft and build
menus.
- Server passwords, admins, whitelist, bans, kicks and admin commands from
the terminal or chat; changes are written to the config file. The hosting
player is always admin.
- Clients keep a player id in client.toml; characters belong to it.
BREAKING CHANGE: protocol version 3 adds the player id and password to
Login, and characters are keyed by player id instead of name, so characters
in existing worlds are not restored.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 20:29:05 +00:00
|
|
|
&Facing,
|
|
|
|
|
&Vitals,
|
|
|
|
|
&Inventory,
|
|
|
|
|
&Skills,
|
|
|
|
|
)>,
|
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
|
|
|
) {
|
|
|
|
|
let Some(entity) = players.by_client.remove(&ClientId::Client(remove.entity)) else {
|
|
|
|
|
return;
|
|
|
|
|
};
|
2026-10-18 21:15:16 +00:00
|
|
|
if let Ok((c, id, pos, position, facing, vitals, inventory, skills)) = characters.get(entity) {
|
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
|
|
|
info!("{} logged out", c.name);
|
|
|
|
|
store.0.insert(
|
feat!: start menu, file-based UI, machine windows and server access control
- Start menu with new game (optionally hosted), load/delete saves and join
server screens; CLI options skip it.
- Menus and HUD are built from rsn layouts in assets/ui with a shared theme,
bindings, conditional visibility and tooltips.
- Machine windows place slots and backgrounds from the machine's ui
definition; slots are filled from and emptied into the inventory.
- Chat with / commands, pause menu, clickable inventory, craft and build
menus.
- Server passwords, admins, whitelist, bans, kicks and admin commands from
the terminal or chat; changes are written to the config file. The hosting
player is always admin.
- Clients keep a player id in client.toml; characters belong to it.
BREAKING CHANGE: protocol version 3 adds the player id and password to
Login, and characters are keyed by player id instead of name, so characters
in existing worlds are not restored.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 20:29:05 +00:00
|
|
|
id.0.clone(),
|
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
|
|
|
SavedCharacter {
|
2026-10-18 18:47:43 +00:00
|
|
|
name: c.name.clone(),
|
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
|
|
|
pos: *pos,
|
2026-10-18 21:15:16 +00:00
|
|
|
position: Some(*position),
|
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
|
|
|
facing: facing.0,
|
|
|
|
|
vitals: vitals.clone(),
|
|
|
|
|
inventory: inventory.clone(),
|
|
|
|
|
skills: skills.clone(),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
commands.entity(entity).despawn();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
feat!: start menu, file-based UI, machine windows and server access control
- Start menu with new game (optionally hosted), load/delete saves and join
server screens; CLI options skip it.
- Menus and HUD are built from rsn layouts in assets/ui with a shared theme,
bindings, conditional visibility and tooltips.
- Machine windows place slots and backgrounds from the machine's ui
definition; slots are filled from and emptied into the inventory.
- Chat with / commands, pause menu, clickable inventory, craft and build
menus.
- Server passwords, admins, whitelist, bans, kicks and admin commands from
the terminal or chat; changes are written to the config file. The hosting
player is always admin.
- Clients keep a player id in client.toml; characters belong to it.
BREAKING CHANGE: protocol version 3 adds the player id and password to
Login, and characters are keyed by player id instead of name, so characters
in existing worlds are not restored.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-10-18 20:29:05 +00:00
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
const ID: &str = "4f1c0c56-1111-4a5e-9a1e-000000000001";
|
|
|
|
|
|
|
|
|
|
fn request<'a>() -> AccessRequest<'a> {
|
|
|
|
|
AccessRequest {
|
|
|
|
|
player_id: ID,
|
|
|
|
|
name: "Jane",
|
|
|
|
|
password: "",
|
|
|
|
|
owner: false,
|
|
|
|
|
online: 0,
|
|
|
|
|
id_online: false,
|
|
|
|
|
new_character: true,
|
|
|
|
|
name_taken: false,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn access_rules() {
|
|
|
|
|
let mut config = ServerConfig::default();
|
|
|
|
|
assert_eq!(check_access(&config, &request()), Ok(()));
|
|
|
|
|
let bad_id = AccessRequest {
|
|
|
|
|
player_id: "nope",
|
|
|
|
|
..request()
|
|
|
|
|
};
|
|
|
|
|
assert!(check_access(&config, &bad_id).is_err());
|
|
|
|
|
|
|
|
|
|
config.password = "secret".into();
|
|
|
|
|
assert!(check_access(&config, &request()).is_err());
|
|
|
|
|
let with_password = AccessRequest {
|
|
|
|
|
password: "secret",
|
|
|
|
|
..request()
|
|
|
|
|
};
|
|
|
|
|
assert_eq!(check_access(&config, &with_password), Ok(()));
|
|
|
|
|
let owner = AccessRequest {
|
|
|
|
|
owner: true,
|
|
|
|
|
..request()
|
|
|
|
|
};
|
|
|
|
|
assert_eq!(check_access(&config, &owner), Ok(()));
|
|
|
|
|
config.password.clear();
|
|
|
|
|
|
|
|
|
|
config.access.banned = vec!["JANE".into()];
|
|
|
|
|
assert!(check_access(&config, &request()).is_err());
|
|
|
|
|
config.access.banned = vec![ID.into()];
|
|
|
|
|
assert!(check_access(&config, &request()).is_err());
|
|
|
|
|
config.access.banned.clear();
|
|
|
|
|
|
|
|
|
|
config.access.whitelist_enabled = true;
|
|
|
|
|
assert!(check_access(&config, &request()).is_err());
|
|
|
|
|
config.access.whitelist = vec!["jane".into()];
|
|
|
|
|
assert_eq!(check_access(&config, &request()), Ok(()));
|
|
|
|
|
config.access.whitelist.clear();
|
|
|
|
|
config.access.admins = vec![ID.into()];
|
|
|
|
|
assert_eq!(
|
|
|
|
|
check_access(&config, &request()),
|
|
|
|
|
Ok(()),
|
|
|
|
|
"admins bypass the whitelist"
|
|
|
|
|
);
|
|
|
|
|
config.access.whitelist_enabled = false;
|
|
|
|
|
config.access.admins.clear();
|
|
|
|
|
|
|
|
|
|
config.max_players = 1;
|
|
|
|
|
let full = AccessRequest {
|
|
|
|
|
online: 1,
|
|
|
|
|
..request()
|
|
|
|
|
};
|
|
|
|
|
assert!(check_access(&config, &full).is_err());
|
|
|
|
|
config.max_players = 32;
|
|
|
|
|
|
|
|
|
|
for req in [
|
|
|
|
|
AccessRequest {
|
|
|
|
|
id_online: true,
|
|
|
|
|
..request()
|
|
|
|
|
},
|
|
|
|
|
AccessRequest {
|
|
|
|
|
name_taken: true,
|
|
|
|
|
..request()
|
|
|
|
|
},
|
|
|
|
|
AccessRequest {
|
|
|
|
|
name: "bad;name",
|
|
|
|
|
..request()
|
|
|
|
|
},
|
|
|
|
|
] {
|
|
|
|
|
assert!(check_access(&config, &req).is_err(), "{req:?}");
|
|
|
|
|
}
|
|
|
|
|
let returning = AccessRequest {
|
|
|
|
|
name_taken: true,
|
|
|
|
|
new_character: false,
|
|
|
|
|
..request()
|
|
|
|
|
};
|
|
|
|
|
assert_eq!(check_access(&config, &returning), Ok(()));
|
|
|
|
|
}
|
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
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn names() {
|
|
|
|
|
assert!(valid_name("Jane Doe"));
|
|
|
|
|
assert!(valid_name("x_1-2"));
|
|
|
|
|
assert!(!valid_name(""));
|
|
|
|
|
assert!(!valid_name(" padded"));
|
|
|
|
|
assert!(!valid_name("semi;colon"));
|
|
|
|
|
assert!(!valid_name(&"a".repeat(25)));
|
|
|
|
|
}
|
|
|
|
|
}
|