mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Update CHANGELOG and a TODO, fix safer deserialisation for inventory
data.
This commit is contained in:
parent
ceee05f757
commit
e0633a238e
@ -16,7 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- NPCs call for help when attacked
|
- NPCs call for help when attacked
|
||||||
- Eyebrows and shapes can now be selected
|
- Eyebrows and shapes can now be selected
|
||||||
- Character name and level information to chat, social tab and `/players` command.
|
- Character name and level information to chat, social tab and `/players` command.
|
||||||
- Added inventory saving
|
- Added inventory, armour and weapon saving
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -45,11 +45,11 @@
|
|||||||
color: None
|
color: None
|
||||||
),
|
),
|
||||||
Steel0:(
|
Steel0:(
|
||||||
vox_spec: ("armor.foot.steel-0", (-2.5, -3.5, -9.0)),
|
vox_spec: ("armor.foot.steel-0", (-2.5, -3.5, -2.0)),
|
||||||
color: None
|
color: None
|
||||||
),
|
),
|
||||||
Leather2:(
|
Leather2:(
|
||||||
vox_spec: ("armor.foot.leather-2", (-2.5, -3.5, -9.0)),
|
vox_spec: ("armor.foot.leather-2", (-2.5, -3.5, -2.0)),
|
||||||
color: None
|
color: None
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
@ -237,12 +237,9 @@ impl Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Request a state transition to `ClientState::Character`.
|
/// Request a state transition to `ClientState::Character`.
|
||||||
pub fn request_character(&mut self, character_id: i32, body: comp::Body, main: Option<String>) {
|
pub fn request_character(&mut self, character_id: i32, body: comp::Body) {
|
||||||
self.postbox.send_message(ClientMsg::Character {
|
self.postbox
|
||||||
character_id,
|
.send_message(ClientMsg::Character { character_id, body });
|
||||||
body,
|
|
||||||
main,
|
|
||||||
});
|
|
||||||
|
|
||||||
self.client_state = ClientState::Pending;
|
self.client_state = ClientState::Pending;
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,20 @@ use serde_derive::{Deserialize, Serialize};
|
|||||||
/// The limit on how many characters that a player can have
|
/// The limit on how many characters that a player can have
|
||||||
pub const MAX_CHARACTERS_PER_PLAYER: usize = 8;
|
pub const MAX_CHARACTERS_PER_PLAYER: usize = 8;
|
||||||
|
|
||||||
|
// TODO: Since loadout persistence came a few weeks after character persistence,
|
||||||
|
// we stored their main weapon in the `tool` field here. While loadout
|
||||||
|
// persistence is still new, saved characters may not have an associated loadout
|
||||||
|
// entry in the DB, so we use this `tool` field to create an entry the first
|
||||||
|
// time they enter the game.
|
||||||
|
//
|
||||||
|
// Once we are happy that all characters have a loadout, or we manually
|
||||||
|
// update/delete those that don't, it's no longer necessary and we can
|
||||||
|
// remove this from here, as well as in the DB schema and persistence code.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct Character {
|
pub struct Character {
|
||||||
pub id: Option<i32>,
|
pub id: Option<i32>,
|
||||||
pub alias: String,
|
pub alias: String,
|
||||||
pub tool: Option<String>, // TODO: Remove once we start persisting inventories
|
pub tool: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a single character item in the character list presented during
|
/// Represents a single character item in the character list presented during
|
||||||
@ -19,6 +28,7 @@ pub struct CharacterItem {
|
|||||||
pub character: Character,
|
pub character: Character,
|
||||||
pub body: comp::Body,
|
pub body: comp::Body,
|
||||||
pub level: usize,
|
pub level: usize,
|
||||||
|
pub loadout: comp::Loadout,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The full representation of the data we store in the database for each
|
/// The full representation of the data we store in the database for each
|
||||||
|
@ -95,7 +95,6 @@ pub enum ServerEvent {
|
|||||||
entity: EcsEntity,
|
entity: EcsEntity,
|
||||||
character_id: i32,
|
character_id: i32,
|
||||||
body: comp::Body,
|
body: comp::Body,
|
||||||
main: Option<String>,
|
|
||||||
},
|
},
|
||||||
ExitIngame {
|
ExitIngame {
|
||||||
entity: EcsEntity,
|
entity: EcsEntity,
|
||||||
|
@ -22,6 +22,7 @@ pub mod effect;
|
|||||||
pub mod event;
|
pub mod event;
|
||||||
pub mod figure;
|
pub mod figure;
|
||||||
pub mod generation;
|
pub mod generation;
|
||||||
|
pub mod loadout_builder;
|
||||||
pub mod msg;
|
pub mod msg;
|
||||||
pub mod npc;
|
pub mod npc;
|
||||||
pub mod path;
|
pub mod path;
|
||||||
@ -38,6 +39,8 @@ pub mod util;
|
|||||||
pub mod vol;
|
pub mod vol;
|
||||||
pub mod volumes;
|
pub mod volumes;
|
||||||
|
|
||||||
|
pub use loadout_builder::LoadoutBuilder;
|
||||||
|
|
||||||
/// The networking module containing high-level wrappers of `TcpListener` and
|
/// The networking module containing high-level wrappers of `TcpListener` and
|
||||||
/// `TcpStream` (`PostOffice` and `PostBox` respectively) and data types used by
|
/// `TcpStream` (`PostOffice` and `PostBox` respectively) and data types used by
|
||||||
/// both the server and client. # Examples
|
/// both the server and client. # Examples
|
||||||
|
178
common/src/loadout_builder.rs
Normal file
178
common/src/loadout_builder.rs
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
use crate::{
|
||||||
|
assets,
|
||||||
|
comp::{
|
||||||
|
item::{Item, ItemKind},
|
||||||
|
CharacterAbility, ItemConfig, Loadout,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Builder for character Loadouts, containing weapon and armour items belonging
|
||||||
|
/// to a character, along with some helper methods for loading Items and
|
||||||
|
/// ItemConfig
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use veloren_common::LoadoutBuilder;
|
||||||
|
///
|
||||||
|
/// // Build a loadout with character starter defaults and a specific sword with default sword abilities
|
||||||
|
/// let loadout = LoadoutBuilder::new()
|
||||||
|
/// .defaults()
|
||||||
|
/// .active_item(LoadoutBuilder::default_item_config_from_str(
|
||||||
|
/// Some("common.items.weapons.sword.zweihander_sword_0"),
|
||||||
|
/// ))
|
||||||
|
/// .build();
|
||||||
|
/// ```
|
||||||
|
pub struct LoadoutBuilder(Loadout);
|
||||||
|
|
||||||
|
impl LoadoutBuilder {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(Loadout {
|
||||||
|
active_item: None,
|
||||||
|
second_item: None,
|
||||||
|
shoulder: None,
|
||||||
|
chest: None,
|
||||||
|
belt: None,
|
||||||
|
hand: None,
|
||||||
|
pants: None,
|
||||||
|
foot: None,
|
||||||
|
back: None,
|
||||||
|
ring: None,
|
||||||
|
neck: None,
|
||||||
|
lantern: None,
|
||||||
|
head: None,
|
||||||
|
tabard: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set default armor items for the loadout. This may vary with game
|
||||||
|
/// updates, but should be safe defaults for a new character.
|
||||||
|
pub fn defaults(self) -> Self {
|
||||||
|
self.chest(Some(assets::load_expect_cloned(
|
||||||
|
"common.items.armor.starter.rugged_chest",
|
||||||
|
)))
|
||||||
|
.pants(Some(assets::load_expect_cloned(
|
||||||
|
"common.items.armor.starter.rugged_pants",
|
||||||
|
)))
|
||||||
|
.foot(Some(assets::load_expect_cloned(
|
||||||
|
"common.items.armor.starter.sandals_0",
|
||||||
|
)))
|
||||||
|
.lantern(Some(assets::load_expect_cloned(
|
||||||
|
"common.items.armor.starter.lantern",
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the default [ItemConfig](../comp/struct.ItemConfig.html) for a tool
|
||||||
|
/// (weapon). This information is required for the `active` and `second`
|
||||||
|
/// weapon items in a loadout. If some customisation to the item's
|
||||||
|
/// abilities or their timings is desired, you should create and provide
|
||||||
|
/// the item config directly to the [active_item](#method.active_item)
|
||||||
|
/// method
|
||||||
|
pub fn default_item_config_from_item(maybe_item: Option<Item>) -> Option<ItemConfig> {
|
||||||
|
if let Some(item) = maybe_item {
|
||||||
|
if let ItemKind::Tool(tool) = item.kind {
|
||||||
|
let mut abilities = tool.get_abilities();
|
||||||
|
let mut ability_drain = abilities.drain(..);
|
||||||
|
|
||||||
|
return Some(ItemConfig {
|
||||||
|
item,
|
||||||
|
ability1: ability_drain.next(),
|
||||||
|
ability2: ability_drain.next(),
|
||||||
|
ability3: ability_drain.next(),
|
||||||
|
block_ability: Some(CharacterAbility::BasicBlock),
|
||||||
|
dodge_ability: Some(CharacterAbility::Roll),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an [Item](../comp/struct.Item.html) by its string
|
||||||
|
/// reference by loading its asset
|
||||||
|
pub fn item_from_str(item_ref: Option<&str>) -> Option<Item> {
|
||||||
|
item_ref.and_then(|specifier| assets::load_cloned::<Item>(&specifier).ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an item's (weapon's) default
|
||||||
|
/// [ItemConfig](../comp/struct.ItemConfig.html)
|
||||||
|
/// by string reference. This will first attempt to load the Item, then
|
||||||
|
/// the default abilities for that item via the
|
||||||
|
/// [default_item_config_from_item](#method.default_item_config_from_item)
|
||||||
|
/// function
|
||||||
|
pub fn default_item_config_from_str(item_ref: Option<&str>) -> Option<ItemConfig> {
|
||||||
|
Self::default_item_config_from_item(Self::item_from_str(item_ref))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn active_item(mut self, item: Option<ItemConfig>) -> Self {
|
||||||
|
self.0.active_item = item;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn second_item(mut self, item: Option<ItemConfig>) -> Self {
|
||||||
|
self.0.active_item = item;
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shoulder(mut self, item: Option<Item>) -> Self {
|
||||||
|
self.0.shoulder = item;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn chest(mut self, item: Option<Item>) -> Self {
|
||||||
|
self.0.chest = item;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn belt(mut self, item: Option<Item>) -> Self {
|
||||||
|
self.0.belt = item;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hand(mut self, item: Option<Item>) -> Self {
|
||||||
|
self.0.hand = item;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pants(mut self, item: Option<Item>) -> Self {
|
||||||
|
self.0.pants = item;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn foot(mut self, item: Option<Item>) -> Self {
|
||||||
|
self.0.foot = item;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn back(mut self, item: Option<Item>) -> Self {
|
||||||
|
self.0.back = item;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ring(mut self, item: Option<Item>) -> Self {
|
||||||
|
self.0.ring = item;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn neck(mut self, item: Option<Item>) -> Self {
|
||||||
|
self.0.neck = item;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lantern(mut self, item: Option<Item>) -> Self {
|
||||||
|
self.0.lantern = item;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn head(mut self, item: Option<Item>) -> Self {
|
||||||
|
self.0.head = item;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tabard(mut self, item: Option<Item>) -> Self {
|
||||||
|
self.0.tabard = item;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self) -> Loadout { self.0 }
|
||||||
|
}
|
@ -17,7 +17,6 @@ pub enum ClientMsg {
|
|||||||
Character {
|
Character {
|
||||||
character_id: i32,
|
character_id: i32,
|
||||||
body: comp::Body,
|
body: comp::Body,
|
||||||
main: Option<String>, // Specifier for the weapon
|
|
||||||
},
|
},
|
||||||
/// Request `ClientState::Registered` from an ingame state
|
/// Request `ClientState::Registered` from an ingame state
|
||||||
ExitIngame,
|
ExitIngame,
|
||||||
|
@ -14,12 +14,11 @@ pub fn handle_create_character(
|
|||||||
entity: EcsEntity,
|
entity: EcsEntity,
|
||||||
character_id: i32,
|
character_id: i32,
|
||||||
body: Body,
|
body: Body,
|
||||||
main: Option<String>,
|
|
||||||
) {
|
) {
|
||||||
let state = &mut server.state;
|
let state = &mut server.state;
|
||||||
let server_settings = &server.server_settings;
|
let server_settings = &server.server_settings;
|
||||||
|
|
||||||
state.create_player_character(entity, character_id, body, main, server_settings);
|
state.create_player_character(entity, character_id, body, server_settings);
|
||||||
sys::subscription::initialize_region_subscription(state.ecs(), entity);
|
sys::subscription::initialize_region_subscription(state.ecs(), entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,8 +74,7 @@ impl Server {
|
|||||||
entity,
|
entity,
|
||||||
character_id,
|
character_id,
|
||||||
body,
|
body,
|
||||||
main,
|
} => handle_create_character(self, entity, character_id, body),
|
||||||
} => handle_create_character(self, entity, character_id, body, main),
|
|
||||||
ServerEvent::LevelUp(entity, new_level) => handle_level_up(self, entity, new_level),
|
ServerEvent::LevelUp(entity, new_level) => handle_level_up(self, entity, new_level),
|
||||||
ServerEvent::ExitIngame { entity } => handle_exit_ingame(self, entity),
|
ServerEvent::ExitIngame { entity } => handle_exit_ingame(self, entity),
|
||||||
ServerEvent::CreateNpc {
|
ServerEvent::CreateNpc {
|
||||||
|
@ -72,16 +72,17 @@ pub fn handle_client_disconnect(server: &mut Server, entity: EcsEntity) -> Event
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sync the player's character data to the database
|
// Sync the player's character data to the database
|
||||||
if let (Some(player), Some(stats), Some(inventory), updater) = (
|
if let (Some(player), Some(stats), Some(inventory), Some(loadout), updater) = (
|
||||||
state.read_storage::<Player>().get(entity),
|
state.read_storage::<Player>().get(entity),
|
||||||
state.read_storage::<comp::Stats>().get(entity),
|
state.read_storage::<comp::Stats>().get(entity),
|
||||||
state.read_storage::<comp::Inventory>().get(entity),
|
state.read_storage::<comp::Inventory>().get(entity),
|
||||||
|
state.read_storage::<comp::Loadout>().get(entity),
|
||||||
state
|
state
|
||||||
.ecs()
|
.ecs()
|
||||||
.read_resource::<persistence::character::CharacterUpdater>(),
|
.read_resource::<persistence::character::CharacterUpdater>(),
|
||||||
) {
|
) {
|
||||||
if let Some(character_id) = player.character_id {
|
if let Some(character_id) = player.character_id {
|
||||||
updater.update(character_id, stats, inventory);
|
updater.update(character_id, stats, inventory, loadout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1
server/src/migrations/2020-05-28-210610_loadout/down.sql
Normal file
1
server/src/migrations/2020-05-28-210610_loadout/down.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
DROP TABLE IF EXISTS "loadout";
|
6
server/src/migrations/2020-05-28-210610_loadout/up.sql
Normal file
6
server/src/migrations/2020-05-28-210610_loadout/up.sql
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS "loadout" (
|
||||||
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
character_id INT NOT NULL,
|
||||||
|
items TEXT NOT NULL,
|
||||||
|
FOREIGN KEY(character_id) REFERENCES "character"(id) ON DELETE CASCADE
|
||||||
|
);
|
@ -4,13 +4,16 @@ use super::{
|
|||||||
error::Error,
|
error::Error,
|
||||||
establish_connection,
|
establish_connection,
|
||||||
models::{
|
models::{
|
||||||
Body, Character, Inventory, InventoryUpdate, NewCharacter, Stats, StatsJoinData,
|
Body, Character, Inventory, InventoryUpdate, Loadout, LoadoutUpdate, NewCharacter,
|
||||||
StatsUpdate,
|
NewLoadout, Stats, StatsJoinData, StatsUpdate,
|
||||||
},
|
},
|
||||||
schema,
|
schema,
|
||||||
};
|
};
|
||||||
use crate::comp;
|
use crate::comp;
|
||||||
use common::character::{Character as CharacterData, CharacterItem, MAX_CHARACTERS_PER_PLAYER};
|
use common::{
|
||||||
|
character::{Character as CharacterData, CharacterItem, MAX_CHARACTERS_PER_PLAYER},
|
||||||
|
LoadoutBuilder,
|
||||||
|
};
|
||||||
use crossbeam::channel;
|
use crossbeam::channel;
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
|
||||||
@ -23,16 +26,17 @@ type CharacterListResult = Result<Vec<CharacterItem>, Error>;
|
|||||||
pub fn load_character_data(
|
pub fn load_character_data(
|
||||||
character_id: i32,
|
character_id: i32,
|
||||||
db_dir: &str,
|
db_dir: &str,
|
||||||
) -> Result<(comp::Stats, comp::Inventory), Error> {
|
) -> Result<(comp::Stats, comp::Inventory, comp::Loadout), Error> {
|
||||||
let connection = establish_connection(db_dir);
|
let connection = establish_connection(db_dir);
|
||||||
|
|
||||||
let (character_data, body_data, stats_data, maybe_inventory) =
|
let (character_data, body_data, stats_data, maybe_inventory, maybe_loadout) =
|
||||||
schema::character::dsl::character
|
schema::character::dsl::character
|
||||||
.filter(schema::character::id.eq(character_id))
|
.filter(schema::character::id.eq(character_id))
|
||||||
.inner_join(schema::body::table)
|
.inner_join(schema::body::table)
|
||||||
.inner_join(schema::stats::table)
|
.inner_join(schema::stats::table)
|
||||||
.left_join(schema::inventory::table)
|
.left_join(schema::inventory::table)
|
||||||
.first::<(Character, Body, Stats, Option<Inventory>)>(&connection)?;
|
.left_join(schema::loadout::table)
|
||||||
|
.first::<(Character, Body, Stats, Option<Inventory>, Option<Loadout>)>(&connection)?;
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
comp::Stats::from(StatsJoinData {
|
comp::Stats::from(StatsJoinData {
|
||||||
@ -60,6 +64,33 @@ pub fn load_character_data(
|
|||||||
},
|
},
|
||||||
|inv| comp::Inventory::from(inv),
|
|inv| comp::Inventory::from(inv),
|
||||||
),
|
),
|
||||||
|
maybe_loadout.map_or_else(
|
||||||
|
|| {
|
||||||
|
// Create if no record was found
|
||||||
|
let default_loadout = LoadoutBuilder::new()
|
||||||
|
.defaults()
|
||||||
|
.active_item(LoadoutBuilder::default_item_config_from_str(
|
||||||
|
character_data.tool.as_deref(),
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let row = NewLoadout::from((character_data.id, &default_loadout));
|
||||||
|
|
||||||
|
if let Err(error) = diesel::insert_into(schema::loadout::table)
|
||||||
|
.values(&row)
|
||||||
|
.execute(&connection)
|
||||||
|
{
|
||||||
|
log::warn!(
|
||||||
|
"Failed to create an loadout record for character {}: {}",
|
||||||
|
&character_data.id,
|
||||||
|
error
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
default_loadout
|
||||||
|
},
|
||||||
|
|data| comp::Loadout::from(&data),
|
||||||
|
),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,24 +102,37 @@ pub fn load_character_data(
|
|||||||
/// stats, body, etc...) the character is skipped, and no entry will be
|
/// stats, body, etc...) the character is skipped, and no entry will be
|
||||||
/// returned.
|
/// returned.
|
||||||
pub fn load_character_list(player_uuid: &str, db_dir: &str) -> CharacterListResult {
|
pub fn load_character_list(player_uuid: &str, db_dir: &str) -> CharacterListResult {
|
||||||
let data: Vec<(Character, Body, Stats)> = schema::character::dsl::character
|
let data = schema::character::dsl::character
|
||||||
.filter(schema::character::player_uuid.eq(player_uuid))
|
.filter(schema::character::player_uuid.eq(player_uuid))
|
||||||
.order(schema::character::id.desc())
|
.order(schema::character::id.desc())
|
||||||
.inner_join(schema::body::table)
|
.inner_join(schema::body::table)
|
||||||
.inner_join(schema::stats::table)
|
.inner_join(schema::stats::table)
|
||||||
.load::<(Character, Body, Stats)>(&establish_connection(db_dir))?;
|
.left_join(schema::loadout::table)
|
||||||
|
.load::<(Character, Body, Stats, Option<Loadout>)>(&establish_connection(db_dir))?;
|
||||||
|
|
||||||
Ok(data
|
Ok(data
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(character_data, body_data, stats_data)| {
|
.map(|(character_data, body_data, stats_data, maybe_loadout)| {
|
||||||
let character = CharacterData::from(character_data);
|
let character = CharacterData::from(character_data);
|
||||||
let body = comp::Body::from(body_data);
|
let body = comp::Body::from(body_data);
|
||||||
let level = stats_data.level as usize;
|
let level = stats_data.level as usize;
|
||||||
|
let loadout = maybe_loadout.as_ref().map_or_else(
|
||||||
|
|| {
|
||||||
|
LoadoutBuilder::new()
|
||||||
|
.defaults()
|
||||||
|
.active_item(LoadoutBuilder::default_item_config_from_str(
|
||||||
|
character.tool.as_deref(),
|
||||||
|
))
|
||||||
|
.build()
|
||||||
|
},
|
||||||
|
|data| comp::Loadout::from(data),
|
||||||
|
);
|
||||||
|
|
||||||
CharacterItem {
|
CharacterItem {
|
||||||
character,
|
character,
|
||||||
body,
|
body,
|
||||||
level,
|
level,
|
||||||
|
loadout,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect())
|
.collect())
|
||||||
@ -112,7 +156,7 @@ pub fn create_character(
|
|||||||
let connection = establish_connection(db_dir);
|
let connection = establish_connection(db_dir);
|
||||||
|
|
||||||
connection.transaction::<_, diesel::result::Error, _>(|| {
|
connection.transaction::<_, diesel::result::Error, _>(|| {
|
||||||
use schema::{body, character, character::dsl::*, inventory, stats};
|
use schema::{body, character, character::dsl::*, inventory, loadout, stats};
|
||||||
|
|
||||||
match body {
|
match body {
|
||||||
comp::Body::Humanoid(body_data) => {
|
comp::Body::Humanoid(body_data) => {
|
||||||
@ -171,6 +215,20 @@ pub fn create_character(
|
|||||||
diesel::insert_into(inventory::table)
|
diesel::insert_into(inventory::table)
|
||||||
.values(&inventory)
|
.values(&inventory)
|
||||||
.execute(&connection)?;
|
.execute(&connection)?;
|
||||||
|
|
||||||
|
// Insert a loadout with defaults and the chosen active weapon
|
||||||
|
let loadout = LoadoutBuilder::new()
|
||||||
|
.defaults()
|
||||||
|
.active_item(LoadoutBuilder::default_item_config_from_str(
|
||||||
|
character_tool.as_deref(),
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let new_loadout = NewLoadout::from((inserted_character.id, &loadout));
|
||||||
|
|
||||||
|
diesel::insert_into(loadout::table)
|
||||||
|
.values(&new_loadout)
|
||||||
|
.execute(&connection)?;
|
||||||
},
|
},
|
||||||
_ => log::warn!("Creating non-humanoid characters is not supported."),
|
_ => log::warn!("Creating non-humanoid characters is not supported."),
|
||||||
};
|
};
|
||||||
@ -216,7 +274,7 @@ fn check_character_limit(uuid: &str, db_dir: &str) -> Result<(), Error> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type CharacterUpdateData = (StatsUpdate, InventoryUpdate);
|
pub type CharacterUpdateData = (StatsUpdate, InventoryUpdate, LoadoutUpdate);
|
||||||
|
|
||||||
pub struct CharacterUpdater {
|
pub struct CharacterUpdater {
|
||||||
update_tx: Option<channel::Sender<Vec<(i32, CharacterUpdateData)>>>,
|
update_tx: Option<channel::Sender<Vec<(i32, CharacterUpdateData)>>>,
|
||||||
@ -240,13 +298,17 @@ impl CharacterUpdater {
|
|||||||
|
|
||||||
pub fn batch_update<'a>(
|
pub fn batch_update<'a>(
|
||||||
&self,
|
&self,
|
||||||
updates: impl Iterator<Item = (i32, &'a comp::Stats, &'a comp::Inventory)>,
|
updates: impl Iterator<Item = (i32, &'a comp::Stats, &'a comp::Inventory, &'a comp::Loadout)>,
|
||||||
) {
|
) {
|
||||||
let updates = updates
|
let updates = updates
|
||||||
.map(|(id, stats, inventory)| {
|
.map(|(id, stats, inventory, loadout)| {
|
||||||
(
|
(
|
||||||
id,
|
id,
|
||||||
(StatsUpdate::from(stats), InventoryUpdate::from(inventory)),
|
(
|
||||||
|
StatsUpdate::from(stats),
|
||||||
|
InventoryUpdate::from(inventory),
|
||||||
|
LoadoutUpdate::from((id, loadout)),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@ -256,8 +318,14 @@ impl CharacterUpdater {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&self, character_id: i32, stats: &comp::Stats, inventory: &comp::Inventory) {
|
pub fn update(
|
||||||
self.batch_update(std::iter::once((character_id, stats, inventory)));
|
&self,
|
||||||
|
character_id: i32,
|
||||||
|
stats: &comp::Stats,
|
||||||
|
inventory: &comp::Inventory,
|
||||||
|
loadout: &comp::Loadout,
|
||||||
|
) {
|
||||||
|
self.batch_update(std::iter::once((character_id, stats, inventory, loadout)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,9 +333,17 @@ fn batch_update(updates: impl Iterator<Item = (i32, CharacterUpdateData)>, db_di
|
|||||||
let connection = establish_connection(db_dir);
|
let connection = establish_connection(db_dir);
|
||||||
|
|
||||||
if let Err(err) = connection.transaction::<_, diesel::result::Error, _>(|| {
|
if let Err(err) = connection.transaction::<_, diesel::result::Error, _>(|| {
|
||||||
updates.for_each(|(character_id, (stats_update, inventory_update))| {
|
updates.for_each(
|
||||||
update(character_id, &stats_update, &inventory_update, &connection)
|
|(character_id, (stats_update, inventory_update, loadout_update))| {
|
||||||
});
|
update(
|
||||||
|
character_id,
|
||||||
|
&stats_update,
|
||||||
|
&inventory_update,
|
||||||
|
&loadout_update,
|
||||||
|
&connection,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}) {
|
}) {
|
||||||
@ -279,6 +355,7 @@ fn update(
|
|||||||
character_id: i32,
|
character_id: i32,
|
||||||
stats: &StatsUpdate,
|
stats: &StatsUpdate,
|
||||||
inventory: &InventoryUpdate,
|
inventory: &InventoryUpdate,
|
||||||
|
loadout: &LoadoutUpdate,
|
||||||
connection: &SqliteConnection,
|
connection: &SqliteConnection,
|
||||||
) {
|
) {
|
||||||
if let Err(error) =
|
if let Err(error) =
|
||||||
@ -305,6 +382,19 @@ fn update(
|
|||||||
error
|
error
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Err(error) = diesel::update(
|
||||||
|
schema::loadout::table.filter(schema::loadout::character_id.eq(character_id)),
|
||||||
|
)
|
||||||
|
.set(loadout)
|
||||||
|
.execute(connection)
|
||||||
|
{
|
||||||
|
log::warn!(
|
||||||
|
"Failed to update loadout for character: {:?}: {:?}",
|
||||||
|
character_id,
|
||||||
|
error
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for CharacterUpdater {
|
impl Drop for CharacterUpdater {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
|
||||||
use super::schema::{body, character, inventory, stats};
|
use super::schema::{body, character, inventory, loadout, stats};
|
||||||
use crate::comp;
|
use crate::comp;
|
||||||
use common::character::Character as CharacterData;
|
use common::{character::Character as CharacterData, LoadoutBuilder};
|
||||||
use diesel::sql_types::Text;
|
use diesel::sql_types::Text;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -41,7 +41,10 @@ impl From<&Character> for CharacterData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Body` represents the body variety for a character
|
/// `Body` represents the body variety for a character, which has a one-to-one
|
||||||
|
/// relationship with Characters. This data is set during player creation, and
|
||||||
|
/// while there is currently no in-game functionality to modify it, it will
|
||||||
|
/// likely be added in the future.
|
||||||
#[derive(Associations, Identifiable, Queryable, Debug, Insertable)]
|
#[derive(Associations, Identifiable, Queryable, Debug, Insertable)]
|
||||||
#[belongs_to(Character)]
|
#[belongs_to(Character)]
|
||||||
#[primary_key(character_id)]
|
#[primary_key(character_id)]
|
||||||
@ -75,7 +78,8 @@ impl From<&Body> for comp::Body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Stats` represents the stats for a character
|
/// `Stats` represents the stats for a character, which has a one-to-one
|
||||||
|
/// relationship with Characters.
|
||||||
#[derive(Associations, AsChangeset, Identifiable, Queryable, Debug, Insertable)]
|
#[derive(Associations, AsChangeset, Identifiable, Queryable, Debug, Insertable)]
|
||||||
#[belongs_to(Character)]
|
#[belongs_to(Character)]
|
||||||
#[primary_key(character_id)]
|
#[primary_key(character_id)]
|
||||||
@ -136,6 +140,11 @@ impl From<&comp::Stats> for StatsUpdate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inventory storage and conversion. Inventories have a one-to-one relationship
|
||||||
|
/// with characters.
|
||||||
|
///
|
||||||
|
/// We store the players inventory as a single TEXT column which is serialised
|
||||||
|
/// JSON representation of the Inventory component.
|
||||||
#[derive(Associations, AsChangeset, Identifiable, Queryable, Debug, Insertable)]
|
#[derive(Associations, AsChangeset, Identifiable, Queryable, Debug, Insertable)]
|
||||||
#[belongs_to(Character)]
|
#[belongs_to(Character)]
|
||||||
#[primary_key(character_id)]
|
#[primary_key(character_id)]
|
||||||
@ -145,6 +154,45 @@ pub struct Inventory {
|
|||||||
items: InventoryData,
|
items: InventoryData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A wrapper type for Inventory components used to serialise to and from JSON
|
||||||
|
/// If the column contains malformed JSON, a default inventory is returned
|
||||||
|
#[derive(SqlType, AsExpression, Debug, Deserialize, Serialize, FromSqlRow, PartialEq)]
|
||||||
|
#[sql_type = "Text"]
|
||||||
|
pub struct InventoryData(comp::Inventory);
|
||||||
|
|
||||||
|
impl<DB> diesel::deserialize::FromSql<Text, DB> for InventoryData
|
||||||
|
where
|
||||||
|
DB: diesel::backend::Backend,
|
||||||
|
String: diesel::deserialize::FromSql<Text, DB>,
|
||||||
|
{
|
||||||
|
fn from_sql(
|
||||||
|
bytes: Option<&<DB as diesel::backend::Backend>::RawValue>,
|
||||||
|
) -> diesel::deserialize::Result<Self> {
|
||||||
|
let t = String::from_sql(bytes)?;
|
||||||
|
|
||||||
|
match serde_json::from_str(&t) {
|
||||||
|
Ok(data) => Ok(Self(data)),
|
||||||
|
Err(error) => {
|
||||||
|
log::warn!("Failed to deserialise inventory data: {}", error);
|
||||||
|
Ok(Self(comp::Inventory::default()))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<DB> diesel::serialize::ToSql<Text, DB> for InventoryData
|
||||||
|
where
|
||||||
|
DB: diesel::backend::Backend,
|
||||||
|
{
|
||||||
|
fn to_sql<W: std::io::Write>(
|
||||||
|
&self,
|
||||||
|
out: &mut diesel::serialize::Output<W, DB>,
|
||||||
|
) -> diesel::serialize::Result {
|
||||||
|
let s = serde_json::to_string(&self.0)?;
|
||||||
|
<String as diesel::serialize::ToSql<Text, DB>>::to_sql(&s, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<(i32, comp::Inventory)> for Inventory {
|
impl From<(i32, comp::Inventory)> for Inventory {
|
||||||
fn from(data: (i32, comp::Inventory)) -> Inventory {
|
fn from(data: (i32, comp::Inventory)) -> Inventory {
|
||||||
let (character_id, inventory) = data;
|
let (character_id, inventory) = data;
|
||||||
@ -175,12 +223,30 @@ impl From<&comp::Inventory> for InventoryUpdate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Type handling for a character's inventory, which is stored as JSON strings
|
/// Loadout holds the armor and weapons owned by a character. This data is
|
||||||
|
/// seperate from the inventory. At the moment, characters have a single Loadout
|
||||||
|
/// which is loaded with their character data, however there are plans for each
|
||||||
|
/// character to have multiple Loadouts which they can switch between during
|
||||||
|
/// gameplay. Due to this Loadouts have a many to one relationship with
|
||||||
|
/// characetrs, and a distinct `id`.
|
||||||
|
#[derive(Associations, Queryable, Debug, Identifiable)]
|
||||||
|
#[belongs_to(Character)]
|
||||||
|
#[primary_key(id)]
|
||||||
|
#[table_name = "loadout"]
|
||||||
|
pub struct Loadout {
|
||||||
|
pub id: i32,
|
||||||
|
pub character_id: i32,
|
||||||
|
pub items: LoadoutData,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A wrapper type for Loadout components used to serialise to and from JSON
|
||||||
|
/// If the column contains malformed JSON, a default loadout is returned, with
|
||||||
|
/// the starter sword set as the main weapon
|
||||||
#[derive(SqlType, AsExpression, Debug, Deserialize, Serialize, FromSqlRow, PartialEq)]
|
#[derive(SqlType, AsExpression, Debug, Deserialize, Serialize, FromSqlRow, PartialEq)]
|
||||||
#[sql_type = "Text"]
|
#[sql_type = "Text"]
|
||||||
pub struct InventoryData(comp::Inventory);
|
pub struct LoadoutData(comp::Loadout);
|
||||||
|
|
||||||
impl<DB> diesel::deserialize::FromSql<Text, DB> for InventoryData
|
impl<DB> diesel::deserialize::FromSql<Text, DB> for LoadoutData
|
||||||
where
|
where
|
||||||
DB: diesel::backend::Backend,
|
DB: diesel::backend::Backend,
|
||||||
String: diesel::deserialize::FromSql<Text, DB>,
|
String: diesel::deserialize::FromSql<Text, DB>,
|
||||||
@ -193,15 +259,23 @@ where
|
|||||||
match serde_json::from_str(&t) {
|
match serde_json::from_str(&t) {
|
||||||
Ok(data) => Ok(Self(data)),
|
Ok(data) => Ok(Self(data)),
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::warn!("Failed to deserialise inventory data: {}", error);
|
log::warn!("Failed to deserialise loadout data: {}", error);
|
||||||
|
|
||||||
Ok(Self(comp::Inventory::default()))
|
// We don't have a weapon reference here, so we default to sword
|
||||||
|
let loadout = LoadoutBuilder::new()
|
||||||
|
.defaults()
|
||||||
|
.active_item(LoadoutBuilder::default_item_config_from_str(Some(
|
||||||
|
"common.items.weapons.sword.starter_sword",
|
||||||
|
)))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Ok(Self(loadout))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<DB> diesel::serialize::ToSql<Text, DB> for InventoryData
|
impl<DB> diesel::serialize::ToSql<Text, DB> for LoadoutData
|
||||||
where
|
where
|
||||||
DB: diesel::backend::Backend,
|
DB: diesel::backend::Backend,
|
||||||
{
|
{
|
||||||
@ -214,6 +288,46 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&Loadout> for comp::Loadout {
|
||||||
|
fn from(loadout: &Loadout) -> comp::Loadout { loadout.items.0.clone() }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable, PartialEq, Debug)]
|
||||||
|
#[table_name = "loadout"]
|
||||||
|
pub struct NewLoadout {
|
||||||
|
pub character_id: i32,
|
||||||
|
pub items: LoadoutData,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(i32, &comp::Loadout)> for NewLoadout {
|
||||||
|
fn from(data: (i32, &comp::Loadout)) -> NewLoadout {
|
||||||
|
let (character_id, loadout) = data;
|
||||||
|
|
||||||
|
NewLoadout {
|
||||||
|
character_id,
|
||||||
|
items: LoadoutData(loadout.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable, PartialEq, Debug, AsChangeset)]
|
||||||
|
#[table_name = "loadout"]
|
||||||
|
pub struct LoadoutUpdate {
|
||||||
|
pub character_id: i32,
|
||||||
|
pub items: LoadoutData,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(i32, &comp::Loadout)> for LoadoutUpdate {
|
||||||
|
fn from(data: (i32, &comp::Loadout)) -> LoadoutUpdate {
|
||||||
|
let (character_id, loadout) = data;
|
||||||
|
|
||||||
|
LoadoutUpdate {
|
||||||
|
character_id,
|
||||||
|
items: LoadoutData(loadout.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -29,6 +29,14 @@ table! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
loadout (id) {
|
||||||
|
id -> Integer,
|
||||||
|
character_id -> Integer,
|
||||||
|
items -> Text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
table! {
|
table! {
|
||||||
stats (character_id) {
|
stats (character_id) {
|
||||||
character_id -> Integer,
|
character_id -> Integer,
|
||||||
@ -42,6 +50,7 @@ table! {
|
|||||||
|
|
||||||
joinable!(body -> character (character_id));
|
joinable!(body -> character (character_id));
|
||||||
joinable!(inventory -> character (character_id));
|
joinable!(inventory -> character (character_id));
|
||||||
|
joinable!(loadout -> character (character_id));
|
||||||
joinable!(stats -> character (character_id));
|
joinable!(stats -> character (character_id));
|
||||||
|
|
||||||
allow_tables_to_appear_in_same_query!(body, character, inventory, stats);
|
allow_tables_to_appear_in_same_query!(body, character, inventory, loadout, stats,);
|
||||||
|
@ -3,8 +3,7 @@ use crate::{
|
|||||||
SpawnPoint,
|
SpawnPoint,
|
||||||
};
|
};
|
||||||
use common::{
|
use common::{
|
||||||
assets,
|
comp,
|
||||||
comp::{self, item},
|
|
||||||
effect::Effect,
|
effect::Effect,
|
||||||
msg::{
|
msg::{
|
||||||
CharacterInfo, ClientState, PlayerListUpdate, RegisterError, RequestStateError, ServerMsg,
|
CharacterInfo, ClientState, PlayerListUpdate, RegisterError, RequestStateError, ServerMsg,
|
||||||
@ -40,7 +39,6 @@ pub trait StateExt {
|
|||||||
entity: EcsEntity,
|
entity: EcsEntity,
|
||||||
character_id: i32,
|
character_id: i32,
|
||||||
body: comp::Body,
|
body: comp::Body,
|
||||||
main: Option<String>,
|
|
||||||
server_settings: &ServerSettings,
|
server_settings: &ServerSettings,
|
||||||
);
|
);
|
||||||
fn notify_registered_clients(&self, msg: ServerMsg);
|
fn notify_registered_clients(&self, msg: ServerMsg);
|
||||||
@ -159,7 +157,6 @@ impl StateExt for State {
|
|||||||
entity: EcsEntity,
|
entity: EcsEntity,
|
||||||
character_id: i32,
|
character_id: i32,
|
||||||
body: comp::Body,
|
body: comp::Body,
|
||||||
main: Option<String>,
|
|
||||||
server_settings: &ServerSettings,
|
server_settings: &ServerSettings,
|
||||||
) {
|
) {
|
||||||
// Grab persisted character data from the db and insert their associated
|
// Grab persisted character data from the db and insert their associated
|
||||||
@ -169,9 +166,10 @@ impl StateExt for State {
|
|||||||
character_id,
|
character_id,
|
||||||
&server_settings.persistence_db_dir,
|
&server_settings.persistence_db_dir,
|
||||||
) {
|
) {
|
||||||
Ok((stats, inventory)) => {
|
Ok((stats, inventory, loadout)) => {
|
||||||
self.write_component(entity, stats);
|
self.write_component(entity, stats);
|
||||||
self.write_component(entity, inventory);
|
self.write_component(entity, inventory);
|
||||||
|
self.write_component(entity, loadout);
|
||||||
},
|
},
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
@ -190,9 +188,6 @@ impl StateExt for State {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Give no item when an invalid specifier is given
|
|
||||||
let main = main.and_then(|specifier| assets::load_cloned::<comp::Item>(&specifier).ok());
|
|
||||||
|
|
||||||
let spawn_point = self.ecs().read_resource::<SpawnPoint>().0;
|
let spawn_point = self.ecs().read_resource::<SpawnPoint>().0;
|
||||||
|
|
||||||
self.write_component(entity, body);
|
self.write_component(entity, body);
|
||||||
@ -214,47 +209,6 @@ impl StateExt for State {
|
|||||||
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::default()),
|
comp::InventoryUpdate::new(comp::InventoryUpdateEvent::default()),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.write_component(
|
|
||||||
entity,
|
|
||||||
if let Some(item::ItemKind::Tool(tool)) = main.as_ref().map(|i| &i.kind) {
|
|
||||||
let mut abilities = tool.get_abilities();
|
|
||||||
let mut ability_drain = abilities.drain(..);
|
|
||||||
comp::Loadout {
|
|
||||||
active_item: main.map(|item| comp::ItemConfig {
|
|
||||||
item,
|
|
||||||
ability1: ability_drain.next(),
|
|
||||||
ability2: ability_drain.next(),
|
|
||||||
ability3: ability_drain.next(),
|
|
||||||
block_ability: Some(comp::CharacterAbility::BasicBlock),
|
|
||||||
dodge_ability: Some(comp::CharacterAbility::Roll),
|
|
||||||
}),
|
|
||||||
second_item: None,
|
|
||||||
shoulder: None,
|
|
||||||
chest: Some(assets::load_expect_cloned(
|
|
||||||
"common.items.armor.starter.rugged_chest",
|
|
||||||
)),
|
|
||||||
belt: None,
|
|
||||||
hand: None,
|
|
||||||
pants: Some(assets::load_expect_cloned(
|
|
||||||
"common.items.armor.starter.rugged_pants",
|
|
||||||
)),
|
|
||||||
foot: Some(assets::load_expect_cloned(
|
|
||||||
"common.items.armor.starter.sandals_0",
|
|
||||||
)),
|
|
||||||
back: None,
|
|
||||||
ring: None,
|
|
||||||
neck: None,
|
|
||||||
lantern: Some(assets::load_expect_cloned(
|
|
||||||
"common.items.armor.starter.lantern",
|
|
||||||
)),
|
|
||||||
head: None,
|
|
||||||
tabard: None,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
comp::Loadout::default()
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Set the character id for the player
|
// Set the character id for the player
|
||||||
// TODO this results in a warning in the console: "Error modifying synced
|
// TODO this results in a warning in the console: "Error modifying synced
|
||||||
// component, it doesn't seem to exist"
|
// component, it doesn't seem to exist"
|
||||||
|
@ -190,11 +190,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
},
|
},
|
||||||
_ => {},
|
_ => {},
|
||||||
},
|
},
|
||||||
ClientMsg::Character {
|
ClientMsg::Character { character_id, body } => match client.client_state {
|
||||||
character_id,
|
|
||||||
body,
|
|
||||||
main,
|
|
||||||
} => match client.client_state {
|
|
||||||
// Become Registered first.
|
// Become Registered first.
|
||||||
ClientState::Connected => client.error_state(RequestStateError::Impossible),
|
ClientState::Connected => client.error_state(RequestStateError::Impossible),
|
||||||
ClientState::Registered | ClientState::Spectator => {
|
ClientState::Registered | ClientState::Spectator => {
|
||||||
@ -218,7 +214,6 @@ impl<'a> System<'a> for Sys {
|
|||||||
entity,
|
entity,
|
||||||
character_id,
|
character_id,
|
||||||
body,
|
body,
|
||||||
main,
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
ClientState::Character => client.error_state(RequestStateError::Already),
|
ClientState::Character => client.error_state(RequestStateError::Already),
|
||||||
|
@ -2,7 +2,7 @@ use crate::{
|
|||||||
persistence::character,
|
persistence::character,
|
||||||
sys::{SysScheduler, SysTimer},
|
sys::{SysScheduler, SysTimer},
|
||||||
};
|
};
|
||||||
use common::comp::{Inventory, Player, Stats};
|
use common::comp::{Inventory, Loadout, Player, Stats};
|
||||||
use specs::{Join, ReadExpect, ReadStorage, System, Write};
|
use specs::{Join, ReadExpect, ReadStorage, System, Write};
|
||||||
|
|
||||||
pub struct Sys;
|
pub struct Sys;
|
||||||
@ -12,6 +12,7 @@ impl<'a> System<'a> for Sys {
|
|||||||
ReadStorage<'a, Player>,
|
ReadStorage<'a, Player>,
|
||||||
ReadStorage<'a, Stats>,
|
ReadStorage<'a, Stats>,
|
||||||
ReadStorage<'a, Inventory>,
|
ReadStorage<'a, Inventory>,
|
||||||
|
ReadStorage<'a, Loadout>,
|
||||||
ReadExpect<'a, character::CharacterUpdater>,
|
ReadExpect<'a, character::CharacterUpdater>,
|
||||||
Write<'a, SysScheduler<Self>>,
|
Write<'a, SysScheduler<Self>>,
|
||||||
Write<'a, SysTimer<Self>>,
|
Write<'a, SysTimer<Self>>,
|
||||||
@ -19,15 +20,30 @@ impl<'a> System<'a> for Sys {
|
|||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
(players, player_stats, player_inventories, updater, mut scheduler, mut timer): Self::SystemData,
|
(
|
||||||
|
players,
|
||||||
|
player_stats,
|
||||||
|
player_inventories,
|
||||||
|
player_loadouts,
|
||||||
|
updater,
|
||||||
|
mut scheduler,
|
||||||
|
mut timer,
|
||||||
|
): Self::SystemData,
|
||||||
) {
|
) {
|
||||||
if scheduler.should_run() {
|
if scheduler.should_run() {
|
||||||
timer.start();
|
timer.start();
|
||||||
updater.batch_update(
|
updater.batch_update(
|
||||||
(&players, &player_stats, &player_inventories)
|
(
|
||||||
|
&players,
|
||||||
|
&player_stats,
|
||||||
|
&player_inventories,
|
||||||
|
&player_loadouts,
|
||||||
|
)
|
||||||
.join()
|
.join()
|
||||||
.filter_map(|(player, stats, inventory)| {
|
.filter_map(|(player, stats, inventory, loadout)| {
|
||||||
player.character_id.map(|id| (id, stats, inventory))
|
player
|
||||||
|
.character_id
|
||||||
|
.map(|id| (id, stats, inventory, loadout))
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
timer.end();
|
timer.end();
|
||||||
|
@ -88,11 +88,9 @@ impl PlayState for CharSelectionState {
|
|||||||
char_data.get(self.char_selection_ui.selected_character)
|
char_data.get(self.char_selection_ui.selected_character)
|
||||||
{
|
{
|
||||||
if let Some(character_id) = selected_character.character.id {
|
if let Some(character_id) = selected_character.character.id {
|
||||||
self.client.borrow_mut().request_character(
|
self.client
|
||||||
character_id,
|
.borrow_mut()
|
||||||
selected_character.body,
|
.request_character(character_id, selected_character.body);
|
||||||
selected_character.character.tool.clone(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,9 +12,10 @@ use crate::{
|
|||||||
use client::Client;
|
use client::Client;
|
||||||
use common::{
|
use common::{
|
||||||
assets,
|
assets,
|
||||||
assets::{load, load_expect},
|
assets::load_expect,
|
||||||
character::{Character, CharacterItem, MAX_CHARACTERS_PER_PLAYER},
|
character::{Character, CharacterItem, MAX_CHARACTERS_PER_PLAYER},
|
||||||
comp::{self, humanoid},
|
comp::{self, humanoid},
|
||||||
|
LoadoutBuilder,
|
||||||
};
|
};
|
||||||
use conrod_core::{
|
use conrod_core::{
|
||||||
color,
|
color,
|
||||||
@ -25,7 +26,6 @@ use conrod_core::{
|
|||||||
widget::{text_box::Event as TextBoxEvent, Button, Image, Rectangle, Scrollbar, Text, TextBox},
|
widget::{text_box::Event as TextBoxEvent, Button, Image, Rectangle, Scrollbar, Text, TextBox},
|
||||||
widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, UiCell, Widget,
|
widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, UiCell, Widget,
|
||||||
};
|
};
|
||||||
use log::error;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
const STARTER_HAMMER: &str = "common.items.weapons.hammer.starter_hammer";
|
const STARTER_HAMMER: &str = "common.items.weapons.hammer.starter_hammer";
|
||||||
@ -354,6 +354,10 @@ impl CharSelectionUi {
|
|||||||
},
|
},
|
||||||
body,
|
body,
|
||||||
level: 1,
|
level: 1,
|
||||||
|
loadout: LoadoutBuilder::new()
|
||||||
|
.defaults()
|
||||||
|
.active_item(LoadoutBuilder::default_item_config_from_str(*tool))
|
||||||
|
.build(),
|
||||||
}])
|
}])
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -363,50 +367,7 @@ impl CharSelectionUi {
|
|||||||
match &mut self.mode {
|
match &mut self.mode {
|
||||||
Mode::Select(character_list) => {
|
Mode::Select(character_list) => {
|
||||||
if let Some(data) = character_list {
|
if let Some(data) = character_list {
|
||||||
if let Some(character_item) = data.get(self.selected_character) {
|
data.get(self.selected_character).map(|c| c.loadout.clone())
|
||||||
let loadout = comp::Loadout {
|
|
||||||
active_item: character_item.character.tool.as_ref().map(|tool| {
|
|
||||||
comp::ItemConfig {
|
|
||||||
item: (*load::<comp::Item>(&tool).unwrap_or_else(|err| {
|
|
||||||
error!(
|
|
||||||
"Could not load item {} maybe it no longer exists: \
|
|
||||||
{:?}",
|
|
||||||
&tool, err
|
|
||||||
);
|
|
||||||
load_expect("common.items.weapons.sword.starter_sword")
|
|
||||||
}))
|
|
||||||
.clone(),
|
|
||||||
ability1: None,
|
|
||||||
ability2: None,
|
|
||||||
ability3: None,
|
|
||||||
block_ability: None,
|
|
||||||
dodge_ability: None,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
second_item: None,
|
|
||||||
shoulder: None,
|
|
||||||
chest: Some(assets::load_expect_cloned(
|
|
||||||
"common.items.armor.starter.rugged_chest",
|
|
||||||
)),
|
|
||||||
belt: None,
|
|
||||||
hand: None,
|
|
||||||
pants: Some(assets::load_expect_cloned(
|
|
||||||
"common.items.armor.starter.rugged_pants",
|
|
||||||
)),
|
|
||||||
foot: Some(assets::load_expect_cloned(
|
|
||||||
"common.items.armor.starter.sandals_0",
|
|
||||||
)),
|
|
||||||
back: None,
|
|
||||||
ring: None,
|
|
||||||
neck: None,
|
|
||||||
lantern: None,
|
|
||||||
head: None,
|
|
||||||
tabard: None,
|
|
||||||
};
|
|
||||||
Some(loadout)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user