Save the selected character, deselect character when deleting, auto select newly created character

This commit is contained in:
Imbris 2020-11-14 14:32:39 -05:00
parent 2c2e4813fd
commit 4f2512f126
11 changed files with 293 additions and 159 deletions

View File

@ -32,6 +32,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Persistent waypoints (start from the last camp fire you visited) - Persistent waypoints (start from the last camp fire you visited)
- NPCs use all three weapon skills in combat - NPCs use all three weapon skills in combat
- Speed stat to weapons which affects weapon attack speed - Speed stat to weapons which affects weapon attack speed
- Saving of the last selected character in the character selection screen
- Autoselecting the newly created character
- Deselecting when the selected character is deleted
### Changed ### Changed

View File

@ -67,6 +67,7 @@ pub enum Event {
Notification(Notification), Notification(Notification),
SetViewDistance(u32), SetViewDistance(u32),
Outcome(Outcome), Outcome(Outcome),
CharacterCreated(CharacterId),
} }
pub struct Client { pub struct Client {
@ -1422,7 +1423,11 @@ impl Client {
Ok(()) Ok(())
} }
fn handle_server_character_screen_msg(&mut self, msg: ServerGeneral) -> Result<(), Error> { fn handle_server_character_screen_msg(
&mut self,
events: &mut Vec<Event>,
msg: ServerGeneral,
) -> Result<(), Error> {
match msg { match msg {
ServerGeneral::CharacterListUpdate(character_list) => { ServerGeneral::CharacterListUpdate(character_list) => {
self.character_list.characters = character_list; self.character_list.characters = character_list;
@ -1438,6 +1443,9 @@ impl Client {
self.clean_state(); self.clean_state();
self.character_list.error = Some(error); self.character_list.error = Some(error);
}, },
ServerGeneral::CharacterCreated(character_id) => {
events.push(Event::CharacterCreated(character_id));
},
ServerGeneral::CharacterSuccess => { ServerGeneral::CharacterSuccess => {
debug!("client is now in ingame state on server"); debug!("client is now in ingame state on server");
if let Some(vd) = self.view_distance { if let Some(vd) = self.view_distance {
@ -1490,7 +1498,7 @@ impl Client {
self.handle_ping_msg(msg?)?; self.handle_ping_msg(msg?)?;
} }
if let Some(msg) = m3 { if let Some(msg) = m3 {
self.handle_server_character_screen_msg(msg?)?; self.handle_server_character_screen_msg(frontend_events, msg?)?;
} }
if let Some(msg) = m4 { if let Some(msg) = m4 {
self.handle_server_in_game_msg(frontend_events, msg?)?; self.handle_server_in_game_msg(frontend_events, msg?)?;

View File

@ -69,6 +69,8 @@ pub enum ServerGeneral {
CharacterListUpdate(Vec<CharacterItem>), CharacterListUpdate(Vec<CharacterItem>),
/// An error occurred while creating or deleting a character /// An error occurred while creating or deleting a character
CharacterActionError(String), CharacterActionError(String),
/// A new character was created
CharacterCreated(crate::character::CharacterId),
CharacterSuccess, CharacterSuccess,
//Ingame related //Ingame related
GroupUpdate(comp::group::ChangeNotification<sync::Uid>), GroupUpdate(comp::group::ChangeNotification<sync::Uid>),
@ -194,7 +196,8 @@ impl ServerMsg {
//Character Screen related //Character Screen related
ServerGeneral::CharacterDataLoadError(_) ServerGeneral::CharacterDataLoadError(_)
| ServerGeneral::CharacterListUpdate(_) | ServerGeneral::CharacterListUpdate(_)
| ServerGeneral::CharacterActionError(_) => { | ServerGeneral::CharacterActionError(_)
| ServerGeneral::CharacterCreated(_) => {
c_type != ClientType::ChatOnly && presence.is_none() c_type != ClientType::ChatOnly && presence.is_none()
}, },
ServerGeneral::CharacterSuccess => { ServerGeneral::CharacterSuccess => {

View File

@ -73,6 +73,7 @@ impl Client {
ServerGeneral::CharacterDataLoadError(_) ServerGeneral::CharacterDataLoadError(_)
| ServerGeneral::CharacterListUpdate(_) | ServerGeneral::CharacterListUpdate(_)
| ServerGeneral::CharacterActionError(_) | ServerGeneral::CharacterActionError(_)
| ServerGeneral::CharacterCreated(_)
| ServerGeneral::CharacterSuccess => { | ServerGeneral::CharacterSuccess => {
self.character_screen_stream.try_lock().unwrap().send(g) self.character_screen_stream.try_lock().unwrap().send(g)
}, },
@ -149,6 +150,7 @@ impl Client {
ServerGeneral::CharacterDataLoadError(_) ServerGeneral::CharacterDataLoadError(_)
| ServerGeneral::CharacterListUpdate(_) | ServerGeneral::CharacterListUpdate(_)
| ServerGeneral::CharacterActionError(_) | ServerGeneral::CharacterActionError(_)
| ServerGeneral::CharacterCreated(_)
| ServerGeneral::CharacterSuccess => { | ServerGeneral::CharacterSuccess => {
PreparedMsg::new(1, &g, &self.character_screen_stream) PreparedMsg::new(1, &g, &self.character_screen_stream)
}, },

View File

@ -62,7 +62,7 @@ use futures_executor::block_on;
use metrics::{PhysicsMetrics, ServerMetrics, StateTickMetrics, TickMetrics}; use metrics::{PhysicsMetrics, ServerMetrics, StateTickMetrics, TickMetrics};
use network::{Network, Pid, ProtocolAddr}; use network::{Network, Pid, ProtocolAddr};
use persistence::{ use persistence::{
character_loader::{CharacterLoader, CharacterLoaderResponseType}, character_loader::{CharacterLoader, CharacterLoaderResponseKind},
character_updater::CharacterUpdater, character_updater::CharacterUpdater,
}; };
use specs::{join::Join, Builder, Entity as EcsEntity, RunNow, SystemData, WorldExt}; use specs::{join::Join, Builder, Entity as EcsEntity, RunNow, SystemData, WorldExt};
@ -539,7 +539,7 @@ impl Server {
.read_resource::<persistence::character_loader::CharacterLoader>() .read_resource::<persistence::character_loader::CharacterLoader>()
.messages() .messages()
.for_each(|query_result| match query_result.result { .for_each(|query_result| match query_result.result {
CharacterLoaderResponseType::CharacterList(result) => match result { CharacterLoaderResponseKind::CharacterList(result) => match result {
Ok(character_list_data) => self.notify_client( Ok(character_list_data) => self.notify_client(
query_result.entity, query_result.entity,
ServerGeneral::CharacterListUpdate(character_list_data), ServerGeneral::CharacterListUpdate(character_list_data),
@ -549,7 +549,23 @@ impl Server {
ServerGeneral::CharacterActionError(error.to_string()), ServerGeneral::CharacterActionError(error.to_string()),
), ),
}, },
CharacterLoaderResponseType::CharacterData(result) => { CharacterLoaderResponseKind::CharacterCreation(result) => match result {
Ok((character_id, list)) => {
self.notify_client(
query_result.entity,
ServerGeneral::CharacterListUpdate(list),
);
self.notify_client(
query_result.entity,
ServerGeneral::CharacterCreated(character_id),
);
},
Err(error) => self.notify_client(
query_result.entity,
ServerGeneral::CharacterActionError(error.to_string()),
),
},
CharacterLoaderResponseKind::CharacterData(result) => {
let message = match *result { let message = match *result {
Ok(character_data) => ServerEvent::UpdateCharacterData { Ok(character_data) => ServerEvent::UpdateCharacterData {
entity: query_result.entity, entity: query_result.entity,

View File

@ -17,7 +17,7 @@ use crate::{
convert_stats_from_database, convert_stats_to_database, convert_stats_from_database, convert_stats_to_database,
convert_waypoint_to_database_json, convert_waypoint_to_database_json,
}, },
character_loader::{CharacterDataResult, CharacterListResult}, character_loader::{CharacterCreationResult, CharacterDataResult, CharacterListResult},
error::Error::DatabaseError, error::Error::DatabaseError,
json_models::CharacterPosition, json_models::CharacterPosition,
PersistedComponents, PersistedComponents,
@ -170,7 +170,7 @@ pub fn create_character(
persisted_components: PersistedComponents, persisted_components: PersistedComponents,
connection: VelorenTransaction, connection: VelorenTransaction,
map: &AbilityMap, map: &AbilityMap,
) -> CharacterListResult { ) -> CharacterCreationResult {
use schema::item::dsl::*; use schema::item::dsl::*;
check_character_limit(uuid, connection)?; check_character_limit(uuid, connection)?;
@ -303,7 +303,7 @@ pub fn create_character(
))); )));
} }
load_character_list(uuid, connection, map) load_character_list(uuid, connection, map).map(|list| (character_id, list))
} }
/// Delete a character. Returns the updated character list. /// Delete a character. Returns the updated character list.

View File

@ -12,6 +12,7 @@ use std::path::Path;
use tracing::error; use tracing::error;
pub(crate) type CharacterListResult = Result<Vec<CharacterItem>, Error>; pub(crate) type CharacterListResult = Result<Vec<CharacterItem>, Error>;
pub(crate) type CharacterCreationResult = Result<(CharacterId, Vec<CharacterItem>), Error>;
pub(crate) type CharacterDataResult = Result<PersistedComponents, Error>; pub(crate) type CharacterDataResult = Result<PersistedComponents, Error>;
type CharacterLoaderRequest = (specs::Entity, CharacterLoaderRequestKind); type CharacterLoaderRequest = (specs::Entity, CharacterLoaderRequestKind);
@ -38,16 +39,17 @@ enum CharacterLoaderRequestKind {
/// Wrapper for results for character actions. Can be a list of /// Wrapper for results for character actions. Can be a list of
/// characters, or component data belonging to an individual character /// characters, or component data belonging to an individual character
#[derive(Debug)] #[derive(Debug)]
pub enum CharacterLoaderResponseType { pub enum CharacterLoaderResponseKind {
CharacterList(CharacterListResult), CharacterList(CharacterListResult),
CharacterData(Box<CharacterDataResult>), CharacterData(Box<CharacterDataResult>),
CharacterCreation(CharacterCreationResult),
} }
/// Common message format dispatched in response to an update request /// Common message format dispatched in response to an update request
#[derive(Debug)] #[derive(Debug)]
pub struct CharacterLoaderResponse { pub struct CharacterLoaderResponse {
pub entity: specs::Entity, pub entity: specs::Entity,
pub result: CharacterLoaderResponseType, pub result: CharacterLoaderResponseKind,
} }
/// A bi-directional messaging resource for making requests to modify or load /// A bi-directional messaging resource for making requests to modify or load
@ -80,14 +82,16 @@ impl CharacterLoader {
for request in internal_rx { for request in internal_rx {
let (entity, kind) = request; let (entity, kind) = request;
if let Err(e) = internal_tx.send(CharacterLoaderResponse { if let Err(e) =
internal_tx.send(CharacterLoaderResponse {
entity, entity,
result: match kind { result: match kind {
CharacterLoaderRequestKind::CreateCharacter { CharacterLoaderRequestKind::CreateCharacter {
player_uuid, player_uuid,
character_alias, character_alias,
persisted_components, persisted_components,
} => CharacterLoaderResponseType::CharacterList(conn.transaction(|txn| { } => CharacterLoaderResponseKind::CharacterCreation(conn.transaction(
|txn| {
create_character( create_character(
&player_uuid, &player_uuid,
&character_alias, &character_alias,
@ -95,30 +99,30 @@ impl CharacterLoader {
txn, txn,
&map, &map,
) )
})), },
)),
CharacterLoaderRequestKind::DeleteCharacter { CharacterLoaderRequestKind::DeleteCharacter {
player_uuid, player_uuid,
character_id, character_id,
} => CharacterLoaderResponseType::CharacterList(conn.transaction(|txn| { } => CharacterLoaderResponseKind::CharacterList(conn.transaction(
delete_character(&player_uuid, character_id, txn, &map) |txn| delete_character(&player_uuid, character_id, txn, &map),
})), )),
CharacterLoaderRequestKind::LoadCharacterList { player_uuid } => { CharacterLoaderRequestKind::LoadCharacterList { player_uuid } => {
CharacterLoaderResponseType::CharacterList( CharacterLoaderResponseKind::CharacterList(conn.transaction(
conn.transaction(|txn| { |txn| load_character_list(&player_uuid, txn, &map),
load_character_list(&player_uuid, txn, &map) ))
}),
)
}, },
CharacterLoaderRequestKind::LoadCharacterData { CharacterLoaderRequestKind::LoadCharacterData {
player_uuid, player_uuid,
character_id, character_id,
} => { } => CharacterLoaderResponseKind::CharacterData(Box::new(
CharacterLoaderResponseType::CharacterData(Box::new(conn.transaction( conn.transaction(|txn| {
|txn| load_character_data(player_uuid, character_id, txn, &map), load_character_data(player_uuid, character_id, txn, &map)
))) }),
)),
}, },
}, })
}) { {
error!(?e, "Could not send send persistence request"); error!(?e, "Could not send send persistence request");
} }
} }

View File

@ -30,8 +30,10 @@ impl CharSelectionState {
Some("fixture.selection_bg"), Some("fixture.selection_bg"),
&*client.borrow(), &*client.borrow(),
); );
let char_selection_ui = CharSelectionUi::new(global_state, &*client.borrow());
Self { Self {
char_selection_ui: CharSelectionUi::new(global_state), char_selection_ui,
client, client,
scene, scene,
} }
@ -98,7 +100,7 @@ impl PlayState for CharSelectionState {
// Maintain the UI. // Maintain the UI.
let events = self let events = self
.char_selection_ui .char_selection_ui
.maintain(global_state, &mut self.client.borrow_mut()); .maintain(global_state, &self.client.borrow());
for event in events { for event in events {
match event { match event {
@ -124,6 +126,15 @@ impl PlayState for CharSelectionState {
ui::Event::ClearCharacterListError => { ui::Event::ClearCharacterListError => {
self.client.borrow_mut().character_list.error = None; self.client.borrow_mut().character_list.error = None;
}, },
ui::Event::SelectCharacter(selected) => {
let client = self.client.borrow();
let server_name = &client.server_info.name;
// Select newly created character
global_state
.profile
.set_selected_character(server_name, selected);
global_state.profile.save_to_file_warn();
},
} }
} }
@ -179,6 +190,9 @@ impl PlayState for CharSelectionState {
); );
return PlayStateResult::Pop; return PlayStateResult::Pop;
}, },
client::Event::CharacterCreated(character_id) => {
self.char_selection_ui.select_character(character_id);
},
_ => {}, _ => {},
} }
} }

View File

@ -130,13 +130,12 @@ pub enum Event {
}, },
DeleteCharacter(CharacterId), DeleteCharacter(CharacterId),
ClearCharacterListError, ClearCharacterListError,
SelectCharacter(Option<CharacterId>),
} }
enum Mode { enum Mode {
Select { Select {
info_content: Option<InfoContent>, info_content: Option<InfoContent>,
// Index of selected character
selected: Option<usize>,
characters_scroll: scrollable::State, characters_scroll: scrollable::State,
character_buttons: Vec<button::State>, character_buttons: Vec<button::State>,
@ -168,7 +167,6 @@ impl Mode {
pub fn select(info_content: Option<InfoContent>) -> Self { pub fn select(info_content: Option<InfoContent>) -> Self {
Self::Select { Self::Select {
info_content, info_content,
selected: None,
characters_scroll: Default::default(), characters_scroll: Default::default(),
character_buttons: Vec::new(), character_buttons: Vec::new(),
new_character_button: Default::default(), new_character_button: Default::default(),
@ -231,8 +229,9 @@ struct Controls {
tooltip_manager: TooltipManager, tooltip_manager: TooltipManager,
// Zone for rotating the character with the mouse // Zone for rotating the character with the mouse
mouse_detector: mouse_detector::State, mouse_detector: mouse_detector::State,
// enter: bool,
mode: Mode, mode: Mode,
// Id of the selected character
selected: Option<CharacterId>,
} }
#[derive(Clone)] #[derive(Clone)]
@ -240,7 +239,7 @@ enum Message {
Back, Back,
Logout, Logout,
EnterWorld, EnterWorld,
Select(usize), Select(CharacterId),
Delete(usize), Delete(usize),
NewCharacter, NewCharacter,
CreateCharacter, CreateCharacter,
@ -265,7 +264,12 @@ enum Message {
} }
impl Controls { impl Controls {
fn new(fonts: Fonts, imgs: Imgs, i18n: std::sync::Arc<Localization>) -> Self { fn new(
fonts: Fonts,
imgs: Imgs,
i18n: std::sync::Arc<Localization>,
selected: Option<CharacterId>,
) -> 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());
@ -279,6 +283,7 @@ impl Controls {
tooltip_manager: TooltipManager::new(TOOLTIP_HOVER_DUR, TOOLTIP_FADE_DUR), tooltip_manager: TooltipManager::new(TOOLTIP_HOVER_DUR, TOOLTIP_FADE_DUR),
mouse_detector: Default::default(), mouse_detector: Default::default(),
mode: Mode::select(Some(InfoContent::LoadingCharacters)), mode: Mode::select(Some(InfoContent::LoadingCharacters)),
selected,
} }
} }
@ -331,7 +336,6 @@ impl Controls {
let content = match &mut self.mode { let content = match &mut self.mode {
Mode::Select { Mode::Select {
ref mut info_content, ref mut info_content,
selected,
ref mut characters_scroll, ref mut characters_scroll,
ref mut character_buttons, ref mut character_buttons,
ref mut new_character_button, ref mut new_character_button,
@ -340,6 +344,24 @@ impl Controls {
ref mut yes_button, ref mut yes_button,
ref mut no_button, ref mut no_button,
} => { } => {
// If no character is selected then select the first one
// Note: we don't need to persist this because it is the default
if self.selected.is_none() {
self.selected = client
.character_list
.characters
.get(0)
.and_then(|i| i.character.id);
}
// Get the index of the selected character
let selected = self.selected.and_then(|id| {
client
.character_list
.characters
.iter()
.position(|i| i.character.id == Some(id))
});
if let Some(error) = &client.character_list.error { if let Some(error) = &client.character_list.error {
// TODO: use more user friendly errors with suggestions on potential solutions // TODO: use more user friendly errors with suggestions on potential solutions
// instead of directly showing error message here // instead of directly showing error message here
@ -386,15 +408,20 @@ impl Controls {
let mut characters = characters let mut characters = characters
.iter() .iter()
.zip(character_buttons.chunks_exact_mut(2)) .zip(character_buttons.chunks_exact_mut(2))
.map(|(character, buttons)| { .filter_map(|(character, buttons)| {
let mut buttons = buttons.iter_mut(); let mut buttons = buttons.iter_mut();
// TODO: eliminate option in character id?
character.character.id.map(|id| {
( (
id,
character, character,
(buttons.next().unwrap(), buttons.next().unwrap()), (buttons.next().unwrap(), buttons.next().unwrap()),
) )
}) })
})
.enumerate() .enumerate()
.map(|(i, (character, (select_button, delete_button)))| { .map(
|(i, (character_id, character, (select_button, delete_button)))| {
Overlay::new( Overlay::new(
// Delete button // Delete button
Button::new( Button::new(
@ -422,7 +449,8 @@ impl Controls {
select_button, select_button,
Column::with_children(vec![ Column::with_children(vec![
Text::new(&character.character.alias).into(), Text::new(&character.character.alias).into(),
// TODO: only construct string once when characters are // TODO: only construct string once when characters
// are
// loaded // loaded
Text::new( Text::new(
i18n.get("char_selection.level_fmt").replace( i18n.get("char_selection.level_fmt").replace(
@ -431,13 +459,15 @@ impl Controls {
), ),
) )
.into(), .into(),
Text::new(i18n.get("char_selection.uncanny_valley")) Text::new(
i18n.get("char_selection.uncanny_valley"),
)
.into(), .into(),
]), ]),
) )
.padding(10) .padding(10)
.style( .style(
style::button::Style::new(if Some(i) == *selected { style::button::Style::new(if Some(i) == selected {
imgs.selection_hover imgs.selection_hover
} else { } else {
imgs.selection imgs.selection
@ -447,14 +477,15 @@ impl Controls {
) )
.width(Length::Fill) .width(Length::Fill)
.height(Length::Fill) .height(Length::Fill)
.on_press(Message::Select(i)), .on_press(Message::Select(character_id)),
) )
.ratio_of_image(imgs.selection), .ratio_of_image(imgs.selection),
) )
.padding(12) .padding(12)
.align_x(Align::End) .align_x(Align::End)
.into() .into()
}) },
)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// Add create new character button // Add create new character button
@ -1191,20 +1222,14 @@ impl Controls {
events.push(Event::Logout); events.push(Event::Logout);
}, },
Message::EnterWorld => { Message::EnterWorld => {
if let Mode::Select { if let (Mode::Select { .. }, Some(selected)) = (&self.mode, self.selected) {
selected: Some(selected), events.push(Event::Play(selected));
..
} = &self.mode
{
// TODO: eliminate option in character id?
if let Some(id) = characters.get(*selected).and_then(|i| i.character.id) {
events.push(Event::Play(id));
}
} }
}, },
Message::Select(idx) => { Message::Select(id) => {
if let Mode::Select { selected, .. } = &mut self.mode { if let Mode::Select { .. } = &mut self.mode {
*selected = Some(idx); self.selected = Some(id);
events.push(Event::SelectCharacter(Some(id)))
} }
}, },
Message::Delete(idx) => { Message::Delete(idx) => {
@ -1276,6 +1301,11 @@ impl Controls {
if let Some(InfoContent::Deletion(idx)) = info_content { if let Some(InfoContent::Deletion(idx)) = info_content {
if let Some(id) = characters.get(*idx).and_then(|i| i.character.id) { if let Some(id) = characters.get(*idx).and_then(|i| i.character.id) {
events.push(Event::DeleteCharacter(id)); events.push(Event::DeleteCharacter(id));
// Deselect if the selected character was deleted
if Some(id) == self.selected {
self.selected = None;
events.push(Event::SelectCharacter(None));
}
} }
*info_content = Some(InfoContent::DeletingCharacter); *info_content = Some(InfoContent::DeletingCharacter);
} }
@ -1343,8 +1373,9 @@ impl Controls {
characters: &'a [CharacterItem], characters: &'a [CharacterItem],
) -> Option<(comp::Body, &'a comp::Loadout)> { ) -> Option<(comp::Body, &'a comp::Loadout)> {
match &self.mode { match &self.mode {
Mode::Select { selected, .. } => selected Mode::Select { .. } => self
.and_then(|idx| characters.get(idx)) .selected
.and_then(|id| characters.iter().find(|i| i.character.id == Some(id)))
.map(|i| (i.body, &i.loadout)), .map(|i| (i.body, &i.loadout)),
Mode::Create { loadout, body, .. } => Some((comp::Body::Humanoid(*body), loadout)), Mode::Create { loadout, body, .. } => Some((comp::Body::Humanoid(*body), loadout)),
} }
@ -1355,10 +1386,15 @@ pub struct CharSelectionUi {
ui: Ui, ui: Ui,
controls: Controls, controls: Controls,
enter_pressed: bool, enter_pressed: bool,
select_character: Option<CharacterId>,
} }
impl CharSelectionUi { impl CharSelectionUi {
pub fn new(global_state: &mut GlobalState) -> Self { pub fn new(global_state: &mut GlobalState, client: &Client) -> Self {
// Load up the last selected character for this server
let server_name = &client.server_info.name;
let selected_character = global_state.profile.get_selected_character(server_name);
// Load language // Load language
let i18n = Localization::load_expect(&i18n_asset_key( let i18n = Localization::load_expect(&i18n_asset_key(
&global_state.settings.language.selected_language, &global_state.settings.language.selected_language,
@ -1390,12 +1426,14 @@ impl CharSelectionUi {
fonts, fonts,
Imgs::load(&mut ui).expect("Failed to load images"), Imgs::load(&mut ui).expect("Failed to load images"),
i18n, i18n,
selected_character,
); );
Self { Self {
ui, ui,
controls, controls,
enter_pressed: false, enter_pressed: false,
select_character: None,
} }
} }
@ -1443,8 +1481,10 @@ impl CharSelectionUi {
self.ui.set_scaling_mode(scale_mode); self.ui.set_scaling_mode(scale_mode);
} }
pub fn select_character(&mut self, id: CharacterId) { self.select_character = Some(id); }
// TODO: do we need whole client here or just character list? // TODO: do we need whole client here or just character list?
pub fn maintain(&mut self, global_state: &mut GlobalState, client: &mut Client) -> Vec<Event> { pub fn maintain(&mut self, global_state: &mut GlobalState, client: &Client) -> Vec<Event> {
let mut events = Vec::new(); let mut events = Vec::new();
let (mut messages, _) = self.ui.maintain( let (mut messages, _) = self.ui.maintain(
@ -1457,6 +1497,10 @@ impl CharSelectionUi {
messages.push(Message::EnterWorld); messages.push(Message::EnterWorld);
} }
if let Some(id) = self.select_character.take() {
messages.push(Message::Select(id))
}
messages.into_iter().for_each(|message| { messages.into_iter().for_each(|message| {
self.controls.update( self.controls.update(
message, message,

View File

@ -13,10 +13,7 @@ pub struct CharacterProfile {
pub hotbar_slots: [Option<hud::HotbarSlotContents>; 10], pub hotbar_slots: [Option<hud::HotbarSlotContents>; 10],
} }
impl Default for CharacterProfile { const DEFAULT_SLOTS: [Option<hud::HotbarSlotContents>; 10] = [
fn default() -> Self {
CharacterProfile {
hotbar_slots: [
None, None,
None, None,
None, None,
@ -27,7 +24,12 @@ impl Default for CharacterProfile {
None, None,
None, None,
None, None,
], ];
impl Default for CharacterProfile {
fn default() -> Self {
CharacterProfile {
hotbar_slots: DEFAULT_SLOTS,
} }
} }
} }
@ -38,12 +40,15 @@ impl Default for CharacterProfile {
pub struct ServerProfile { pub struct ServerProfile {
/// A map of character's by id to their CharacterProfile. /// A map of character's by id to their CharacterProfile.
pub characters: HashMap<CharacterId, CharacterProfile>, pub characters: HashMap<CharacterId, CharacterProfile>,
// Selected character in the chararacter selection screen
pub selected_character: Option<CharacterId>,
} }
impl Default for ServerProfile { impl Default for ServerProfile {
fn default() -> Self { fn default() -> Self {
ServerProfile { ServerProfile {
characters: HashMap::new(), characters: HashMap::new(),
selected_character: None,
} }
} }
} }
@ -106,26 +111,23 @@ impl Profile {
/// Get the hotbar_slots for the requested character_id. /// Get the hotbar_slots for the requested character_id.
/// ///
/// if the server or character does not exist then the appropriate fields /// If the server or character does not exist then the default hotbar_slots
/// will be initialised and default hotbar_slots (empty) returned. /// (empty) is returned.
/// ///
/// # Arguments /// # Arguments
/// ///
/// * server - current server the character is on. /// * server - current server the character is on.
/// * character_id - id of the character. /// * character_id - id of the character.
pub fn get_hotbar_slots( pub fn get_hotbar_slots(
&mut self, &self,
server: &str, server: &str,
character_id: CharacterId, character_id: CharacterId,
) -> [Option<hud::HotbarSlotContents>; 10] { ) -> [Option<hud::HotbarSlotContents>; 10] {
self.servers self.servers
.entry(server.to_string()) .get(server)
.or_insert(ServerProfile::default()) .and_then(|s| s.characters.get(&character_id))
// Get or update the CharacterProfile. .map(|c| c.hotbar_slots)
.characters .unwrap_or(DEFAULT_SLOTS)
.entry(character_id)
.or_insert(CharacterProfile::default())
.hotbar_slots
} }
/// Set the hotbar_slots for the requested character_id. /// Set the hotbar_slots for the requested character_id.
@ -154,6 +156,41 @@ impl Profile {
.hotbar_slots = slots; .hotbar_slots = slots;
} }
/// Get the selected_character for the provided server.
///
/// if the server does not exist then the default selected_character (None)
/// is returned.
///
/// # Arguments
///
/// * server - current server the character is on.
pub fn get_selected_character(&self, server: &str) -> Option<CharacterId> {
self.servers
.get(server)
.map(|s| s.selected_character)
.unwrap_or_default()
}
/// Set the selected_character for the provided server.
///
/// If the server does not exist then the appropriate fields
/// will be initialised and the selected_character added.
///
/// # Arguments
///
/// * server - current server the character is on.
/// * selected_character - option containing selected character ID
pub fn set_selected_character(
&mut self,
server: &str,
selected_character: Option<CharacterId>,
) {
self.servers
.entry(server.to_string())
.or_insert(ServerProfile::default())
.selected_character = selected_character;
}
/// Save the current profile to disk. /// Save the current profile to disk.
fn save_to_file(&self) -> std::io::Result<()> { fn save_to_file(&self) -> std::io::Result<()> {
let path = Profile::get_path(); let path = Profile::get_path();
@ -188,7 +225,7 @@ mod tests {
#[test] #[test]
fn test_get_slots_with_empty_profile() { fn test_get_slots_with_empty_profile() {
let mut profile = Profile::default(); let profile = Profile::default();
let slots = profile.get_hotbar_slots("TestServer", 12345); let slots = profile.get_hotbar_slots("TestServer", 12345);
assert_eq!(slots, [ assert_eq!(slots, [
None, None,

View File

@ -178,6 +178,7 @@ impl SessionState {
global_state.settings.save_to_file_warn(); global_state.settings.save_to_file_warn();
}, },
client::Event::Outcome(outcome) => outcomes.push(outcome), client::Event::Outcome(outcome) => outcomes.push(outcome),
client::Event::CharacterCreated(_) => {},
} }
} }
@ -928,7 +929,7 @@ impl PlayState for SessionState {
HudEvent::ChangeHotbarState(state) => { HudEvent::ChangeHotbarState(state) => {
let client = self.client.borrow(); let client = self.client.borrow();
let server = &client.server_info.name; let server_name = &client.server_info.name;
// If we are changing the hotbar state this CANNOT be None. // If we are changing the hotbar state this CANNOT be None.
let character_id = match client.presence().unwrap() { let character_id = match client.presence().unwrap() {
PresenceKind::Character(id) => id, PresenceKind::Character(id) => id,
@ -938,9 +939,11 @@ impl PlayState for SessionState {
}; };
// Get or update the ServerProfile. // Get or update the ServerProfile.
global_state global_state.profile.set_hotbar_slots(
.profile server_name,
.set_hotbar_slots(server, character_id, state.slots); character_id,
state.slots,
);
global_state.profile.save_to_file_warn(); global_state.profile.save_to_file_warn();