Merge branch 'energy-recharge' into 'master'

Energy recharge

See merge request veloren/veloren!724
This commit is contained in:
Timo Koesters 2020-01-19 22:26:02 +00:00
commit 7f179fa2eb
14 changed files with 234 additions and 72 deletions

Binary file not shown.

BIN
assets/voxygen/element/icons/staff_m2.vox (Stored with Git LFS)

Binary file not shown.

View File

@ -5,6 +5,7 @@ use specs_idvs::IDVStorage;
pub struct Energy {
current: u32,
maximum: u32,
pub regen_rate: f32,
pub last_change: Option<(i32, f64, EnergySource)>,
}
@ -12,14 +13,23 @@ pub struct Energy {
pub enum EnergySource {
CastSpell,
LevelUp,
Regen,
Revive,
Unknown,
}
#[derive(Debug)]
pub enum StatChangeError {
Underflow,
Overflow,
}
impl Energy {
pub fn new(amount: u32) -> Energy {
Energy {
current: amount,
maximum: amount,
regen_rate: 0.0,
last_change: None,
}
}
@ -43,6 +53,21 @@ impl Energy {
self.last_change = Some((amount, 0.0, cause));
}
pub fn try_change_by(
&mut self,
amount: i32,
cause: EnergySource,
) -> Result<(), StatChangeError> {
if self.current as i32 + amount < 0 {
Err(StatChangeError::Underflow)
} else if self.current as i32 + amount > self.maximum as i32 {
Err(StatChangeError::Overflow)
} else {
self.change_by(amount, cause);
Ok(())
}
}
pub fn set_maximum(&mut self, amount: u32) {
self.maximum = amount;
self.current = self.current.min(self.maximum);

View File

@ -26,7 +26,7 @@ pub use controller::{
ControlEvent, Controller, ControllerInputs, Input, InputState, InventoryManip, MountState,
Mounting,
};
pub use energy::Energy;
pub use energy::{Energy, EnergySource};
pub use inputs::CanBuild;
pub use inventory::{item, Inventory, InventoryUpdate, Item, ItemKind};
pub use last::Last;

View File

@ -76,6 +76,26 @@ impl Health {
self.current = self.current.min(self.maximum);
}
}
#[derive(Debug)]
pub enum StatChangeError {
Underflow,
Overflow,
}
use std::error::Error;
use std::fmt;
impl fmt::Display for StatChangeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::Underflow => "insufficient stat quantity",
Self::Overflow => "stat quantity would overflow",
}
)
}
}
impl Error for StatChangeError {}
impl Exp {
pub fn current(&self) -> u32 {
@ -164,10 +184,7 @@ impl Stats {
current: 0,
maximum: 50,
},
equipment: Equipment {
main: main,
alt: None,
},
equipment: Equipment { main, alt: None },
is_dead: false,
};

View File

@ -39,7 +39,7 @@ pub struct DeltaTime(pub f32);
/// upper limit. If delta time exceeds this value, the game's physics will begin to produce time
/// lag. Ideally, we'd avoid such a situation.
const MAX_DELTA_TIME: f32 = 1.0;
const HUMANOID_JUMP_ACCEL: f32 = 16.0;
const HUMANOID_JUMP_ACCEL: f32 = 26.0;
#[derive(Default)]
pub struct BlockChange {

View File

@ -2,8 +2,8 @@ use super::movement::ROLL_DURATION;
use crate::{
comp::{
self, item, projectile, ActionState, ActionState::*, Body, CharacterState, ControlEvent,
Controller, ControllerInputs, HealthChange, HealthSource, ItemKind, Mounting,
MovementState, MovementState::*, PhysicsState, Projectile, Stats, Vel,
Controller, ControllerInputs, Energy, EnergySource, HealthChange, HealthSource, ItemKind,
Mounting, MovementState, MovementState::*, PhysicsState, Projectile, Stats, Vel,
},
event::{Emitter, EventBus, LocalEvent, ServerEvent},
state::DeltaTime,
@ -16,6 +16,8 @@ use specs::{
use std::time::Duration;
use vek::*;
const CHARGE_COST: i32 = 200;
/// # Controller System
/// #### Responsible for validating controller inputs and setting new Character States
/// ----
@ -247,6 +249,7 @@ impl<'a> System<'a> for Sys {
WriteStorage<'a, Controller>,
WriteStorage<'a, CharacterState>,
ReadStorage<'a, Stats>,
WriteStorage<'a, Energy>,
ReadStorage<'a, Body>,
ReadStorage<'a, Vel>,
ReadStorage<'a, PhysicsState>,
@ -264,6 +267,7 @@ impl<'a> System<'a> for Sys {
mut controllers,
mut character_states,
stats,
mut energies,
bodies,
velocities,
physics_states,
@ -273,12 +277,24 @@ impl<'a> System<'a> for Sys {
) {
let mut server_emitter = server_bus.emitter();
let mut local_emitter = local_bus.emitter();
for (entity, uid, controller, mut character, stats, body, vel, physics, mount) in (
for (
entity,
uid,
controller,
mut character,
stats,
mut energy,
body,
vel,
physics,
mount,
) in (
&entities,
&uids,
&mut controllers,
&mut character_states,
&stats,
&mut energies.restrict_mut(),
&bodies,
&velocities,
&physics_states,
@ -579,9 +595,15 @@ impl<'a> System<'a> for Sys {
// Try to charge
if inputs.charge.is_pressed() && !inputs.charge.is_held_down() {
character.action = Charge {
time_left: Duration::from_millis(250),
};
if energy
.get_mut_unchecked()
.try_change_by(-CHARGE_COST, EnergySource::CastSpell)
.is_ok()
{
character.action = Charge {
time_left: Duration::from_millis(250),
}
}
continue;
}

View File

@ -15,13 +15,13 @@ use vek::*;
pub const ROLL_DURATION: Duration = Duration::from_millis(600);
const HUMANOID_ACCEL: f32 = 50.0;
const HUMANOID_ACCEL: f32 = 100.0;
const HUMANOID_SPEED: f32 = 120.0;
const HUMANOID_AIR_ACCEL: f32 = 10.0;
const HUMANOID_AIR_ACCEL: f32 = 15.0;
const HUMANOID_AIR_SPEED: f32 = 100.0;
const HUMANOID_WATER_ACCEL: f32 = 70.0;
const HUMANOID_WATER_SPEED: f32 = 120.0;
const HUMANOID_CLIMB_ACCEL: f32 = 5.0;
const HUMANOID_CLIMB_ACCEL: f32 = 10.0;
const ROLL_SPEED: f32 = 17.0;
const CHARGE_SPEED: f32 = 20.0;
const GLIDE_ACCEL: f32 = 15.0;
@ -221,14 +221,9 @@ impl<'a> System<'a> for Sys {
if inputs.climb_down.is_pressed() && !inputs.climb.is_pressed() {
vel.0 -= dt.0 * vel.0.map(|e| e.abs().powf(1.5) * e.signum() * 6.0);
} else if inputs.climb.is_pressed() && !inputs.climb_down.is_pressed() {
vel.0.z = (vel.0.z + dt.0 * GRAVITY * 1.25).min(CLIMB_SPEED);
vel.0.z = (vel.0.z + dt.0 * GRAVITY * 1.25).min(CLIMB_SPEED).max(0.0);
} else {
vel.0.z = vel.0.z + dt.0 * GRAVITY * 1.5;
vel.0 = Lerp::lerp(
vel.0,
Vec3::zero(),
30.0 * dt.0 / (1.0 - vel.0.z.min(0.0) * 5.0),
);
vel.0.z = (vel.0.z - dt.0 * GRAVITY * 0.01).min(CLIMB_SPEED);
}
}

View File

@ -11,14 +11,14 @@ use {
vek::*,
};
pub const GRAVITY: f32 = 9.81 * 4.0;
pub const GRAVITY: f32 = 9.81 * 7.0;
const BOUYANCY: f32 = 0.0;
// Friction values used for linear damping. They are unitless quantities. The
// value of these quantities must be between zero and one. They represent the
// amount an object will slow down within 1/60th of a second. Eg. if the frction
// is 0.01, and the speed is 1.0, then after 1/60th of a second the speed will
// be 0.99. after 1 second the speed will be 0.54, which is 0.99 ^ 60.
const FRIC_GROUND: f32 = 0.08;
const FRIC_GROUND: f32 = 0.15;
const FRIC_AIR: f32 = 0.0125;
const FRIC_FLUID: f32 = 0.2;
@ -33,7 +33,7 @@ fn integrate_forces(dt: f32, mut lv: Vec3<f32>, grav: f32, damp: f32) -> Vec3<f3
// must be interpolated accordingly
let linear_damp = (1.0 - damp.min(1.0)).powf(dt * 60.0);
lv.z = (lv.z - grav * dt).max(-50.0);
lv.z = (lv.z - grav * dt).max(-80.0);
lv * linear_damp
}
@ -275,6 +275,7 @@ impl<'a> System<'a> for Sys {
{
// ...block-hop!
pos.0.z = (pos.0.z + 0.1).ceil();
vel.0.z = 0.0;
on_ground = true;
break;
} else {

View File

@ -1,22 +1,28 @@
use crate::{
comp::{HealthSource, Stats},
comp::{ActionState, CharacterState, Energy, EnergySource, HealthSource, Stats},
event::{EventBus, ServerEvent},
state::DeltaTime,
};
use specs::{Entities, Join, Read, System, WriteStorage};
use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage};
/// This system kills players
/// and handles players levelling up
const ENERGY_REGEN_ACCEL: f32 = 20.0;
/// This system kills players, levels them up, and regenerates energy.
pub struct Sys;
impl<'a> System<'a> for Sys {
type SystemData = (
Entities<'a>,
Read<'a, DeltaTime>,
Read<'a, EventBus<ServerEvent>>,
ReadStorage<'a, CharacterState>,
WriteStorage<'a, Stats>,
WriteStorage<'a, Energy>,
);
fn run(&mut self, (entities, dt, server_event_bus, mut stats): Self::SystemData) {
fn run(
&mut self,
(entities, dt, server_event_bus, character_states, mut stats,mut energies): Self::SystemData,
) {
let mut server_event_emitter = server_event_bus.emitter();
// Increment last change timer
@ -27,7 +33,14 @@ impl<'a> System<'a> for Sys {
stats.set_event_emission(true);
// Mutates all stats every tick causing the server to resend this component for every entity every tick
for (entity, mut stats) in (&entities, &mut stats.restrict_mut()).join() {
for (entity, character_state, mut stats, mut energy) in (
&entities,
&character_states,
&mut stats.restrict_mut(),
&mut energies.restrict_mut(),
)
.join()
{
let (set_dead, level_up) = {
let stat = stats.get_unchecked();
(
@ -58,6 +71,31 @@ impl<'a> System<'a> for Sys {
stat.health
.set_to(stat.health.maximum(), HealthSource::LevelUp)
}
// Accelerate recharging energy if not wielding.
match character_state.action {
ActionState::Idle => {
if {
let energy = energy.get_unchecked();
energy.current() < energy.maximum()
} {
let mut energy = energy.get_mut_unchecked();
// Have to account for Calc I differential equations due to acceleration
energy.change_by(
(energy.regen_rate * dt.0 + ENERGY_REGEN_ACCEL * dt.0.powf(2.0) / 2.0)
as i32,
EnergySource::Regen,
);
energy.regen_rate += ENERGY_REGEN_ACCEL * dt.0;
}
}
// All other states do not regen and set the rate back to zero.
_ => {
if energy.get_unchecked().regen_rate != 0.0 {
energy.get_mut_unchecked().regen_rate = 0.0
}
}
}
}
}
}

View File

@ -258,7 +258,7 @@ impl Server {
state.write_component(entity, body);
state.write_component(entity, comp::Stats::new(name, main));
state.write_component(entity, comp::Energy::new(200));
state.write_component(entity, comp::Energy::new(1000));
state.write_component(entity, comp::Controller::default());
state.write_component(entity, comp::Pos(spawn_point));
state.write_component(entity, comp::Vel(Vec3::zero()));
@ -434,6 +434,17 @@ impl Server {
.map(|err| {
error!("Failed to insert ForceUpdate on dead client: {:?}", err)
});
state
.ecs()
.write_storage::<comp::Energy>()
.get_mut(entity)
.map(|energy| {
energy.set_to(energy.maximum(), comp::EnergySource::Revive)
});
let _ = state
.ecs()
.write_storage::<comp::CharacterState>()
.insert(entity, comp::CharacterState::default());
} else {
// If not a player delete the entity
if let Err(err) = state.delete_entity_recorded(entity) {
@ -613,11 +624,11 @@ impl Server {
}
ServerEvent::LandOnGround { entity, vel } => {
if vel.z <= -25.0 {
if vel.z <= -37.0 {
if let Some(stats) =
state.ecs().write_storage::<comp::Stats>().get_mut(entity)
{
let falldmg = (vel.z / 5.0) as i32;
let falldmg = (vel.z / 2.5) as i32;
if falldmg < 0 {
stats.health.change_by(comp::HealthChange {
amount: falldmg,
@ -1185,7 +1196,7 @@ impl StateExt for State {
.with(comp::Controller::default())
.with(body)
.with(stats)
.with(comp::Energy::new(100))
.with(comp::Energy::new(500))
.with(comp::Gravity(1.0))
.with(comp::CharacterState::default())
}

View File

@ -120,7 +120,7 @@ image_ids! {
flyingrod_m1: "voxygen.element.icons.debug_wand_m1",
flyingrod_m2: "voxygen.element.icons.debug_wand_m2",
charge: "voxygen.element.icons.skill_charge_2",
charge: "voxygen.element.icons.skill_charge_3",
// Icons

View File

@ -544,7 +544,7 @@ impl Hud {
let own_level = stats
.get(client.entity())
.map_or(0, |stats| stats.level.level());
//self.input = client.read_storage::<comp::ControllerInputs>();
if let Some(stats) = stats.get(me) {
// Hurt Frame
let hp_percentage =
@ -1653,10 +1653,12 @@ impl Hud {
let energy = ecs.read_storage::<comp::Energy>();
let character_state = ecs.read_storage::<comp::CharacterState>();
let entity = client.entity();
if let (Some(stats), Some(energy), Some(character_state)) = (
let controller = ecs.read_storage::<comp::Controller>();
if let (Some(stats), Some(energy), Some(character_state), Some(controller)) = (
stats.get(entity),
energy.get(entity),
character_state.get(entity),
controller.get(entity).map(|c| &c.inputs),
) {
Skillbar::new(
global_state,
@ -1666,6 +1668,7 @@ impl Hud {
&energy,
&character_state,
self.pulse,
&controller,
)
.set(self.ids.skillbar, ui_widgets);
}

View File

@ -3,7 +3,9 @@ use super::{
/*FOCUS_COLOR, RAGE_COLOR,*/ HP_COLOR, LOW_HP_COLOR, MANA_COLOR, TEXT_COLOR, XP_COLOR,
};
use crate::GlobalState;
use common::comp::{item::Debug, item::Tool, ActionState, CharacterState, Energy, ItemKind, Stats};
use common::comp::{
item::Debug, item::Tool, ActionState, CharacterState, ControllerInputs, Energy, ItemKind, Stats,
};
use conrod_core::{
color,
widget::{self, Button, Image, Rectangle, Text},
@ -103,6 +105,7 @@ pub struct Skillbar<'a> {
stats: &'a Stats,
energy: &'a Energy,
character_state: &'a CharacterState,
controller: &'a ControllerInputs,
pulse: f32,
#[conrod(common_builder)]
common: widget::CommonBuilder,
@ -118,6 +121,7 @@ impl<'a> Skillbar<'a> {
energy: &'a Energy,
character_state: &'a CharacterState,
pulse: f32,
controller: &'a ControllerInputs,
) -> Self {
Self {
imgs,
@ -129,6 +133,7 @@ impl<'a> Skillbar<'a> {
common: widget::CommonBuilder::default(),
character_state,
pulse,
controller,
}
}
}
@ -526,17 +531,32 @@ impl<'a> Widget for Skillbar<'a> {
match self.character_state.action {
ActionState::Attack { .. } => {
let fade_pulse = (self.pulse * 4.0/*speed factor*/).cos() * 0.5 + 0.6; //Animation timer;
Image::new(self.imgs.skillbar_slot_big)
.w_h(40.0 * scale, 40.0 * scale)
.top_left_with_margins_on(state.ids.hotbar_align, -40.0 * scale, 0.0)
.set(state.ids.m1_slot, ui);
Image::new(self.imgs.skillbar_slot_big_act)
.w_h(40.0 * scale, 40.0 * scale)
.middle_of(state.ids.m1_slot)
.color(Some(Color::Rgba(1.0, 1.0, 1.0, fade_pulse)))
.floating(true)
.set(state.ids.m1_slot_act, ui);
if self.controller.primary.is_pressed() {
let fade_pulse = (self.pulse * 4.0/*speed factor*/).cos() * 0.5 + 0.6; //Animation timer;
Image::new(self.imgs.skillbar_slot_big)
.w_h(40.0 * scale, 40.0 * scale)
.top_left_with_margins_on(
state.ids.hotbar_align,
-40.0 * scale,
0.0,
)
.set(state.ids.m1_slot, ui);
Image::new(self.imgs.skillbar_slot_big_act)
.w_h(40.0 * scale, 40.0 * scale)
.middle_of(state.ids.m1_slot)
.color(Some(Color::Rgba(1.0, 1.0, 1.0, fade_pulse)))
.floating(true)
.set(state.ids.m1_slot_act, ui);
} else {
Image::new(self.imgs.skillbar_slot_big)
.w_h(40.0 * scale, 40.0 * scale)
.top_left_with_margins_on(
state.ids.hotbar_align,
-40.0 * scale,
0.0,
)
.set(state.ids.m1_slot, ui);
}
}
_ => {
Image::new(self.imgs.skillbar_slot_big)
@ -548,9 +568,8 @@ impl<'a> Widget for Skillbar<'a> {
}
}
// M1 Slot
Image::new(self.imgs.skillbar_slot_big_bg)
.w_h(36.0 * scale, 36.0 * scale)
.w_h(38.0 * scale, 38.0 * scale)
.color(match self.stats.equipment.main.as_ref().map(|i| &i.kind) {
Some(ItemKind::Tool { kind, .. }) => match kind {
Tool::Bow => Some(BG_COLOR_2),
@ -576,7 +595,7 @@ impl<'a> Widget for Skillbar<'a> {
.w(match self.stats.equipment.main.as_ref().map(|i| &i.kind) {
Some(ItemKind::Tool { kind, .. }) => match kind {
Tool::Bow => 30.0 * scale,
Tool::Staff => 30.0 * scale,
Tool::Staff => 32.0 * scale,
_ => 38.0 * scale,
},
_ => 38.0 * scale,
@ -584,7 +603,7 @@ impl<'a> Widget for Skillbar<'a> {
.h(match self.stats.equipment.main.as_ref().map(|i| &i.kind) {
Some(ItemKind::Tool { kind, .. }) => match kind {
Tool::Bow => 30.0 * scale,
Tool::Staff => 36.0 * scale,
Tool::Staff => 32.0 * scale,
_ => 38.0 * scale,
},
_ => 38.0 * scale,
@ -595,16 +614,43 @@ impl<'a> Widget for Skillbar<'a> {
match self.character_state.action {
ActionState::Block { .. } => {
let fade_pulse = (self.pulse * 4.0/*speed factor*/).cos() * 0.5 + 0.6; //Animation timer;
Image::new(self.imgs.skillbar_slot_big)
.w_h(40.0 * scale, 40.0 * scale)
.right_from(state.ids.m1_slot, 0.0)
.set(state.ids.m2_slot, ui);
Image::new(self.imgs.skillbar_slot_big_act)
.w_h(40.0 * scale, 40.0 * scale)
.middle_of(state.ids.m2_slot)
.color(Some(Color::Rgba(1.0, 1.0, 1.0, fade_pulse)))
.floating(true)
.set(state.ids.m2_slot_act, ui);
if self.controller.secondary.is_pressed() {
Image::new(self.imgs.skillbar_slot_big)
.w_h(40.0 * scale, 40.0 * scale)
.right_from(state.ids.m1_slot, 0.0)
.set(state.ids.m2_slot, ui);
Image::new(self.imgs.skillbar_slot_big_act)
.w_h(40.0 * scale, 40.0 * scale)
.middle_of(state.ids.m2_slot)
.color(Some(Color::Rgba(1.0, 1.0, 1.0, fade_pulse)))
.floating(true)
.set(state.ids.m2_slot_act, ui);
} else {
Image::new(self.imgs.skillbar_slot_big)
.w_h(40.0 * scale, 40.0 * scale)
.right_from(state.ids.m1_slot, 0.0)
.set(state.ids.m2_slot, ui);
}
}
ActionState::Attack { .. } => {
let fade_pulse = (self.pulse * 4.0/*speed factor*/).cos() * 0.5 + 0.6; //Animation timer;
if self.controller.secondary.is_pressed() {
Image::new(self.imgs.skillbar_slot_big)
.w_h(40.0 * scale, 40.0 * scale)
.right_from(state.ids.m1_slot, 0.0)
.set(state.ids.m2_slot, ui);
Image::new(self.imgs.skillbar_slot_big_act)
.w_h(40.0 * scale, 40.0 * scale)
.middle_of(state.ids.m2_slot)
.color(Some(Color::Rgba(1.0, 1.0, 1.0, fade_pulse)))
.floating(true)
.set(state.ids.m2_slot_act, ui);
} else {
Image::new(self.imgs.skillbar_slot_big)
.w_h(40.0 * scale, 40.0 * scale)
.right_from(state.ids.m1_slot, 0.0)
.set(state.ids.m2_slot, ui);
}
}
_ => {
Image::new(self.imgs.skillbar_slot_big)
@ -615,7 +661,7 @@ impl<'a> Widget for Skillbar<'a> {
}
Image::new(self.imgs.skillbar_slot_big_bg)
.w_h(36.0 * scale, 36.0 * scale)
.w_h(38.0 * scale, 38.0 * scale)
.color(match self.stats.equipment.main.as_ref().map(|i| &i.kind) {
Some(ItemKind::Tool { kind, .. }) => match kind {
Tool::Bow => Some(BG_COLOR_2),
@ -726,14 +772,18 @@ impl<'a> Widget for Skillbar<'a> {
}
}
Image::new(self.imgs.skillbar_slot_bg)
.w_h(19.0 * scale, 19.0 * scale)
.w_h(19.5 * scale, 19.5 * scale)
.color(Some(BG_COLOR))
.middle_of(state.ids.slot1)
.set(state.ids.slot1_bg, ui);
// TODO: Changeable slot image
Image::new(self.imgs.charge)
.w_h(18.0 * scale, 18.0 * scale)
//.color(Some(BG_COLOR))
.color(if self.energy.current() as f64 >= 200.0 {
Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))
} else {
Some(Color::Rgba(0.4, 0.4, 0.4, 1.0))
})
.middle_of(state.ids.slot1_bg)
.set(state.ids.slot1_icon, ui);
// Slot 6
@ -915,8 +965,8 @@ impl<'a> Widget for Skillbar<'a> {
.set(state.ids.health_text, ui);
let energy_text = format!(
"{}/{}",
self.energy.current() as u32,
self.energy.maximum() as u32
self.energy.current() as u32 / 10, //TODO Fix regeneration with smaller energy numbers instead of dividing by 10 here
self.energy.maximum() as u32 / 10
);
Text::new(&energy_text)
.mid_top_with_margin_on(state.ids.energybar_bg, 6.0 * scale)