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

View File

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

View File

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

View File

@ -1,94 +1,42 @@
use crate::comp::{
use crate::{
comp::{
self,
inventory::item::{armor::Protection, ItemKind},
Body, Inventory,
Inventory,
},
util::Dir,
};
use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage};
use specs_idvs::IdvStorage;
use std::ops::Mul;
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)]
/// 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 {
/// Base poise amount for this entity
base_max: u32,
/// Poise of entity at any given moment
// Current and base_max are scaled by 256 within this module compared to what is visible to
// outside this module. The scaling is done to allow poise to function as a fixed point while
// 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,
/// 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,
/// Last poise change, storing time since last change, the change itself,
/// and the knockback direction vector
pub last_change: (f64, PoiseChange, Vec3<f32>),
/// Direction that the last poise change came from
pub last_change: Dir,
/// Rate of poise regeneration per tick. Starts at zero and accelerates.
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
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize, Eq, Hash)]
pub enum PoiseState {
@ -105,95 +53,77 @@ pub enum PoiseState {
}
impl Poise {
/// Creates a new poise struct based on the body it is being assigned to
pub fn new(body: Body) -> Self {
let mut poise = Poise::default();
poise.update_base_max(Some(body));
poise.set_maximum(poise.base_max);
poise.set_to(poise.maximum, PoiseSource::Revive);
/// Maximum value allowed for poise before scaling
const MAX_POISE: u16 = u16::MAX - 1;
/// The maximum value allowed for current and maximum poise
/// Maximum value is (u16:MAX - 1) * 256, which only requires 24 bits. This
/// can fit into an f32 with no loss to precision
// 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 knockback(&self) -> Vec3<f32> { self.last_change.2 }
/// Defines the poise states based on fraction of maximum poise
pub fn poise_state(&self) -> PoiseState {
if self.current >= 7 * self.maximum / 10 {
PoiseState::Normal
} else if self.current >= 5 * self.maximum / 10 {
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
pub fn new(body: comp::Body) -> Self {
let poise = u32::from(body.base_poise()) * Self::SCALING_FACTOR_INT;
Poise {
current: poise,
base_max: poise,
maximum: poise,
last_change: Dir::default(),
regen_rate: 0.0,
}
}
/// Gets the current poise value
pub fn current(&self) -> u32 { self.current }
/// Gets the maximum poise value
pub fn maximum(&self) -> u32 { self.maximum }
/// 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;
pub fn change_by(&mut self, change: f32, impulse: Vec3<f32>) {
self.current = (((self.current() + change).clamp(0.0, f32::from(Self::MAX_POISE))
* Self::SCALING_FACTOR_FLOAT) as u32)
.min(self.maximum);
self.last_change = Dir::from_unnormalized(impulse).unwrap_or_default();
}
/// 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; }
/// Sets the maximum and updates the current value to max out at the new
/// maximum
pub fn set_maximum(&mut self, amount: u32) {
self.maximum = amount;
self.current = self.current.min(self.maximum);
}
/// Returns knockback as a Dir
/// Kept as helper function should additional fields ever be added to last
/// change
pub fn knockback(&self) -> Dir { self.last_change }
/// Sets the `Poise` base_max
fn set_base_max(&mut self, amount: u32) {
self.base_max = amount;
self.current = self.current.min(self.maximum);
}
/// Resets the maximum to the base_max. Example use would be a potion
/// wearing off
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());
/// Defines the poise states based on current poise value
pub fn poise_state(&self) -> PoiseState {
match self.current() {
x if x > 70.0 => PoiseState::Normal,
x if x > 50.0 => PoiseState::Interrupted,
x if x > 40.0 => PoiseState::Stunned,
x if x > 20.0 => PoiseState::Dazed,
_ => PoiseState::KnockedDown,
}
}
@ -218,6 +148,14 @@ impl Poise {
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 {

View File

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

View File

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

View File

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

View File

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

View File

@ -18,7 +18,7 @@ use common::{
chat::{KillSource, KillType},
inventory::item::MaterialStatManifest,
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},
lottery::{LootSpec, Lottery},
@ -40,12 +40,7 @@ use specs::{join::Join, saveload::MarkerAllocator, Builder, Entity as EcsEntity,
use tracing::error;
use vek::{Vec2, Vec3};
pub fn handle_poise(
server: &Server,
entity: EcsEntity,
change: PoiseChange,
knockback_dir: Vec3<f32>,
) {
pub fn handle_poise(server: &Server, entity: EcsEntity, change: f32, knockback_dir: Vec3<f32>) {
let ecs = &server.state.ecs();
if let Some(character_state) = ecs.read_storage::<CharacterState>().get(entity) {
// 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
if let Some(mut poise) = ecs.write_storage::<comp::Poise>().get_mut(entity) {
let poise_damage = PoiseChange {
amount: -(mass.0 * vel.magnitude_squared() / 1500.0) as i32,
source: PoiseSource::Falling,
};
let poise_change = poise_damage.modify_poise_damage(inventories.get(entity));
let poise_damage = -(mass.0 * vel.magnitude_squared() / 1500.0);
let poise_change = Poise::apply_poise_reduction(poise_damage, inventories.get(entity));
poise.change_by(poise_change, Vec3::unit_z());
}
}

View File

@ -142,9 +142,9 @@ impl StateExt for State {
.get_mut(entity)
.map(|mut health| health.change_by(change));
},
Effect::PoiseChange(poise_damage) => {
Effect::Poise(poise) => {
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
if let Some(character_state) = self
.ecs()

View File

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

View File

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