mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'xvar/skills' into 'master'
Initial implementation of skills and skill groups See merge request veloren/veloren!1135
This commit is contained in:
commit
7ac5876743
@ -13,6 +13,7 @@ mod location;
|
||||
mod phys;
|
||||
mod player;
|
||||
pub mod projectile;
|
||||
pub mod skills;
|
||||
mod stats;
|
||||
mod visual;
|
||||
|
||||
@ -42,5 +43,6 @@ pub use location::{Waypoint, WaypointArea};
|
||||
pub use phys::{Collider, ForceUpdate, Gravity, Mass, Ori, PhysicsState, Pos, Scale, Sticky, Vel};
|
||||
pub use player::Player;
|
||||
pub use projectile::Projectile;
|
||||
pub use skills::{Skill, SkillGroup, SkillGroupType, SkillSet};
|
||||
pub use stats::{Exp, HealthChange, HealthSource, Level, Stats};
|
||||
pub use visual::{LightAnimation, LightEmitter};
|
||||
|
328
common/src/comp/skills.rs
Normal file
328
common/src/comp/skills.rs
Normal file
@ -0,0 +1,328 @@
|
||||
use lazy_static::lazy_static;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
hash::Hash,
|
||||
};
|
||||
use tracing::warn;
|
||||
|
||||
lazy_static! {
|
||||
// Determines the skills that comprise each skill group - this data is used to determine
|
||||
// which of a player's skill groups a particular skill should be added to when a skill unlock
|
||||
// is requested. TODO: Externalise this data in a RON file for ease of modification
|
||||
pub static ref SKILL_GROUP_DEFS: HashMap<SkillGroupType, HashSet<Skill>> = {
|
||||
let mut defs = HashMap::new();
|
||||
defs.insert(SkillGroupType::T1, [ Skill::TestT1Skill1,
|
||||
Skill::TestT1Skill2,
|
||||
Skill::TestT1Skill3,
|
||||
Skill::TestT1Skill4,
|
||||
Skill::TestT1Skill5]
|
||||
.iter().cloned().collect::<HashSet<Skill>>());
|
||||
|
||||
defs.insert(SkillGroupType::Swords, [ Skill::TestSwordSkill1,
|
||||
Skill::TestSwordSkill2,
|
||||
Skill::TestSwordSkill3]
|
||||
.iter().cloned().collect::<HashSet<Skill>>());
|
||||
|
||||
defs.insert(SkillGroupType::Axes, [ Skill::TestAxeSkill1,
|
||||
Skill::TestAxeSkill2,
|
||||
Skill::TestAxeSkill3]
|
||||
.iter().cloned().collect::<HashSet<Skill>>());
|
||||
|
||||
defs
|
||||
};
|
||||
}
|
||||
|
||||
/// Represents a skill that a player can unlock, that either grants them some
|
||||
/// kind of active ability, or a passive effect etc. Obviously because this is
|
||||
/// an enum it doesn't describe what the skill actually -does-, this will be
|
||||
/// handled by dedicated ECS systems.
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum Skill {
|
||||
TestT1Skill1,
|
||||
TestT1Skill2,
|
||||
TestT1Skill3,
|
||||
TestT1Skill4,
|
||||
TestT1Skill5,
|
||||
TestSwordSkill1,
|
||||
TestSwordSkill2,
|
||||
TestSwordSkill3,
|
||||
TestAxeSkill1,
|
||||
TestAxeSkill2,
|
||||
TestAxeSkill3,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum SkillGroupType {
|
||||
T1,
|
||||
Swords,
|
||||
Axes,
|
||||
}
|
||||
|
||||
/// A group of skills that have been unlocked by a player. Each skill group has
|
||||
/// independent exp and skill points which are used to unlock skills in that
|
||||
/// skill group.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub struct SkillGroup {
|
||||
pub skill_group_type: SkillGroupType,
|
||||
pub exp: u32,
|
||||
pub available_sp: u8,
|
||||
}
|
||||
|
||||
impl SkillGroup {
|
||||
fn new(skill_group_type: SkillGroupType) -> SkillGroup {
|
||||
SkillGroup {
|
||||
skill_group_type,
|
||||
exp: 0,
|
||||
available_sp: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains all of a player's skill groups and skills. Provides methods for
|
||||
/// manipulating assigned skills and skill groups including unlocking skills,
|
||||
/// refunding skills etc.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct SkillSet {
|
||||
pub skill_groups: Vec<SkillGroup>,
|
||||
pub skills: HashSet<Skill>,
|
||||
}
|
||||
|
||||
impl Default for SkillSet {
|
||||
/// Instantiate a new skill set with the default skill groups with no
|
||||
/// unlocked skills in them - used when adding a skill set to a new
|
||||
/// player
|
||||
fn default() -> Self {
|
||||
// TODO: Default skill groups for new players?
|
||||
Self {
|
||||
skill_groups: Vec::new(),
|
||||
skills: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SkillSet {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
skill_groups: Vec::new(),
|
||||
skills: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Game design to determine how skill groups are unlocked
|
||||
/// Unlocks a skill group for a player. It starts with 0 exp and 0 skill
|
||||
/// points.
|
||||
///
|
||||
/// ```
|
||||
/// use veloren_common::comp::skills::{SkillGroupType, SkillSet};
|
||||
///
|
||||
/// let mut skillset = SkillSet::new();
|
||||
/// skillset.unlock_skill_group(SkillGroupType::Axes);
|
||||
///
|
||||
/// assert_eq!(skillset.skill_groups.len(), 1);
|
||||
/// ```
|
||||
pub fn unlock_skill_group(&mut self, skill_group_type: SkillGroupType) {
|
||||
if !self
|
||||
.skill_groups
|
||||
.iter()
|
||||
.any(|x| x.skill_group_type == skill_group_type)
|
||||
{
|
||||
self.skill_groups.push(SkillGroup::new(skill_group_type));
|
||||
} else {
|
||||
warn!("Tried to unlock already known skill group");
|
||||
}
|
||||
}
|
||||
|
||||
/// Unlocks a skill for a player, assuming they have the relevant skill
|
||||
/// group unlocked and available SP in that skill group.
|
||||
///
|
||||
/// ```
|
||||
/// use veloren_common::comp::skills::{Skill, SkillGroupType, SkillSet};
|
||||
///
|
||||
/// let mut skillset = SkillSet::new();
|
||||
/// skillset.unlock_skill_group(SkillGroupType::Axes);
|
||||
/// skillset.add_skill_points(SkillGroupType::Axes, 1);
|
||||
///
|
||||
/// skillset.unlock_skill(Skill::TestAxeSkill2);
|
||||
///
|
||||
/// assert_eq!(skillset.skills.len(), 1);
|
||||
/// ```
|
||||
pub fn unlock_skill(&mut self, skill: Skill) {
|
||||
if !self.skills.contains(&skill) {
|
||||
if let Some(skill_group_type) = SkillSet::get_skill_group_type_for_skill(&skill) {
|
||||
if let Some(mut skill_group) = self
|
||||
.skill_groups
|
||||
.iter_mut()
|
||||
.find(|x| x.skill_group_type == skill_group_type)
|
||||
{
|
||||
if skill_group.available_sp > 0 {
|
||||
skill_group.available_sp -= 1;
|
||||
self.skills.insert(skill);
|
||||
} else {
|
||||
warn!("Tried to unlock skill for skill group with no available SP");
|
||||
}
|
||||
} else {
|
||||
warn!("Tried to unlock skill for a skill group that player does not have");
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
?skill,
|
||||
"Tried to unlock skill that does not exist in any skill group!"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
warn!("Tried to unlock already unlocked skill");
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes a skill from a player and refunds 1 skill point in the relevant
|
||||
/// skill group.
|
||||
///
|
||||
/// ```
|
||||
/// use veloren_common::comp::skills::{Skill, SkillGroupType, SkillSet};
|
||||
///
|
||||
/// let mut skillset = SkillSet::new();
|
||||
/// skillset.unlock_skill_group(SkillGroupType::Axes);
|
||||
/// skillset.add_skill_points(SkillGroupType::Axes, 1);
|
||||
/// skillset.unlock_skill(Skill::TestAxeSkill2);
|
||||
///
|
||||
/// skillset.refund_skill(Skill::TestAxeSkill2);
|
||||
///
|
||||
/// assert_eq!(skillset.skills.len(), 0);
|
||||
/// ```
|
||||
pub fn refund_skill(&mut self, skill: Skill) {
|
||||
if self.skills.contains(&skill) {
|
||||
if let Some(skill_group_type) = SkillSet::get_skill_group_type_for_skill(&skill) {
|
||||
if let Some(mut skill_group) = self
|
||||
.skill_groups
|
||||
.iter_mut()
|
||||
.find(|x| x.skill_group_type == skill_group_type)
|
||||
{
|
||||
skill_group.available_sp += 1;
|
||||
self.skills.remove(&skill);
|
||||
} else {
|
||||
warn!("Tried to refund skill for a skill group that player does not have");
|
||||
}
|
||||
} else {
|
||||
warn!(
|
||||
?skill,
|
||||
"Tried to refund skill that does not exist in any skill group"
|
||||
)
|
||||
}
|
||||
} else {
|
||||
warn!("Tried to refund skill that has not been unlocked");
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the skill group type for a skill from the static skill group
|
||||
/// definitions.
|
||||
fn get_skill_group_type_for_skill(skill: &Skill) -> Option<SkillGroupType> {
|
||||
SKILL_GROUP_DEFS.iter().find_map(|(key, val)| {
|
||||
if val.contains(&skill) {
|
||||
Some(*key)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Adds skill points to a skill group as long as the player has that skill
|
||||
/// group type.
|
||||
///
|
||||
/// ```
|
||||
/// use veloren_common::comp::skills::{SkillGroupType, SkillSet};
|
||||
///
|
||||
/// let mut skillset = SkillSet::new();
|
||||
/// skillset.unlock_skill_group(SkillGroupType::Axes);
|
||||
/// skillset.add_skill_points(SkillGroupType::Axes, 1);
|
||||
///
|
||||
/// assert_eq!(skillset.skill_groups[0].available_sp, 1);
|
||||
/// ```
|
||||
pub fn add_skill_points(
|
||||
&mut self,
|
||||
skill_group_type: SkillGroupType,
|
||||
number_of_skill_points: u8,
|
||||
) {
|
||||
if let Some(mut skill_group) = self
|
||||
.skill_groups
|
||||
.iter_mut()
|
||||
.find(|x| x.skill_group_type == skill_group_type)
|
||||
{
|
||||
skill_group.available_sp += number_of_skill_points;
|
||||
} else {
|
||||
warn!("Tried to add skill points to a skill group that player does not have");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_refund_skill() {
|
||||
let mut skillset = SkillSet::new();
|
||||
skillset.unlock_skill_group(SkillGroupType::Axes);
|
||||
skillset.add_skill_points(SkillGroupType::Axes, 1);
|
||||
skillset.unlock_skill(Skill::TestAxeSkill2);
|
||||
|
||||
assert_eq!(skillset.skill_groups[0].available_sp, 0);
|
||||
assert_eq!(skillset.skills.len(), 1);
|
||||
assert_eq!(
|
||||
skillset.skills.get(&Skill::TestAxeSkill2),
|
||||
Some(&Skill::TestAxeSkill2)
|
||||
);
|
||||
|
||||
skillset.refund_skill(Skill::TestAxeSkill2);
|
||||
|
||||
assert_eq!(skillset.skill_groups[0].available_sp, 1);
|
||||
assert_eq!(skillset.skills.get(&Skill::TestAxeSkill2), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unlock_skillgroup() {
|
||||
let mut skillset = SkillSet::new();
|
||||
skillset.unlock_skill_group(SkillGroupType::Axes);
|
||||
|
||||
assert_eq!(skillset.skill_groups.len(), 1);
|
||||
assert_eq!(
|
||||
skillset.skill_groups[0],
|
||||
SkillGroup::new(SkillGroupType::Axes)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unlock_skill() {
|
||||
let mut skillset = SkillSet::new();
|
||||
|
||||
skillset.unlock_skill_group(SkillGroupType::Axes);
|
||||
skillset.add_skill_points(SkillGroupType::Axes, 1);
|
||||
|
||||
assert_eq!(skillset.skill_groups[0].available_sp, 1);
|
||||
assert_eq!(skillset.skills.len(), 0);
|
||||
|
||||
// Try unlocking a skill with enough skill points
|
||||
skillset.unlock_skill(Skill::TestAxeSkill2);
|
||||
|
||||
assert_eq!(skillset.skill_groups[0].available_sp, 0);
|
||||
assert_eq!(skillset.skills.len(), 1);
|
||||
assert_eq!(
|
||||
skillset.skills.get(&Skill::TestAxeSkill2),
|
||||
Some(&Skill::TestAxeSkill2)
|
||||
);
|
||||
|
||||
// Try unlocking a skill without enough skill points
|
||||
skillset.unlock_skill(Skill::TestAxeSkill1);
|
||||
|
||||
assert_eq!(skillset.skills.len(), 1);
|
||||
assert_eq!(skillset.skills.get(&Skill::TestAxeSkill1), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_skill_points() {
|
||||
let mut skillset = SkillSet::new();
|
||||
skillset.unlock_skill_group(SkillGroupType::Axes);
|
||||
skillset.add_skill_points(SkillGroupType::Axes, 1);
|
||||
|
||||
assert_eq!(skillset.skill_groups[0].available_sp, 1);
|
||||
}
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
use crate::{
|
||||
comp,
|
||||
comp::{body::humanoid::Species, Body},
|
||||
comp::{body::humanoid::Species, skills::SkillSet, Body},
|
||||
sync::Uid,
|
||||
};
|
||||
use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IDVStorage;
|
||||
use std::{error::Error, fmt};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct HealthChange {
|
||||
@ -73,7 +74,7 @@ pub enum StatChangeError {
|
||||
Underflow,
|
||||
Overflow,
|
||||
}
|
||||
use std::{error::Error, fmt};
|
||||
|
||||
impl fmt::Display for StatChangeError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", match self {
|
||||
@ -124,6 +125,7 @@ pub struct Stats {
|
||||
pub health: Health,
|
||||
pub level: Level,
|
||||
pub exp: Exp,
|
||||
pub skill_set: SkillSet,
|
||||
pub endurance: u32,
|
||||
pub fitness: u32,
|
||||
pub willpower: u32,
|
||||
@ -179,6 +181,7 @@ impl Stats {
|
||||
current: 0,
|
||||
maximum: 50,
|
||||
},
|
||||
skill_set: SkillSet::default(),
|
||||
endurance,
|
||||
fitness,
|
||||
willpower,
|
||||
|
@ -1,4 +1,8 @@
|
||||
use crate::{comp, terrain::block::Block};
|
||||
use crate::{
|
||||
comp,
|
||||
comp::{Skill, SkillGroupType},
|
||||
terrain::block::Block,
|
||||
};
|
||||
use vek::*;
|
||||
|
||||
/// Messages sent from the client to the server
|
||||
@ -40,4 +44,7 @@ pub enum ClientMsg {
|
||||
},
|
||||
Disconnect,
|
||||
Terminate,
|
||||
UnlockSkill(Skill),
|
||||
RefundSkill(Skill),
|
||||
UnlockSkillGroup(SkillGroupType),
|
||||
}
|
||||
|
22
server/src/migrations/2020-07-03-194516_skills/down.sql
Normal file
22
server/src/migrations/2020-07-03-194516_skills/down.sql
Normal file
@ -0,0 +1,22 @@
|
||||
PRAGMA foreign_keys=off;
|
||||
|
||||
-- SQLite does not support removing columns from tables so we must rename the current table,
|
||||
-- recreate the previous version of the table, then copy over the data from the renamed table
|
||||
ALTER TABLE stats RENAME TO _stats_old;
|
||||
|
||||
CREATE TABLE "stats" (
|
||||
character_id INT NOT NULL PRIMARY KEY,
|
||||
level INT NOT NULL DEFAULT 1,
|
||||
exp INT NOT NULL DEFAULT 0,
|
||||
endurance INT NOT NULL DEFAULT 0,
|
||||
fitness INT NOT NULL DEFAULT 0,
|
||||
willpower INT NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY(character_id) REFERENCES "character"(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
INSERT INTO "stats" (character_id, level, exp, endurance, fitness, willpower)
|
||||
SELECT character_id, level, exp, endurance, fitness, willpower FROM _stats_old;
|
||||
|
||||
DROP TABLE _stats_old;
|
||||
|
||||
PRAGMA foreign_keys=on;
|
5
server/src/migrations/2020-07-03-194516_skills/up.sql
Normal file
5
server/src/migrations/2020-07-03-194516_skills/up.sql
Normal file
@ -0,0 +1,5 @@
|
||||
ALTER TABLE "stats" ADD COLUMN skills TEXT;
|
||||
|
||||
-- Update all existing stats records to "" which will cause characters to be populated
|
||||
-- with the default skill groups/skills on next login.
|
||||
UPDATE "stats" SET skills = '""';
|
@ -16,7 +16,7 @@ use super::{
|
||||
},
|
||||
schema,
|
||||
};
|
||||
use crate::comp;
|
||||
use crate::{comp, persistence::models::SkillSetData};
|
||||
use common::{
|
||||
character::{Character as CharacterData, CharacterItem, MAX_CHARACTERS_PER_PLAYER},
|
||||
LoadoutBuilder,
|
||||
@ -411,6 +411,7 @@ fn create_character(
|
||||
endurance: default_stats.endurance as i32,
|
||||
fitness: default_stats.fitness as i32,
|
||||
willpower: default_stats.willpower as i32,
|
||||
skills: SkillSetData(default_stats.skill_set),
|
||||
};
|
||||
|
||||
diesel::insert_into(stats::table)
|
||||
@ -576,14 +577,16 @@ fn update(
|
||||
loadout: &LoadoutUpdate,
|
||||
connection: &SqliteConnection,
|
||||
) {
|
||||
// Update Stats
|
||||
if let Err(e) =
|
||||
diesel::update(schema::stats::table.filter(schema::stats::character_id.eq(character_id)))
|
||||
.set(stats)
|
||||
.execute(connection)
|
||||
{
|
||||
warn!(?e, ?character_id, "Failed to update stats for character",)
|
||||
error!(?e, ?character_id, "Failed to update stats for character",)
|
||||
}
|
||||
|
||||
// Update Inventory
|
||||
if let Err(e) = diesel::update(
|
||||
schema::inventory::table.filter(schema::inventory::character_id.eq(character_id)),
|
||||
)
|
||||
@ -597,6 +600,7 @@ fn update(
|
||||
)
|
||||
}
|
||||
|
||||
// Update Loadout
|
||||
if let Err(e) = diesel::update(
|
||||
schema::loadout::table.filter(schema::loadout::character_id.eq(character_id)),
|
||||
)
|
||||
|
@ -92,6 +92,7 @@ pub struct Stats {
|
||||
pub endurance: i32,
|
||||
pub fitness: i32,
|
||||
pub willpower: i32,
|
||||
pub skills: SkillSetData,
|
||||
}
|
||||
|
||||
impl From<StatsJoinData<'_>> for comp::Stats {
|
||||
@ -113,7 +114,7 @@ impl From<StatsJoinData<'_>> for comp::Stats {
|
||||
base_stats.endurance = data.stats.endurance as u32;
|
||||
base_stats.fitness = data.stats.fitness as u32;
|
||||
base_stats.willpower = data.stats.willpower as u32;
|
||||
|
||||
base_stats.skill_set = data.stats.skills.0.clone();
|
||||
base_stats
|
||||
}
|
||||
}
|
||||
@ -127,6 +128,7 @@ pub struct StatsUpdate {
|
||||
pub endurance: i32,
|
||||
pub fitness: i32,
|
||||
pub willpower: i32,
|
||||
pub skills: SkillSetData,
|
||||
}
|
||||
|
||||
impl From<&comp::Stats> for StatsUpdate {
|
||||
@ -137,10 +139,50 @@ impl From<&comp::Stats> for StatsUpdate {
|
||||
endurance: stats.endurance as i32,
|
||||
fitness: stats.fitness as i32,
|
||||
willpower: stats.willpower as i32,
|
||||
skills: SkillSetData(stats.skill_set.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper type for the SkillSet of a character used to serialise to and from
|
||||
/// JSON If the column contains malformed JSON, a default skillset is returned
|
||||
#[derive(AsExpression, Debug, Deserialize, Serialize, PartialEq, FromSqlRow)]
|
||||
#[sql_type = "Text"]
|
||||
pub struct SkillSetData(pub comp::SkillSet);
|
||||
|
||||
impl<DB> diesel::deserialize::FromSql<Text, DB> for SkillSetData
|
||||
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(e) => {
|
||||
warn!(?e, "Failed to deserialize skill set data");
|
||||
Ok(Self(comp::SkillSet::default()))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<DB> diesel::serialize::ToSql<Text, DB> for SkillSetData
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Inventory storage and conversion. Inventories have a one-to-one relationship
|
||||
/// with characters.
|
||||
///
|
||||
@ -354,6 +396,7 @@ mod tests {
|
||||
endurance: 2,
|
||||
fitness: 3,
|
||||
willpower: 4,
|
||||
skills: SkillSetData(stats.skill_set)
|
||||
})
|
||||
}
|
||||
|
||||
@ -380,6 +423,7 @@ mod tests {
|
||||
endurance: 0,
|
||||
fitness: 2,
|
||||
willpower: 3,
|
||||
skills: SkillSetData(comp::SkillSet::new()),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -45,6 +45,7 @@ table! {
|
||||
endurance -> Integer,
|
||||
fitness -> Integer,
|
||||
willpower -> Integer,
|
||||
skills -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,4 +54,4 @@ joinable!(inventory -> character (character_id));
|
||||
joinable!(loadout -> character (character_id));
|
||||
joinable!(stats -> character (character_id));
|
||||
|
||||
allow_tables_to_appear_in_same_query!(body, character, inventory, loadout, stats,);
|
||||
allow_tables_to_appear_in_same_query!(body, character, inventory, loadout, stats);
|
||||
|
@ -43,7 +43,7 @@ impl Sys {
|
||||
uids: &ReadStorage<'_, Uid>,
|
||||
can_build: &ReadStorage<'_, CanBuild>,
|
||||
force_updates: &ReadStorage<'_, ForceUpdate>,
|
||||
stats: &ReadStorage<'_, Stats>,
|
||||
stats: &mut WriteStorage<'_, Stats>,
|
||||
chat_modes: &ReadStorage<'_, ChatMode>,
|
||||
accounts: &mut WriteExpect<'_, AuthProvider>,
|
||||
block_changes: &mut Write<'_, BlockChange>,
|
||||
@ -367,6 +367,21 @@ impl Sys {
|
||||
);
|
||||
}
|
||||
},
|
||||
ClientMsg::UnlockSkill(skill) => {
|
||||
stats
|
||||
.get_mut(entity)
|
||||
.map(|s| s.skill_set.unlock_skill(skill));
|
||||
},
|
||||
ClientMsg::RefundSkill(skill) => {
|
||||
stats
|
||||
.get_mut(entity)
|
||||
.map(|s| s.skill_set.refund_skill(skill));
|
||||
},
|
||||
ClientMsg::UnlockSkillGroup(skill_group_type) => {
|
||||
stats
|
||||
.get_mut(entity)
|
||||
.map(|s| s.skill_set.unlock_skill_group(skill_group_type));
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -386,7 +401,7 @@ impl<'a> System<'a> for Sys {
|
||||
ReadStorage<'a, Uid>,
|
||||
ReadStorage<'a, CanBuild>,
|
||||
ReadStorage<'a, ForceUpdate>,
|
||||
ReadStorage<'a, Stats>,
|
||||
WriteStorage<'a, Stats>,
|
||||
ReadStorage<'a, ChatMode>,
|
||||
WriteExpect<'a, AuthProvider>,
|
||||
Write<'a, BlockChange>,
|
||||
@ -416,7 +431,7 @@ impl<'a> System<'a> for Sys {
|
||||
uids,
|
||||
can_build,
|
||||
force_updates,
|
||||
stats,
|
||||
mut stats,
|
||||
chat_modes,
|
||||
mut accounts,
|
||||
mut block_changes,
|
||||
@ -476,7 +491,7 @@ impl<'a> System<'a> for Sys {
|
||||
&uids,
|
||||
&can_build,
|
||||
&force_updates,
|
||||
&stats,
|
||||
&mut stats,
|
||||
&chat_modes,
|
||||
&mut accounts,
|
||||
&mut block_changes,
|
||||
|
Loading…
Reference in New Issue
Block a user