Remove persistence loading error from SkillSet.

This is needed (for now) in order to parallelize ingame_chat, because
one of the handled messages updates this value on the server.  It turns
out that the value is not actually used on the server, only the client,
so this was mostly a matter of threading this back to the correct place.
Additionally, we took the opportunity to modify the UI to not log you
into the game until your character was confirmed to be loaded, which
was a todo item that lets us simplify some error handling logic and
remove stuff from global state.
This commit is contained in:
Joshua Yanovski 2022-09-15 17:11:10 -07:00
parent 8dfe53e788
commit 4b5a9fe0f4
21 changed files with 218 additions and 155 deletions

View File

@ -0,0 +1,24 @@
char_selection-loading_characters = Loading characters...
char_selection-delete_permanently = Permanently delete this Character?
char_selection-deleting_character = Deleting Character...
char_selection-change_server = Change Server
char_selection-enter_world = Enter World
char_selection-spectate = Spectate World
char_selection-joining_character = Joining world...
char_selection-logout = Logout
char_selection-create_new_character = Create New Character
char_selection-creating_character = Creating Character...
char_selection-character_creation = Character Creation
char_selection-human_default = Human Default
char_selection-level_fmt = Level { $level_nb }
char_selection-uncanny_valley = Wilderness
char_selection-plains_of_uncertainty = Plains of Uncertainty
char_selection-beard = Beard
char_selection-hair_style = Hair Style
char_selection-hair_color = Hair Color
char_selection-eye_color = Eye Color
char_selection-skin = Skin
char_selection-eyeshape = Eye Details
char_selection-accessories = Accessories
char_selection-create_info_name = Your Character needs a name!
char_selection-version_mismatch = WARNING! This server is running a different, possibly incompatible game version. Please update your game.

View File

@ -32,7 +32,7 @@ use common::{
GroupManip, InputKind, InventoryAction, InventoryEvent, InventoryUpdateEvent,
MapMarkerChange, UtteranceKind,
},
event::{EventBus, LocalEvent},
event::{EventBus, LocalEvent, UpdateCharacterMetadata},
grid::Grid,
link::Is,
lod,
@ -107,6 +107,7 @@ pub enum Event {
Outcome(Outcome),
CharacterCreated(CharacterId),
CharacterEdited(CharacterId),
CharacterJoined(UpdateCharacterMetadata),
CharacterError(String),
MapMarker(comp::MapMarkerUpdate),
}
@ -889,7 +890,6 @@ impl Client {
| ClientGeneral::UnlockSkillGroup(_)
| ClientGeneral::RequestPlayerPhysics { .. }
| ClientGeneral::RequestLossyTerrainCompression { .. }
| ClientGeneral::AcknowledgePersistenceLoadError
| ClientGeneral::UpdateMapMarker(_) => {
#[cfg(feature = "tracy")]
{
@ -1660,10 +1660,6 @@ impl Client {
}))
}
pub fn acknolwedge_persistence_load_error(&mut self) {
self.send_msg(ClientGeneral::AcknowledgePersistenceLoadError)
}
/// Execute a single client tick, handle input and update the game state by
/// the given duration.
pub fn tick(
@ -2405,7 +2401,11 @@ impl Client {
warn!("CharacterActionError: {:?}.", error);
events.push(Event::CharacterError(error));
},
ServerGeneral::CharacterDataLoadError(error) => {
ServerGeneral::CharacterDataLoadResult(Ok(metadata)) => {
trace!("Handling join result by server");
events.push(Event::CharacterJoined(metadata));
},
ServerGeneral::CharacterDataLoadResult(Err(error)) => {
trace!("Handling join error by server");
self.presence = None;
self.clean_state();

View File

@ -96,7 +96,6 @@ pub enum ClientGeneral {
RequestLossyTerrainCompression {
lossy_terrain_compression: bool,
},
AcknowledgePersistenceLoadError,
}
impl ClientMsg {
@ -137,7 +136,6 @@ impl ClientMsg {
| ClientGeneral::UnlockSkillGroup(_)
| ClientGeneral::RequestPlayerPhysics { .. }
| ClientGeneral::RequestLossyTerrainCompression { .. }
| ClientGeneral::AcknowledgePersistenceLoadError
| ClientGeneral::UpdateMapMarker(_) => {
c_type == ClientType::Game && presence.is_some()
},

View File

@ -7,6 +7,7 @@ use common::{
calendar::Calendar,
character::{self, CharacterItem},
comp::{self, invite::InviteKind, item::MaterialStatManifest},
event::UpdateCharacterMetadata,
lod,
outcome::Outcome,
recipe::{ComponentRecipeBook, RecipeBook},
@ -135,8 +136,8 @@ impl SerializedTerrainChunk<'_> {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ServerGeneral<'a> {
//Character Screen related
/// An error occurred while loading character data
CharacterDataLoadError(String),
/// Result of loading character data
CharacterDataLoadResult(Result<UpdateCharacterMetadata, String>),
/// A list of characters belonging to the a authenticated player was sent
CharacterListUpdate(Vec<CharacterItem>),
/// An error occurred while creating or deleting a character
@ -292,7 +293,7 @@ impl ServerMsg<'_> {
registered
&& match g {
//Character Screen related
ServerGeneral::CharacterDataLoadError(_)
ServerGeneral::CharacterDataLoadResult(_)
| ServerGeneral::CharacterListUpdate(_)
| ServerGeneral::CharacterActionError(_)
| ServerGeneral::CharacterEdited(_)

View File

@ -239,9 +239,6 @@ pub struct SkillSet {
skills: HashMap<Skill, u16>,
pub modify_health: bool,
pub modify_energy: bool,
/// Used to indicate to the frontend that there was an error in loading the
/// skillset from the database
pub persistence_load_error: Option<SkillsPersistenceError>,
}
impl Component for SkillSet {
@ -259,7 +256,6 @@ impl Default for SkillSet {
skills: SkillSet::initial_skills(),
modify_health: false,
modify_energy: false,
persistence_load_error: None,
};
// Insert default skill groups
@ -281,17 +277,20 @@ impl SkillSet {
skills
}
/// NOTE: This does *not* return an error on failure, since we can partially
/// recover from some failures. Instead, it returns the error in the
/// second return value; make sure to handle it if present!
pub fn load_from_database(
skill_groups: HashMap<SkillGroupKind, SkillGroup>,
mut all_skills: HashMap<SkillGroupKind, Result<Vec<Skill>, SkillsPersistenceError>>,
) -> Self {
) -> (Self, Option<SkillsPersistenceError>) {
let mut skillset = SkillSet {
skill_groups,
skills: SkillSet::initial_skills(),
modify_health: true,
modify_energy: true,
persistence_load_error: None,
};
let mut persistence_load_error = None;
// Loops while checking the all_skills hashmap. For as long as it can find an
// entry where the skill group kind is unlocked, insert the skills corresponding
@ -317,18 +316,16 @@ impl SkillSet {
{
skillset = backup_skillset;
// If unlocking failed, set persistence_load_error
skillset.persistence_load_error =
persistence_load_error =
Some(SkillsPersistenceError::SkillsUnlockFailed)
}
},
Err(persistence_error) => {
skillset.persistence_load_error = Some(persistence_error)
},
Err(persistence_error) => persistence_load_error = Some(persistence_error),
}
}
}
skillset
(skillset, persistence_load_error)
}
/// Checks if a particular skill group is accessible for an entity

View File

@ -35,6 +35,8 @@ pub enum LocalEvent {
CreateOutcome(Outcome),
}
pub type UpdateCharacterMetadata = Option<comp::skillset::SkillsPersistenceError>;
#[allow(clippy::large_enum_variant)] // TODO: Pending review in #587
#[derive(strum::EnumDiscriminants)]
#[strum_discriminants(repr(usize))]
@ -120,6 +122,7 @@ pub enum ServerEvent {
comp::ActiveAbilities,
Option<comp::MapMarker>,
),
metadata: UpdateCharacterMetadata,
},
ExitIngame {
entity: EcsEntity,

View File

@ -94,7 +94,7 @@ impl Client {
ServerMsg::General(g) => {
match g {
//Character Screen related
ServerGeneral::CharacterDataLoadError(_)
ServerGeneral::CharacterDataLoadResult(_)
| ServerGeneral::CharacterListUpdate(_)
| ServerGeneral::CharacterActionError(_)
| ServerGeneral::CharacterCreated(_)
@ -164,7 +164,7 @@ impl Client {
ServerMsg::General(g) => {
match g {
//Character Screen related
ServerGeneral::CharacterDataLoadError(_)
ServerGeneral::CharacterDataLoadResult(_)
| ServerGeneral::CharacterListUpdate(_)
| ServerGeneral::CharacterActionError(_)
| ServerGeneral::CharacterCreated(_)

View File

@ -11,7 +11,7 @@ use common::{
Object, Ori, PidController, Poise, Pos, Projectile, Scale, SkillSet, Stats, Vel,
WaypointArea,
},
event::EventBus,
event::{EventBus, UpdateCharacterMetadata},
lottery::LootSpec,
outcome::Outcome,
rtsim::RtSimEntity,
@ -37,6 +37,7 @@ pub fn handle_loaded_character_data(
server: &mut Server,
entity: EcsEntity,
loaded_components: PersistedComponents,
metadata: UpdateCharacterMetadata,
) {
if let Some(marker) = loaded_components.map_marker {
server.notify_client(
@ -50,6 +51,8 @@ pub fn handle_loaded_character_data(
.state
.update_character_data(entity, loaded_components);
sys::subscription::initialize_region_subscription(server.state.ecs(), entity);
// We notify the client with the metadata result from the operation.
server.notify_client(entity, ServerGeneral::CharacterDataLoadResult(Ok(metadata)));
}
pub fn handle_create_npc(

View File

@ -139,7 +139,11 @@ impl Server {
entity,
character_id,
} => handle_initialize_character(self, entity, character_id),
ServerEvent::UpdateCharacterData { entity, components } => {
ServerEvent::UpdateCharacterData {
entity,
components,
metadata,
} => {
let (
body,
stats,
@ -160,7 +164,7 @@ impl Server {
active_abilities,
map_marker,
};
handle_loaded_character_data(self, entity, components);
handle_loaded_character_data(self, entity, components, metadata);
},
ServerEvent::ExitIngame { entity } => {
cancel_trade_for(self, entity);

View File

@ -908,7 +908,7 @@ impl Server {
},
CharacterLoaderResponseKind::CharacterData(result) => {
let message = match *result {
Ok(character_data) => {
Ok((character_data, skill_set_persistence_load_error)) => {
let PersistedComponents {
body,
stats,
@ -932,6 +932,7 @@ impl Server {
ServerEvent::UpdateCharacterData {
entity: query_result.entity,
components: character_data,
metadata: skill_set_persistence_load_error,
}
},
Err(error) => {
@ -940,7 +941,7 @@ impl Server {
// to display
self.notify_client(
query_result.entity,
ServerGeneral::CharacterDataLoadError(error.to_string()),
ServerGeneral::CharacterDataLoadResult(Err(error.to_string())),
);
// Clean up the entity data on the server

View File

@ -254,21 +254,26 @@ pub fn load_character_data(
})
})?;
Ok(PersistedComponents {
body: convert_body_from_database(&body_data.variant, &body_data.body_data)?,
stats: convert_stats_from_database(character_data.alias),
skill_set: convert_skill_set_from_database(&skill_group_data),
inventory: convert_inventory_from_database_items(
character_containers.inventory_container_id,
&inventory_items,
character_containers.loadout_container_id,
&loadout_items,
)?,
waypoint: char_waypoint,
pets,
active_abilities: convert_active_abilities_from_database(&ability_set_data),
map_marker: char_map_marker,
})
let (skill_set, skill_set_persistence_load_error) =
convert_skill_set_from_database(&skill_group_data);
Ok((
PersistedComponents {
body: convert_body_from_database(&body_data.variant, &body_data.body_data)?,
stats: convert_stats_from_database(character_data.alias),
skill_set,
inventory: convert_inventory_from_database_items(
character_containers.inventory_container_id,
&inventory_items,
character_containers.loadout_container_id,
&loadout_items,
)?,
waypoint: char_waypoint,
pets,
active_abilities: convert_active_abilities_from_database(&ability_set_data),
map_marker: char_map_marker,
},
skill_set_persistence_load_error,
))
}
/// Loads a list of characters belonging to the player. This data is a small

View File

@ -606,7 +606,12 @@ pub fn convert_stats_from_database(alias: String) -> Stats {
new_stats
}
pub fn convert_skill_set_from_database(skill_groups: &[SkillGroup]) -> SkillSet {
/// NOTE: This does *not* return an error on failure, since we can partially
/// recover from some failures. Instead, it returns the error in the second
/// return value; make sure to handle it if present!
pub fn convert_skill_set_from_database(
skill_groups: &[SkillGroup],
) -> (SkillSet, Option<skillset::SkillsPersistenceError>) {
let (skillless_skill_groups, deserialized_skills) =
convert_skill_groups_from_database(skill_groups);
SkillSet::load_from_database(skillless_skill_groups, deserialized_skills)

View File

@ -3,7 +3,10 @@ use crate::persistence::{
error::PersistenceError,
establish_connection, ConnectionMode, DatabaseSettings, PersistedComponents,
};
use common::character::{CharacterId, CharacterItem};
use common::{
character::{CharacterId, CharacterItem},
event::UpdateCharacterMetadata,
};
use crossbeam_channel::{self, TryIter};
use rusqlite::Connection;
use std::sync::{Arc, RwLock};
@ -13,7 +16,8 @@ pub(crate) type CharacterListResult = Result<Vec<CharacterItem>, PersistenceErro
pub(crate) type CharacterCreationResult =
Result<(CharacterId, Vec<CharacterItem>), PersistenceError>;
pub(crate) type CharacterEditResult = Result<(CharacterId, Vec<CharacterItem>), PersistenceError>;
pub(crate) type CharacterDataResult = Result<PersistedComponents, PersistenceError>;
pub(crate) type CharacterDataResult =
Result<(PersistedComponents, UpdateCharacterMetadata), PersistenceError>;
type CharacterLoaderRequest = (specs::Entity, CharacterLoaderRequestKind);
/// Available database operations when modifying a player's character list

View File

@ -49,10 +49,10 @@ impl Sys {
.any(|x| x == character_id)
{
debug!("player recently logged out pending persistence, aborting");
client.send(ServerGeneral::CharacterDataLoadError(
client.send(ServerGeneral::CharacterDataLoadResult(Err(
"You have recently logged out, please wait a few seconds and try again"
.to_string(),
))?;
)))?;
} else if character_updater.disconnect_all_clients_requested() {
// If we're in the middle of disconnecting all clients due to a persistence
// transaction failure, prevent new logins
@ -61,11 +61,11 @@ impl Sys {
"Rejecting player login while pending disconnection of all players is \
in progress"
);
client.send(ServerGeneral::CharacterDataLoadError(
client.send(ServerGeneral::CharacterDataLoadResult(Err(
"The server is currently recovering from an error, please wait a few \
seconds and try again"
.to_string(),
))?;
)))?;
} else {
// Send a request to load the character's component data from the
// DB. Once loaded, persisted components such as stats and inventory
@ -104,9 +104,9 @@ impl Sys {
}
} else {
debug!("Client is not yet registered");
client.send(ServerGeneral::CharacterDataLoadError(String::from(
client.send(ServerGeneral::CharacterDataLoadResult(Err(String::from(
"Failed to fetch player entity",
)))?
))))?
}
},
ClientGeneral::RequestCharacterList => {

View File

@ -280,11 +280,6 @@ impl Sys {
} => {
presence.lossy_terrain_compression = lossy_terrain_compression;
},
ClientGeneral::AcknowledgePersistenceLoadError => {
skill_sets
.get_mut(entity)
.map(|mut skill_set| skill_set.persistence_load_error = None);
},
ClientGeneral::UpdateMapMarker(update) => {
server_emitter.emit(ServerEvent::UpdateMapMarker { entity, update });
},

View File

@ -92,6 +92,7 @@ use common::{
BuffData, BuffKind, Health, Item, MapMarkerChange,
},
consts::MAX_PICKUP_RANGE,
event::UpdateCharacterMetadata,
link::Is,
mounting::Mount,
outcome::Outcome,
@ -500,6 +501,7 @@ pub struct HudInfo {
pub is_first_person: bool,
pub target_entity: Option<specs::Entity>,
pub selected_entity: Option<(specs::Entity, Instant)>,
pub persistence_load_error: UpdateCharacterMetadata,
}
#[derive(Clone)]
@ -1282,41 +1284,38 @@ impl Hud {
// Check if there was a persistence load error of the skillset, and if so
// display a dialog prompt
if self.show.prompt_dialog.is_none() {
if let Some(skill_set) = skill_sets.get(me) {
if let Some(persistence_error) = skill_set.persistence_load_error {
use comp::skillset::SkillsPersistenceError;
let persistence_error = match persistence_error {
SkillsPersistenceError::HashMismatch => {
"There was a difference detected in one of your skill groups since \
you last played."
},
SkillsPersistenceError::DeserializationFailure => {
"There was a error in loading some of your skills from the \
database."
},
SkillsPersistenceError::SpentExpMismatch => {
"The amount of free experience you had in one of your skill groups \
differed from when you last played."
},
SkillsPersistenceError::SkillsUnlockFailed => {
"Your skills were not able to be obtained in the same order you \
acquired them. Prerequisites or costs may have changed."
},
};
if let Some(persistence_error) = info.persistence_load_error {
use comp::skillset::SkillsPersistenceError;
let persistence_error = match persistence_error {
SkillsPersistenceError::HashMismatch => {
"There was a difference detected in one of your skill groups since you \
last played."
},
SkillsPersistenceError::DeserializationFailure => {
"There was a error in loading some of your skills from the database."
},
SkillsPersistenceError::SpentExpMismatch => {
"The amount of free experience you had in one of your skill groups \
differed from when you last played."
},
SkillsPersistenceError::SkillsUnlockFailed => {
"Your skills were not able to be obtained in the same order you \
acquired them. Prerequisites or costs may have changed."
},
};
let common_message = "Some of your skill points have been reset. You will \
need to reassign them.";
let common_message = "Some of your skill points have been reset. You will \
need to reassign them.";
warn!("{}\n{}", persistence_error, common_message);
let prompt_dialog = PromptDialogSettings::new(
format!("{}\n", common_message),
Event::AcknowledgePersistenceLoadError,
None,
)
.with_no_negative_option();
// self.set_prompt_dialog(prompt_dialog);
self.show.prompt_dialog = Some(prompt_dialog);
}
warn!("{}\n{}", persistence_error, common_message);
let prompt_dialog = PromptDialogSettings::new(
format!("{}\n", common_message),
Event::AcknowledgePersistenceLoadError,
None,
)
.with_no_negative_option();
// self.set_prompt_dialog(prompt_dialog);
self.show.prompt_dialog = Some(prompt_dialog);
}
}

View File

@ -80,10 +80,6 @@ pub struct GlobalState {
// TODO: redo this so that the watcher doesn't have to exist for reloading to occur
pub i18n: LocalizationHandle,
pub clipboard: iced_winit::Clipboard,
// NOTE: This can be removed from GlobalState if client state behavior is refactored to not
// enter the game before confirmation of successful character load
/// An error returned by Client that needs to be displayed by the UI
pub client_error: Option<String>,
// Used to clear the shadow textures when entering a PlayState that doesn't utilise shadows
pub clear_shadows_next_frame: bool,
}

View File

@ -286,7 +286,6 @@ fn main() {
singleplayer: None,
i18n,
clipboard,
client_error: None,
clear_shadows_next_frame: false,
};

View File

@ -12,7 +12,7 @@ use client::{self, Client};
use common::{comp, resources::DeltaTime};
use common_base::span;
use specs::WorldExt;
use std::{cell::RefCell, mem, rc::Rc};
use std::{cell::RefCell, rc::Rc};
use tracing::error;
use ui::CharSelectionUi;
@ -78,11 +78,11 @@ impl PlayState for CharSelectionState {
fn tick(&mut self, global_state: &mut GlobalState, events: Vec<WinEvent>) -> PlayStateResult {
span!(_guard, "tick", "<CharSelectionState as PlayState>::tick");
let (client_presence, client_registered) = {
let client_registered = {
let client = self.client.borrow();
(client.presence(), client.registered())
client.registered()
};
if client_presence.is_none() && client_registered {
if client_registered {
// Handle window events
for event in events {
if self.char_selection_ui.handle_event(event.clone()) {
@ -132,17 +132,11 @@ impl PlayState for CharSelectionState {
self.client.borrow_mut().delete_character(character_id);
},
ui::Event::Play(character_id) => {
{
let mut c = self.client.borrow_mut();
c.request_character(character_id);
//Send our ViewDistance and LoD distance
c.set_view_distance(global_state.settings.graphics.view_distance);
c.set_lod_distance(global_state.settings.graphics.lod_distance);
}
return PlayStateResult::Switch(Box::new(SessionState::new(
global_state,
Rc::clone(&self.client),
)));
let mut c = self.client.borrow_mut();
c.request_character(character_id);
//Send our ViewDistance and LoD distance
c.set_view_distance(global_state.settings.graphics.view_distance);
c.set_lod_distance(global_state.settings.graphics.lod_distance);
},
ui::Event::ClearCharacterListError => {
self.char_selection_ui.error = None;
@ -192,11 +186,12 @@ impl PlayState for CharSelectionState {
// Tick the client (currently only to keep the connection alive).
let localized_strings = &global_state.i18n.read();
match self.client.borrow_mut().tick(
let res = self.client.borrow_mut().tick(
comp::ControllerInputs::default(),
global_state.clock.dt(),
|_| {},
) {
);
match res {
Ok(events) => {
for event in events {
match event {
@ -218,8 +213,18 @@ impl PlayState for CharSelectionState {
self.char_selection_ui.select_character(character_id);
},
client::Event::CharacterError(error) => {
global_state.client_error = Some(error);
self.char_selection_ui.display_error(error);
},
client::Event::CharacterJoined(metadata) => {
// NOTE: It's possible we'll lose disconnect messages this way,
// among other things, but oh well.
return PlayStateResult::Switch(Box::new(SessionState::new(
global_state,
metadata,
Rc::clone(&self.client),
)));
},
// TODO: See if we should handle StartSpectate here instead.
_ => {},
}
}
@ -232,16 +237,15 @@ impl PlayState for CharSelectionState {
},
}
if let Some(error) = mem::take(&mut global_state.client_error) {
self.char_selection_ui.display_error(error);
}
// TODO: make sure rendering is not relying on cleaned up stuff
self.client.borrow_mut().cleanup();
PlayStateResult::Continue
} else {
error!("Client not in pending or registered state. Popping char selection play state");
error!(
"Client not in character screen, pending, or registered state. Popping char \
selection play state"
);
// TODO set global_state.info_message
PlayStateResult::Pop
}

View File

@ -258,6 +258,7 @@ enum InfoContent {
CreatingCharacter,
EditingCharacter,
DeletingCharacter,
JoiningCharacter,
CharacterError(String),
}
@ -761,6 +762,11 @@ impl Controls {
.size(fonts.cyri.scale(24))
.into()
},
InfoContent::JoiningCharacter => {
Text::new(i18n.get("char_selection-joining_character"))
.size(fonts.cyri.scale(24))
.into()
},
InfoContent::CharacterError(error) => Column::with_children(vec![
Text::new(error).size(fonts.cyri.scale(24)).into(),
Row::with_children(vec![neat_button(
@ -1401,9 +1407,50 @@ impl Controls {
Message::Logout => {
events.push(Event::Logout);
},
Message::ConfirmDeletion => {
if let Mode::Select { info_content, .. } = &mut self.mode {
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);
}
}
},
Message::CancelDeletion => {
if let Mode::Select { info_content, .. } = &mut self.mode {
if let Some(InfoContent::Deletion(_)) = info_content {
*info_content = None;
}
}
},
Message::ClearCharacterListError => {
events.push(Event::ClearCharacterListError);
},
Message::DoNothing => {},
_ if matches!(self.mode, Mode::Select {
info_content: Some(_),
..
}) =>
{
// Don't allow use of the UI on the select screen to deal with
// things other than the event currently being
// procesed; all the select screen events after this
// modify the info content or selection, except for Spectate
// which currently causes us to exit the
// character select state.
},
Message::EnterWorld => {
if let (Mode::Select { .. }, Some(selected)) = (&self.mode, self.selected) {
if let (Mode::Select { info_content, .. }, Some(selected)) =
(&mut self.mode, self.selected)
{
events.push(Event::Play(selected));
*info_content = Some(InfoContent::JoiningCharacter);
}
},
Message::Select(id) => {
@ -1529,32 +1576,6 @@ impl Controls {
);
}
},
Message::ConfirmDeletion => {
if let Mode::Select { info_content, .. } = &mut self.mode {
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);
}
}
},
Message::CancelDeletion => {
if let Mode::Select { info_content, .. } = &mut self.mode {
if let Some(InfoContent::Deletion(_)) = info_content {
*info_content = None;
}
}
},
Message::ClearCharacterListError => {
events.push(Event::ClearCharacterListError);
},
Message::HairStyle(value) => {
if let Mode::CreateOrEdit { body, .. } = &mut self.mode {
body.hair_style = value;
@ -1597,7 +1618,6 @@ impl Controls {
body.validate();
}
},
Message::DoNothing => {},
}
}

View File

@ -24,6 +24,7 @@ use common::{
ChatMsg, ChatType, InputKind, InventoryUpdateEvent, Pos, Stats, UtteranceKind, Vel,
},
consts::MAX_MOUNT_RANGE,
event::UpdateCharacterMetadata,
link::Is,
mounting::Mount,
outcome::Outcome,
@ -74,6 +75,7 @@ enum TickAction {
pub struct SessionState {
scene: Scene,
client: Rc<RefCell<Client>>,
metadata: UpdateCharacterMetadata,
hud: Hud,
key_state: KeyState,
inputs: comp::ControllerInputs,
@ -97,7 +99,11 @@ pub struct SessionState {
/// Represents an active game session (i.e., the one being played).
impl SessionState {
/// Create a new `SessionState`.
pub fn new(global_state: &mut GlobalState, client: Rc<RefCell<Client>>) -> Self {
pub fn new(
global_state: &mut GlobalState,
metadata: UpdateCharacterMetadata,
client: Rc<RefCell<Client>>,
) -> Self {
// Create a scene for this session. The scene handles visible elements of the
// game world.
let mut scene = Scene::new(
@ -153,6 +159,7 @@ impl SessionState {
#[cfg(not(target_os = "macos"))]
mumble_link,
hitboxes: HashMap::new(),
metadata,
}
}
@ -350,9 +357,8 @@ impl SessionState {
client::Event::Outcome(outcome) => outcomes.push(outcome),
client::Event::CharacterCreated(_) => {},
client::Event::CharacterEdited(_) => {},
client::Event::CharacterError(error) => {
global_state.client_error = Some(error);
},
client::Event::CharacterError(_) => {},
client::Event::CharacterJoined(_) => {},
client::Event::MapMarker(event) => {
self.hud.show.update_map_markers(event);
},
@ -1150,6 +1156,7 @@ impl PlayState for SessionState {
),
target_entity: self.target_entity,
selected_entity: self.selected_entity,
persistence_load_error: self.metadata,
},
self.interactable,
);
@ -1568,9 +1575,7 @@ impl PlayState for SessionState {
settings_change.process(global_state, self);
},
HudEvent::AcknowledgePersistenceLoadError => {
self.client
.borrow_mut()
.acknolwedge_persistence_load_error();
self.metadata = None;
},
HudEvent::MapMarkerEvent(event) => {
self.client.borrow_mut().map_marker_event(event);