diff --git a/assets/common/abilities/axe/doublestrike.ron b/assets/common/abilities/axe/doublestrike.ron new file mode 100644 index 0000000000..7151798fd1 --- /dev/null +++ b/assets/common/abilities/axe/doublestrike.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/axe/leap.ron b/assets/common/abilities/axe/leap.ron new file mode 100644 index 0000000000..1b6cd34c6d --- /dev/null +++ b/assets/common/abilities/axe/leap.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/axe/spin.ron b/assets/common/abilities/axe/spin.ron new file mode 100644 index 0000000000..8a31ce4361 --- /dev/null +++ b/assets/common/abilities/axe/spin.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/bow/basic.ron b/assets/common/abilities/bow/basic.ron new file mode 100644 index 0000000000..4747e5a087 --- /dev/null +++ b/assets/common/abilities/bow/basic.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/bow/charged.ron b/assets/common/abilities/bow/charged.ron new file mode 100644 index 0000000000..8de9cf9fbc --- /dev/null +++ b/assets/common/abilities/bow/charged.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/bow/repeater.ron b/assets/common/abilities/bow/repeater.ron new file mode 100644 index 0000000000..619c326200 --- /dev/null +++ b/assets/common/abilities/bow/repeater.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/dagger/tempbasic.ron b/assets/common/abilities/dagger/tempbasic.ron new file mode 100644 index 0000000000..0d31c0b0f4 --- /dev/null +++ b/assets/common/abilities/dagger/tempbasic.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/debug/forwardboost.ron b/assets/common/abilities/debug/forwardboost.ron new file mode 100644 index 0000000000..81aefd978e --- /dev/null +++ b/assets/common/abilities/debug/forwardboost.ron @@ -0,0 +1,4 @@ +Boost( + movement_duration: 50, + only_up: false, +) \ No newline at end of file diff --git a/assets/common/abilities/debug/possess.ron b/assets/common/abilities/debug/possess.ron new file mode 100644 index 0000000000..99563c1e78 --- /dev/null +++ b/assets/common/abilities/debug/possess.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/debug/upboost.ron b/assets/common/abilities/debug/upboost.ron new file mode 100644 index 0000000000..d0456d54ba --- /dev/null +++ b/assets/common/abilities/debug/upboost.ron @@ -0,0 +1,4 @@ +Boost( + movement_duration: 50, + only_up: true, +) \ No newline at end of file diff --git a/assets/common/abilities/empty/basic.ron b/assets/common/abilities/empty/basic.ron new file mode 100644 index 0000000000..3562af81b5 --- /dev/null +++ b/assets/common/abilities/empty/basic.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/farming/basic.ron b/assets/common/abilities/farming/basic.ron new file mode 100644 index 0000000000..0a60b62df7 --- /dev/null +++ b/assets/common/abilities/farming/basic.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/hammer/charged.ron b/assets/common/abilities/hammer/charged.ron new file mode 100644 index 0000000000..5b69a8d716 --- /dev/null +++ b/assets/common/abilities/hammer/charged.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/hammer/leap.ron b/assets/common/abilities/hammer/leap.ron new file mode 100644 index 0000000000..70ec3019bf --- /dev/null +++ b/assets/common/abilities/hammer/leap.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/hammer/singlestrike.ron b/assets/common/abilities/hammer/singlestrike.ron new file mode 100644 index 0000000000..57fc0ffa8a --- /dev/null +++ b/assets/common/abilities/hammer/singlestrike.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/sceptre/healingbeam.ron b/assets/common/abilities/sceptre/healingbeam.ron new file mode 100644 index 0000000000..88fc4f02eb --- /dev/null +++ b/assets/common/abilities/sceptre/healingbeam.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/sceptre/healingbomb.ron b/assets/common/abilities/sceptre/healingbomb.ron new file mode 100644 index 0000000000..427cd42424 --- /dev/null +++ b/assets/common/abilities/sceptre/healingbomb.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/shield/block.ron b/assets/common/abilities/shield/block.ron new file mode 100644 index 0000000000..ca0309ede7 --- /dev/null +++ b/assets/common/abilities/shield/block.ron @@ -0,0 +1 @@ +BasicBlock \ No newline at end of file diff --git a/assets/common/abilities/shield/tempbasic.ron b/assets/common/abilities/shield/tempbasic.ron new file mode 100644 index 0000000000..eca1996eb1 --- /dev/null +++ b/assets/common/abilities/shield/tempbasic.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/staff/firebomb.ron b/assets/common/abilities/staff/firebomb.ron new file mode 100644 index 0000000000..05a18db3d3 --- /dev/null +++ b/assets/common/abilities/staff/firebomb.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/staff/fireshockwave.ron b/assets/common/abilities/staff/fireshockwave.ron new file mode 100644 index 0000000000..fabf1b00b5 --- /dev/null +++ b/assets/common/abilities/staff/fireshockwave.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/staff/flamethrower.ron b/assets/common/abilities/staff/flamethrower.ron new file mode 100644 index 0000000000..1fccd24fe3 --- /dev/null +++ b/assets/common/abilities/staff/flamethrower.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/sword/dash.ron b/assets/common/abilities/sword/dash.ron new file mode 100644 index 0000000000..2f08b14737 --- /dev/null +++ b/assets/common/abilities/sword/dash.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/sword/spin.ron b/assets/common/abilities/sword/spin.ron new file mode 100644 index 0000000000..94b55b93bc --- /dev/null +++ b/assets/common/abilities/sword/spin.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/sword/triplestrike.ron b/assets/common/abilities/sword/triplestrike.ron new file mode 100644 index 0000000000..ffdd3097b8 --- /dev/null +++ b/assets/common/abilities/sword/triplestrike.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/unique/beastclaws/basic.ron b/assets/common/abilities/unique/beastclaws/basic.ron new file mode 100644 index 0000000000..77e860349c --- /dev/null +++ b/assets/common/abilities/unique/beastclaws/basic.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/unique/stonegolemfist/basic.ron b/assets/common/abilities/unique/stonegolemfist/basic.ron new file mode 100644 index 0000000000..2ce4d66744 --- /dev/null +++ b/assets/common/abilities/unique/stonegolemfist/basic.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/unique/stonegolemfist/shockwave.ron b/assets/common/abilities/unique/stonegolemfist/shockwave.ron new file mode 100644 index 0000000000..ac29382ee5 --- /dev/null +++ b/assets/common/abilities/unique/stonegolemfist/shockwave.ron @@ -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, +) \ No newline at end of file diff --git a/assets/common/abilities/weapon_ability_manifest.ron b/assets/common/abilities/weapon_ability_manifest.ron new file mode 100644 index 0000000000..6694484688 --- /dev/null +++ b/assets/common/abilities/weapon_ability_manifest.ron @@ -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: [], + ), +}) \ No newline at end of file diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index b88392fe8b..30689de66c 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -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, projectile_gravity: Option, @@ -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, - projectile: Projectile, + projectile: ProjectileConstructor, projectile_body: Body, projectile_light: Option, projectile_gravity: Option, @@ -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, + stage_data: Vec>, 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, projectile_gravity: Option, @@ -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, _specifier: &str) -> Result { + 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, } -impl From 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, diff --git a/common/src/comp/inventory/item/mod.rs b/common/src/comp/inventory/item/mod.rs index 6aa649971b..7933596660 100644 --- a/common/src/comp/inventory/item/mod.rs +++ b/common/src/comp/inventory/item/mod.rs @@ -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}, diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index a5f7b9cd91..5f46baca2e 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -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 { - 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 { + 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 { + pub primary: T, + pub secondary: T, + pub skills: Vec, +} + +impl AbilitySet { + pub fn modified_by_tool(self, tool: &Tool) -> Self { + self.map(|a| a.adjusted_by_stats(tool.base_power(), tool.base_speed())) + } +} + +impl AbilitySet { + pub fn map U>(self, mut f: F) -> AbilitySet { + AbilitySet { + primary: f(self.primary), + secondary: f(self.secondary), + skills: self.skills.into_iter().map(|x| f(x)).collect(), + } + } +} + +impl Default for AbilitySet { + fn default() -> Self { + AbilitySet { + primary: CharacterAbility::default(), + secondary: CharacterAbility::default(), + skills: vec![], + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AbilityMap(HashMap>); + +impl Asset for AbilityMap { + const ENDINGS: &'static [&'static str] = &["ron"]; + + fn parse(buf_reader: BufReader, specifier: &str) -> Result { + ron::de::from_reader::, AbilityMap>(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, diff --git a/common/src/comp/inventory/slot.rs b/common/src/comp/inventory/slot.rs index b199c7417e..02f641b41c 100644 --- a/common/src/comp/inventory/slot.rs +++ b/common/src/comp/inventory/slot.rs @@ -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, loadout: &mut Loadout, + map: &AbilityMap, ) -> Option { 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 { - 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 { - loadout_replace(equip_slot, None, loadout) +pub fn loadout_remove( + equip_slot: EquipSlot, + loadout: &mut Loadout, + map: &AbilityMap, +) -> Option { + 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 diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 53f6a2e27a..362d0ad06a 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -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}; diff --git a/common/src/comp/projectile.rs b/common/src/comp/projectile.rs index f24bd79b50..f5a2d09891 100644 --- a/common/src/comp/projectile.rs +++ b/common/src/comp/projectile.rs @@ -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 }, + Buff { + buff: BuffEffect, + chance: Option, + }, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -32,3 +40,185 @@ pub struct Projectile { impl Component for Projectile { type Storage = FlaggedStorage>; } + +#[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) -> 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 + } +} diff --git a/common/src/loadout_builder.rs b/common/src/loadout_builder.rs index c816a0dc09..f0be91a52d 100644 --- a/common/src/loadout_builder.rs +++ b/common/src/loadout_builder.rs @@ -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, 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) -> Self { diff --git a/common/src/state.rs b/common/src/state.rs index 621ae69eb6..071ee1366b 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -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::::default()); + ecs.insert(comp::item::tool::AbilityMap::load_expect_cloned( + "common.abilities.weapon_ability_manifest", + )); // TODO: only register on the server ecs.insert(EventBus::::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::().0 } + /// Get a reference to this state's ability map. + pub fn ability_map(&self) -> Fetch { self.ecs.read_resource() } + /// Get a reference to this state's terrain. pub fn terrain(&self) -> Fetch { self.ecs.read_resource() } diff --git a/common/src/states/basic_ranged.rs b/common/src/states/basic_ranged.rs index d919f0cb6d..702dfa1ba7 100644 --- a/common/src/states/basic_ranged.rs +++ b/common/src/states/basic_ranged.rs @@ -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, pub projectile_gravity: Option, @@ -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, diff --git a/common/src/states/charged_ranged.rs b/common/src/states/charged_ranged.rs index 951dac68e0..6074edd2bf 100644 --- a/common/src/states/charged_ranged.rs +++ b/common/src/states/charged_ranged.rs @@ -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, diff --git a/common/src/states/combo_melee.rs b/common/src/states/combo_melee.rs index ad3a3aae84..ec3c25e822 100644 --- a/common/src/states/combo_melee.rs +++ b/common/src/states/combo_melee.rs @@ -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 { /// 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 { + pub fn to_duration(self) -> Stage { + Stage:: { + 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, + pub stage_data: Vec>, /// Initial energy gain per strike pub initial_energy_gain: u32, /// Max energy gain per strike diff --git a/common/src/states/repeater_ranged.rs b/common/src/states/repeater_ranged.rs index b6ee044680..1540814697 100644 --- a/common/src/states/repeater_ranged.rs +++ b/common/src/states/repeater_ranged.rs @@ -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, /// Projectile options - pub projectile: Projectile, + pub projectile: ProjectileConstructor, pub projectile_body: Body, pub projectile_light: Option, pub projectile_gravity: Option, @@ -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)) diff --git a/common/src/sys/projectile.rs b/common/src/sys/projectile.rs index 9e3f66b958..1ef563ab5a 100644 --- a/common/src/sys/projectile.rs +++ b/common/src/sys/projectile.rs @@ -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::() < 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), diff --git a/server/src/character_creator.rs b/server/src/character_creator.rs index ed285d047a..6fdeadc53a 100644 --- a/server/src/character_creator.rs +++ b/server/src/character_creator.rs @@ -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, 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(); diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 7ea9da3e64..92efc769ea 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -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)) diff --git a/server/src/events/interaction.rs b/server/src/events/interaction.rs index 1789f94b22..4f322654f1 100644 --- a/server/src/events/interaction.rs +++ b/server/src/events/interaction.rs @@ -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::(); 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); } diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index 2bfa117a08..958a7208c0 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -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)) = diff --git a/server/src/lib.rs b/server/src/lib.rs index 79980fbc18..1e5d6c4b39 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -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::::new()); // System timers for performance monitoring diff --git a/server/src/persistence/character.rs b/server/src/persistence/character.rs index 66f29d4a03..b54dbd94c1 100644 --- a/server/src/persistence/character.rs +++ b/server/src/persistence/character.rs @@ -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::(&*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 diff --git a/server/src/persistence/character/conversions.rs b/server/src/persistence/character/conversions.rs index 503b80be8b..59574ff059 100644 --- a/server/src/persistence/character/conversions.rs +++ b/server/src/persistence/character/conversions.rs @@ -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 { +pub fn convert_loadout_from_database_items( + database_items: &[Item], + map: &AbilityMap, +) -> Result { 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 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)), diff --git a/server/src/persistence/character_loader.rs b/server/src/persistence/character_loader.rs index 049a0c47c1..8d72e8f80b 100644 --- a/server/src/persistence/character_loader.rs +++ b/server/src/persistence/character_loader.rs @@ -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 { + pub fn new(db_dir: &Path, map: &AbilityMap) -> diesel::QueryResult { let (update_tx, internal_rx) = channel::unbounded::(); let (internal_tx, update_rx) = channel::unbounded::(); 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), ))) }, }, diff --git a/server/src/sys/msg/character_screen.rs b/server/src/sys/msg/character_screen.rs index 6dae2482fb..d9667b8f1f 100644 --- a/server/src/sys/msg/character_screen.rs +++ b/server/src/sys/msg/character_screen.rs @@ -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, ) }); } diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index 39fdcb37c2..634bca2b81 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -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::(); } - 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()); diff --git a/voxygen/src/menu/char_selection/ui/mod.rs b/voxygen/src/menu/char_selection/ui/mod.rs index 8df8486b9d..f885664397 100644 --- a/voxygen/src/menu/char_selection/ui/mod.rs +++ b/voxygen/src/menu/char_selection/ui/mod.rs @@ -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, characters: &[CharacterItem]) { + fn update( + &mut self, + message: Message, + events: &mut Vec, + 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