Added rules menu when connecting to server

This commit is contained in:
Joshua Barretto 2024-01-14 18:59:13 +00:00 committed by Maxicarlos08
parent 3d9ab445d9
commit f99bf91617
No known key found for this signature in database
8 changed files with 372 additions and 8 deletions

View File

@ -82,6 +82,8 @@ main-servers-stream_error = Client connection/compression/(de)serialization erro
main-servers-database_error = Server database error: { $raw_error }
main-servers-persistence_error = Server persistence error (Probably Asset/Character Data related): { $raw_error }
main-servers-other_error = Server general error: { $raw_error }
main-server-rules = This server has rules that must be accepted
main-server-rules-seen-before = These rules have changed since the last time you accepted them
main-credits = Credits
main-credits-created_by = created by
main-credits-music = Music
@ -110,4 +112,4 @@ loading-tips =
.a18 = Need more bags or better armor to continue your journey? Press '{ $gameinput-crafting }' to open the crafting menu!
.a19 = Press '{ $gameinput-roll }' to roll. Rolling can be used to move faster and dodge enemy attacks.
.a20 = Wondering what an item is used for? Search 'input:<item name>' in crafting to see what recipes it's used in.
.a21 = You can take screenshots with '{ $gameinput-screenshot }'.
.a21 = You can take screenshots with '{ $gameinput-screenshot }'.

View File

@ -59,6 +59,8 @@ impl CharSelectionState {
})
.unwrap_or_default()
}
pub fn client(&self) -> &RefCell<Client> { &self.client }
}
impl PlayState for CharSelectionState {

View File

@ -1,8 +1,7 @@
mod client_init;
mod scene;
mod ui;
use super::char_selection::CharSelectionState;
use super::{char_selection::CharSelectionState, dummy_scene::Scene, server_info::ServerInfoState};
#[cfg(feature = "singleplayer")]
use crate::singleplayer::SingleplayerState;
use crate::{
@ -20,7 +19,6 @@ use client_init::{ClientInit, Error as InitError, Msg as InitMsg};
use common::comp;
use common_base::span;
use i18n::LocalizationHandle;
use scene::Scene;
#[cfg(feature = "singleplayer")]
use server::ServerInitStage;
use std::sync::Arc;
@ -294,10 +292,24 @@ impl PlayState for MainMenuState {
core::mem::replace(&mut self.init, InitState::None)
{
self.main_menu_ui.connected();
return PlayStateResult::Push(Box::new(CharSelectionState::new(
let server_info = client.server_info().clone();
let char_select = CharSelectionState::new(
global_state,
std::rc::Rc::new(std::cell::RefCell::new(*client)),
)));
);
let new_state = ServerInfoState::try_from_server_info(
global_state,
self.main_menu_ui.bg_img_spec(),
char_select,
server_info,
)
.map(|s| Box::new(s) as _)
.unwrap_or_else(|s| Box::new(s) as _);
return PlayStateResult::Push(new_state);
}
}
}

View File

@ -207,7 +207,7 @@ impl Showing {
}
}
struct Controls {
pub struct Controls {
fonts: Fonts,
imgs: Imgs,
bg_img: widget::image::Handle,
@ -677,6 +677,7 @@ pub struct MainMenuUi {
// TODO: re add this
// tip_no: u16,
controls: Controls,
bg_img_spec: &'static str,
}
impl MainMenuUi {
@ -707,9 +708,15 @@ impl MainMenuUi {
server,
);
Self { ui, controls }
Self {
ui,
controls,
bg_img_spec,
}
}
pub fn bg_img_spec(&self) -> &'static str { self.bg_img_spec }
pub fn update_language(&mut self, i18n: LocalizationHandle, settings: &Settings) {
self.controls.i18n = i18n;
let i18n = &i18n.read();

View File

@ -1,2 +1,4 @@
pub mod char_selection;
pub mod dummy_scene;
pub mod main;
pub mod server_info;

View File

@ -0,0 +1,336 @@
use super::{char_selection::CharSelectionState, dummy_scene::Scene};
use crate::{
render::{Drawer, GlobalsBindGroup},
settings::Settings,
ui::{
fonts::IcedFonts as Fonts,
ice::{component::neat_button, load_font, style, widget, Element, IcedUi as Ui},
img_ids::ImageGraphic,
Graphic,
},
window::{self, Event},
Direction, GlobalState, PlayState, PlayStateResult,
};
use client::ServerInfo;
use common::assets::{self, AssetExt};
use common_base::span;
use i18n::LocalizationHandle;
use iced::{
button, scrollable, Align, Column, Container, HorizontalAlignment, Length, Row, Scrollable,
VerticalAlignment,
};
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};
image_ids_ice! {
struct Imgs {
<ImageGraphic>
button: "voxygen.element.ui.generic.buttons.button",
button_hover: "voxygen.element.ui.generic.buttons.button_hover",
button_press: "voxygen.element.ui.generic.buttons.button_press",
}
}
pub struct Controls {
fonts: Fonts,
imgs: Imgs,
i18n: LocalizationHandle,
bg_img: widget::image::Handle,
accept_button: button::State,
decline_button: button::State,
scrollable: scrollable::State,
server_info: ServerInfo,
seen_before: bool,
}
pub struct ServerInfoState {
ui: Ui,
scene: Scene,
controls: Controls,
char_select: Option<CharSelectionState>,
}
#[derive(Clone)]
pub enum Message {
Accept,
Decline,
}
fn rules_hash(rules: &Option<String>) -> u64 {
let mut hasher = DefaultHasher::default();
rules.hash(&mut hasher);
hasher.finish()
}
impl ServerInfoState {
/// Create a new `MainMenuState`.
pub fn try_from_server_info(
global_state: &mut GlobalState,
bg_img_spec: &'static str,
char_select: CharSelectionState,
server_info: ServerInfo,
) -> Result<Self, CharSelectionState> {
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
// this state
if server_info.rules.is_none()
|| server.map_or(false, |s| {
s.accepted_rules == Some(rules_hash(&server_info.rules))
})
{
return Err(char_select);
}
// Load language
let i18n = &global_state.i18n.read();
// TODO: don't add default font twice
let font = load_font(&i18n.fonts().get("cyri").unwrap().asset_key);
let mut ui = Ui::new(
&mut global_state.window,
font,
global_state.settings.interface.ui_scale,
)
.unwrap();
Ok(Self {
scene: Scene::new(global_state.window.renderer_mut()),
controls: Controls {
bg_img: ui.add_graphic(Graphic::Image(
assets::Image::load_expect(bg_img_spec).read().to_image(),
None,
)),
imgs: Imgs::load(&mut ui).expect("Failed to load images"),
fonts: Fonts::load(i18n.fonts(), &mut ui).expect("Impossible to load fonts"),
i18n: global_state.i18n.clone(),
accept_button: Default::default(),
decline_button: Default::default(),
scrollable: Default::default(),
server_info,
seen_before: server.map_or(false, |s| s.accepted_rules.is_some()),
},
ui,
char_select: Some(char_select),
})
}
fn handle_event(&mut self, event: window::Event) -> bool {
match event {
// Pass events to ui.
window::Event::IcedUi(event) => {
self.ui.handle_event(event);
true
},
window::Event::ScaleFactorChanged(s) => {
self.ui.scale_factor_changed(s);
false
},
_ => false,
}
}
}
impl PlayState for ServerInfoState {
fn enter(&mut self, _global_state: &mut GlobalState, _: Direction) {
/*
// Updated localization in case the selected language was changed
self.main_menu_ui
.update_language(global_state.i18n, &global_state.settings);
// Set scale mode in case it was change
self.main_menu_ui
.set_scale_mode(global_state.settings.interface.ui_scale);
*/
}
#[allow(clippy::single_match)] // TODO: remove when event match has multiple arms
fn tick(&mut self, global_state: &mut GlobalState, events: Vec<Event>) -> PlayStateResult {
span!(_guard, "tick", "<ServerInfoState as PlayState>::tick");
// Handle window events.
for event in events {
// Pass all events to the ui first.
if self.handle_event(event.clone()) {
continue;
}
match event {
Event::Close => return PlayStateResult::Shutdown,
// Ignore all other events.
_ => {},
}
}
// Maintain the UI.
let view = self.controls.view();
let (messages, _) = self.ui.maintain(
view,
global_state.window.renderer_mut(),
None,
&mut global_state.clipboard,
);
for message in messages {
match message {
Message::Accept => {
// Update last-accepted rules hash so we don't see the message again
if let Some(server) = global_state
.profile
.servers
.get_mut(&self.controls.server_info.name)
{
server.accepted_rules = Some(rules_hash(&self.controls.server_info.rules));
}
return PlayStateResult::Switch(Box::new(self.char_select.take().unwrap()));
},
Message::Decline => return PlayStateResult::Pop,
}
}
PlayStateResult::Continue
}
fn name(&self) -> &'static str { "Server Info" }
fn capped_fps(&self) -> bool { true }
fn globals_bind_group(&self) -> &GlobalsBindGroup { self.scene.global_bind_group() }
fn render(&self, drawer: &mut Drawer<'_>, _: &Settings) {
// Draw the UI to the screen.
let mut third_pass = drawer.third_pass();
if let Some(mut ui_drawer) = third_pass.draw_ui() {
self.ui.render(&mut ui_drawer);
};
}
fn egui_enabled(&self) -> bool { false }
}
impl Controls {
fn view(&mut self) -> Element<Message> {
pub const TEXT_COLOR: iced::Color = iced::Color::from_rgb(1.0, 1.0, 1.0);
pub const IMPORTANT_TEXT_COLOR: iced::Color = iced::Color::from_rgb(1.0, 0.85, 0.5);
pub const DISABLED_TEXT_COLOR: iced::Color = iced::Color::from_rgba(1.0, 1.0, 1.0, 0.2);
pub const FILL_FRAC_ONE: f32 = 0.67;
let i18n = self.i18n.read();
// TODO: consider setting this as the default in the renderer
let button_style = style::button::Style::new(self.imgs.button)
.hover_image(self.imgs.button_hover)
.press_image(self.imgs.button_press)
.text_color(TEXT_COLOR)
.disabled_text_color(DISABLED_TEXT_COLOR);
let accept_button = Container::new(
Container::new(neat_button(
&mut self.accept_button,
i18n.get_msg("common-accept"),
FILL_FRAC_ONE,
button_style,
Some(Message::Accept),
))
.max_width(200),
)
.width(Length::Fill)
.align_x(Align::Center);
let decline_button = Container::new(
Container::new(neat_button(
&mut self.decline_button,
i18n.get_msg("common-decline"),
FILL_FRAC_ONE,
button_style,
Some(Message::Decline),
))
.max_width(200),
)
.width(Length::Fill)
.align_x(Align::Center);
let mut elements = Vec::new();
elements.push(
Container::new(
iced::Text::new(i18n.get_msg("main-server-rules"))
.size(self.fonts.cyri.scale(30))
.horizontal_alignment(HorizontalAlignment::Center),
)
.width(Length::Fill)
.into(),
);
if self.seen_before {
elements.push(
Container::new(
iced::Text::new(i18n.get_msg("main-server-rules-seen-before"))
.size(self.fonts.cyri.scale(20))
.color(IMPORTANT_TEXT_COLOR)
.horizontal_alignment(HorizontalAlignment::Center),
)
.width(Length::Fill)
.into(),
);
}
// elements.push(iced::Text::new(format!("{}: {}", self.server_info.name,
// self.server_info.description)) .size(self.fonts.cyri.scale(20))
// .width(Length::Shrink)
// .horizontal_alignment(HorizontalAlignment::Center)
// .into());
elements.push(
Scrollable::new(&mut self.scrollable)
.push(
iced::Text::new(self.server_info.rules.as_deref().unwrap_or("<rules>"))
.size(self.fonts.cyri.scale(16))
.width(Length::Shrink)
.horizontal_alignment(HorizontalAlignment::Left)
.vertical_alignment(VerticalAlignment::Top),
)
.height(Length::Fill)
.width(Length::Fill)
.into(),
);
elements.push(
Row::with_children(vec![decline_button.into(), accept_button.into()])
.width(Length::Shrink)
.height(Length::Shrink)
.padding(25)
.into(),
);
Container::new(
Container::new(
Column::with_children(elements)
.spacing(10)
.padding(20),
)
.style(
style::container::Style::color_with_double_cornerless_border(
(22, 18, 16, 255).into(),
(11, 11, 11, 255).into(),
(54, 46, 38, 255).into(),
),
)
.max_width(1000)
.align_x(Align::Center)
// .width(Length::Shrink)
// .height(Length::Shrink)
.padding(15),
)
.style(style::container::Style::image(self.bg_img))
.width(Length::Fill)
.height(Length::Fill)
.align_x(Align::Center)
.padding(50)
.into()
}
}

View File

@ -39,6 +39,8 @@ pub struct ServerProfile {
pub selected_character: Option<CharacterId>,
/// Last spectate position
pub spectate_position: Option<vek::Vec3<f32>>,
/// Hash of left-accepted server rules
pub accepted_rules: Option<u64>,
}
impl Default for ServerProfile {
@ -47,6 +49,7 @@ impl Default for ServerProfile {
characters: HashMap::new(),
selected_character: None,
spectate_position: None,
accepted_rules: None,
}
}
}