mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
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:
parent
8dfe53e788
commit
4b5a9fe0f4
24
assets/voxygen/i18n/en/char_selection.ftl
Normal file
24
assets/voxygen/i18n/en/char_selection.ftl
Normal 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.
|
@ -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();
|
||||
|
@ -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()
|
||||
},
|
||||
|
@ -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(_)
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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(_)
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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 => {
|
||||
|
@ -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 });
|
||||
},
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -286,7 +286,6 @@ fn main() {
|
||||
singleplayer: None,
|
||||
i18n,
|
||||
clipboard,
|
||||
client_error: None,
|
||||
clear_shadows_next_frame: false,
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 => {},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user