Feat: Day duration slider on map creation UI (#1909)

This commit is contained in:
Joaquin Tornello 2024-01-03 22:21:53 +00:00 committed by Isse
parent c37f0d4c90
commit 0ecfbce4d2
8 changed files with 115 additions and 27 deletions

View File

@ -40,6 +40,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added hit_timing to BasicMelee abilities - Added hit_timing to BasicMelee abilities
- A tavern building where npcs go to relax. - A tavern building where npcs go to relax.
- Toggle for walking instead of running (Default: `I`). - Toggle for walking instead of running (Default: `I`).
- Added day duration slider configuration on map creation UI.
### Changed ### Changed

View File

@ -7,30 +7,30 @@ main-tip = Tip:
main-unbound_key_tip = unbound main-unbound_key_tip = unbound
main-notice = main-notice =
Welcome to the alpha version of Veloren! Welcome to the alpha version of Veloren!
Before you dive into the fun, please keep a few things in mind: Before you dive into the fun, please keep a few things in mind:
- This is a very early alpha. Expect bugs, extremely unfinished gameplay, unpolished mechanics, and missing features. - This is a very early alpha. Expect bugs, extremely unfinished gameplay, unpolished mechanics, and missing features.
- If you have constructive feedback or bug reports, you can contact us via Reddit, GitLab, or our community Discord server. - If you have constructive feedback or bug reports, you can contact us via Reddit, GitLab, or our community Discord server.
- Veloren is licensed under the GPL 3 open-source licence. That means you're free to play, modify, and redistribute the game however - Veloren is licensed under the GPL 3 open-source licence. That means you're free to play, modify, and redistribute the game however
you wish (provided derived work is also under GPL 3). you wish (provided derived work is also under GPL 3).
- Veloren is a non-profit community project, and everybody working on it is a volunteer. - Veloren is a non-profit community project, and everybody working on it is a volunteer.
If you like what you see, you're welcome to join the development or art teams! If you like what you see, you're welcome to join the development or art teams!
Thanks for taking the time to read this notice, we hope you enjoy the game! Thanks for taking the time to read this notice, we hope you enjoy the game!
~ The Veloren Devs ~ The Veloren Devs
main-login_process = main-login_process =
Information on the Login Process: Information on the Login Process:
Please note that you need an account Please note that you need an account
to play on auth-enabled servers. to play on auth-enabled servers.
You can create an account over at You can create an account over at
https://veloren.net/account/. https://veloren.net/account/.
main-singleplayer-new = New main-singleplayer-new = New
main-singleplayer-delete = Delete main-singleplayer-delete = Delete
@ -38,6 +38,7 @@ main-singleplayer-regenerate = Regenerate
main-singleplayer-create_custom = Create Custom main-singleplayer-create_custom = Create Custom
main-singleplayer-invalid_name = Error: Invalid name main-singleplayer-invalid_name = Error: Invalid name
main-singleplayer-seed = Seed main-singleplayer-seed = Seed
main-singleplayer-day_length = Day duration
main-singleplayer-random_seed = Random main-singleplayer-random_seed = Random
main-singleplayer-size_lg = Logarithmic size main-singleplayer-size_lg = Logarithmic size
main-singleplayer-map_large_warning = Warning: Large worlds will take a long time to start for the first time main-singleplayer-map_large_warning = Warning: Large worlds will take a long time to start for the first time

View File

@ -35,3 +35,6 @@ pub const ENERGY_PER_LEVEL: u16 = 5;
pub const HP_PER_LEVEL: u16 = 5; pub const HP_PER_LEVEL: u16 = 5;
pub const TELEPORTER_RADIUS: f32 = 3.; pub const TELEPORTER_RADIUS: f32 = 3.;
// Map settings
pub const DAY_LENGTH_DEFAULT: f64 = 30.0;

View File

@ -16,6 +16,7 @@ pub use whitelist::{Whitelist, WhitelistInfo, WhitelistRecord};
use chrono::Utc; use chrono::Utc;
use common::{ use common::{
calendar::{Calendar, CalendarEvent}, calendar::{Calendar, CalendarEvent},
consts::DAY_LENGTH_DEFAULT,
resources::BattleMode, resources::BattleMode,
rtsim::WorldSettings, rtsim::WorldSettings,
}; };
@ -205,7 +206,7 @@ impl Default for Settings {
world_seed: DEFAULT_WORLD_SEED, world_seed: DEFAULT_WORLD_SEED,
server_name: "Veloren Server".into(), server_name: "Veloren Server".into(),
max_players: 100, max_players: 100,
day_length: 30.0, day_length: DAY_LENGTH_DEFAULT,
start_time: 9.0 * 3600.0, start_time: 9.0 * 3600.0,
map_file: None, map_file: None,
max_view_distance: Some(65), max_view_distance: Some(65),

View File

@ -92,6 +92,7 @@ const BG_IMGS: [&str; 14] = [
pub enum WorldChange { pub enum WorldChange {
Name(String), Name(String),
Seed(u32), Seed(u32),
DayLength(f64),
SizeX(u32), SizeX(u32),
SizeY(u32), SizeY(u32),
Scale(f64), Scale(f64),
@ -108,6 +109,7 @@ impl WorldChange {
match self { match self {
WorldChange::Name(name) => world.name = name, WorldChange::Name(name) => world.name = name,
WorldChange::Seed(seed) => world.seed = seed, WorldChange::Seed(seed) => world.seed = seed,
WorldChange::DayLength(d) => world.day_length = d,
WorldChange::SizeX(s) => gen_opts.x_lg = s, WorldChange::SizeX(s) => gen_opts.x_lg = s,
WorldChange::SizeY(s) => gen_opts.y_lg = s, WorldChange::SizeY(s) => gen_opts.y_lg = s,
WorldChange::Scale(scale) => gen_opts.scale = scale, WorldChange::Scale(scale) => gen_opts.scale = scale,

View File

@ -47,6 +47,7 @@ pub struct Screen {
world_name: text_input::State, world_name: text_input::State,
map_seed: text_input::State, map_seed: text_input::State,
day_length: slider::State,
random_seed_button: button::State, random_seed_button: button::State,
world_size_x: slider::State, world_size_x: slider::State,
world_size_y: slider::State, world_size_y: slider::State,
@ -196,6 +197,9 @@ impl Screen {
const SLIDER_BAR_PAD: u16 = 0; const SLIDER_BAR_PAD: u16 = 0;
// Height of interactable area // Height of interactable area
const SLIDER_HEIGHT: u16 = 30; const SLIDER_HEIGHT: u16 = 30;
// Day length slider values
pub const DAY_LENGTH_MIN: f64 = 10.0;
pub const DAY_LENGTH_MAX: f64 = 60.0;
let mut gen_content = vec![ let mut gen_content = vec![
BackgroundContainer::new( BackgroundContainer::new(
@ -288,6 +292,42 @@ impl Screen {
gen_content.push(Row::with_children(seed_content).into()); gen_content.push(Row::with_children(seed_content).into());
if let Some(gen_opts) = world.gen_opts.as_ref() { if let Some(gen_opts) = world.gen_opts.as_ref() {
// Day length setting label
gen_content.push(
Text::new(format!(
"{}: {}",
i18n.get_msg("main-singleplayer-day_length"),
world.day_length
))
.size(SLIDER_TEXT_SIZE)
.horizontal_alignment(iced::HorizontalAlignment::Center)
.into(),
);
// Day length setting slider
if can_edit {
gen_content.push(
Row::with_children(vec![
Slider::new(
&mut self.day_length,
DAY_LENGTH_MIN..=DAY_LENGTH_MAX,
world.day_length,
move |d| message(WorldChange::DayLength(d)),
)
.height(SLIDER_HEIGHT)
.style(style::slider::Style::images(
imgs.slider_indicator,
imgs.slider_range,
SLIDER_BAR_PAD,
SLIDER_CURSOR_SIZE,
SLIDER_BAR_HEIGHT,
))
.into(),
])
.into(),
)
}
gen_content.push( gen_content.push(
Text::new(format!( Text::new(format!(
"{}: x: {}, y: {}", "{}: x: {}, y: {}",

View File

@ -87,6 +87,7 @@ impl SingleplayerState {
settings.map_file = Some(file_opts); settings.map_file = Some(file_opts);
settings.world_seed = world.seed; settings.world_seed = world.seed;
settings.day_length = world.day_length;
let (stop_server_s, stop_server_r) = unbounded(); let (stop_server_s, stop_server_r) = unbounded();

View File

@ -1,9 +1,10 @@
use std::{ use std::{
fs, fs,
io::Read,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use common::assets::ASSETS_PATH; use common::{assets::ASSETS_PATH, consts::DAY_LENGTH_DEFAULT};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use server::{FileOpts, GenOpts, DEFAULT_WORLD_MAP}; use server::{FileOpts, GenOpts, DEFAULT_WORLD_MAP};
use tracing::error; use tracing::error;
@ -18,6 +19,7 @@ struct World0 {
pub struct SingleplayerWorld { pub struct SingleplayerWorld {
pub name: String, pub name: String,
pub gen_opts: Option<GenOpts>, pub gen_opts: Option<GenOpts>,
pub day_length: f64,
pub seed: u32, pub seed: u32,
pub is_generated: bool, pub is_generated: bool,
pub path: PathBuf, pub path: PathBuf,
@ -40,7 +42,12 @@ fn load_map(path: &Path) -> Option<SingleplayerWorld> {
return None; return None;
}; };
version::try_load(&f, path) let Ok(bytes) = f.bytes().collect::<Result<Vec<u8>, _>>() else {
error!("Failed to read {}", meta_path.to_string_lossy());
return None;
};
version::try_load(std::io::Cursor::new(bytes), path)
} }
fn write_world_meta(world: &SingleplayerWorld) { fn write_world_meta(world: &SingleplayerWorld) {
@ -78,11 +85,13 @@ fn migrate_old_singleplayer(from: &Path, to: &Path) {
} }
let mut seed = 0; let mut seed = 0;
let mut day_length = DAY_LENGTH_DEFAULT;
let (map_file, gen_opts) = fs::read_to_string(to.join("server_config/settings.ron")) let (map_file, gen_opts) = fs::read_to_string(to.join("server_config/settings.ron"))
.ok() .ok()
.and_then(|settings| { .and_then(|settings| {
let settings: server::Settings = ron::from_str(&settings).ok()?; let settings: server::Settings = ron::from_str(&settings).ok()?;
seed = settings.world_seed; seed = settings.world_seed;
day_length = settings.day_length;
Some(match settings.map_file? { Some(match settings.map_file? {
FileOpts::LoadOrGenerate { name, opts, .. } => { FileOpts::LoadOrGenerate { name, opts, .. } => {
(Some(PathBuf::from(name)), Some(opts)) (Some(PathBuf::from(name)), Some(opts))
@ -107,6 +116,7 @@ fn migrate_old_singleplayer(from: &Path, to: &Path) {
name: "singleplayer world".to_string(), name: "singleplayer world".to_string(),
gen_opts, gen_opts,
seed, seed,
day_length,
path: to.to_path_buf(), path: to.to_path_buf(),
// Isn't persisted so doesn't matter what it's set to. // Isn't persisted so doesn't matter what it's set to.
is_generated: false, is_generated: false,
@ -226,6 +236,7 @@ impl SingleplayerWorlds {
let new_world = SingleplayerWorld { let new_world = SingleplayerWorld {
name: "New World".to_string(), name: "New World".to_string(),
gen_opts: None, gen_opts: None,
day_length: DAY_LENGTH_DEFAULT,
seed: 0, seed: 0,
is_generated: false, is_generated: false,
map_path: path.join("map.bin"), map_path: path.join("map.bin"),
@ -251,13 +262,13 @@ mod version {
use super::*; use super::*;
pub type Current = V1; pub type Current = V2;
type LoadWorldFn<R> = type LoadWorldFn<R> =
fn(R, &Path) -> Result<SingleplayerWorld, (&'static str, ron::de::SpannedError)>; fn(R, &Path) -> Result<SingleplayerWorld, (&'static str, ron::de::SpannedError)>;
fn loaders<'a, R: std::io::Read + Clone>() -> &'a [LoadWorldFn<R>] { fn loaders<'a, R: std::io::Read + Clone>() -> &'a [LoadWorldFn<R>] {
// Step [4] // Step [4]
&[load_raw::<V1, _>] &[load_raw::<V2, _>, load_raw::<V1, _>]
} }
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
@ -269,18 +280,6 @@ mod version {
seed: u32, seed: u32,
} }
impl V1 {
/// This function is only needed for the current version
pub fn from_world(world: &SingleplayerWorld) -> Self {
V1 {
version: 1,
name: world.name.clone(),
gen_opts: world.gen_opts.clone(),
seed: world.seed,
}
}
}
impl ToWorld for V1 { impl ToWorld for V1 {
fn to_world(self, path: PathBuf) -> SingleplayerWorld { fn to_world(self, path: PathBuf) -> SingleplayerWorld {
let map_path = path.join("map.bin"); let map_path = path.join("map.bin");
@ -290,6 +289,46 @@ mod version {
name: self.name, name: self.name,
gen_opts: self.gen_opts, gen_opts: self.gen_opts,
seed: self.seed, seed: self.seed,
day_length: DAY_LENGTH_DEFAULT,
is_generated,
path,
map_path,
}
}
}
#[derive(Deserialize, Serialize)]
pub struct V2 {
#[serde(deserialize_with = "version::<_, 2>")]
version: u64,
name: String,
gen_opts: Option<GenOpts>,
seed: u32,
day_length: f64,
}
impl V2 {
pub fn from_world(world: &SingleplayerWorld) -> Self {
V2 {
version: 2,
name: world.name.clone(),
gen_opts: world.gen_opts.clone(),
seed: world.seed,
day_length: world.day_length,
}
}
}
impl ToWorld for V2 {
fn to_world(self, path: PathBuf) -> SingleplayerWorld {
let map_path = path.join("map.bin");
let is_generated = fs::metadata(&map_path).is_ok_and(|f| f.is_file());
SingleplayerWorld {
name: self.name,
gen_opts: self.gen_opts,
seed: self.seed,
day_length: self.day_length,
is_generated, is_generated,
path, path,
map_path, map_path,