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)
|
- 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
|
||||||
|
|
||||||
|
@ -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?)?;
|
||||||
|
@ -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 => {
|
||||||
|
@ -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)
|
||||||
},
|
},
|
||||||
|
@ -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,
|
||||||
|
@ -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.
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user