mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Server rules i18n and rules button in character screen
This commit is contained in:
parent
e4dbe50f39
commit
d6371f7f9b
@ -26,3 +26,4 @@ char_selection-starting_site_name = { $name }
|
|||||||
char_selection-starting_site_kind = Kind: { $kind }
|
char_selection-starting_site_kind = Kind: { $kind }
|
||||||
char_selection-create_info_name = Your Character needs a name!
|
char_selection-create_info_name = Your Character needs a name!
|
||||||
char_selection-version_mismatch = WARNING! This server is running a different, possibly incompatible game version. Please update your game.
|
char_selection-version_mismatch = WARNING! This server is running a different, possibly incompatible game version. Please update your game.
|
||||||
|
char_selection-rules = Rules
|
||||||
|
@ -137,6 +137,7 @@ hud-settings-music_spacing = Music Spacing
|
|||||||
hud-settings-audio_device = Audio Device
|
hud-settings-audio_device = Audio Device
|
||||||
hud-settings-reset_sound = Reset to Defaults
|
hud-settings-reset_sound = Reset to Defaults
|
||||||
hud-settings-english_fallback = Display English for missing translations
|
hud-settings-english_fallback = Display English for missing translations
|
||||||
|
hud-settings-language_share_with_server = Share configured language with servers (for localizing rules and motd)
|
||||||
hud-settings-awaitingkey = Press a key...
|
hud-settings-awaitingkey = Press a key...
|
||||||
hud-settings-unbound = None
|
hud-settings-unbound = None
|
||||||
hud-settings-reset_keybinds = Reset to Defaults
|
hud-settings-reset_keybinds = Reset to Defaults
|
||||||
|
@ -56,6 +56,7 @@ use common_base::{prof_span, span};
|
|||||||
use common_net::{
|
use common_net::{
|
||||||
msg::{
|
msg::{
|
||||||
self,
|
self,
|
||||||
|
server::ServerDescription,
|
||||||
world_msg::{EconomyInfo, PoiInfo, SiteId, SiteInfo},
|
world_msg::{EconomyInfo, PoiInfo, SiteId, SiteInfo},
|
||||||
ChatTypeContext, ClientGeneral, ClientMsg, ClientRegister, ClientType, DisconnectReason,
|
ChatTypeContext, ClientGeneral, ClientMsg, ClientRegister, ClientType, DisconnectReason,
|
||||||
InviteAnswer, Notification, PingMsg, PlayerInfo, PlayerListUpdate, RegisterError,
|
InviteAnswer, Notification, PingMsg, PlayerInfo, PlayerListUpdate, RegisterError,
|
||||||
@ -228,6 +229,8 @@ pub struct Client {
|
|||||||
presence: Option<PresenceKind>,
|
presence: Option<PresenceKind>,
|
||||||
runtime: Arc<Runtime>,
|
runtime: Arc<Runtime>,
|
||||||
server_info: ServerInfo,
|
server_info: ServerInfo,
|
||||||
|
/// Localized server motd and rules
|
||||||
|
server_description: ServerDescription,
|
||||||
world_data: WorldData,
|
world_data: WorldData,
|
||||||
weather: WeatherLerp,
|
weather: WeatherLerp,
|
||||||
player_list: HashMap<Uid, PlayerInfo>,
|
player_list: HashMap<Uid, PlayerInfo>,
|
||||||
@ -305,6 +308,7 @@ impl Client {
|
|||||||
mismatched_server_info: &mut Option<ServerInfo>,
|
mismatched_server_info: &mut Option<ServerInfo>,
|
||||||
username: &str,
|
username: &str,
|
||||||
password: &str,
|
password: &str,
|
||||||
|
locale: Option<String>,
|
||||||
auth_trusted: impl FnMut(&str) -> bool,
|
auth_trusted: impl FnMut(&str) -> bool,
|
||||||
init_stage_update: &(dyn Fn(ClientInitStage) + Send + Sync),
|
init_stage_update: &(dyn Fn(ClientInitStage) + Send + Sync),
|
||||||
add_foreign_systems: impl Fn(&mut DispatcherBuilder) + Send + 'static,
|
add_foreign_systems: impl Fn(&mut DispatcherBuilder) + Send + 'static,
|
||||||
@ -365,6 +369,7 @@ impl Client {
|
|||||||
Self::register(
|
Self::register(
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
|
locale,
|
||||||
auth_trusted,
|
auth_trusted,
|
||||||
&server_info,
|
&server_info,
|
||||||
&mut register_stream,
|
&mut register_stream,
|
||||||
@ -386,6 +391,7 @@ impl Client {
|
|||||||
ability_map,
|
ability_map,
|
||||||
server_constants,
|
server_constants,
|
||||||
repair_recipe_book,
|
repair_recipe_book,
|
||||||
|
description,
|
||||||
} = loop {
|
} = loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
// Spawn in a blocking thread (leaving the network thread free). This is mostly
|
// Spawn in a blocking thread (leaving the network thread free). This is mostly
|
||||||
@ -725,6 +731,7 @@ impl Client {
|
|||||||
presence: None,
|
presence: None,
|
||||||
runtime,
|
runtime,
|
||||||
server_info,
|
server_info,
|
||||||
|
server_description: description,
|
||||||
world_data: WorldData {
|
world_data: WorldData {
|
||||||
lod_base,
|
lod_base,
|
||||||
lod_alt,
|
lod_alt,
|
||||||
@ -801,6 +808,7 @@ impl Client {
|
|||||||
async fn register(
|
async fn register(
|
||||||
username: &str,
|
username: &str,
|
||||||
password: &str,
|
password: &str,
|
||||||
|
locale: Option<String>,
|
||||||
mut auth_trusted: impl FnMut(&str) -> bool,
|
mut auth_trusted: impl FnMut(&str) -> bool,
|
||||||
server_info: &ServerInfo,
|
server_info: &ServerInfo,
|
||||||
register_stream: &mut Stream,
|
register_stream: &mut Stream,
|
||||||
@ -838,7 +846,10 @@ impl Client {
|
|||||||
|
|
||||||
debug!("Registering client...");
|
debug!("Registering client...");
|
||||||
|
|
||||||
register_stream.send(ClientRegister { token_or_username })?;
|
register_stream.send(ClientRegister {
|
||||||
|
token_or_username,
|
||||||
|
locale,
|
||||||
|
})?;
|
||||||
|
|
||||||
match register_stream.recv::<ServerRegisterAnswer>().await? {
|
match register_stream.recv::<ServerRegisterAnswer>().await? {
|
||||||
Err(RegisterError::AuthError(err)) => Err(Error::AuthErr(err)),
|
Err(RegisterError::AuthError(err)) => Err(Error::AuthErr(err)),
|
||||||
@ -1182,6 +1193,8 @@ impl Client {
|
|||||||
|
|
||||||
pub fn server_info(&self) -> &ServerInfo { &self.server_info }
|
pub fn server_info(&self) -> &ServerInfo { &self.server_info }
|
||||||
|
|
||||||
|
pub fn server_description(&self) -> &ServerDescription { &self.server_description }
|
||||||
|
|
||||||
pub fn world_data(&self) -> &WorldData { &self.world_data }
|
pub fn world_data(&self) -> &WorldData { &self.world_data }
|
||||||
|
|
||||||
pub fn recipe_book(&self) -> &RecipeBook { &self.recipe_book }
|
pub fn recipe_book(&self) -> &RecipeBook { &self.recipe_book }
|
||||||
@ -3010,6 +3023,7 @@ mod tests {
|
|||||||
&mut None,
|
&mut None,
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
|
None,
|
||||||
|suggestion: &str| suggestion == auth_server,
|
|suggestion: &str| suggestion == auth_server,
|
||||||
&|_| {},
|
&|_| {},
|
||||||
|_| {},
|
|_| {},
|
||||||
|
@ -36,6 +36,7 @@ pub enum ClientType {
|
|||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct ClientRegister {
|
pub struct ClientRegister {
|
||||||
pub token_or_username: String,
|
pub token_or_username: String,
|
||||||
|
pub locale: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Messages sent from the client to the server
|
/// Messages sent from the client to the server
|
||||||
|
@ -47,10 +47,14 @@ pub enum ServerMsg {
|
|||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct ServerInfo {
|
pub struct ServerInfo {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: String,
|
|
||||||
pub git_hash: String,
|
pub git_hash: String,
|
||||||
pub git_date: String,
|
pub git_date: String,
|
||||||
pub auth_provider: Option<String>,
|
pub auth_provider: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
pub struct ServerDescription {
|
||||||
|
pub motd: String,
|
||||||
pub rules: Option<String>,
|
pub rules: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,6 +74,7 @@ pub enum ServerInit {
|
|||||||
material_stats: MaterialStatManifest,
|
material_stats: MaterialStatManifest,
|
||||||
ability_map: comp::item::tool::AbilityMap,
|
ability_map: comp::item::tool::AbilityMap,
|
||||||
server_constants: ServerConstants,
|
server_constants: ServerConstants,
|
||||||
|
description: ServerDescription,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -651,9 +651,7 @@ impl ServerChatCommand {
|
|||||||
"Make a sprite at your location",
|
"Make a sprite at your location",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
ServerChatCommand::Motd => {
|
ServerChatCommand::Motd => cmd(vec![], "View the server description", None),
|
||||||
cmd(vec![Message(Optional)], "View the server description", None)
|
|
||||||
},
|
|
||||||
ServerChatCommand::Object => cmd(
|
ServerChatCommand::Object => cmd(
|
||||||
vec![Enum("object", OBJECTS.clone(), Required)],
|
vec![Enum("object", OBJECTS.clone(), Required)],
|
||||||
"Spawn an object",
|
"Spawn an object",
|
||||||
@ -720,7 +718,7 @@ impl ServerChatCommand {
|
|||||||
Some(Moderator),
|
Some(Moderator),
|
||||||
),
|
),
|
||||||
ServerChatCommand::SetMotd => cmd(
|
ServerChatCommand::SetMotd => cmd(
|
||||||
vec![Message(Optional)],
|
vec![Any("locale", Optional), Message(Optional)],
|
||||||
"Set the server description",
|
"Set the server description",
|
||||||
Some(Admin),
|
Some(Admin),
|
||||||
),
|
),
|
||||||
|
@ -15,6 +15,7 @@ pub struct Client {
|
|||||||
pub participant: Option<Participant>,
|
pub participant: Option<Participant>,
|
||||||
pub last_ping: f64,
|
pub last_ping: f64,
|
||||||
pub login_msg_sent: AtomicBool,
|
pub login_msg_sent: AtomicBool,
|
||||||
|
pub locale: Option<String>,
|
||||||
|
|
||||||
//TODO: Consider splitting each of these out into their own components so all the message
|
//TODO: Consider splitting each of these out into their own components so all the message
|
||||||
//processing systems can run in parallel with each other (though it may turn out not to
|
//processing systems can run in parallel with each other (though it may turn out not to
|
||||||
@ -48,6 +49,7 @@ impl Client {
|
|||||||
client_type: ClientType,
|
client_type: ClientType,
|
||||||
participant: Participant,
|
participant: Participant,
|
||||||
last_ping: f64,
|
last_ping: f64,
|
||||||
|
locale: Option<String>,
|
||||||
general_stream: Stream,
|
general_stream: Stream,
|
||||||
ping_stream: Stream,
|
ping_stream: Stream,
|
||||||
register_stream: Stream,
|
register_stream: Stream,
|
||||||
@ -65,6 +67,7 @@ impl Client {
|
|||||||
client_type,
|
client_type,
|
||||||
participant: Some(participant),
|
participant: Some(participant),
|
||||||
last_ping,
|
last_ping,
|
||||||
|
locale,
|
||||||
login_msg_sent: AtomicBool::new(false),
|
login_msg_sent: AtomicBool::new(false),
|
||||||
general_stream,
|
general_stream,
|
||||||
ping_stream,
|
ping_stream,
|
||||||
|
@ -6,7 +6,8 @@ use crate::{
|
|||||||
location::Locations,
|
location::Locations,
|
||||||
login_provider::LoginProvider,
|
login_provider::LoginProvider,
|
||||||
settings::{
|
settings::{
|
||||||
Ban, BanAction, BanInfo, EditableSetting, SettingError, WhitelistInfo, WhitelistRecord,
|
server_description::ServerDescription, Ban, BanAction, BanInfo, EditableSetting,
|
||||||
|
SettingError, WhitelistInfo, WhitelistRecord,
|
||||||
},
|
},
|
||||||
sys::terrain::NpcData,
|
sys::terrain::NpcData,
|
||||||
weather::WeatherSim,
|
weather::WeatherSim,
|
||||||
@ -775,11 +776,23 @@ fn handle_motd(
|
|||||||
_args: Vec<String>,
|
_args: Vec<String>,
|
||||||
_action: &ServerChatCommand,
|
_action: &ServerChatCommand,
|
||||||
) -> CmdResult<()> {
|
) -> CmdResult<()> {
|
||||||
|
let locale = server
|
||||||
|
.state
|
||||||
|
.ecs()
|
||||||
|
.read_storage::<Client>()
|
||||||
|
.get(client)
|
||||||
|
.and_then(|client| client.locale.clone());
|
||||||
|
|
||||||
server.notify_client(
|
server.notify_client(
|
||||||
client,
|
client,
|
||||||
ServerGeneral::server_msg(
|
ServerGeneral::server_msg(
|
||||||
ChatType::CommandInfo,
|
ChatType::CommandInfo,
|
||||||
server.editable_settings().server_description.motd.clone(),
|
server
|
||||||
|
.editable_settings()
|
||||||
|
.server_description
|
||||||
|
.get(locale.as_ref())
|
||||||
|
.map_or("", |d| &d.motd)
|
||||||
|
.to_string(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -790,22 +803,31 @@ fn handle_set_motd(
|
|||||||
client: EcsEntity,
|
client: EcsEntity,
|
||||||
_target: EcsEntity,
|
_target: EcsEntity,
|
||||||
args: Vec<String>,
|
args: Vec<String>,
|
||||||
_action: &ServerChatCommand,
|
action: &ServerChatCommand,
|
||||||
) -> CmdResult<()> {
|
) -> CmdResult<()> {
|
||||||
let data_dir = server.data_dir();
|
let data_dir = server.data_dir();
|
||||||
let client_uuid = uuid(server, client, "client")?;
|
let client_uuid = uuid(server, client, "client")?;
|
||||||
// Ensure the person setting this has a real role in the settings file, since
|
// Ensure the person setting this has a real role in the settings file, since
|
||||||
// it's persistent.
|
// it's persistent.
|
||||||
let _client_real_role = real_role(server, client_uuid, "client")?;
|
let _client_real_role = real_role(server, client_uuid, "client")?;
|
||||||
match parse_cmd_args!(args, String) {
|
match parse_cmd_args!(args, String, String) {
|
||||||
Some(msg) => {
|
(Some(locale), Some(msg)) => {
|
||||||
let edit =
|
let edit =
|
||||||
server
|
server
|
||||||
.editable_settings_mut()
|
.editable_settings_mut()
|
||||||
.server_description
|
.server_description
|
||||||
.edit(data_dir.as_ref(), |d| {
|
.edit(data_dir.as_ref(), |d| {
|
||||||
let info = format!("Server message of the day set to {:?}", msg);
|
let info = format!("Server message of the day set to {:?}", msg);
|
||||||
d.motd = msg;
|
|
||||||
|
if let Some(description) = d.descriptions.get_mut(&locale) {
|
||||||
|
description.motd = msg;
|
||||||
|
} else {
|
||||||
|
d.descriptions.insert(locale, ServerDescription {
|
||||||
|
motd: msg,
|
||||||
|
rules: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Some(info)
|
Some(info)
|
||||||
});
|
});
|
||||||
drop(data_dir);
|
drop(data_dir);
|
||||||
@ -813,20 +835,25 @@ fn handle_set_motd(
|
|||||||
unreachable!("edit always returns Some")
|
unreachable!("edit always returns Some")
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
None => {
|
(Some(locale), None) => {
|
||||||
let edit =
|
let edit =
|
||||||
server
|
server
|
||||||
.editable_settings_mut()
|
.editable_settings_mut()
|
||||||
.server_description
|
.server_description
|
||||||
.edit(data_dir.as_ref(), |d| {
|
.edit(data_dir.as_ref(), |d| {
|
||||||
d.motd.clear();
|
if let Some(description) = d.descriptions.get_mut(&locale) {
|
||||||
|
description.motd.clear();
|
||||||
Some("Removed server message of the day".to_string())
|
Some("Removed server message of the day".to_string())
|
||||||
|
} else {
|
||||||
|
Some("This locale had no motd set".to_string())
|
||||||
|
}
|
||||||
});
|
});
|
||||||
drop(data_dir);
|
drop(data_dir);
|
||||||
edit_setting_feedback(server, client, edit, || {
|
edit_setting_feedback(server, client, edit, || {
|
||||||
unreachable!("edit always returns Some")
|
unreachable!("edit always returns Some")
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
_ => Err(Content::Plain(action.help_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,6 +146,7 @@ impl ConnectionHandler {
|
|||||||
client_type,
|
client_type,
|
||||||
participant,
|
participant,
|
||||||
server_data.time,
|
server_data.time,
|
||||||
|
None,
|
||||||
general_stream,
|
general_stream,
|
||||||
ping_stream,
|
ping_stream,
|
||||||
register_stream,
|
register_stream,
|
||||||
|
@ -624,14 +624,12 @@ impl Server {
|
|||||||
|
|
||||||
pub fn get_server_info(&self) -> ServerInfo {
|
pub fn get_server_info(&self) -> ServerInfo {
|
||||||
let settings = self.state.ecs().fetch::<Settings>();
|
let settings = self.state.ecs().fetch::<Settings>();
|
||||||
let editable_settings = self.state.ecs().fetch::<EditableSettings>();
|
|
||||||
ServerInfo {
|
ServerInfo {
|
||||||
name: settings.server_name.clone(),
|
name: settings.server_name.clone(),
|
||||||
description: editable_settings.server_description.motd.clone(),
|
|
||||||
git_hash: common::util::GIT_HASH.to_string(),
|
git_hash: common::util::GIT_HASH.to_string(),
|
||||||
git_date: common::util::GIT_DATE.to_string(),
|
git_date: common::util::GIT_DATE.to_string(),
|
||||||
auth_provider: settings.auth_server_address.clone(),
|
auth_provider: settings.auth_server_address.clone(),
|
||||||
rules: editable_settings.server_description.rules.clone(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ pub use admin::{AdminRecord, Admins};
|
|||||||
pub use banlist::{
|
pub use banlist::{
|
||||||
Ban, BanAction, BanEntry, BanError, BanErrorKind, BanInfo, BanKind, BanRecord, Banlist,
|
Ban, BanAction, BanEntry, BanError, BanErrorKind, BanInfo, BanKind, BanRecord, Banlist,
|
||||||
};
|
};
|
||||||
pub use server_description::ServerDescription;
|
pub use server_description::ServerDescriptions;
|
||||||
pub use whitelist::{Whitelist, WhitelistInfo, WhitelistRecord};
|
pub use whitelist::{Whitelist, WhitelistInfo, WhitelistRecord};
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
@ -362,7 +362,7 @@ const MIGRATION_UPGRADE_GUARANTEE: &str = "Any valid file of an old verison shou
|
|||||||
pub struct EditableSettings {
|
pub struct EditableSettings {
|
||||||
pub whitelist: Whitelist,
|
pub whitelist: Whitelist,
|
||||||
pub banlist: Banlist,
|
pub banlist: Banlist,
|
||||||
pub server_description: ServerDescription,
|
pub server_description: ServerDescriptions,
|
||||||
pub admins: Admins,
|
pub admins: Admins,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -371,7 +371,7 @@ impl EditableSettings {
|
|||||||
Self {
|
Self {
|
||||||
whitelist: Whitelist::load(data_dir),
|
whitelist: Whitelist::load(data_dir),
|
||||||
banlist: Banlist::load(data_dir),
|
banlist: Banlist::load(data_dir),
|
||||||
server_description: ServerDescription::load(data_dir),
|
server_description: ServerDescriptions::load(data_dir),
|
||||||
admins: Admins::load(data_dir),
|
admins: Admins::load(data_dir),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -379,8 +379,13 @@ impl EditableSettings {
|
|||||||
pub fn singleplayer(data_dir: &Path) -> Self {
|
pub fn singleplayer(data_dir: &Path) -> Self {
|
||||||
let load = Self::load(data_dir);
|
let load = Self::load(data_dir);
|
||||||
|
|
||||||
let mut server_description = ServerDescription::default();
|
let mut server_description = ServerDescriptions::default();
|
||||||
server_description.motd = "Who needs friends anyway?".into();
|
server_description
|
||||||
|
.descriptions
|
||||||
|
.values_mut()
|
||||||
|
.for_each(|entry| {
|
||||||
|
entry.motd = "Who needs friends anyway?".into();
|
||||||
|
});
|
||||||
|
|
||||||
let mut admins = Admins::default();
|
let mut admins = Admins::default();
|
||||||
// TODO: Let the player choose if they want to use admin commands or not
|
// TODO: Let the player choose if they want to use admin commands or not
|
||||||
|
@ -20,22 +20,22 @@ pub use self::v2::*;
|
|||||||
pub enum ServerDescriptionRaw {
|
pub enum ServerDescriptionRaw {
|
||||||
V0(v0::ServerDescription),
|
V0(v0::ServerDescription),
|
||||||
V1(v1::ServerDescription),
|
V1(v1::ServerDescription),
|
||||||
V2(ServerDescription),
|
V2(ServerDescriptions),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ServerDescription> for ServerDescriptionRaw {
|
impl From<ServerDescriptions> for ServerDescriptionRaw {
|
||||||
fn from(value: ServerDescription) -> Self {
|
fn from(value: ServerDescriptions) -> Self {
|
||||||
// Replace variant with that of current latest version.
|
// Replace variant with that of current latest version.
|
||||||
Self::V2(value)
|
Self::V2(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<ServerDescriptionRaw> for (Version, ServerDescription) {
|
impl TryFrom<ServerDescriptionRaw> for (Version, ServerDescriptions) {
|
||||||
type Error = <ServerDescription as EditableSetting>::Error;
|
type Error = <ServerDescriptions as EditableSetting>::Error;
|
||||||
|
|
||||||
fn try_from(
|
fn try_from(
|
||||||
value: ServerDescriptionRaw,
|
value: ServerDescriptionRaw,
|
||||||
) -> Result<Self, <ServerDescription as EditableSetting>::Error> {
|
) -> Result<Self, <ServerDescriptions as EditableSetting>::Error> {
|
||||||
use ServerDescriptionRaw::*;
|
use ServerDescriptionRaw::*;
|
||||||
Ok(match value {
|
Ok(match value {
|
||||||
// Old versions
|
// Old versions
|
||||||
@ -48,9 +48,9 @@ impl TryFrom<ServerDescriptionRaw> for (Version, ServerDescription) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Final = ServerDescription;
|
type Final = ServerDescriptions;
|
||||||
|
|
||||||
impl EditableSetting for ServerDescription {
|
impl EditableSetting for ServerDescriptions {
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
type Legacy = legacy::ServerDescription;
|
type Legacy = legacy::ServerDescription;
|
||||||
type Setting = ServerDescriptionRaw;
|
type Setting = ServerDescriptionRaw;
|
||||||
@ -169,30 +169,46 @@ mod v1 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use super::{v2 as next, MIGRATION_UPGRADE_GUARANTEE};
|
use super::v2 as next;
|
||||||
impl TryFrom<ServerDescription> for Final {
|
impl TryFrom<ServerDescription> for Final {
|
||||||
type Error = <Final as EditableSetting>::Error;
|
type Error = <Final as EditableSetting>::Error;
|
||||||
|
|
||||||
fn try_from(mut value: ServerDescription) -> Result<Final, Self::Error> {
|
fn try_from(mut value: ServerDescription) -> Result<Final, Self::Error> {
|
||||||
value.validate()?;
|
value.validate()?;
|
||||||
Ok(next::ServerDescription::migrate(value)
|
Ok(next::ServerDescriptions::migrate(value))
|
||||||
.try_into()
|
|
||||||
.expect(MIGRATION_UPGRADE_GUARANTEE))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod v2 {
|
mod v2 {
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use super::{v1 as prev, Final};
|
use super::{v1 as prev, Final};
|
||||||
use crate::settings::editable::{EditableSetting, Version};
|
use crate::settings::editable::{EditableSetting, Version};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// Map of all localized [`ServerDescription`]s
|
||||||
|
#[derive(Clone, Deserialize, Serialize)]
|
||||||
|
pub struct ServerDescriptions {
|
||||||
|
pub default_locale: String,
|
||||||
|
pub descriptions: HashMap<String, ServerDescription>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
#[derive(Clone, Deserialize, Serialize)]
|
||||||
pub struct ServerDescription {
|
pub struct ServerDescription {
|
||||||
pub motd: String,
|
pub motd: String,
|
||||||
pub rules: Option<String>,
|
pub rules: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ServerDescriptions {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
default_locale: "en".to_string(),
|
||||||
|
descriptions: HashMap::from([("en".to_string(), ServerDescription::default())]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for ServerDescription {
|
impl Default for ServerDescription {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -202,14 +218,31 @@ mod v2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerDescription {
|
impl ServerDescriptions {
|
||||||
|
pub fn get(&self, locale: Option<&String>) -> Option<&ServerDescription> {
|
||||||
|
let locale = locale.map_or(&self.default_locale, |locale| {
|
||||||
|
if self.descriptions.contains_key(locale) {
|
||||||
|
locale
|
||||||
|
} else {
|
||||||
|
&self.default_locale
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
self.descriptions.get(locale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerDescriptions {
|
||||||
/// One-off migration from the previous version. This must be
|
/// One-off migration from the previous version. This must be
|
||||||
/// guaranteed to produce a valid settings file as long as it is
|
/// guaranteed to produce a valid settings file as long as it is
|
||||||
/// called with a valid settings file from the previous version.
|
/// called with a valid settings file from the previous version.
|
||||||
pub(super) fn migrate(prev: prev::ServerDescription) -> Self {
|
pub(super) fn migrate(prev: prev::ServerDescription) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
default_locale: "en".to_string(),
|
||||||
|
descriptions: HashMap::from([("en".to_string(), ServerDescription {
|
||||||
motd: prev.0,
|
motd: prev.0,
|
||||||
rules: None,
|
rules: None,
|
||||||
|
})]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,9 +253,24 @@ mod v2 {
|
|||||||
/// been modified during validation (this is why validate takes
|
/// been modified during validation (this is why validate takes
|
||||||
/// `&mut self`).
|
/// `&mut self`).
|
||||||
pub(super) fn validate(&mut self) -> Result<Version, <Final as EditableSetting>::Error> {
|
pub(super) fn validate(&mut self) -> Result<Version, <Final as EditableSetting>::Error> {
|
||||||
|
if self.descriptions.is_empty() {
|
||||||
|
*self = Self::default();
|
||||||
|
Ok(Version::Old)
|
||||||
|
} else if !self.descriptions.contains_key(&self.default_locale) {
|
||||||
|
// default locale not present, select the a random one (as ordering in hashmaps
|
||||||
|
// isn't predictable)
|
||||||
|
self.default_locale = self
|
||||||
|
.descriptions
|
||||||
|
.keys()
|
||||||
|
.next()
|
||||||
|
.expect("We know descriptions isn't empty")
|
||||||
|
.to_string();
|
||||||
|
Ok(Version::Old)
|
||||||
|
} else {
|
||||||
Ok(Version::Latest)
|
Ok(Version::Latest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: Whenever there is a version upgrade, copy this note as well as the
|
// NOTE: Whenever there is a version upgrade, copy this note as well as the
|
||||||
// commented-out code below to the next version, then uncomment the code
|
// commented-out code below to the next version, then uncomment the code
|
||||||
|
@ -45,10 +45,13 @@ impl Sys {
|
|||||||
) -> Result<(), crate::error::Error> {
|
) -> Result<(), crate::error::Error> {
|
||||||
let mut send_join_messages = || -> Result<(), crate::error::Error> {
|
let mut send_join_messages = || -> Result<(), crate::error::Error> {
|
||||||
// Give the player a welcome message
|
// Give the player a welcome message
|
||||||
if !editable_settings.server_description.motd.is_empty() {
|
let localized_description = editable_settings
|
||||||
|
.server_description
|
||||||
|
.get(client.locale.as_ref());
|
||||||
|
if !localized_description.map_or(true, |d| d.motd.is_empty()) {
|
||||||
client.send(ServerGeneral::server_msg(
|
client.send(ServerGeneral::server_msg(
|
||||||
ChatType::CommandInfo,
|
ChatType::CommandInfo,
|
||||||
editable_settings.server_description.motd.as_str(),
|
localized_description.map_or("", |d| &d.motd),
|
||||||
))?;
|
))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,8 +16,8 @@ use common::{
|
|||||||
use common_base::prof_span;
|
use common_base::prof_span;
|
||||||
use common_ecs::{Job, Origin, Phase, System};
|
use common_ecs::{Job, Origin, Phase, System};
|
||||||
use common_net::msg::{
|
use common_net::msg::{
|
||||||
CharacterInfo, ClientRegister, DisconnectReason, PlayerInfo, PlayerListUpdate, RegisterError,
|
server::ServerDescription, CharacterInfo, ClientRegister, DisconnectReason, PlayerInfo,
|
||||||
ServerGeneral, ServerInit, WorldMapMsg,
|
PlayerListUpdate, RegisterError, ServerGeneral, ServerInit, WorldMapMsg,
|
||||||
};
|
};
|
||||||
use hashbrown::{hash_map, HashMap};
|
use hashbrown::{hash_map, HashMap};
|
||||||
use itertools::Either;
|
use itertools::Either;
|
||||||
@ -129,12 +129,20 @@ impl<'a> System<'a> for Sys {
|
|||||||
|
|
||||||
// defer auth lockup
|
// defer auth lockup
|
||||||
for (entity, client) in (&read_data.entities, &mut clients).join() {
|
for (entity, client) in (&read_data.entities, &mut clients).join() {
|
||||||
|
let mut locale = None;
|
||||||
|
|
||||||
let _ = super::try_recv_all(client, 0, |_, msg: ClientRegister| {
|
let _ = super::try_recv_all(client, 0, |_, msg: ClientRegister| {
|
||||||
trace!(?msg.token_or_username, "defer auth lockup");
|
trace!(?msg.token_or_username, "defer auth lockup");
|
||||||
let pending = read_data.login_provider.verify(&msg.token_or_username);
|
let pending = read_data.login_provider.verify(&msg.token_or_username);
|
||||||
|
locale = msg.locale;
|
||||||
let _ = pending_logins.insert(entity, pending);
|
let _ = pending_logins.insert(entity, pending);
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update locale
|
||||||
|
if let Some(locale) = locale {
|
||||||
|
client.locale = Some(locale);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let old_player_count = player_list.len();
|
let old_player_count = player_list.len();
|
||||||
@ -320,6 +328,19 @@ impl<'a> System<'a> for Sys {
|
|||||||
// Tell the client its request was successful.
|
// Tell the client its request was successful.
|
||||||
client.send(Ok(()))?;
|
client.send(Ok(()))?;
|
||||||
|
|
||||||
|
|
||||||
|
let description = read_data
|
||||||
|
.editable_settings
|
||||||
|
.server_description
|
||||||
|
.get(client.locale.as_ref())
|
||||||
|
.map(|description|
|
||||||
|
ServerDescription {
|
||||||
|
motd: description.motd.clone(),
|
||||||
|
rules: description.rules.clone()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
// Send client all the tracked components currently attached to its entity
|
// Send client all the tracked components currently attached to its entity
|
||||||
// as well as synced resources (currently only `TimeOfDay`)
|
// as well as synced resources (currently only `TimeOfDay`)
|
||||||
debug!("Starting initial sync with client.");
|
debug!("Starting initial sync with client.");
|
||||||
@ -340,6 +361,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
server_constants: ServerConstants {
|
server_constants: ServerConstants {
|
||||||
day_cycle_coefficient: read_data.settings.day_cycle_coefficient()
|
day_cycle_coefficient: read_data.settings.day_cycle_coefficient()
|
||||||
},
|
},
|
||||||
|
description,
|
||||||
})?;
|
})?;
|
||||||
debug!("Done initial sync with client.");
|
debug!("Done initial sync with client.");
|
||||||
|
|
||||||
|
@ -17,6 +17,8 @@ widget_ids! {
|
|||||||
window_r,
|
window_r,
|
||||||
english_fallback_button,
|
english_fallback_button,
|
||||||
english_fallback_button_label,
|
english_fallback_button_label,
|
||||||
|
share_with_server_checkbox,
|
||||||
|
share_with_server_checkbox_label,
|
||||||
window_scrollbar,
|
window_scrollbar,
|
||||||
language_list[],
|
language_list[],
|
||||||
}
|
}
|
||||||
@ -86,9 +88,64 @@ impl<'a> Widget for Language<'a> {
|
|||||||
.rgba(0.33, 0.33, 0.33, 1.0)
|
.rgba(0.33, 0.33, 0.33, 1.0)
|
||||||
.set(state.ids.window_scrollbar, ui);
|
.set(state.ids.window_scrollbar, ui);
|
||||||
|
|
||||||
|
// Share with server button
|
||||||
|
let share_with_server = ToggleButton::new(
|
||||||
|
self.global_state.settings.language.share_with_server,
|
||||||
|
self.imgs.checkbox,
|
||||||
|
self.imgs.checkbox_checked,
|
||||||
|
)
|
||||||
|
.w_h(18.0, 18.0)
|
||||||
|
.top_left_with_margin_on(state.ids.window, 20.0)
|
||||||
|
.hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
|
||||||
|
.press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
|
||||||
|
.set(state.ids.share_with_server_checkbox, ui);
|
||||||
|
|
||||||
|
if share_with_server != self.global_state.settings.language.share_with_server {
|
||||||
|
events.push(ToggleShareWithServer(share_with_server));
|
||||||
|
}
|
||||||
|
|
||||||
|
Text::new(
|
||||||
|
&self
|
||||||
|
.localized_strings
|
||||||
|
.get_msg("hud-settings-language_share_with_server"),
|
||||||
|
)
|
||||||
|
.right_from(state.ids.share_with_server_checkbox, 10.0)
|
||||||
|
.font_size(self.fonts.cyri.scale(14))
|
||||||
|
.font_id(self.fonts.cyri.conrod_id)
|
||||||
|
.graphics_for(state.ids.share_with_server_checkbox)
|
||||||
|
.color(TEXT_COLOR)
|
||||||
|
.set(state.ids.share_with_server_checkbox_label, ui);
|
||||||
|
|
||||||
|
// English as fallback language
|
||||||
|
let show_english_fallback = ToggleButton::new(
|
||||||
|
self.global_state.settings.language.use_english_fallback,
|
||||||
|
self.imgs.checkbox,
|
||||||
|
self.imgs.checkbox_checked,
|
||||||
|
)
|
||||||
|
.w_h(18.0, 18.0)
|
||||||
|
.down_from(state.ids.share_with_server_checkbox, 10.0)
|
||||||
|
.hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
|
||||||
|
.press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
|
||||||
|
.set(state.ids.english_fallback_button, ui);
|
||||||
|
|
||||||
|
if self.global_state.settings.language.use_english_fallback != show_english_fallback {
|
||||||
|
events.push(ToggleEnglishFallback(show_english_fallback));
|
||||||
|
}
|
||||||
|
|
||||||
|
Text::new(
|
||||||
|
&self
|
||||||
|
.localized_strings
|
||||||
|
.get_msg("hud-settings-english_fallback"),
|
||||||
|
)
|
||||||
|
.right_from(state.ids.english_fallback_button, 10.0)
|
||||||
|
.font_size(self.fonts.cyri.scale(14))
|
||||||
|
.font_id(self.fonts.cyri.conrod_id)
|
||||||
|
.graphics_for(state.ids.english_fallback_button)
|
||||||
|
.color(TEXT_COLOR)
|
||||||
|
.set(state.ids.english_fallback_button_label, ui);
|
||||||
|
|
||||||
// List available languages
|
// List available languages
|
||||||
let selected_language = &self.global_state.settings.language.selected_language;
|
let selected_language = &self.global_state.settings.language.selected_language;
|
||||||
let english_fallback = self.global_state.settings.language.use_english_fallback;
|
|
||||||
let language_list = list_localizations();
|
let language_list = list_localizations();
|
||||||
if state.ids.language_list.len() < language_list.len() {
|
if state.ids.language_list.len() < language_list.len() {
|
||||||
state.update(|state| {
|
state.update(|state| {
|
||||||
@ -107,7 +164,7 @@ impl<'a> Widget for Language<'a> {
|
|||||||
self.imgs.nothing
|
self.imgs.nothing
|
||||||
});
|
});
|
||||||
let button = if i == 0 {
|
let button = if i == 0 {
|
||||||
button.mid_top_with_margin_on(state.ids.window, 20.0)
|
button.mid_top_with_margin_on(state.ids.window, 58.0)
|
||||||
} else {
|
} else {
|
||||||
button.mid_bottom_with_margin_on(state.ids.language_list[i - 1], -button_h)
|
button.mid_bottom_with_margin_on(state.ids.language_list[i - 1], -button_h)
|
||||||
};
|
};
|
||||||
@ -127,40 +184,6 @@ impl<'a> Widget for Language<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// English as fallback language
|
|
||||||
let show_english_fallback = ToggleButton::new(
|
|
||||||
english_fallback,
|
|
||||||
self.imgs.checkbox,
|
|
||||||
self.imgs.checkbox_checked,
|
|
||||||
)
|
|
||||||
.w_h(18.0, 18.0);
|
|
||||||
let show_english_fallback = if let Some(id) = state.ids.language_list.last() {
|
|
||||||
show_english_fallback.down_from(*id, 8.0)
|
|
||||||
//mid_bottom_with_margin_on(id, -button_h)
|
|
||||||
} else {
|
|
||||||
show_english_fallback.mid_top_with_margin_on(state.ids.window, 20.0)
|
|
||||||
};
|
|
||||||
let show_english_fallback = show_english_fallback
|
|
||||||
.hover_images(self.imgs.checkbox_mo, self.imgs.checkbox_checked_mo)
|
|
||||||
.press_images(self.imgs.checkbox_press, self.imgs.checkbox_checked)
|
|
||||||
.set(state.ids.english_fallback_button, ui);
|
|
||||||
|
|
||||||
if english_fallback != show_english_fallback {
|
|
||||||
events.push(ToggleEnglishFallback(show_english_fallback));
|
|
||||||
}
|
|
||||||
|
|
||||||
Text::new(
|
|
||||||
&self
|
|
||||||
.localized_strings
|
|
||||||
.get_msg("hud-settings-english_fallback"),
|
|
||||||
)
|
|
||||||
.right_from(state.ids.english_fallback_button, 10.0)
|
|
||||||
.font_size(self.fonts.cyri.scale(14))
|
|
||||||
.font_id(self.fonts.cyri.conrod_id)
|
|
||||||
.graphics_for(state.ids.english_fallback_button)
|
|
||||||
.color(TEXT_COLOR)
|
|
||||||
.set(state.ids.english_fallback_button_label, ui);
|
|
||||||
|
|
||||||
events
|
events
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
menu::{main::rand_bg_image_spec, server_info::ServerInfoState},
|
||||||
render::{Drawer, GlobalsBindGroup},
|
render::{Drawer, GlobalsBindGroup},
|
||||||
scene::simple::{self as scene, Scene},
|
scene::simple::{self as scene, Scene},
|
||||||
session::SessionState,
|
session::SessionState,
|
||||||
@ -160,6 +161,28 @@ impl PlayState for CharSelectionState {
|
|||||||
Rc::clone(&self.client),
|
Rc::clone(&self.client),
|
||||||
)));
|
)));
|
||||||
},
|
},
|
||||||
|
ui::Event::ShowRules => {
|
||||||
|
let client = self.client.borrow();
|
||||||
|
|
||||||
|
let server_info = client.server_info().clone();
|
||||||
|
let server_description = client.server_description().clone();
|
||||||
|
|
||||||
|
let char_select =
|
||||||
|
CharSelectionState::new(global_state, Rc::clone(&self.client));
|
||||||
|
|
||||||
|
let new_state = ServerInfoState::try_from_server_info(
|
||||||
|
global_state,
|
||||||
|
rand_bg_image_spec(),
|
||||||
|
char_select,
|
||||||
|
server_info,
|
||||||
|
server_description,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.map(|s| Box::new(s) as _)
|
||||||
|
.unwrap_or_else(|s| Box::new(s) as _);
|
||||||
|
|
||||||
|
return PlayStateResult::Switch(new_state);
|
||||||
|
},
|
||||||
ui::Event::ClearCharacterListError => {
|
ui::Event::ClearCharacterListError => {
|
||||||
self.char_selection_ui.error = None;
|
self.char_selection_ui.error = None;
|
||||||
},
|
},
|
||||||
|
@ -153,6 +153,7 @@ pub enum Event {
|
|||||||
DeleteCharacter(CharacterId),
|
DeleteCharacter(CharacterId),
|
||||||
ClearCharacterListError,
|
ClearCharacterListError,
|
||||||
SelectCharacter(Option<CharacterId>),
|
SelectCharacter(Option<CharacterId>),
|
||||||
|
ShowRules,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Mode {
|
enum Mode {
|
||||||
@ -163,6 +164,7 @@ enum Mode {
|
|||||||
character_buttons: Vec<button::State>,
|
character_buttons: Vec<button::State>,
|
||||||
new_character_button: button::State,
|
new_character_button: button::State,
|
||||||
logout_button: button::State,
|
logout_button: button::State,
|
||||||
|
rule_button: button::State,
|
||||||
enter_world_button: button::State,
|
enter_world_button: button::State,
|
||||||
spectate_button: button::State,
|
spectate_button: button::State,
|
||||||
yes_button: button::State,
|
yes_button: button::State,
|
||||||
@ -204,6 +206,7 @@ impl Mode {
|
|||||||
character_buttons: Vec::new(),
|
character_buttons: Vec::new(),
|
||||||
new_character_button: Default::default(),
|
new_character_button: Default::default(),
|
||||||
logout_button: Default::default(),
|
logout_button: Default::default(),
|
||||||
|
rule_button: Default::default(),
|
||||||
enter_world_button: Default::default(),
|
enter_world_button: Default::default(),
|
||||||
spectate_button: Default::default(),
|
spectate_button: Default::default(),
|
||||||
yes_button: Default::default(),
|
yes_button: Default::default(),
|
||||||
@ -308,12 +311,14 @@ struct Controls {
|
|||||||
map_img: GraphicId,
|
map_img: GraphicId,
|
||||||
possible_starting_sites: Vec<SiteInfo>,
|
possible_starting_sites: Vec<SiteInfo>,
|
||||||
world_sz: Vec2<u32>,
|
world_sz: Vec2<u32>,
|
||||||
|
has_rules: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
enum Message {
|
enum Message {
|
||||||
Back,
|
Back,
|
||||||
Logout,
|
Logout,
|
||||||
|
ShowRules,
|
||||||
EnterWorld,
|
EnterWorld,
|
||||||
Spectate,
|
Spectate,
|
||||||
Select(CharacterId),
|
Select(CharacterId),
|
||||||
@ -356,6 +361,7 @@ impl Controls {
|
|||||||
map_img: GraphicId,
|
map_img: GraphicId,
|
||||||
possible_starting_sites: Vec<SiteInfo>,
|
possible_starting_sites: Vec<SiteInfo>,
|
||||||
world_sz: Vec2<u32>,
|
world_sz: Vec2<u32>,
|
||||||
|
has_rules: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let version = common::util::DISPLAY_VERSION_LONG.clone();
|
let version = common::util::DISPLAY_VERSION_LONG.clone();
|
||||||
let alpha = format!("Veloren {}", common::util::DISPLAY_VERSION.as_str());
|
let alpha = format!("Veloren {}", common::util::DISPLAY_VERSION.as_str());
|
||||||
@ -377,6 +383,7 @@ impl Controls {
|
|||||||
map_img,
|
map_img,
|
||||||
possible_starting_sites,
|
possible_starting_sites,
|
||||||
world_sz,
|
world_sz,
|
||||||
|
has_rules,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -467,6 +474,7 @@ impl Controls {
|
|||||||
ref mut character_buttons,
|
ref mut character_buttons,
|
||||||
ref mut new_character_button,
|
ref mut new_character_button,
|
||||||
ref mut logout_button,
|
ref mut logout_button,
|
||||||
|
ref mut rule_button,
|
||||||
ref mut enter_world_button,
|
ref mut enter_world_button,
|
||||||
ref mut spectate_button,
|
ref mut spectate_button,
|
||||||
ref mut yes_button,
|
ref mut yes_button,
|
||||||
@ -738,7 +746,25 @@ impl Controls {
|
|||||||
])
|
])
|
||||||
.height(Length::Fill);
|
.height(Length::Fill);
|
||||||
|
|
||||||
let left_column = Column::with_children(vec![server.into(), characters.into()])
|
let mut left_column_children = vec![server.into(), characters.into()];
|
||||||
|
|
||||||
|
if self.has_rules {
|
||||||
|
left_column_children.push(
|
||||||
|
Container::new(neat_button(
|
||||||
|
rule_button,
|
||||||
|
i18n.get_msg("char_selection-rules").into_owned(),
|
||||||
|
FILL_FRAC_ONE,
|
||||||
|
button_style,
|
||||||
|
Some(Message::ShowRules),
|
||||||
|
))
|
||||||
|
.align_y(Align::End)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.center_x()
|
||||||
|
.height(Length::Units(52))
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let left_column = Column::with_children(left_column_children)
|
||||||
.spacing(10)
|
.spacing(10)
|
||||||
.width(Length::Units(322)) // TODO: see if we can get iced to work with settings below
|
.width(Length::Units(322)) // TODO: see if we can get iced to work with settings below
|
||||||
// .max_width(360)
|
// .max_width(360)
|
||||||
@ -1670,6 +1696,9 @@ impl Controls {
|
|||||||
Message::Logout => {
|
Message::Logout => {
|
||||||
events.push(Event::Logout);
|
events.push(Event::Logout);
|
||||||
},
|
},
|
||||||
|
Message::ShowRules => {
|
||||||
|
events.push(Event::ShowRules);
|
||||||
|
},
|
||||||
Message::ConfirmDeletion => {
|
Message::ConfirmDeletion => {
|
||||||
if let Mode::Select { info_content, .. } = &mut self.mode {
|
if let Mode::Select { info_content, .. } = &mut self.mode {
|
||||||
if let Some(InfoContent::Deletion(idx)) = info_content {
|
if let Some(InfoContent::Deletion(idx)) = info_content {
|
||||||
@ -1997,6 +2026,7 @@ impl CharSelectionUi {
|
|||||||
.map(|info| info.site.clone())
|
.map(|info| info.site.clone())
|
||||||
.collect(),
|
.collect(),
|
||||||
client.world_data().chunk_size().as_(),
|
client.world_data().chunk_size().as_(),
|
||||||
|
client.server_description().rules.is_some(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
@ -49,6 +49,7 @@ impl ClientInit {
|
|||||||
username: String,
|
username: String,
|
||||||
password: String,
|
password: String,
|
||||||
runtime: Arc<runtime::Runtime>,
|
runtime: Arc<runtime::Runtime>,
|
||||||
|
locale: Option<String>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let (tx, rx) = unbounded();
|
let (tx, rx) = unbounded();
|
||||||
let (trust_tx, trust_rx) = unbounded();
|
let (trust_tx, trust_rx) = unbounded();
|
||||||
@ -81,6 +82,7 @@ impl ClientInit {
|
|||||||
&mut mismatched_server_info,
|
&mut mismatched_server_info,
|
||||||
&username,
|
&username,
|
||||||
&password,
|
&password,
|
||||||
|
locale.clone(),
|
||||||
trust_fn,
|
trust_fn,
|
||||||
&|stage| {
|
&|stage| {
|
||||||
let _ = init_stage_tx.send(stage);
|
let _ = init_stage_tx.send(stage);
|
||||||
|
@ -26,6 +26,8 @@ use tokio::runtime;
|
|||||||
use tracing::error;
|
use tracing::error;
|
||||||
use ui::{Event as MainMenuEvent, MainMenuUi};
|
use ui::{Event as MainMenuEvent, MainMenuUi};
|
||||||
|
|
||||||
|
pub use ui::rand_bg_image_spec;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum DetailedInitializationStage {
|
pub enum DetailedInitializationStage {
|
||||||
#[cfg(feature = "singleplayer")]
|
#[cfg(feature = "singleplayer")]
|
||||||
@ -123,6 +125,9 @@ impl PlayState for MainMenuState {
|
|||||||
ConnectionArgs::Mpsc(14004),
|
ConnectionArgs::Mpsc(14004),
|
||||||
&mut self.init,
|
&mut self.init,
|
||||||
&global_state.tokio_runtime,
|
&global_state.tokio_runtime,
|
||||||
|
global_state.settings.language.share_with_server.then_some(
|
||||||
|
global_state.settings.language.selected_language.clone(),
|
||||||
|
),
|
||||||
&global_state.i18n,
|
&global_state.i18n,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -294,6 +299,7 @@ impl PlayState for MainMenuState {
|
|||||||
self.main_menu_ui.connected();
|
self.main_menu_ui.connected();
|
||||||
|
|
||||||
let server_info = client.server_info().clone();
|
let server_info = client.server_info().clone();
|
||||||
|
let server_description = client.server_description().clone();
|
||||||
|
|
||||||
let char_select = CharSelectionState::new(
|
let char_select = CharSelectionState::new(
|
||||||
global_state,
|
global_state,
|
||||||
@ -305,6 +311,8 @@ impl PlayState for MainMenuState {
|
|||||||
self.main_menu_ui.bg_img_spec(),
|
self.main_menu_ui.bg_img_spec(),
|
||||||
char_select,
|
char_select,
|
||||||
server_info,
|
server_info,
|
||||||
|
server_description,
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.map(|s| Box::new(s) as _)
|
.map(|s| Box::new(s) as _)
|
||||||
.unwrap_or_else(|s| Box::new(s) as _);
|
.unwrap_or_else(|s| Box::new(s) as _);
|
||||||
@ -354,6 +362,11 @@ impl PlayState for MainMenuState {
|
|||||||
connection_args,
|
connection_args,
|
||||||
&mut self.init,
|
&mut self.init,
|
||||||
&global_state.tokio_runtime,
|
&global_state.tokio_runtime,
|
||||||
|
global_state
|
||||||
|
.settings
|
||||||
|
.language
|
||||||
|
.share_with_server
|
||||||
|
.then_some(global_state.settings.language.selected_language.clone()),
|
||||||
&global_state.i18n,
|
&global_state.i18n,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -584,6 +597,7 @@ fn attempt_login(
|
|||||||
connection_args: ConnectionArgs,
|
connection_args: ConnectionArgs,
|
||||||
init: &mut InitState,
|
init: &mut InitState,
|
||||||
runtime: &Arc<runtime::Runtime>,
|
runtime: &Arc<runtime::Runtime>,
|
||||||
|
locale: Option<String>,
|
||||||
localized_strings: &LocalizationHandle,
|
localized_strings: &LocalizationHandle,
|
||||||
) {
|
) {
|
||||||
let localization = localized_strings.read();
|
let localization = localized_strings.read();
|
||||||
@ -616,6 +630,7 @@ fn attempt_login(
|
|||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
Arc::clone(runtime),
|
Arc::clone(runtime),
|
||||||
|
locale,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -696,7 +696,7 @@ impl MainMenuUi {
|
|||||||
|
|
||||||
let fonts = Fonts::load(i18n.fonts(), &mut ui).expect("Impossible to load fonts");
|
let fonts = Fonts::load(i18n.fonts(), &mut ui).expect("Impossible to load fonts");
|
||||||
|
|
||||||
let bg_img_spec = BG_IMGS.choose(&mut thread_rng()).unwrap();
|
let bg_img_spec = rand_bg_image_spec();
|
||||||
|
|
||||||
let bg_img = assets::Image::load_expect(bg_img_spec).read().to_image();
|
let bg_img = assets::Image::load_expect(bg_img_spec).read().to_image();
|
||||||
let controls = Controls::new(
|
let controls = Controls::new(
|
||||||
@ -814,3 +814,5 @@ impl MainMenuUi {
|
|||||||
|
|
||||||
pub fn render<'a>(&'a self, drawer: &mut UiDrawer<'_, 'a>) { self.ui.render(drawer); }
|
pub fn render<'a>(&'a self, drawer: &mut UiDrawer<'_, 'a>) { self.ui.render(drawer); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn rand_bg_image_spec() -> &'static str { BG_IMGS.choose(&mut thread_rng()).unwrap() }
|
||||||
|
@ -17,6 +17,7 @@ use common::{
|
|||||||
comp,
|
comp,
|
||||||
};
|
};
|
||||||
use common_base::span;
|
use common_base::span;
|
||||||
|
use common_net::msg::server::ServerDescription;
|
||||||
use i18n::LocalizationHandle;
|
use i18n::LocalizationHandle;
|
||||||
use iced::{
|
use iced::{
|
||||||
button, scrollable, Align, Column, Container, HorizontalAlignment, Length, Row, Scrollable,
|
button, scrollable, Align, Column, Container, HorizontalAlignment, Length, Row, Scrollable,
|
||||||
@ -47,7 +48,8 @@ pub struct Controls {
|
|||||||
decline_button: button::State,
|
decline_button: button::State,
|
||||||
scrollable: scrollable::State,
|
scrollable: scrollable::State,
|
||||||
server_info: ServerInfo,
|
server_info: ServerInfo,
|
||||||
seen_before: bool,
|
server_description: ServerDescription,
|
||||||
|
changed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ServerInfoState {
|
pub struct ServerInfoState {
|
||||||
@ -76,15 +78,18 @@ impl ServerInfoState {
|
|||||||
bg_img_spec: &'static str,
|
bg_img_spec: &'static str,
|
||||||
char_select: CharSelectionState,
|
char_select: CharSelectionState,
|
||||||
server_info: ServerInfo,
|
server_info: ServerInfo,
|
||||||
|
server_description: ServerDescription,
|
||||||
|
force_show: bool,
|
||||||
) -> Result<Self, CharSelectionState> {
|
) -> Result<Self, CharSelectionState> {
|
||||||
let server = global_state.profile.servers.get(&server_info.name);
|
let server = global_state.profile.servers.get(&server_info.name);
|
||||||
|
|
||||||
// If there are no rules, or we've already accepted these rules, we don't need
|
// If there are no rules, or we've already accepted these rules, we don't need
|
||||||
// this state
|
// this state
|
||||||
if server_info.rules.is_none()
|
if (server_description.rules.is_none()
|
||||||
|| server.map_or(false, |s| {
|
|| server.map_or(false, |s| {
|
||||||
s.accepted_rules == Some(rules_hash(&server_info.rules))
|
s.accepted_rules == Some(rules_hash(&server_description.rules))
|
||||||
})
|
}))
|
||||||
|
&& !force_show
|
||||||
{
|
{
|
||||||
return Err(char_select);
|
return Err(char_select);
|
||||||
}
|
}
|
||||||
@ -101,6 +106,11 @@ impl ServerInfoState {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let changed = server.map_or(false, |s| {
|
||||||
|
s.accepted_rules
|
||||||
|
.is_some_and(|accepted| accepted != rules_hash(&server_description.rules))
|
||||||
|
});
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
scene: Scene::new(global_state.window.renderer_mut()),
|
scene: Scene::new(global_state.window.renderer_mut()),
|
||||||
controls: Controls {
|
controls: Controls {
|
||||||
@ -110,12 +120,13 @@ impl ServerInfoState {
|
|||||||
)),
|
)),
|
||||||
imgs: Imgs::load(&mut ui).expect("Failed to load images"),
|
imgs: Imgs::load(&mut ui).expect("Failed to load images"),
|
||||||
fonts: Fonts::load(i18n.fonts(), &mut ui).expect("Impossible to load fonts"),
|
fonts: Fonts::load(i18n.fonts(), &mut ui).expect("Impossible to load fonts"),
|
||||||
i18n: global_state.i18n.clone(),
|
i18n: global_state.i18n,
|
||||||
accept_button: Default::default(),
|
accept_button: Default::default(),
|
||||||
decline_button: Default::default(),
|
decline_button: Default::default(),
|
||||||
scrollable: Default::default(),
|
scrollable: Default::default(),
|
||||||
server_info,
|
server_info,
|
||||||
seen_before: server.map_or(false, |s| s.accepted_rules.is_some()),
|
server_description,
|
||||||
|
changed,
|
||||||
},
|
},
|
||||||
ui,
|
ui,
|
||||||
char_select: Some(char_select),
|
char_select: Some(char_select),
|
||||||
@ -191,6 +202,7 @@ impl PlayState for ServerInfoState {
|
|||||||
&mut global_state.clipboard,
|
&mut global_state.clipboard,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#[allow(clippy::never_loop)] // TODO: Remove when more message types are added
|
||||||
for message in messages {
|
for message in messages {
|
||||||
match message {
|
match message {
|
||||||
Message::Accept => {
|
Message::Accept => {
|
||||||
@ -200,7 +212,8 @@ impl PlayState for ServerInfoState {
|
|||||||
.servers
|
.servers
|
||||||
.get_mut(&self.controls.server_info.name)
|
.get_mut(&self.controls.server_info.name)
|
||||||
{
|
{
|
||||||
server.accepted_rules = Some(rules_hash(&self.controls.server_info.rules));
|
server.accepted_rules =
|
||||||
|
Some(rules_hash(&self.controls.server_description.rules));
|
||||||
}
|
}
|
||||||
|
|
||||||
return PlayStateResult::Switch(Box::new(self.char_select.take().unwrap()));
|
return PlayStateResult::Switch(Box::new(self.char_select.take().unwrap()));
|
||||||
@ -277,18 +290,18 @@ impl Controls {
|
|||||||
elements.push(
|
elements.push(
|
||||||
Container::new(
|
Container::new(
|
||||||
iced::Text::new(i18n.get_msg("main-server-rules"))
|
iced::Text::new(i18n.get_msg("main-server-rules"))
|
||||||
.size(self.fonts.cyri.scale(30))
|
.size(self.fonts.cyri.scale(36))
|
||||||
.horizontal_alignment(HorizontalAlignment::Center),
|
.horizontal_alignment(HorizontalAlignment::Center),
|
||||||
)
|
)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if self.seen_before {
|
if self.changed {
|
||||||
elements.push(
|
elements.push(
|
||||||
Container::new(
|
Container::new(
|
||||||
iced::Text::new(i18n.get_msg("main-server-rules-seen-before"))
|
iced::Text::new(i18n.get_msg("main-server-rules-seen-before"))
|
||||||
.size(self.fonts.cyri.scale(20))
|
.size(self.fonts.cyri.scale(30))
|
||||||
.color(IMPORTANT_TEXT_COLOR)
|
.color(IMPORTANT_TEXT_COLOR)
|
||||||
.horizontal_alignment(HorizontalAlignment::Center),
|
.horizontal_alignment(HorizontalAlignment::Center),
|
||||||
)
|
)
|
||||||
@ -306,8 +319,13 @@ impl Controls {
|
|||||||
elements.push(
|
elements.push(
|
||||||
Scrollable::new(&mut self.scrollable)
|
Scrollable::new(&mut self.scrollable)
|
||||||
.push(
|
.push(
|
||||||
iced::Text::new(self.server_info.rules.as_deref().unwrap_or("<rules>"))
|
iced::Text::new(
|
||||||
.size(self.fonts.cyri.scale(16))
|
self.server_description
|
||||||
|
.rules
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or("<rules>"),
|
||||||
|
)
|
||||||
|
.size(self.fonts.cyri.scale(26))
|
||||||
.width(Length::Shrink)
|
.width(Length::Shrink)
|
||||||
.horizontal_alignment(HorizontalAlignment::Left)
|
.horizontal_alignment(HorizontalAlignment::Left)
|
||||||
.vertical_alignment(VerticalAlignment::Top),
|
.vertical_alignment(VerticalAlignment::Top),
|
||||||
|
@ -161,6 +161,7 @@ pub enum Interface {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum Language {
|
pub enum Language {
|
||||||
ChangeLanguage(Box<LanguageMetadata>),
|
ChangeLanguage(Box<LanguageMetadata>),
|
||||||
|
ToggleShareWithServer(bool),
|
||||||
ToggleEnglishFallback(bool),
|
ToggleEnglishFallback(bool),
|
||||||
}
|
}
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -717,6 +718,9 @@ impl SettingsChange {
|
|||||||
.i18n
|
.i18n
|
||||||
.set_english_fallback(settings.language.use_english_fallback);
|
.set_english_fallback(settings.language.use_english_fallback);
|
||||||
},
|
},
|
||||||
|
Language::ToggleShareWithServer(share) => {
|
||||||
|
settings.language.share_with_server = share;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
SettingsChange::Networking(networking_change) => match networking_change {
|
SettingsChange::Networking(networking_change) => match networking_change {
|
||||||
Networking::AdjustTerrainViewDistance(terrain_vd) => {
|
Networking::AdjustTerrainViewDistance(terrain_vd) => {
|
||||||
|
@ -4,6 +4,8 @@ use serde::{Deserialize, Serialize};
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct LanguageSettings {
|
pub struct LanguageSettings {
|
||||||
pub selected_language: String,
|
pub selected_language: String,
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub share_with_server: bool,
|
||||||
pub use_english_fallback: bool,
|
pub use_english_fallback: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -11,7 +13,10 @@ impl Default for LanguageSettings {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
selected_language: i18n::REFERENCE_LANG.to_string(),
|
selected_language: i18n::REFERENCE_LANG.to_string(),
|
||||||
|
share_with_server: true,
|
||||||
use_english_fallback: true,
|
use_english_fallback: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_true() -> bool { true }
|
||||||
|
Loading…
Reference in New Issue
Block a user