Exp is now awarded to specific skill groups. It's automatically split between a general pool and weapon pools based on if you have the weapon in your loadout and if you've unlocked the weapon pools.

This commit is contained in:
Sam 2020-11-14 20:05:18 -06:00
parent 1dd47bc6d9
commit 63eb71ed5b
5 changed files with 212 additions and 47 deletions

View File

@ -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::<comp::Stats>()
.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

View File

@ -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<ToolKind>, Option<ToolKind>) {
(
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
}
}),
)
}

View File

@ -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,

View File

@ -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<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.insert(
SkillGroupType::General, [
Skill::General(GeneralSkill::HealthIncrease1),
].iter().cloned().collect::<HashSet<Skill>>());
defs.insert(
SkillGroupType::Weapon(ToolKind::Sword), [
Skill::Sword(SwordSkill::UnlockSpin),
].iter().cloned().collect::<HashSet<Skill>>());
defs.insert(
SkillGroupType::Weapon(ToolKind::Axe), [
Skill::Axe(AxeSkill::UnlockLeap),
].iter().cloned().collect::<HashSet<Skill>>());
defs.insert(
SkillGroupType::Weapon(ToolKind::Hammer), [
Skill::Hammer(HammerSkill::UnlockLeap),
].iter().cloned().collect::<HashSet<Skill>>());
defs.insert(
SkillGroupType::Weapon(ToolKind::Bow), [
Skill::Bow(BowSkill::UnlockRepeater),
].iter().cloned().collect::<HashSet<Skill>>());
defs.insert(
SkillGroupType::Weapon(ToolKind::Staff), [
Skill::Staff(StaffSkill::UnlockShockwave),
].iter().cloned().collect::<HashSet<Skill>>());
defs.insert(
SkillGroupType::Weapon(ToolKind::Sceptre), [
Skill::Sceptre(SceptreSkill::Unlock404),
].iter().cloned().collect::<HashSet<Skill>>());
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)]

View File

@ -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::<comp::Inventory>().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::<SkillGroupType>::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::<comp::Inventory>().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::<SkillGroupType>::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);
}
}
})();