Resolve Issue #978 - Extract SkillSet Into Its Own Component

This commit is contained in:
holychowders 2021-04-14 15:35:34 +00:00 committed by Samuel Keiffer
parent 6f9f28af18
commit 568a8d9666
36 changed files with 560 additions and 512 deletions

1
.gitignore vendored
View File

@ -5,6 +5,7 @@ target
**/*.vi **/*.vi
**/*.swp **/*.swp
**/*tags
# IntelliJ # IntelliJ

View File

@ -14,6 +14,7 @@ sum_type! {
Player(comp::Player), Player(comp::Player),
CanBuild(comp::CanBuild), CanBuild(comp::CanBuild),
Stats(comp::Stats), Stats(comp::Stats),
SkillSet(comp::SkillSet),
Buffs(comp::Buffs), Buffs(comp::Buffs),
Auras(comp::Auras), Auras(comp::Auras),
Energy(comp::Energy), Energy(comp::Energy),
@ -48,6 +49,7 @@ sum_type! {
Player(PhantomData<comp::Player>), Player(PhantomData<comp::Player>),
CanBuild(PhantomData<comp::CanBuild>), CanBuild(PhantomData<comp::CanBuild>),
Stats(PhantomData<comp::Stats>), Stats(PhantomData<comp::Stats>),
SkillSet(PhantomData<comp::SkillSet>),
Buffs(PhantomData<comp::Buffs>), Buffs(PhantomData<comp::Buffs>),
Auras(PhantomData<comp::Auras>), Auras(PhantomData<comp::Auras>),
Energy(PhantomData<comp::Energy>), Energy(PhantomData<comp::Energy>),
@ -82,6 +84,7 @@ impl sync::CompPacket for EcsCompPacket {
EcsCompPacket::Player(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Player(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::CanBuild(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::CanBuild(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Stats(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Stats(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::SkillSet(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Buffs(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Buffs(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Auras(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Auras(comp) => sync::handle_insert(comp, entity, world),
EcsCompPacket::Energy(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Energy(comp) => sync::handle_insert(comp, entity, world),
@ -120,6 +123,7 @@ impl sync::CompPacket for EcsCompPacket {
EcsCompPacket::Player(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Player(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::CanBuild(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::CanBuild(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Stats(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Stats(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::SkillSet(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Buffs(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Buffs(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Auras(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Auras(comp) => sync::handle_modify(comp, entity, world),
EcsCompPacket::Energy(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Energy(comp) => sync::handle_modify(comp, entity, world),
@ -158,6 +162,7 @@ impl sync::CompPacket for EcsCompPacket {
EcsCompPhantom::Player(_) => sync::handle_remove::<comp::Player>(entity, world), EcsCompPhantom::Player(_) => sync::handle_remove::<comp::Player>(entity, world),
EcsCompPhantom::CanBuild(_) => sync::handle_remove::<comp::CanBuild>(entity, world), EcsCompPhantom::CanBuild(_) => sync::handle_remove::<comp::CanBuild>(entity, world),
EcsCompPhantom::Stats(_) => sync::handle_remove::<comp::Stats>(entity, world), EcsCompPhantom::Stats(_) => sync::handle_remove::<comp::Stats>(entity, world),
EcsCompPhantom::SkillSet(_) => sync::handle_remove::<comp::SkillSet>(entity, world),
EcsCompPhantom::Buffs(_) => sync::handle_remove::<comp::Buffs>(entity, world), EcsCompPhantom::Buffs(_) => sync::handle_remove::<comp::Buffs>(entity, world),
EcsCompPhantom::Auras(_) => sync::handle_remove::<comp::Auras>(entity, world), EcsCompPhantom::Auras(_) => sync::handle_remove::<comp::Auras>(entity, world),
EcsCompPhantom::Energy(_) => sync::handle_remove::<comp::Energy>(entity, world), EcsCompPhantom::Energy(_) => sync::handle_remove::<comp::Energy>(entity, world),

View File

@ -13,7 +13,7 @@ use crate::{
poise::PoiseChange, poise::PoiseChange,
skills::SkillGroupKind, skills::SkillGroupKind,
Body, Combo, Energy, EnergyChange, EnergySource, Health, HealthChange, HealthSource, Body, Combo, Energy, EnergyChange, EnergySource, Health, HealthChange, HealthSource,
Inventory, Stats, Inventory, SkillSet, Stats,
}, },
event::ServerEvent, event::ServerEvent,
outcome::Outcome, outcome::Outcome,
@ -742,15 +742,15 @@ pub fn weapon_rating<T: ItemDesc>(item: &T, msm: &MaterialStatManifest) -> f32 {
} }
} }
fn weapon_skills(inventory: &Inventory, stats: &Stats) -> f32 { fn weapon_skills(inventory: &Inventory, skill_set: &SkillSet) -> f32 {
let (mainhand, offhand) = get_weapons(inventory); let (mainhand, offhand) = get_weapons(inventory);
let mainhand_skills = if let Some(tool) = mainhand { let mainhand_skills = if let Some(tool) = mainhand {
stats.skill_set.earned_sp(SkillGroupKind::Weapon(tool)) as f32 skill_set.earned_sp(SkillGroupKind::Weapon(tool)) as f32
} else { } else {
0.0 0.0
}; };
let offhand_skills = if let Some(tool) = offhand { let offhand_skills = if let Some(tool) = offhand {
stats.skill_set.earned_sp(SkillGroupKind::Weapon(tool)) as f32 skill_set.earned_sp(SkillGroupKind::Weapon(tool)) as f32
} else { } else {
0.0 0.0
}; };
@ -779,7 +779,7 @@ fn get_weapon_rating(inventory: &Inventory, msm: &MaterialStatManifest) -> f32 {
pub fn combat_rating( pub fn combat_rating(
inventory: &Inventory, inventory: &Inventory,
health: &Health, health: &Health,
stats: &Stats, skill_set: &SkillSet,
body: Body, body: Body,
msm: &MaterialStatManifest, msm: &MaterialStatManifest,
) -> f32 { ) -> f32 {
@ -793,8 +793,8 @@ pub fn combat_rating(
// Assumes a standard person has earned 20 skill points in the general skill // Assumes a standard person has earned 20 skill points in the general skill
// tree and 10 skill points for the weapon skill tree // tree and 10 skill points for the weapon skill tree
let skills_rating = (stats.skill_set.earned_sp(SkillGroupKind::General) as f32 / 20.0 let skills_rating = (skill_set.earned_sp(SkillGroupKind::General) as f32 / 20.0
+ weapon_skills(inventory, stats) / 10.0) + weapon_skills(inventory, skill_set) / 10.0)
/ 2.0; / 2.0;
let weapon_rating = get_weapon_rating(inventory, msm); let weapon_rating = get_weapon_rating(inventory, msm);

View File

@ -5,6 +5,8 @@ use crate::{
use hashbrown::{HashMap, HashSet}; use hashbrown::{HashMap, HashSet};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage};
use specs_idvs::IdvStorage;
use std::hash::Hash; use std::hash::Hash;
use tracing::{trace, warn}; use tracing::{trace, warn};
@ -334,6 +336,10 @@ pub struct SkillSet {
pub modify_energy: bool, pub modify_energy: bool,
} }
impl Component for SkillSet {
type Storage = DerefFlaggedStorage<Self, IdvStorage<Self>>;
}
impl Default for SkillSet { impl Default for SkillSet {
/// Instantiate a new skill set with the default skill groups with no /// 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 /// unlocked skills in them - used when adding a skill set to a new

View File

@ -1,4 +1,3 @@
use crate::comp::skills::SkillSet;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage}; use specs::{Component, DerefFlaggedStorage};
use specs_idvs::IdvStorage; use specs_idvs::IdvStorage;
@ -24,9 +23,6 @@ impl Error for StatChangeError {}
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Stats { pub struct Stats {
pub name: String, pub name: String,
// TODO: Make skillset a separate component, probably too heavy for something that will
// potentially be updated every tick (especially as more buffs are added)
pub skill_set: SkillSet,
pub damage_reduction: f32, pub damage_reduction: f32,
pub max_health_modifier: f32, pub max_health_modifier: f32,
} }
@ -35,7 +31,6 @@ impl Stats {
pub fn new(name: String) -> Self { pub fn new(name: String) -> Self {
Self { Self {
name, name,
skill_set: SkillSet::default(),
damage_reduction: 0.0, damage_reduction: 0.0,
max_health_modifier: 1.0, max_health_modifier: 1.0,
} }
@ -46,7 +41,6 @@ impl Stats {
pub fn empty() -> Self { pub fn empty() -> Self {
Self { Self {
name: "".to_owned(), name: "".to_owned(),
skill_set: SkillSet::default(),
damage_reduction: 0.0, damage_reduction: 0.0,
max_health_modifier: 1.0, max_health_modifier: 1.0,
} }

View File

@ -104,6 +104,7 @@ pub enum ServerEvent {
components: ( components: (
comp::Body, comp::Body,
comp::Stats, comp::Stats,
comp::SkillSet,
comp::Inventory, comp::Inventory,
Option<comp::Waypoint>, Option<comp::Waypoint>,
), ),
@ -115,6 +116,7 @@ pub enum ServerEvent {
CreateNpc { CreateNpc {
pos: comp::Pos, pos: comp::Pos,
stats: comp::Stats, stats: comp::Stats,
skill_set: comp::SkillSet,
health: comp::Health, health: comp::Health,
poise: comp::Poise, poise: comp::Poise,
loadout: comp::inventory::loadout::Loadout, loadout: comp::inventory::loadout::Loadout,

View File

@ -86,8 +86,8 @@ impl CharacterBehavior for Data {
None, None,
) )
.build(); .build();
let mut stats = comp::Stats::new("Summon".to_string()); let stats = comp::Stats::new("Summon".to_string());
stats.skill_set = SkillSetBuilder::build_skillset( let skill_set = SkillSetBuilder::build_skillset(
&None, &None,
self.static_data.summon_info.skillset_config, self.static_data.summon_info.skillset_config,
) )
@ -97,6 +97,7 @@ impl CharacterBehavior for Data {
update.server_events.push_front(ServerEvent::CreateNpc { update.server_events.push_front(ServerEvent::CreateNpc {
pos: *data.pos, pos: *data.pos,
stats, stats,
skill_set,
health: comp::Health::new( health: comp::Health::new(
body, body,
self.static_data.summon_info.health_scaling, self.static_data.summon_info.health_scaling,

View File

@ -2,7 +2,7 @@ use crate::{
comp::{ comp::{
self, item::MaterialStatManifest, Beam, Body, CharacterState, Combo, ControlAction, self, item::MaterialStatManifest, Beam, Body, CharacterState, Combo, ControlAction,
Controller, ControllerInputs, Energy, Health, InputAttr, InputKind, Inventory, Controller, ControllerInputs, Energy, Health, InputAttr, InputKind, Inventory,
InventoryAction, Melee, Ori, PhysicsState, Pos, StateUpdate, Stats, Vel, InventoryAction, Melee, Ori, PhysicsState, Pos, SkillSet, StateUpdate, Stats, Vel,
}, },
resources::DeltaTime, resources::DeltaTime,
uid::Uid, uid::Uid,
@ -91,6 +91,7 @@ pub struct JoinData<'a> {
pub melee_attack: Option<&'a Melee>, pub melee_attack: Option<&'a Melee>,
pub updater: &'a LazyUpdate, pub updater: &'a LazyUpdate,
pub stats: &'a Stats, pub stats: &'a Stats,
pub skill_set: &'a SkillSet,
pub msm: &'a MaterialStatManifest, pub msm: &'a MaterialStatManifest,
pub combo: &'a Combo, pub combo: &'a Combo,
pub alignment: Option<&'a comp::Alignment>, pub alignment: Option<&'a comp::Alignment>,
@ -121,6 +122,7 @@ pub struct JoinStruct<'a> {
pub melee_attack: Option<&'a Melee>, pub melee_attack: Option<&'a Melee>,
pub beam: Option<&'a Beam>, pub beam: Option<&'a Beam>,
pub stat: &'a Stats, pub stat: &'a Stats,
pub skill_set: &'a SkillSet,
pub combo: &'a Combo, pub combo: &'a Combo,
pub alignment: Option<&'a comp::Alignment>, pub alignment: Option<&'a comp::Alignment>,
} }
@ -148,6 +150,7 @@ impl<'a> JoinData<'a> {
physics: j.physics, physics: j.physics,
melee_attack: j.melee_attack, melee_attack: j.melee_attack,
stats: j.stat, stats: j.stat,
skill_set: j.skill_set,
updater, updater,
dt, dt,
msm, msm,

View File

@ -31,10 +31,10 @@ pub struct Data {
impl Data { impl Data {
pub fn create_adjusted_by_skills(join_data: &JoinData) -> Self { pub fn create_adjusted_by_skills(join_data: &JoinData) -> Self {
let mut data = Data::default(); let mut data = Data::default();
if let Ok(Some(level)) = join_data.stats.skill_set.skill_level(Skill::Climb(Cost)) { if let Ok(Some(level)) = join_data.skill_set.skill_level(Skill::Climb(Cost)) {
data.static_data.energy_cost *= 0.8_f32.powi(level.into()); data.static_data.energy_cost *= 0.8_f32.powi(level.into());
} }
if let Ok(Some(level)) = join_data.stats.skill_set.skill_level(Skill::Climb(Speed)) { if let Ok(Some(level)) = join_data.skill_set.skill_level(Skill::Climb(Speed)) {
data.static_data.movement_speed *= 1.2_f32.powi(level.into()); data.static_data.movement_speed *= 1.2_f32.powi(level.into());
} }
data data

View File

@ -302,11 +302,7 @@ pub fn handle_orientation(data: &JoinData, update: &mut StateUpdate, rate: f32)
fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32, depth: f32) { fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32, depth: f32) {
let mut water_accel = BASE_HUMANOID_WATER_ACCEL; let mut water_accel = BASE_HUMANOID_WATER_ACCEL;
let mut water_speed = BASE_HUMANOID_WATER_SPEED; let mut water_speed = BASE_HUMANOID_WATER_SPEED;
if let Ok(Some(level)) = data if let Ok(Some(level)) = data.skill_set.skill_level(Skill::Swim(SwimSkill::Speed)) {
.stats
.skill_set
.skill_level(Skill::Swim(SwimSkill::Speed))
{
water_speed *= 1.4_f32.powi(level.into()); water_speed *= 1.4_f32.powi(level.into());
water_accel *= 1.4_f32.powi(level.into()); water_accel *= 1.4_f32.powi(level.into());
} }
@ -501,8 +497,7 @@ fn handle_ability(data: &JoinData, update: &mut StateUpdate, input: InputKind) {
}; };
let unlocked = |(s, a): (Option<Skill>, CharacterAbility)| { let unlocked = |(s, a): (Option<Skill>, CharacterAbility)| {
s.map_or(true, |s| data.stats.skill_set.has_skill(s)) s.map_or(true, |s| data.skill_set.has_skill(s)).then_some(a)
.then_some(a)
}; };
if let Some(equip_slot) = equip_slot { if let Some(equip_slot) = equip_slot {
@ -523,7 +518,7 @@ fn handle_ability(data: &JoinData, update: &mut StateUpdate, input: InputKind) {
}) })
.map(|a| { .map(|a| {
let tool = unwrap_tool_data(data, equip_slot).map(|t| t.kind); let tool = unwrap_tool_data(data, equip_slot).map(|t| t.kind);
a.adjusted_by_skills(&data.stats.skill_set, tool) a.adjusted_by_skills(&data.skill_set, tool)
}) })
.filter(|ability| ability.requirements_paid(data, update)) .filter(|ability| ability.requirements_paid(data, update))
{ {
@ -570,8 +565,7 @@ pub fn attempt_input(data: &JoinData, update: &mut StateUpdate) {
/// attempts to perform their dodge ability /// attempts to perform their dodge ability
pub fn handle_dodge_input(data: &JoinData, update: &mut StateUpdate) { pub fn handle_dodge_input(data: &JoinData, update: &mut StateUpdate) {
if input_is_pressed(data, InputKind::Roll) && data.body.is_humanoid() { if input_is_pressed(data, InputKind::Roll) && data.body.is_humanoid() {
let ability = let ability = CharacterAbility::default_roll().adjusted_by_skills(&data.skill_set, None);
CharacterAbility::default_roll().adjusted_by_skills(&data.stats.skill_set, None);
if ability.requirements_paid(data, update) { if ability.requirements_paid(data, update) {
update.character = CharacterState::from(( update.character = CharacterState::from((
&ability, &ability,

View File

@ -11,7 +11,7 @@ use common::{
slot::{EquipSlot, Slot}, slot::{EquipSlot, Slot},
}, },
Beam, Body, CharacterState, Combo, Controller, Energy, Health, Inventory, Melee, Mounting, Beam, Body, CharacterState, Combo, Controller, Energy, Health, Inventory, Melee, Mounting,
Ori, PhysicsState, Poise, PoiseState, Pos, StateUpdate, Stats, Vel, Ori, PhysicsState, Poise, PoiseState, Pos, SkillSet, StateUpdate, Stats, Vel,
}, },
event::{EventBus, LocalEvent, ServerEvent}, event::{EventBus, LocalEvent, ServerEvent},
resources::DeltaTime, resources::DeltaTime,
@ -73,6 +73,7 @@ pub struct ReadData<'a> {
uids: ReadStorage<'a, Uid>, uids: ReadStorage<'a, Uid>,
mountings: ReadStorage<'a, Mounting>, mountings: ReadStorage<'a, Mounting>,
stats: ReadStorage<'a, Stats>, stats: ReadStorage<'a, Stats>,
skill_sets: ReadStorage<'a, SkillSet>,
msm: Read<'a, MaterialStatManifest>, msm: Read<'a, MaterialStatManifest>,
combos: ReadStorage<'a, Combo>, combos: ReadStorage<'a, Combo>,
alignments: ReadStorage<'a, comp::Alignment>, alignments: ReadStorage<'a, comp::Alignment>,
@ -134,6 +135,7 @@ impl<'a> System<'a> for Sys {
body, body,
physics, physics,
stat, stat,
skill_set,
combo, combo,
) in ( ) in (
&read_data.entities, &read_data.entities,
@ -149,6 +151,7 @@ impl<'a> System<'a> for Sys {
&read_data.bodies, &read_data.bodies,
&read_data.physics_states, &read_data.physics_states,
&read_data.stats, &read_data.stats,
&read_data.skill_sets,
&read_data.combos, &read_data.combos,
) )
.join() .join()
@ -259,6 +262,7 @@ impl<'a> System<'a> for Sys {
melee_attack: read_data.melee_attacks.get(entity), melee_attack: read_data.melee_attacks.get(entity),
beam: read_data.beams.get(entity), beam: read_data.beams.get(entity),
stat: &stat, stat: &stat,
skill_set: &skill_set,
combo: &combo, combo: &combo,
alignment: read_data.alignments.get(entity), alignment: read_data.alignments.get(entity),
}; };

View File

@ -183,6 +183,7 @@ impl State {
ecs.register::<comp::Body>(); ecs.register::<comp::Body>();
ecs.register::<comp::Player>(); ecs.register::<comp::Player>();
ecs.register::<comp::Stats>(); ecs.register::<comp::Stats>();
ecs.register::<comp::SkillSet>();
ecs.register::<comp::Buffs>(); ecs.register::<comp::Buffs>();
ecs.register::<comp::Auras>(); ecs.register::<comp::Auras>();
ecs.register::<comp::Energy>(); ecs.register::<comp::Energy>();

View File

@ -3,7 +3,7 @@ use common::{
self, self,
skills::{GeneralSkill, Skill}, skills::{GeneralSkill, Skill},
Body, CharacterState, Combo, Energy, EnergyChange, EnergySource, Health, Poise, Body, CharacterState, Combo, Energy, EnergyChange, EnergySource, Health, Poise,
PoiseChange, PoiseSource, Pos, Stats, PoiseChange, PoiseSource, Pos, SkillSet, Stats,
}, },
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
outcome::Outcome, outcome::Outcome,
@ -40,6 +40,7 @@ impl<'a> System<'a> for Sys {
type SystemData = ( type SystemData = (
ReadData<'a>, ReadData<'a>,
WriteStorage<'a, Stats>, WriteStorage<'a, Stats>,
WriteStorage<'a, SkillSet>,
WriteStorage<'a, Health>, WriteStorage<'a, Health>,
WriteStorage<'a, Poise>, WriteStorage<'a, Poise>,
WriteStorage<'a, Energy>, WriteStorage<'a, Energy>,
@ -55,7 +56,8 @@ impl<'a> System<'a> for Sys {
_job: &mut Job<Self>, _job: &mut Job<Self>,
( (
read_data, read_data,
mut stats, stats,
mut skill_sets,
mut healths, mut healths,
mut poises, mut poises,
mut energies, mut energies,
@ -79,10 +81,11 @@ impl<'a> System<'a> for Sys {
poises.set_event_emission(true); poises.set_event_emission(true);
// Update stats // Update stats
for (entity, uid, mut stats, mut health, pos) in ( for (entity, uid, stats, mut skill_set, mut health, pos) in (
&read_data.entities, &read_data.entities,
&read_data.uids, &read_data.uids,
&mut stats.restrict_mut(), &stats,
&mut skill_sets.restrict_mut(),
&mut healths.restrict_mut(), &mut healths.restrict_mut(),
&read_data.positions, &read_data.positions,
) )
@ -102,8 +105,7 @@ impl<'a> System<'a> for Sys {
health.is_dead = true; health.is_dead = true;
} }
let stat = stats;
let stat = stats.get_unchecked();
let update_max_hp = { let update_max_hp = {
let health = health.get_unchecked(); let health = health.get_unchecked();
@ -116,24 +118,24 @@ impl<'a> System<'a> for Sys {
health.scale_maximum(stat.max_health_modifier); health.scale_maximum(stat.max_health_modifier);
} }
let skills_to_level = stat let skillset = skill_set.get_unchecked();
.skill_set let skills_to_level = skillset
.skill_groups .skill_groups
.iter() .iter()
.filter_map(|s_g| { .filter_map(|s_g| {
(s_g.exp >= stat.skill_set.skill_point_cost(s_g.skill_group_kind)) (s_g.exp >= skillset.skill_point_cost(s_g.skill_group_kind))
.then(|| s_g.skill_group_kind) .then(|| s_g.skill_group_kind)
}) })
.collect::<HashSet<_>>(); .collect::<HashSet<_>>();
if !skills_to_level.is_empty() { if !skills_to_level.is_empty() {
let mut stat = stats.get_mut_unchecked(); let mut skill_set = skill_set.get_mut_unchecked();
for skill_group in skills_to_level { for skill_group in skills_to_level {
stat.skill_set.earn_skill_point(skill_group); skill_set.earn_skill_point(skill_group);
outcomes.push(Outcome::SkillPointGain { outcomes.push(Outcome::SkillPointGain {
uid: *uid, uid: *uid,
skill_tree: skill_group, skill_tree: skill_group,
total_points: stat.skill_set.earned_sp(skill_group), total_points: skill_set.earned_sp(skill_group),
pos: pos.0, pos: pos.0,
}); });
} }
@ -141,37 +143,35 @@ impl<'a> System<'a> for Sys {
} }
// Apply effects from leveling skills // Apply effects from leveling skills
for (mut stats, mut health, mut energy, body) in ( for (mut skill_set, mut health, mut energy, body) in (
&mut stats.restrict_mut(), &mut skill_sets.restrict_mut(),
&mut healths.restrict_mut(), &mut healths.restrict_mut(),
&mut energies.restrict_mut(), &mut energies.restrict_mut(),
&read_data.bodies, &read_data.bodies,
) )
.join() .join()
{ {
let stat = stats.get_unchecked(); let skillset = skill_set.get_unchecked();
if stat.skill_set.modify_health { if skillset.modify_health {
let mut health = health.get_mut_unchecked(); let mut health = health.get_mut_unchecked();
let health_level = stat let health_level = skillset
.skill_set
.skill_level(Skill::General(GeneralSkill::HealthIncrease)) .skill_level(Skill::General(GeneralSkill::HealthIncrease))
.unwrap_or(None) .unwrap_or(None)
.unwrap_or(0); .unwrap_or(0);
health.update_max_hp(Some(*body), health_level); health.update_max_hp(Some(*body), health_level);
let mut stat = stats.get_mut_unchecked(); let mut skillset = skill_set.get_mut_unchecked();
stat.skill_set.modify_health = false; skillset.modify_health = false;
} }
let stat = stats.get_unchecked(); let skillset = skill_set.get_unchecked();
if stat.skill_set.modify_energy { if skillset.modify_energy {
let mut energy = energy.get_mut_unchecked(); let mut energy = energy.get_mut_unchecked();
let energy_level = stat let energy_level = skillset
.skill_set
.skill_level(Skill::General(GeneralSkill::EnergyIncrease)) .skill_level(Skill::General(GeneralSkill::EnergyIncrease))
.unwrap_or(None) .unwrap_or(None)
.unwrap_or(0); .unwrap_or(0);
energy.update_max_energy(Some(*body), energy_level); energy.update_max_energy(Some(*body), energy_level);
let mut stat = stats.get_mut_unchecked(); let mut skill_set = skill_set.get_mut_unchecked();
stat.skill_set.modify_energy = false; skill_set.modify_energy = false;
} }
} }

View File

@ -1,5 +1,7 @@
use crate::persistence::character_loader::CharacterLoader; use crate::persistence::character_loader::CharacterLoader;
use common::comp::{inventory::loadout_builder::LoadoutBuilder, Body, Inventory, Item, Stats}; use common::comp::{
inventory::loadout_builder::LoadoutBuilder, Body, Inventory, Item, SkillSet, Stats,
};
use specs::{Entity, ReadExpect}; use specs::{Entity, ReadExpect};
const VALID_STARTER_ITEMS: [&str; 6] = [ const VALID_STARTER_ITEMS: [&str; 6] = [
@ -32,6 +34,7 @@ pub fn create_character(
} }
let stats = Stats::new(character_alias.to_string()); let stats = Stats::new(character_alias.to_string());
let skill_set = SkillSet::default();
let loadout = LoadoutBuilder::new() let loadout = LoadoutBuilder::new()
.defaults() .defaults()
@ -56,6 +59,6 @@ pub fn create_character(
entity, entity,
player_uuid, player_uuid,
character_alias, character_alias,
(body, stats, inventory, waypoint), (body, stats, skill_set, inventory, waypoint),
); );
} }

View File

@ -849,9 +849,7 @@ fn handle_spawn(
); );
let body = body(); let body = body();
let loadout = LoadoutBuilder::build_loadout(body, None, None, None).build(); let loadout = LoadoutBuilder::build_loadout(body, None, None, None).build();
let inventory = Inventory::new_with_loadout(loadout); let inventory = Inventory::new_with_loadout(loadout);
let mut entity_base = server let mut entity_base = server
@ -859,6 +857,7 @@ fn handle_spawn(
.create_npc( .create_npc(
pos, pos,
comp::Stats::new(get_npc_name(id, npc::BodyType::from_body(body))), comp::Stats::new(get_npc_name(id, npc::BodyType::from_body(body))),
comp::SkillSet::default(),
comp::Health::new(body, 1), comp::Health::new(body, 1),
comp::Poise::new(body), comp::Poise::new(body),
inventory, inventory,
@ -951,13 +950,21 @@ fn handle_spawn_training_dummy(
let body = comp::Body::Object(comp::object::Body::TrainingDummy); let body = comp::Body::Object(comp::object::Body::TrainingDummy);
let stats = comp::Stats::new("Training Dummy".to_string()); let stats = comp::Stats::new("Training Dummy".to_string());
let skill_set = comp::SkillSet::default();
let health = comp::Health::new(body, 0); let health = comp::Health::new(body, 0);
let poise = comp::Poise::new(body); let poise = comp::Poise::new(body);
server server
.state .state
.create_npc(pos, stats, health, poise, Inventory::new_empty(), body) .create_npc(
pos,
stats,
skill_set,
health,
poise,
Inventory::new_empty(),
body,
)
.with(comp::Vel(vel)) .with(comp::Vel(vel))
.with(comp::MountState::Unmounted) .with(comp::MountState::Unmounted)
.build(); .build();
@ -2174,13 +2181,13 @@ fn handle_skill_point(
.map(|alias| find_alias(server.state.ecs(), &alias).map(|(target, _)| target)) .map(|alias| find_alias(server.state.ecs(), &alias).map(|(target, _)| target))
.unwrap_or(Ok(target))?; .unwrap_or(Ok(target))?;
if let Some(mut stats) = server if let Some(mut skill_set) = server
.state .state
.ecs_mut() .ecs_mut()
.write_storage::<comp::Stats>() .write_storage::<comp::SkillSet>()
.get_mut(player) .get_mut(player)
{ {
stats.skill_set.add_skill_points(skill_tree, sp); skill_set.add_skill_points(skill_tree, sp);
Ok(()) Ok(())
} else { } else {
Err("Player has no stats!".into()) Err("Player has no stats!".into())

View File

@ -8,7 +8,8 @@ use common::{
buff::{BuffCategory, BuffData, BuffKind, BuffSource}, buff::{BuffCategory, BuffData, BuffKind, BuffSource},
inventory::loadout::Loadout, inventory::loadout::Loadout,
shockwave, Agent, Alignment, Body, Gravity, Health, HomeChunk, Inventory, Item, ItemDrop, shockwave, Agent, Alignment, Body, Gravity, Health, HomeChunk, Inventory, Item, ItemDrop,
LightEmitter, Object, Ori, Poise, Pos, Projectile, Scale, Stats, Vel, WaypointArea, LightEmitter, Object, Ori, Poise, Pos, Projectile, Scale, SkillSet, Stats, Vel,
WaypointArea,
}, },
outcome::Outcome, outcome::Outcome,
rtsim::RtSimEntity, rtsim::RtSimEntity,
@ -34,6 +35,7 @@ pub fn handle_loaded_character_data(
loaded_components: ( loaded_components: (
comp::Body, comp::Body,
comp::Stats, comp::Stats,
comp::SkillSet,
comp::Inventory, comp::Inventory,
Option<comp::Waypoint>, Option<comp::Waypoint>,
), ),
@ -49,6 +51,7 @@ pub fn handle_create_npc(
server: &mut Server, server: &mut Server,
pos: Pos, pos: Pos,
stats: Stats, stats: Stats,
skill_set: SkillSet,
health: Health, health: Health,
poise: Poise, poise: Poise,
loadout: Loadout, loadout: Loadout,
@ -64,7 +67,7 @@ pub fn handle_create_npc(
let entity = server let entity = server
.state .state
.create_npc(pos, stats, health, poise, inventory, body) .create_npc(pos, stats, skill_set, health, poise, inventory, body)
.with(scale) .with(scale)
.with(alignment); .with(alignment);

View File

@ -15,7 +15,7 @@ use common::{
chat::{KillSource, KillType}, chat::{KillSource, KillType},
inventory::item::MaterialStatManifest, inventory::item::MaterialStatManifest,
object, Alignment, Body, CharacterState, Energy, EnergyChange, Group, Health, HealthChange, object, Alignment, Body, CharacterState, Energy, EnergyChange, Group, Health, HealthChange,
HealthSource, Inventory, Player, Poise, PoiseChange, PoiseSource, Pos, Stats, HealthSource, Inventory, Player, Poise, PoiseChange, PoiseSource, Pos, SkillSet, Stats,
}, },
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
lottery::{LootSpec, Lottery}, lottery::{LootSpec, Lottery},
@ -178,7 +178,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
// Give EXP to the killer if entity had stats // Give EXP to the killer if entity had stats
(|| { (|| {
let mut stats = state.ecs().write_storage::<Stats>(); let mut skill_set = state.ecs().write_storage::<SkillSet>();
let healths = state.ecs().read_storage::<Health>(); let healths = state.ecs().read_storage::<Health>();
let inventories = state.ecs().read_storage::<Inventory>(); let inventories = state.ecs().read_storage::<Inventory>();
let players = state.ecs().read_storage::<Player>(); let players = state.ecs().read_storage::<Player>();
@ -193,18 +193,23 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
} else { } else {
return; return;
}; };
let (entity_stats, entity_health, entity_inventory, entity_body) = if let ( let (entity_skill_set, entity_health, entity_inventory, entity_body) = if let (
Some(entity_stats), Some(entity_skill_set),
Some(entity_health), Some(entity_health),
Some(entity_inventory), Some(entity_inventory),
Some(entity_body), Some(entity_body),
) = ( ) = (
stats.get(entity), skill_set.get(entity),
healths.get(entity), healths.get(entity),
inventories.get(entity), inventories.get(entity),
bodies.get(entity), bodies.get(entity),
) { ) {
(entity_stats, entity_health, entity_inventory, entity_body) (
entity_skill_set,
entity_health,
entity_inventory,
entity_body,
)
} else { } else {
return; return;
}; };
@ -228,7 +233,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
let mut exp_reward = combat::combat_rating( let mut exp_reward = combat::combat_rating(
entity_inventory, entity_inventory,
entity_health, entity_health,
entity_stats, entity_skill_set,
*entity_body, *entity_body,
&msm, &msm,
) * 2.5; ) * 2.5;
@ -268,24 +273,24 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc
// Divides exp reward by square root of number of people in group // Divides exp reward by square root of number of people in group
exp_reward /= (non_pet_group_members_in_range as f32).sqrt(); exp_reward /= (non_pet_group_members_in_range as f32).sqrt();
members_in_range.into_iter().for_each(|(e, uid)| { members_in_range.into_iter().for_each(|(e, uid)| {
if let (Some(inventory), Some(mut stats)) = (inventories.get(e), stats.get_mut(e)) { if let (Some(inventory), Some(mut skill_set)) =
handle_exp_gain(exp_reward, inventory, &mut stats, uid, &mut outcomes); (inventories.get(e), skill_set.get_mut(e))
{
handle_exp_gain(exp_reward, inventory, &mut skill_set, uid, &mut outcomes);
} }
}); });
} }
if let (Some(mut attacker_stats), Some(attacker_uid), Some(attacker_inventory)) = ( if let (Some(mut attacker_skill_set), Some(attacker_uid), Some(attacker_inventory)) = (
stats.get_mut(attacker), skill_set.get_mut(attacker),
uids.get(attacker), uids.get(attacker),
inventories.get(attacker), inventories.get(attacker),
) { ) {
// TODO: Discuss whether we should give EXP by Player // TODO: Discuss whether we should give EXP by Player Killing or not.
// Killing or not.
// attacker_stats.exp.change_by(exp_reward.ceil() as i64);
handle_exp_gain( handle_exp_gain(
exp_reward, exp_reward,
attacker_inventory, attacker_inventory,
&mut attacker_stats, &mut attacker_skill_set,
attacker_uid, attacker_uid,
&mut outcomes, &mut outcomes,
); );
@ -856,7 +861,7 @@ pub fn handle_energy_change(server: &Server, entity: EcsEntity, change: EnergyCh
fn handle_exp_gain( fn handle_exp_gain(
exp_reward: f32, exp_reward: f32,
inventory: &Inventory, inventory: &Inventory,
stats: &mut Stats, skill_set: &mut SkillSet,
uid: &Uid, uid: &Uid,
outcomes: &mut Vec<Outcome>, outcomes: &mut Vec<Outcome>,
) { ) {
@ -864,26 +869,18 @@ fn handle_exp_gain(
let mut xp_pools = HashSet::<SkillGroupKind>::new(); let mut xp_pools = HashSet::<SkillGroupKind>::new();
xp_pools.insert(SkillGroupKind::General); xp_pools.insert(SkillGroupKind::General);
if let Some(w) = main_tool_kind { if let Some(w) = main_tool_kind {
if stats if skill_set.contains_skill_group(SkillGroupKind::Weapon(w)) {
.skill_set
.contains_skill_group(SkillGroupKind::Weapon(w))
{
xp_pools.insert(SkillGroupKind::Weapon(w)); xp_pools.insert(SkillGroupKind::Weapon(w));
} }
} }
if let Some(w) = second_tool_kind { if let Some(w) = second_tool_kind {
if stats if skill_set.contains_skill_group(SkillGroupKind::Weapon(w)) {
.skill_set
.contains_skill_group(SkillGroupKind::Weapon(w))
{
xp_pools.insert(SkillGroupKind::Weapon(w)); xp_pools.insert(SkillGroupKind::Weapon(w));
} }
} }
let num_pools = xp_pools.len() as f32; let num_pools = xp_pools.len() as f32;
for pool in xp_pools { for pool in xp_pools {
stats skill_set.change_experience(pool, (exp_reward / num_pools).ceil() as i32);
.skill_set
.change_experience(pool, (exp_reward / num_pools).ceil() as i32);
} }
outcomes.push(Outcome::ExpChange { outcomes.push(Outcome::ExpChange {
uid: *uid, uid: *uid,

View File

@ -140,6 +140,7 @@ impl Server {
ServerEvent::CreateNpc { ServerEvent::CreateNpc {
pos, pos,
stats, stats,
skill_set,
health, health,
poise, poise,
loadout, loadout,
@ -154,6 +155,7 @@ impl Server {
self, self,
pos, pos,
stats, stats,
skill_set,
health, health,
poise, poise,
loadout, loadout,

View File

@ -175,9 +175,9 @@ pub fn handle_client_disconnect(
// the race condition of their login fetching their old data // the race condition of their login fetching their old data
// and overwriting the data saved here. // and overwriting the data saved here.
fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity { fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity {
if let (Some(presence), Some(stats), Some(inventory), mut character_updater) = ( if let (Some(presence), Some(skill_set), Some(inventory), mut character_updater) = (
state.read_storage::<Presence>().get(entity), state.read_storage::<Presence>().get(entity),
state.read_storage::<comp::Stats>().get(entity), state.read_storage::<comp::SkillSet>().get(entity),
state.read_storage::<comp::Inventory>().get(entity), state.read_storage::<comp::Inventory>().get(entity),
state.ecs().fetch_mut::<CharacterUpdater>(), state.ecs().fetch_mut::<CharacterUpdater>(),
) { ) {
@ -191,7 +191,7 @@ fn persist_entity(state: &mut State, entity: EcsEntity) -> EcsEntity {
character_updater.add_pending_logout_update( character_updater.add_pending_logout_update(
char_id, char_id,
(stats.clone(), inventory.clone(), waypoint), (skill_set.clone(), inventory.clone(), waypoint),
); );
}, },
PresenceKind::Spectator => { /* Do nothing, spectators do not need persisting */ }, PresenceKind::Spectator => { /* Do nothing, spectators do not need persisting */ },

View File

@ -15,9 +15,9 @@ use crate::{
convert_body_from_database, convert_body_to_database_json, convert_body_from_database, convert_body_to_database_json,
convert_character_from_database, convert_inventory_from_database_items, convert_character_from_database, convert_inventory_from_database_items,
convert_items_to_database_items, convert_loadout_from_database_items, convert_items_to_database_items, convert_loadout_from_database_items,
convert_skill_groups_to_database, convert_skills_to_database, convert_skill_groups_to_database, convert_skill_set_from_database,
convert_stats_from_database, convert_waypoint_from_database_json, convert_skills_to_database, convert_stats_from_database,
convert_waypoint_to_database_json, convert_waypoint_from_database_json, convert_waypoint_to_database_json,
}, },
character_loader::{CharacterCreationResult, CharacterDataResult, CharacterListResult}, character_loader::{CharacterCreationResult, CharacterDataResult, CharacterListResult},
error::PersistenceError::DatabaseError, error::PersistenceError::DatabaseError,
@ -198,7 +198,8 @@ pub fn load_character_data(
Ok(( Ok((
convert_body_from_database(&body_data)?, convert_body_from_database(&body_data)?,
convert_stats_from_database(character_data.alias, &skill_data, &skill_group_data), convert_stats_from_database(character_data.alias),
convert_skill_set_from_database(&skill_data, &skill_group_data),
convert_inventory_from_database_items( convert_inventory_from_database_items(
character_containers.inventory_container_id, character_containers.inventory_container_id,
&inventory_items, &inventory_items,
@ -301,7 +302,7 @@ pub fn create_character(
) -> CharacterCreationResult { ) -> CharacterCreationResult {
check_character_limit(uuid, connection)?; check_character_limit(uuid, connection)?;
let (body, stats, inventory, waypoint) = persisted_components; let (body, _stats, skill_set, inventory, waypoint) = persisted_components;
// Fetch new entity IDs for character, inventory and loadout // Fetch new entity IDs for character, inventory and loadout
let mut new_entity_ids = get_new_entity_ids(connection, |next_id| next_id + 3)?; let mut new_entity_ids = get_new_entity_ids(connection, |next_id| next_id + 3)?;
@ -387,8 +388,6 @@ pub fn create_character(
])?; ])?;
drop(stmt); drop(stmt);
let skill_set = stats.skill_set;
let db_skill_groups = convert_skill_groups_to_database(character_id, skill_set.skill_groups); let db_skill_groups = convert_skill_groups_to_database(character_id, skill_set.skill_groups);
#[rustfmt::skip] #[rustfmt::skip]
@ -682,7 +681,7 @@ fn get_pseudo_container_id(
pub fn update( pub fn update(
char_id: CharacterId, char_id: CharacterId,
char_stats: comp::Stats, char_skill_set: comp::SkillSet,
inventory: comp::Inventory, inventory: comp::Inventory,
char_waypoint: Option<comp::Waypoint>, char_waypoint: Option<comp::Waypoint>,
connection: &mut Transaction, connection: &mut Transaction,
@ -781,8 +780,6 @@ pub fn update(
} }
} }
let char_skill_set = char_stats.skill_set;
let db_skill_groups = convert_skill_groups_to_database(char_id, char_skill_set.skill_groups); let db_skill_groups = convert_skill_groups_to_database(char_id, char_skill_set.skill_groups);
#[rustfmt::skip] #[rustfmt::skip]

View File

@ -428,26 +428,22 @@ pub fn convert_character_from_database(character: &Character) -> common::charact
} }
} }
pub fn convert_stats_from_database( pub fn convert_stats_from_database(alias: String) -> common::comp::Stats {
alias: String,
skills: &[Skill],
skill_groups: &[SkillGroup],
) -> common::comp::Stats {
let mut new_stats = common::comp::Stats::empty(); let mut new_stats = common::comp::Stats::empty();
new_stats.name = alias; new_stats.name = alias;
/*new_stats.update_max_hp(new_stats.body_type); new_stats
new_stats.health.set_to( }
new_stats.health.maximum(),
common::comp::HealthSource::Revive, pub fn convert_skill_set_from_database(
);*/ skills: &[Skill],
new_stats.skill_set = skills::SkillSet { skill_groups: &[SkillGroup],
) -> common::comp::SkillSet {
skills::SkillSet {
skill_groups: convert_skill_groups_from_database(skill_groups), skill_groups: convert_skill_groups_from_database(skill_groups),
skills: convert_skills_from_database(skills), skills: convert_skills_from_database(skills),
modify_health: true, modify_health: true,
modify_energy: true, modify_energy: true,
}; }
new_stats
} }
fn get_item_from_asset(item_definition_id: &str) -> Result<common::comp::Item, PersistenceError> { fn get_item_from_asset(item_definition_id: &str) -> Result<common::comp::Item, PersistenceError> {

View File

@ -14,7 +14,7 @@ use std::{
}; };
use tracing::{debug, error, info, trace, warn}; use tracing::{debug, error, info, trace, warn};
pub type CharacterUpdateData = (comp::Stats, comp::Inventory, Option<comp::Waypoint>); pub type CharacterUpdateData = (comp::SkillSet, comp::Inventory, Option<comp::Waypoint>);
pub enum CharacterUpdaterEvent { pub enum CharacterUpdaterEvent {
BatchUpdate(Vec<(CharacterId, CharacterUpdateData)>), BatchUpdate(Vec<(CharacterId, CharacterUpdateData)>),
@ -131,17 +131,17 @@ impl CharacterUpdater {
updates: impl Iterator< updates: impl Iterator<
Item = ( Item = (
CharacterId, CharacterId,
&'a comp::Stats, &'a comp::SkillSet,
&'a comp::Inventory, &'a comp::Inventory,
Option<&'a comp::Waypoint>, Option<&'a comp::Waypoint>,
), ),
>, >,
) { ) {
let updates = updates let updates = updates
.map(|(character_id, stats, inventory, waypoint)| { .map(|(character_id, skill_set, inventory, waypoint)| {
( (
character_id, character_id,
(stats.clone(), inventory.clone(), waypoint.cloned()), (skill_set.clone(), inventory.clone(), waypoint.cloned()),
) )
}) })
.chain(self.pending_logout_updates.drain()) .chain(self.pending_logout_updates.drain())
@ -161,11 +161,16 @@ impl CharacterUpdater {
pub fn update( pub fn update(
&mut self, &mut self,
character_id: CharacterId, character_id: CharacterId,
stats: &comp::Stats, skill_set: &comp::SkillSet,
inventory: &comp::Inventory, inventory: &comp::Inventory,
waypoint: Option<&comp::Waypoint>, waypoint: Option<&comp::Waypoint>,
) { ) {
self.batch_update(std::iter::once((character_id, stats, inventory, waypoint))); self.batch_update(std::iter::once((
character_id,
skill_set,
inventory,
waypoint,
)));
} }
/// Indicates to the batch update thread that a requested disconnection of /// Indicates to the batch update thread that a requested disconnection of

View File

@ -22,6 +22,7 @@ use tracing::info;
pub type PersistedComponents = ( pub type PersistedComponents = (
comp::Body, comp::Body,
comp::Stats, comp::Stats,
comp::SkillSet,
comp::Inventory, comp::Inventory,
Option<comp::Waypoint>, Option<comp::Waypoint>,
); );

View File

@ -126,6 +126,7 @@ impl<'a> System<'a> for Sys {
_ => ServerEvent::CreateNpc { _ => ServerEvent::CreateNpc {
pos: comp::Pos(spawn_pos), pos: comp::Pos(spawn_pos),
stats: comp::Stats::new(entity.get_name()), stats: comp::Stats::new(entity.get_name()),
skill_set: comp::SkillSet::default(),
health: comp::Health::new(body, 10), health: comp::Health::new(body, 10),
loadout: match body { loadout: match body {
comp::Body::Humanoid(_) => entity.get_loadout(), comp::Body::Humanoid(_) => entity.get_loadout(),

View File

@ -31,10 +31,12 @@ pub trait StateExt {
/// Updates a component associated with the entity based on the `Effect` /// Updates a component associated with the entity based on the `Effect`
fn apply_effect(&self, entity: EcsEntity, effect: Effect, source: Option<Uid>); fn apply_effect(&self, entity: EcsEntity, effect: Effect, source: Option<Uid>);
/// Build a non-player character /// Build a non-player character
#[allow(clippy::too_many_arguments)]
fn create_npc( fn create_npc(
&mut self, &mut self,
pos: comp::Pos, pos: comp::Pos,
stats: comp::Stats, stats: comp::Stats,
skill_set: comp::SkillSet,
health: comp::Health, health: comp::Health,
poise: comp::Poise, poise: comp::Poise,
inventory: comp::Inventory, inventory: comp::Inventory,
@ -161,6 +163,7 @@ impl StateExt for State {
&mut self, &mut self,
pos: comp::Pos, pos: comp::Pos,
stats: comp::Stats, stats: comp::Stats,
skill_set: comp::SkillSet,
health: comp::Health, health: comp::Health,
poise: comp::Poise, poise: comp::Poise,
inventory: comp::Inventory, inventory: comp::Inventory,
@ -192,13 +195,13 @@ impl StateExt for State {
.with(body) .with(body)
.with(comp::Energy::new( .with(comp::Energy::new(
body, body,
stats skill_set
.skill_set
.skill_level(Skill::General(GeneralSkill::EnergyIncrease)) .skill_level(Skill::General(GeneralSkill::EnergyIncrease))
.unwrap_or(None) .unwrap_or(None)
.unwrap_or(0), .unwrap_or(0),
)) ))
.with(stats) .with(stats)
.with(skill_set)
.with(health) .with(health)
.with(poise) .with(poise)
.with(comp::Alignment::Npc) .with(comp::Alignment::Npc)
@ -252,6 +255,7 @@ impl StateExt for State {
// recognize a possesed airship; that system should be refactored to use `.maybe()` // recognize a possesed airship; that system should be refactored to use `.maybe()`
.with(comp::Energy::new(ship.into(), 0)) .with(comp::Energy::new(ship.into(), 0))
.with(comp::Stats::new("Airship".to_string())) .with(comp::Stats::new("Airship".to_string()))
.with(comp::SkillSet::default())
.with(comp::Combo::default()); .with(comp::Combo::default());
if mountable { if mountable {
@ -399,7 +403,7 @@ impl StateExt for State {
} }
fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents) { fn update_character_data(&mut self, entity: EcsEntity, components: PersistedComponents) {
let (body, stats, inventory, waypoint) = components; let (body, stats, skill_set, inventory, waypoint) = components;
if let Some(player_uid) = self.read_component_copied::<Uid>(entity) { if let Some(player_uid) = self.read_component_copied::<Uid>(entity) {
// Notify clients of a player list update // Notify clients of a player list update
@ -420,13 +424,11 @@ impl StateExt for State {
}); });
self.write_component_ignore_entity_dead(entity, body); self.write_component_ignore_entity_dead(entity, body);
let (health_level, energy_level) = ( let (health_level, energy_level) = (
stats skill_set
.skill_set
.skill_level(Skill::General(GeneralSkill::HealthIncrease)) .skill_level(Skill::General(GeneralSkill::HealthIncrease))
.unwrap_or(None) .unwrap_or(None)
.unwrap_or(0), .unwrap_or(0),
stats skill_set
.skill_set
.skill_level(Skill::General(GeneralSkill::EnergyIncrease)) .skill_level(Skill::General(GeneralSkill::EnergyIncrease))
.unwrap_or(None) .unwrap_or(None)
.unwrap_or(0), .unwrap_or(0),
@ -435,6 +437,7 @@ impl StateExt for State {
self.write_component_ignore_entity_dead(entity, comp::Energy::new(body, energy_level)); self.write_component_ignore_entity_dead(entity, comp::Energy::new(body, energy_level));
self.write_component_ignore_entity_dead(entity, comp::Poise::new(body)); self.write_component_ignore_entity_dead(entity, comp::Poise::new(body));
self.write_component_ignore_entity_dead(entity, stats); self.write_component_ignore_entity_dead(entity, stats);
self.write_component_ignore_entity_dead(entity, skill_set);
self.write_component_ignore_entity_dead(entity, inventory); self.write_component_ignore_entity_dead(entity, inventory);
self.write_component_ignore_entity_dead( self.write_component_ignore_entity_dead(
entity, entity,

View File

@ -16,7 +16,7 @@ use common::{
skills::{AxeSkill, BowSkill, HammerSkill, Skill, StaffSkill, SwordSkill}, skills::{AxeSkill, BowSkill, HammerSkill, Skill, StaffSkill, SwordSkill},
Agent, Alignment, BehaviorCapability, BehaviorState, Body, CharacterState, ControlAction, Agent, Alignment, BehaviorCapability, BehaviorState, Body, CharacterState, ControlAction,
ControlEvent, Controller, Energy, Health, HealthChange, InputKind, Inventory, ControlEvent, Controller, Energy, Health, HealthChange, InputKind, Inventory,
InventoryAction, LightEmitter, MountState, Ori, PhysicsState, Pos, Scale, Stats, InventoryAction, LightEmitter, MountState, Ori, PhysicsState, Pos, Scale, SkillSet, Stats,
UnresolvedChatMsg, Vel, UnresolvedChatMsg, Vel,
}, },
effect::{BuffEffect, Effect}, effect::{BuffEffect, Effect},
@ -55,7 +55,7 @@ struct AgentData<'a> {
energy: &'a Energy, energy: &'a Energy,
body: Option<&'a Body>, body: Option<&'a Body>,
inventory: &'a Inventory, inventory: &'a Inventory,
stats: &'a Stats, skill_set: &'a SkillSet,
physics_state: &'a PhysicsState, physics_state: &'a PhysicsState,
alignment: Option<&'a Alignment>, alignment: Option<&'a Alignment>,
traversal_config: TraversalConfig, traversal_config: TraversalConfig,
@ -84,6 +84,7 @@ pub struct ReadData<'a> {
healths: ReadStorage<'a, Health>, healths: ReadStorage<'a, Health>,
inventories: ReadStorage<'a, Inventory>, inventories: ReadStorage<'a, Inventory>,
stats: ReadStorage<'a, Stats>, stats: ReadStorage<'a, Stats>,
skill_set: ReadStorage<'a, SkillSet>,
physics_states: ReadStorage<'a, PhysicsState>, physics_states: ReadStorage<'a, PhysicsState>,
char_states: ReadStorage<'a, CharacterState>, char_states: ReadStorage<'a, CharacterState>,
uids: ReadStorage<'a, Uid>, uids: ReadStorage<'a, Uid>,
@ -147,7 +148,7 @@ impl<'a> System<'a> for Sys {
), ),
read_data.bodies.maybe(), read_data.bodies.maybe(),
&read_data.inventories, &read_data.inventories,
&read_data.stats, &read_data.skill_set,
&read_data.physics_states, &read_data.physics_states,
&read_data.uids, &read_data.uids,
&mut agents, &mut agents,
@ -176,7 +177,7 @@ impl<'a> System<'a> for Sys {
(pos, vel, ori), (pos, vel, ori),
body, body,
inventory, inventory,
stats, skill_set,
physics_state, physics_state,
uid, uid,
agent, agent,
@ -269,7 +270,7 @@ impl<'a> System<'a> for Sys {
energy, energy,
body, body,
inventory, inventory,
stats, skill_set,
physics_state, physics_state,
alignment: alignment.as_ref(), alignment: alignment.as_ref(),
traversal_config, traversal_config,
@ -1621,10 +1622,7 @@ impl<'a> AgentData<'a> {
.actions .actions
.push(ControlAction::basic_input(InputKind::Secondary)); .push(ControlAction::basic_input(InputKind::Secondary));
agent.action_timer += dt.0; agent.action_timer += dt.0;
} else if self } else if self.skill_set.has_skill(Skill::Axe(AxeSkill::UnlockLeap))
.stats
.skill_set
.has_skill(Skill::Axe(AxeSkill::UnlockLeap))
&& self.energy.current() > 800 && self.energy.current() > 800
&& thread_rng().gen_bool(0.5) && thread_rng().gen_bool(0.5)
{ {
@ -1680,7 +1678,6 @@ impl<'a> AgentData<'a> {
.push(ControlAction::basic_input(InputKind::Secondary)); .push(ControlAction::basic_input(InputKind::Secondary));
agent.action_timer += dt.0; agent.action_timer += dt.0;
} else if self } else if self
.stats
.skill_set .skill_set
.has_skill(Skill::Hammer(HammerSkill::UnlockLeap)) .has_skill(Skill::Hammer(HammerSkill::UnlockLeap))
&& self.energy.current() > 700 && self.energy.current() > 700
@ -1711,7 +1708,6 @@ impl<'a> AgentData<'a> {
controller.inputs.move_dir = controller.inputs.move_dir =
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed; bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
if self if self
.stats
.skill_set .skill_set
.has_skill(Skill::Hammer(HammerSkill::UnlockLeap)) .has_skill(Skill::Hammer(HammerSkill::UnlockLeap))
&& agent.action_timer > 5.0 && agent.action_timer > 5.0
@ -1746,7 +1742,6 @@ impl<'a> AgentData<'a> {
if dist_sqrd < min_attack_dist.powi(2) { if dist_sqrd < min_attack_dist.powi(2) {
controller.inputs.move_dir = Vec2::zero(); controller.inputs.move_dir = Vec2::zero();
if self if self
.stats
.skill_set .skill_set
.has_skill(Skill::Sword(SwordSkill::UnlockSpin)) .has_skill(Skill::Sword(SwordSkill::UnlockSpin))
&& agent.action_timer < 2.0 && agent.action_timer < 2.0
@ -1841,7 +1836,6 @@ impl<'a> AgentData<'a> {
.push(ControlAction::basic_input(InputKind::Secondary)); .push(ControlAction::basic_input(InputKind::Secondary));
agent.action_timer += dt.0; agent.action_timer += dt.0;
} else if self } else if self
.stats
.skill_set .skill_set
.has_skill(Skill::Bow(BowSkill::UnlockRepeater)) .has_skill(Skill::Bow(BowSkill::UnlockRepeater))
&& self.energy.current() > 400 && self.energy.current() > 400
@ -1924,7 +1918,6 @@ impl<'a> AgentData<'a> {
agent.action_timer = 0.0; agent.action_timer = 0.0;
} }
if self if self
.stats
.skill_set .skill_set
.has_skill(Skill::Staff(StaffSkill::UnlockShockwave)) .has_skill(Skill::Staff(StaffSkill::UnlockShockwave))
&& self.energy.current() > 800 && self.energy.current() > 800

View File

@ -1,6 +1,6 @@
use crate::{client::Client, presence::Presence, Settings}; use crate::{client::Client, presence::Presence, Settings};
use common::{ use common::{
comp::{CanBuild, ControlEvent, Controller, ForceUpdate, Health, Ori, Pos, Stats, Vel}, comp::{CanBuild, ControlEvent, Controller, ForceUpdate, Health, Ori, Pos, SkillSet, Vel},
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
terrain::TerrainGrid, terrain::TerrainGrid,
vol::ReadVol, vol::ReadVol,
@ -21,7 +21,7 @@ impl Sys {
terrain: &ReadExpect<'_, TerrainGrid>, terrain: &ReadExpect<'_, TerrainGrid>,
can_build: &ReadStorage<'_, CanBuild>, can_build: &ReadStorage<'_, CanBuild>,
force_updates: &ReadStorage<'_, ForceUpdate>, force_updates: &ReadStorage<'_, ForceUpdate>,
stats: &mut WriteStorage<'_, Stats>, skill_sets: &mut WriteStorage<'_, SkillSet>,
healths: &ReadStorage<'_, Health>, healths: &ReadStorage<'_, Health>,
block_changes: &mut Write<'_, BlockChange>, block_changes: &mut Write<'_, BlockChange>,
positions: &mut WriteStorage<'_, Pos>, positions: &mut WriteStorage<'_, Pos>,
@ -139,19 +139,19 @@ impl Sys {
} }
}, },
ClientGeneral::UnlockSkill(skill) => { ClientGeneral::UnlockSkill(skill) => {
stats skill_sets
.get_mut(entity) .get_mut(entity)
.map(|mut s| s.skill_set.unlock_skill(skill)); .map(|mut skill_set| skill_set.unlock_skill(skill));
}, },
ClientGeneral::RefundSkill(skill) => { ClientGeneral::RefundSkill(skill) => {
stats skill_sets
.get_mut(entity) .get_mut(entity)
.map(|mut s| s.skill_set.refund_skill(skill)); .map(|mut skill_set| skill_set.refund_skill(skill));
}, },
ClientGeneral::UnlockSkillGroup(skill_group_kind) => { ClientGeneral::UnlockSkillGroup(skill_group_kind) => {
stats skill_sets
.get_mut(entity) .get_mut(entity)
.map(|mut s| s.skill_set.unlock_skill_group(skill_group_kind)); .map(|mut skill_set| skill_set.unlock_skill_group(skill_group_kind));
}, },
ClientGeneral::RequestSiteInfo(id) => { ClientGeneral::RequestSiteInfo(id) => {
server_emitter.emit(ServerEvent::RequestSiteInfo { entity, id }); server_emitter.emit(ServerEvent::RequestSiteInfo { entity, id });
@ -173,7 +173,7 @@ impl<'a> System<'a> for Sys {
ReadExpect<'a, TerrainGrid>, ReadExpect<'a, TerrainGrid>,
ReadStorage<'a, CanBuild>, ReadStorage<'a, CanBuild>,
ReadStorage<'a, ForceUpdate>, ReadStorage<'a, ForceUpdate>,
WriteStorage<'a, Stats>, WriteStorage<'a, SkillSet>,
ReadStorage<'a, Health>, ReadStorage<'a, Health>,
Write<'a, BlockChange>, Write<'a, BlockChange>,
WriteStorage<'a, Pos>, WriteStorage<'a, Pos>,
@ -198,7 +198,7 @@ impl<'a> System<'a> for Sys {
terrain, terrain,
can_build, can_build,
force_updates, force_updates,
mut stats, mut skill_sets,
healths, healths,
mut block_changes, mut block_changes,
mut positions, mut positions,
@ -225,7 +225,7 @@ impl<'a> System<'a> for Sys {
&terrain, &terrain,
&can_build, &can_build,
&force_updates, &force_updates,
&mut stats, &mut skill_sets,
&healths, &healths,
&mut block_changes, &mut block_changes,
&mut positions, &mut positions,

View File

@ -1,5 +1,5 @@
use crate::{persistence::character_updater, presence::Presence, sys::SysScheduler}; use crate::{persistence::character_updater, presence::Presence, sys::SysScheduler};
use common::comp::{Inventory, Stats, Waypoint}; use common::comp::{Inventory, SkillSet, Waypoint};
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
use common_net::msg::PresenceKind; use common_net::msg::PresenceKind;
use specs::{Join, ReadStorage, Write, WriteExpect}; use specs::{Join, ReadStorage, Write, WriteExpect};
@ -11,7 +11,7 @@ impl<'a> System<'a> for Sys {
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
type SystemData = ( type SystemData = (
ReadStorage<'a, Presence>, ReadStorage<'a, Presence>,
ReadStorage<'a, Stats>, ReadStorage<'a, SkillSet>,
ReadStorage<'a, Inventory>, ReadStorage<'a, Inventory>,
ReadStorage<'a, Waypoint>, ReadStorage<'a, Waypoint>,
WriteExpect<'a, character_updater::CharacterUpdater>, WriteExpect<'a, character_updater::CharacterUpdater>,
@ -26,7 +26,7 @@ impl<'a> System<'a> for Sys {
_job: &mut Job<Self>, _job: &mut Job<Self>,
( (
presences, presences,
player_stats, player_skill_set,
player_inventories, player_inventories,
player_waypoint, player_waypoint,
mut updater, mut updater,
@ -37,14 +37,16 @@ impl<'a> System<'a> for Sys {
updater.batch_update( updater.batch_update(
( (
&presences, &presences,
&player_stats, &player_skill_set,
&player_inventories, &player_inventories,
player_waypoint.maybe(), player_waypoint.maybe(),
) )
.join() .join()
.filter_map( .filter_map(
|(presence, stats, inventory, waypoint)| match presence.kind { |(presence, skill_set, inventory, waypoint)| match presence.kind {
PresenceKind::Character(id) => Some((id, stats, inventory, waypoint)), PresenceKind::Character(id) => {
Some((id, skill_set, inventory, waypoint))
},
PresenceKind::Spectator => None, PresenceKind::Spectator => None,
}, },
), ),

View File

@ -2,7 +2,8 @@ use common::{
comp::{ comp::{
item::MaterialStatManifest, Auras, BeamSegment, Body, Buffs, CanBuild, CharacterState, item::MaterialStatManifest, Auras, BeamSegment, Body, Buffs, CanBuild, CharacterState,
Collider, Combo, Energy, Gravity, Group, Health, Inventory, Item, LightEmitter, Mass, Collider, Combo, Energy, Gravity, Group, Health, Inventory, Item, LightEmitter, Mass,
MountState, Mounting, Ori, Player, Poise, Pos, Scale, Shockwave, Stats, Sticky, Vel, MountState, Mounting, Ori, Player, Poise, Pos, Scale, Shockwave, SkillSet, Stats, Sticky,
Vel,
}, },
uid::Uid, uid::Uid,
}; };
@ -42,6 +43,7 @@ pub struct TrackedComps<'a> {
pub body: ReadStorage<'a, Body>, pub body: ReadStorage<'a, Body>,
pub player: ReadStorage<'a, Player>, pub player: ReadStorage<'a, Player>,
pub stats: ReadStorage<'a, Stats>, pub stats: ReadStorage<'a, Stats>,
pub skill_set: ReadStorage<'a, SkillSet>,
pub buffs: ReadStorage<'a, Buffs>, pub buffs: ReadStorage<'a, Buffs>,
pub auras: ReadStorage<'a, Auras>, pub auras: ReadStorage<'a, Auras>,
pub energy: ReadStorage<'a, Energy>, pub energy: ReadStorage<'a, Energy>,
@ -84,6 +86,10 @@ impl<'a> TrackedComps<'a> {
.get(entity) .get(entity)
.cloned() .cloned()
.map(|c| comps.push(c.into())); .map(|c| comps.push(c.into()));
self.skill_set
.get(entity)
.cloned()
.map(|c| comps.push(c.into()));
self.buffs self.buffs
.get(entity) .get(entity)
.cloned() .cloned()
@ -179,6 +185,7 @@ pub struct ReadTrackers<'a> {
pub body: ReadExpect<'a, UpdateTracker<Body>>, pub body: ReadExpect<'a, UpdateTracker<Body>>,
pub player: ReadExpect<'a, UpdateTracker<Player>>, pub player: ReadExpect<'a, UpdateTracker<Player>>,
pub stats: ReadExpect<'a, UpdateTracker<Stats>>, pub stats: ReadExpect<'a, UpdateTracker<Stats>>,
pub skill_set: ReadExpect<'a, UpdateTracker<SkillSet>>,
pub buffs: ReadExpect<'a, UpdateTracker<Buffs>>, pub buffs: ReadExpect<'a, UpdateTracker<Buffs>>,
pub auras: ReadExpect<'a, UpdateTracker<Auras>>, pub auras: ReadExpect<'a, UpdateTracker<Auras>>,
pub energy: ReadExpect<'a, UpdateTracker<Energy>>, pub energy: ReadExpect<'a, UpdateTracker<Energy>>,
@ -214,6 +221,7 @@ impl<'a> ReadTrackers<'a> {
.with_component(&comps.uid, &*self.body, &comps.body, filter) .with_component(&comps.uid, &*self.body, &comps.body, filter)
.with_component(&comps.uid, &*self.player, &comps.player, filter) .with_component(&comps.uid, &*self.player, &comps.player, filter)
.with_component(&comps.uid, &*self.stats, &comps.stats, filter) .with_component(&comps.uid, &*self.stats, &comps.stats, filter)
.with_component(&comps.uid, &*self.skill_set, &comps.skill_set, filter)
.with_component(&comps.uid, &*self.buffs, &comps.buffs, filter) .with_component(&comps.uid, &*self.buffs, &comps.buffs, filter)
.with_component(&comps.uid, &*self.auras, &comps.auras, filter) .with_component(&comps.uid, &*self.auras, &comps.auras, filter)
.with_component(&comps.uid, &*self.energy, &comps.energy, filter) .with_component(&comps.uid, &*self.energy, &comps.energy, filter)
@ -256,6 +264,7 @@ pub struct WriteTrackers<'a> {
body: WriteExpect<'a, UpdateTracker<Body>>, body: WriteExpect<'a, UpdateTracker<Body>>,
player: WriteExpect<'a, UpdateTracker<Player>>, player: WriteExpect<'a, UpdateTracker<Player>>,
stats: WriteExpect<'a, UpdateTracker<Stats>>, stats: WriteExpect<'a, UpdateTracker<Stats>>,
skill_set: WriteExpect<'a, UpdateTracker<SkillSet>>,
buffs: WriteExpect<'a, UpdateTracker<Buffs>>, buffs: WriteExpect<'a, UpdateTracker<Buffs>>,
auras: WriteExpect<'a, UpdateTracker<Auras>>, auras: WriteExpect<'a, UpdateTracker<Auras>>,
energy: WriteExpect<'a, UpdateTracker<Energy>>, energy: WriteExpect<'a, UpdateTracker<Energy>>,
@ -285,6 +294,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
trackers.body.record_changes(&comps.body); trackers.body.record_changes(&comps.body);
trackers.player.record_changes(&comps.player); trackers.player.record_changes(&comps.player);
trackers.stats.record_changes(&comps.stats); trackers.stats.record_changes(&comps.stats);
trackers.skill_set.record_changes(&comps.skill_set);
trackers.buffs.record_changes(&comps.buffs); trackers.buffs.record_changes(&comps.buffs);
trackers.auras.record_changes(&comps.auras); trackers.auras.record_changes(&comps.auras);
trackers.energy.record_changes(&comps.energy); trackers.energy.record_changes(&comps.energy);
@ -329,6 +339,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) {
log_counts!(auras, "Auras"); log_counts!(auras, "Auras");
log_counts!(player, "Players"); log_counts!(player, "Players");
log_counts!(stats, "Stats"); log_counts!(stats, "Stats");
log_counts!(skill_set, "SkillSet");
log_counts!(energy, "Energies"); log_counts!(energy, "Energies");
log_counts!(combo, "Combos"); log_counts!(combo, "Combos");
log_vounts!(health, "Healths"); log_vounts!(health, "Healths");
@ -354,6 +365,7 @@ pub fn register_trackers(world: &mut World) {
world.register_tracker::<Body>(); world.register_tracker::<Body>();
world.register_tracker::<Player>(); world.register_tracker::<Player>();
world.register_tracker::<Stats>(); world.register_tracker::<Stats>();
world.register_tracker::<SkillSet>();
world.register_tracker::<Buffs>(); world.register_tracker::<Buffs>();
world.register_tracker::<Auras>(); world.register_tracker::<Auras>();
world.register_tracker::<Energy>(); world.register_tracker::<Energy>();

View File

@ -154,7 +154,7 @@ impl<'a> System<'a> for Sys {
let economy = entity.trading_information.as_ref(); let economy = entity.trading_information.as_ref();
let skillset_config = entity.skillset_config; let skillset_config = entity.skillset_config;
stats.skill_set = let skill_set =
SkillSetBuilder::build_skillset(&main_tool, skillset_config).build(); SkillSetBuilder::build_skillset(&main_tool, skillset_config).build();
let loadout = let loadout =
LoadoutBuilder::build_loadout(body, main_tool, loadout_config, economy).build(); LoadoutBuilder::build_loadout(body, main_tool, loadout_config, economy).build();
@ -189,6 +189,7 @@ impl<'a> System<'a> for Sys {
server_emitter.emit(ServerEvent::CreateNpc { server_emitter.emit(ServerEvent::CreateNpc {
pos: Pos(entity.pos), pos: Pos(entity.pos),
stats, stats,
skill_set,
health, health,
poise, poise,
loadout, loadout,

View File

@ -20,7 +20,7 @@ use common::{
combat::{combat_rating, Damage}, combat::{combat_rating, Damage},
comp::{ comp::{
item::{ItemDef, MaterialStatManifest, Quality}, item::{ItemDef, MaterialStatManifest, Quality},
Body, Energy, Health, Inventory, Poise, Stats, Body, Energy, Health, Inventory, Poise, SkillSet, Stats,
}, },
}; };
use conrod_core::{ use conrod_core::{
@ -473,6 +473,7 @@ pub struct Bag<'a> {
pulse: f32, pulse: f32,
localized_strings: &'a Localization, localized_strings: &'a Localization,
stats: &'a Stats, stats: &'a Stats,
skill_set: &'a SkillSet,
health: &'a Health, health: &'a Health,
energy: &'a Energy, energy: &'a Energy,
show: &'a Show, show: &'a Show,
@ -494,6 +495,7 @@ impl<'a> Bag<'a> {
pulse: f32, pulse: f32,
localized_strings: &'a Localization, localized_strings: &'a Localization,
stats: &'a Stats, stats: &'a Stats,
skill_set: &'a SkillSet,
health: &'a Health, health: &'a Health,
energy: &'a Energy, energy: &'a Energy,
show: &'a Show, show: &'a Show,
@ -513,6 +515,7 @@ impl<'a> Bag<'a> {
pulse, pulse,
localized_strings, localized_strings,
stats, stats,
skill_set,
energy, energy,
health, health,
show, show,
@ -738,8 +741,14 @@ impl<'a> Widget for Bag<'a> {
.resize(STATS.len(), &mut ui.widget_id_generator()) .resize(STATS.len(), &mut ui.widget_id_generator())
}); });
// Stats // Stats
let combat_rating = let combat_rating = combat_rating(
combat_rating(inventory, self.health, self.stats, *self.body, &self.msm).min(999.9); inventory,
self.health,
self.skill_set,
*self.body,
&self.msm,
)
.min(999.9);
let indicator_col = cr_color(combat_rating); let indicator_col = cr_color(combat_rating);
for i in STATS.iter().copied().enumerate() { for i in STATS.iter().copied().enumerate() {
let btn = Button::image(match i.1 { let btn = Button::image(match i.1 {

View File

@ -9,7 +9,7 @@ use crate::{
GlobalState, GlobalState,
}; };
use client::Client; use client::Client;
use common::comp::Stats; use common::comp::{SkillSet, Stats};
use conrod_core::{ use conrod_core::{
widget::{self, Button, Image, Text}, widget::{self, Button, Image, Text},
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon, widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
@ -59,6 +59,7 @@ pub struct Buttons<'a> {
tooltip_manager: &'a mut TooltipManager, tooltip_manager: &'a mut TooltipManager,
localized_strings: &'a Localization, localized_strings: &'a Localization,
stats: &'a Stats, stats: &'a Stats,
skill_set: &'a SkillSet,
pulse: f32, pulse: f32,
} }
@ -74,6 +75,7 @@ impl<'a> Buttons<'a> {
tooltip_manager: &'a mut TooltipManager, tooltip_manager: &'a mut TooltipManager,
localized_strings: &'a Localization, localized_strings: &'a Localization,
stats: &'a Stats, stats: &'a Stats,
skill_set: &'a SkillSet,
pulse: f32, pulse: f32,
) -> Self { ) -> Self {
Self { Self {
@ -87,6 +89,7 @@ impl<'a> Buttons<'a> {
tooltip_manager, tooltip_manager,
localized_strings, localized_strings,
stats, stats,
skill_set,
pulse, pulse,
} }
} }
@ -335,7 +338,7 @@ impl<'a> Widget for Buttons<'a> {
.set(state.ids.map_text, ui); .set(state.ids.map_text, ui);
} }
// Diary // Diary
let unspent_sp = self.stats.skill_set.has_available_sp(); let unspent_sp = self.skill_set.has_available_sp();
if Button::image(if !unspent_sp { if Button::image(if !unspent_sp {
self.imgs.spellbook_button self.imgs.spellbook_button
} else { } else {

File diff suppressed because it is too large Load Diff

View File

@ -348,6 +348,7 @@ impl<'a> Widget for Group<'a> {
}; };
let client_state = self.client.state(); let client_state = self.client.state();
let stats = client_state.ecs().read_storage::<common::comp::Stats>(); let stats = client_state.ecs().read_storage::<common::comp::Stats>();
let skill_sets = client_state.ecs().read_storage::<common::comp::SkillSet>();
let healths = client_state.ecs().read_storage::<common::comp::Health>(); let healths = client_state.ecs().read_storage::<common::comp::Health>();
let energy = client_state.ecs().read_storage::<common::comp::Energy>(); let energy = client_state.ecs().read_storage::<common::comp::Energy>();
let buffs = client_state.ecs().read_storage::<common::comp::Buffs>(); let buffs = client_state.ecs().read_storage::<common::comp::Buffs>();
@ -361,6 +362,7 @@ impl<'a> Widget for Group<'a> {
self.show.group = true; self.show.group = true;
let entity = uid_allocator.retrieve_entity_internal(uid.into()); let entity = uid_allocator.retrieve_entity_internal(uid.into());
let stats = entity.and_then(|entity| stats.get(entity)); let stats = entity.and_then(|entity| stats.get(entity));
let skill_set = entity.and_then(|entity| skill_sets.get(entity));
let health = entity.and_then(|entity| healths.get(entity)); let health = entity.and_then(|entity| healths.get(entity));
let energy = entity.and_then(|entity| energy.get(entity)); let energy = entity.and_then(|entity| energy.get(entity));
let buffs = entity.and_then(|entity| buffs.get(entity)); let buffs = entity.and_then(|entity| buffs.get(entity));
@ -368,11 +370,11 @@ impl<'a> Widget for Group<'a> {
let is_leader = uid == leader; let is_leader = uid == leader;
let body = entity.and_then(|entity| bodies.get(entity)); let body = entity.and_then(|entity| bodies.get(entity));
if let (Some(stats), Some(inventory), Some(health), Some(body)) = if let (Some(stats), Some(skill_set), Some(inventory), Some(health), Some(body)) =
(stats, inventory, health, body) (stats, skill_set, inventory, health, body)
{ {
let combat_rating = let combat_rating =
combat::combat_rating(inventory, health, stats, *body, &self.msm); combat::combat_rating(inventory, health, skill_set, *body, &self.msm);
let char_name = stats.name.to_string(); let char_name = stats.name.to_string();
let health_perc = let health_perc =
health.current() as f64 / health.base_max().max(health.maximum()) as f64; health.current() as f64 / health.base_max().max(health.maximum()) as f64;

View File

@ -76,8 +76,11 @@ impl State {
use specs::WorldExt; use specs::WorldExt;
let inventories = client.state().ecs().read_storage::<Inventory>(); let inventories = client.state().ecs().read_storage::<Inventory>();
let inventory = inventories.get(client.entity()); let inventory = inventories.get(client.entity());
let stats = client.state().ecs().read_storage::<common::comp::Stats>(); let skill_sets = client
let stat = stats.get(client.entity()); .state()
.ecs()
.read_storage::<common::comp::SkillSet>();
let skill_set = skill_sets.get(client.entity());
let hands = let hands =
|equip_slot| match inventory.and_then(|i| i.equipped(equip_slot).map(|i| i.kind())) { |equip_slot| match inventory.and_then(|i| i.equipped(equip_slot).map(|i| i.kind())) {
@ -91,8 +94,8 @@ impl State {
_ => None, _ => None,
}; };
let should_be_present = if let (Some(inventory), Some(stat), Some(equip_slot)) = let should_be_present = if let (Some(inventory), Some(skill_set), Some(equip_slot)) =
(inventory, stat, equip_slot) (inventory, skill_set, equip_slot)
{ {
inventory.equipped(equip_slot).map_or(false, |i| { inventory.equipped(equip_slot).map_or(false, |i| {
i.item_config_expect() i.item_config_expect()
@ -100,9 +103,7 @@ impl State {
.abilities .abilities
.get(0) .get(0)
.as_ref() .as_ref()
.map_or(false, |(s, _)| { .map_or(false, |(s, _)| s.map_or(true, |s| skill_set.has_skill(s)))
s.map_or(true, |s| stat.skill_set.has_skill(s))
})
}) })
} else { } else {
false false
@ -128,9 +129,12 @@ impl State {
use specs::WorldExt; use specs::WorldExt;
let inventories = client.state().ecs().read_storage::<Inventory>(); let inventories = client.state().ecs().read_storage::<Inventory>();
let inventory = inventories.get(client.entity()); let inventory = inventories.get(client.entity());
let stats = client.state().ecs().read_storage::<common::comp::Stats>(); let skill_sets = client
let stat = stats.get(client.entity()); .state()
let should_be_present = if let (Some(inventory), Some(stat)) = (inventory, stat) { .ecs()
.read_storage::<common::comp::SkillSet>();
let skill_set = skill_sets.get(client.entity());
let should_be_present = if let (Some(inventory), Some(skill_set)) = (inventory, skill_set) {
let hands = |equip_slot| match inventory.equipped(equip_slot).map(|i| i.kind()) { let hands = |equip_slot| match inventory.equipped(equip_slot).map(|i| i.kind()) {
Some(ItemKind::Tool(tool)) => Some(tool.hands), Some(ItemKind::Tool(tool)) => Some(tool.hands),
_ => None, _ => None,
@ -154,9 +158,7 @@ impl State {
.abilities .abilities
.get(skill_index) .get(skill_index)
.as_ref() .as_ref()
.map_or(false, |(s, _)| { .map_or(false, |(s, _)| s.map_or(true, |s| skill_set.has_skill(s)))
s.map_or(true, |s| stat.skill_set.has_skill(s))
})
}) })
} else { } else {
false false

View File

@ -959,6 +959,7 @@ impl Hud {
let ecs = client.state().ecs(); let ecs = client.state().ecs();
let pos = ecs.read_storage::<comp::Pos>(); let pos = ecs.read_storage::<comp::Pos>();
let stats = ecs.read_storage::<comp::Stats>(); let stats = ecs.read_storage::<comp::Stats>();
let skill_sets = ecs.read_storage::<comp::SkillSet>();
let healths = ecs.read_storage::<comp::Health>(); let healths = ecs.read_storage::<comp::Health>();
let buffs = ecs.read_storage::<comp::Buffs>(); let buffs = ecs.read_storage::<comp::Buffs>();
let energy = ecs.read_storage::<comp::Energy>(); let energy = ecs.read_storage::<comp::Energy>();
@ -1456,11 +1457,12 @@ impl Hud {
let speech_bubbles = &self.speech_bubbles; let speech_bubbles = &self.speech_bubbles;
// Render overhead name tags and health bars // Render overhead name tags and health bars
for (pos, info, bubble, _, health, _, height_offset, hpfl, in_group) in ( for (pos, info, bubble, _, _, health, _, height_offset, hpfl, in_group) in (
&entities, &entities,
&pos, &pos,
interpolated.maybe(), interpolated.maybe(),
&stats, &stats,
&skill_sets,
healths.maybe(), healths.maybe(),
&buffs, &buffs,
energy.maybe(), energy.maybe(),
@ -1472,7 +1474,7 @@ impl Hud {
) )
.join() .join()
.filter(|t| { .filter(|t| {
let health = t.4; let health = t.5;
let entity = t.0; let entity = t.0;
entity != me && !health.map_or(false, |h| h.is_dead) entity != me && !health.map_or(false, |h| h.is_dead)
}) })
@ -1482,6 +1484,7 @@ impl Hud {
pos, pos,
interpolated, interpolated,
stats, stats,
skill_set,
health, health,
buffs, buffs,
energy, energy,
@ -1523,7 +1526,7 @@ impl Hud {
buffs, buffs,
energy, energy,
combat_rating: health.map_or(0.0, |health| { combat_rating: health.map_or(0.0, |health| {
combat::combat_rating(inventory, health, stats, *body, &msm) combat::combat_rating(inventory, health, skill_set, *body, &msm)
}), }),
}); });
let bubble = if dist_sqr < SPEECH_BUBBLE_RANGE.powi(2) { let bubble = if dist_sqr < SPEECH_BUBBLE_RANGE.powi(2) {
@ -1538,6 +1541,7 @@ impl Hud {
info, info,
bubble, bubble,
stats, stats,
skill_set,
health, health,
buffs, buffs,
body.height() * scale.map_or(1.0, |s| s.0) + 0.5, body.height() * scale.map_or(1.0, |s| s.0) + 0.5,
@ -2167,10 +2171,12 @@ impl Hud {
// Bag button and nearby icons // Bag button and nearby icons
let ecs = client.state().ecs(); let ecs = client.state().ecs();
let entity = client.entity();
let stats = ecs.read_storage::<comp::Stats>(); let stats = ecs.read_storage::<comp::Stats>();
let skill_sets = ecs.read_storage::<comp::SkillSet>();
let buffs = ecs.read_storage::<comp::Buffs>(); let buffs = ecs.read_storage::<comp::Buffs>();
let msm = ecs.read_resource::<MaterialStatManifest>(); let msm = ecs.read_resource::<MaterialStatManifest>();
if let Some(player_stats) = stats.get(client.entity()) { if let (Some(player_stats), Some(skill_set)) = (stats.get(entity), skill_sets.get(entity)) {
match Buttons::new( match Buttons::new(
client, client,
self.show.bag, self.show.bag,
@ -2181,6 +2187,7 @@ impl Hud {
tooltip_manager, tooltip_manager,
i18n, i18n,
&player_stats, &player_stats,
&skill_set,
self.pulse, self.pulse,
) )
.set(self.ids.buttons, ui_widgets) .set(self.ids.buttons, ui_widgets)
@ -2342,8 +2349,9 @@ impl Hud {
} }
// Bag contents // Bag contents
if self.show.bag { if self.show.bag {
if let (Some(player_stats), Some(health), Some(energy), Some(body)) = ( if let (Some(player_stats), Some(skill_set), Some(health), Some(energy), Some(body)) = (
stats.get(client.entity()), stats.get(client.entity()),
skill_sets.get(client.entity()),
healths.get(entity), healths.get(entity),
energies.get(entity), energies.get(entity),
bodies.get(entity), bodies.get(entity),
@ -2360,6 +2368,7 @@ impl Hud {
self.pulse, self.pulse,
i18n, i18n,
&player_stats, &player_stats,
&skill_set,
&health, &health,
&energy, &energy,
&self.show, &self.show,
@ -2758,12 +2767,12 @@ impl Hud {
// Diary // Diary
if self.show.diary { if self.show.diary {
let entity = client.entity(); let entity = client.entity();
let stats = ecs.read_storage::<comp::Stats>(); let skill_sets = ecs.read_storage::<comp::SkillSet>();
if let Some(stats) = stats.get(entity) { if let Some(skill_set) = skill_sets.get(entity) {
for event in Diary::new( for event in Diary::new(
&self.show, &self.show,
client, client,
&stats, &skill_set,
&self.imgs, &self.imgs,
&self.item_imgs, &self.item_imgs,
&self.fonts, &self.fonts,