mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Save the selected character, deselect character when deleting, auto select newly created character
This commit is contained in:
parent
2c2e4813fd
commit
4f2512f126
@ -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)
|
||||
- NPCs use all three weapon skills in combat
|
||||
- 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
|
||||
|
||||
|
@ -67,6 +67,7 @@ pub enum Event {
|
||||
Notification(Notification),
|
||||
SetViewDistance(u32),
|
||||
Outcome(Outcome),
|
||||
CharacterCreated(CharacterId),
|
||||
}
|
||||
|
||||
pub struct Client {
|
||||
@ -1422,7 +1423,11 @@ impl Client {
|
||||
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 {
|
||||
ServerGeneral::CharacterListUpdate(character_list) => {
|
||||
self.character_list.characters = character_list;
|
||||
@ -1438,6 +1443,9 @@ impl Client {
|
||||
self.clean_state();
|
||||
self.character_list.error = Some(error);
|
||||
},
|
||||
ServerGeneral::CharacterCreated(character_id) => {
|
||||
events.push(Event::CharacterCreated(character_id));
|
||||
},
|
||||
ServerGeneral::CharacterSuccess => {
|
||||
debug!("client is now in ingame state on server");
|
||||
if let Some(vd) = self.view_distance {
|
||||
@ -1490,7 +1498,7 @@ impl Client {
|
||||
self.handle_ping_msg(msg?)?;
|
||||
}
|
||||
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 {
|
||||
self.handle_server_in_game_msg(frontend_events, msg?)?;
|
||||
|
@ -69,6 +69,8 @@ pub enum ServerGeneral {
|
||||
CharacterListUpdate(Vec<CharacterItem>),
|
||||
/// An error occurred while creating or deleting a character
|
||||
CharacterActionError(String),
|
||||
/// A new character was created
|
||||
CharacterCreated(crate::character::CharacterId),
|
||||
CharacterSuccess,
|
||||
//Ingame related
|
||||
GroupUpdate(comp::group::ChangeNotification<sync::Uid>),
|
||||
@ -194,7 +196,8 @@ impl ServerMsg {
|
||||
//Character Screen related
|
||||
ServerGeneral::CharacterDataLoadError(_)
|
||||
| ServerGeneral::CharacterListUpdate(_)
|
||||
| ServerGeneral::CharacterActionError(_) => {
|
||||
| ServerGeneral::CharacterActionError(_)
|
||||
| ServerGeneral::CharacterCreated(_) => {
|
||||
c_type != ClientType::ChatOnly && presence.is_none()
|
||||
},
|
||||
ServerGeneral::CharacterSuccess => {
|
||||
|
@ -73,6 +73,7 @@ impl Client {
|
||||
ServerGeneral::CharacterDataLoadError(_)
|
||||
| ServerGeneral::CharacterListUpdate(_)
|
||||
| ServerGeneral::CharacterActionError(_)
|
||||
| ServerGeneral::CharacterCreated(_)
|
||||
| ServerGeneral::CharacterSuccess => {
|
||||
self.character_screen_stream.try_lock().unwrap().send(g)
|
||||
},
|
||||
@ -149,6 +150,7 @@ impl Client {
|
||||
ServerGeneral::CharacterDataLoadError(_)
|
||||
| ServerGeneral::CharacterListUpdate(_)
|
||||
| ServerGeneral::CharacterActionError(_)
|
||||
| ServerGeneral::CharacterCreated(_)
|
||||
| ServerGeneral::CharacterSuccess => {
|
||||
PreparedMsg::new(1, &g, &self.character_screen_stream)
|
||||
},
|
||||
|
@ -62,7 +62,7 @@ use futures_executor::block_on;
|
||||
use metrics::{PhysicsMetrics, ServerMetrics, StateTickMetrics, TickMetrics};
|
||||
use network::{Network, Pid, ProtocolAddr};
|
||||
use persistence::{
|
||||
character_loader::{CharacterLoader, CharacterLoaderResponseType},
|
||||
character_loader::{CharacterLoader, CharacterLoaderResponseKind},
|
||||
character_updater::CharacterUpdater,
|
||||
};
|
||||
use specs::{join::Join, Builder, Entity as EcsEntity, RunNow, SystemData, WorldExt};
|
||||
@ -539,7 +539,7 @@ impl Server {
|
||||
.read_resource::<persistence::character_loader::CharacterLoader>()
|
||||
.messages()
|
||||
.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(
|
||||
query_result.entity,
|
||||
ServerGeneral::CharacterListUpdate(character_list_data),
|
||||
@ -549,7 +549,23 @@ impl Server {
|
||||
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 {
|
||||
Ok(character_data) => ServerEvent::UpdateCharacterData {
|
||||
entity: query_result.entity,
|
||||
|
@ -17,7 +17,7 @@ use crate::{
|
||||
convert_stats_from_database, convert_stats_to_database,
|
||||
convert_waypoint_to_database_json,
|
||||
},
|
||||
character_loader::{CharacterDataResult, CharacterListResult},
|
||||
character_loader::{CharacterCreationResult, CharacterDataResult, CharacterListResult},
|
||||
error::Error::DatabaseError,
|
||||
json_models::CharacterPosition,
|
||||
PersistedComponents,
|
||||
@ -170,7 +170,7 @@ pub fn create_character(
|
||||
persisted_components: PersistedComponents,
|
||||
connection: VelorenTransaction,
|
||||
map: &AbilityMap,
|
||||
) -> CharacterListResult {
|
||||
) -> CharacterCreationResult {
|
||||
use schema::item::dsl::*;
|
||||
|
||||
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.
|
||||
|
@ -12,6 +12,7 @@ use std::path::Path;
|
||||
use tracing::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>;
|
||||
type CharacterLoaderRequest = (specs::Entity, CharacterLoaderRequestKind);
|
||||
|
||||
@ -38,16 +39,17 @@ enum CharacterLoaderRequestKind {
|
||||
/// Wrapper for results for character actions. Can be a list of
|
||||
/// characters, or component data belonging to an individual character
|
||||
#[derive(Debug)]
|
||||
pub enum CharacterLoaderResponseType {
|
||||
pub enum CharacterLoaderResponseKind {
|
||||
CharacterList(CharacterListResult),
|
||||
CharacterData(Box<CharacterDataResult>),
|
||||
CharacterCreation(CharacterCreationResult),
|
||||
}
|
||||
|
||||
/// Common message format dispatched in response to an update request
|
||||
#[derive(Debug)]
|
||||
pub struct CharacterLoaderResponse {
|
||||
pub entity: specs::Entity,
|
||||
pub result: CharacterLoaderResponseType,
|
||||
pub result: CharacterLoaderResponseKind,
|
||||
}
|
||||
|
||||
/// A bi-directional messaging resource for making requests to modify or load
|
||||
@ -80,14 +82,16 @@ impl CharacterLoader {
|
||||
for request in internal_rx {
|
||||
let (entity, kind) = request;
|
||||
|
||||
if let Err(e) = internal_tx.send(CharacterLoaderResponse {
|
||||
if let Err(e) =
|
||||
internal_tx.send(CharacterLoaderResponse {
|
||||
entity,
|
||||
result: match kind {
|
||||
CharacterLoaderRequestKind::CreateCharacter {
|
||||
player_uuid,
|
||||
character_alias,
|
||||
persisted_components,
|
||||
} => CharacterLoaderResponseType::CharacterList(conn.transaction(|txn| {
|
||||
} => CharacterLoaderResponseKind::CharacterCreation(conn.transaction(
|
||||
|txn| {
|
||||
create_character(
|
||||
&player_uuid,
|
||||
&character_alias,
|
||||
@ -95,30 +99,30 @@ impl CharacterLoader {
|
||||
txn,
|
||||
&map,
|
||||
)
|
||||
})),
|
||||
},
|
||||
)),
|
||||
CharacterLoaderRequestKind::DeleteCharacter {
|
||||
player_uuid,
|
||||
character_id,
|
||||
} => CharacterLoaderResponseType::CharacterList(conn.transaction(|txn| {
|
||||
delete_character(&player_uuid, character_id, txn, &map)
|
||||
})),
|
||||
} => CharacterLoaderResponseKind::CharacterList(conn.transaction(
|
||||
|txn| delete_character(&player_uuid, character_id, txn, &map),
|
||||
)),
|
||||
CharacterLoaderRequestKind::LoadCharacterList { player_uuid } => {
|
||||
CharacterLoaderResponseType::CharacterList(
|
||||
conn.transaction(|txn| {
|
||||
load_character_list(&player_uuid, txn, &map)
|
||||
}),
|
||||
)
|
||||
CharacterLoaderResponseKind::CharacterList(conn.transaction(
|
||||
|txn| load_character_list(&player_uuid, txn, &map),
|
||||
))
|
||||
},
|
||||
CharacterLoaderRequestKind::LoadCharacterData {
|
||||
player_uuid,
|
||||
character_id,
|
||||
} => {
|
||||
CharacterLoaderResponseType::CharacterData(Box::new(conn.transaction(
|
||||
|txn| load_character_data(player_uuid, character_id, txn, &map),
|
||||
)))
|
||||
} => CharacterLoaderResponseKind::CharacterData(Box::new(
|
||||
conn.transaction(|txn| {
|
||||
load_character_data(player_uuid, character_id, txn, &map)
|
||||
}),
|
||||
)),
|
||||
},
|
||||
},
|
||||
}) {
|
||||
})
|
||||
{
|
||||
error!(?e, "Could not send send persistence request");
|
||||
}
|
||||
}
|
||||
|
@ -30,8 +30,10 @@ impl CharSelectionState {
|
||||
Some("fixture.selection_bg"),
|
||||
&*client.borrow(),
|
||||
);
|
||||
let char_selection_ui = CharSelectionUi::new(global_state, &*client.borrow());
|
||||
|
||||
Self {
|
||||
char_selection_ui: CharSelectionUi::new(global_state),
|
||||
char_selection_ui,
|
||||
client,
|
||||
scene,
|
||||
}
|
||||
@ -98,7 +100,7 @@ impl PlayState for CharSelectionState {
|
||||
// Maintain the UI.
|
||||
let events = self
|
||||
.char_selection_ui
|
||||
.maintain(global_state, &mut self.client.borrow_mut());
|
||||
.maintain(global_state, &self.client.borrow());
|
||||
|
||||
for event in events {
|
||||
match event {
|
||||
@ -124,6 +126,15 @@ impl PlayState for CharSelectionState {
|
||||
ui::Event::ClearCharacterListError => {
|
||||
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;
|
||||
},
|
||||
client::Event::CharacterCreated(character_id) => {
|
||||
self.char_selection_ui.select_character(character_id);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
@ -130,13 +130,12 @@ pub enum Event {
|
||||
},
|
||||
DeleteCharacter(CharacterId),
|
||||
ClearCharacterListError,
|
||||
SelectCharacter(Option<CharacterId>),
|
||||
}
|
||||
|
||||
enum Mode {
|
||||
Select {
|
||||
info_content: Option<InfoContent>,
|
||||
// Index of selected character
|
||||
selected: Option<usize>,
|
||||
|
||||
characters_scroll: scrollable::State,
|
||||
character_buttons: Vec<button::State>,
|
||||
@ -168,7 +167,6 @@ impl Mode {
|
||||
pub fn select(info_content: Option<InfoContent>) -> Self {
|
||||
Self::Select {
|
||||
info_content,
|
||||
selected: None,
|
||||
characters_scroll: Default::default(),
|
||||
character_buttons: Vec::new(),
|
||||
new_character_button: Default::default(),
|
||||
@ -231,8 +229,9 @@ struct Controls {
|
||||
tooltip_manager: TooltipManager,
|
||||
// Zone for rotating the character with the mouse
|
||||
mouse_detector: mouse_detector::State,
|
||||
// enter: bool,
|
||||
mode: Mode,
|
||||
// Id of the selected character
|
||||
selected: Option<CharacterId>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -240,7 +239,7 @@ enum Message {
|
||||
Back,
|
||||
Logout,
|
||||
EnterWorld,
|
||||
Select(usize),
|
||||
Select(CharacterId),
|
||||
Delete(usize),
|
||||
NewCharacter,
|
||||
CreateCharacter,
|
||||
@ -265,7 +264,12 @@ enum Message {
|
||||
}
|
||||
|
||||
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 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),
|
||||
mouse_detector: Default::default(),
|
||||
mode: Mode::select(Some(InfoContent::LoadingCharacters)),
|
||||
selected,
|
||||
}
|
||||
}
|
||||
|
||||
@ -331,7 +336,6 @@ impl Controls {
|
||||
let content = match &mut self.mode {
|
||||
Mode::Select {
|
||||
ref mut info_content,
|
||||
selected,
|
||||
ref mut characters_scroll,
|
||||
ref mut character_buttons,
|
||||
ref mut new_character_button,
|
||||
@ -340,6 +344,24 @@ impl Controls {
|
||||
ref mut yes_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 {
|
||||
// TODO: use more user friendly errors with suggestions on potential solutions
|
||||
// instead of directly showing error message here
|
||||
@ -386,15 +408,20 @@ impl Controls {
|
||||
let mut characters = characters
|
||||
.iter()
|
||||
.zip(character_buttons.chunks_exact_mut(2))
|
||||
.map(|(character, buttons)| {
|
||||
.filter_map(|(character, buttons)| {
|
||||
let mut buttons = buttons.iter_mut();
|
||||
// TODO: eliminate option in character id?
|
||||
character.character.id.map(|id| {
|
||||
(
|
||||
id,
|
||||
character,
|
||||
(buttons.next().unwrap(), buttons.next().unwrap()),
|
||||
)
|
||||
})
|
||||
})
|
||||
.enumerate()
|
||||
.map(|(i, (character, (select_button, delete_button)))| {
|
||||
.map(
|
||||
|(i, (character_id, character, (select_button, delete_button)))| {
|
||||
Overlay::new(
|
||||
// Delete button
|
||||
Button::new(
|
||||
@ -422,7 +449,8 @@ impl Controls {
|
||||
select_button,
|
||||
Column::with_children(vec![
|
||||
Text::new(&character.character.alias).into(),
|
||||
// TODO: only construct string once when characters are
|
||||
// TODO: only construct string once when characters
|
||||
// are
|
||||
// loaded
|
||||
Text::new(
|
||||
i18n.get("char_selection.level_fmt").replace(
|
||||
@ -431,13 +459,15 @@ impl Controls {
|
||||
),
|
||||
)
|
||||
.into(),
|
||||
Text::new(i18n.get("char_selection.uncanny_valley"))
|
||||
Text::new(
|
||||
i18n.get("char_selection.uncanny_valley"),
|
||||
)
|
||||
.into(),
|
||||
]),
|
||||
)
|
||||
.padding(10)
|
||||
.style(
|
||||
style::button::Style::new(if Some(i) == *selected {
|
||||
style::button::Style::new(if Some(i) == selected {
|
||||
imgs.selection_hover
|
||||
} else {
|
||||
imgs.selection
|
||||
@ -447,14 +477,15 @@ impl Controls {
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.height(Length::Fill)
|
||||
.on_press(Message::Select(i)),
|
||||
.on_press(Message::Select(character_id)),
|
||||
)
|
||||
.ratio_of_image(imgs.selection),
|
||||
)
|
||||
.padding(12)
|
||||
.align_x(Align::End)
|
||||
.into()
|
||||
})
|
||||
},
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Add create new character button
|
||||
@ -1191,20 +1222,14 @@ impl Controls {
|
||||
events.push(Event::Logout);
|
||||
},
|
||||
Message::EnterWorld => {
|
||||
if let Mode::Select {
|
||||
selected: Some(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));
|
||||
}
|
||||
if let (Mode::Select { .. }, Some(selected)) = (&self.mode, self.selected) {
|
||||
events.push(Event::Play(selected));
|
||||
}
|
||||
},
|
||||
Message::Select(idx) => {
|
||||
if let Mode::Select { selected, .. } = &mut self.mode {
|
||||
*selected = Some(idx);
|
||||
Message::Select(id) => {
|
||||
if let Mode::Select { .. } = &mut self.mode {
|
||||
self.selected = Some(id);
|
||||
events.push(Event::SelectCharacter(Some(id)))
|
||||
}
|
||||
},
|
||||
Message::Delete(idx) => {
|
||||
@ -1276,6 +1301,11 @@ impl Controls {
|
||||
if let Some(InfoContent::Deletion(idx)) = info_content {
|
||||
if let Some(id) = characters.get(*idx).and_then(|i| i.character.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);
|
||||
}
|
||||
@ -1343,8 +1373,9 @@ impl Controls {
|
||||
characters: &'a [CharacterItem],
|
||||
) -> Option<(comp::Body, &'a comp::Loadout)> {
|
||||
match &self.mode {
|
||||
Mode::Select { selected, .. } => selected
|
||||
.and_then(|idx| characters.get(idx))
|
||||
Mode::Select { .. } => self
|
||||
.selected
|
||||
.and_then(|id| characters.iter().find(|i| i.character.id == Some(id)))
|
||||
.map(|i| (i.body, &i.loadout)),
|
||||
Mode::Create { loadout, body, .. } => Some((comp::Body::Humanoid(*body), loadout)),
|
||||
}
|
||||
@ -1355,10 +1386,15 @@ pub struct CharSelectionUi {
|
||||
ui: Ui,
|
||||
controls: Controls,
|
||||
enter_pressed: bool,
|
||||
select_character: Option<CharacterId>,
|
||||
}
|
||||
|
||||
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
|
||||
let i18n = Localization::load_expect(&i18n_asset_key(
|
||||
&global_state.settings.language.selected_language,
|
||||
@ -1390,12 +1426,14 @@ impl CharSelectionUi {
|
||||
fonts,
|
||||
Imgs::load(&mut ui).expect("Failed to load images"),
|
||||
i18n,
|
||||
selected_character,
|
||||
);
|
||||
|
||||
Self {
|
||||
ui,
|
||||
controls,
|
||||
enter_pressed: false,
|
||||
select_character: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1443,8 +1481,10 @@ impl CharSelectionUi {
|
||||
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?
|
||||
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 messages, _) = self.ui.maintain(
|
||||
@ -1457,6 +1497,10 @@ impl CharSelectionUi {
|
||||
messages.push(Message::EnterWorld);
|
||||
}
|
||||
|
||||
if let Some(id) = self.select_character.take() {
|
||||
messages.push(Message::Select(id))
|
||||
}
|
||||
|
||||
messages.into_iter().for_each(|message| {
|
||||
self.controls.update(
|
||||
message,
|
||||
|
@ -13,10 +13,7 @@ pub struct CharacterProfile {
|
||||
pub hotbar_slots: [Option<hud::HotbarSlotContents>; 10],
|
||||
}
|
||||
|
||||
impl Default for CharacterProfile {
|
||||
fn default() -> Self {
|
||||
CharacterProfile {
|
||||
hotbar_slots: [
|
||||
const DEFAULT_SLOTS: [Option<hud::HotbarSlotContents>; 10] = [
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
@ -27,7 +24,12 @@ impl Default for CharacterProfile {
|
||||
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 {
|
||||
/// A map of character's by id to their CharacterProfile.
|
||||
pub characters: HashMap<CharacterId, CharacterProfile>,
|
||||
// Selected character in the chararacter selection screen
|
||||
pub selected_character: Option<CharacterId>,
|
||||
}
|
||||
|
||||
impl Default for ServerProfile {
|
||||
fn default() -> Self {
|
||||
ServerProfile {
|
||||
characters: HashMap::new(),
|
||||
selected_character: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -106,26 +111,23 @@ impl Profile {
|
||||
|
||||
/// Get the hotbar_slots for the requested character_id.
|
||||
///
|
||||
/// if the server or character does not exist then the appropriate fields
|
||||
/// will be initialised and default hotbar_slots (empty) returned.
|
||||
/// If the server or character does not exist then the default hotbar_slots
|
||||
/// (empty) is returned.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * server - current server the character is on.
|
||||
/// * character_id - id of the character.
|
||||
pub fn get_hotbar_slots(
|
||||
&mut self,
|
||||
&self,
|
||||
server: &str,
|
||||
character_id: CharacterId,
|
||||
) -> [Option<hud::HotbarSlotContents>; 10] {
|
||||
self.servers
|
||||
.entry(server.to_string())
|
||||
.or_insert(ServerProfile::default())
|
||||
// Get or update the CharacterProfile.
|
||||
.characters
|
||||
.entry(character_id)
|
||||
.or_insert(CharacterProfile::default())
|
||||
.hotbar_slots
|
||||
.get(server)
|
||||
.and_then(|s| s.characters.get(&character_id))
|
||||
.map(|c| c.hotbar_slots)
|
||||
.unwrap_or(DEFAULT_SLOTS)
|
||||
}
|
||||
|
||||
/// Set the hotbar_slots for the requested character_id.
|
||||
@ -154,6 +156,41 @@ impl Profile {
|
||||
.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.
|
||||
fn save_to_file(&self) -> std::io::Result<()> {
|
||||
let path = Profile::get_path();
|
||||
@ -188,7 +225,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_get_slots_with_empty_profile() {
|
||||
let mut profile = Profile::default();
|
||||
let profile = Profile::default();
|
||||
let slots = profile.get_hotbar_slots("TestServer", 12345);
|
||||
assert_eq!(slots, [
|
||||
None,
|
||||
|
@ -178,6 +178,7 @@ impl SessionState {
|
||||
global_state.settings.save_to_file_warn();
|
||||
},
|
||||
client::Event::Outcome(outcome) => outcomes.push(outcome),
|
||||
client::Event::CharacterCreated(_) => {},
|
||||
}
|
||||
}
|
||||
|
||||
@ -928,7 +929,7 @@ impl PlayState for SessionState {
|
||||
HudEvent::ChangeHotbarState(state) => {
|
||||
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.
|
||||
let character_id = match client.presence().unwrap() {
|
||||
PresenceKind::Character(id) => id,
|
||||
@ -938,9 +939,11 @@ impl PlayState for SessionState {
|
||||
};
|
||||
|
||||
// Get or update the ServerProfile.
|
||||
global_state
|
||||
.profile
|
||||
.set_hotbar_slots(server, character_id, state.slots);
|
||||
global_state.profile.set_hotbar_slots(
|
||||
server_name,
|
||||
character_id,
|
||||
state.slots,
|
||||
);
|
||||
|
||||
global_state.profile.save_to_file_warn();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user