Merge branch 'sam/minotaur' into 'master'

Overhauled Minotaur

See merge request veloren/veloren!2193
This commit is contained in:
Samuel Keiffer 2021-05-04 16:26:08 +00:00
commit fb940ad27a
75 changed files with 1445 additions and 274 deletions

View File

@ -92,6 +92,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Debug Kit is split to "admin_cosmetics" and "debug"
- Potion Kit is renamed to "consumables" and gives potions and mushroom curry
- Cultist Kit gives cape, rings and necklace in addition to armour and weapons.
- Reworked minotaur to have unique attacks.
### Removed

2
Cargo.lock generated
View File

@ -5542,6 +5542,8 @@ dependencies = [
"specs-idvs",
"spin_sleep",
"structopt",
"strum",
"strum_macros",
"tracing",
"tracing-subscriber",
"uuid",

View File

@ -207,6 +207,14 @@
(None, "common.abilities.custom.mindflayer.summonminions"),
],
),
Custom("Minotaur"): (
primary: "common.abilities.custom.minotaur.cleave",
secondary: "common.abilities.custom.minotaur.cripplingstrike",
abilities: [
(None, "common.abilities.custom.minotaur.charge"),
(None, "common.abilities.custom.minotaur.frenzy"),
],
),
Custom("Bird Large Breathe"): (
primary: "common.abilities.custom.birdlargebreathe.firebomb",
secondary: "common.abilities.custom.birdlargebreathe.triplestrike",

View File

@ -8,4 +8,5 @@ BasicMelee(
base_poise_damage: 40,
range: 5.0,
max_angle: 120.0,
damage_effect: None,
)

View File

@ -2,7 +2,7 @@ BasicBeam(
buildup_duration: 0.40,
recover_duration: 0.50,
beam_duration: 1.0,
damage: 350,
damage: 500,
tick_rate: 0.9,
range: 22.0,
max_angle: 15.0,

View File

@ -2,7 +2,7 @@ SpinMelee(
buildup_duration: 0.5,
swing_duration: 0.2,
recover_duration: 0.6,
base_damage: 80.0,
base_damage: 100.0,
base_poise_damage: 1.0,
knockback: ( strength: 7.0, direction: Towards),
range: 16.0,

View File

@ -0,0 +1,19 @@
DashMelee(
energy_cost: 0,
base_damage: 150,
scaled_damage: 600,
base_poise_damage: 25,
scaled_poise_damage: 100,
base_knockback: 10.0,
scaled_knockback: 30.0,
range: 5.0,
angle: 90.0,
energy_drain: 0,
forward_speed: 5.0,
buildup_duration: 0.4,
charge_duration: 4.0,
swing_duration: 0.1,
recover_duration: 0.5,
charge_through: false,
is_interruptible: false,
)

View File

@ -0,0 +1,18 @@
ChargedMelee(
energy_cost: 0,
energy_drain: 0,
initial_damage: 0,
scaled_damage: 500,
initial_poise_damage: 50,
scaled_poise_damage: 150,
initial_knockback: 0.0,
scaled_knockback: 0.0,
range: 5.0,
max_angle: 45.0,
speed: 1.0,
charge_duration: 1.5,
swing_duration: 0.1,
hit_timing: 0.8,
recover_duration: 0.5,
specifier: Some(GroundCleave),
)

View File

@ -0,0 +1,17 @@
BasicMelee(
energy_cost: 0,
buildup_duration: 0.3,
swing_duration: 0.1,
recover_duration: 0.6,
base_damage: 150.0,
base_poise_damage: 60.0,
knockback: 15.0,
range: 5.0,
max_angle: 60.0,
damage_effect: Some(Buff((
kind: Crippled,
dur_secs: 15.0,
strength: Value(0.5),
chance: 1.0,
))),
)

View File

@ -0,0 +1,9 @@
SelfBuff(
buildup_duration: 0.25,
cast_duration: 0.8,
recover_duration: 0.25,
buff_kind: Frenzied,
buff_strength: 0.5,
buff_duration: Some(300.0),
energy_cost: 0,
)

View File

@ -8,4 +8,5 @@ BasicMelee(
knockback: 25.0,
range: 1.2,
max_angle: 50.0,
damage_effect: None,
)

View File

@ -8,4 +8,5 @@ BasicMelee(
knockback: 0.0,
range: 3.5,
max_angle: 20.0,
damage_effect: None,
)

View File

@ -8,4 +8,5 @@ BasicMelee(
knockback: 0.0,
range: 3.5,
max_angle: 15.0,
damage_effect: None,
)

View File

@ -8,4 +8,5 @@ BasicMelee(
knockback: 0.0,
range: 3.5,
max_angle: 20.0,
damage_effect: None,
)

View File

@ -8,4 +8,5 @@ BasicMelee(
knockback: 0.0,
range: 3.5,
max_angle: 20.0,
damage_effect: None,
)

View File

@ -8,4 +8,5 @@ BasicMelee(
knockback: 0.0,
range: 3.0,
max_angle: 120.0,
damage_effect: None,
)

View File

@ -6,14 +6,14 @@ ItemDef(
hands: Two,
stats: Direct((
equip_time_secs: 0.5,
power: 1.8,
power: 1.0,
poise_strength: 1.0,
speed: 1.0,
crit_chance: 0.048611112,
crit_mult: 1.6530613,
crit_chance: 0.1,
crit_mult: 1.5,
)),
)),
quality: Low,
quality: Legendary,
tags: [],
ability_spec: Some(Custom("Axe Simple")),
ability_spec: Some(Custom("Minotaur")),
)

View File

@ -6,9 +6,9 @@ ItemDef(
hands: Two,
stats: Direct((
equip_time_secs: 0.5,
power: 4.0,
power: 2.5,
poise_strength: 1.0,
speed: 0.5,
speed: 0.8,
crit_chance: 0.078125,
crit_mult: 1.3657143,
)),

View File

@ -6,9 +6,9 @@ ItemDef(
hands: Two,
stats: Direct((
equip_time_secs: 0.4,
power: 2.3,
power: 1.67,
poise_strength: 1.5,
speed: 1.0,
speed: 1.2,
crit_chance: 0.078125,
crit_mult: 1.3657143,
)),

View File

@ -6,9 +6,9 @@ ItemDef(
hands: Two,
stats: Direct((
equip_time_secs: 0.4,
power: 4.0,
poise_strength: 1.5,
speed: 0.5,
power: 2.5,
poise_strength: 1.0,
speed: 0.8,
crit_chance: 0.078125,
crit_mult: 1.3657143,
)),

View File

@ -6,9 +6,9 @@ ItemDef(
hands: Two,
stats: Direct((
equip_time_secs: 0.4,
power: 1.8,
power: 2.0,
poise_strength: 1.5,
speed: 1.2,
speed: 1.0,
crit_chance: 0.21153846,
crit_mult: 1.4502164,
)),

View File

@ -6,9 +6,9 @@ ItemDef(
hands: Two,
stats: Direct((
equip_time_secs: 0.6,
power: 1.8,
power: 1.67,
poise_strength: 1.0,
speed: 1.6,
speed: 1.2,
crit_chance: 0.2002994,
crit_mult: 1.3798152,
)),

View File

@ -6,9 +6,9 @@ ItemDef(
hands: Two,
stats: Direct((
equip_time_secs: 0.6,
power: 3.0,
power: 2.5,
poise_strength: 1.0,
speed: 0.67,
speed: 0.8,
crit_chance: 0.1002994,
crit_mult: 1.3798152,
)),

View File

@ -39,5 +39,20 @@
("common.items.consumable.potion_med", 100),
("common.items.consumable.potion_big", 100),
("common.items.food.apple_mushroom_curry", 100),
]
],
"tier-4": [
("common.items.armor.steel.belt", 1),
("common.items.armor.steel.chest", 1),
("common.items.armor.steel.foot", 1),
("common.items.armor.steel.hand", 1),
("common.items.armor.steel.pants", 1),
("common.items.armor.steel.shoulder", 1),
("common.items.weapons.sword.cobalt-0", 1),
("common.items.weapons.axe.cobalt_axe-0", 1),
("common.items.weapons.hammer.cobalt_hammer-0", 1),
("common.items.weapons.bow.frostwood-0", 1),
("common.items.weapons.staff.frostwood_torch", 1),
("common.items.weapons.sceptre.fork0", 1),
("common.items.consumable.potion_med", 100),
],
})

View File

@ -824,5 +824,12 @@
],
threshold: 1.25,
),
GroundSlam: (
files: [
"voxygen.audio.sfx.abilities.minotaur_smash_1",
"voxygen.audio.sfx.abilities.minotaur_smash_2",
],
threshold: 0.2,
),
}
)

BIN
assets/voxygen/audio/sfx/abilities/minotaur_smash_1.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/audio/sfx/abilities/minotaur_smash_2.ogg (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/de_buffs/buff_frenzy_0.png (Stored with Git LFS) Normal file

Binary file not shown.

BIN
assets/voxygen/element/de_buffs/debuff_cripple_0.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -19,6 +19,8 @@
"buff.desc.invulnerability": "You cannot be damaged by any attack.",
"buff.title.protectingward": "Protecting Ward",
"buff.desc.protectingward": "You are protected, somewhat, from attacks.",
"buff.title.frenzied": "Frenzied",
"buff.desc.frenzied": "You are imbued with unnatural speed and can ignore minor injuries.",
// Debuffs
"buff.title.bleed": "Bleeding",
"buff.desc.bleed": "Inflicts regular damage.",
@ -26,6 +28,8 @@
"buff.desc.cursed": "You are cursed.",
"buff.title.burn": "On Fire",
"buff.desc.burn": "You are burning alive",
"buff.title.crippled": "Crippled",
"buff.desc.crippled": "Your movement is crippled as your legs are heavily injured.",
// Buffs stats
"buff.stat.health": "Restores {str_total} Health",
"buff.stat.increase_max_stamina": "Raises Maximum Stamina by {strength}",

View File

@ -7,6 +7,7 @@
"hud.outcome.burning": "died of: burning",
"hud.outcome.curse": "died of: curse",
"hud.outcome.bleeding": "died of: bleeding",
"hud.outcome.crippled": "died of: crippled",
// Chat outputs
"hud.chat.online_msg": "[{name}] is online now",

View File

@ -64,6 +64,8 @@ const int LIFESTEAL_BEAM = 22;
const int CULTIST_FLAME = 23;
const int STATIC_SMOKE = 24;
const int BLOOD = 25;
const int ENRAGED = 26;
const int BIG_SHRAPNEL = 27;
// meters per second squared (acceleration)
const float earth_gravity = 9.807;
@ -218,6 +220,17 @@ void main() {
vec4(vec3(0.25), 1),
spin_in_axis(vec3(1,0,0),0)
);
} else if (inst_mode == BIG_SHRAPNEL) {
float brown_color = 0.05 + 0.1 * rand1;
attr = Attr(
linear_motion(
vec3(0),
normalize(vec3(rand4, rand5, rand6)) * 15.0 + grav_vel(earth_gravity)
),
vec3(5 * (1 - percent())),
vec4(vec3(brown_color, brown_color / 2, 0), 1),
spin_in_axis(vec3(1,0,0),0)
);
} else if (inst_mode == FIREWORK_BLUE) {
f_reflect = 0.0; // Fire doesn't reflect light, it emits it
attr = Attr(
@ -421,6 +434,15 @@ void main() {
vec4(1, 0, 0, 1),
spin_in_axis(vec3(1,0,0),0)
);
} else if (inst_mode == ENRAGED) {
f_reflect = 0.0;
float red_color = 1.2 + 0.3 * rand3;
attr = Attr(
(inst_dir * slow_end(1.5)) + vec3(rand0, rand1, rand2) * (percent() + 2) * 0.1,
vec3((3.5 * (1 - slow_start(0.2)))),
vec4(red_color, 0.0, 0.0, 1),
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
);
} else {
attr = Attr(
linear_motion(

View File

@ -1043,7 +1043,7 @@
color: None
),
"common.items.npc_weapons.axe.minotaur_axe": (
vox_spec: ("weapon.axe.2haxe_minotaur", (-2.5, -9.0, -6.0)),
vox_spec: ("weapon.axe.2haxe_minotaur", (-2.5, -9.0, -8.0)),
color: None
),
"common.items.npc_weapons.hammer.yeti_hammer": (

Binary file not shown.

View File

@ -49,7 +49,7 @@ ron = { version = "0.6", default-features = false }
serde_json = "1.0.50"
serde_repr = "0.1.6"
# esv export
# csv export
csv = { version = "1.1.3", optional = true }
structopt = { version = "0.3.13", optional = true }
@ -59,6 +59,10 @@ slotmap = { version = "1.0", features = ["serde"] }
indexmap = "1.3.0"
slab = "0.4.2"
# Strum
strum = "0.20"
strum_macros = "0.20"
# ECS
specs = { git = "https://github.com/amethyst/specs.git", features = ["serde", "storage-event-control", "nightly"], rev = "5a9b71035007be0e3574f35184acac1cd4530496" }
specs-idvs = { git = "https://gitlab.com/veloren/specs-idvs.git", rev = "b65fb220e94f5d3c9bc30074a076149763795556" }

View File

@ -1,4 +1,8 @@
use crate::{assets, comp, npc, terrain};
use crate::{
assets,
comp::{self, buff::BuffKind},
npc, terrain,
};
use assets::AssetExt;
use hashbrown::HashMap;
use lazy_static::lazy_static;
@ -8,6 +12,7 @@ use std::{
path::Path,
str::FromStr,
};
use strum::IntoEnumIterator;
use tracing::warn;
/// Struct representing a command that a user can run from server chat.
@ -210,21 +215,40 @@ lazy_static! {
.map(|s| s.to_string())
.collect();
static ref BUFFS: Vec<String> = vec![
// Debuffs
"burning", "bleeding", "curse",
// Heal
"regeneration", "saturation", "potion", "campfire_heal",
// Outmaxing stats
"increase_max_energy", "increase_max_health",
// Defensive buffs
"invulnerability", "protecting_ward",
// One command to rule them all
"all",
]
.iter()
.map(|b| b.to_string())
.collect();
pub static ref BUFF_PARSER: HashMap<String, BuffKind> = {
let string_from_buff = |kind| match kind {
BuffKind::Burning => "burning",
BuffKind::Regeneration => "regeration",
BuffKind::Saturation => "saturation",
BuffKind::Bleeding => "bleeding",
BuffKind::Cursed => "cursed",
BuffKind::Potion => "potion",
BuffKind::CampfireHeal => "campfire_heal",
BuffKind::IncreaseMaxEnergy => "increase_max_energy",
BuffKind::IncreaseMaxHealth => "increase_max_health",
BuffKind::Invulnerability => "invulnerability",
BuffKind::ProtectingWard => "protecting_ward",
BuffKind::Frenzied => "frenzied",
BuffKind::Crippled => "crippled",
};
let mut buff_parser = HashMap::new();
BuffKind::iter().for_each(|kind| {buff_parser.insert(string_from_buff(kind).to_string(), kind);});
buff_parser
};
pub static ref BUFF_PACK: Vec<String> = {
let mut buff_pack: Vec<_> = BUFF_PARSER.keys().cloned().collect();
// Remove invulnerability as it removes debuffs
buff_pack.retain(|kind| kind != "invulnerability");
buff_pack
};
static ref BUFFS: Vec<String> = {
let mut buff_pack: Vec<_> = BUFF_PARSER.keys().cloned().collect();
// Add all as valid command
buff_pack.push("all".to_string());
buff_pack
};
static ref BLOCK_KINDS: Vec<String> = terrain::block::BLOCK_KINDS
.keys()

View File

@ -2,8 +2,8 @@ use crate::{
assets::{self, Asset},
combat::{self, CombatEffect, Knockback},
comp::{
aura, beam, inventory::item::tool::ToolKind, projectile::ProjectileConstructor, skills,
Body, CharacterState, EnergySource, LightEmitter, StateUpdate,
aura, beam, buff, inventory::item::tool::ToolKind, projectile::ProjectileConstructor,
skills, Body, CharacterState, EnergySource, LightEmitter, StateUpdate,
},
states::{
behavior::JoinData,
@ -30,6 +30,7 @@ pub enum CharacterAbilityType {
BasicBeam,
RepeaterRanged,
BasicAura,
SelfBuff,
}
impl From<&CharacterState> for CharacterAbilityType {
@ -49,6 +50,7 @@ impl From<&CharacterState> for CharacterAbilityType {
CharacterState::BasicBeam(_) => Self::BasicBeam,
CharacterState::RepeaterRanged(_) => Self::RepeaterRanged,
CharacterState::BasicAura(_) => Self::BasicAura,
CharacterState::SelfBuff(_) => Self::SelfBuff,
_ => Self::BasicMelee,
}
}
@ -66,6 +68,7 @@ pub enum CharacterAbility {
knockback: f32,
range: f32,
max_angle: f32,
damage_effect: Option<CombatEffect>,
},
BasicRanged {
energy_cost: f32,
@ -188,6 +191,7 @@ pub enum CharacterAbility {
swing_duration: f32,
hit_timing: f32,
recover_duration: f32,
specifier: Option<charged_melee::FrontendSpecifier>,
},
ChargedRanged {
energy_cost: f32,
@ -268,6 +272,15 @@ pub enum CharacterAbility {
summon_amount: u32,
summon_info: basic_summon::SummonInfo,
},
SelfBuff {
buildup_duration: f32,
cast_duration: f32,
recover_duration: f32,
buff_kind: buff::BuffKind,
buff_strength: f32,
buff_duration: Option<f32>,
energy_cost: f32,
},
}
impl Default for CharacterAbility {
@ -282,6 +295,7 @@ impl Default for CharacterAbility {
knockback: 0.0,
range: 3.5,
max_angle: 15.0,
damage_effect: None,
}
}
}
@ -313,7 +327,8 @@ impl CharacterAbility {
| CharacterAbility::ChargedMelee { energy_cost, .. }
| CharacterAbility::Shockwave { energy_cost, .. }
| CharacterAbility::BasicAura { energy_cost, .. }
| CharacterAbility::BasicBlock { energy_cost, .. } => update
| CharacterAbility::BasicBlock { energy_cost, .. }
| CharacterAbility::SelfBuff { energy_cost, .. } => update
.energy
.try_change_by(-(*energy_cost as i32), EnergySource::Ability)
.is_ok(),
@ -334,7 +349,11 @@ impl CharacterAbility {
.is_ok()
},
CharacterAbility::HealingBeam { .. } => data.combo.counter() > 0,
_ => true,
CharacterAbility::ComboMelee { .. }
| CharacterAbility::Boost { .. }
| CharacterAbility::BasicBeam { .. }
| CharacterAbility::Blink { .. }
| CharacterAbility::BasicSummon { .. } => true,
}
}
@ -586,6 +605,18 @@ impl CharacterAbility {
*cast_duration /= speed;
*recover_duration /= speed;
},
SelfBuff {
ref mut buff_strength,
ref mut buildup_duration,
ref mut cast_duration,
ref mut recover_duration,
..
} => {
*buff_strength *= power;
*buildup_duration /= speed;
*cast_duration /= speed;
*recover_duration /= speed;
},
}
self
}
@ -605,7 +636,8 @@ impl CharacterAbility {
| Shockwave { energy_cost, .. }
| HealingBeam { energy_cost, .. }
| BasicAura { energy_cost, .. }
| BasicBlock { energy_cost, .. } => *energy_cost as u32,
| BasicBlock { energy_cost, .. }
| SelfBuff { energy_cost, .. } => *energy_cost as u32,
BasicBeam { energy_drain, .. } => {
if *energy_drain > f32::EPSILON {
1
@ -1164,6 +1196,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState {
knockback,
range,
max_angle,
damage_effect,
energy_cost: _,
} => CharacterState::BasicMelee(basic_melee::Data {
static_data: basic_melee::StaticData {
@ -1175,6 +1208,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState {
knockback: *knockback,
range: *range,
max_angle: *max_angle,
damage_effect: *damage_effect,
ability_info,
},
timer: Duration::default(),
@ -1419,6 +1453,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState {
recover_duration,
range,
max_angle,
specifier,
} => CharacterState::ChargedMelee(charged_melee::Data {
static_data: charged_melee::StaticData {
energy_cost: *energy_cost,
@ -1437,6 +1472,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState {
hit_timing: *hit_timing,
recover_duration: Duration::from_secs_f32(*recover_duration),
ability_info,
specifier: *specifier,
},
stage_section: StageSection::Charge,
timer: Duration::default(),
@ -1657,6 +1693,27 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState {
timer: Duration::default(),
stage_section: StageSection::Buildup,
}),
CharacterAbility::SelfBuff {
buildup_duration,
cast_duration,
recover_duration,
buff_kind,
buff_strength,
buff_duration,
energy_cost: _,
} => CharacterState::SelfBuff(self_buff::Data {
static_data: self_buff::StaticData {
buildup_duration: Duration::from_secs_f32(*buildup_duration),
cast_duration: Duration::from_secs_f32(*cast_duration),
recover_duration: Duration::from_secs_f32(*recover_duration),
buff_kind: *buff_kind,
buff_strength: *buff_strength,
buff_duration: buff_duration.map(Duration::from_secs_f32),
ability_info,
},
timer: Duration::default(),
stage_section: StageSection::Buildup,
}),
}
}
}

View File

@ -40,6 +40,7 @@ pub enum Tactic {
Mindflayer,
BirdLargeBreathe,
BirdLargeFire,
Minotaur,
}
#[derive(Copy, Clone, Debug, PartialEq)]
@ -299,10 +300,17 @@ pub struct Agent {
pub behavior: Behavior,
pub psyche: Psyche,
pub inbox: VecDeque<AgentEvent>,
pub action_timer: f32,
pub action_state: ActionState,
pub bearing: Vec2<f32>,
}
#[derive(Clone, Debug, Default)]
pub struct ActionState {
pub timer: f32,
pub counter: f32,
pub condition: bool,
}
impl Agent {
pub fn with_patrol_origin(mut self, origin: Vec3<f32>) -> Self {
self.patrol_origin = Some(origin);

View File

@ -441,10 +441,10 @@ impl Body {
biped_large::Species::Wendigo => 2800,
biped_large::Species::Troll => 2400,
biped_large::Species::Dullahan => 3000,
biped_large::Species::Mindflayer => 8000,
biped_large::Species::Mindflayer => 25000,
biped_large::Species::Tidalwarrior => 2500,
biped_large::Species::Yeti => 4000,
biped_large::Species::Minotaur => 5000,
biped_large::Species::Minotaur => 30000,
biped_large::Species::Harvester => 3000,
biped_large::Species::Blueoni => 2400,
biped_large::Species::Redoni => 2400,
@ -553,10 +553,12 @@ impl Body {
biped_large::Species::Wendigo => 80,
biped_large::Species::Troll => 60,
biped_large::Species::Dullahan => 120,
biped_large::Species::Mindflayer => 250,
biped_large::Species::Tidalwarrior => 90,
biped_large::Species::Yeti => 80,
biped_large::Species::Harvester => 80,
// Boss enemies have their health set, not adjusted by level.
biped_large::Species::Mindflayer => 0,
biped_large::Species::Minotaur => 0,
_ => 100,
},
Body::BipedSmall(_) => 10,
@ -605,7 +607,11 @@ impl Body {
pub fn combat_multiplier(&self) -> f32 {
match self {
Body::Object(_) | Body::Ship(_) => 0.0,
Body::BipedLarge(b) if matches!(b.species, biped_large::Species::Mindflayer) => 4.0,
Body::BipedLarge(b) => match b.species {
biped_large::Species::Mindflayer => 4.8,
biped_large::Species::Minotaur => 3.2,
_ => 1.0,
},
_ => 1.0,
}
}

View File

@ -9,33 +9,59 @@ use specs::{Component, DerefFlaggedStorage};
use specs_idvs::IdvStorage;
#[cfg(not(target_arch = "wasm32"))]
use std::{cmp::Ordering, time::Duration};
use strum_macros::EnumIter;
/// De/buff Kind.
/// This is used to determine what effects a buff will have
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, PartialOrd, Ord)]
#[derive(
Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, PartialOrd, Ord, EnumIter,
)]
pub enum BuffKind {
/// Does damage to a creature over time
Burning,
// Buffs
/// Restores health/time for some period
/// Strength should be 10x the healing per second
Regeneration,
/// Restores health/time for some period for consumables
/// Strength should be 10x the healing per second
Saturation,
/// Lowers health over time for some duration
Bleeding,
/// Lower a creature's max health over time
Cursed,
/// Applied when drinking a potion
/// Strength should be 10x the healing per second
Potion,
/// Applied when sitting at a campfire
/// Strength is fraction of health resotred per second
CampfireHeal,
/// Raises maximum stamina
/// Strength should be 10x the effect to max energy
IncreaseMaxEnergy,
/// Raises maximum health
/// Strength should be 10x the effect to max health
IncreaseMaxHealth,
/// Makes you immune to attacks
/// Strength does not affect this buff
Invulnerability,
/// Reduces incoming damage
/// Strength scales the damage reduction non-linearly. 0.5 provides 50% DR,
/// 1.0 provides 67% DR
ProtectingWard,
/// Increases movement speed and gives health regeneration
/// Strength scales the movement speed linearly. 0.5 is 150% speed, 1.0 is
/// 200% speed. Provides regeneration at 10x the value of the strength
Frenzied,
// Debuffs
/// Does damage to a creature over time
/// Strength should be 10x the DPS of the debuff
Burning,
/// Lowers health over time for some duration
/// Strength should be 10x the DPS of the debuff
Bleeding,
/// Lower a creature's max health over time
/// Strength only affects the target max health, 0.5 targets 50% of base
/// max, 1.0 targets 100% of base max
Cursed,
/// Reduces movement speed and causes bleeding damage
/// Strength scales the movement speed debuff non-linearly. 0.5 is 50%
/// speed, 1.0 is 33% speed. Bleeding is at 10x the value of the strength.
Crippled,
}
#[cfg(not(target_arch = "wasm32"))]
@ -54,6 +80,8 @@ impl BuffKind {
BuffKind::Invulnerability => true,
BuffKind::ProtectingWard => true,
BuffKind::Burning => false,
BuffKind::Crippled => false,
BuffKind::Frenzied => true,
}
}
@ -118,6 +146,8 @@ pub enum BuffEffect {
kind: ModifierKind,
target_fraction: f32,
},
/// Modifies move speed of target
MovementSpeed(f32),
}
/// Actual de/buff.
@ -174,6 +204,8 @@ impl Buff {
cat_ids: Vec<BuffCategory>,
source: BuffSource,
) -> Self {
// Normalized nonlinear scaling
let nn_scaling = |a| a / (a + 0.5);
let (effects, time) = match kind {
BuffKind::Bleeding => (
vec![BuffEffect::HealthChangeOverTime {
@ -235,7 +267,7 @@ impl Buff {
// Causes non-linearity in effect strength, but necessary to allow for tool
// power and other things to affect the strength. 0.5 also still provides 50%
// damage reduction.
data.strength / (0.5 + data.strength),
nn_scaling(data.strength),
)],
data.duration,
),
@ -247,6 +279,28 @@ impl Buff {
}],
data.duration,
),
BuffKind::Crippled => (
vec![
BuffEffect::MovementSpeed(1.0 - nn_scaling(data.strength)),
BuffEffect::HealthChangeOverTime {
rate: -data.strength * 40.0,
accumulated: 0.0,
kind: ModifierKind::Additive,
},
],
data.duration,
),
BuffKind::Frenzied => (
vec![
BuffEffect::MovementSpeed(1.0 + data.strength),
BuffEffect::HealthChangeOverTime {
rate: data.strength * 100.0,
accumulated: 0.0,
kind: ModifierKind::Additive,
},
],
data.duration,
),
};
Buff {
kind,

View File

@ -99,6 +99,8 @@ pub enum CharacterState {
Blink(blink::Data),
/// Summons creatures that fight for the caster
BasicSummon(basic_summon::Data),
/// Inserts a buff on the caster
SelfBuff(self_buff::Data),
}
impl CharacterState {
@ -120,6 +122,7 @@ impl CharacterState {
| CharacterState::BasicBeam(_)
| CharacterState::BasicAura(_)
| CharacterState::HealingBeam(_)
| CharacterState::SelfBuff(_)
)
}
@ -143,6 +146,7 @@ impl CharacterState {
| CharacterState::BasicBeam(_)
| CharacterState::BasicAura(_)
| CharacterState::HealingBeam(_)
| CharacterState::SelfBuff(_)
)
}

View File

@ -25,6 +25,7 @@ pub struct Stats {
pub name: String,
pub damage_reduction: f32,
pub max_health_modifier: f32,
pub move_speed_modifier: f32,
}
impl Stats {
@ -33,6 +34,7 @@ impl Stats {
name,
damage_reduction: 0.0,
max_health_modifier: 1.0,
move_speed_modifier: 1.0,
}
}
@ -43,6 +45,7 @@ impl Stats {
name: "".to_owned(),
damage_reduction: 0.0,
max_health_modifier: 1.0,
move_speed_modifier: 1.0,
}
}
@ -50,6 +53,7 @@ impl Stats {
pub fn reset_temp_modifiers(&mut self) {
self.damage_reduction = 0.0;
self.max_health_modifier = 1.0;
self.move_speed_modifier = 1.0;
}
}

View File

@ -68,6 +68,9 @@ pub enum Outcome {
pos: Vec3<f32>,
state: PoiseState,
},
GroundSlam {
pos: Vec3<f32>,
},
}
impl Outcome {
@ -81,7 +84,8 @@ impl Outcome {
| Outcome::SummonedCreature { pos, .. }
| Outcome::Damage { pos, .. }
| Outcome::Block { pos, .. }
| Outcome::PoiseChange { pos, .. } => Some(*pos),
| Outcome::PoiseChange { pos, .. }
| Outcome::GroundSlam { pos } => Some(*pos),
Outcome::BreakBlock { pos, .. } => Some(pos.map(|e| e as f32 + 0.5)),
Outcome::ExpChange { .. } | Outcome::ComboChange { .. } => None,
}

View File

@ -29,6 +29,8 @@ pub struct StaticData {
pub range: f32,
/// Max angle (45.0 will give you a 90.0 angle window)
pub max_angle: f32,
/// Adds an effect onto the main damage of the attack
pub damage_effect: Option<CombatEffect>,
/// What key is used to press ability
pub ability_info: AbilityInfo,
}
@ -50,6 +52,7 @@ impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data);
handle_orientation(data, &mut update, 1.0);
handle_move(data, &mut update, 0.7);
handle_jump(data, &mut update, 1.0);
handle_orientation(data, &mut update, 0.35);
@ -97,15 +100,20 @@ impl CharacterBehavior for Data {
.with_requirement(CombatRequirement::AnyDamage);
let energy = AttackEffect::new(None, CombatEffect::EnergyReward(50.0))
.with_requirement(CombatRequirement::AnyDamage);
let buff = CombatEffect::Buff(CombatBuff::default_physical());
let damage = AttackDamage::new(
let mut damage = AttackDamage::new(
Damage {
source: DamageSource::Melee,
value: self.static_data.base_damage as f32,
},
Some(GroupTarget::OutOfGroup),
)
.with_effect(buff);
);
match self.static_data.damage_effect {
Some(effect) => damage = damage.with_effect(effect),
None => {
let buff = CombatEffect::Buff(CombatBuff::default_physical());
damage = damage.with_effect(buff);
},
}
let (crit_chance, crit_mult) =
get_crit_data(data, self.static_data.ability_info);
let attack = Attack::default()

View File

@ -1,6 +1,8 @@
use crate::{
combat::{Attack, AttackDamage, AttackEffect, CombatBuff, CombatEffect, CombatRequirement},
comp::{tool::ToolKind, CharacterState, EnergyChange, EnergySource, Melee, StateUpdate},
event::LocalEvent,
outcome::Outcome,
states::{
behavior::{CharacterBehavior, JoinData},
utils::{StageSection, *},
@ -45,6 +47,8 @@ pub struct StaticData {
pub recover_duration: Duration,
/// What key is used to press ability
pub ability_info: AbilityInfo,
/// Used to specify the melee attack to the frontend
pub specifier: Option<FrontendSpecifier>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -199,6 +203,17 @@ impl CharacterBehavior for Data {
})
.filter(|(_, tool)| tool == &Some(ToolKind::Pick)),
});
if let Some(FrontendSpecifier::GroundCleave) = self.static_data.specifier {
// Send local event used for frontend shenanigans
update.local_events.push_front(LocalEvent::CreateOutcome(
Outcome::GroundSlam {
pos: data.pos.0
+ *data.ori.look_dir()
* (data.body.radius() + self.static_data.range),
},
));
}
} else if self.timer < self.static_data.swing_duration {
// Swings
update.character = CharacterState::ChargedMelee(Data {
@ -250,3 +265,9 @@ impl CharacterBehavior for Data {
update
}
}
/// Used to specify a particular effect for frontend purposes
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum FrontendSpecifier {
GroundCleave,
}

View File

@ -71,12 +71,12 @@ impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data);
handle_orientation(data, &mut update, 1.0);
handle_move(data, &mut update, 0.1);
match self.stage_section {
StageSection::Buildup => {
if self.timer < self.static_data.buildup_duration {
handle_orientation(data, &mut update, 1.0);
// Build up
update.character = CharacterState::DashMelee(Data {
timer: self
@ -106,6 +106,7 @@ impl CharacterBehavior for Data {
/ self.static_data.charge_duration.as_secs_f32())
.min(1.0);
handle_orientation(data, &mut update, 0.6);
handle_forced_movement(
data,
&mut update,

View File

@ -21,6 +21,7 @@ pub mod idle;
pub mod leap_melee;
pub mod repeater_ranged;
pub mod roll;
pub mod self_buff;
pub mod shockwave;
pub mod sit;
pub mod sneak;

View File

@ -0,0 +1,131 @@
use crate::{
comp::{
buff::{Buff, BuffChange, BuffData, BuffKind, BuffSource},
CharacterState, StateUpdate,
},
event::ServerEvent,
states::{
behavior::{CharacterBehavior, JoinData},
utils::*,
},
};
use serde::{Deserialize, Serialize};
use std::time::Duration;
/// Separated out to condense update portions of character state
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct StaticData {
/// How long until state should create the aura
pub buildup_duration: Duration,
/// How long the state is creating an aura
pub cast_duration: Duration,
/// How long the state has until exiting
pub recover_duration: Duration,
/// What kind of buff is created
pub buff_kind: BuffKind,
/// Strength of the created buff
pub buff_strength: f32,
/// How long buff lasts
pub buff_duration: Option<Duration>,
/// What key is used to press ability
pub ability_info: AbilityInfo,
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Data {
/// Struct containing data that does not change over the course of the
/// character state
pub static_data: StaticData,
/// Timer for each stage
pub timer: Duration,
/// What section the character stage is in
pub stage_section: StageSection,
}
impl CharacterBehavior for Data {
fn behavior(&self, data: &JoinData) -> StateUpdate {
let mut update = StateUpdate::from(data);
handle_move(data, &mut update, 0.8);
handle_jump(data, &mut update, 1.0);
match self.stage_section {
StageSection::Buildup => {
if self.timer < self.static_data.buildup_duration {
// Build up
update.character = CharacterState::SelfBuff(Data {
timer: self
.timer
.checked_add(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default(),
..*self
});
} else {
// Creates buff
let buff = Buff::new(
self.static_data.buff_kind,
BuffData {
strength: self.static_data.buff_strength,
duration: self.static_data.buff_duration,
},
Vec::new(),
BuffSource::Character { by: *data.uid },
);
update.server_events.push_front(ServerEvent::Buff {
entity: data.entity,
buff_change: BuffChange::Add(buff),
});
// Build up
update.character = CharacterState::SelfBuff(Data {
timer: Duration::default(),
stage_section: StageSection::Cast,
..*self
});
}
},
StageSection::Cast => {
if self.timer < self.static_data.cast_duration {
// Cast
update.character = CharacterState::SelfBuff(Data {
timer: self
.timer
.checked_add(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default(),
..*self
});
} else {
update.character = CharacterState::SelfBuff(Data {
timer: Duration::default(),
stage_section: StageSection::Recover,
..*self
});
}
},
StageSection::Recover => {
if self.timer < self.static_data.recover_duration {
update.character = CharacterState::SelfBuff(Data {
timer: self
.timer
.checked_add(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default(),
..*self
});
} else {
// Done
update.character = CharacterState::Wielding;
}
},
_ => {
// If it somehow ends up in an incorrect stage section
update.character = CharacterState::Wielding;
},
}
// At end of state logic so an interrupt isn't overwritten
if !input_is_pressed(data, self.static_data.ability_info.input) {
handle_state_interrupt(data, &mut update, false);
}
update
}
}

View File

@ -175,7 +175,6 @@ impl CharacterBehavior for Data {
},
0.1,
);
handle_orientation(data, &mut update, 1.0);
}
// Swings

View File

@ -71,7 +71,7 @@ impl Body {
biped_large::Species::Occultsaurok => 100.0,
biped_large::Species::Mightysaurok => 100.0,
biped_large::Species::Mindflayer => 90.0,
biped_large::Species::Minotaur => 90.0,
biped_large::Species::Minotaur => 60.0,
_ => 80.0,
},
Body::BirdMedium(_) => 80.0,
@ -243,6 +243,8 @@ pub fn handle_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
/// Updates components to move player as if theyre on ground or in air
#[allow(clippy::assign_op_pattern)] // TODO: Pending review in #587
fn basic_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) {
let efficiency = efficiency * data.stats.move_speed_modifier;
let accel = if data.physics.on_ground {
data.body.base_accel()
} else {
@ -267,6 +269,8 @@ pub fn handle_forced_movement(
movement: ForcedMovement,
efficiency: f32,
) {
let efficiency = efficiency * data.stats.move_speed_modifier;
match movement {
ForcedMovement::Forward { strength } => {
if let Some(accel) = data.physics.on_ground.then_some(data.body.base_accel()) {
@ -324,6 +328,7 @@ pub fn handle_orientation(data: &JoinData, update: &mut StateUpdate, efficiency:
/// Updates components to move player as if theyre swimming
fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32, submersion: f32) -> bool {
let efficiency = efficiency * data.stats.move_speed_modifier;
if let Some(force) = data.body.swim_thrust() {
let force = efficiency * force;
let mut water_accel = force / data.mass.0;
@ -361,6 +366,8 @@ fn swim_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32, submers
/// Updates components to move entity as if it's flying
pub fn fly_move(data: &JoinData, update: &mut StateUpdate, efficiency: f32) -> bool {
let efficiency = efficiency * data.stats.move_speed_modifier;
let glider = match data.character {
CharacterState::Glide(data) => Some(data),
_ => None,

View File

@ -189,6 +189,9 @@ impl<'a> System<'a> for Sys {
stat.max_health_modifier *= current_fraction;
}
},
BuffEffect::MovementSpeed(ms) => {
stat.move_speed_modifier *= *ms;
},
};
}
}

View File

@ -341,6 +341,7 @@ impl<'a> System<'a> for Sys {
CharacterState::HealingBeam(data) => data.handle_event(&j, action),
CharacterState::Blink(data) => data.handle_event(&j, action),
CharacterState::BasicSummon(data) => data.handle_event(&j, action),
CharacterState::SelfBuff(data) => data.handle_event(&j, action),
};
local_emitter.append(&mut state_update.local_events);
server_emitter.append(&mut state_update.server_events);
@ -395,6 +396,7 @@ impl<'a> System<'a> for Sys {
CharacterState::HealingBeam(data) => data.behavior(&j),
CharacterState::Blink(data) => data.behavior(&j),
CharacterState::BasicSummon(data) => data.behavior(&j),
CharacterState::SelfBuff(data) => data.behavior(&j),
};
local_emitter.append(&mut state_update.local_events);

View File

@ -248,7 +248,8 @@ impl<'a> System<'a> for Sys {
| CharacterState::BasicAura { .. }
| CharacterState::HealingBeam { .. }
| CharacterState::Blink { .. }
| CharacterState::BasicSummon { .. } => {
| CharacterState::BasicSummon { .. }
| CharacterState::SelfBuff { .. } => {
if energy.get_unchecked().regen_rate != 0.0 {
energy.get_mut_unchecked().regen_rate = 0.0
}

View File

@ -11,7 +11,7 @@ use authc::Uuid;
use chrono::{NaiveTime, Timelike};
use common::{
assets,
cmd::{ChatCommand, CHAT_COMMANDS, CHAT_SHORTCUTS},
cmd::{ChatCommand, BUFF_PACK, BUFF_PARSER, CHAT_COMMANDS, CHAT_SHORTCUTS},
comp::{
self,
aura::{Aura, AuraKind, AuraTarget},
@ -36,13 +36,11 @@ use common_net::{
};
use common_state::{BuildAreaError, BuildAreas};
use core::{convert::TryFrom, ops::Not, time::Duration};
use hashbrown::HashSet;
use hashbrown::{HashMap, HashSet};
use rand::Rng;
use specs::{Builder, Entity as EcsEntity, Join, WorldExt};
use wiring::{Circuit, Wire, WiringAction, WiringActionEffect, WiringElement};
use hashbrown::HashMap;
use vek::*;
use wiring::{Circuit, Wire, WiringAction, WiringActionEffect, WiringElement};
use world::util::Sampler;
use crate::{client::Client, login_provider::LoginProvider, wiring};
@ -2615,22 +2613,6 @@ fn handle_apply_buff(
args: String,
action: &ChatCommand,
) -> CmdResult<()> {
const BUFF_PACK: &[&str] = &[
// Debuffs
"burning",
"bleeding",
"curse",
// Healing
"regeneration",
"saturation",
"potion",
"campfire_heal",
// Outmaxing stats
"increase_max_energy",
"increase_max_health",
// Defensive buffs (invulnerability is skipped because it ruins all debuffs)
"protecting_ward",
];
if let (Some(buff), strength, duration) =
scan_fmt_some!(&args, &action.arg_fmt(), String, f32, f64)
{
@ -2640,7 +2622,7 @@ fn handle_apply_buff(
if buff != "all" {
cast_buff(&buff, buffdata, server, target)
} else {
for kind in BUFF_PACK {
for kind in BUFF_PACK.iter() {
cast_buff(kind, buffdata, server, target)?;
}
Ok(())
@ -2663,19 +2645,4 @@ fn cast_buff(kind: &str, data: BuffData, server: &mut Server, target: EcsEntity)
}
}
fn parse_buffkind(buff: &str) -> Option<BuffKind> {
match buff {
"burning" => Some(BuffKind::Burning),
"regeneration" => Some(BuffKind::Regeneration),
"saturation" => Some(BuffKind::Saturation),
"bleeding" => Some(BuffKind::Bleeding),
"curse" => Some(BuffKind::Cursed),
"potion" => Some(BuffKind::Potion),
"campfire_heal" => Some(BuffKind::CampfireHeal),
"increase_max_energy" => Some(BuffKind::IncreaseMaxEnergy),
"increase_max_health" => Some(BuffKind::IncreaseMaxHealth),
"invulnerability" => Some(BuffKind::Invulnerability),
"protecting_ward" => Some(BuffKind::ProtectingWard),
_ => None,
}
}
fn parse_buffkind(buff: &str) -> Option<BuffKind> { BUFF_PARSER.get(buff).copied() }

View File

@ -550,10 +550,10 @@ impl<'a> AgentData<'a> {
}
// Interact if incoming messages
if !agent.inbox.is_empty() {
agent.action_timer = 0.1;
agent.action_state.timer = 0.1;
}
if agent.action_timer > 0.0 {
if agent.action_timer
if agent.action_state.timer > 0.0 {
if agent.action_state.timer
< (if agent.behavior.is(BehaviorState::TRADING) {
TRADE_INTERACTION_TIME
} else {
@ -562,7 +562,7 @@ impl<'a> AgentData<'a> {
{
self.interact(agent, controller, &read_data, event_emitter);
} else {
agent.action_timer = 0.0;
agent.action_state.timer = 0.0;
agent.target = None;
controller.actions.push(ControlAction::Stand);
self.idle(agent, controller, &read_data);
@ -582,7 +582,7 @@ impl<'a> AgentData<'a> {
event_emitter: &mut Emitter<'_, ServerEvent>,
) {
if self.damage < HEALING_ITEM_THRESHOLD && self.heal_self(agent, controller) {
agent.action_timer = 0.01;
agent.action_state.timer = 0.01;
return;
}
@ -596,12 +596,15 @@ impl<'a> AgentData<'a> {
let dist_sqrd = self.pos.0.distance_squared(tgt_pos.0);
// Should the agent flee?
if 1.0 - agent.psyche.aggro > self.damage && self.flees {
if agent.action_timer == 0.0 && agent.behavior.can(BehaviorCapability::SPEAK) {
if agent.action_state.timer == 0.0
&& agent.behavior.can(BehaviorCapability::SPEAK)
{
let msg = "npc.speech.villager_under_attack".to_string();
event_emitter
.emit(ServerEvent::Chat(UnresolvedChatMsg::npc(*self.uid, msg)));
agent.action_timer = 0.01;
} else if agent.action_timer < FLEE_DURATION || dist_sqrd < MAX_FLEE_DIST {
agent.action_state.timer = 0.01;
} else if agent.action_state.timer < FLEE_DURATION || dist_sqrd < MAX_FLEE_DIST
{
self.flee(
agent,
controller,
@ -610,7 +613,7 @@ impl<'a> AgentData<'a> {
&read_data.dt,
);
} else {
agent.action_timer = 0.0;
agent.action_state.timer = 0.0;
agent.target = None;
self.idle(agent, controller, &read_data);
}
@ -705,11 +708,11 @@ impl<'a> AgentData<'a> {
};
if self.damage < HEALING_ITEM_THRESHOLD && self.heal_self(agent, controller) {
agent.action_timer = 0.01;
agent.action_state.timer = 0.01;
return;
}
agent.action_timer = 0.0;
agent.action_state.timer = 0.0;
if let Some((travel_to, _destination)) = &agent.rtsim_controller.travel_to {
// if it has an rtsim destination and can fly then it should
// if it is flying and bumps something above it then it should move down
@ -901,7 +904,7 @@ impl<'a> AgentData<'a> {
// .events
// .push(ControlEvent::InviteResponse(InviteResponse::Decline));
// }
agent.action_timer += read_data.dt.0;
agent.action_state.timer += read_data.dt.0;
let msg = agent.inbox.pop_back();
match msg {
Some(AgentEvent::Talk(by, subject)) => {
@ -1250,7 +1253,7 @@ impl<'a> AgentData<'a> {
if let Some(Target { target, .. }) = &agent.target {
self.look_toward(controller, read_data, target);
} else {
agent.action_timer = 0.0;
agent.action_state.timer = 0.0;
}
}
},
@ -1314,7 +1317,7 @@ impl<'a> AgentData<'a> {
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
}
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
}
/// Attempt to consume a healing item, and return whether any healing items
@ -1379,7 +1382,7 @@ impl<'a> AgentData<'a> {
read_data: &ReadData,
event_emitter: &mut Emitter<'_, ServerEvent>,
) {
agent.action_timer = 0.0;
agent.action_state.timer = 0.0;
// Search area
let target = self.cached_spatial_grid.0
@ -1538,6 +1541,7 @@ impl<'a> AgentData<'a> {
"Bird Large Breathe" => Tactic::BirdLargeBreathe,
"Bird Large Fire" => Tactic::BirdLargeFire,
"Mindflayer" => Tactic::Mindflayer,
"Minotaur" => Tactic::Minotaur,
_ => Tactic::Melee,
},
AbilitySpec::Tool(tool_kind) => tool_tactic(*tool_kind),
@ -1644,16 +1648,16 @@ impl<'a> AgentData<'a> {
Tactic::Axe => {
if dist_sqrd < min_attack_dist.powi(2) && angle < 45.0 {
controller.inputs.move_dir = Vec2::zero();
if agent.action_timer > 6.0 {
if agent.action_state.timer > 6.0 {
controller
.actions
.push(ControlAction::CancelInput(InputKind::Secondary));
agent.action_timer = 0.0;
} else if agent.action_timer > 4.0 && self.energy.current() > 10 {
agent.action_state.timer = 0.0;
} else if agent.action_state.timer > 4.0 && self.energy.current() > 10 {
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
} else if self.skill_set.has_skill(Skill::Axe(AxeSkill::UnlockLeap))
&& self.energy.current() > 800
&& thread_rng().gen_bool(0.5)
@ -1661,12 +1665,12 @@ impl<'a> AgentData<'a> {
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
} else {
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
}
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
@ -1699,16 +1703,16 @@ impl<'a> AgentData<'a> {
Tactic::Hammer => {
if dist_sqrd < min_attack_dist.powi(2) && angle < 45.0 {
controller.inputs.move_dir = Vec2::zero();
if agent.action_timer > 4.0 {
if agent.action_state.timer > 4.0 {
controller
.actions
.push(ControlAction::CancelInput(InputKind::Secondary));
agent.action_timer = 0.0;
} else if agent.action_timer > 2.0 {
agent.action_state.timer = 0.0;
} else if agent.action_state.timer > 2.0 {
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
} else if self
.skill_set
.has_skill(Skill::Hammer(HammerSkill::UnlockLeap))
@ -1718,12 +1722,12 @@ impl<'a> AgentData<'a> {
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
} else {
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
}
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
@ -1742,14 +1746,14 @@ impl<'a> AgentData<'a> {
if self
.skill_set
.has_skill(Skill::Hammer(HammerSkill::UnlockLeap))
&& agent.action_timer > 5.0
&& agent.action_state.timer > 5.0
{
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
agent.action_timer = 0.0;
agent.action_state.timer = 0.0;
} else {
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
}
} else {
controller.inputs.move_dir =
@ -1776,20 +1780,20 @@ impl<'a> AgentData<'a> {
if self
.skill_set
.has_skill(Skill::Sword(SwordSkill::UnlockSpin))
&& agent.action_timer < 2.0
&& agent.action_state.timer < 2.0
&& self.energy.current() > 600
{
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
agent.action_timer += dt.0;
} else if agent.action_timer > 2.0 {
agent.action_timer = 0.0;
agent.action_state.timer += dt.0;
} else if agent.action_state.timer > 2.0 {
agent.action_state.timer = 0.0;
} else {
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
}
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
@ -1805,13 +1809,13 @@ impl<'a> AgentData<'a> {
if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) {
controller.inputs.move_dir =
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
if agent.action_timer > 4.0 && angle < 45.0 {
if agent.action_state.timer > 4.0 && angle < 45.0 {
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
agent.action_timer = 0.0;
agent.action_state.timer = 0.0;
} else {
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
}
} else {
controller.inputs.move_dir =
@ -1886,16 +1890,17 @@ impl<'a> AgentData<'a> {
.try_normalized()
.unwrap_or_else(Vec2::zero)
* speed;
if agent.action_timer > 4.0 {
if agent.action_state.timer > 4.0 {
controller
.actions
.push(ControlAction::CancelInput(InputKind::Secondary));
agent.action_timer = 0.0;
} else if agent.action_timer > 2.0 && self.energy.current() > 300 {
agent.action_state.timer = 0.0;
} else if agent.action_state.timer > 2.0 && self.energy.current() > 300
{
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
} else if self
.skill_set
.has_skill(Skill::Bow(BowSkill::UnlockRepeater))
@ -1908,7 +1913,7 @@ impl<'a> AgentData<'a> {
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
} else {
controller
.actions
@ -1916,7 +1921,7 @@ impl<'a> AgentData<'a> {
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
}
} else {
controller.inputs.move_dir =
@ -1961,22 +1966,22 @@ impl<'a> AgentData<'a> {
.actions
.push(ControlAction::basic_input(InputKind::Roll));
} else if dist_sqrd < (5.0 * min_attack_dist).powi(2) && angle < 15.0 {
if agent.action_timer < 1.5 {
if agent.action_state.timer < 1.5 {
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
.xy()
.rotated_z(0.47 * PI)
.try_normalized()
.unwrap_or_else(Vec2::unit_y);
agent.action_timer += dt.0;
} else if agent.action_timer < 3.0 {
agent.action_state.timer += dt.0;
} else if agent.action_state.timer < 3.0 {
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
.xy()
.rotated_z(-0.47 * PI)
.try_normalized()
.unwrap_or_else(Vec2::unit_y);
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
} else {
agent.action_timer = 0.0;
agent.action_state.timer = 0.0;
}
if self
.skill_set
@ -2080,13 +2085,13 @@ impl<'a> AgentData<'a> {
if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) && angle < 90.0 {
controller.inputs.move_dir =
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
if agent.action_timer > 5.0 {
if agent.action_state.timer > 5.0 {
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
agent.action_timer = 0.0;
agent.action_state.timer = 0.0;
} else {
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
}
} else {
controller.inputs.move_dir =
@ -2116,7 +2121,7 @@ impl<'a> AgentData<'a> {
} else if dist_sqrd < ((radius as f32 + 1.0) * min_attack_dist).powi(2)
&& dist_sqrd > (radius as f32 * min_attack_dist).powi(2)
{
if agent.action_timer < circle_time as f32 {
if agent.action_state.timer < circle_time as f32 {
let move_dir = (tgt_pos.0 - self.pos.0)
.xy()
.rotated_z(0.47 * PI)
@ -2133,16 +2138,16 @@ impl<'a> AgentData<'a> {
.1
.map_or(true, |b| b.is_some());
if obstacle_left {
agent.action_timer = circle_time as f32;
agent.action_state.timer = circle_time as f32;
}
controller.inputs.move_dir = move_dir;
agent.action_timer += dt.0;
} else if agent.action_timer < circle_time as f32 + 0.5 {
agent.action_state.timer += dt.0;
} else if agent.action_state.timer < circle_time as f32 + 0.5 {
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
agent.action_timer += dt.0;
} else if agent.action_timer < 2.0 * circle_time as f32 + 0.5 {
agent.action_state.timer += dt.0;
} else if agent.action_state.timer < 2.0 * circle_time as f32 + 0.5 {
let move_dir = (tgt_pos.0 - self.pos.0)
.xy()
.rotated_z(-0.47 * PI)
@ -2159,20 +2164,20 @@ impl<'a> AgentData<'a> {
.1
.map_or(true, |b| b.is_some());
if obstacle_right {
agent.action_timer = 2.0 * circle_time as f32 + 0.5;
agent.action_state.timer = 2.0 * circle_time as f32 + 0.5;
}
controller.inputs.move_dir = move_dir;
agent.action_timer += dt.0;
} else if agent.action_timer < 2.0 * circle_time as f32 + 1.0 {
if agent.action_timer < 2.0 * circle_time as f32 {
agent.action_timer = 2.0 * circle_time as f32;
agent.action_state.timer += dt.0;
} else if agent.action_state.timer < 2.0 * circle_time as f32 + 1.0 {
if agent.action_state.timer < 2.0 * circle_time as f32 {
agent.action_state.timer = 2.0 * circle_time as f32;
}
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
} else {
agent.action_timer = 0.0;
agent.action_state.timer = 0.0;
}
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
@ -2215,16 +2220,16 @@ impl<'a> AgentData<'a> {
},
) {
if can_see_tgt(&*terrain, self.pos, tgt_pos, dist_sqrd) && angle < 15.0 {
if agent.action_timer > 5.0 {
agent.action_timer = 0.0;
} else if agent.action_timer > 2.5 {
if agent.action_state.timer > 5.0 {
agent.action_state.timer = 0.0;
} else if agent.action_state.timer > 2.5 {
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
.xy()
.rotated_z(1.75 * PI)
.try_normalized()
.unwrap_or_else(Vec2::zero)
* speed;
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
} else {
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
.xy()
@ -2232,7 +2237,7 @@ impl<'a> AgentData<'a> {
.try_normalized()
.unwrap_or_else(Vec2::zero)
* speed;
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
}
controller
.actions
@ -2254,21 +2259,21 @@ impl<'a> AgentData<'a> {
},
Tactic::TailSlap => {
if dist_sqrd < (1.5 * min_attack_dist).powi(2) && angle < 90.0 {
if agent.action_timer > 4.0 {
if agent.action_state.timer > 4.0 {
controller
.actions
.push(ControlAction::CancelInput(InputKind::Primary));
agent.action_timer = 0.0;
} else if agent.action_timer > 1.0 {
agent.action_state.timer = 0.0;
} else if agent.action_state.timer > 1.0 {
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
} else {
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
}
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
.xy()
@ -2336,18 +2341,18 @@ impl<'a> AgentData<'a> {
Tactic::QuadLowBasic => {
if dist_sqrd < (1.5 * min_attack_dist).powi(2) {
controller.inputs.move_dir = Vec2::zero();
if agent.action_timer > 5.0 {
agent.action_timer = 0.0;
} else if agent.action_timer > 2.0 && angle < 90.0 {
if agent.action_state.timer > 5.0 {
agent.action_state.timer = 0.0;
} else if agent.action_state.timer > 2.0 && angle < 90.0 {
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
} else if angle < 90.0 {
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
}
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
@ -2410,18 +2415,18 @@ impl<'a> AgentData<'a> {
Tactic::QuadMedBasic => {
if dist_sqrd < min_attack_dist.powi(2) && angle < 90.0 {
controller.inputs.move_dir = Vec2::zero();
if agent.action_timer < 2.0 {
if agent.action_state.timer < 2.0 {
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
agent.action_timer += dt.0;
} else if agent.action_timer < 3.0 {
agent.action_state.timer += dt.0;
} else if agent.action_state.timer < 3.0 {
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
} else {
agent.action_timer = 0.0;
agent.action_state.timer = 0.0;
}
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
@ -2450,7 +2455,7 @@ impl<'a> AgentData<'a> {
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
} else if dist_sqrd < (7.0 * min_attack_dist).powi(2) && angle < 15.0 {
if agent.action_timer < 2.0 {
if agent.action_state.timer < 2.0 {
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
.xy()
.rotated_z(0.47 * PI)
@ -2459,8 +2464,8 @@ impl<'a> AgentData<'a> {
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
agent.action_timer += dt.0;
} else if agent.action_timer < 4.0 && angle < 15.0 {
agent.action_state.timer += dt.0;
} else if agent.action_state.timer < 4.0 && angle < 15.0 {
controller.inputs.move_dir = (tgt_pos.0 - self.pos.0)
.xy()
.rotated_z(-0.47 * PI)
@ -2469,14 +2474,14 @@ impl<'a> AgentData<'a> {
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
agent.action_timer += dt.0;
} else if agent.action_timer < 6.0 && angle < 15.0 {
agent.action_state.timer += dt.0;
} else if agent.action_state.timer < 6.0 && angle < 15.0 {
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
} else {
agent.action_timer = 0.0;
agent.action_state.timer = 0.0;
}
} else if dist_sqrd < MAX_CHASE_DIST.powi(2) {
if let Some((bearing, speed)) = agent.chaser.chase(
@ -2563,21 +2568,20 @@ impl<'a> AgentData<'a> {
const MINDFLAYER_ATTACK_DIST: f32 = 16.0;
const MINION_SUMMON_THRESHOLD: f32 = 0.20;
let health_fraction = self.health.map_or(0.5, |h| h.fraction());
// Extreme hack to set action_timer at start of combat
if agent.action_timer < MINION_SUMMON_THRESHOLD
&& health_fraction > MINION_SUMMON_THRESHOLD
{
agent.action_timer = health_fraction - MINION_SUMMON_THRESHOLD;
// Sets counter at start of combat
if agent.action_state.condition {
agent.action_state.counter = 1.0 - MINION_SUMMON_THRESHOLD;
agent.action_state.condition = true;
}
let mindflayer_is_far = dist_sqrd > MINDFLAYER_ATTACK_DIST.powi(2);
if agent.action_timer > health_fraction {
if agent.action_state.counter > health_fraction {
// Summon minions at particular thresholds of health
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(1)));
if matches!(self.char_state, CharacterState::BasicSummon(c) if matches!(c.stage_section, StageSection::Recover))
{
agent.action_timer -= MINION_SUMMON_THRESHOLD;
agent.action_state.counter -= MINION_SUMMON_THRESHOLD;
}
} else if mindflayer_is_far {
// If too far from target, blink to them.
@ -2605,7 +2609,7 @@ impl<'a> AgentData<'a> {
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
} else if thread_rng().gen_bool(health_fraction.into()) && angle < 30.0 {
} else if thread_rng().gen_bool(health_fraction.into()) {
// Else if at high health, use primary
controller
.actions
@ -2866,18 +2870,92 @@ impl<'a> AgentData<'a> {
self.jump_if(controller, bearing.z > 1.5);
controller.inputs.move_z = bearing.z;
}
} else if self.energy.current() > 600 && agent.action_timer < 3.0 && angle < 15.0 {
} else if self.energy.current() > 600
&& agent.action_state.timer < 3.0
&& angle < 15.0
{
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
agent.action_timer += dt.0;
} else if agent.action_timer < 6.0 && angle < 90.0 {
agent.action_state.timer += dt.0;
} else if agent.action_state.timer < 6.0 && angle < 90.0 {
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
agent.action_timer += dt.0;
agent.action_state.timer += dt.0;
} else {
agent.action_timer = 0.0;
agent.action_state.timer = 0.0;
}
},
Tactic::Minotaur => {
const MINOTAUR_FRENZY_THRESHOLD: f32 = 0.5;
const MINOTAUR_ATTACK_RANGE: f32 = 5.0;
const MINOTAUR_CHARGE_DISTANCE: f32 = 15.0;
let minotaur_attack_distance =
self.body.map_or(0.0, |b| b.radius()) + MINOTAUR_ATTACK_RANGE;
let health_fraction = self.health.map_or(1.0, |h| h.fraction());
// Sets action float at start of combat
if agent.action_state.counter < MINOTAUR_FRENZY_THRESHOLD
&& health_fraction > MINOTAUR_FRENZY_THRESHOLD
{
agent.action_state.counter = MINOTAUR_FRENZY_THRESHOLD;
}
if health_fraction < agent.action_state.counter {
// Makes minotaur buff itself with frenzy
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(1)));
if matches!(self.char_state, CharacterState::SelfBuff(c) if matches!(c.stage_section, StageSection::Recover))
{
agent.action_state.counter = 0.0;
}
} else if matches!(self.char_state, CharacterState::DashMelee(c) if !matches!(c.stage_section, StageSection::Recover))
{
// If already charging, keep charging if not in recover
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
} else if matches!(self.char_state, CharacterState::ChargedMelee(c) if matches!(c.stage_section, StageSection::Charge) && c.timer < c.static_data.charge_duration)
{
// If already charging a melee attack, keep charging it if charging
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
} else if dist_sqrd > MINOTAUR_CHARGE_DISTANCE.powi(2) {
// Charges at target if they are far enough away
if angle < 60.0 {
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(0)));
}
} else if dist_sqrd < minotaur_attack_distance.powi(2) {
if agent.action_state.condition && !self.char_state.is_attack() {
// Cripple target if not just used cripple
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
agent.action_state.condition = false;
} else if !self.char_state.is_attack() {
// Cleave target if not just used cleave
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
agent.action_state.condition = true;
}
}
// Make minotaur move towards target
if let Some((bearing, speed)) = agent.chaser.chase(
&*terrain,
self.pos.0,
self.vel.0,
tgt_pos.0,
TraversalConfig {
min_tgt_dist: 1.25,
..self.traversal_config
},
) {
controller.inputs.move_dir =
bearing.xy().try_normalized().unwrap_or_else(Vec2::zero) * speed;
}
},
}

View File

@ -63,7 +63,8 @@ impl Animation for AlphaAnimation {
let pullback = 1.0 - move3;
let move1 = move1base * pullback;
let move2 = move2base * pullback;
next.second.position = Vec3::new(0.0, 0.0, 0.0);
next.second.orientation = Quaternion::rotation_x(0.0);
next.shoulder_l.position = Vec3::new(
-s_a.shoulder.0,
s_a.shoulder.1,
@ -79,7 +80,6 @@ impl Animation for AlphaAnimation {
);
next.shoulder_r.orientation =
Quaternion::rotation_x(move1 * 0.8 + 0.6 * speednorm + (footrotl * -0.2) * speednorm);
next.torso.orientation = Quaternion::rotation_z(0.0);
next.main.position = Vec3::new(0.0, 0.0, 0.0);
next.main.orientation = Quaternion::rotation_x(0.0);
@ -231,6 +231,38 @@ impl Animation for AlphaAnimation {
* Quaternion::rotation_z(move1 * -0.5 + move2 * 0.6);
next.head.orientation = Quaternion::rotation_x(move1 * 0.3);
},
"Minotaur" => {
next.control_l.position = Vec3::new(0.0, 4.0, 5.0);
next.control_r.position = Vec3::new(0.0, 4.0, 5.0);
next.weapon_l.position = Vec3::new(
-12.0 + move1 * -9.0 + move2 * 16.0,
-6.0 + move2 * 8.0,
-18.0 + move1 * 8.0 + move2 * -4.0,
);
next.weapon_r.position = Vec3::new(
12.0 + move1 * 9.0 + move2 * -16.0,
-6.0 + move2 * 8.0,
-18.0 + move1 * 8.0 + move2 * -8.0,
);
next.weapon_l.orientation = Quaternion::rotation_x(-1.67)
* Quaternion::rotation_y(move1 * -0.3 + move2 * 1.0)
* Quaternion::rotation_z(move1 * 0.8 + move2 * -1.8);
next.weapon_r.orientation = Quaternion::rotation_x(-1.67)
* Quaternion::rotation_y(move1 * 0.3 + move2 * -0.6)
* Quaternion::rotation_z(move1 * -0.8 + move2 * 1.8);
next.control_l.orientation = Quaternion::rotation_x(1.57 + move2 * 1.0);
next.control_r.orientation = Quaternion::rotation_x(1.57 + move2 * 1.0);
next.shoulder_l.orientation = Quaternion::rotation_x(-0.3)
* Quaternion::rotation_y(move1 * 0.7 + move2 * -0.7);
next.shoulder_r.orientation = Quaternion::rotation_x(-0.3)
* Quaternion::rotation_y(move1 * -0.7 + move2 * 0.7);
next.head.orientation =
Quaternion::rotation_x(move1 * -0.6 + move2 * 0.4)
},
_ => {},
}
}

View File

@ -0,0 +1,159 @@
use super::{
super::{vek::*, Animation},
BipedLargeSkeleton, SkeletonAttr,
};
use common::{
comp::item::tool::{AbilitySpec, ToolKind},
states::utils::StageSection,
};
use std::f32::consts::PI;
pub struct ChargeMeleeAnimation;
impl Animation for ChargeMeleeAnimation {
#[allow(clippy::type_complexity)]
type Dependency<'a> = (
(Option<ToolKind>, Option<&'a AbilitySpec>),
(Option<ToolKind>, Option<&'a AbilitySpec>),
Vec3<f32>,
f32,
Option<StageSection>,
f32,
);
type Skeleton = BipedLargeSkeleton;
#[cfg(feature = "use-dyn-lib")]
const UPDATE_FN: &'static [u8] = b"biped_large_chargemelee\0";
#[cfg_attr(feature = "be-dyn-lib", export_name = "biped_large_chargemelee")]
#[allow(clippy::approx_constant)] // TODO: Pending review in #587
fn update_skeleton_inner<'a>(
skeleton: &Self::Skeleton,
(
(active_tool_kind, active_tool_spec),
_second_tool,
velocity,
_global_time,
stage_section,
acc_vel,
): Self::Dependency<'a>,
anim_time: f32,
rate: &mut f32,
s_a: &SkeletonAttr,
) -> Self::Skeleton {
*rate = 1.0;
let mut next = (*skeleton).clone();
let speed = Vec2::<f32>::from(velocity).magnitude();
let lab: f32 = 0.65 * s_a.tempo;
let speednorm = (speed / 12.0).powf(0.4);
let foothoril = (acc_vel * lab + PI * 1.45).sin() * speednorm;
let foothorir = (acc_vel * lab + PI * (0.45)).sin() * speednorm;
let footrotl = ((1.0 / (0.5 + (0.5) * ((acc_vel * lab + PI * 1.4).sin()).powi(2))).sqrt())
* ((acc_vel * lab + PI * 1.4).sin());
let footrotr = ((1.0 / (0.5 + (0.5) * ((acc_vel * lab + PI * 0.4).sin()).powi(2))).sqrt())
* ((acc_vel * lab + PI * 0.4).sin());
let (move1base, move2base, movement3, tension) = match stage_section {
Some(StageSection::Charge) => (
(anim_time.powf(0.25)).min(1.0),
0.0,
0.0,
(anim_time * 100.0).sin(),
),
Some(StageSection::Swing) => (1.0, anim_time.powf(0.25), 0.0, 0.0),
Some(StageSection::Recover) => (1.0, 1.0, anim_time.powi(4), 0.0),
_ => (0.0, 0.0, 0.0, 0.0),
};
let pullback = 1.0 - movement3;
let move1 = move1base * pullback;
let move2 = move2base * pullback;
next.second.position = Vec3::new(0.0, 0.0, 0.0);
next.second.orientation = Quaternion::rotation_x(0.0);
next.shoulder_l.position = Vec3::new(
-s_a.shoulder.0,
s_a.shoulder.1,
s_a.shoulder.2 - foothorir * 1.0,
);
next.shoulder_l.orientation =
Quaternion::rotation_x(move1 * 0.8 + 0.6 * speednorm + (footrotr * -0.2) * speednorm);
next.shoulder_r.position = Vec3::new(
s_a.shoulder.0,
s_a.shoulder.1,
s_a.shoulder.2 - foothoril * 1.0,
);
next.shoulder_r.orientation =
Quaternion::rotation_x(move1 * 0.8 + 0.6 * speednorm + (footrotl * -0.2) * speednorm);
next.torso.orientation = Quaternion::rotation_z(0.0);
next.main.position = Vec3::new(0.0, 0.0, 0.0);
next.main.orientation = Quaternion::rotation_x(0.0);
next.hand_l.position = Vec3::new(0.0, 0.0, s_a.grip.0);
next.hand_r.position = Vec3::new(0.0, 0.0, s_a.grip.0);
next.hand_l.orientation = Quaternion::rotation_x(0.0);
next.hand_r.orientation = Quaternion::rotation_x(0.0);
#[allow(clippy::single_match)]
match active_tool_kind {
Some(ToolKind::Natural) => {
if let Some(AbilitySpec::Custom(spec)) = active_tool_spec {
match spec.as_str() {
"Minotaur" => {
next.upper_torso.orientation =
Quaternion::rotation_x(move1 * 0.3 + move2 * -0.9);
next.lower_torso.orientation =
Quaternion::rotation_x(move1 * -0.3 + move2 * 0.9);
next.head.orientation =
Quaternion::rotation_x(move1 * -0.5 + move2 * 0.5);
next.control_l.position = Vec3::new(0.0, 4.0, 5.0);
next.control_r.position = Vec3::new(0.0, 4.0, 5.0);
next.weapon_l.position = Vec3::new(
-12.0 + move2 * 5.0,
-6.0 + move1 * 22.0 + move2 * 8.0,
-18.0 + move1 * 16.0 + move2 * -19.0,
);
next.weapon_r.position = Vec3::new(
12.0 + move2 * -5.0,
-6.0 + move1 * 22.0 + move2 * 8.0,
-18.0 + move1 * 14.0 + move2 * -19.0,
);
next.torso.position = Vec3::new(0.0, move2 * 1.5, 0.0);
next.weapon_l.orientation =
Quaternion::rotation_x(
-1.67 + move1 * 2.8 + tension * 0.03 + move2 * -2.3,
) * Quaternion::rotation_y(move1 * 0.3 + move2 * 0.5);
next.weapon_r.orientation = Quaternion::rotation_x(
-1.67 + move1 * 1.6 + tension * -0.03 + move2 * -0.7,
) * Quaternion::rotation_y(
move1 * -0.3 + move2 * -0.5,
) * Quaternion::rotation_z(0.0);
next.control_l.orientation =
Quaternion::rotation_x(1.57 + move1 * 0.2 + move2 * 0.1);
next.control_r.orientation =
Quaternion::rotation_x(1.57 + move1 * 0.4 + move2 * -0.4);
next.control.orientation =
Quaternion::rotation_x(0.0) * Quaternion::rotation_y(0.0);
next.shoulder_l.orientation =
Quaternion::rotation_x(-0.3 + move1 * 1.0);
next.shoulder_r.orientation =
Quaternion::rotation_x(-0.3 + move1 * 1.0);
},
_ => {},
}
}
},
_ => {},
}
next
}
}

View File

@ -2,15 +2,19 @@ use super::{
super::{vek::*, Animation},
BipedLargeSkeleton, SkeletonAttr,
};
use common::{comp::item::ToolKind, states::utils::StageSection};
use common::{
comp::item::tool::{AbilitySpec, ToolKind},
states::utils::StageSection,
};
use std::f32::consts::PI;
pub struct DashAnimation;
impl Animation for DashAnimation {
#[allow(clippy::type_complexity)]
type Dependency<'a> = (
Option<ToolKind>,
Option<ToolKind>,
(Option<ToolKind>, Option<&'a AbilitySpec>),
(Option<ToolKind>, Option<&'a AbilitySpec>),
Vec3<f32>,
f32,
Option<StageSection>,
@ -25,7 +29,14 @@ impl Animation for DashAnimation {
#[allow(clippy::single_match)] // TODO: Pending review in #587
fn update_skeleton_inner<'a>(
skeleton: &Self::Skeleton,
(active_tool_kind, _second_tool_kind, velocity, _global_time, stage_section, acc_vel): Self::Dependency<'a>,
(
(active_tool_kind, active_tool_spec),
_second_tool,
velocity,
_global_time,
stage_section,
acc_vel,
): Self::Dependency<'a>,
anim_time: f32,
rate: &mut f32,
s_a: &SkeletonAttr,
@ -49,7 +60,8 @@ impl Animation for DashAnimation {
next.hand_l.position = Vec3::new(0.0, 0.0, s_a.grip.0);
next.hand_r.position = Vec3::new(0.0, 0.0, s_a.grip.0);
next.second.position = Vec3::new(0.0, 0.0, 0.0);
next.second.orientation = Quaternion::rotation_x(0.0);
next.hand_l.orientation = Quaternion::rotation_x(0.0);
next.hand_r.orientation = Quaternion::rotation_x(0.0);
let (move1base, move2base, move3base, move4) = match stage_section {
@ -136,6 +148,51 @@ impl Animation for DashAnimation {
* Quaternion::rotation_y(-1.8 + move1 * -0.2 + move3 * -0.2)
* Quaternion::rotation_z(move1 * -0.8 + move3 * -0.1);
},
Some(ToolKind::Natural) => {
if let Some(AbilitySpec::Custom(spec)) = active_tool_spec {
match spec.as_str() {
"Minotaur" => {
next.head.orientation =
Quaternion::rotation_x(move1 * 0.4 + move3 * 0.5)
* Quaternion::rotation_z(move1 * -0.3 + move3 * -0.3);
next.upper_torso.orientation =
Quaternion::rotation_x(move1 * -0.4 + move3 * 0.9)
* Quaternion::rotation_z(move1 * 0.6 + move3 * -1.5);
next.lower_torso.orientation =
Quaternion::rotation_y(move1 * -0.2 + move3 * -0.1)
* Quaternion::rotation_x(
move1 * 0.4 + move3 * -0.7 + footrotr * 0.1,
)
* Quaternion::rotation_z(move1 * -0.6 + move3 * 1.6);
next.control_l.position = Vec3::new(0.0, 4.0, 5.0);
next.control_r.position = Vec3::new(0.0, 4.0, 5.0);
next.weapon_l.position = Vec3::new(-12.0 + move1 * -3.0, -6.0, -18.0);
next.weapon_r.position = Vec3::new(
12.0 + move1 * -3.0,
-6.0 + move1 * 2.0,
-18.0 + move1 * 2.0,
);
next.weapon_l.orientation = Quaternion::rotation_x(-1.67 + move1 * 0.4)
* Quaternion::rotation_y(move1 * 0.4 + move2 * 0.2)
* Quaternion::rotation_z(move3 * -0.5);
next.weapon_r.orientation = Quaternion::rotation_x(-1.67 + move1 * 0.3)
* Quaternion::rotation_y(move1 * 0.6 + move2 * -0.6)
* Quaternion::rotation_z(move3 * -0.5);
next.control_l.orientation = Quaternion::rotation_x(1.57);
next.control_r.orientation = Quaternion::rotation_x(1.57);
next.control.orientation =
Quaternion::rotation_x(0.0) * Quaternion::rotation_y(0.0);
next.shoulder_l.orientation = Quaternion::rotation_x(-0.3);
next.shoulder_r.orientation = Quaternion::rotation_x(-0.3);
},
_ => {},
}
}
},
_ => {},
}

View File

@ -101,8 +101,11 @@ impl Animation for IdleAnimation {
next.main.orientation = Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57);
},
Some(ToolKind::Hammer) | Some(ToolKind::Axe) => {
next.main.position = Vec3::new(-10.0, -8.0, 12.0);
next.main.position = Vec3::new(-6.0, -8.0, 8.0);
next.main.orientation = Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57);
next.second.position = Vec3::new(6.0, -8.0, 8.0);
next.second.orientation =
Quaternion::rotation_y(-2.5) * Quaternion::rotation_z(1.57);
},
_ => {
next.main.position = Vec3::new(-2.0, -5.0, -6.0);

View File

@ -28,6 +28,7 @@ impl Animation for JumpAnimation {
let torso = (anim_time * lab + 1.5 * PI).sin();
let wave_slow = (anim_time * 0.8).sin();
next.hold.scale = Vec3::one() * 0.0;
next.head.scale = Vec3::one() * 1.02;
@ -57,7 +58,7 @@ impl Animation for JumpAnimation {
next.second.position = Vec3::new(0.0, 0.0, 0.0);
next.second.orientation =
Quaternion::rotation_x(PI) * Quaternion::rotation_y(0.0) * Quaternion::rotation_z(0.0);
next.second.scale = Vec3::one() * 0.0;
next.second.scale = Vec3::one() * 1.0;
match active_tool_kind {
Some(ToolKind::Bow) => {

View File

@ -3,12 +3,14 @@ pub mod beam;
pub mod beta;
pub mod blink;
pub mod charge;
pub mod chargemelee;
pub mod dash;
pub mod equip;
pub mod idle;
pub mod jump;
pub mod leapmelee;
pub mod run;
pub mod selfbuff;
pub mod shockwave;
pub mod shoot;
pub mod spin;
@ -20,11 +22,11 @@ pub mod wield;
// Reexports
pub use self::{
alpha::AlphaAnimation, beam::BeamAnimation, beta::BetaAnimation, blink::BlinkAnimation,
charge::ChargeAnimation, dash::DashAnimation, equip::EquipAnimation, idle::IdleAnimation,
jump::JumpAnimation, leapmelee::LeapAnimation, run::RunAnimation,
shockwave::ShockwaveAnimation, shoot::ShootAnimation, spin::SpinAnimation,
spinmelee::SpinMeleeAnimation, stunned::StunnedAnimation, summon::SummonAnimation,
wield::WieldAnimation,
charge::ChargeAnimation, chargemelee::ChargeMeleeAnimation, dash::DashAnimation,
equip::EquipAnimation, idle::IdleAnimation, jump::JumpAnimation, leapmelee::LeapAnimation,
run::RunAnimation, selfbuff::SelfBuffAnimation, shockwave::ShockwaveAnimation,
shoot::ShootAnimation, spin::SpinAnimation, spinmelee::SpinMeleeAnimation,
stunned::StunnedAnimation, summon::SummonAnimation, wield::WieldAnimation,
};
use super::{make_bone, vek::*, FigureBoneData, Skeleton};
@ -54,6 +56,8 @@ skeleton_impls!(struct BipedLargeSkeleton {
control,
control_l,
control_r,
weapon_l,
weapon_r,
leg_control_l,
leg_control_r,
arm_control_l,
@ -78,8 +82,11 @@ impl Skeleton for BipedLargeSkeleton {
let torso_mat = base_mat * Mat4::<f32>::from(self.torso);
let upper_torso_mat = torso_mat * upper_torso;
let control_mat = Mat4::<f32>::from(self.control);
let control_l_mat = Mat4::<f32>::from(self.control_l);
let control_r_mat = Mat4::<f32>::from(self.control_r);
let weapon_l_mat = control_mat * Mat4::<f32>::from(self.weapon_l);
let weapon_r_mat = control_mat * Mat4::<f32>::from(self.weapon_r);
let lower_torso_mat = upper_torso_mat * Mat4::<f32>::from(self.lower_torso);
let leg_l = Mat4::<f32>::from(self.leg_l);
@ -92,8 +99,6 @@ impl Skeleton for BipedLargeSkeleton {
let arm_control_r = upper_torso_mat * Mat4::<f32>::from(self.arm_control_r);
let head_mat = upper_torso_mat * Mat4::<f32>::from(self.head);
let control_mat = Mat4::<f32>::from(self.control);
let hand_l_mat = Mat4::<f32>::from(self.hand_l);
*(<&mut [_; Self::BONE_COUNT]>::try_from(&mut buf[0..Self::BONE_COUNT]).unwrap()) = [
@ -102,12 +107,16 @@ impl Skeleton for BipedLargeSkeleton {
make_bone(upper_torso_mat),
make_bone(lower_torso_mat),
make_bone(lower_torso_mat * Mat4::<f32>::from(self.tail)),
make_bone(upper_torso_mat * control_mat * Mat4::<f32>::from(self.main)),
make_bone(upper_torso_mat * control_mat * Mat4::<f32>::from(self.second)),
make_bone(upper_torso_mat * weapon_l_mat * Mat4::<f32>::from(self.main)),
make_bone(upper_torso_mat * weapon_r_mat * Mat4::<f32>::from(self.second)),
make_bone(arm_control_l * Mat4::<f32>::from(self.shoulder_l)),
make_bone(arm_control_r * Mat4::<f32>::from(self.shoulder_r)),
make_bone(arm_control_l * control_mat * control_l_mat * Mat4::<f32>::from(self.hand_l)),
make_bone(arm_control_r * control_mat * control_r_mat * Mat4::<f32>::from(self.hand_r)),
make_bone(
arm_control_l * weapon_l_mat * control_l_mat * Mat4::<f32>::from(self.hand_l),
),
make_bone(
arm_control_r * weapon_r_mat * control_r_mat * Mat4::<f32>::from(self.hand_r),
),
make_bone(leg_control_l * leg_l),
make_bone(leg_control_r * leg_r),
make_bone(leg_control_l * Mat4::<f32>::from(self.foot_l)),

View File

@ -271,12 +271,12 @@ impl Animation for RunAnimation {
s_a.upper_torso.1 + shortalt * -1.5 * speednorm,
);
next.upper_torso.orientation =
Quaternion::rotation_z(short * 0.18 * speednorm + tilt * -1.0)
Quaternion::rotation_z(short * 0.07 * speednorm + tilt * -1.0)
* Quaternion::rotation_y(tilt);
next.lower_torso.position = Vec3::new(0.0, s_a.lower_torso.0, s_a.lower_torso.1);
next.lower_torso.orientation =
Quaternion::rotation_z(short * 0.15 * speednorm + tilt * 0.5)
Quaternion::rotation_z(short * 0.05 * speednorm + tilt * 0.5)
* Quaternion::rotation_y(tilt * -0.5)
* Quaternion::rotation_x(0.14 * speednorm);
@ -287,7 +287,7 @@ impl Animation for RunAnimation {
next.tail.orientation = Quaternion::rotation_x(shortalt * 0.3 * speednorm);
next.second.position = Vec3::new(0.0, 0.0, 0.0);
next.second.orientation = Quaternion::rotation_x(PI)
next.second.orientation = Quaternion::rotation_x(0.0)
* Quaternion::rotation_y(0.0)
* Quaternion::rotation_z(0.0);
@ -308,9 +308,12 @@ impl Animation for RunAnimation {
Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57);
},
Some(ToolKind::Hammer) | Some(ToolKind::Axe) => {
next.main.position = Vec3::new(-10.0, -8.0, 12.0);
next.main.position = Vec3::new(-6.0, -8.0, 8.0);
next.main.orientation =
Quaternion::rotation_y(2.5) * Quaternion::rotation_z(1.57);
next.second.position = Vec3::new(6.0, -8.0, 8.0);
next.second.orientation =
Quaternion::rotation_y(-2.5) * Quaternion::rotation_z(1.57);
},
_ => {
next.main.position = Vec3::new(-2.0, -5.0, -6.0);

View File

@ -0,0 +1,163 @@
use super::{
super::{vek::*, Animation},
BipedLargeSkeleton, SkeletonAttr,
};
use common::{
comp::item::tool::{AbilitySpec, ToolKind},
states::utils::StageSection,
};
use std::f32::consts::PI;
pub struct SelfBuffAnimation;
impl Animation for SelfBuffAnimation {
#[allow(clippy::type_complexity)]
type Dependency<'a> = (
(Option<ToolKind>, Option<&'a AbilitySpec>),
(Option<ToolKind>, Option<&'a AbilitySpec>),
Vec3<f32>,
f32,
Option<StageSection>,
f32,
);
type Skeleton = BipedLargeSkeleton;
#[cfg(feature = "use-dyn-lib")]
const UPDATE_FN: &'static [u8] = b"biped_large_selfbuff\0";
#[cfg_attr(feature = "be-dyn-lib", export_name = "biped_large_selfbuff")]
fn update_skeleton_inner<'a>(
skeleton: &Self::Skeleton,
(
(active_tool_kind, active_tool_spec),
_second_tool,
velocity,
_global_time,
stage_section,
acc_vel,
): Self::Dependency<'a>,
anim_time: f32,
rate: &mut f32,
s_a: &SkeletonAttr,
) -> Self::Skeleton {
*rate = 1.0;
let mut next = (*skeleton).clone();
let speed = Vec2::<f32>::from(velocity).magnitude();
let lab: f32 = 0.65 * s_a.tempo;
let speednorm = (speed / 12.0).powf(0.4);
let foothoril = (acc_vel * lab + PI * 1.45).sin() * speednorm;
let foothorir = (acc_vel * lab + PI * (0.45)).sin() * speednorm;
let footrotl = ((1.0 / (0.5 + (0.5) * ((acc_vel * lab + PI * 1.4).sin()).powi(2))).sqrt())
* ((acc_vel * lab + PI * 1.4).sin());
let footrotr = ((1.0 / (0.5 + (0.5) * ((acc_vel * lab + PI * 0.4).sin()).powi(2))).sqrt())
* ((acc_vel * lab + PI * 0.4).sin());
let (move1base, movement3, tensionbase, tension2base) = match stage_section {
Some(StageSection::Buildup) => (
(anim_time.powf(0.25)).min(1.0),
0.0,
(anim_time * 10.0).sin(),
0.0,
),
Some(StageSection::Cast) => {
(1.0, 0.0, (anim_time * 30.0).sin(), (anim_time * 12.0).sin())
},
Some(StageSection::Recover) => (1.0, anim_time.powi(4), 1.0, 1.0),
_ => (0.0, 0.0, 0.0, 0.0),
};
let pullback = 1.0 - movement3;
let move1 = move1base * pullback;
let tension = tensionbase * pullback;
let tension2 = tension2base * pullback;
next.second.position = Vec3::new(0.0, 0.0, 0.0);
next.second.orientation = Quaternion::rotation_x(0.0);
next.shoulder_l.position = Vec3::new(
-s_a.shoulder.0,
s_a.shoulder.1,
s_a.shoulder.2 - foothorir * 1.0,
);
next.shoulder_l.orientation =
Quaternion::rotation_x(move1 * 0.8 + 0.6 * speednorm + (footrotr * -0.2) * speednorm);
next.shoulder_r.position = Vec3::new(
s_a.shoulder.0,
s_a.shoulder.1,
s_a.shoulder.2 - foothoril * 1.0,
);
next.shoulder_r.orientation =
Quaternion::rotation_x(move1 * 0.8 + 0.6 * speednorm + (footrotl * -0.2) * speednorm);
next.torso.orientation = Quaternion::rotation_z(0.0);
next.main.position = Vec3::new(0.0, 0.0, 0.0);
next.main.orientation = Quaternion::rotation_x(0.0);
next.hand_l.position = Vec3::new(0.0, 0.0, s_a.grip.0);
next.hand_r.position = Vec3::new(0.0, 0.0, s_a.grip.0);
next.hand_l.orientation = Quaternion::rotation_x(0.0);
next.hand_r.orientation = Quaternion::rotation_x(0.0);
// TODO: Remove clippy allow when second species is added
#[allow(clippy::single_match)]
match active_tool_kind {
Some(ToolKind::Natural) => {
if let Some(AbilitySpec::Custom(spec)) = active_tool_spec {
match spec.as_str() {
"Minotaur" => {
next.upper_torso.orientation =
Quaternion::rotation_x(move1 * -0.1 + tension2 * 0.05);
next.lower_torso.orientation =
Quaternion::rotation_x(move1 * 0.1 + tension2 * -0.05);
next.head.orientation =
Quaternion::rotation_x(move1 * 0.8 + tension2 * -0.1)
* Quaternion::rotation_y(tension2 * -0.1);
next.control_l.position = Vec3::new(0.0, 4.0, 5.0);
next.control_r.position = Vec3::new(0.0, 4.0, 5.0);
next.weapon_l.position = Vec3::new(
-12.0 + move1 * -15.0,
-6.0 + move1 * 13.0,
-18.0 + move1 * 16.0 + tension2 * 3.0,
);
next.weapon_r.position = Vec3::new(
12.0 + move1 * 1.0,
-6.0 + move1 * 7.0 + tension * 0.3,
-18.0 + move1 * -2.0,
);
next.weapon_l.orientation = Quaternion::rotation_x(-1.67 + move1 * 1.9)
* Quaternion::rotation_y(move1 * 0.25 + tension2 * 0.06)
* Quaternion::rotation_z(move1 * 1.3);
next.weapon_r.orientation = Quaternion::rotation_x(-1.67 + move1 * 0.8)
* Quaternion::rotation_y(move1 * -0.85 + tension * 0.12)
* Quaternion::rotation_z(move1 * 0.7);
next.control_l.orientation = Quaternion::rotation_x(1.57 + move1 * 0.1)
* Quaternion::rotation_y(0.0);
next.control_r.orientation = Quaternion::rotation_x(1.57 + move1 * 0.1)
* Quaternion::rotation_y(0.0);
next.control.orientation =
Quaternion::rotation_x(0.0) * Quaternion::rotation_y(0.0);
next.shoulder_l.orientation =
Quaternion::rotation_x(-0.3 + move1 * 2.2 + tension2 * 0.17)
* Quaternion::rotation_y(move1 * 0.95);
next.shoulder_r.orientation =
Quaternion::rotation_x(-0.3 + move1 * 0.1)
* Quaternion::rotation_y(move1 * -0.35);
},
_ => {},
}
}
},
_ => {},
}
next
}
}

View File

@ -65,7 +65,8 @@ impl Animation for StunnedAnimation {
let short = (acc_vel * lab).sin() * speednorm;
let shortalt = (anim_time * lab * 16.0 + PI / 2.0).sin();
next.second.position = Vec3::new(0.0, 0.0, 0.0);
next.second.orientation = Quaternion::rotation_x(0.0);
if s_a.beast {
next.jaw.position = Vec3::new(0.0, s_a.jaw.0, s_a.jaw.1);
} else {
@ -391,6 +392,24 @@ impl Animation for StunnedAnimation {
next.torso.orientation = Quaternion::rotation_x(-0.25);
}
},
"Minotaur" => {
next.control_l.position = Vec3::new(0.0, 4.0, 5.0);
next.control_r.position = Vec3::new(0.0, 4.0, 5.0);
next.weapon_l.position = Vec3::new(-12.0, -6.0, -18.0);
next.weapon_r.position = Vec3::new(12.0, -6.0, -18.0);
next.weapon_l.orientation = Quaternion::rotation_x(-1.57 - 0.1);
next.weapon_r.orientation = Quaternion::rotation_x(-1.57 - 0.1);
next.control_l.orientation = Quaternion::rotation_x(1.57);
next.control_r.orientation = Quaternion::rotation_x(1.57);
next.control.orientation =
Quaternion::rotation_x(0.0) * Quaternion::rotation_y(0.0);
next.shoulder_l.orientation = Quaternion::rotation_x(-0.3);
next.shoulder_r.orientation = Quaternion::rotation_x(-0.3);
},
_ => {},
}
}

View File

@ -91,6 +91,8 @@ impl Animation for WieldAnimation {
let short = (acc_vel * lab).sin() * speednorm;
let shortalt = (anim_time * lab * 16.0 + PI / 2.0).sin();
next.second.position = Vec3::new(0.0, 0.0, 0.0);
next.second.orientation = Quaternion::rotation_x(0.0);
if s_a.beast {
next.jaw.position = Vec3::new(0.0, s_a.jaw.0, s_a.jaw.1);
@ -169,12 +171,12 @@ impl Animation for WieldAnimation {
5.0 + s_a.grip.0 / 1.2,
-4.0 + -s_a.grip.0 / 2.0 + short * -1.5,
);
next.second.scale = Vec3::one() * 0.0;
next.control_l.orientation =
Quaternion::rotation_x(PI / 2.0) * Quaternion::rotation_y(-0.2);
next.control_r.orientation = Quaternion::rotation_x(PI / 2.2)
* Quaternion::rotation_y(0.2)
* Quaternion::rotation_z(0.0);
next.control_r.orientation =
Quaternion::rotation_x(PI / 2.2) * Quaternion::rotation_y(0.2);
next.control.orientation =
Quaternion::rotation_x(-0.2 + short * 0.2) * Quaternion::rotation_y(-0.1);
@ -430,6 +432,24 @@ impl Animation for WieldAnimation {
next.torso.orientation = Quaternion::rotation_x(-0.25);
}
},
"Minotaur" => {
next.control_l.position = Vec3::new(0.0, 4.0, 5.0);
next.control_r.position = Vec3::new(0.0, 4.0, 5.0);
next.weapon_l.position = Vec3::new(-12.0, -6.0, -18.0);
next.weapon_r.position = Vec3::new(12.0, -6.0, -18.0);
next.weapon_l.orientation = Quaternion::rotation_x(-1.57 - 0.1);
next.weapon_r.orientation = Quaternion::rotation_x(-1.57 - 0.1);
next.control_l.orientation = Quaternion::rotation_x(1.57);
next.control_r.orientation = Quaternion::rotation_x(1.57);
next.control.orientation =
Quaternion::rotation_x(0.0) * Quaternion::rotation_y(0.0);
next.shoulder_l.orientation = Quaternion::rotation_x(-0.3);
next.shoulder_r.orientation = Quaternion::rotation_x(-0.3);
},
_ => {},
}
}

View File

@ -79,6 +79,7 @@ fn maps_basic_melee() {
range: 1.0,
max_angle: 1.0,
ability_info: empty_ability_info(),
damage_effect: None,
},
timer: Duration::default(),
stage_section: states::utils::StageSection::Buildup,

View File

@ -181,6 +181,7 @@ pub enum SfxEvent {
FireShot,
FlameThrower,
PoiseChange(PoiseState),
GroundSlam,
}
#[derive(Clone, Debug, PartialEq, Deserialize, Hash, Eq)]
@ -325,6 +326,10 @@ impl SfxMgr {
false,
);
},
Outcome::GroundSlam { pos, .. } => {
let sfx_trigger_item = triggers.get_key_value(&SfxEvent::GroundSlam);
audio.emit_sfx(sfx_trigger_item, *pos, Some(1.0), false);
},
Outcome::ProjectileShot { pos, body, .. } => {
match body {
Body::Object(

View File

@ -628,6 +628,7 @@ fn insert_killing_buff(buff: BuffKind, localized_strings: &Localization, templat
BuffKind::Burning => localized_strings.get("hud.outcome.burning"),
BuffKind::Bleeding => localized_strings.get("hud.outcome.bleeding"),
BuffKind::Cursed => localized_strings.get("hud.outcome.curse"),
BuffKind::Crippled => localized_strings.get("hud.outcome.crippled"),
BuffKind::Regeneration
| BuffKind::Saturation
| BuffKind::Potion
@ -635,7 +636,8 @@ fn insert_killing_buff(buff: BuffKind, localized_strings: &Localization, templat
| BuffKind::IncreaseMaxEnergy
| BuffKind::IncreaseMaxHealth
| BuffKind::Invulnerability
| BuffKind::ProtectingWard => {
| BuffKind::ProtectingWard
| BuffKind::Frenzied => {
tracing::error!("Player was killed by a positive buff!");
localized_strings.get("hud.outcome.mysterious")
},

View File

@ -569,11 +569,13 @@ image_ids! {
buff_healthplus_0: "voxygen.element.de_buffs.buff_healthplus_0",
buff_invincibility_0: "voxygen.element.de_buffs.buff_invincibility_0",
buff_dmg_red_0: "voxygen.element.de_buffs.buff_damage_reduce_0",
buff_frenzy_0: "voxygen.element.de_buffs.buff_frenzy_0",
// Debuffs
debuff_skull_0: "voxygen.element.de_buffs.debuff_skull_0",
debuff_bleed_0: "voxygen.element.de_buffs.debuff_bleed_0",
debuff_burning_0: "voxygen.element.de_buffs.debuff_burning_0",
debuff_crippled_0: "voxygen.element.de_buffs.debuff_cripple_0",
// Animation Frames
// Buff Frame

View File

@ -3622,10 +3622,12 @@ pub fn get_buff_image(buff: BuffKind, imgs: &Imgs) -> conrod_core::image::Id {
BuffKind::IncreaseMaxHealth { .. } => imgs.buff_healthplus_0,
BuffKind::Invulnerability => imgs.buff_invincibility_0,
BuffKind::ProtectingWard => imgs.buff_dmg_red_0,
BuffKind::Frenzied { .. } => imgs.buff_frenzy_0,
// Debuffs
BuffKind::Bleeding { .. } => imgs.debuff_bleed_0,
BuffKind::Cursed { .. } => imgs.debuff_skull_0,
BuffKind::Burning { .. } => imgs.debuff_burning_0,
BuffKind::Crippled { .. } => imgs.debuff_crippled_0,
}
}
@ -3640,10 +3642,12 @@ pub fn get_buff_title(buff: BuffKind, localized_strings: &Localization) -> &str
BuffKind::IncreaseMaxEnergy { .. } => localized_strings.get("buff.title.staminaup"),
BuffKind::Invulnerability => localized_strings.get("buff.title.invulnerability"),
BuffKind::ProtectingWard => localized_strings.get("buff.title.protectingward"),
BuffKind::Frenzied => localized_strings.get("buff.title.frenzied"),
// Debuffs
BuffKind::Bleeding { .. } => localized_strings.get("buff.title.bleed"),
BuffKind::Cursed { .. } => localized_strings.get("buff.title.cursed"),
BuffKind::Burning { .. } => localized_strings.get("buff.title.burn"),
BuffKind::Crippled { .. } => localized_strings.get("buff.title.crippled"),
}
}
@ -3670,10 +3674,12 @@ pub fn get_buff_desc(buff: BuffKind, data: BuffData, localized_strings: &Localiz
BuffKind::ProtectingWard => {
Cow::Borrowed(localized_strings.get("buff.desc.protectingward"))
},
BuffKind::Frenzied => Cow::Borrowed(localized_strings.get("buff.desc.frenzied")),
// Debuffs
BuffKind::Bleeding { .. } => Cow::Borrowed(localized_strings.get("buff.desc.bleed")),
BuffKind::Cursed { .. } => Cow::Borrowed(localized_strings.get("buff.desc.cursed")),
BuffKind::Burning { .. } => Cow::Borrowed(localized_strings.get("buff.desc.burn")),
BuffKind::Crippled { .. } => Cow::Borrowed(localized_strings.get("buff.desc.crippled")),
}
}

View File

@ -118,7 +118,9 @@ pub fn consumable_desc(effects: &[Effect], i18n: &Localization) -> String {
| BuffKind::Burning
| BuffKind::CampfireHeal
| BuffKind::Cursed
| BuffKind::ProtectingWard => continue,
| BuffKind::ProtectingWard
| BuffKind::Crippled
| BuffKind::Frenzied => continue,
};
write!(&mut description, "{}", buff_desc).unwrap();
@ -138,7 +140,9 @@ pub fn consumable_desc(effects: &[Effect], i18n: &Localization) -> String {
| BuffKind::Potion
| BuffKind::CampfireHeal
| BuffKind::Cursed
| BuffKind::ProtectingWard => continue,
| BuffKind::ProtectingWard
| BuffKind::Crippled
| BuffKind::Frenzied => continue,
}
} else if let BuffKind::Saturation | BuffKind::Regeneration = buff.kind {
i18n.get("buff.text.every_second").to_string()

View File

@ -122,6 +122,8 @@ pub enum ParticleMode {
CultistFlame = 23,
StaticSmoke = 24,
Blood = 25,
Enraged = 26,
BigShrapnel = 27,
}
impl ParticleMode {

View File

@ -3495,7 +3495,6 @@ struct SidedBLCentralVoxSpec {
torso_upper: BipedLargeCentralSubSpec,
torso_lower: BipedLargeCentralSubSpec,
tail: BipedLargeCentralSubSpec,
second: BipedLargeCentralSubSpec,
}
#[derive(Deserialize)]
struct BipedLargeCentralSubSpec {
@ -3523,13 +3522,16 @@ struct BipedLargeLateralSubSpec {
lateral: VoxSimple,
}
#[derive(Deserialize)]
struct BipedLargeWeaponSpec(HashMap<String, ArmorVoxSpec>);
struct BipedLargeMainSpec(HashMap<String, ArmorVoxSpec>);
#[derive(Deserialize)]
struct BipedLargeSecondSpec(HashMap<String, ArmorVoxSpec>);
make_vox_spec!(
biped_large::Body,
struct BipedLargeSpec {
central: BipedLargeCentralSpec = "voxygen.voxel.biped_large_central_manifest",
lateral: BipedLargeLateralSpec = "voxygen.voxel.biped_large_lateral_manifest",
weapon: BipedLargeWeaponSpec = "voxygen.voxel.biped_weapon_manifest",
main: BipedLargeMainSpec = "voxygen.voxel.biped_weapon_manifest",
second: BipedLargeSecondSpec = "voxygen.voxel.biped_weapon_manifest",
},
|FigureKey { body, extra }, spec| {
const DEFAULT_LOADOUT: super::cache::CharacterCacheKey = super::cache::CharacterCacheKey {
@ -3567,15 +3569,17 @@ make_vox_spec!(
body.body_type,
)),
tool.and_then(|tool| tool.active.as_ref()).map(|tool| {
spec.weapon.read().0.mesh_main(
spec.main.read().0.mesh_main(
&tool.name,
false,
)
}),
tool.and_then(|tool| tool.active.as_ref()).map(|tool| {
spec.second.read().0.mesh_second(
&tool.name,
false,
)
}),
Some(spec.central.read().0.mesh_second(
body.species,
body.body_type,
)),
Some(spec.lateral.read().0.mesh_shoulder_l(
body.species,
body.body_type,
@ -3693,22 +3697,6 @@ impl BipedLargeCentralSpec {
(central, Vec3::from(spec.tail.offset))
}
fn mesh_second(&self, species: BLSpecies, body_type: BLBodyType) -> BoneMeshes {
let spec = match self.0.get(&(species, body_type)) {
Some(spec) => spec,
None => {
error!(
"No second weapon specification exists for the combination of {:?} and {:?}",
species, body_type
);
return load_mesh("not_found", Vec3::new(-5.0, -5.0, -2.5));
},
};
let central = graceful_load_segment(&spec.second.central.0);
(central, Vec3::from(spec.second.offset))
}
}
impl BipedLargeLateralSpec {
fn mesh_shoulder_l(&self, species: BLSpecies, body_type: BLBodyType) -> BoneMeshes {
@ -3839,7 +3827,7 @@ impl BipedLargeLateralSpec {
(lateral, Vec3::from(spec.foot_r.offset))
}
}
impl BipedLargeWeaponSpec {
impl BipedLargeMainSpec {
fn mesh_main(&self, item_definition_id: &str, flipped: bool) -> BoneMeshes {
let spec = match self.0.get(item_definition_id) {
Some(spec) => spec,
@ -3868,6 +3856,35 @@ impl BipedLargeWeaponSpec {
(tool_kind_segment, offset)
}
}
impl BipedLargeSecondSpec {
fn mesh_second(&self, item_definition_id: &str, flipped: bool) -> BoneMeshes {
let spec = match self.0.get(item_definition_id) {
Some(spec) => spec,
None => {
error!(?item_definition_id, "No tool/weapon specification exists");
return load_mesh("not_found", Vec3::new(-1.5, -1.5, -7.0));
},
};
let tool_kind_segment = if flipped {
graceful_load_segment_flipped(&spec.vox_spec.0, true)
} else {
graceful_load_segment(&spec.vox_spec.0)
};
let offset = Vec3::new(
if flipped {
0.0 - spec.vox_spec.1[0] - (tool_kind_segment.sz.x as f32)
} else {
spec.vox_spec.1[0]
},
spec.vox_spec.1[1],
spec.vox_spec.1[2],
);
(tool_kind_segment, offset)
}
}
////
#[derive(Deserialize)]
struct GolemCentralSpec(HashMap<(GSpecies, GBodyType), SidedGCentralVoxSpec>);

View File

@ -3713,7 +3713,84 @@ impl FigureMgr {
skeleton_attr,
)
},
CharacterState::BasicMelee(_) => {
CharacterState::ChargedMelee(s) => {
let stage_time = s.timer.as_secs_f32();
let stage_progress = match s.stage_section {
StageSection::Charge => {
stage_time / s.static_data.charge_duration.as_secs_f32()
},
StageSection::Swing => {
stage_time / s.static_data.swing_duration.as_secs_f32()
},
StageSection::Recover => {
stage_time / s.static_data.recover_duration.as_secs_f32()
},
_ => 0.0,
};
anim::biped_large::ChargeMeleeAnimation::update_skeleton(
&target_base,
(
(active_tool_kind, active_tool_spec),
(second_tool_kind, second_tool_spec),
rel_vel,
time,
Some(s.stage_section),
state.acc_vel,
),
stage_progress,
&mut state_animation_rate,
skeleton_attr,
)
},
CharacterState::SelfBuff(s) => {
let stage_time = s.timer.as_secs_f32();
let stage_progress = match s.stage_section {
StageSection::Buildup => {
stage_time / s.static_data.buildup_duration.as_secs_f32()
},
StageSection::Cast => {
stage_time / s.static_data.cast_duration.as_secs_f32()
},
StageSection::Recover => {
stage_time / s.static_data.recover_duration.as_secs_f32()
},
_ => 0.0,
};
anim::biped_large::SelfBuffAnimation::update_skeleton(
&target_base,
(
(active_tool_kind, active_tool_spec),
(second_tool_kind, second_tool_spec),
rel_vel,
time,
Some(s.stage_section),
state.acc_vel,
),
stage_progress,
&mut state_animation_rate,
skeleton_attr,
)
},
CharacterState::BasicMelee(s) => {
let stage_time = s.timer.as_secs_f32();
let stage_progress = match s.stage_section {
StageSection::Buildup => {
stage_time / s.static_data.buildup_duration.as_secs_f32()
},
StageSection::Swing => {
stage_time / s.static_data.swing_duration.as_secs_f32()
},
StageSection::Recover => {
stage_time / s.static_data.recover_duration.as_secs_f32()
},
_ => 0.0,
};
anim::biped_large::AlphaAnimation::update_skeleton(
&target_base,
(
@ -3721,10 +3798,10 @@ impl FigureMgr {
(second_tool_kind, second_tool_spec),
rel_vel,
time,
None,
Some(s.stage_section),
state.acc_vel,
),
state.state_time,
stage_progress,
&mut state_animation_rate,
skeleton_attr,
)
@ -3875,8 +3952,8 @@ impl FigureMgr {
anim::biped_large::DashAnimation::update_skeleton(
&target_base,
(
active_tool_kind,
second_tool_kind,
(active_tool_kind, active_tool_spec),
(second_tool_kind, second_tool_spec),
rel_vel,
time,
Some(s.stage_section),

View File

@ -215,6 +215,16 @@ impl ParticleMgr {
});
}
},
Outcome::GroundSlam { pos, .. } => {
self.particles.resize_with(self.particles.len() + 100, || {
Particle::new(
Duration::from_millis(1000),
time,
ParticleMode::BigShrapnel,
*pos,
)
});
},
Outcome::ProjectileShot { .. }
| Outcome::Beam { .. }
| Outcome::ExpChange { .. }
@ -643,6 +653,36 @@ impl ParticleMgr {
},
);
},
CharacterState::SelfBuff(c) => {
use buff::BuffKind;
if let BuffKind::Frenzied = c.static_data.buff_kind {
if matches!(c.stage_section, StageSection::Cast) {
self.particles.resize_with(
self.particles.len()
+ usize::from(
self.scheduler.heartbeats(Duration::from_millis(5)),
),
|| {
let start_pos = pos.0
+ Vec3::new(
body.radius(),
body.radius(),
body.height() / 2.0,
)
.map(|d| d * rng.gen_range(-1.0..1.0));
let end_pos = pos.0 + (start_pos - pos.0) * 6.0;
Particle::new_directed(
Duration::from_secs(1),
time,
ParticleMode::Enraged,
start_pos,
end_pos,
)
},
);
}
}
},
_ => {},
}
}
@ -818,9 +858,9 @@ impl ParticleMgr {
.join()
{
for (buff_kind, _) in buffs.kinds.iter() {
#[allow(clippy::single_match)]
use buff::BuffKind;
match buff_kind {
buff::BuffKind::Cursed | buff::BuffKind::Burning => {
BuffKind::Cursed | BuffKind::Burning => {
self.particles.resize_with(
self.particles.len()
+ usize::from(self.scheduler.heartbeats(Duration::from_millis(15))),
@ -850,6 +890,29 @@ impl ParticleMgr {
},
);
},
BuffKind::Frenzied => {
self.particles.resize_with(
self.particles.len()
+ usize::from(self.scheduler.heartbeats(Duration::from_millis(15))),
|| {
let start_pos = pos.0
+ Vec3::new(body.radius(), body.radius(), body.height() / 2.0)
.map(|d| d * rng.gen_range(-1.0..1.0));
let end_pos = start_pos
+ Vec3::unit_z() * body.height()
+ Vec3::<f32>::zero()
.map(|_| rng.gen_range(-1.0..1.0))
.normalized();
Particle::new_directed(
Duration::from_secs(1),
time,
ParticleMode::Enraged,
start_pos,
end_pos,
)
},
);
},
_ => {},
}
}