Made poise a float at interface of module.

This commit is contained in:
Sam 2021-09-25 14:07:47 -04:00
parent 0af6969d19
commit 942376f88c
12 changed files with 122 additions and 212 deletions

View File

@ -10,7 +10,6 @@ use crate::{
}, },
slot::EquipSlot, slot::EquipSlot,
}, },
poise::PoiseChange,
skills::SkillGroupKind, skills::SkillGroupKind,
Alignment, Body, CharacterState, Combo, Energy, Health, HealthChange, Inventory, Ori, Alignment, Body, CharacterState, Combo, Energy, Health, HealthChange, Inventory, Ori,
Player, Poise, SkillSet, Stats, Player, Poise, SkillSet, Stats,
@ -281,8 +280,8 @@ impl Attack {
} }
}, },
CombatEffect::Poise(p) => { CombatEffect::Poise(p) => {
let change = PoiseChange::from_value(*p, target.inventory); let change = -Poise::apply_poise_reduction(*p, target.inventory);
if change.amount != 0 { if change.abs() > Poise::POISE_EPSILON {
emit(ServerEvent::PoiseChange { emit(ServerEvent::PoiseChange {
entity: target.entity, entity: target.entity,
change, change,
@ -409,8 +408,8 @@ impl Attack {
} }
}, },
CombatEffect::Poise(p) => { CombatEffect::Poise(p) => {
let change = PoiseChange::from_value(p, target.inventory); let change = -Poise::apply_poise_reduction(p, target.inventory);
if change.amount != 0 { if change.abs() > Poise::POISE_EPSILON {
emit(ServerEvent::PoiseChange { emit(ServerEvent::PoiseChange {
entity: target.entity, entity: target.entity,
change, change,

View File

@ -774,7 +774,7 @@ impl Body {
} }
} }
pub fn base_poise(&self) -> u32 { pub fn base_poise(&self) -> u16 {
match self { match self {
Body::Humanoid(_) => 100, Body::Humanoid(_) => 100,
Body::BipedLarge(biped_large) => match biped_large.species { Body::BipedLarge(biped_large) => match biped_large.species {

View File

@ -91,7 +91,7 @@ pub use self::{
}, },
player::DisconnectReason, player::DisconnectReason,
player::Player, player::Player,
poise::{Poise, PoiseChange, PoiseSource, PoiseState}, poise::{Poise, PoiseState},
projectile::{Projectile, ProjectileConstructor}, projectile::{Projectile, ProjectileConstructor},
shockwave::{Shockwave, ShockwaveHitEntities}, shockwave::{Shockwave, ShockwaveHitEntities},
skills::{Skill, SkillGroup, SkillGroupKind, SkillSet}, skills::{Skill, SkillGroup, SkillGroupKind, SkillSet},

View File

@ -1,94 +1,42 @@
use crate::comp::{ use crate::{
inventory::item::{armor::Protection, ItemKind}, comp::{
Body, Inventory, self,
inventory::item::{armor::Protection, ItemKind},
Inventory,
},
util::Dir,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage}; use specs::{Component, DerefFlaggedStorage};
use specs_idvs::IdvStorage; use specs_idvs::IdvStorage;
use std::ops::Mul;
use vek::*; use vek::*;
/// A change in the poise component. Stores the amount as a signed
/// integer to allow for added or removed poise. Also has a field to
/// label where the poise change came from.
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct PoiseChange {
/// Value of the change in poise
pub amount: i32,
/// Source of change in poise
pub source: PoiseSource,
}
impl PoiseChange {
/// Alters poise damage as a result of armor poise damage reduction
pub fn modify_poise_damage(self, inventory: Option<&Inventory>) -> PoiseChange {
let poise_damage_reduction = inventory.map_or(0.0, Poise::compute_poise_damage_reduction);
let poise_damage = self.amount as f32 * (1.0 - poise_damage_reduction);
// Add match on poise source when different calculations per source
// are needed/wanted
PoiseChange {
amount: poise_damage as i32,
source: self.source,
}
}
/// Creates a poise change from a float
pub fn from_value(poise_damage: f32, inventory: Option<&Inventory>) -> Self {
let poise_damage_reduction = inventory.map_or(0.0, Poise::compute_poise_damage_reduction);
let poise_change = -poise_damage * (1.0 - poise_damage_reduction);
Self {
amount: poise_change as i32,
source: PoiseSource::Attack,
}
}
}
/// Sources of poise change
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub enum PoiseSource {
LevelUp,
Attack,
Explosion,
Falling,
Revive,
Regen,
Other,
}
/// Poise component
#[derive(Clone, Copy, Debug, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Serialize, Deserialize)]
/// Poise is represented by u32s within the module, but treated as a float by
/// the rest of the game.
// As a general rule, all input and output values to public functions should be
// floats rather than integers.
pub struct Poise { pub struct Poise {
/// Base poise amount for this entity // Current and base_max are scaled by 256 within this module compared to what is visible to
base_max: u32, // outside this module. The scaling is done to allow poise to function as a fixed point while
/// Poise of entity at any given moment // still having the advantages of being an integer. The scaling of 256 was chosen so that max
// poise could be u16::MAX - 1, and then the scaled poise could fit inside an f32 with no
// precision loss
/// Current poise is how much poise the entity currently has
current: u32, current: u32,
/// Maximum poise of entity at a given time /// Base max is the amount of poise the entity has without considering
/// temporary modifiers such as buffs
base_max: u32,
/// Maximum is the amount of poise the entity has after temporary modifiers
/// are considered
maximum: u32, maximum: u32,
/// Last poise change, storing time since last change, the change itself, /// Direction that the last poise change came from
/// and the knockback direction vector pub last_change: Dir,
pub last_change: (f64, PoiseChange, Vec3<f32>),
/// Rate of poise regeneration per tick. Starts at zero and accelerates. /// Rate of poise regeneration per tick. Starts at zero and accelerates.
pub regen_rate: f32, pub regen_rate: f32,
} }
impl Default for Poise {
fn default() -> Self {
Self {
current: 0,
maximum: 0,
base_max: 0,
last_change: (
0.0,
PoiseChange {
amount: 0,
source: PoiseSource::Revive,
},
Vec3::zero(),
),
regen_rate: 0.0,
}
}
}
/// States to define effects of a poise change /// States to define effects of a poise change
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize, Eq, Hash)] #[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize, Eq, Hash)]
pub enum PoiseState { pub enum PoiseState {
@ -105,95 +53,77 @@ pub enum PoiseState {
} }
impl Poise { impl Poise {
/// Creates a new poise struct based on the body it is being assigned to /// Maximum value allowed for poise before scaling
pub fn new(body: Body) -> Self { const MAX_POISE: u16 = u16::MAX - 1;
let mut poise = Poise::default(); /// The maximum value allowed for current and maximum poise
poise.update_base_max(Some(body)); /// Maximum value is (u16:MAX - 1) * 256, which only requires 24 bits. This
poise.set_maximum(poise.base_max); /// can fit into an f32 with no loss to precision
poise.set_to(poise.maximum, PoiseSource::Revive); // Cast to u32 done as u32::from cannot be called inside constant
const MAX_SCALED_POISE: u32 = Self::MAX_POISE as u32 * Self::SCALING_FACTOR_INT;
/// Used when comparisons to poise are needed outside this module.
// This value is chosen as anything smaller than this is more precise than our
// units of poise.
pub const POISE_EPSILON: f32 = 0.5 / Self::MAX_SCALED_POISE as f32;
/// The amount poise is scaled by within this module
const SCALING_FACTOR_FLOAT: f32 = 256.;
const SCALING_FACTOR_INT: u32 = Self::SCALING_FACTOR_FLOAT as u32;
poise /// Returns the current value of poise casted to a float
pub fn current(&self) -> f32 { self.current as f32 / Self::SCALING_FACTOR_FLOAT }
/// Returns the base maximum value of poise casted to a float
pub fn base_max(&self) -> f32 { self.base_max as f32 / Self::SCALING_FACTOR_FLOAT }
/// Returns the maximum value of poise casted to a float
pub fn maximum(&self) -> f32 { self.maximum as f32 / Self::SCALING_FACTOR_FLOAT }
/// Returns the fraction of poise an entity has remaining
pub fn fraction(&self) -> f32 { self.current() / self.maximum().max(1.0) }
/// Updates the maximum value for poise
pub fn update_maximum(&mut self, modifiers: comp::stats::StatsModifier) {
let maximum = modifiers
.compute_maximum(self.base_max())
.mul(Self::SCALING_FACTOR_FLOAT)
// NaN does not need to be handled here as rust will automatically change to 0 when casting to u32
.clamp(0.0, Self::MAX_SCALED_POISE as f32) as u32;
self.maximum = maximum;
self.current = self.current.min(self.maximum);
} }
/// Returns knockback as a Vec3 pub fn new(body: comp::Body) -> Self {
pub fn knockback(&self) -> Vec3<f32> { self.last_change.2 } let poise = u32::from(body.base_poise()) * Self::SCALING_FACTOR_INT;
Poise {
/// Defines the poise states based on fraction of maximum poise current: poise,
pub fn poise_state(&self) -> PoiseState { base_max: poise,
if self.current >= 7 * self.maximum / 10 { maximum: poise,
PoiseState::Normal last_change: Dir::default(),
} else if self.current >= 5 * self.maximum / 10 { regen_rate: 0.0,
PoiseState::Interrupted
} else if self.current >= 4 * self.maximum / 10 {
PoiseState::Stunned
} else if self.current >= 2 * self.maximum / 10 {
PoiseState::Dazed
} else {
PoiseState::KnockedDown
} }
} }
/// Gets the current poise value pub fn change_by(&mut self, change: f32, impulse: Vec3<f32>) {
pub fn current(&self) -> u32 { self.current } self.current = (((self.current() + change).clamp(0.0, f32::from(Self::MAX_POISE))
* Self::SCALING_FACTOR_FLOAT) as u32)
/// Gets the maximum poise value .min(self.maximum);
pub fn maximum(&self) -> u32 { self.maximum } self.last_change = Dir::from_unnormalized(impulse).unwrap_or_default();
/// Gets the base_max value
pub fn base_max(&self) -> u32 { self.base_max }
/// Sets the poise value to a provided value. First cuts off the value
/// at the maximum. In most cases change_by() should be used.
pub fn set_to(&mut self, amount: u32, cause: PoiseSource) {
let amount = amount.min(self.maximum);
self.last_change = (
0.0,
PoiseChange {
amount: amount as i32 - self.current as i32,
source: cause,
},
Vec3::zero(),
);
self.current = amount;
} }
/// Changes the current poise due to an in-game effect.
pub fn change_by(&mut self, change: PoiseChange, impulse: Vec3<f32>) {
self.current = ((self.current as i32 + change.amount).max(0) as u32).min(self.maximum);
self.last_change = (
0.0,
PoiseChange {
amount: change.amount,
source: change.source,
},
impulse,
);
}
/// Resets current value to maximum
pub fn reset(&mut self) { self.current = self.maximum; } pub fn reset(&mut self) { self.current = self.maximum; }
/// Sets the maximum and updates the current value to max out at the new /// Returns knockback as a Dir
/// maximum /// Kept as helper function should additional fields ever be added to last
pub fn set_maximum(&mut self, amount: u32) { /// change
self.maximum = amount; pub fn knockback(&self) -> Dir { self.last_change }
self.current = self.current.min(self.maximum);
}
/// Sets the `Poise` base_max /// Defines the poise states based on current poise value
fn set_base_max(&mut self, amount: u32) { pub fn poise_state(&self) -> PoiseState {
self.base_max = amount; match self.current() {
self.current = self.current.min(self.maximum); x if x > 70.0 => PoiseState::Normal,
} x if x > 50.0 => PoiseState::Interrupted,
x if x > 40.0 => PoiseState::Stunned,
/// Resets the maximum to the base_max. Example use would be a potion x if x > 20.0 => PoiseState::Dazed,
/// wearing off _ => PoiseState::KnockedDown,
pub fn reset_max(&mut self) { self.maximum = self.base_max; }
/// Sets the base_max based on the entity `Body`
pub fn update_base_max(&mut self, body: Option<Body>) {
if let Some(body) = body {
self.set_base_max(body.base_poise());
} }
} }
@ -218,6 +148,14 @@ impl Poise {
None => 1.0, None => 1.0,
} }
} }
/// Modifies a poise change when optionally given an inventory to aid in
/// calculation of poise damage reduction
pub fn apply_poise_reduction(value: f32, inventory: Option<&Inventory>) -> f32 {
inventory.map_or(value, |inv| {
value * (1.0 - Poise::compute_poise_damage_reduction(inv))
})
}
} }
impl Component for Poise { impl Component for Poise {

View File

@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Effect { pub enum Effect {
Health(comp::HealthChange), Health(comp::HealthChange),
PoiseChange(comp::PoiseChange), Poise(f32),
Damage(combat::Damage), Damage(combat::Damage),
Buff(BuffEffect), Buff(BuffEffect),
} }
@ -22,7 +22,7 @@ impl Effect {
pub fn info(&self) -> String { pub fn info(&self) -> String {
match self { match self {
Effect::Health(c) => format!("{:+} health", c.amount), Effect::Health(c) => format!("{:+} health", c.amount),
Effect::PoiseChange(c) => format!("{:+} poise", c.amount), Effect::Poise(p) => format!("{:+} poise", p),
Effect::Damage(d) => format!("{:+}", d.value), Effect::Damage(d) => format!("{:+}", d.value),
Effect::Buff(e) => format!("{:?} buff", e), Effect::Buff(e) => format!("{:?} buff", e),
} }
@ -31,7 +31,7 @@ impl Effect {
pub fn is_harm(&self) -> bool { pub fn is_harm(&self) -> bool {
match self { match self {
Effect::Health(c) => c.amount < 0.0, Effect::Health(c) => c.amount < 0.0,
Effect::PoiseChange(c) => c.amount < 0, Effect::Poise(p) => *p < 0.0,
Effect::Damage(_) => true, Effect::Damage(_) => true,
Effect::Buff(e) => !e.kind.is_buff(), Effect::Buff(e) => !e.kind.is_buff(),
} }
@ -42,8 +42,8 @@ impl Effect {
Effect::Health(change) => { Effect::Health(change) => {
change.amount *= modifier; change.amount *= modifier;
}, },
Effect::PoiseChange(change) => { Effect::Poise(poise) => {
change.amount = (change.amount as f32 * modifier) as i32; *poise *= modifier;
}, },
Effect::Damage(damage) => { Effect::Damage(damage) => {
damage.interpolate_damage(modifier, 0.0); damage.interpolate_damage(modifier, 0.0);

View File

@ -53,7 +53,7 @@ pub enum ServerEvent {
}, },
PoiseChange { PoiseChange {
entity: EcsEntity, entity: EcsEntity,
change: comp::PoiseChange, change: f32,
kb_dir: Vec3<f32>, kb_dir: Vec3<f32>,
}, },
Delete(EcsEntity), Delete(EcsEntity),

View File

@ -173,7 +173,7 @@ impl<'a> System<'a> for Sys {
}); });
server_emitter.emit(ServerEvent::Knockback { server_emitter.emit(ServerEvent::Knockback {
entity, entity,
impulse: 5.0 * poise.knockback(), impulse: 5.0 * *poise.knockback(),
}); });
}, },
PoiseState::Dazed => { PoiseState::Dazed => {
@ -195,7 +195,7 @@ impl<'a> System<'a> for Sys {
}); });
server_emitter.emit(ServerEvent::Knockback { server_emitter.emit(ServerEvent::Knockback {
entity, entity,
impulse: 10.0 * poise.knockback(), impulse: 10.0 * *poise.knockback(),
}); });
}, },
PoiseState::KnockedDown => { PoiseState::KnockedDown => {
@ -217,7 +217,7 @@ impl<'a> System<'a> for Sys {
}); });
server_emitter.emit(ServerEvent::Knockback { server_emitter.emit(ServerEvent::Knockback {
entity, entity,
impulse: 10.0 * poise.knockback(), impulse: 10.0 * *poise.knockback(),
}); });
}, },
} }

View File

@ -3,8 +3,8 @@ use common::{
comp::{ comp::{
self, self,
skills::{GeneralSkill, Skill}, skills::{GeneralSkill, Skill},
Body, CharacterState, Combo, Energy, Health, Inventory, Poise, PoiseChange, PoiseSource, Body, CharacterState, Combo, Energy, Health, Inventory, Poise, Pos, SkillSet, Stats,
Pos, SkillSet, Stats, StatsModifier, StatsModifier,
}, },
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
outcome::Outcome, outcome::Outcome,
@ -75,15 +75,10 @@ impl<'a> System<'a> for Sys {
// Increment last change timer // Increment last change timer
healths.set_event_emission(false); // avoid unnecessary syncing healths.set_event_emission(false); // avoid unnecessary syncing
poises.set_event_emission(false); // avoid unnecessary syncing
for mut health in (&mut healths).join() { for mut health in (&mut healths).join() {
health.last_change.0 += f64::from(dt); health.last_change.0 += f64::from(dt);
} }
for mut poise in (&mut poises).join() {
poise.last_change.0 += f64::from(dt);
}
healths.set_event_emission(true); healths.set_event_emission(true);
poises.set_event_emission(true);
// Update stats // Update stats
for (entity, uid, stats, mut skill_set, mut health, pos, mut energy, inventory) in ( for (entity, uid, stats, mut skill_set, mut health, pos, mut energy, inventory) in (
@ -217,15 +212,7 @@ impl<'a> System<'a> for Sys {
if res_poise { if res_poise {
let poise = &mut *poise; let poise = &mut *poise;
poise.change_by( poise.change_by(poise.regen_rate * dt, Vec3::zero());
PoiseChange {
amount: (poise.regen_rate * dt
+ POISE_REGEN_ACCEL * dt.powi(2) / 2.0)
as i32,
source: PoiseSource::Regen,
},
Vec3::zero(),
);
poise.regen_rate = (poise.regen_rate + POISE_REGEN_ACCEL * dt).min(10.0); poise.regen_rate = (poise.regen_rate + POISE_REGEN_ACCEL * dt).min(10.0);
} }
}, },

View File

@ -18,7 +18,7 @@ use common::{
chat::{KillSource, KillType}, chat::{KillSource, KillType},
inventory::item::MaterialStatManifest, inventory::item::MaterialStatManifest,
object, Alignment, Auras, Body, CharacterState, Energy, Group, Health, HealthChange, object, Alignment, Auras, Body, CharacterState, Energy, Group, Health, HealthChange,
Inventory, Player, Poise, PoiseChange, PoiseSource, Pos, SkillSet, Stats, Inventory, Player, Poise, Pos, SkillSet, Stats,
}, },
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
lottery::{LootSpec, Lottery}, lottery::{LootSpec, Lottery},
@ -40,12 +40,7 @@ use specs::{join::Join, saveload::MarkerAllocator, Builder, Entity as EcsEntity,
use tracing::error; use tracing::error;
use vek::{Vec2, Vec3}; use vek::{Vec2, Vec3};
pub fn handle_poise( pub fn handle_poise(server: &Server, entity: EcsEntity, change: f32, knockback_dir: Vec3<f32>) {
server: &Server,
entity: EcsEntity,
change: PoiseChange,
knockback_dir: Vec3<f32>,
) {
let ecs = &server.state.ecs(); let ecs = &server.state.ecs();
if let Some(character_state) = ecs.read_storage::<CharacterState>().get(entity) { if let Some(character_state) = ecs.read_storage::<CharacterState>().get(entity) {
// Entity is invincible to poise change during stunned/staggered character state // Entity is invincible to poise change during stunned/staggered character state
@ -620,11 +615,8 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>)
} }
// Handle poise change // Handle poise change
if let Some(mut poise) = ecs.write_storage::<comp::Poise>().get_mut(entity) { if let Some(mut poise) = ecs.write_storage::<comp::Poise>().get_mut(entity) {
let poise_damage = PoiseChange { let poise_damage = -(mass.0 * vel.magnitude_squared() / 1500.0);
amount: -(mass.0 * vel.magnitude_squared() / 1500.0) as i32, let poise_change = Poise::apply_poise_reduction(poise_damage, inventories.get(entity));
source: PoiseSource::Falling,
};
let poise_change = poise_damage.modify_poise_damage(inventories.get(entity));
poise.change_by(poise_change, Vec3::unit_z()); poise.change_by(poise_change, Vec3::unit_z());
} }
} }

View File

@ -142,9 +142,9 @@ impl StateExt for State {
.get_mut(entity) .get_mut(entity)
.map(|mut health| health.change_by(change)); .map(|mut health| health.change_by(change));
}, },
Effect::PoiseChange(poise_damage) => { Effect::Poise(poise) => {
let inventories = self.ecs().read_storage::<Inventory>(); let inventories = self.ecs().read_storage::<Inventory>();
let change = poise_damage.modify_poise_damage(inventories.get(entity)); let change = Poise::apply_poise_reduction(poise, inventories.get(entity));
// Check to make sure the entity is not already stunned // Check to make sure the entity is not already stunned
if let Some(character_state) = self if let Some(character_state) = self
.ecs() .ecs()

View File

@ -1,5 +1,5 @@
use common::{ use common::{
comp::{Object, PhysicsState, PoiseChange, PoiseSource, Pos, Vel}, comp::{Object, PhysicsState, Pos, Vel},
effect::Effect, effect::Effect,
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
resources::DeltaTime, resources::DeltaTime,
@ -56,10 +56,7 @@ impl<'a> System<'a> for Sys {
kind: DamageKind::Energy, kind: DamageKind::Energy,
value: 40.0, value: 40.0,
})), })),
RadiusEffect::Entity(Effect::PoiseChange(PoiseChange { RadiusEffect::Entity(Effect::Poise(-100.0)),
source: PoiseSource::Explosion,
amount: -100,
})),
RadiusEffect::TerrainDestruction(4.0), RadiusEffect::TerrainDestruction(4.0),
], ],
radius: 12.0, radius: 12.0,
@ -151,10 +148,7 @@ impl<'a> System<'a> for Sys {
kind: DamageKind::Energy, kind: DamageKind::Energy,
value: 5.0, value: 5.0,
})), })),
RadiusEffect::Entity(Effect::PoiseChange(PoiseChange { RadiusEffect::Entity(Effect::Poise(-40.0)),
source: PoiseSource::Explosion,
amount: -40,
})),
RadiusEffect::TerrainDestruction(4.0), RadiusEffect::TerrainDestruction(4.0),
], ],
radius: 12.0, radius: 12.0,

View File

@ -603,8 +603,8 @@ fn selected_entity_window(
ui.label("State"); ui.label("State");
poise_state_label(ui, poise); poise_state_label(ui, poise);
ui.end_row(); ui.end_row();
two_col_row(ui, "Current", format!("{}/{}", poise.current(), poise.maximum())); two_col_row(ui, "Current", format!("{:.1}/{:.1}", poise.current(), poise.maximum()));
two_col_row(ui, "Base Max", poise.base_max().to_string()); two_col_row(ui, "Base Max", format!("{:.1}", poise.base_max()));
}); });
}); });
} }