diff --git a/client/src/lib.rs b/client/src/lib.rs index 21a645c7eb..466778ab1f 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -936,7 +936,24 @@ impl Client { /// Send a chat message to the server. pub fn send_chat(&mut self, message: String) { match validate_chat_msg(&message) { - Ok(()) => self.send_msg(ClientGeneral::ChatMsg(message)), + /* Ok(()) => self.send_msg(ClientGeneral::ChatMsg(message)), */ + Ok(()) => { + if message.starts_with('@') { + if message == "@stats" { + let stats = self + .state + .ecs() + .read_storage::() + .get(self.entity) + .cloned() + .unwrap(); + + tracing::info!("{:?}", stats.skill_set); + } + } else { + self.send_msg(ClientGeneral::ChatMsg(message)) + } + }, Err(ChatMsgValidationError::TooLong) => tracing::warn!( "Attempted to send a message that's too long (Over {} bytes)", MAX_BYTES_CHAT_MSG diff --git a/common/src/combat.rs b/common/src/combat.rs index da56410bed..16c63419a2 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -1,6 +1,6 @@ use crate::{ comp::{ - inventory::item::{armor::Protection, ItemKind}, + inventory::{item::{armor::Protection, tool::ToolKind, ItemKind}, slot::EquipSlot}, BuffKind, HealthChange, HealthSource, Inventory, }, uid::Uid, @@ -195,3 +195,23 @@ impl Knockback { } } } + +pub fn get_weapons(inv: &Inventory) -> (Option, Option) { + ( + inv.equipped(EquipSlot::Mainhand).and_then(|i| { + if let ItemKind::Tool(tool) = &i.kind() { + Some(tool.kind) + } else { + None + } + }), + inv.equipped(EquipSlot::Offhand).and_then(|i| { + if let ItemKind::Tool(tool) = &i.kind() { + Some(tool.kind) + } else { + None + } + }), + + ) +} \ No newline at end of file diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index f6584a385f..653af6e834 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -1,8 +1,8 @@ use crate::{ assets::{self, Asset}, comp::{ - projectile::ProjectileConstructor, Body, CharacterState, EnergySource, Gravity, - LightEmitter, StateUpdate, + projectile::ProjectileConstructor, + Body, CharacterState, EnergySource, Gravity, LightEmitter, StateUpdate, }, states::{ behavior::JoinData, diff --git a/common/src/comp/skills.rs b/common/src/comp/skills.rs index b31d30dfee..8261231763 100644 --- a/common/src/comp/skills.rs +++ b/common/src/comp/skills.rs @@ -1,3 +1,4 @@ +use crate::comp::item::tool::ToolKind; use hashbrown::{HashMap, HashSet}; use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; @@ -10,23 +11,34 @@ lazy_static! { // is requested. TODO: Externalise this data in a RON file for ease of modification pub static ref SKILL_GROUP_DEFS: HashMap> = { let mut defs = HashMap::new(); - defs.insert(SkillGroupType::T1, [ Skill::TestT1Skill1, - Skill::TestT1Skill2, - Skill::TestT1Skill3, - Skill::TestT1Skill4, - Skill::TestT1Skill5] - .iter().cloned().collect::>()); - - defs.insert(SkillGroupType::Swords, [ Skill::TestSwordSkill1, - Skill::TestSwordSkill2, - Skill::TestSwordSkill3] - .iter().cloned().collect::>()); - - defs.insert(SkillGroupType::Axes, [ Skill::TestAxeSkill1, - Skill::TestAxeSkill2, - Skill::TestAxeSkill3] - .iter().cloned().collect::>()); - + defs.insert( + SkillGroupType::General, [ + Skill::General(GeneralSkill::HealthIncrease1), + ].iter().cloned().collect::>()); + defs.insert( + SkillGroupType::Weapon(ToolKind::Sword), [ + Skill::Sword(SwordSkill::UnlockSpin), + ].iter().cloned().collect::>()); + defs.insert( + SkillGroupType::Weapon(ToolKind::Axe), [ + Skill::Axe(AxeSkill::UnlockLeap), + ].iter().cloned().collect::>()); + defs.insert( + SkillGroupType::Weapon(ToolKind::Hammer), [ + Skill::Hammer(HammerSkill::UnlockLeap), + ].iter().cloned().collect::>()); + defs.insert( + SkillGroupType::Weapon(ToolKind::Bow), [ + Skill::Bow(BowSkill::UnlockRepeater), + ].iter().cloned().collect::>()); + defs.insert( + SkillGroupType::Weapon(ToolKind::Staff), [ + Skill::Staff(StaffSkill::UnlockShockwave), + ].iter().cloned().collect::>()); + defs.insert( + SkillGroupType::Weapon(ToolKind::Sceptre), [ + Skill::Sceptre(SceptreSkill::Unlock404), + ].iter().cloned().collect::>()); defs }; } @@ -37,30 +49,66 @@ lazy_static! { /// 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, + General(GeneralSkill), + Sword(SwordSkill), + Axe(AxeSkill), + Hammer(HammerSkill), + Bow(BowSkill), + Staff(StaffSkill), + Sceptre(SceptreSkill), +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub enum SwordSkill { + UnlockSpin, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub enum AxeSkill { + UnlockLeap, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub enum HammerSkill { + UnlockLeap, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub enum BowSkill { + UnlockRepeater, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub enum StaffSkill { + UnlockShockwave, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub enum SceptreSkill { + Unlock404, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub enum GeneralSkill { + HealthIncrease1, + UnlockSwordTree, + UnlockAxeTree, + UnlockHammerTree, + UnlockBowTree, + UnlockStaffTree, + UnlockSceptreTree, } #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub enum SkillGroupType { - T1, - Swords, - Axes, + General, + Weapon(ToolKind), } /// 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)] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct SkillGroup { pub skill_group_type: SkillGroupType, pub exp: u32, @@ -93,20 +141,17 @@ impl Default for SkillSet { fn default() -> Self { // TODO: Default skill groups for new players? Self { - skill_groups: Vec::new(), + skill_groups: vec![ + SkillGroup::new(SkillGroupType::General), + SkillGroup::new(SkillGroupType::Weapon(ToolKind::Sword)), + SkillGroup::new(SkillGroupType::Weapon(ToolKind::Bow)), + ], 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. @@ -251,6 +296,27 @@ impl SkillSet { warn!("Tried to add skill points to a skill group that player does not have"); } } + + /// Checks if the skill set of an entity contains a particular skill group + /// type + pub fn contains_skill_group(&self, skill_group_type: SkillGroupType) -> bool { + self.skill_groups + .iter() + .any(|x| x.skill_group_type == skill_group_type) + } + + /// Adds experience to the skill group within an entity's skill set + pub fn add_experience(&mut self, skill_group_type: SkillGroupType, amount: u32) { + if let Some(mut skill_group) = self + .skill_groups + .iter_mut() + .find(|x| x.skill_group_type == skill_group_type) + { + skill_group.exp += amount; + } else { + warn!("Tried to add experience to a skill group that player does not have"); + } + } } #[cfg(test)] diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index dc01768a4e..21d83483c5 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -1,11 +1,12 @@ use crate::{ client::Client, - comp::{biped_large, quadruped_low, quadruped_medium, quadruped_small, theropod, PhysicsState}, + comp::{biped_large, quadruped_low, quadruped_medium, quadruped_small, skills::SkillGroupType, theropod, PhysicsState}, rtsim::RtSim, Server, SpawnPoint, StateExt, }; use common::{ assets::AssetExt, + combat, comp::{ self, aura, buff, chat::{KillSource, KillType}, @@ -29,6 +30,7 @@ use common_sys::state::BlockChange; use comp::item::Reagent; use rand::prelude::*; use specs::{join::Join, saveload::MarkerAllocator, Entity as EcsEntity, WorldExt}; +use std::collections::HashSet; use tracing::error; use vek::Vec3; @@ -223,16 +225,76 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc let exp = exp_reward / (num_not_pets_in_range as f32 + ATTACKER_EXP_WEIGHT); exp_reward = exp * ATTACKER_EXP_WEIGHT; members_in_range.into_iter().for_each(|e| { + let (main_tool_kind, second_tool_kind) = + if let Some(inventory) = state.ecs().read_storage::().get(e) { + combat::get_weapons(inventory) + } else { + (None, None) + }; if let Some(mut stats) = stats.get_mut(e) { - stats.exp.change_by(exp.ceil() as i64); + // stats.exp.change_by(exp.ceil() as i64); + let mut xp_pools = HashSet::::new(); + xp_pools.insert(SkillGroupType::General); + if let Some(w) = main_tool_kind { + if stats + .skill_set + .contains_skill_group(SkillGroupType::Weapon(w)) + { + xp_pools.insert(SkillGroupType::Weapon(w)); + } + } + if let Some(w) = second_tool_kind { + if stats + .skill_set + .contains_skill_group(SkillGroupType::Weapon(w)) + { + xp_pools.insert(SkillGroupType::Weapon(w)); + } + } + let num_pools = xp_pools.len() as f32; + for pool in xp_pools.drain() { + stats + .skill_set + .add_experience(pool, (exp / num_pools).ceil() as u32); + } } }); } + let (main_tool_kind, second_tool_kind) = + if let Some(inventory) = state.ecs().read_storage::().get(attacker) { + combat::get_weapons(inventory) + } else { + (None, None) + }; if let Some(mut attacker_stats) = stats.get_mut(attacker) { // TODO: Discuss whether we should give EXP by Player // Killing or not. - attacker_stats.exp.change_by(exp_reward.ceil() as i64); + // attacker_stats.exp.change_by(exp_reward.ceil() as i64); + let mut xp_pools = HashSet::::new(); + xp_pools.insert(SkillGroupType::General); + if let Some(w) = main_tool_kind { + if attacker_stats + .skill_set + .contains_skill_group(SkillGroupType::Weapon(w)) + { + xp_pools.insert(SkillGroupType::Weapon(w)); + } + } + if let Some(w) = second_tool_kind { + if attacker_stats + .skill_set + .contains_skill_group(SkillGroupType::Weapon(w)) + { + xp_pools.insert(SkillGroupType::Weapon(w)); + } + } + let num_pools = xp_pools.len() as f32; + for pool in xp_pools.drain() { + attacker_stats + .skill_set + .add_experience(pool, (exp_reward / num_pools).ceil() as u32); + } } })();