Initial implementation of starting site choice

This commit is contained in:
Joshua Barretto 2023-03-31 14:24:14 +01:00
parent d1a7884ac8
commit acec45b756
13 changed files with 247 additions and 87 deletions

View File

@ -19,5 +19,8 @@ char_selection-eye_color = Eye Color
char_selection-skin = Skin char_selection-skin = Skin
char_selection-eyeshape = Eye Details char_selection-eyeshape = Eye Details
char_selection-accessories = Accessories char_selection-accessories = Accessories
char_selection-starting_site = Starting Site
char_selection-starting_site_name = Name: { $name }
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.

View File

@ -189,6 +189,7 @@ impl BotClient {
Some("common.items.weapons.sword.starter".to_string()), Some("common.items.weapons.sword.starter".to_string()),
None, None,
body.into(), body.into(),
None,
); );
client.load_character_list(); client.load_character_list();
} }

View File

@ -148,6 +148,7 @@ fn run_client(
Some("common.items.weapons.sword.starter".into()), Some("common.items.weapons.sword.starter".into()),
None, None,
body(), body(),
None,
); );
client.load_character_list(); client.load_character_list();

View File

@ -951,6 +951,7 @@ impl Client {
mainhand: Option<String>, mainhand: Option<String>,
offhand: Option<String>, offhand: Option<String>,
body: comp::Body, body: comp::Body,
start_site: Option<SiteId>,
) { ) {
self.character_list.loading = true; self.character_list.loading = true;
self.send_msg(ClientGeneral::CreateCharacter { self.send_msg(ClientGeneral::CreateCharacter {
@ -958,6 +959,7 @@ impl Client {
mainhand, mainhand,
offhand, offhand,
body, body,
start_site,
}); });
} }

View File

@ -48,6 +48,7 @@ pub enum ClientGeneral {
mainhand: Option<String>, mainhand: Option<String>,
offhand: Option<String>, offhand: Option<String>,
body: comp::Body, body: comp::Body,
start_site: Option<SiteId>,
}, },
DeleteCharacter(CharacterId), DeleteCharacter(CharacterId),
EditCharacter { EditCharacter {

View File

@ -1,7 +1,10 @@
use crate::persistence::{character_updater::CharacterUpdater, PersistedComponents}; use crate::persistence::{character_updater::CharacterUpdater, PersistedComponents};
use common::{ use common::{
character::CharacterId, character::CharacterId,
comp::{inventory::loadout_builder::LoadoutBuilder, Body, Inventory, Item, SkillSet, Stats}, comp::{
inventory::loadout_builder::LoadoutBuilder, Body, Inventory, Item, SkillSet, Stats,
Waypoint,
},
}; };
use specs::{Entity, WriteExpect}; use specs::{Entity, WriteExpect};
@ -32,6 +35,7 @@ pub fn create_character(
character_offhand: Option<String>, character_offhand: Option<String>,
body: Body, body: Body,
character_updater: &mut WriteExpect<'_, CharacterUpdater>, character_updater: &mut WriteExpect<'_, CharacterUpdater>,
waypoint: Option<Waypoint>,
) -> Result<(), CreationError> { ) -> Result<(), CreationError> {
// quick fix whitelist validation for now; eventually replace the // quick fix whitelist validation for now; eventually replace the
// `Option<String>` with an index into a server-provided list of starter // `Option<String>` with an index into a server-provided list of starter
@ -63,7 +67,6 @@ pub fn create_character(
.push(Item::new_from_asset_expect("common.items.food.cheese")) .push(Item::new_from_asset_expect("common.items.food.cheese"))
.expect("Inventory has at least 1 slot left!"); .expect("Inventory has at least 1 slot left!");
let waypoint = None;
let map_marker = None; let map_marker = None;
character_updater.create_character(entity, player_uuid, character_alias, PersistedComponents { character_updater.create_character(entity, player_uuid, character_alias, PersistedComponents {

View File

@ -1,3 +1,8 @@
#[cfg(not(feature = "worldgen"))]
use crate::test_world::{IndexOwned, World};
#[cfg(feature = "worldgen")]
use world::{IndexOwned, World};
use crate::{ use crate::{
automod::AutoMod, automod::AutoMod,
character_creator, character_creator,
@ -7,15 +12,17 @@ use crate::{
EditableSettings, EditableSettings,
}; };
use common::{ use common::{
comp::{Admin, AdminRole, ChatType, Player, UnresolvedChatMsg}, comp::{Admin, AdminRole, ChatType, Player, UnresolvedChatMsg, Waypoint},
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
resources::Time,
terrain::TerrainChunkSize,
uid::Uid, uid::Uid,
}; };
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
use common_net::msg::{ClientGeneral, ServerGeneral}; use common_net::msg::{ClientGeneral, ServerGeneral};
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage}; use specs::{Entities, Join, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage};
use std::sync::{atomic::Ordering, Arc}; use std::sync::{atomic::Ordering, Arc};
use tracing::debug; use tracing::{debug, error};
impl Sys { impl Sys {
fn handle_client_character_screen_msg( fn handle_client_character_screen_msg(
@ -32,6 +39,9 @@ impl Sys {
censor: &ReadExpect<'_, Arc<censor::Censor>>, censor: &ReadExpect<'_, Arc<censor::Censor>>,
automod: &AutoMod, automod: &AutoMod,
msg: ClientGeneral, msg: ClientGeneral,
time: Time,
index: &ReadExpect<'_, IndexOwned>,
world: &ReadExpect<'_, Arc<World>>,
) -> 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
@ -135,6 +145,7 @@ impl Sys {
mainhand, mainhand,
offhand, offhand,
body, body,
start_site,
} => { } => {
if censor.check(&alias) { if censor.check(&alias) {
debug!(?alias, "denied alias as it contained a banned word"); debug!(?alias, "denied alias as it contained a banned word");
@ -151,6 +162,22 @@ impl Sys {
offhand.clone(), offhand.clone(),
body, body,
character_updater, character_updater,
start_site.and_then(|site_idx| {
// TODO: This corresponds to the ID generation logic in `world/src/lib.rs`
// Really, we should have a way to consistently refer to sites, but that's a job for rtsim2
// and the site changes that it will require. Until then, this code is very hacky.
world.civs().sites.iter()
.find(|(_, site)| site.site_tmp.map(|i| i.id()) == Some(site_idx))
.map(Some)
.unwrap_or_else(|| {
error!("Tried to create character with starting site index {}, but such a site does not exist", site_idx);
None
})
.map(|(_, site)| {
let wpos2d = TerrainChunkSize::center_wpos(site.center);
Waypoint::new(world.find_accessible_pos(index.as_index_ref(), wpos2d, true), time)
})
}),
) { ) {
debug!( debug!(
?error, ?error,
@ -226,6 +253,9 @@ impl<'a> System<'a> for Sys {
ReadExpect<'a, EditableSettings>, ReadExpect<'a, EditableSettings>,
ReadExpect<'a, Arc<censor::Censor>>, ReadExpect<'a, Arc<censor::Censor>>,
ReadExpect<'a, AutoMod>, ReadExpect<'a, AutoMod>,
ReadExpect<'a, Time>,
ReadExpect<'a, IndexOwned>,
ReadExpect<'a, Arc<World>>,
); );
const NAME: &'static str = "msg::character_screen"; const NAME: &'static str = "msg::character_screen";
@ -247,6 +277,9 @@ impl<'a> System<'a> for Sys {
editable_settings, editable_settings,
censor, censor,
automod, automod,
time,
index,
world,
): Self::SystemData, ): Self::SystemData,
) { ) {
let mut server_emitter = server_event_bus.emitter(); let mut server_emitter = server_event_bus.emitter();
@ -267,6 +300,9 @@ impl<'a> System<'a> for Sys {
&censor, &censor,
&automod, &automod,
msg, msg,
*time,
&index,
&world,
) )
}); });
} }

View File

@ -74,8 +74,8 @@ use crate::{
}, },
settings::chat::ChatFilter, settings::chat::ChatFilter,
ui::{ ui::{
self, fonts::Fonts, img_ids::Rotations, slot, slot::SlotKey, Graphic, Ingameable, self, default_water_color, fonts::Fonts, img_ids::Rotations, slot, slot::SlotKey, Graphic,
ScaleMode, Ui, Ingameable, ScaleMode, Ui,
}, },
window::Event as WinEvent, window::Event as WinEvent,
GlobalState, GlobalState,
@ -106,7 +106,7 @@ use common::{
terrain::{SpriteKind, TerrainChunk, UnlockKind}, terrain::{SpriteKind, TerrainChunk, UnlockKind},
trade::{ReducedInventory, TradeAction}, trade::{ReducedInventory, TradeAction},
uid::Uid, uid::Uid,
util::{srgba_to_linear, Dir}, util::Dir,
vol::RectRasterableVol, vol::RectRasterableVol,
}; };
use common_base::{prof_span, span}; use common_base::{prof_span, span};
@ -1303,15 +1303,15 @@ impl Hud {
ui.set_scaling_mode(settings.interface.ui_scale); ui.set_scaling_mode(settings.interface.ui_scale);
// Generate ids. // Generate ids.
let ids = Ids::new(ui.id_generator()); let ids = Ids::new(ui.id_generator());
// NOTE: Use a border the same color as the LOD ocean color (but with a
// translucent alpha since UI have transparency and LOD doesn't).
let water_color = srgba_to_linear(Rgba::new(0.0, 0.18, 0.37, 1.0));
// Load world map // Load world map
let mut layers = Vec::new(); let mut layers = Vec::new();
for layer in client.world_data().map_layers() { for layer in client.world_data().map_layers() {
layers.push( // NOTE: Use a border the same color as the LOD ocean color (but with a
ui.add_graphic_with_rotations(Graphic::Image(Arc::clone(layer), Some(water_color))), // translucent alpha since UI have transparency and LOD doesn't).
); layers.push(ui.add_graphic_with_rotations(Graphic::Image(
Arc::clone(layer),
Some(default_water_color()),
)));
} }
let world_map = (layers, client.world_data().chunk_size().map(|e| e as u32)); let world_map = (layers, client.world_data().chunk_size().map(|e| e as u32));
// Load images. // Load images.

View File

@ -117,10 +117,11 @@ impl PlayState for CharSelectionState {
mainhand, mainhand,
offhand, offhand,
body, body,
start_site,
} => { } => {
self.client self.client
.borrow_mut() .borrow_mut()
.create_character(alias, mainhand, offhand, body); .create_character(alias, mainhand, offhand, body, start_site);
}, },
ui::Event::EditCharacter { ui::Event::EditCharacter {
alias, alias,

View File

@ -1,7 +1,7 @@
use crate::{ use crate::{
render::UiDrawer, render::UiDrawer,
ui::{ ui::{
self, self, default_water_color,
fonts::IcedFonts as Fonts, fonts::IcedFonts as Fonts,
ice::{ ice::{
component::{ component::{
@ -15,7 +15,8 @@ use crate::{
}, },
Element, IcedRenderer, IcedUi as Ui, Element, IcedRenderer, IcedUi as Ui,
}, },
img_ids::ImageGraphic, img_ids::{GraphicCreator, ImageGraphic, PixelGraphic},
GraphicId,
}, },
window, GlobalState, window, GlobalState,
}; };
@ -25,6 +26,7 @@ use common::{
comp::{self, humanoid, inventory::slot::EquipSlot, Inventory, Item}, comp::{self, humanoid, inventory::slot::EquipSlot, Inventory, Item},
LoadoutBuilder, LoadoutBuilder,
}; };
use common_net::msg::world_msg::{SiteId, SiteInfo, SiteKind};
use i18n::{Localization, LocalizationHandle}; use i18n::{Localization, LocalizationHandle};
//ImageFrame, Tooltip, //ImageFrame, Tooltip,
use crate::settings::Settings; use crate::settings::Settings;
@ -133,6 +135,7 @@ pub enum Event {
mainhand: Option<String>, mainhand: Option<String>,
offhand: Option<String>, offhand: Option<String>,
body: comp::Body, body: comp::Body,
start_site: Option<SiteId>,
}, },
EditCharacter { EditCharacter {
alias: String, alias: String,
@ -168,13 +171,15 @@ enum Mode {
species_buttons: [button::State; 6], species_buttons: [button::State; 6],
tool_buttons: [button::State; 6], tool_buttons: [button::State; 6],
sliders: Sliders, sliders: Sliders,
scroll: scrollable::State, left_scroll: scrollable::State,
right_scroll: scrollable::State,
name_input: text_input::State, name_input: text_input::State,
back_button: button::State, back_button: button::State,
create_button: button::State, create_button: button::State,
rand_character_button: button::State, rand_character_button: button::State,
rand_name_button: button::State, rand_name_button: button::State,
character_id: Option<CharacterId>, character_id: Option<CharacterId>,
start_site_idx: usize,
}, },
} }
@ -217,13 +222,15 @@ impl Mode {
species_buttons: Default::default(), species_buttons: Default::default(),
tool_buttons: Default::default(), tool_buttons: Default::default(),
sliders: Default::default(), sliders: Default::default(),
scroll: Default::default(), left_scroll: Default::default(),
right_scroll: Default::default(),
name_input: Default::default(), name_input: Default::default(),
back_button: Default::default(), back_button: Default::default(),
create_button: Default::default(), create_button: Default::default(),
rand_character_button: Default::default(), rand_character_button: Default::default(),
rand_name_button: Default::default(), rand_name_button: Default::default(),
character_id: None, character_id: None,
start_site_idx: 0,
} }
} }
@ -243,13 +250,15 @@ impl Mode {
species_buttons: Default::default(), species_buttons: Default::default(),
tool_buttons: Default::default(), tool_buttons: Default::default(),
sliders: Default::default(), sliders: Default::default(),
scroll: Default::default(), left_scroll: Default::default(),
right_scroll: Default::default(),
name_input: Default::default(), name_input: Default::default(),
back_button: Default::default(), back_button: Default::default(),
create_button: Default::default(), create_button: Default::default(),
rand_character_button: Default::default(), rand_character_button: Default::default(),
rand_name_button: Default::default(), rand_name_button: Default::default(),
character_id: Some(character_id), character_id: Some(character_id),
start_site_idx: 0,
} }
} }
} }
@ -279,6 +288,8 @@ struct Controls {
// Id of the selected character // Id of the selected character
selected: Option<CharacterId>, selected: Option<CharacterId>,
default_name: String, default_name: String,
map_img: GraphicId,
possible_starting_sites: Vec<SiteInfo>,
} }
#[derive(Clone)] #[derive(Clone)]
@ -309,6 +320,7 @@ enum Message {
EyeColor(u8), EyeColor(u8),
Accessory(u8), Accessory(u8),
Beard(u8), Beard(u8),
StartSite(usize),
// Workaround for widgets that require a message but we don't want them to actually do // Workaround for widgets that require a message but we don't want them to actually do
// anything // anything
DoNothing, DoNothing,
@ -321,6 +333,8 @@ impl Controls {
selected: Option<CharacterId>, selected: Option<CharacterId>,
default_name: String, default_name: String,
server_info: &ServerInfo, server_info: &ServerInfo,
map_img: GraphicId,
possible_starting_sites: Vec<SiteInfo>,
) -> 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());
@ -339,6 +353,8 @@ impl Controls {
mode: Mode::select(Some(InfoContent::LoadingCharacters)), mode: Mode::select(Some(InfoContent::LoadingCharacters)),
selected, selected,
default_name, default_name,
map_img,
possible_starting_sites,
} }
} }
@ -831,7 +847,8 @@ impl Controls {
inventory: _, inventory: _,
mainhand, mainhand,
offhand: _, offhand: _,
ref mut scroll, ref mut left_scroll,
ref mut right_scroll,
ref mut body_type_buttons, ref mut body_type_buttons,
ref mut species_buttons, ref mut species_buttons,
ref mut tool_buttons, ref mut tool_buttons,
@ -842,6 +859,7 @@ impl Controls {
ref mut rand_character_button, ref mut rand_character_button,
ref mut rand_name_button, ref mut rand_name_button,
character_id, character_id,
start_site_idx,
} => { } => {
let unselected_style = style::button::Style::new(imgs.icon_border) let unselected_style = style::button::Style::new(imgs.icon_border)
.hover_image(imgs.icon_border_mo) .hover_image(imgs.icon_border_mo)
@ -1070,19 +1088,19 @@ impl Controls {
// Height of interactable area // Height of interactable area
const SLIDER_HEIGHT: u16 = 30; const SLIDER_HEIGHT: u16 = 30;
fn char_slider<'a>( fn char_slider<'a, T: Copy + Into<u32>>(
text: String, text: String,
state: &'a mut slider::State, state: &'a mut slider::State,
max: u8, max: T,
selected_val: u8, selected_val: T,
on_change: impl 'static + Fn(u8) -> Message, on_change: impl 'static + Fn(u32) -> Message,
(fonts, imgs): (&Fonts, &Imgs), (fonts, imgs): (&Fonts, &Imgs),
) -> Element<'a, Message> { ) -> Element<'a, Message> {
Column::with_children(vec![ Column::with_children(vec![
Text::new(text) Text::new(text)
.size(fonts.cyri.scale(SLIDER_TEXT_SIZE)) .size(fonts.cyri.scale(SLIDER_TEXT_SIZE))
.into(), .into(),
Slider::new(state, 0..=max, selected_val, on_change) Slider::new(state, 0..=max.into(), selected_val.into(), on_change)
.height(SLIDER_HEIGHT) .height(SLIDER_HEIGHT)
.style(style::slider::Style::images( .style(style::slider::Style::images(
imgs.slider_indicator, imgs.slider_indicator,
@ -1096,13 +1114,13 @@ impl Controls {
.align_items(Align::Center) .align_items(Align::Center)
.into() .into()
} }
fn char_slider_greyable<'a>( fn char_slider_greyable<'a, T: Copy + Into<u32>>(
active: bool, active: bool,
text: String, text: String,
state: &'a mut slider::State, state: &'a mut slider::State,
max: u8, max: T,
selected_val: u8, selected_val: T,
on_change: impl 'static + Fn(u8) -> Message, on_change: impl 'static + Fn(u32) -> Message,
(fonts, imgs): (&Fonts, &Imgs), (fonts, imgs): (&Fonts, &Imgs),
) -> Element<'a, Message> { ) -> Element<'a, Message> {
if active { if active {
@ -1115,19 +1133,21 @@ impl Controls {
.into(), .into(),
// "Disabled" slider // "Disabled" slider
// TODO: add iced support for disabled sliders (like buttons) // TODO: add iced support for disabled sliders (like buttons)
Slider::new(state, 0..=max, selected_val, |_| Message::DoNothing) Slider::new(state, 0..=max.into(), selected_val.into(), |_| {
.height(SLIDER_HEIGHT) Message::DoNothing
.style(style::slider::Style { })
cursor: style::slider::Cursor::Color(Rgba::zero()), .height(SLIDER_HEIGHT)
bar: style::slider::Bar::Image( .style(style::slider::Style {
imgs.slider_range, cursor: style::slider::Cursor::Color(Rgba::zero()),
Rgba::from_translucent(255, 51), bar: style::slider::Bar::Image(
SLIDER_BAR_PAD, imgs.slider_range,
), Rgba::from_translucent(255, 51),
labels: false, SLIDER_BAR_PAD,
..Default::default() ),
}) labels: false,
.into(), ..Default::default()
})
.into(),
]) ])
.align_items(Align::Center) .align_items(Align::Center)
.into() .into()
@ -1140,7 +1160,7 @@ impl Controls {
&mut sliders.hair_style, &mut sliders.hair_style,
body.species.num_hair_styles(body.body_type) - 1, body.species.num_hair_styles(body.body_type) - 1,
body.hair_style, body.hair_style,
Message::HairStyle, |x| Message::HairStyle(x as u8),
(fonts, imgs), (fonts, imgs),
), ),
char_slider( char_slider(
@ -1148,7 +1168,7 @@ impl Controls {
&mut sliders.hair_color, &mut sliders.hair_color,
body.species.num_hair_colors() - 1, body.species.num_hair_colors() - 1,
body.hair_color, body.hair_color,
Message::HairColor, |x| Message::HairColor(x as u8),
(fonts, imgs), (fonts, imgs),
), ),
char_slider( char_slider(
@ -1156,7 +1176,7 @@ impl Controls {
&mut sliders.skin, &mut sliders.skin,
body.species.num_skin_colors() - 1, body.species.num_skin_colors() - 1,
body.skin, body.skin,
Message::Skin, |x| Message::Skin(x as u8),
(fonts, imgs), (fonts, imgs),
), ),
char_slider( char_slider(
@ -1164,7 +1184,7 @@ impl Controls {
&mut sliders.eyes, &mut sliders.eyes,
body.species.num_eyes(body.body_type) - 1, body.species.num_eyes(body.body_type) - 1,
body.eyes, body.eyes,
Message::Eyes, |x| Message::Eyes(x as u8),
(fonts, imgs), (fonts, imgs),
), ),
char_slider( char_slider(
@ -1172,7 +1192,7 @@ impl Controls {
&mut sliders.eye_color, &mut sliders.eye_color,
body.species.num_eye_colors() - 1, body.species.num_eye_colors() - 1,
body.eye_color, body.eye_color,
Message::EyeColor, |x| Message::EyeColor(x as u8),
(fonts, imgs), (fonts, imgs),
), ),
char_slider_greyable( char_slider_greyable(
@ -1181,7 +1201,7 @@ impl Controls {
&mut sliders.accessory, &mut sliders.accessory,
body.species.num_accessories(body.body_type) - 1, body.species.num_accessories(body.body_type) - 1,
body.accessory, body.accessory,
Message::Accessory, |x| Message::Accessory(x as u8),
(fonts, imgs), (fonts, imgs),
), ),
char_slider_greyable( char_slider_greyable(
@ -1190,7 +1210,7 @@ impl Controls {
&mut sliders.beard, &mut sliders.beard,
body.species.num_beards(body.body_type) - 1, body.species.num_beards(body.body_type) - 1,
body.beard, body.beard,
Message::Beard, |x| Message::Beard(x as u8),
(fonts, imgs), (fonts, imgs),
), ),
]) ])
@ -1213,7 +1233,7 @@ impl Controls {
tooltip::text(&tooltip_text, tooltip_style) tooltip::text(&tooltip_text, tooltip_style)
}); });
let column_content = vec![ let left_column_content = vec![
body_type.into(), body_type.into(),
tool.into(), tool.into(),
species.into(), species.into(),
@ -1221,47 +1241,96 @@ impl Controls {
rand_character.into(), rand_character.into(),
]; ];
let left_column = Container::new( let right_column_content = vec![
Scrollable::new(scroll) Image::new(self.map_img)
.push( .height(Length::Units(300))
Column::with_children(column_content) .width(Length::Units(300))
.align_items(Align::Center) .into(),
.width(Length::Fill) Column::with_children(if self.possible_starting_sites.is_empty() {
.spacing(5), Vec::new()
) } else {
.padding(5) let site_slider = char_slider(
.width(Length::Fill) i18n.get_msg("char_selection-starting_site").into_owned(),
.align_items(Align::Center) &mut sliders.starting_site,
.style(style::scrollable::Style { self.possible_starting_sites.len() as u32 - 1,
track: None, *start_site_idx as u32,
scroller: style::scrollable::Scroller::Color(UI_MAIN), |x| Message::StartSite(x as usize),
}), (fonts, imgs),
) );
.width(Length::Units(320)) // TODO: see if we can get iced to work with settings below
//.max_width(360)
//.width(Length::Fill)
.height(Length::Fill);
let left_column = Column::with_children(vec![ let site_name = Text::new(i18n
Container::new(left_column) .get_msg_ctx("char_selection-starting_site_name", &i18n::fluent_args! {
.style(style::container::Style::color(Rgba::from_translucent( "name" => self.possible_starting_sites[*start_site_idx].name.as_deref()
0, .unwrap_or("Unknown"),
BANNER_ALPHA, })
))) .into_owned())
.width(Length::Units(320)) .size(fonts.cyri.scale(SLIDER_TEXT_SIZE))
.center_x() .into();
.into(),
Image::new(imgs.frame_bottom) let site_kind = Text::new(i18n
.height(Length::Units(40)) .get_msg_ctx("char_selection-starting_site_kind", &i18n::fluent_args! {
.width(Length::Units(320)) "kind" => match self.possible_starting_sites[*start_site_idx].kind {
.color(Rgba::from_translucent(0, BANNER_ALPHA)) SiteKind::Town => i18n.get_msg("hud-map-town").into_owned(),
.into(), SiteKind::Castle => i18n.get_msg("hud-map-castle").into_owned(),
]) SiteKind::Bridge => i18n.get_msg("hud-map-bridge").into_owned(),
.height(Length::Fill); _ => "Unknown".to_string(),
},
})
.into_owned())
.size(fonts.cyri.scale(SLIDER_TEXT_SIZE))
.into();
vec![site_slider, site_name, site_kind]
})
.max_width(200)
.padding(5)
.into(),
];
let column = |column_content, scroll| {
let column = Container::new(
Scrollable::new(scroll)
.push(
Column::with_children(column_content)
.align_items(Align::Center)
.width(Length::Fill)
.spacing(5),
)
.padding(5)
.width(Length::Fill)
.align_items(Align::Center)
.style(style::scrollable::Style {
track: None,
scroller: style::scrollable::Scroller::Color(UI_MAIN),
}),
)
.width(Length::Units(320)) // TODO: see if we can get iced to work with settings below
//.max_width(360)
//.width(Length::Fill)
.height(Length::Fill);
Column::with_children(vec![
Container::new(column)
.style(style::container::Style::color(Rgba::from_translucent(
0,
BANNER_ALPHA,
)))
.width(Length::Units(320))
.center_x()
.into(),
Image::new(imgs.frame_bottom)
.height(Length::Units(40))
.width(Length::Units(320))
.color(Rgba::from_translucent(0, BANNER_ALPHA))
.into(),
])
.height(Length::Fill)
};
let top = Row::with_children(vec![ let top = Row::with_children(vec![
left_column.into(), column(left_column_content, left_scroll).into(),
MouseDetector::new(&mut self.mouse_detector, Length::Fill, Length::Fill).into(), MouseDetector::new(&mut self.mouse_detector, Length::Fill, Length::Fill).into(),
column(right_column_content, right_scroll).into(),
]) ])
.padding(10) .padding(10)
.width(Length::Fill) .width(Length::Fill)
@ -1516,6 +1585,7 @@ impl Controls {
body, body,
mainhand, mainhand,
offhand, offhand,
start_site_idx,
.. ..
} = &self.mode } = &self.mode
{ {
@ -1524,6 +1594,10 @@ impl Controls {
mainhand: mainhand.map(String::from), mainhand: mainhand.map(String::from),
offhand: offhand.map(String::from), offhand: offhand.map(String::from),
body: comp::Body::Humanoid(*body), body: comp::Body::Humanoid(*body),
start_site: self
.possible_starting_sites
.get(*start_site_idx)
.map(|info| info.id),
}); });
self.mode = Mode::select(Some(InfoContent::CreatingCharacter)); self.mode = Mode::select(Some(InfoContent::CreatingCharacter));
} }
@ -1643,6 +1717,11 @@ impl Controls {
body.validate(); body.validate();
} }
}, },
Message::StartSite(idx) => {
if let Mode::CreateOrEdit { start_site_idx, .. } = &mut self.mode {
*start_site_idx = idx;
}
},
} }
} }
@ -1707,6 +1786,19 @@ impl CharSelectionUi {
selected_character, selected_character,
default_name, default_name,
client.server_info(), client.server_info(),
ui.add_graphic(
PixelGraphic::new_graphic((
client.world_data().map_image().clone(),
Some(default_water_color()),
))
.expect("Failed to generate map image"),
),
client.sites()
.values()
// TODO: Enforce this server-side and add some way to customise it?
.filter(|info| matches!(&info.site.kind, SiteKind::Town | SiteKind::Castle | SiteKind::Bridge))
.map(|info| info.site.clone())
.collect(),
); );
Self { Self {
@ -1814,4 +1906,5 @@ struct Sliders {
eye_color: slider::State, eye_color: slider::State,
accessory: slider::State, accessory: slider::State,
beard: slider::State, beard: slider::State,
starting_site: slider::State,
} }

View File

@ -3,6 +3,7 @@ use common::{
assets::{self, AssetExt, DotVoxAsset, Error}, assets::{self, AssetExt, DotVoxAsset, Error},
figure::Segment, figure::Segment,
}; };
use image::DynamicImage;
use std::sync::Arc; use std::sync::Arc;
use vek::*; use vek::*;
@ -112,6 +113,16 @@ impl<'a> GraphicCreator<'a> for VoxelPixArtGraphic {
} }
} }
pub enum PixelGraphic {}
impl<'a> GraphicCreator<'a> for PixelGraphic {
type Specifier = (Arc<DynamicImage>, Option<Rgba<f32>>);
fn new_graphic((img, col): Self::Specifier) -> Result<Graphic, Error> {
Ok(Graphic::Image(img, col))
}
}
pub struct Rotations { pub struct Rotations {
pub none: conrod_core::image::Id, pub none: conrod_core::image::Id,
pub cw90: conrod_core::image::Id, pub cw90: conrod_core::image::Id,

View File

@ -1090,3 +1090,6 @@ fn default_scissor(physical_resolution: Vec2<u32>) -> Aabr<u16> {
}, },
} }
} }
// TODO: Don't hard code this
pub fn default_water_color() -> Rgba<f32> { srgba_to_linear(Rgba::new(0.0, 0.18, 0.37, 1.0)) }

View File

@ -219,6 +219,11 @@ impl World {
pub fn sample_blocks(&self) -> BlockGen { BlockGen::new(ColumnGen::new(&self.sim)) } pub fn sample_blocks(&self) -> BlockGen { BlockGen::new(ColumnGen::new(&self.sim)) }
/// Find a position that's accessible to a player at the given world
/// position by searching blocks vertically.
///
/// If `ascending` is `true`, we try to find the highest accessible position
/// instead of the lowest.
pub fn find_accessible_pos( pub fn find_accessible_pos(
&self, &self,
index: IndexRef, index: IndexRef,