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
- A tavern building where npcs go to relax.
- Toggle for walking instead of running (Default: `I`).
- Added day duration slider configuration on map creation UI.
### Changed

View File

@ -38,6 +38,7 @@ main-singleplayer-regenerate = Regenerate
main-singleplayer-create_custom = Create Custom
main-singleplayer-invalid_name = Error: Invalid name
main-singleplayer-seed = Seed
main-singleplayer-day_length = Day duration
main-singleplayer-random_seed = Random
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

View File

@ -35,3 +35,6 @@ pub const ENERGY_PER_LEVEL: u16 = 5;
pub const HP_PER_LEVEL: u16 = 5;
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 common::{
calendar::{Calendar, CalendarEvent},
consts::DAY_LENGTH_DEFAULT,
resources::BattleMode,
rtsim::WorldSettings,
};
@ -205,7 +206,7 @@ impl Default for Settings {
world_seed: DEFAULT_WORLD_SEED,
server_name: "Veloren Server".into(),
max_players: 100,
day_length: 30.0,
day_length: DAY_LENGTH_DEFAULT,
start_time: 9.0 * 3600.0,
map_file: None,
max_view_distance: Some(65),

View File

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

View File

@ -47,6 +47,7 @@ pub struct Screen {
world_name: text_input::State,
map_seed: text_input::State,
day_length: slider::State,
random_seed_button: button::State,
world_size_x: slider::State,
world_size_y: slider::State,
@ -196,6 +197,9 @@ impl Screen {
const SLIDER_BAR_PAD: u16 = 0;
// Height of interactable area
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![
BackgroundContainer::new(
@ -288,6 +292,42 @@ impl Screen {
gen_content.push(Row::with_children(seed_content).into());
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(
Text::new(format!(
"{}: x: {}, y: {}",

View File

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

View File

@ -1,9 +1,10 @@
use std::{
fs,
io::Read,
path::{Path, PathBuf},
};
use common::assets::ASSETS_PATH;
use common::{assets::ASSETS_PATH, consts::DAY_LENGTH_DEFAULT};
use serde::{Deserialize, Serialize};
use server::{FileOpts, GenOpts, DEFAULT_WORLD_MAP};
use tracing::error;
@ -18,6 +19,7 @@ struct World0 {
pub struct SingleplayerWorld {
pub name: String,
pub gen_opts: Option<GenOpts>,
pub day_length: f64,
pub seed: u32,
pub is_generated: bool,
pub path: PathBuf,
@ -40,7 +42,12 @@ fn load_map(path: &Path) -> Option<SingleplayerWorld> {
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) {
@ -78,11 +85,13 @@ fn migrate_old_singleplayer(from: &Path, to: &Path) {
}
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"))
.ok()
.and_then(|settings| {
let settings: server::Settings = ron::from_str(&settings).ok()?;
seed = settings.world_seed;
day_length = settings.day_length;
Some(match settings.map_file? {
FileOpts::LoadOrGenerate { name, opts, .. } => {
(Some(PathBuf::from(name)), Some(opts))
@ -107,6 +116,7 @@ fn migrate_old_singleplayer(from: &Path, to: &Path) {
name: "singleplayer world".to_string(),
gen_opts,
seed,
day_length,
path: to.to_path_buf(),
// Isn't persisted so doesn't matter what it's set to.
is_generated: false,
@ -226,6 +236,7 @@ impl SingleplayerWorlds {
let new_world = SingleplayerWorld {
name: "New World".to_string(),
gen_opts: None,
day_length: DAY_LENGTH_DEFAULT,
seed: 0,
is_generated: false,
map_path: path.join("map.bin"),
@ -251,13 +262,13 @@ mod version {
use super::*;
pub type Current = V1;
pub type Current = V2;
type LoadWorldFn<R> =
fn(R, &Path) -> Result<SingleplayerWorld, (&'static str, ron::de::SpannedError)>;
fn loaders<'a, R: std::io::Read + Clone>() -> &'a [LoadWorldFn<R>] {
// Step [4]
&[load_raw::<V1, _>]
&[load_raw::<V2, _>, load_raw::<V1, _>]
}
#[derive(Deserialize, Serialize)]
@ -269,18 +280,6 @@ mod version {
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 {
fn to_world(self, path: PathBuf) -> SingleplayerWorld {
let map_path = path.join("map.bin");
@ -290,6 +289,46 @@ mod version {
name: self.name,
gen_opts: self.gen_opts,
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,
path,
map_path,