Merge branch 'sam/ron-abilities' into 'master'

Move abilities to .ron files

Closes #821

See merge request veloren/veloren!1508
This commit is contained in:
Samuel Keiffer 2020-11-13 17:55:57 +00:00
commit 1ed90bd0bf
53 changed files with 1332 additions and 819 deletions

View File

@ -0,0 +1,36 @@
ComboMelee(
stage_data: [
(
stage: 1,
base_damage: 90,
max_damage: 110,
damage_increase: 10,
knockback: 8.0,
range: 3.5,
angle: 50.0,
base_buildup_duration: 350,
base_swing_duration: 75,
base_recover_duration: 400,
forward_movement: 0.5,
),
(
stage: 2,
base_damage: 130,
max_damage: 160,
damage_increase: 15,
knockback: 12.0,
range: 3.5,
angle: 30.0,
base_buildup_duration: 500,
base_swing_duration: 100,
base_recover_duration: 500,
forward_movement: 0.25,
),
],
initial_energy_gain: 0,
max_energy_gain: 100,
energy_increase: 20,
speed_increase: 0.05,
max_speed_increase: 1.6,
is_interruptible: false,
)

View File

@ -0,0 +1,13 @@
LeapMelee(
energy_cost: 450,
buildup_duration: 200,
movement_duration: 200,
swing_duration: 200,
recover_duration: 200,
base_damage: 240,
knockback: 12.0,
range: 4.5,
max_angle: 30.0,
forward_leap_strength: 28.0,
vertical_leap_strength: 8.0,
)

View File

@ -0,0 +1,14 @@
SpinMelee(
buildup_duration: 100,
swing_duration: 250,
recover_duration: 100,
base_damage: 60,
knockback: 0.0,
range: 3.5,
energy_cost: 100,
is_infinite: true,
is_helicopter: true,
is_interruptible: false,
forward_speed: 0.0,
num_spins: 1,
)

View File

@ -0,0 +1,15 @@
BasicRanged(
energy_cost: 0,
buildup_duration: 200,
recover_duration: 300,
projectile: Arrow(
damage: 40.0,
knockback: 10.0,
energy_regen: 50,
),
projectile_body: Object(Arrow),
projectile_light: None,
projectile_gravity: Some(Gravity(0.2)),
projectile_speed: 100.0,
can_continue: true,
)

View File

@ -0,0 +1,17 @@
ChargedRanged(
energy_cost: 0,
energy_drain: 300,
initial_damage: 40,
max_damage: 200,
initial_knockback: 10.0,
max_knockback: 20.0,
speed: 1.0,
buildup_duration: 100,
charge_duration: 1500,
recover_duration: 500,
projectile_body: Object(MultiArrow),
projectile_light: None,
projectile_gravity: Some(Gravity(0.2)),
initial_projectile_speed: 100.0,
max_projectile_speed: 500.0,
)

View File

@ -0,0 +1,18 @@
RepeaterRanged(
energy_cost: 450,
movement_duration: 300,
buildup_duration: 200,
shoot_duration: 200,
recover_duration: 800,
leap: Some(5.0),
projectile: Arrow(
damage: 40.0,
knockback: 10.0,
energy_regen: 0,
),
projectile_body: Object(Arrow),
projectile_light: None,
projectile_gravity: Some(Gravity(0.2)),
projectile_speed: 100.0,
reps_remaining: 5,
)

View File

@ -0,0 +1,10 @@
BasicMelee(
energy_cost: 0,
buildup_duration: 100,
swing_duration: 100,
recover_duration: 300,
base_damage: 50,
knockback: 0.0,
range: 3.5,
max_angle: 20.0,
)

View File

@ -0,0 +1,4 @@
Boost(
movement_duration: 50,
only_up: false,
)

View File

@ -0,0 +1,14 @@
BasicRanged(
energy_cost: 0,
buildup_duration: 0,
recover_duration: 10,
projectile: Possess,
projectile_body: Object(ArrowSnake),
/*projectile_light: Some(LightEmitter {
col: (0.0, 1.0, 0.33).into(),
..Default::default()
}),*/
projectile_gravity: None,
projectile_speed: 100.0,
can_continue: false,
)

View File

@ -0,0 +1,4 @@
Boost(
movement_duration: 50,
only_up: true,
)

View File

@ -0,0 +1,10 @@
BasicMelee(
energy_cost: 0,
buildup_duration: 0,
swing_duration: 100,
recover_duration: 900,
base_damage: 20,
knockback: 0.0,
range: 3.5,
max_angle: 15.0,
)

View File

@ -0,0 +1,10 @@
BasicMelee(
energy_cost: 0,
buildup_duration: 600,
swing_duration: 100,
recover_duration: 150,
base_damage: 50,
knockback: 0.0,
range: 3.5,
max_angle: 20.0,
)

View File

@ -0,0 +1,14 @@
ChargedMelee(
energy_cost: 0,
energy_drain: 300,
initial_damage: 10,
max_damage: 170,
initial_knockback: 10.0,
max_knockback: 60.0,
range: 3.5,
max_angle: 30.0,
speed: 1.0,
charge_duration: 1200,
swing_duration: 200,
recover_duration: 300,
)

View File

@ -0,0 +1,13 @@
LeapMelee(
energy_cost: 700,
buildup_duration: 100,
movement_duration: 800,
swing_duration: 150,
recover_duration: 200,
base_damage: 240,
knockback: 25.0,
range: 4.5,
max_angle: 360.0,
forward_leap_strength: 28.0,
vertical_leap_strength: 8.0,
)

View File

@ -0,0 +1,21 @@
ComboMelee(
stage_data: [(
stage: 1,
base_damage: 120,
max_damage: 150,
damage_increase: 10,
knockback: 0.0,
range: 3.5,
angle: 20.0,
base_buildup_duration: 600,
base_swing_duration: 60,
base_recover_duration: 300,
forward_movement: 0.0,
)],
initial_energy_gain: 0,
max_energy_gain: 100,
energy_increase: 20,
speed_increase: 0.05,
max_speed_increase: 1.4,
is_interruptible: false,
)

View File

@ -0,0 +1,14 @@
BasicBeam(
buildup_duration: 250,
recover_duration: 250,
beam_duration: 1000,
base_hps: 60,
base_dps: 60,
tick_rate: 2.0,
range: 25.0,
max_angle: 1.0,
lifesteal_eff: 0.20,
energy_regen: 50,
energy_cost: 100,
energy_drain: 0,
)

View File

@ -0,0 +1,18 @@
BasicRanged(
energy_cost: 800,
buildup_duration: 800,
recover_duration: 50,
projectile: Heal(
heal: 140.0,
damage: 50.0,
radius: 6.0,
),
projectile_body: Object(BoltNature),
/*projectile_light: Some(LightEmitter {
col: (0.0, 1.0, 0.0).into(),
..Default::default()
}),*/
projectile_gravity: Some(Gravity(0.5)),
projectile_speed: 40.0,
can_continue: false,
)

View File

@ -0,0 +1 @@
BasicBlock

View File

@ -0,0 +1,10 @@
BasicMelee(
energy_cost: 0,
buildup_duration: 100,
swing_duration: 100,
recover_duration: 300,
base_damage: 40,
knockback: 0.0,
range: 3.0,
max_angle: 120.0,
)

View File

@ -0,0 +1,18 @@
BasicRanged(
energy_cost: 0,
buildup_duration: 500,
recover_duration: 350,
projectile: Fireball(
damage: 100.0,
radius: 5.0,
energy_regen: 50,
),
projectile_body: Object(BoltFire),
/*projectile_light: Some(LightEmitter {
col: (1.0, 0.75, 0.11).into(),
..Default::default()
}),*/
projectile_gravity: Some(Gravity(0.3)),
projectile_speed: 60.0,
can_continue: true,
)

View File

@ -0,0 +1,14 @@
Shockwave(
energy_cost: 600,
buildup_duration: 700,
swing_duration: 100,
recover_duration: 300,
damage: 200,
knockback: Away(25.0),
shockwave_angle: 360.0,
shockwave_vertical_angle: 90.0,
shockwave_speed: 20.0,
shockwave_duration: 500,
requires_ground: false,
move_efficiency: 0.1,
)

View File

@ -0,0 +1,14 @@
BasicBeam(
buildup_duration: 250,
recover_duration: 250,
beam_duration: 500,
base_hps: 0,
base_dps: 150,
tick_rate: 3.0,
range: 15.0,
max_angle: 22.5,
lifesteal_eff: 0.0,
energy_regen: 0,
energy_cost: 0,
energy_drain: 350,
)

View File

@ -0,0 +1,17 @@
DashMelee(
energy_cost: 200,
base_damage: 120,
max_damage: 240,
base_knockback: 8.0,
max_knockback: 15.0,
range: 5.0,
angle: 45.0,
energy_drain: 500,
forward_speed: 4.0,
buildup_duration: 250,
charge_duration: 600,
swing_duration: 100,
recover_duration: 500,
infinite_charge: true,
is_interruptible: true,
)

View File

@ -0,0 +1,14 @@
SpinMelee(
buildup_duration: 750,
swing_duration: 500,
recover_duration: 500,
base_damage: 140,
knockback: 10.0,
range: 3.5,
energy_cost: 200,
is_infinite: false,
is_helicopter: false,
is_interruptible: true,
forward_speed: 1.0,
num_spins: 3,
)

View File

@ -0,0 +1,49 @@
ComboMelee(
stage_data: [
(
stage: 1,
base_damage: 100,
max_damage: 120,
damage_increase: 10,
knockback: 10.0,
range: 4.0,
angle: 30.0,
base_buildup_duration: 350,
base_swing_duration: 100,
base_recover_duration: 400,
forward_movement: 0.5,
),
(
stage: 2,
base_damage: 80,
max_damage: 110,
damage_increase: 15,
knockback: 12.0,
range: 3.5,
angle: 180.0,
base_buildup_duration: 400,
base_swing_duration: 600,
base_recover_duration: 400,
forward_movement: 0.0,
),
(
stage: 3,
base_damage: 130,
max_damage: 170,
damage_increase: 20,
knockback: 14.0,
range: 6.0,
angle: 10.0,
base_buildup_duration: 500,
base_swing_duration: 200,
base_recover_duration: 300,
forward_movement: 1.2,
),
],
initial_energy_gain: 0,
max_energy_gain: 100,
energy_increase: 20,
speed_increase: 0.05,
max_speed_increase: 1.8,
is_interruptible: true,
)

View File

@ -0,0 +1,10 @@
BasicMelee(
energy_cost: 0,
buildup_duration: 250,
swing_duration: 250,
recover_duration: 250,
knockback: 25.0,
base_damage: 200,
range: 5.0,
max_angle: 120.0,
)

View File

@ -0,0 +1,10 @@
BasicMelee(
energy_cost: 0,
buildup_duration: 400,
swing_duration: 100,
recover_duration: 250,
knockback: 25.0,
base_damage: 200,
range: 5.0,
max_angle: 120.0,
)

View File

@ -0,0 +1,14 @@
Shockwave(
energy_cost: 0,
buildup_duration: 500,
swing_duration: 200,
recover_duration: 800,
damage: 500,
knockback: TowardsUp(40.0),
shockwave_angle: 90.0,
shockwave_vertical_angle: 15.0,
shockwave_speed: 20.0,
shockwave_duration: 2000,
requires_ground: true,
move_efficiency: 0.05,
)

View File

@ -0,0 +1,81 @@
// Maps a tool kind to a set of abilities
// A set of abilities is a primary, a secondary, and a vec of all extra abilities
({
Sword: (
primary: "common.abilities.sword.triplestrike",
secondary: "common.abilities.sword.dash",
skills: [
"common.abilities.sword.spin",
],
),
Axe: (
primary: "common.abilities.axe.doublestrike",
secondary: "common.abilities.axe.spin",
skills: [
"common.abilities.axe.leap",
],
),
Hammer: (
primary: "common.abilities.hammer.singlestrike",
secondary: "common.abilities.hammer.charged",
skills: [
"common.abilities.hammer.leap",
],
),
Bow: (
primary: "common.abilities.bow.basic",
secondary: "common.abilities.bow.charged",
skills: [
"common.abilities.bow.repeater",
],
),
Staff: (
primary: "common.abilities.staff.firebomb",
secondary: "common.abilities.staff.flamethrower",
skills: [
"common.abilities.staff.fireshockwave",
],
),
Sceptre: (
primary: "common.abilities.sceptre.healingbeam",
secondary: "common.abilities.sceptre.healingbomb",
skills: [],
),
Dagger: (
primary: "common.abilities.dagger.tempbasic",
secondary: "common.abilities.dagger.tempbasic",
skills: [],
),
Shield: (
primary: "common.abilities.shield.tempbasic",
secondary: "common.abilities.shield.block",
skills: [],
),
Unique(StoneGolemFist): (
primary: "common.abilities.unique.stonegolemfist.basic",
secondary: "common.abilities.unique.stonegolemfist.shockwave",
skills: [],
),
Unique(BeastClaws): (
primary: "common.abilities.unique.beastclaws.basic",
secondary: "common.abilities.unique.beastclaws.basic",
skills: [],
),
Debug: (
primary: "common.abilities.debug.forwardboost",
secondary: "common.abilities.debug.upboost",
skills: [
"common.abilities.debug.possess",
],
),
Farming: (
primary: "common.abilities.farming.basic",
secondary: "common.abilities.farming.basic",
skills: [],
),
Empty: (
primary: "common.abilities.empty.basic",
secondary: "common.abilities.empty.basic",
skills: [],
),
})

View File

@ -1,7 +1,9 @@
use crate::{
assets::{self, Asset},
comp::{
item::{armor::Protection, Item, ItemKind},
Body, CharacterState, EnergySource, Gravity, LightEmitter, Projectile, StateUpdate,
item::{armor::Protection, tool::AbilityMap, Item, ItemKind},
projectile::ProjectileConstructor,
Body, CharacterState, EnergySource, Gravity, LightEmitter, StateUpdate,
},
states::{
utils::{AbilityKey, StageSection},
@ -14,7 +16,7 @@ use arraygen::Arraygen;
use serde::{Deserialize, Serialize};
use specs::{Component, FlaggedStorage};
use specs_idvs::IdvStorage;
use std::time::Duration;
use std::{fs::File, io::BufReader, time::Duration};
use vek::Vec3;
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug, Serialize, Deserialize)]
@ -59,9 +61,9 @@ impl From<&CharacterState> for CharacterAbilityType {
pub enum CharacterAbility {
BasicMelee {
energy_cost: u32,
buildup_duration: Duration,
swing_duration: Duration,
recover_duration: Duration,
buildup_duration: u64,
swing_duration: u64,
recover_duration: u64,
base_damage: u32,
knockback: f32,
range: f32,
@ -69,9 +71,9 @@ pub enum CharacterAbility {
},
BasicRanged {
energy_cost: u32,
buildup_duration: Duration,
recover_duration: Duration,
projectile: Projectile,
buildup_duration: u64,
recover_duration: u64,
projectile: ProjectileConstructor,
projectile_body: Body,
projectile_light: Option<LightEmitter>,
projectile_gravity: Option<Gravity>,
@ -80,12 +82,12 @@ pub enum CharacterAbility {
},
RepeaterRanged {
energy_cost: u32,
movement_duration: Duration,
buildup_duration: Duration,
shoot_duration: Duration,
recover_duration: Duration,
movement_duration: u64,
buildup_duration: u64,
shoot_duration: u64,
recover_duration: u64,
leap: Option<f32>,
projectile: Projectile,
projectile: ProjectileConstructor,
projectile_body: Body,
projectile_light: Option<LightEmitter>,
projectile_gravity: Option<Gravity>,
@ -93,7 +95,7 @@ pub enum CharacterAbility {
reps_remaining: u32,
},
Boost {
movement_duration: Duration,
movement_duration: u64,
only_up: bool,
},
DashMelee {
@ -106,23 +108,23 @@ pub enum CharacterAbility {
angle: f32,
energy_drain: u32,
forward_speed: f32,
buildup_duration: Duration,
charge_duration: Duration,
swing_duration: Duration,
recover_duration: Duration,
buildup_duration: u64,
charge_duration: u64,
swing_duration: u64,
recover_duration: u64,
infinite_charge: bool,
is_interruptible: bool,
},
BasicBlock,
Roll {
energy_cost: u32,
buildup_duration: Duration,
movement_duration: Duration,
recover_duration: Duration,
buildup_duration: u64,
movement_duration: u64,
recover_duration: u64,
roll_strength: f32,
},
ComboMelee {
stage_data: Vec<combo_melee::Stage>,
stage_data: Vec<combo_melee::Stage<u64>>,
initial_energy_gain: u32,
max_energy_gain: u32,
energy_increase: u32,
@ -132,10 +134,10 @@ pub enum CharacterAbility {
},
LeapMelee {
energy_cost: u32,
buildup_duration: Duration,
movement_duration: Duration,
swing_duration: Duration,
recover_duration: Duration,
buildup_duration: u64,
movement_duration: u64,
swing_duration: u64,
recover_duration: u64,
base_damage: u32,
range: f32,
max_angle: f32,
@ -144,9 +146,9 @@ pub enum CharacterAbility {
vertical_leap_strength: f32,
},
SpinMelee {
buildup_duration: Duration,
swing_duration: Duration,
recover_duration: Duration,
buildup_duration: u64,
swing_duration: u64,
recover_duration: u64,
base_damage: u32,
knockback: f32,
range: f32,
@ -167,9 +169,9 @@ pub enum CharacterAbility {
range: f32,
max_angle: f32,
speed: f32,
charge_duration: Duration,
swing_duration: Duration,
recover_duration: Duration,
charge_duration: u64,
swing_duration: u64,
recover_duration: u64,
},
ChargedRanged {
energy_cost: u32,
@ -179,9 +181,9 @@ pub enum CharacterAbility {
initial_knockback: f32,
max_knockback: f32,
speed: f32,
buildup_duration: Duration,
charge_duration: Duration,
recover_duration: Duration,
buildup_duration: u64,
charge_duration: u64,
recover_duration: u64,
projectile_body: Body,
projectile_light: Option<LightEmitter>,
projectile_gravity: Option<Gravity>,
@ -190,22 +192,22 @@ pub enum CharacterAbility {
},
Shockwave {
energy_cost: u32,
buildup_duration: Duration,
swing_duration: Duration,
recover_duration: Duration,
buildup_duration: u64,
swing_duration: u64,
recover_duration: u64,
damage: u32,
knockback: Knockback,
shockwave_angle: f32,
shockwave_vertical_angle: f32,
shockwave_speed: f32,
shockwave_duration: Duration,
shockwave_duration: u64,
requires_ground: bool,
move_efficiency: f32,
},
BasicBeam {
buildup_duration: Duration,
recover_duration: Duration,
beam_duration: Duration,
buildup_duration: u64,
recover_duration: u64,
beam_duration: u64,
base_hps: u32,
base_dps: u32,
tick_rate: f32,
@ -218,6 +220,29 @@ pub enum CharacterAbility {
},
}
impl Default for CharacterAbility {
fn default() -> Self {
CharacterAbility::BasicMelee {
energy_cost: 0,
buildup_duration: 250,
swing_duration: 250,
recover_duration: 500,
base_damage: 10,
knockback: 0.0,
range: 3.5,
max_angle: 15.0,
}
}
}
impl Asset for CharacterAbility {
const ENDINGS: &'static [&'static str] = &["ron"];
fn parse(buf_reader: BufReader<File>, _specifier: &str) -> Result<Self, assets::Error> {
ron::de::from_reader(buf_reader).map_err(assets::Error::parse_error)
}
}
impl CharacterAbility {
/// Attempts to fulfill requirements, mutating `update` (taking energy) if
/// applicable.
@ -282,12 +307,178 @@ impl CharacterAbility {
fn default_roll() -> CharacterAbility {
CharacterAbility::Roll {
energy_cost: 100,
buildup_duration: Duration::from_millis(100),
movement_duration: Duration::from_millis(250),
recover_duration: Duration::from_millis(150),
buildup_duration: 100,
movement_duration: 250,
recover_duration: 150,
roll_strength: 2.5,
}
}
pub fn adjusted_by_stats(mut self, power: f32, speed: f32) -> Self {
use CharacterAbility::*;
match self {
BasicMelee {
ref mut buildup_duration,
ref mut swing_duration,
ref mut recover_duration,
ref mut base_damage,
..
} => {
*buildup_duration = (*buildup_duration as f32 / speed) as u64;
*swing_duration = (*swing_duration as f32 / speed) as u64;
*recover_duration = (*recover_duration as f32 / speed) as u64;
*base_damage = (*base_damage as f32 * power) as u32;
},
BasicRanged {
ref mut buildup_duration,
ref mut recover_duration,
ref mut projectile,
..
} => {
*buildup_duration = (*buildup_duration as f32 / speed) as u64;
*recover_duration = (*recover_duration as f32 / speed) as u64;
*projectile = projectile.modified_projectile(power);
},
RepeaterRanged {
ref mut movement_duration,
ref mut buildup_duration,
ref mut shoot_duration,
ref mut recover_duration,
ref mut projectile,
..
} => {
*movement_duration = (*movement_duration as f32 / speed) as u64;
*buildup_duration = (*buildup_duration as f32 / speed) as u64;
*shoot_duration = (*shoot_duration as f32 / speed) as u64;
*recover_duration = (*recover_duration as f32 / speed) as u64;
*projectile = projectile.modified_projectile(power);
},
Boost {
ref mut movement_duration,
..
} => {
*movement_duration = (*movement_duration as f32 / speed) as u64;
},
DashMelee {
ref mut base_damage,
ref mut max_damage,
ref mut buildup_duration,
ref mut swing_duration,
ref mut recover_duration,
..
} => {
*base_damage = (*base_damage as f32 * power) as u32;
*max_damage = (*max_damage as f32 * power) as u32;
*buildup_duration = (*buildup_duration as f32 / speed) as u64;
*swing_duration = (*swing_duration as f32 / speed) as u64;
*recover_duration = (*recover_duration as f32 / speed) as u64;
},
BasicBlock => {},
Roll {
ref mut buildup_duration,
ref mut movement_duration,
ref mut recover_duration,
..
} => {
*buildup_duration = (*buildup_duration as f32 / speed) as u64;
*movement_duration = (*movement_duration as f32 / speed) as u64;
*recover_duration = (*recover_duration as f32 / speed) as u64;
},
ComboMelee {
ref mut stage_data, ..
} => {
*stage_data = stage_data
.iter_mut()
.map(|s| s.adjusted_by_stats(power, speed))
.collect();
},
LeapMelee {
ref mut buildup_duration,
ref mut movement_duration,
ref mut swing_duration,
ref mut recover_duration,
ref mut base_damage,
..
} => {
*buildup_duration = (*buildup_duration as f32 / speed) as u64;
*movement_duration = (*movement_duration as f32 / speed) as u64;
*swing_duration = (*swing_duration as f32 / speed) as u64;
*recover_duration = (*recover_duration as f32 / speed) as u64;
*base_damage = (*base_damage as f32 * power) as u32;
},
SpinMelee {
ref mut buildup_duration,
ref mut swing_duration,
ref mut recover_duration,
ref mut base_damage,
..
} => {
*buildup_duration = (*buildup_duration as f32 / speed) as u64;
*swing_duration = (*swing_duration as f32 / speed) as u64;
*recover_duration = (*recover_duration as f32 / speed) as u64;
*base_damage = (*base_damage as f32 * power) as u32;
},
ChargedMelee {
ref mut initial_damage,
ref mut max_damage,
speed: ref mut ability_speed,
ref mut charge_duration,
ref mut swing_duration,
ref mut recover_duration,
..
} => {
*initial_damage = (*initial_damage as f32 * power) as u32;
*max_damage = (*max_damage as f32 * power) as u32;
*ability_speed *= speed;
*charge_duration = (*charge_duration as f32 / speed) as u64;
*swing_duration = (*swing_duration as f32 / speed) as u64;
*recover_duration = (*recover_duration as f32 / speed) as u64;
},
ChargedRanged {
ref mut initial_damage,
ref mut max_damage,
speed: ref mut ability_speed,
ref mut buildup_duration,
ref mut charge_duration,
ref mut recover_duration,
..
} => {
*initial_damage = (*initial_damage as f32 * power) as u32;
*max_damage = (*max_damage as f32 * power) as u32;
*ability_speed *= speed;
*buildup_duration = (*buildup_duration as f32 / speed) as u64;
*charge_duration = (*charge_duration as f32 / speed) as u64;
*recover_duration = (*recover_duration as f32 / speed) as u64;
},
Shockwave {
ref mut buildup_duration,
ref mut swing_duration,
ref mut recover_duration,
ref mut damage,
..
} => {
*buildup_duration = (*buildup_duration as f32 / speed) as u64;
*swing_duration = (*swing_duration as f32 / speed) as u64;
*recover_duration = (*recover_duration as f32 / speed) as u64;
*damage = (*damage as f32 * power) as u32;
},
BasicBeam {
ref mut buildup_duration,
ref mut recover_duration,
ref mut base_hps,
ref mut base_dps,
ref mut tick_rate,
..
} => {
*buildup_duration = (*buildup_duration as f32 / speed) as u64;
*recover_duration = (*recover_duration as f32 / speed) as u64;
*base_hps = (*base_hps as f32 * power) as u32;
*base_dps = (*base_dps as f32 * power) as u32;
*tick_rate *= speed;
},
}
self
}
}
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
@ -300,17 +491,16 @@ pub struct ItemConfig {
pub dodge_ability: Option<CharacterAbility>,
}
impl From<Item> for ItemConfig {
fn from(item: Item) -> Self {
impl From<(Item, &AbilityMap)> for ItemConfig {
fn from((item, map): (Item, &AbilityMap)) -> Self {
if let ItemKind::Tool(tool) = &item.kind() {
let mut abilities = tool.get_abilities();
let mut ability_drain = abilities.drain(..);
let abilities = tool.get_abilities(map);
return ItemConfig {
item,
ability1: ability_drain.next(),
ability2: ability_drain.next(),
ability3: ability_drain.next(),
ability1: Some(abilities.primary),
ability2: Some(abilities.secondary),
ability3: abilities.skills.get(0).cloned(),
block_ability: None,
dodge_ability: Some(CharacterAbility::default_roll()),
};
@ -392,9 +582,9 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
energy_cost: _,
} => CharacterState::BasicMelee(basic_melee::Data {
static_data: basic_melee::StaticData {
buildup_duration: *buildup_duration,
swing_duration: *swing_duration,
recover_duration: *recover_duration,
buildup_duration: Duration::from_millis(*buildup_duration),
swing_duration: Duration::from_millis(*swing_duration),
recover_duration: Duration::from_millis(*recover_duration),
base_damage: *base_damage,
knockback: *knockback,
range: *range,
@ -416,9 +606,9 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
energy_cost: _,
} => CharacterState::BasicRanged(basic_ranged::Data {
static_data: basic_ranged::StaticData {
buildup_duration: *buildup_duration,
recover_duration: *recover_duration,
projectile: projectile.clone(),
buildup_duration: Duration::from_millis(*buildup_duration),
recover_duration: Duration::from_millis(*recover_duration),
projectile: *projectile,
projectile_body: *projectile_body,
projectile_light: *projectile_light,
projectile_gravity: *projectile_gravity,
@ -436,7 +626,7 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
only_up,
} => CharacterState::Boost(boost::Data {
static_data: boost::StaticData {
movement_duration: *movement_duration,
movement_duration: Duration::from_millis(*movement_duration),
only_up: *only_up,
},
timer: Duration::default(),
@ -468,10 +658,10 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
energy_drain: *energy_drain,
forward_speed: *forward_speed,
infinite_charge: *infinite_charge,
buildup_duration: *buildup_duration,
charge_duration: *charge_duration,
swing_duration: *swing_duration,
recover_duration: *recover_duration,
buildup_duration: Duration::from_millis(*buildup_duration),
charge_duration: Duration::from_millis(*charge_duration),
swing_duration: Duration::from_millis(*swing_duration),
recover_duration: Duration::from_millis(*recover_duration),
is_interruptible: *is_interruptible,
ability_key: key,
},
@ -490,9 +680,9 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
roll_strength,
} => CharacterState::Roll(roll::Data {
static_data: roll::StaticData {
buildup_duration: *buildup_duration,
movement_duration: *movement_duration,
recover_duration: *recover_duration,
buildup_duration: Duration::from_millis(*buildup_duration),
movement_duration: Duration::from_millis(*movement_duration),
recover_duration: Duration::from_millis(*recover_duration),
roll_strength: *roll_strength,
},
timer: Duration::default(),
@ -511,7 +701,7 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
} => CharacterState::ComboMelee(combo_melee::Data {
static_data: combo_melee::StaticData {
num_stages: stage_data.len() as u32,
stage_data: stage_data.clone(),
stage_data: stage_data.iter().map(|stage| stage.to_duration()).collect(),
initial_energy_gain: *initial_energy_gain,
max_energy_gain: *max_energy_gain,
energy_increase: *energy_increase,
@ -540,10 +730,10 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
vertical_leap_strength,
} => CharacterState::LeapMelee(leap_melee::Data {
static_data: leap_melee::StaticData {
buildup_duration: *buildup_duration,
movement_duration: *movement_duration,
swing_duration: *swing_duration,
recover_duration: *recover_duration,
buildup_duration: Duration::from_millis(*buildup_duration),
movement_duration: Duration::from_millis(*movement_duration),
swing_duration: Duration::from_millis(*swing_duration),
recover_duration: Duration::from_millis(*recover_duration),
base_damage: *base_damage,
knockback: *knockback,
range: *range,
@ -570,9 +760,9 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
num_spins,
} => CharacterState::SpinMelee(spin_melee::Data {
static_data: spin_melee::StaticData {
buildup_duration: *buildup_duration,
swing_duration: *swing_duration,
recover_duration: *recover_duration,
buildup_duration: Duration::from_millis(*buildup_duration),
swing_duration: Duration::from_millis(*swing_duration),
recover_duration: Duration::from_millis(*recover_duration),
base_damage: *base_damage,
knockback: *knockback,
range: *range,
@ -613,9 +803,9 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
speed: *speed,
range: *range,
max_angle: *max_angle,
charge_duration: *charge_duration,
swing_duration: *swing_duration,
recover_duration: *recover_duration,
charge_duration: Duration::from_millis(*charge_duration),
swing_duration: Duration::from_millis(*swing_duration),
recover_duration: Duration::from_millis(*recover_duration),
ability_key: key,
},
stage_section: StageSection::Charge,
@ -641,9 +831,9 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
max_projectile_speed,
} => CharacterState::ChargedRanged(charged_ranged::Data {
static_data: charged_ranged::StaticData {
buildup_duration: *buildup_duration,
charge_duration: *charge_duration,
recover_duration: *recover_duration,
buildup_duration: Duration::from_millis(*buildup_duration),
charge_duration: Duration::from_millis(*charge_duration),
recover_duration: Duration::from_millis(*recover_duration),
energy_drain: *energy_drain,
initial_damage: *initial_damage,
max_damage: *max_damage,
@ -676,12 +866,12 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
reps_remaining,
} => CharacterState::RepeaterRanged(repeater_ranged::Data {
static_data: repeater_ranged::StaticData {
movement_duration: *movement_duration,
buildup_duration: *buildup_duration,
shoot_duration: *shoot_duration,
recover_duration: *recover_duration,
movement_duration: Duration::from_millis(*movement_duration),
buildup_duration: Duration::from_millis(*buildup_duration),
shoot_duration: Duration::from_millis(*shoot_duration),
recover_duration: Duration::from_millis(*recover_duration),
leap: *leap,
projectile: projectile.clone(),
projectile: *projectile,
projectile_body: *projectile_body,
projectile_light: *projectile_light,
projectile_gravity: *projectile_gravity,
@ -706,15 +896,15 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
move_efficiency,
} => CharacterState::Shockwave(shockwave::Data {
static_data: shockwave::StaticData {
buildup_duration: *buildup_duration,
swing_duration: *swing_duration,
recover_duration: *recover_duration,
buildup_duration: Duration::from_millis(*buildup_duration),
swing_duration: Duration::from_millis(*swing_duration),
recover_duration: Duration::from_millis(*recover_duration),
damage: *damage,
knockback: *knockback,
shockwave_angle: *shockwave_angle,
shockwave_vertical_angle: *shockwave_vertical_angle,
shockwave_speed: *shockwave_speed,
shockwave_duration: *shockwave_duration,
shockwave_duration: Duration::from_millis(*shockwave_duration),
requires_ground: *requires_ground,
move_efficiency: *move_efficiency,
},
@ -736,9 +926,9 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState {
energy_drain,
} => CharacterState::BasicBeam(basic_beam::Data {
static_data: basic_beam::StaticData {
buildup_duration: *buildup_duration,
recover_duration: *recover_duration,
beam_duration: *beam_duration,
buildup_duration: Duration::from_millis(*buildup_duration),
recover_duration: Duration::from_millis(*recover_duration),
beam_duration: Duration::from_millis(*beam_duration),
base_hps: *base_hps,
base_dps: *base_dps,
tick_rate: *tick_rate,

View File

@ -2,7 +2,7 @@ pub mod armor;
pub mod tool;
// Reexports
pub use tool::{Hands, Tool, ToolKind, UniqueKind};
pub use tool::{AbilitySet, Hands, Tool, ToolKind, UniqueKind};
use crate::{
assets::{self, Asset, Error},

View File

@ -2,17 +2,12 @@
// version in voxygen\src\meta.rs in order to reset save files to being empty
use crate::{
comp::{
body::object,
buff::{Buff, BuffCategory, BuffData, BuffKind, BuffSource},
projectile, Body, CharacterAbility, Gravity, LightEmitter, Projectile,
},
effect::Effect,
states::combo_melee,
Damage, DamageSource, Explosion, GroupTarget, Knockback, RadiusEffect,
assets::{self, Asset},
comp::CharacterAbility,
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
use std::{collections::HashMap, fs::File, io::BufReader, time::Duration};
use tracing::error;
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ToolKind {
@ -90,578 +85,89 @@ impl Tool {
Duration::from_millis(self.stats.equip_time_millis as u64)
}
/// Converts milliseconds to a `Duration` adjusted by `base_speed()`
pub fn adjusted_duration(&self, millis: u64) -> Duration {
Duration::from_millis(millis).div_f32(self.base_speed())
}
pub fn get_abilities(&self) -> Vec<CharacterAbility> {
use CharacterAbility::*;
use ToolKind::*;
use UniqueKind::*;
match &self.kind {
Sword => vec![
ComboMelee {
stage_data: vec![
combo_melee::Stage {
stage: 1,
base_damage: (100.0 * self.base_power()) as u32,
max_damage: (120.0 * self.base_power()) as u32,
damage_increase: (10.0 * self.base_power()) as u32,
knockback: 10.0,
range: 4.0,
angle: 30.0,
base_buildup_duration: self.adjusted_duration(350),
base_swing_duration: self.adjusted_duration(100),
base_recover_duration: self.adjusted_duration(400),
forward_movement: 0.5,
},
combo_melee::Stage {
stage: 2,
base_damage: (80.0 * self.base_power()) as u32,
max_damage: (110.0 * self.base_power()) as u32,
damage_increase: (15.0 * self.base_power()) as u32,
knockback: 12.0,
range: 3.5,
angle: 180.0,
base_buildup_duration: self.adjusted_duration(400),
base_swing_duration: self.adjusted_duration(600),
base_recover_duration: self.adjusted_duration(400),
forward_movement: 0.0,
},
combo_melee::Stage {
stage: 3,
base_damage: (130.0 * self.base_power()) as u32,
max_damage: (170.0 * self.base_power()) as u32,
damage_increase: (20.0 * self.base_power()) as u32,
knockback: 14.0,
range: 6.0,
angle: 10.0,
base_buildup_duration: self.adjusted_duration(500),
base_swing_duration: self.adjusted_duration(200),
base_recover_duration: self.adjusted_duration(300),
forward_movement: 1.2,
},
],
initial_energy_gain: 0,
max_energy_gain: 100,
energy_increase: 20,
speed_increase: 0.05,
max_speed_increase: 1.8,
is_interruptible: true,
},
DashMelee {
energy_cost: 200,
base_damage: (120.0 * self.base_power()) as u32,
max_damage: (240.0 * self.base_power()) as u32,
base_knockback: 8.0,
max_knockback: 15.0,
range: 5.0,
angle: 45.0,
energy_drain: 500,
forward_speed: 4.0,
buildup_duration: self.adjusted_duration(250),
charge_duration: Duration::from_millis(600),
swing_duration: self.adjusted_duration(100),
recover_duration: self.adjusted_duration(500),
infinite_charge: true,
is_interruptible: true,
},
SpinMelee {
buildup_duration: self.adjusted_duration(750),
swing_duration: self.adjusted_duration(500),
recover_duration: self.adjusted_duration(500),
base_damage: (140.0 * self.base_power()) as u32,
knockback: 10.0,
range: 3.5,
energy_cost: 200,
is_infinite: false,
is_helicopter: false,
is_interruptible: true,
forward_speed: 1.0,
num_spins: 3,
},
],
Axe => vec![
ComboMelee {
stage_data: vec![
combo_melee::Stage {
stage: 1,
base_damage: (90.0 * self.base_power()) as u32,
max_damage: (110.0 * self.base_power()) as u32,
damage_increase: (10.0 * self.base_power()) as u32,
knockback: 8.0,
range: 3.5,
angle: 50.0,
base_buildup_duration: self.adjusted_duration(350),
base_swing_duration: self.adjusted_duration(75),
base_recover_duration: self.adjusted_duration(400),
forward_movement: 0.5,
},
combo_melee::Stage {
stage: 2,
base_damage: (130.0 * self.base_power()) as u32,
max_damage: (160.0 * self.base_power()) as u32,
damage_increase: (15.0 * self.base_power()) as u32,
knockback: 12.0,
range: 3.5,
angle: 30.0,
base_buildup_duration: self.adjusted_duration(500),
base_swing_duration: self.adjusted_duration(100),
base_recover_duration: self.adjusted_duration(500),
forward_movement: 0.25,
},
],
initial_energy_gain: 0,
max_energy_gain: 100,
energy_increase: 20,
speed_increase: 0.05,
max_speed_increase: 1.6,
is_interruptible: false,
},
SpinMelee {
buildup_duration: self.adjusted_duration(100),
swing_duration: self.adjusted_duration(250),
recover_duration: self.adjusted_duration(100),
base_damage: (60.0 * self.base_power()) as u32,
knockback: 0.0,
range: 3.5,
energy_cost: 100,
is_infinite: true,
is_helicopter: true,
is_interruptible: false,
forward_speed: 0.0,
num_spins: 1,
},
LeapMelee {
energy_cost: 450,
buildup_duration: self.adjusted_duration(200),
movement_duration: Duration::from_millis(200),
swing_duration: self.adjusted_duration(200),
recover_duration: self.adjusted_duration(200),
base_damage: (240.0 * self.base_power()) as u32,
knockback: 12.0,
range: 4.5,
max_angle: 30.0,
forward_leap_strength: 28.0,
vertical_leap_strength: 8.0,
},
],
Hammer => vec![
ComboMelee {
stage_data: vec![combo_melee::Stage {
stage: 1,
base_damage: (120.0 * self.base_power()) as u32,
max_damage: (150.0 * self.base_power()) as u32,
damage_increase: (10.0 * self.base_power()) as u32,
knockback: 0.0,
range: 3.5,
angle: 20.0,
base_buildup_duration: self.adjusted_duration(600),
base_swing_duration: self.adjusted_duration(60),
base_recover_duration: self.adjusted_duration(300),
forward_movement: 0.0,
}],
initial_energy_gain: 0,
max_energy_gain: 100,
energy_increase: 20,
speed_increase: 0.05,
max_speed_increase: 1.4,
is_interruptible: false,
},
ChargedMelee {
energy_cost: 1,
energy_drain: 300,
initial_damage: (10.0 * self.base_power()) as u32,
max_damage: (170.0 * self.base_power()) as u32,
initial_knockback: 10.0,
max_knockback: 60.0,
range: 3.5,
max_angle: 30.0,
speed: self.base_speed(),
charge_duration: Duration::from_millis(1200),
swing_duration: self.adjusted_duration(200),
recover_duration: self.adjusted_duration(300),
},
LeapMelee {
energy_cost: 700,
buildup_duration: self.adjusted_duration(100),
movement_duration: Duration::from_millis(800),
swing_duration: self.adjusted_duration(150),
recover_duration: self.adjusted_duration(200),
base_damage: (240.0 * self.base_power()) as u32,
knockback: 25.0,
range: 4.5,
max_angle: 360.0,
forward_leap_strength: 28.0,
vertical_leap_strength: 8.0,
},
],
Farming => vec![BasicMelee {
energy_cost: 1,
buildup_duration: self.adjusted_duration(600),
swing_duration: self.adjusted_duration(100),
recover_duration: self.adjusted_duration(150),
base_damage: (50.0 * self.base_power()) as u32,
knockback: 0.0,
range: 3.5,
max_angle: 20.0,
}],
Bow => vec![
BasicRanged {
energy_cost: 0,
buildup_duration: self.adjusted_duration(200),
recover_duration: self.adjusted_duration(300),
projectile: Projectile {
hit_solid: vec![projectile::Effect::Stick],
hit_entity: vec![
projectile::Effect::Damage(Some(GroupTarget::OutOfGroup), Damage {
source: DamageSource::Projectile,
value: 40.0 * self.base_power(),
}),
projectile::Effect::Knockback(Knockback::Away(10.0)),
projectile::Effect::RewardEnergy(50),
projectile::Effect::Vanish,
projectile::Effect::Buff {
buff: Buff::new(
BuffKind::Bleeding,
BuffData {
strength: 20.0 * self.base_power(),
duration: Some(Duration::from_secs(5)),
},
vec![BuffCategory::Physical],
BuffSource::Unknown,
),
chance: Some(0.10),
},
],
time_left: Duration::from_secs(15),
owner: None,
ignore_group: true,
},
projectile_body: Body::Object(object::Body::Arrow),
projectile_light: None,
projectile_gravity: Some(Gravity(0.2)),
projectile_speed: 100.0,
can_continue: true,
},
ChargedRanged {
energy_cost: 0,
energy_drain: 300,
initial_damage: (40.0 * self.base_power()) as u32,
max_damage: (200.0 * self.base_power()) as u32,
initial_knockback: 10.0,
max_knockback: 20.0,
speed: self.base_speed(),
buildup_duration: self.adjusted_duration(100),
charge_duration: Duration::from_millis(1500),
recover_duration: self.adjusted_duration(500),
projectile_body: Body::Object(object::Body::MultiArrow),
projectile_light: None,
projectile_gravity: Some(Gravity(0.2)),
initial_projectile_speed: 100.0,
max_projectile_speed: 500.0,
},
RepeaterRanged {
energy_cost: 450,
movement_duration: Duration::from_millis(300),
buildup_duration: self.adjusted_duration(200),
shoot_duration: self.adjusted_duration(200),
recover_duration: self.adjusted_duration(800),
leap: Some(5.0),
projectile: Projectile {
hit_solid: vec![projectile::Effect::Stick],
hit_entity: vec![
projectile::Effect::Damage(Some(GroupTarget::OutOfGroup), Damage {
source: DamageSource::Projectile,
value: 40.0 * self.base_power(),
}),
projectile::Effect::Knockback(Knockback::Away(10.0)),
projectile::Effect::Vanish,
projectile::Effect::Buff {
buff: Buff::new(
BuffKind::Bleeding,
BuffData {
strength: 20.0 * self.base_power(),
duration: Some(Duration::from_secs(5)),
},
vec![BuffCategory::Physical],
BuffSource::Unknown,
),
chance: Some(0.10),
},
],
time_left: Duration::from_secs(15),
owner: None,
ignore_group: true,
},
projectile_body: Body::Object(object::Body::Arrow),
projectile_light: None,
projectile_gravity: Some(Gravity(0.2)),
projectile_speed: 100.0,
reps_remaining: 5,
},
],
Dagger => vec![BasicMelee {
energy_cost: 0,
buildup_duration: self.adjusted_duration(100),
swing_duration: self.adjusted_duration(100),
recover_duration: self.adjusted_duration(300),
base_damage: (50.0 * self.base_power()) as u32,
knockback: 0.0,
range: 3.5,
max_angle: 20.0,
}],
Sceptre => vec![
BasicBeam {
buildup_duration: self.adjusted_duration(250),
recover_duration: self.adjusted_duration(250),
beam_duration: Duration::from_secs(1),
base_hps: (60.0 * self.base_power()) as u32,
base_dps: (60.0 * self.base_power()) as u32,
tick_rate: 2.0 * self.base_speed(),
range: 25.0,
max_angle: 1.0,
lifesteal_eff: 0.20,
energy_regen: 50,
energy_cost: 100,
energy_drain: 0,
},
BasicRanged {
energy_cost: 800,
buildup_duration: self.adjusted_duration(800),
recover_duration: self.adjusted_duration(50),
projectile: Projectile {
hit_solid: vec![
projectile::Effect::Explode(Explosion {
effects: vec![
RadiusEffect::Entity(
Some(GroupTarget::OutOfGroup),
Effect::Damage(Damage {
source: DamageSource::Explosion,
value: 50.0 * self.base_power(),
}),
),
RadiusEffect::Entity(
Some(GroupTarget::InGroup),
Effect::Damage(Damage {
source: DamageSource::Healing,
value: 140.0 * self.base_power(),
}),
),
],
radius: 3.0 + 2.5 * self.base_power(),
energy_regen: 0,
}),
projectile::Effect::Vanish,
],
hit_entity: vec![
projectile::Effect::Explode(Explosion {
effects: vec![
RadiusEffect::Entity(
Some(GroupTarget::OutOfGroup),
Effect::Damage(Damage {
source: DamageSource::Explosion,
value: 50.0 * self.base_power(),
}),
),
RadiusEffect::Entity(
Some(GroupTarget::InGroup),
Effect::Damage(Damage {
source: DamageSource::Healing,
value: 140.0 * self.base_power(),
}),
),
],
radius: 3.0 + 2.5 * self.base_power(),
energy_regen: 0,
}),
projectile::Effect::Vanish,
],
time_left: Duration::from_secs(20),
owner: None,
ignore_group: true,
},
projectile_body: Body::Object(object::Body::BoltNature),
projectile_light: Some(LightEmitter {
col: (0.0, 1.0, 0.0).into(),
..Default::default()
}),
projectile_gravity: Some(Gravity(0.5)),
projectile_speed: 40.0,
can_continue: false,
},
],
Staff => vec![
BasicRanged {
energy_cost: 0,
buildup_duration: self.adjusted_duration(500),
recover_duration: self.adjusted_duration(350),
projectile: Projectile {
hit_solid: vec![
projectile::Effect::Explode(Explosion {
effects: vec![RadiusEffect::Entity(
Some(GroupTarget::OutOfGroup),
Effect::Damage(Damage {
source: DamageSource::Explosion,
value: 100.0 * self.base_power(),
}),
)],
radius: 5.0,
energy_regen: 50,
}),
projectile::Effect::Vanish,
],
hit_entity: vec![
projectile::Effect::Explode(Explosion {
effects: vec![RadiusEffect::Entity(
Some(GroupTarget::OutOfGroup),
Effect::Damage(Damage {
source: DamageSource::Explosion,
value: 100.0 * self.base_power(),
}),
)],
radius: 5.0,
energy_regen: 50,
}),
projectile::Effect::Vanish,
],
time_left: Duration::from_secs(20),
owner: None,
ignore_group: true,
},
projectile_body: Body::Object(object::Body::BoltFire),
projectile_light: Some(LightEmitter {
col: (1.0, 0.75, 0.11).into(),
..Default::default()
}),
projectile_gravity: Some(Gravity(0.3)),
projectile_speed: 60.0,
can_continue: true,
},
BasicBeam {
buildup_duration: self.adjusted_duration(250),
recover_duration: self.adjusted_duration(250),
beam_duration: self.adjusted_duration(500),
base_hps: 0,
base_dps: (150.0 * self.base_power()) as u32,
tick_rate: 3.0 * self.base_speed(),
range: 15.0,
max_angle: 22.5,
lifesteal_eff: 0.0,
energy_regen: 0,
energy_cost: 0,
energy_drain: 350,
},
Shockwave {
energy_cost: 600,
buildup_duration: self.adjusted_duration(700),
swing_duration: self.adjusted_duration(100),
recover_duration: self.adjusted_duration(300),
damage: (200.0 * self.base_power()) as u32,
knockback: Knockback::Away(25.0),
shockwave_angle: 360.0,
shockwave_vertical_angle: 90.0,
shockwave_speed: 20.0,
shockwave_duration: Duration::from_millis(500),
requires_ground: false,
move_efficiency: 0.1,
},
],
Shield => vec![
BasicMelee {
energy_cost: 0,
buildup_duration: self.adjusted_duration(100),
swing_duration: self.adjusted_duration(100),
recover_duration: self.adjusted_duration(300),
base_damage: (40.0 * self.base_power()) as u32,
knockback: 0.0,
range: 3.0,
max_angle: 120.0,
},
BasicBlock,
],
Unique(StoneGolemFist) => vec![
BasicMelee {
energy_cost: 0,
buildup_duration: self.adjusted_duration(400),
swing_duration: self.adjusted_duration(100),
recover_duration: self.adjusted_duration(250),
knockback: 25.0,
base_damage: 200,
range: 5.0,
max_angle: 120.0,
},
Shockwave {
energy_cost: 0,
buildup_duration: self.adjusted_duration(500),
swing_duration: self.adjusted_duration(200),
recover_duration: self.adjusted_duration(800),
damage: 500,
knockback: Knockback::TowardsUp(40.0),
shockwave_angle: 90.0,
shockwave_vertical_angle: 15.0,
shockwave_speed: 20.0,
shockwave_duration: Duration::from_millis(2000),
requires_ground: true,
move_efficiency: 0.05,
},
],
Unique(BeastClaws) => vec![BasicMelee {
energy_cost: 0,
buildup_duration: self.adjusted_duration(250),
swing_duration: self.adjusted_duration(250),
recover_duration: self.adjusted_duration(250),
knockback: 25.0,
base_damage: 200,
range: 5.0,
max_angle: 120.0,
}],
Debug => vec![
CharacterAbility::Boost {
movement_duration: Duration::from_millis(50),
only_up: false,
},
CharacterAbility::Boost {
movement_duration: Duration::from_millis(50),
only_up: true,
},
BasicRanged {
energy_cost: 0,
buildup_duration: Duration::from_millis(0),
recover_duration: self.adjusted_duration(10),
projectile: Projectile {
hit_solid: vec![projectile::Effect::Stick],
hit_entity: vec![projectile::Effect::Stick, projectile::Effect::Possess],
time_left: Duration::from_secs(10),
owner: None,
ignore_group: false,
},
projectile_body: Body::Object(object::Body::ArrowSnake),
projectile_light: Some(LightEmitter {
col: (0.0, 1.0, 0.33).into(),
..Default::default()
}),
projectile_gravity: None,
projectile_speed: 100.0,
can_continue: false,
},
],
Empty => vec![BasicMelee {
energy_cost: 0,
buildup_duration: Duration::from_millis(0),
swing_duration: self.adjusted_duration(100),
recover_duration: self.adjusted_duration(900),
base_damage: 20,
knockback: 0.0,
range: 3.5,
max_angle: 15.0,
}],
pub fn get_abilities(&self, map: &AbilityMap) -> AbilitySet<CharacterAbility> {
if let Some(set) = map.0.get(&self.kind).cloned() {
set.modified_by_tool(&self)
} else {
error!(
"ToolKind: {:?} has no AbilitySet in the ability map falling back to default",
&self.kind
);
Default::default()
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AbilitySet<T> {
pub primary: T,
pub secondary: T,
pub skills: Vec<T>,
}
impl AbilitySet<CharacterAbility> {
pub fn modified_by_tool(self, tool: &Tool) -> Self {
self.map(|a| a.adjusted_by_stats(tool.base_power(), tool.base_speed()))
}
}
impl<T> AbilitySet<T> {
pub fn map<U, F: FnMut(T) -> U>(self, mut f: F) -> AbilitySet<U> {
AbilitySet {
primary: f(self.primary),
secondary: f(self.secondary),
skills: self.skills.into_iter().map(|x| f(x)).collect(),
}
}
}
impl Default for AbilitySet<CharacterAbility> {
fn default() -> Self {
AbilitySet {
primary: CharacterAbility::default(),
secondary: CharacterAbility::default(),
skills: vec![],
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AbilityMap<T = CharacterAbility>(HashMap<ToolKind, AbilitySet<T>>);
impl Asset for AbilityMap {
const ENDINGS: &'static [&'static str] = &["ron"];
fn parse(buf_reader: BufReader<File>, specifier: &str) -> Result<Self, assets::Error> {
ron::de::from_reader::<BufReader<File>, AbilityMap<String>>(buf_reader)
.map(|map| {
AbilityMap(
map.0
.into_iter()
.map(|(kind, set)| {
(
kind,
set.map(|s| match CharacterAbility::load(&s) {
Ok(ability) => ability.as_ref().clone(),
Err(err) => {
error!(
?err,
"Error loading CharacterAbility: {} for the ability \
map: {} replacing with default",
s,
specifier
);
CharacterAbility::default()
},
}),
)
})
.collect(),
)
})
.map_err(assets::Error::parse_error)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum UniqueKind {
StoneGolemFist,

View File

@ -1,6 +1,9 @@
use crate::{
comp,
comp::{item, item::armor, ItemConfig},
comp::{
item::{self, armor, tool::AbilityMap},
ItemConfig,
},
};
use comp::{Inventory, Loadout};
use serde::{Deserialize, Serialize};
@ -90,6 +93,7 @@ fn loadout_replace(
equip_slot: EquipSlot,
item: Option<item::Item>,
loadout: &mut Loadout,
map: &AbilityMap,
) -> Option<item::Item> {
use std::mem::replace;
match equip_slot {
@ -106,12 +110,16 @@ fn loadout_replace(
EquipSlot::Armor(ArmorSlot::Tabard) => replace(&mut loadout.tabard, item),
EquipSlot::Lantern => replace(&mut loadout.lantern, item),
EquipSlot::Glider => replace(&mut loadout.glider, item),
EquipSlot::Mainhand => {
replace(&mut loadout.active_item, item.map(ItemConfig::from)).map(|i| i.item)
},
EquipSlot::Offhand => {
replace(&mut loadout.second_item, item.map(ItemConfig::from)).map(|i| i.item)
},
EquipSlot::Mainhand => replace(
&mut loadout.active_item,
item.map(|item| ItemConfig::from((item, map))),
)
.map(|i| i.item),
EquipSlot::Offhand => replace(
&mut loadout.second_item,
item.map(|item| ItemConfig::from((item, map))),
)
.map(|i| i.item),
}
}
@ -122,15 +130,18 @@ fn loadout_insert(
equip_slot: EquipSlot,
item: item::Item,
loadout: &mut Loadout,
map: &AbilityMap,
) -> Option<item::Item> {
loadout_replace(equip_slot, Some(item), loadout)
loadout_replace(equip_slot, Some(item), loadout, map)
}
/// Remove an item from a loadout.
///
/// ```
/// use veloren_common::{
/// assets::Asset,
/// comp::{
/// item::tool::AbilityMap,
/// slot::{loadout_remove, EquipSlot},
/// Inventory,
/// },
@ -139,20 +150,27 @@ fn loadout_insert(
///
/// let mut inv = Inventory::new_empty();
///
/// let map = AbilityMap::load_expect_cloned("common.abilities.weapon_ability_manifest");
///
/// let mut loadout = LoadoutBuilder::new()
/// .defaults()
/// .active_item(Some(LoadoutBuilder::default_item_config_from_str(
/// "common.items.weapons.sword.zweihander_sword_0",
/// &map,
/// )))
/// .build();
///
/// let slot = EquipSlot::Mainhand;
///
/// loadout_remove(slot, &mut loadout);
/// loadout_remove(slot, &mut loadout, &map);
/// assert_eq!(None, loadout.active_item);
/// ```
pub fn loadout_remove(equip_slot: EquipSlot, loadout: &mut Loadout) -> Option<item::Item> {
loadout_replace(equip_slot, None, loadout)
pub fn loadout_remove(
equip_slot: EquipSlot,
loadout: &mut Loadout,
map: &AbilityMap,
) -> Option<item::Item> {
loadout_replace(equip_slot, None, loadout, map)
}
/// Swap item in an inventory slot with one in a loadout slot.
@ -161,6 +179,7 @@ fn swap_inventory_loadout(
equip_slot: EquipSlot,
inventory: &mut Inventory,
loadout: &mut Loadout,
map: &AbilityMap,
) {
// Check if loadout slot can hold item
if inventory
@ -168,7 +187,7 @@ fn swap_inventory_loadout(
.map_or(true, |item| equip_slot.can_hold(&item.kind()))
{
// Take item from loadout
let from_equip = loadout_remove(equip_slot, loadout);
let from_equip = loadout_remove(equip_slot, loadout, map);
// Swap with item in the inventory
let from_inv = if let Some(item) = from_equip {
// If this fails and we get item back as an err it will just be put back in the
@ -179,14 +198,14 @@ fn swap_inventory_loadout(
};
// Put item from the inventory in loadout
if let Some(item) = from_inv {
loadout_insert(equip_slot, item, loadout).unwrap_none(); // Can never fail
loadout_insert(equip_slot, item, loadout, map).unwrap_none(); // Can never fail
}
}
}
/// Swap items in loadout. Does nothing if items are not compatible with their
/// new slots.
fn swap_loadout(slot_a: EquipSlot, slot_b: EquipSlot, loadout: &mut Loadout) {
fn swap_loadout(slot_a: EquipSlot, slot_b: EquipSlot, loadout: &mut Loadout, map: &AbilityMap) {
// Ensure that the slots are not the same
if slot_a == slot_b {
warn!("Tried to swap equip slot with itself");
@ -194,19 +213,19 @@ fn swap_loadout(slot_a: EquipSlot, slot_b: EquipSlot, loadout: &mut Loadout) {
}
// Get items from the slots
let item_a = loadout_remove(slot_a, loadout);
let item_b = loadout_remove(slot_b, loadout);
let item_a = loadout_remove(slot_a, loadout, map);
let item_b = loadout_remove(slot_b, loadout, map);
// Check if items can go in the other slots
if item_a.as_ref().map_or(true, |i| slot_b.can_hold(&i.kind()))
&& item_b.as_ref().map_or(true, |i| slot_a.can_hold(&i.kind()))
{
// Swap
loadout_replace(slot_b, item_a, loadout).unwrap_none();
loadout_replace(slot_a, item_b, loadout).unwrap_none();
loadout_replace(slot_b, item_a, loadout, map).unwrap_none();
loadout_replace(slot_a, item_b, loadout, map).unwrap_none();
} else {
// Otherwise put the items back
loadout_replace(slot_a, item_a, loadout).unwrap_none();
loadout_replace(slot_b, item_b, loadout).unwrap_none();
loadout_replace(slot_a, item_a, loadout, map).unwrap_none();
loadout_replace(slot_b, item_b, loadout, map).unwrap_none();
}
}
@ -219,6 +238,7 @@ pub fn swap(
slot_b: Slot,
inventory: Option<&mut Inventory>,
loadout: Option<&mut Loadout>,
map: &AbilityMap,
) {
match (slot_a, slot_b) {
(Slot::Inventory(slot_a), Slot::Inventory(slot_b)) => {
@ -227,12 +247,12 @@ pub fn swap(
(Slot::Inventory(inv_slot), Slot::Equip(equip_slot))
| (Slot::Equip(equip_slot), Slot::Inventory(inv_slot)) => {
if let Some((inventory, loadout)) = loadout.and_then(|l| inventory.map(|i| (i, l))) {
swap_inventory_loadout(inv_slot, equip_slot, inventory, loadout);
swap_inventory_loadout(inv_slot, equip_slot, inventory, loadout, map);
}
},
(Slot::Equip(slot_a), Slot::Equip(slot_b)) => {
loadout.map(|l| swap_loadout(slot_a, slot_b, l));
loadout.map(|l| swap_loadout(slot_a, slot_b, l, map));
},
}
}
@ -245,6 +265,7 @@ pub fn swap(
/// use veloren_common::{
/// assets::Asset,
/// comp::{
/// item::tool::AbilityMap,
/// slot::{equip, EquipSlot},
/// Inventory, Item,
/// },
@ -258,10 +279,12 @@ pub fn swap(
///
/// let mut loadout = LoadoutBuilder::new().defaults().build();
///
/// equip(0, &mut inv, &mut loadout);
/// let map = AbilityMap::load_expect_cloned("common.abilities.weapon_ability_manifest");
///
/// equip(0, &mut inv, &mut loadout, &map);
/// assert_eq!(Some(boots), loadout.foot);
/// ```
pub fn equip(slot: usize, inventory: &mut Inventory, loadout: &mut Loadout) {
pub fn equip(slot: usize, inventory: &mut Inventory, loadout: &mut Loadout, map: &AbilityMap) {
use armor::Armor;
use item::{armor::ArmorKind, ItemKind};
@ -289,10 +312,10 @@ pub fn equip(slot: usize, inventory: &mut Inventory, loadout: &mut Loadout) {
// If item is going to mainhand, put mainhand in offhand and place offhand in
// inventory
if let EquipSlot::Mainhand = equip_slot {
swap_loadout(EquipSlot::Mainhand, EquipSlot::Offhand, loadout);
swap_loadout(EquipSlot::Mainhand, EquipSlot::Offhand, loadout, map);
}
swap_inventory_loadout(slot, equip_slot, inventory, loadout);
swap_inventory_loadout(slot, equip_slot, inventory, loadout, map);
}
}
@ -301,7 +324,9 @@ pub fn equip(slot: usize, inventory: &mut Inventory, loadout: &mut Loadout) {
///
/// ```
/// use veloren_common::{
/// assets::Asset,
/// comp::{
/// item::tool::AbilityMap,
/// slot::{unequip, EquipSlot},
/// Inventory,
/// },
@ -310,22 +335,30 @@ pub fn equip(slot: usize, inventory: &mut Inventory, loadout: &mut Loadout) {
///
/// let mut inv = Inventory::new_empty();
///
/// let map = AbilityMap::load_expect_cloned("common.abilities.weapon_ability_manifest");
///
/// let mut loadout = LoadoutBuilder::new()
/// .defaults()
/// .active_item(Some(LoadoutBuilder::default_item_config_from_str(
/// "common.items.weapons.sword.zweihander_sword_0",
/// &map,
/// )))
/// .build();
///
/// let slot = EquipSlot::Mainhand;
///
/// unequip(slot, &mut inv, &mut loadout);
/// unequip(slot, &mut inv, &mut loadout, &map);
/// assert_eq!(None, loadout.active_item);
/// ```
pub fn unequip(slot: EquipSlot, inventory: &mut Inventory, loadout: &mut Loadout) {
loadout_remove(slot, loadout) // Remove item from loadout
pub fn unequip(
slot: EquipSlot,
inventory: &mut Inventory,
loadout: &mut Loadout,
map: &AbilityMap,
) {
loadout_remove(slot, loadout, map) // Remove item from loadout
.and_then(|i| inventory.push(i)) // Insert into inventory
.and_then(|i| loadout_insert(slot, i, loadout)) // If that fails put back in loadout
.and_then(|i| loadout_insert(slot, i, loadout, map)) // If that fails put back in loadout
.unwrap_none(); // Never fails
}
@ -341,8 +374,12 @@ mod tests {
amount: 0,
};
use crate::assets::Asset;
let map = AbilityMap::load_expect_cloned("common.abilities.weapon_ability_manifest");
let sword = LoadoutBuilder::default_item_config_from_str(
"common.items.weapons.sword.zweihander_sword_0",
&map,
);
let mut loadout = LoadoutBuilder::new()
@ -352,11 +389,11 @@ mod tests {
.build();
assert_eq!(Some(sword.clone()), loadout.active_item);
unequip(EquipSlot::Mainhand, &mut inv, &mut loadout);
unequip(EquipSlot::Mainhand, &mut inv, &mut loadout, &map);
// We have space in the inventory, so this should have unequipped
assert_eq!(None, loadout.active_item);
unequip(EquipSlot::Offhand, &mut inv, &mut loadout);
unequip(EquipSlot::Offhand, &mut inv, &mut loadout, &map);
// There is no more space in the inventory, so this should still be equipped
assert_eq!(Some(sword.clone()), loadout.second_item);
@ -382,9 +419,12 @@ mod tests {
let mut loadout = LoadoutBuilder::new().defaults().build();
use crate::assets::Asset;
let map = AbilityMap::load_expect_cloned("common.abilities.weapon_ability_manifest");
// We should start with the starting sandles
assert_eq!(starting_sandles, loadout.foot);
equip(0, &mut inv, &mut loadout);
equip(0, &mut inv, &mut loadout, &map);
// We should now have the testing boots equiped
assert_eq!(boots, loadout.foot);
@ -404,6 +444,9 @@ mod tests {
"common.items.armor.starter.sandals_0",
));
use crate::assets::Asset;
let map = AbilityMap::load_expect_cloned("common.abilities.weapon_ability_manifest");
let mut loadout = LoadoutBuilder::new().defaults().build();
// We should start with the starting sandles
@ -416,6 +459,7 @@ mod tests {
EquipSlot::Armor(ArmorSlot::Feet),
boots.clone(),
&mut loadout,
&map,
)
);
@ -425,8 +469,12 @@ mod tests {
#[test]
fn test_loadout_remove() {
use crate::assets::Asset;
let map = AbilityMap::load_expect_cloned("common.abilities.weapon_ability_manifest");
let sword = LoadoutBuilder::default_item_config_from_str(
"common.items.weapons.sword.zweihander_sword_0",
&map,
);
let mut loadout = LoadoutBuilder::new()
@ -437,7 +485,7 @@ mod tests {
// The swap should return the sword
assert_eq!(
Some(sword.item),
loadout_remove(EquipSlot::Mainhand, &mut loadout,)
loadout_remove(EquipSlot::Mainhand, &mut loadout, &map)
);
// We should now have nothing equiped

View File

@ -61,7 +61,7 @@ pub use phys::{
Sticky, Vel,
};
pub use player::Player;
pub use projectile::Projectile;
pub use projectile::{Projectile, ProjectileConstructor};
pub use shockwave::{Shockwave, ShockwaveHitEntities};
pub use skills::{Skill, SkillGroup, SkillGroupType, SkillSet};
pub use stats::{Exp, Level, Stats};

View File

@ -1,4 +1,9 @@
use crate::{comp::Buff, sync::Uid, Damage, Explosion, GroupTarget, Knockback};
use crate::{
comp::buff::{BuffCategory, BuffData, BuffKind},
effect::{self, BuffEffect},
sync::Uid,
Damage, DamageSource, Explosion, GroupTarget, Knockback, RadiusEffect,
};
use serde::{Deserialize, Serialize};
use specs::{Component, FlaggedStorage};
use specs_idvs::IdvStorage;
@ -13,7 +18,10 @@ pub enum Effect {
Vanish,
Stick,
Possess,
Buff { buff: Buff, chance: Option<f32> },
Buff {
buff: BuffEffect,
chance: Option<f32>,
},
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -32,3 +40,185 @@ pub struct Projectile {
impl Component for Projectile {
type Storage = FlaggedStorage<Self, IdvStorage<Self>>;
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub enum ProjectileConstructor {
Arrow {
damage: f32,
knockback: f32,
energy_regen: u32,
},
Fireball {
damage: f32,
radius: f32,
energy_regen: u32,
},
Heal {
heal: f32,
damage: f32,
radius: f32,
},
Possess,
}
impl ProjectileConstructor {
pub fn create_projectile(self, owner: Option<Uid>) -> Projectile {
use ProjectileConstructor::*;
match self {
Arrow {
damage,
knockback,
energy_regen,
} => {
let buff = BuffEffect {
kind: BuffKind::Bleeding,
data: BuffData {
strength: damage / 2.0,
duration: Some(Duration::from_secs(5)),
},
cat_ids: vec![BuffCategory::Physical],
};
Projectile {
hit_solid: vec![Effect::Stick],
hit_entity: vec![
Effect::Damage(Some(GroupTarget::OutOfGroup), Damage {
source: DamageSource::Projectile,
value: damage,
}),
Effect::Knockback(Knockback::Away(knockback)),
Effect::RewardEnergy(energy_regen),
Effect::Vanish,
Effect::Buff {
buff,
chance: Some(0.10),
},
],
time_left: Duration::from_secs(15),
owner,
ignore_group: true,
}
},
Fireball {
damage,
radius,
energy_regen,
} => Projectile {
hit_solid: vec![
Effect::Explode(Explosion {
effects: vec![RadiusEffect::Entity(
Some(GroupTarget::OutOfGroup),
effect::Effect::Damage(Damage {
source: DamageSource::Explosion,
value: damage,
}),
)],
radius,
energy_regen,
}),
Effect::Vanish,
],
hit_entity: vec![
Effect::Explode(Explosion {
effects: vec![RadiusEffect::Entity(
Some(GroupTarget::OutOfGroup),
effect::Effect::Damage(Damage {
source: DamageSource::Explosion,
value: damage,
}),
)],
radius,
energy_regen,
}),
Effect::Vanish,
],
time_left: Duration::from_secs(20),
owner,
ignore_group: true,
},
Heal {
heal,
damage,
radius,
} => Projectile {
hit_solid: vec![
Effect::Explode(Explosion {
effects: vec![
RadiusEffect::Entity(
Some(GroupTarget::OutOfGroup),
effect::Effect::Damage(Damage {
source: DamageSource::Explosion,
value: damage,
}),
),
RadiusEffect::Entity(
Some(GroupTarget::InGroup),
effect::Effect::Damage(Damage {
source: DamageSource::Healing,
value: heal,
}),
),
],
radius,
energy_regen: 0,
}),
Effect::Vanish,
],
hit_entity: vec![
Effect::Explode(Explosion {
effects: vec![
RadiusEffect::Entity(
Some(GroupTarget::OutOfGroup),
effect::Effect::Damage(Damage {
source: DamageSource::Explosion,
value: damage,
}),
),
RadiusEffect::Entity(
Some(GroupTarget::InGroup),
effect::Effect::Damage(Damage {
source: DamageSource::Healing,
value: heal,
}),
),
],
radius,
energy_regen: 0,
}),
Effect::Vanish,
],
time_left: Duration::from_secs(20),
owner,
ignore_group: true,
},
Possess => Projectile {
hit_solid: vec![Effect::Stick],
hit_entity: vec![Effect::Stick, Effect::Possess],
time_left: Duration::from_secs(10),
owner,
ignore_group: false,
},
}
}
pub fn modified_projectile(mut self, power: f32) -> Self {
use ProjectileConstructor::*;
match self {
Arrow { ref mut damage, .. } => {
*damage *= power;
},
Fireball { ref mut damage, .. } => {
*damage *= power;
},
Heal {
ref mut damage,
ref mut heal,
..
} => {
*damage *= power;
*heal *= power;
},
Possess => {},
}
self
}
}

View File

@ -1,23 +1,28 @@
use crate::comp::{
biped_large, golem,
item::{Item, ItemKind},
item::{tool::AbilityMap, Item, ItemKind},
Alignment, Body, CharacterAbility, ItemConfig, Loadout,
};
use rand::Rng;
use std::time::Duration;
/// Builder for character Loadouts, containing weapon and armour items belonging
/// to a character, along with some helper methods for loading Items and
/// ItemConfig
///
/// ```
/// use veloren_common::LoadoutBuilder;
/// use veloren_common::{
/// assets::Asset,
/// comp::item::tool::AbilityMap,
/// LoadoutBuilder,
/// };
///
/// let map = AbilityMap::load_expect_cloned("common.abilities.weapon_ability_manifest");
///
/// // Build a loadout with character starter defaults and a specific sword with default sword abilities
/// let loadout = LoadoutBuilder::new()
/// .defaults()
/// .active_item(Some(LoadoutBuilder::default_item_config_from_str(
/// "common.items.weapons.sword.zweihander_sword_0"
/// "common.items.weapons.sword.zweihander_sword_0", &map
/// )))
/// .build();
/// ```
@ -72,6 +77,7 @@ impl LoadoutBuilder {
alignment: Alignment,
mut main_tool: Option<Item>,
is_giant: bool,
map: &AbilityMap,
) -> Self {
match body {
Body::Golem(golem) => match golem.species {
@ -145,21 +151,12 @@ impl LoadoutBuilder {
};
let active_item = if let Some(ItemKind::Tool(_)) = main_tool.as_ref().map(|i| i.kind()) {
main_tool.map(ItemConfig::from)
main_tool.map(|item| ItemConfig::from((item, map)))
} else {
Some(ItemConfig {
// We need the empty item so npcs can attack
item: Item::new_from_asset_expect("common.items.weapons.empty.empty"),
ability1: Some(CharacterAbility::BasicMelee {
energy_cost: 0,
buildup_duration: Duration::from_millis(0),
swing_duration: Duration::from_millis(100),
recover_duration: Duration::from_millis(300),
base_damage: 40,
knockback: 0.0,
range: 3.5,
max_angle: 15.0,
}),
ability1: Some(CharacterAbility::default()),
ability2: None,
ability3: None,
block_ability: None,
@ -334,9 +331,9 @@ impl LoadoutBuilder {
item: Item::new_from_asset_expect("common.items.weapons.empty.empty"),
ability1: Some(CharacterAbility::BasicMelee {
energy_cost: 10,
buildup_duration: Duration::from_millis(500),
swing_duration: Duration::from_millis(100),
recover_duration: Duration::from_millis(100),
buildup_duration: 500,
swing_duration: 100,
recover_duration: 100,
base_damage: body.base_dmg(),
knockback: 0.0,
range: body.base_range(),
@ -370,7 +367,9 @@ impl LoadoutBuilder {
/// abilities or their timings is desired, you should create and provide
/// the item config directly to the [active_item](#method.active_item)
/// method
pub fn default_item_config_from_item(item: Item) -> ItemConfig { ItemConfig::from(item) }
pub fn default_item_config_from_item(item: Item, map: &AbilityMap) -> ItemConfig {
ItemConfig::from((item, map))
}
/// Get an item's (weapon's) default
/// [ItemConfig](../comp/struct.ItemConfig.html)
@ -378,8 +377,8 @@ impl LoadoutBuilder {
/// the default abilities for that item via the
/// [default_item_config_from_item](#method.default_item_config_from_item)
/// function
pub fn default_item_config_from_str(item_ref: &str) -> ItemConfig {
Self::default_item_config_from_item(Item::new_from_asset_expect(item_ref))
pub fn default_item_config_from_str(item_ref: &str, map: &AbilityMap) -> ItemConfig {
Self::default_item_config_from_item(Item::new_from_asset_expect(item_ref), map)
}
pub fn active_item(mut self, item: Option<ItemConfig>) -> Self {

View File

@ -1,4 +1,5 @@
use crate::{
assets::Asset,
comp,
event::{EventBus, LocalEvent, ServerEvent},
metrics::{PhysicsMetrics, SysMetrics},
@ -180,6 +181,9 @@ impl State {
ecs.insert(BlockChange::default());
ecs.insert(TerrainChanges::default());
ecs.insert(EventBus::<LocalEvent>::default());
ecs.insert(comp::item::tool::AbilityMap::load_expect_cloned(
"common.abilities.weapon_ability_manifest",
));
// TODO: only register on the server
ecs.insert(EventBus::<ServerEvent>::default());
ecs.insert(comp::group::GroupManager::default());
@ -253,6 +257,9 @@ impl State {
/// Get the current delta time.
pub fn get_delta_time(&self) -> f32 { self.ecs.read_resource::<DeltaTime>().0 }
/// Get a reference to this state's ability map.
pub fn ability_map(&self) -> Fetch<comp::item::tool::AbilityMap> { self.ecs.read_resource() }
/// Get a reference to this state's terrain.
pub fn terrain(&self) -> Fetch<TerrainGrid> { self.ecs.read_resource() }

View File

@ -1,5 +1,5 @@
use crate::{
comp::{Body, CharacterState, Gravity, LightEmitter, Projectile, StateUpdate},
comp::{Body, CharacterState, Gravity, LightEmitter, ProjectileConstructor, StateUpdate},
event::ServerEvent,
states::utils::*,
sys::character_behavior::{CharacterBehavior, JoinData},
@ -8,14 +8,14 @@ use serde::{Deserialize, Serialize};
use std::time::Duration;
/// Separated out to condense update portions of character state
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct StaticData {
/// How much buildup is required before the attack
pub buildup_duration: Duration,
/// How long the state has until exiting
pub recover_duration: Duration,
/// Projectile variables
pub projectile: Projectile,
pub projectile: ProjectileConstructor,
pub projectile_body: Body,
pub projectile_light: Option<LightEmitter>,
pub projectile_gravity: Option<Gravity>,
@ -54,7 +54,6 @@ impl CharacterBehavior for Data {
if self.timer < self.static_data.buildup_duration {
// Build up
update.character = CharacterState::BasicRanged(Data {
static_data: self.static_data.clone(),
timer: self
.timer
.checked_add(Duration::from_secs_f32(data.dt.0))
@ -64,7 +63,6 @@ impl CharacterBehavior for Data {
} else {
// Transitions to recover section of stage
update.character = CharacterState::BasicRanged(Data {
static_data: self.static_data.clone(),
timer: Duration::default(),
stage_section: StageSection::Recover,
..*self
@ -74,8 +72,10 @@ impl CharacterBehavior for Data {
StageSection::Recover => {
if !self.exhausted {
// Fire
let mut projectile = self.static_data.projectile.clone();
projectile.owner = Some(*data.uid);
let projectile = self
.static_data
.projectile
.create_projectile(Some(*data.uid));
update.server_events.push_front(ServerEvent::Shoot {
entity: data.entity,
dir: data.inputs.look_dir,
@ -87,7 +87,6 @@ impl CharacterBehavior for Data {
});
update.character = CharacterState::BasicRanged(Data {
static_data: self.static_data.clone(),
exhausted: true,
continue_next: false,
..*self
@ -96,7 +95,6 @@ impl CharacterBehavior for Data {
if ability_key_is_pressed(data, self.static_data.ability_key) {
// Recovers
update.character = CharacterState::BasicRanged(Data {
static_data: self.static_data.clone(),
timer: self
.timer
.checked_add(Duration::from_secs_f32(data.dt.0))
@ -107,7 +105,6 @@ impl CharacterBehavior for Data {
} else {
// Recovers
update.character = CharacterState::BasicRanged(Data {
static_data: self.static_data.clone(),
timer: self
.timer
.checked_add(Duration::from_secs_f32(data.dt.0))
@ -118,7 +115,6 @@ impl CharacterBehavior for Data {
} else if self.continue_next {
// Restarts character state
update.character = CharacterState::BasicRanged(Data {
static_data: self.static_data.clone(),
timer: Duration::default(),
stage_section: StageSection::Buildup,
exhausted: false,

View File

@ -1,9 +1,10 @@
use crate::{
comp::{
buff::{Buff, BuffCategory, BuffData, BuffKind, BuffSource},
buff::{BuffCategory, BuffData, BuffKind},
projectile, Body, CharacterState, EnergyChange, EnergySource, Gravity, LightEmitter,
Projectile, StateUpdate,
},
effect::BuffEffect,
event::ServerEvent,
states::utils::*,
sys::character_behavior::{CharacterBehavior, JoinData},
@ -98,30 +99,28 @@ impl CharacterBehavior for Data {
* (self.static_data.max_knockback - self.static_data.initial_knockback)
as f32);
// Fire
let mut projectile = Projectile {
let projectile = Projectile {
hit_solid: vec![projectile::Effect::Stick],
hit_entity: vec![
projectile::Effect::Damage(Some(GroupTarget::OutOfGroup), damage),
projectile::Effect::Knockback(Knockback::Away(knockback)),
projectile::Effect::Vanish,
projectile::Effect::Buff {
buff: Buff::new(
BuffKind::Bleeding,
BuffData {
buff: BuffEffect {
kind: BuffKind::Bleeding,
data: BuffData {
strength: damage.value / 5.0,
duration: Some(Duration::from_secs(5)),
},
vec![BuffCategory::Physical],
BuffSource::Unknown,
),
cat_ids: vec![BuffCategory::Physical],
},
chance: Some(0.10),
},
],
time_left: Duration::from_secs(15),
owner: None,
owner: Some(*data.uid),
ignore_group: true,
};
projectile.owner = Some(*data.uid);
update.server_events.push_front(ServerEvent::Shoot {
entity: data.entity,
dir: data.inputs.look_dir,

View File

@ -7,8 +7,8 @@ use crate::{
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Stage {
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct Stage<T> {
/// Specifies which stage the combo attack is in
pub stage: u32,
/// Initial damage of stage
@ -24,23 +24,51 @@ pub struct Stage {
/// Angle of attack
pub angle: f32,
/// Initial buildup duration of stage (how long until state can deal damage)
pub base_buildup_duration: Duration,
pub base_buildup_duration: T,
/// Duration of stage spent in swing (controls animation stuff, and can also
/// be used to handle movement separately to buildup)
pub base_swing_duration: Duration,
pub base_swing_duration: T,
/// Initial recover duration of stage (how long until character exits state)
pub base_recover_duration: Duration,
pub base_recover_duration: T,
/// How much forward movement there is in the swing portion of the stage
pub forward_movement: f32,
}
impl Stage<u64> {
pub fn to_duration(self) -> Stage<Duration> {
Stage::<Duration> {
stage: self.stage,
base_damage: self.base_damage,
max_damage: self.max_damage,
damage_increase: self.damage_increase,
knockback: self.knockback,
range: self.range,
angle: self.angle,
base_buildup_duration: Duration::from_millis(self.base_buildup_duration),
base_swing_duration: Duration::from_millis(self.base_swing_duration),
base_recover_duration: Duration::from_millis(self.base_recover_duration),
forward_movement: self.forward_movement,
}
}
pub fn adjusted_by_stats(mut self, power: f32, speed: f32) -> Self {
self.base_damage = (self.base_damage as f32 * power) as u32;
self.max_damage = (self.max_damage as f32 * power) as u32;
self.damage_increase = (self.damage_increase as f32 * power) as u32;
self.base_buildup_duration = (self.base_buildup_duration as f32 / speed) as u64;
self.base_swing_duration = (self.base_swing_duration as f32 / speed) as u64;
self.base_recover_duration = (self.base_recover_duration as f32 / speed) as u64;
self
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
/// Separated out to condense update portions of character state
pub struct StaticData {
/// Indicates number of stages in combo
pub num_stages: u32,
/// Data for each stage
pub stage_data: Vec<Stage>,
pub stage_data: Vec<Stage<Duration>>,
/// Initial energy gain per strike
pub initial_energy_gain: u32,
/// Max energy gain per strike

View File

@ -1,5 +1,5 @@
use crate::{
comp::{Body, CharacterState, Gravity, LightEmitter, Projectile, StateUpdate},
comp::{Body, CharacterState, Gravity, LightEmitter, ProjectileConstructor, StateUpdate},
event::ServerEvent,
states::utils::{StageSection, *},
sys::character_behavior::*,
@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};
use std::time::Duration;
use vek::Vec3;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
/// Separated out to condense update portions of character state
pub struct StaticData {
/// How long the state is in movement
@ -23,7 +23,7 @@ pub struct StaticData {
/// Whether there should be a jump and how strong the leap is
pub leap: Option<f32>,
/// Projectile options
pub projectile: Projectile,
pub projectile: ProjectileConstructor,
pub projectile_body: Body,
pub projectile_light: Option<LightEmitter>,
pub projectile_gravity: Option<Gravity>,
@ -71,7 +71,6 @@ impl CharacterBehavior for Data {
if self.timer < self.static_data.movement_duration {
// Do movement
update.character = CharacterState::RepeaterRanged(Data {
static_data: self.static_data.clone(),
timer: self
.timer
.checked_add(Duration::from_secs_f32(data.dt.0))
@ -81,7 +80,6 @@ impl CharacterBehavior for Data {
} else {
// Transition to buildup
update.character = CharacterState::RepeaterRanged(Data {
static_data: self.static_data.clone(),
timer: Duration::default(),
stage_section: StageSection::Buildup,
..*self
@ -101,7 +99,6 @@ impl CharacterBehavior for Data {
if self.timer < self.static_data.buildup_duration {
// Buildup to attack
update.character = CharacterState::RepeaterRanged(Data {
static_data: self.static_data.clone(),
timer: self
.timer
.checked_add(Duration::from_secs_f32(data.dt.0))
@ -111,7 +108,6 @@ impl CharacterBehavior for Data {
} else {
// Transition to shoot
update.character = CharacterState::RepeaterRanged(Data {
static_data: self.static_data.clone(),
timer: Duration::default(),
stage_section: StageSection::Shoot,
..*self
@ -125,8 +121,10 @@ impl CharacterBehavior for Data {
}
if self.reps_remaining > 0 {
// Fire
let mut projectile = self.static_data.projectile.clone();
projectile.owner = Some(*data.uid);
let projectile = self
.static_data
.projectile
.create_projectile(Some(*data.uid));
update.server_events.push_front(ServerEvent::Shoot {
entity: data.entity,
// Provides slight variation to projectile direction
@ -155,7 +153,6 @@ impl CharacterBehavior for Data {
// Shoot projectiles
update.character = CharacterState::RepeaterRanged(Data {
static_data: self.static_data.clone(),
timer: self
.timer
.checked_add(Duration::from_secs_f32(data.dt.0))
@ -166,7 +163,6 @@ impl CharacterBehavior for Data {
} else if self.timer < self.static_data.shoot_duration {
// Finish shooting
update.character = CharacterState::RepeaterRanged(Data {
static_data: self.static_data.clone(),
timer: self
.timer
.checked_add(Duration::from_secs_f32(data.dt.0))
@ -176,7 +172,6 @@ impl CharacterBehavior for Data {
} else {
// Transition to recover
update.character = CharacterState::RepeaterRanged(Data {
static_data: self.static_data.clone(),
timer: Duration::default(),
stage_section: StageSection::Recover,
..*self
@ -190,7 +185,6 @@ impl CharacterBehavior for Data {
} else if self.timer < self.static_data.recover_duration {
// Recover from attack
update.character = CharacterState::RepeaterRanged(Data {
static_data: self.static_data.clone(),
timer: self
.timer
.checked_add(Duration::from_secs_f32(data.dt.0))

View File

@ -1,6 +1,6 @@
use crate::{
comp::{
buff::{BuffChange, BuffSource},
buff::{Buff, BuffChange, BuffSource},
projectile, EnergyChange, EnergySource, Group, HealthSource, Loadout, Ori, PhysicsState,
Pos, Projectile, Vel,
},
@ -176,10 +176,13 @@ impl<'a> System<'a> for Sys {
uid_allocator.retrieve_entity_internal(other.into())
{
if chance.map_or(true, |c| thread_rng().gen::<f32>() < c) {
let mut buff = buff.clone();
if let Some(uid) = projectile.owner {
buff.source = BuffSource::Character { by: uid };
}
let source = if let Some(owner) = projectile.owner {
BuffSource::Character { by: owner }
} else {
BuffSource::Unknown
};
let buff =
Buff::new(buff.kind, buff.data, buff.cat_ids, source);
server_emitter.emit(ServerEvent::Buff {
entity,
buff_change: BuffChange::Add(buff),

View File

@ -1,6 +1,6 @@
use crate::persistence::character_loader::CharacterLoader;
use common::{
comp::{Body, Inventory, Stats},
comp::{item::tool::AbilityMap, Body, Inventory, Stats},
loadout_builder::LoadoutBuilder,
};
use specs::{Entity, ReadExpect};
@ -12,6 +12,7 @@ pub fn create_character(
character_tool: Option<String>,
body: Body,
character_loader: &ReadExpect<'_, CharacterLoader>,
map: &AbilityMap,
) {
let stats = Stats::new(character_alias.to_string(), body);
@ -19,6 +20,7 @@ pub fn create_character(
.defaults()
.active_item(Some(LoadoutBuilder::default_item_config_from_str(
character_tool.as_deref().unwrap(),
map,
)))
.build();

View File

@ -655,14 +655,19 @@ fn handle_spawn(
let body = body();
let map = server.state().ability_map();
let loadout =
LoadoutBuilder::build_loadout(body, alignment, None, false, &map)
.build();
drop(map);
let mut entity_base = server
.state
.create_npc(
pos,
comp::Stats::new(get_npc_name(id).into(), body),
comp::Health::new(body, 1),
LoadoutBuilder::build_loadout(body, alignment, None, false)
.build(),
loadout,
body,
)
.with(comp::Vel(vel))

View File

@ -4,7 +4,11 @@ use crate::{
Server,
};
use common::{
comp::{self, item, Pos},
comp::{
self,
item::{self, tool::AbilityMap},
Pos,
},
consts::MAX_MOUNT_RANGE,
msg::ServerGeneral,
sync::{Uid, WorldSyncExt},
@ -103,8 +107,8 @@ pub fn handle_unmount(server: &mut Server, mounter: EcsEntity) {
#[allow(clippy::nonminimal_bool)] // TODO: Pending review in #587
pub fn handle_possess(server: &Server, possessor_uid: Uid, possesse_uid: Uid) {
let state = &server.state;
let ecs = state.ecs();
let ecs = &server.state.ecs();
let ability_map = ecs.fetch::<AbilityMap>();
if let (Some(possessor), Some(possesse)) = (
ecs.entity_from_uid(possessor_uid.into()),
ecs.entity_from_uid(possesse_uid.into()),
@ -177,7 +181,7 @@ pub fn handle_possess(server: &Server, possessor_uid: Uid, possesse_uid: Uid) {
let item = comp::Item::new_from_asset_expect("common.items.debug.possess");
if let item::ItemKind::Tool(_) = item.kind() {
let debug_item = comp::ItemConfig::from(item);
let debug_item = comp::ItemConfig::from((item, &*ability_map));
std::mem::swap(&mut loadout.active_item, &mut loadout.second_item);
loadout.active_item = Some(debug_item);
}

View File

@ -203,7 +203,8 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
if let Some(lantern) = lantern_opt {
swap_lantern(&mut state.ecs().write_storage(), entity, &lantern);
}
slot::equip(slot, inventory, loadout);
let ability_map = state.ability_map();
slot::equip(slot, inventory, loadout, &ability_map);
Some(comp::InventoryUpdateEvent::Used)
} else {
None
@ -338,7 +339,8 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
if slot == slot::EquipSlot::Lantern {
snuff_lantern(&mut state.ecs().write_storage(), entity);
}
slot::unequip(slot, inventory, loadout);
let ability_map = state.ability_map();
slot::unequip(slot, inventory, loadout, &ability_map);
Some(comp::InventoryUpdateEvent::Used)
} else {
error!(?entity, "Entity doesn't have a loadout, can't unequip...");
@ -364,12 +366,14 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
let mut loadouts = ecs.write_storage();
let inventory = inventories.get_mut(entity);
let loadout = loadouts.get_mut(entity);
let ability_map = state.ability_map();
slot::swap(a, b, inventory, loadout);
slot::swap(a, b, inventory, loadout, &ability_map);
// :/
drop(loadouts);
drop(inventories);
drop(ability_map);
state.write_component(
entity,
@ -378,6 +382,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
},
comp::InventoryManip::Drop(slot) => {
let ability_map = state.ability_map();
let item = match slot {
Slot::Inventory(slot) => state
.ecs()
@ -388,8 +393,9 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
.ecs()
.write_storage()
.get_mut(entity)
.and_then(|ldt| slot::loadout_remove(slot, ldt)),
.and_then(|ldt| slot::loadout_remove(slot, ldt, &ability_map)),
};
drop(ability_map);
// FIXME: We should really require the drop and write to be atomic!
if let (Some(mut item), Some(pos)) =

View File

@ -159,9 +159,9 @@ impl Server {
state
.ecs_mut()
.insert(CharacterUpdater::new(&persistence_db_dir)?);
state
.ecs_mut()
.insert(CharacterLoader::new(&persistence_db_dir)?);
let character_loader = CharacterLoader::new(&persistence_db_dir, &*state.ability_map());
state.ecs_mut().insert(character_loader?);
state.ecs_mut().insert(Vec::<Outcome>::new());
// System timers for performance monitoring

View File

@ -25,6 +25,7 @@ use crate::{
};
use common::{
character::{CharacterId, CharacterItem, MAX_CHARACTERS_PER_PLAYER},
comp::item::tool::AbilityMap,
state::Time,
};
use core::ops::Range;
@ -60,6 +61,7 @@ pub fn load_character_data(
requesting_player_uuid: String,
char_id: CharacterId,
connection: VelorenTransaction,
map: &AbilityMap,
) -> CharacterDataResult {
use schema::{body::dsl::*, character::dsl::*, item::dsl::*, stats::dsl::*};
@ -102,7 +104,7 @@ pub fn load_character_data(
convert_body_from_database(&char_body)?,
convert_stats_from_database(&stats_data, character_data.alias),
convert_inventory_from_database_items(&inventory_items)?,
convert_loadout_from_database_items(&loadout_items)?,
convert_loadout_from_database_items(&loadout_items, map)?,
waypoint,
))
}
@ -117,6 +119,7 @@ pub fn load_character_data(
pub fn load_character_list(
player_uuid_: &str,
connection: VelorenTransaction,
map: &AbilityMap,
) -> CharacterListResult {
use schema::{body::dsl::*, character::dsl::*, item::dsl::*, stats::dsl::*};
@ -149,7 +152,7 @@ pub fn load_character_list(
.filter(parent_container_item_id.eq(loadout_container_id))
.load::<Item>(&*connection)?;
let loadout = convert_loadout_from_database_items(&loadout_items)?;
let loadout = convert_loadout_from_database_items(&loadout_items, map)?;
Ok(CharacterItem {
character: char,
@ -166,6 +169,7 @@ pub fn create_character(
character_alias: &str,
persisted_components: PersistedComponents,
connection: VelorenTransaction,
map: &AbilityMap,
) -> CharacterListResult {
use schema::item::dsl::*;
@ -299,7 +303,7 @@ pub fn create_character(
)));
}
load_character_list(uuid, connection)
load_character_list(uuid, connection, map)
}
/// Delete a character. Returns the updated character list.
@ -307,6 +311,7 @@ pub fn delete_character(
requesting_player_uuid: &str,
char_id: CharacterId,
connection: VelorenTransaction,
map: &AbilityMap,
) -> CharacterListResult {
use schema::{body::dsl::*, character::dsl::*, stats::dsl::*};
@ -387,7 +392,7 @@ pub fn delete_character(
)));
}
load_character_list(requesting_player_uuid, connection)
load_character_list(requesting_player_uuid, connection, map)
}
/// Before creating a character, we ensure that the limit on the number of

View File

@ -9,7 +9,7 @@ use crate::persistence::{
};
use common::{
character::CharacterId,
comp::{Body as CompBody, *},
comp::{item::tool::AbilityMap, Body as CompBody, *},
loadout_builder,
};
use core::{convert::TryFrom, num::NonZeroU64};
@ -240,7 +240,10 @@ pub fn convert_inventory_from_database_items(database_items: &[Item]) -> Result<
Ok(inventory)
}
pub fn convert_loadout_from_database_items(database_items: &[Item]) -> Result<Loadout, Error> {
pub fn convert_loadout_from_database_items(
database_items: &[Item],
map: &AbilityMap,
) -> Result<Loadout, Error> {
let mut loadout = loadout_builder::LoadoutBuilder::new();
for db_item in database_items.iter() {
let item = common::comp::Item::new_from_asset(db_item.item_definition_id.as_str())?;
@ -251,8 +254,8 @@ pub fn convert_loadout_from_database_items(database_items: &[Item]) -> Result<Lo
)?));
match db_item.position.as_str() {
"active_item" => loadout = loadout.active_item(Some(ItemConfig::from(item))),
"second_item" => loadout = loadout.second_item(Some(ItemConfig::from(item))),
"active_item" => loadout = loadout.active_item(Some(ItemConfig::from((item, map)))),
"second_item" => loadout = loadout.second_item(Some(ItemConfig::from((item, map)))),
"lantern" => loadout = loadout.lantern(Some(item)),
"shoulder" => loadout = loadout.shoulder(Some(item)),
"chest" => loadout = loadout.chest(Some(item)),

View File

@ -3,7 +3,10 @@ use crate::persistence::{
error::Error,
establish_connection, PersistedComponents,
};
use common::character::{CharacterId, CharacterItem};
use common::{
character::{CharacterId, CharacterItem},
comp::item::tool::AbilityMap,
};
use crossbeam::{channel, channel::TryIter};
use std::path::Path;
use tracing::error;
@ -65,12 +68,14 @@ pub struct CharacterLoader {
}
impl CharacterLoader {
pub fn new(db_dir: &Path) -> diesel::QueryResult<Self> {
pub fn new(db_dir: &Path, map: &AbilityMap) -> diesel::QueryResult<Self> {
let (update_tx, internal_rx) = channel::unbounded::<CharacterLoaderRequest>();
let (internal_tx, update_rx) = channel::unbounded::<CharacterLoaderResponse>();
let mut conn = establish_connection(db_dir)?;
let map = map.clone();
std::thread::spawn(move || {
for request in internal_rx {
let (entity, kind) = request;
@ -88,19 +93,20 @@ impl CharacterLoader {
&character_alias,
persisted_components,
txn,
&map,
)
})),
CharacterLoaderRequestKind::DeleteCharacter {
player_uuid,
character_id,
} => {
CharacterLoaderResponseType::CharacterList(conn.transaction(|txn| {
delete_character(&player_uuid, character_id, txn)
}))
},
} => CharacterLoaderResponseType::CharacterList(conn.transaction(|txn| {
delete_character(&player_uuid, character_id, txn, &map)
})),
CharacterLoaderRequestKind::LoadCharacterList { player_uuid } => {
CharacterLoaderResponseType::CharacterList(
conn.transaction(|txn| load_character_list(&player_uuid, txn)),
conn.transaction(|txn| {
load_character_list(&player_uuid, txn, &map)
}),
)
},
CharacterLoaderRequestKind::LoadCharacterData {
@ -108,7 +114,7 @@ impl CharacterLoader {
character_id,
} => {
CharacterLoaderResponseType::CharacterData(Box::new(conn.transaction(
|txn| load_character_data(player_uuid, character_id, txn),
|txn| load_character_data(player_uuid, character_id, txn, &map),
)))
},
},

View File

@ -4,7 +4,7 @@ use crate::{
persistence::character_loader::CharacterLoader, presence::Presence, EditableSettings,
};
use common::{
comp::{ChatType, Player, UnresolvedChatMsg},
comp::{item::tool::AbilityMap, ChatType, Player, UnresolvedChatMsg},
event::{EventBus, ServerEvent},
msg::{ClientGeneral, ServerGeneral},
span,
@ -28,6 +28,7 @@ impl Sys {
editable_settings: &ReadExpect<'_, EditableSettings>,
alias_validator: &ReadExpect<'_, AliasValidator>,
msg: ClientGeneral,
map: &AbilityMap,
) -> Result<(), crate::error::Error> {
match msg {
// Request spectator state
@ -101,6 +102,7 @@ impl Sys {
tool,
body,
character_loader,
map,
);
}
},
@ -134,6 +136,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Presence>,
ReadExpect<'a, EditableSettings>,
ReadExpect<'a, AliasValidator>,
ReadExpect<'a, AbilityMap>,
);
fn run(
@ -149,6 +152,7 @@ impl<'a> System<'a> for Sys {
presences,
editable_settings,
alias_validator,
map,
): Self::SystemData,
) {
span!(_guard, "run", "msg::character_screen::Sys::run");
@ -171,6 +175,7 @@ impl<'a> System<'a> for Sys {
&editable_settings,
&alias_validator,
msg,
&map,
)
});
}

View File

@ -1,7 +1,7 @@
use super::SysTimer;
use crate::{chunk_generator::ChunkGenerator, client::Client, presence::Presence, Tick};
use common::{
comp::{self, bird_medium, Alignment, Pos},
comp::{self, bird_medium, item::tool::AbilityMap, Alignment, Pos},
event::{EventBus, ServerEvent},
generation::get_npc_name,
msg::ServerGeneral,
@ -12,7 +12,7 @@ use common::{
LoadoutBuilder,
};
use rand::Rng;
use specs::{Join, Read, ReadStorage, System, Write, WriteExpect};
use specs::{Join, Read, ReadExpect, ReadStorage, System, Write, WriteExpect};
use std::sync::Arc;
use vek::*;
@ -35,6 +35,7 @@ impl<'a> System<'a> for Sys {
ReadStorage<'a, Pos>,
ReadStorage<'a, Presence>,
ReadStorage<'a, Client>,
ReadExpect<'a, AbilityMap>,
);
fn run(
@ -49,6 +50,7 @@ impl<'a> System<'a> for Sys {
positions,
presences,
clients,
map,
): Self::SystemData,
) {
span!(_guard, "run", "terrain::Sys::run");
@ -142,9 +144,14 @@ impl<'a> System<'a> for Sys {
scale = 2.0 + rand::random::<f32>();
}
let loadout =
LoadoutBuilder::build_loadout(body, alignment, main_tool, entity.is_giant)
.build();
let loadout = LoadoutBuilder::build_loadout(
body,
alignment,
main_tool,
entity.is_giant,
&map,
)
.build();
let health = comp::Health::new(stats.body_type, stats.level.level());

View File

@ -24,7 +24,7 @@ use client::Client;
use common::{
assets::Asset,
character::{CharacterId, CharacterItem, MAX_CHARACTERS_PER_PLAYER},
comp::{self, humanoid},
comp::{self, humanoid, item::tool::AbilityMap},
LoadoutBuilder,
};
//ImageFrame, Tooltip,
@ -179,12 +179,14 @@ impl Mode {
}
}
pub fn create(name: String) -> Self {
pub fn create(name: String, map: &AbilityMap) -> Self {
let tool = STARTER_SWORD;
let loadout = LoadoutBuilder::new()
.defaults()
.active_item(Some(LoadoutBuilder::default_item_config_from_str(tool)))
.active_item(Some(LoadoutBuilder::default_item_config_from_str(
tool, map,
)))
.build();
let loadout = Box::new(loadout);
@ -1172,7 +1174,13 @@ impl Controls {
.into()
}
fn update(&mut self, message: Message, events: &mut Vec<Event>, characters: &[CharacterItem]) {
fn update(
&mut self,
message: Message,
events: &mut Vec<Event>,
characters: &[CharacterItem],
map: &AbilityMap,
) {
match message {
Message::Back => {
if matches!(&self.mode, Mode::Create { .. }) {
@ -1206,7 +1214,7 @@ impl Controls {
},
Message::NewCharacter => {
if matches!(&self.mode, Mode::Select { .. }) {
self.mode = Mode::create(String::new());
self.mode = Mode::create(String::new(), map);
}
},
Message::CreateCharacter => {
@ -1242,7 +1250,8 @@ impl Controls {
Message::Tool(value) => {
if let Mode::Create { tool, loadout, .. } = &mut self.mode {
*tool = value;
loadout.active_item = Some(LoadoutBuilder::default_item_config_from_str(*tool));
loadout.active_item =
Some(LoadoutBuilder::default_item_config_from_str(*tool, map));
}
},
Message::RandomizeCharacter => {
@ -1435,8 +1444,12 @@ impl CharSelectionUi {
}
messages.into_iter().for_each(|message| {
self.controls
.update(message, &mut events, &client.character_list.characters)
self.controls.update(
message,
&mut events,
&client.character_list.characters,
&*client.state().ability_map(),
)
});
events