mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Merge pull request #2970 from kacpherek/fix/multiplayer-safety-notice
Show the multiplayer safety notice on the first Multiplayer click
This commit is contained in:
7 files changed
+163
-7
No files matched your search
@@ -9716,6 +9716,7 @@ impl Game<'_> {
|
||||
));
|
||||
if let Some(title) = &mut self.title {
|
||||
title.last_server = self.settings.file.string("lastServer");
|
||||
title.skip_multiplayer_warning = self.settings.file.bool("skipMultiplayerWarning");
|
||||
}
|
||||
self.capture(false);
|
||||
Ok(())
|
||||
@@ -15421,6 +15422,7 @@ fn start_game<'a>(
|
||||
&game.options.resources,
|
||||
);
|
||||
title.last_server = game.settings.file.string("lastServer");
|
||||
title.skip_multiplayer_warning = game.settings.file.bool("skipMultiplayerWarning");
|
||||
game.title = Some(title);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -611,6 +611,9 @@ impl Game<'_> {
|
||||
json!({ "mode": "multiplayer", "disconnected": reason })
|
||||
}
|
||||
Mode::Multiplayer(_) => json!({ "mode": "multiplayer" }),
|
||||
Mode::MultiplayerWarning { check } => {
|
||||
json!({ "mode": "multiplayer-warning", "check": check })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,12 +19,18 @@ pub(super) const CONTROL_HELP: &[&str] = &[
|
||||
impl Game<'_> {
|
||||
pub(super) fn save_title_options(&mut self) {
|
||||
if let Some(title) = &self.title
|
||||
&& title.last_server != self.settings.file.string("lastServer")
|
||||
&& (title.last_server != self.settings.file.string("lastServer")
|
||||
|| title.skip_multiplayer_warning
|
||||
!= self.settings.file.bool("skipMultiplayerWarning"))
|
||||
{
|
||||
self.settings.file.set(
|
||||
"lastServer",
|
||||
horizon_client::options_file::V::Str(title.last_server.clone()),
|
||||
);
|
||||
self.settings.file.set(
|
||||
"skipMultiplayerWarning",
|
||||
horizon_client::options_file::V::Bool(title.skip_multiplayer_warning),
|
||||
);
|
||||
self.settings.save(&Self::options_path());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,7 +325,9 @@ impl Game<'_> {
|
||||
&self.options.resources,
|
||||
));
|
||||
}
|
||||
let skip = self.settings.file.bool("skipMultiplayerWarning");
|
||||
if let Some(title) = &mut self.title {
|
||||
title.skip_multiplayer_warning = skip;
|
||||
title.failed(error);
|
||||
}
|
||||
self.capture(false);
|
||||
@@ -608,6 +610,7 @@ impl Game<'_> {
|
||||
let saves = std::env::current_dir()?.join("saves");
|
||||
let mut title = horizon_client::title::Title::new(saves.clone(), &self.options.resources);
|
||||
title.last_server = self.settings.file.string("lastServer");
|
||||
title.skip_multiplayer_warning = self.settings.file.bool("skipMultiplayerWarning");
|
||||
if single_player && reason.is_none() {
|
||||
self.title = Some(title);
|
||||
self.capture(false);
|
||||
|
||||
@@ -76,6 +76,7 @@ impl StartupMenu {
|
||||
&self.root,
|
||||
);
|
||||
title.last_server = settings.file.string("lastServer");
|
||||
title.skip_multiplayer_warning = settings.file.bool("skipMultiplayerWarning");
|
||||
self.title = Some(title);
|
||||
}
|
||||
}
|
||||
@@ -320,12 +321,11 @@ impl Loading {
|
||||
}
|
||||
|
||||
fn save_menu_address(&mut self) {
|
||||
if self
|
||||
.menu
|
||||
.title
|
||||
.as_ref()
|
||||
.is_some_and(|title| title.last_server != self.settings.file.string("lastServer"))
|
||||
{
|
||||
if self.menu.title.as_ref().is_some_and(|title| {
|
||||
title.last_server != self.settings.file.string("lastServer")
|
||||
|| title.skip_multiplayer_warning
|
||||
!= self.settings.file.bool("skipMultiplayerWarning")
|
||||
}) {
|
||||
self.save_menu_settings();
|
||||
}
|
||||
}
|
||||
@@ -336,6 +336,10 @@ impl Loading {
|
||||
"lastServer",
|
||||
horizon_client::options_file::V::Str(title.last_server.clone()),
|
||||
);
|
||||
self.settings.file.set(
|
||||
"skipMultiplayerWarning",
|
||||
horizon_client::options_file::V::Bool(title.skip_multiplayer_warning),
|
||||
);
|
||||
}
|
||||
self.settings.save(&Game::options_path());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Title, world selection and world creation screens (TitleScreen,
|
||||
//! SelectWorldScreen, CreateWorldScreen layouts), drawn as HUD quads.
|
||||
use crate::{
|
||||
dialog_screen::BOX,
|
||||
game_rules_screen::{Exit, RulesScreen},
|
||||
hud::{HudAtlas, HudBatch, rgba},
|
||||
lang,
|
||||
@@ -33,6 +34,12 @@ fn edit_top(h: f32) -> f32 {
|
||||
pub(crate) fn confirm_top(h: f32, lines: usize) -> f32 {
|
||||
((h.ceil() - 61.0 - lines as f32 * 9.0) / 2.0).floor()
|
||||
}
|
||||
/// The SafetyScreen's column (WarningScreen: title, 12 px around the message
|
||||
/// box of its lines and 4 px padding, the checkbox, then the buttons, 8 px
|
||||
/// apart): the title's top.
|
||||
fn warning_top(h: f32, lines: usize) -> f32 {
|
||||
((h.ceil() - 102.0 - lines as f32 * 9.0) / 2.0).floor()
|
||||
}
|
||||
|
||||
fn translate(key: &str, fallback: &str, args: &[&dyn std::fmt::Display]) -> String {
|
||||
let args: Vec<Text> = args
|
||||
@@ -119,6 +126,11 @@ pub enum Mode {
|
||||
return_to: Box<Mode>,
|
||||
},
|
||||
Multiplayer(crate::multiplayer::Screen),
|
||||
/// SafetyScreen: the warning before the first Multiplayer click, with its
|
||||
/// "Do not show this screen again" box.
|
||||
MultiplayerWarning {
|
||||
check: bool,
|
||||
},
|
||||
/// CreateFlatWorldScreen over Create New World: the fields that screen
|
||||
/// keeps and the superflat editor on top of them.
|
||||
CreateFlat {
|
||||
@@ -160,6 +172,8 @@ pub struct Title {
|
||||
pub mode: Mode,
|
||||
/// Direct Connection's address, synchronized with `options.txt`'s `lastServer`.
|
||||
pub last_server: String,
|
||||
/// `skipMultiplayerWarning`, synchronized with `options.txt` the same way.
|
||||
pub skip_multiplayer_warning: bool,
|
||||
pub saves: PathBuf,
|
||||
/// The prepared resource root: the flat preset list and block registry.
|
||||
resources: PathBuf,
|
||||
@@ -171,6 +185,8 @@ pub struct Title {
|
||||
scroll: Cell<crate::ui::Scroll>,
|
||||
create_return_to_main: bool,
|
||||
message_lines: Cell<usize>,
|
||||
/// The SafetyScreen's checkbox as last drawn: x, y, width, height.
|
||||
warning_check: Cell<[f32; 4]>,
|
||||
backup: Option<mpsc::Receiver<Result<u64, String>>>,
|
||||
backup_shown: Cell<bool>,
|
||||
/// The backup a `BackupBeforeUpgrade` asked for opens its world when done.
|
||||
@@ -297,6 +313,7 @@ impl Title {
|
||||
Self {
|
||||
mode,
|
||||
last_server: String::new(),
|
||||
skip_multiplayer_warning: false,
|
||||
saves,
|
||||
resources: resources.to_owned(),
|
||||
started: now,
|
||||
@@ -306,6 +323,7 @@ impl Title {
|
||||
scroll: Cell::default(),
|
||||
create_return_to_main: false,
|
||||
message_lines: Cell::new(1),
|
||||
warning_check: Cell::new([0.0; 4]),
|
||||
backup: None,
|
||||
backup_shown: Cell::new(false),
|
||||
backup_then_open: false,
|
||||
@@ -1034,6 +1052,28 @@ impl Title {
|
||||
),
|
||||
]
|
||||
}
|
||||
Mode::MultiplayerWarning { .. } => {
|
||||
let lines = self.message_lines.get();
|
||||
let y = warning_top(h, lines) + 82.0 + lines as f32 * 9.0;
|
||||
vec![
|
||||
b(
|
||||
(w / 2.0).floor() - 154.0,
|
||||
y,
|
||||
150.0,
|
||||
&lang::get_or("gui.proceed", "Proceed"),
|
||||
true,
|
||||
70,
|
||||
),
|
||||
b(
|
||||
(w / 2.0).floor() + 4.0,
|
||||
y,
|
||||
150.0,
|
||||
&lang::get_or("gui.back", "Back"),
|
||||
true,
|
||||
71,
|
||||
),
|
||||
]
|
||||
}
|
||||
Mode::Backup { .. } => Vec::new(),
|
||||
Mode::Multiplayer(screen) => screen
|
||||
.buttons(w, h)
|
||||
@@ -1365,6 +1405,74 @@ impl Title {
|
||||
centered(atlas, batch, line, top + 17.0 + i as f32 * 9.0, u32::MAX);
|
||||
}
|
||||
}
|
||||
Mode::MultiplayerWarning { check } => {
|
||||
crate::ui::menu_background(batch, atlas, 0.0, 0.0, w, h, false);
|
||||
let message = lang::get_or(
|
||||
"multiplayerWarning.message",
|
||||
"Caution: Online play is offered by third-party servers that are not owned, operated, or supervised by Mojang Studios or Microsoft. During online play, you may be exposed to unmoderated chat messages or other types of user-generated content that may not be suitable for everyone.",
|
||||
);
|
||||
// The message sits in a text box `width - 100` wide with 4 px
|
||||
// inside, left aligned (FittingMultiLineTextWidget).
|
||||
let box_w = w - 100.0;
|
||||
let lines = wrapped(atlas, &message, box_w - 8.0);
|
||||
self.message_lines.set(lines.len());
|
||||
let top = warning_top(h, lines.len());
|
||||
let header = Text::translate("multiplayerWarning.header", &[]).bold();
|
||||
batch.text_styled(
|
||||
atlas,
|
||||
&header,
|
||||
(w / 2.0).floor() - (atlas.font.width_text(&header) / 2.0).floor(),
|
||||
top,
|
||||
u32::MAX,
|
||||
true,
|
||||
);
|
||||
let box_x = ((w - box_w) / 2.0).floor();
|
||||
let box_y = top + 29.0;
|
||||
let box_h = lines.len() as f32 * 9.0 + 8.0;
|
||||
crate::ui::nine_slice(
|
||||
batch,
|
||||
atlas,
|
||||
"gui/sprites/widget/text_field",
|
||||
box_x,
|
||||
box_y,
|
||||
box_w,
|
||||
box_h,
|
||||
u32::MAX,
|
||||
);
|
||||
for (i, line) in lines.iter().enumerate() {
|
||||
batch.text(
|
||||
atlas,
|
||||
line,
|
||||
box_x + 4.0,
|
||||
box_y + 4.0 + i as f32 * 9.0,
|
||||
u32::MAX,
|
||||
true,
|
||||
);
|
||||
}
|
||||
// Checkbox: the box, its label right of it (as the dialogs draw theirs).
|
||||
let label =
|
||||
lang::get_or("multiplayerWarning.check", "Do not show this screen again");
|
||||
let width = BOX + 4.0 + HudBatch::text_width(atlas, &label).ceil();
|
||||
let x = (w / 2.0).floor() - (width / 2.0).floor();
|
||||
let y = top + 57.0 + lines.len() as f32 * 9.0;
|
||||
self.warning_check.set([x, y, width, BOX]);
|
||||
let hover = (x..x + width).contains(&cursor.0) && (y..y + BOX).contains(&cursor.1);
|
||||
let sprite = match (*check, hover) {
|
||||
(true, true) => "gui/sprites/widget/checkbox_selected_highlighted",
|
||||
(true, false) => "gui/sprites/widget/checkbox_selected",
|
||||
(false, true) => "gui/sprites/widget/checkbox_highlighted",
|
||||
(false, false) => "gui/sprites/widget/checkbox",
|
||||
};
|
||||
batch.sprite_scaled(atlas, sprite, x, y, BOX, BOX, u32::MAX);
|
||||
batch.text(
|
||||
atlas,
|
||||
&label,
|
||||
x + BOX + 4.0,
|
||||
y + 4.0,
|
||||
rgba(224, 224, 224, 255),
|
||||
true,
|
||||
);
|
||||
}
|
||||
Mode::Multiplayer(screen) => screen.draw(
|
||||
atlas,
|
||||
batch,
|
||||
@@ -1471,6 +1579,14 @@ impl Title {
|
||||
}
|
||||
return (Action::None, changed);
|
||||
}
|
||||
// The SafetyScreen's box toggles where it was last drawn.
|
||||
if let Mode::MultiplayerWarning { check } = &mut self.mode {
|
||||
let [bx, by, bw, bh] = self.warning_check.get();
|
||||
if (bx..bx + bw).contains(&x) && (by..by + bh).contains(&y) {
|
||||
*check = !*check;
|
||||
return (Action::None, true);
|
||||
}
|
||||
}
|
||||
let hit = self
|
||||
.buttons(w, h)
|
||||
.into_iter()
|
||||
@@ -1493,9 +1609,26 @@ impl Title {
|
||||
}
|
||||
(Mode::Main, Some(2)) => Action::Quit,
|
||||
(Mode::Main, Some(4)) => {
|
||||
// TitleScreen.init: SafetyScreen first, unless it was turned off.
|
||||
self.mode = if self.skip_multiplayer_warning {
|
||||
Mode::Multiplayer(crate::multiplayer::Screen::list(&self.saves))
|
||||
} else {
|
||||
Mode::MultiplayerWarning { check: false }
|
||||
};
|
||||
Action::None
|
||||
}
|
||||
(Mode::MultiplayerWarning { check }, Some(70)) => {
|
||||
// SafetyScreen Proceed: the box saves the choice.
|
||||
if *check {
|
||||
self.skip_multiplayer_warning = true;
|
||||
}
|
||||
self.mode = Mode::Multiplayer(crate::multiplayer::Screen::list(&self.saves));
|
||||
Action::None
|
||||
}
|
||||
(Mode::MultiplayerWarning { .. }, Some(71)) => {
|
||||
self.mode = Mode::Main;
|
||||
Action::None
|
||||
}
|
||||
(Mode::Multiplayer(_), hit) => match self.multiplayer_click(hit, x, y, (w, h)) {
|
||||
crate::multiplayer::Outcome::Back => {
|
||||
self.mode = Mode::Main;
|
||||
|
||||
@@ -88,6 +88,9 @@ fn direct_connection_remembers_the_address_on_cancel_escape_and_join() {
|
||||
let mut f = Fixture::new();
|
||||
f.title.last_server = "localhost:25566".into();
|
||||
f.click("Multiplayer");
|
||||
// SafetyScreen comes first while its box was never ticked.
|
||||
assert!(matches!(f.title.mode, Mode::MultiplayerWarning { .. }));
|
||||
f.click("Proceed");
|
||||
f.click("Direct Connection");
|
||||
assert_eq!(f.title.focused_field().unwrap().1, "localhost:25566");
|
||||
|
||||
@@ -100,6 +103,7 @@ fn direct_connection_remembers_the_address_on_cancel_escape_and_join() {
|
||||
f.title.escape();
|
||||
f.title.escape();
|
||||
f.click("Multiplayer");
|
||||
f.click("Proceed");
|
||||
f.click("Direct Connection");
|
||||
assert_eq!(f.title.focused_field().unwrap().1, "[::1]:25568");
|
||||
|
||||
@@ -122,6 +126,7 @@ fn direct_connection_remembers_the_address_on_cancel_escape_and_join() {
|
||||
fn f5_reloads_the_server_list() {
|
||||
let mut f = Fixture::new();
|
||||
f.click("Multiplayer");
|
||||
f.click("Proceed");
|
||||
let file = crate::multiplayer::servers_file(&f.title.saves);
|
||||
horizon_protocol::servers::save(
|
||||
&file,
|
||||
|
||||
Reference in new issue
Block a user