mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Added totem ability, totem ai, totem ability set, particles for totem abilities, and totem voxel model.
This commit is contained in:
parent
ac2f097d80
commit
5bf99eac11
@ -116,6 +116,11 @@
|
||||
(None, "common.abilities.custom.tidalwarrior.totem"),
|
||||
],
|
||||
),
|
||||
Custom("Tidal Totem"): (
|
||||
primary: "common.abilities.custom.tidalwarrior.totem_wave",
|
||||
secondary: "common.abilities.custom.tidalwarrior.totem_wave",
|
||||
abilities: [],
|
||||
),
|
||||
Custom("Quad Med Quick"): (
|
||||
primary: "common.abilities.custom.quadmedquick.triplestrike",
|
||||
secondary: "common.abilities.custom.quadmedquick.dash",
|
||||
|
@ -13,4 +13,5 @@ Shockwave(
|
||||
requires_ground: false,
|
||||
move_efficiency: 0.1,
|
||||
damage_kind: Energy,
|
||||
specifier: Fire,
|
||||
)
|
||||
|
@ -13,4 +13,5 @@ Shockwave(
|
||||
requires_ground: true,
|
||||
move_efficiency: 0.0,
|
||||
damage_kind: Crushing,
|
||||
specifier: Ground,
|
||||
)
|
@ -13,4 +13,5 @@ Shockwave(
|
||||
requires_ground: true,
|
||||
move_efficiency: 0.05,
|
||||
damage_kind: Crushing,
|
||||
specifier: Ground,
|
||||
)
|
||||
|
@ -2,12 +2,11 @@ BasicSummon(
|
||||
buildup_duration: 0.5,
|
||||
cast_duration: 1.0,
|
||||
recover_duration: 0.5,
|
||||
summon_amount: 6,
|
||||
summon_amount: 1,
|
||||
summon_info: (
|
||||
// If this is still HaniwaSentry, code reviewers open a comment. I'll know what to do.
|
||||
body: Object(HaniwaSentry),
|
||||
body: Object(SeaLantern),
|
||||
scale: None,
|
||||
health_scaling: 20,
|
||||
health_scaling: 0,
|
||||
loadout_config: None,
|
||||
skillset_config: None,
|
||||
),
|
||||
|
17
assets/common/abilities/custom/tidalwarrior/totem_wave.ron
Normal file
17
assets/common/abilities/custom/tidalwarrior/totem_wave.ron
Normal file
@ -0,0 +1,17 @@
|
||||
Shockwave(
|
||||
energy_cost: 0,
|
||||
buildup_duration: 1.4,
|
||||
swing_duration: 0.1,
|
||||
recover_duration: 0.5,
|
||||
damage: 10,
|
||||
poise_damage: 0,
|
||||
knockback: ( strength: 100.0, direction: Up),
|
||||
shockwave_angle: 360.0,
|
||||
shockwave_vertical_angle: 30.0,
|
||||
shockwave_speed: 10.0,
|
||||
shockwave_duration: 5.0,
|
||||
requires_ground: true,
|
||||
move_efficiency: 0.0,
|
||||
damage_kind: Crushing,
|
||||
specifier: Water,
|
||||
)
|
@ -13,4 +13,5 @@ Shockwave(
|
||||
requires_ground: false,
|
||||
move_efficiency: 0.1,
|
||||
damage_kind: Energy,
|
||||
specifier: Fire,
|
||||
)
|
||||
|
19
assets/common/items/npc_weapons/unique/tidal_totem.ron
Normal file
19
assets/common/items/npc_weapons/unique/tidal_totem.ron
Normal file
@ -0,0 +1,19 @@
|
||||
ItemDef(
|
||||
name: "Tidal Totem",
|
||||
description: "Yeet",
|
||||
kind: Tool((
|
||||
kind: Natural,
|
||||
hands: Two,
|
||||
stats: Direct((
|
||||
equip_time_secs: 0.01,
|
||||
power: 1.0,
|
||||
poise_strength: 1.0,
|
||||
speed: 1.0,
|
||||
crit_chance: 0.0625,
|
||||
crit_mult: 1.9142857,
|
||||
)),
|
||||
)),
|
||||
quality: Low,
|
||||
tags: [],
|
||||
ability_spec: Some(Custom("Tidal Totem")),
|
||||
)
|
@ -68,6 +68,7 @@ const int ENRAGED = 26;
|
||||
const int BIG_SHRAPNEL = 27;
|
||||
const int LASER = 28;
|
||||
const int BUBBLES = 29;
|
||||
const int WATER = 30;
|
||||
|
||||
// meters per second squared (acceleration)
|
||||
const float earth_gravity = 9.807;
|
||||
@ -501,6 +502,17 @@ void main() {
|
||||
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
|
||||
);
|
||||
break;
|
||||
case WATER:
|
||||
f_reflect = 0.0; // Magic water doesn't reflect light, it emits it
|
||||
blue_color = 1.25 + 0.2 * rand3 + 1.75 * max(floor(rand4 + 0.15), 0.0);
|
||||
size = 8.0 * (1 - slow_start(0.1)) * slow_end(0.15);
|
||||
attr = Attr(
|
||||
(inst_dir * slow_end(0.2)) + vec3(rand0, rand1, rand2) * 0.5,
|
||||
vec3(size),
|
||||
vec4(0.5 * blue_color, 0.9 * blue_color, blue_color, 1),
|
||||
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 5 + 3 * rand9)
|
||||
);
|
||||
break;
|
||||
default:
|
||||
attr = Attr(
|
||||
linear_motion(
|
||||
|
BIN
assets/voxygen/voxel/object/sea_lantern.vox
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/voxel/object/sea_lantern.vox
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -709,4 +709,14 @@
|
||||
central: ("object.haniwa_sentry.bone1"),
|
||||
)
|
||||
),
|
||||
SeaLantern: (
|
||||
bone0: (
|
||||
offset: (-4.5, -4.5, 0.0),
|
||||
central: ("object.sea_lantern"),
|
||||
),
|
||||
bone1: (
|
||||
offset: (0.0, 0.0, 0.0),
|
||||
central: ("armor.empty"),
|
||||
)
|
||||
),
|
||||
})
|
||||
|
@ -2,7 +2,7 @@ use crate::{
|
||||
assets::{self, Asset},
|
||||
combat::{self, CombatEffect, DamageKind, Knockback},
|
||||
comp::{
|
||||
aura, beam, buff, inventory::item::tool::ToolKind, projectile::ProjectileConstructor,
|
||||
self, aura, beam, buff, inventory::item::tool::ToolKind, projectile::ProjectileConstructor,
|
||||
skills, Body, CharacterState, EnergySource, LightEmitter, StateUpdate,
|
||||
},
|
||||
states::{
|
||||
@ -233,6 +233,7 @@ pub enum CharacterAbility {
|
||||
requires_ground: bool,
|
||||
move_efficiency: f32,
|
||||
damage_kind: DamageKind,
|
||||
specifier: comp::shockwave::FrontendSpecifier,
|
||||
},
|
||||
BasicBeam {
|
||||
buildup_duration: f32,
|
||||
@ -1596,6 +1597,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState {
|
||||
requires_ground,
|
||||
move_efficiency,
|
||||
damage_kind,
|
||||
specifier,
|
||||
} => CharacterState::Shockwave(shockwave::Data {
|
||||
static_data: shockwave::StaticData {
|
||||
buildup_duration: Duration::from_secs_f32(*buildup_duration),
|
||||
@ -1612,6 +1614,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState {
|
||||
move_efficiency: *move_efficiency,
|
||||
ability_info,
|
||||
damage_kind: *damage_kind,
|
||||
specifier: *specifier,
|
||||
},
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Buildup,
|
||||
|
@ -491,6 +491,7 @@ impl Body {
|
||||
object::Body::TrainingDummy => 10000,
|
||||
object::Body::Crossbow => 800,
|
||||
object::Body::HaniwaSentry => 600,
|
||||
object::Body::SeaLantern => 1000,
|
||||
_ => 10000,
|
||||
},
|
||||
Body::Golem(golem) => match golem.species {
|
||||
|
@ -83,6 +83,7 @@ make_case_elim!(
|
||||
SilverOre = 68,
|
||||
ClayRocket = 69,
|
||||
HaniwaSentry = 70,
|
||||
SeaLantern = 71,
|
||||
}
|
||||
);
|
||||
|
||||
@ -93,7 +94,7 @@ impl Body {
|
||||
}
|
||||
}
|
||||
|
||||
pub const ALL_OBJECTS: [Body; 71] = [
|
||||
pub const ALL_OBJECTS: [Body; 72] = [
|
||||
Body::Arrow,
|
||||
Body::Bomb,
|
||||
Body::Scarecrow,
|
||||
@ -165,6 +166,7 @@ pub const ALL_OBJECTS: [Body; 71] = [
|
||||
Body::GoldOre,
|
||||
Body::ClayRocket,
|
||||
Body::HaniwaSentry,
|
||||
Body::SeaLantern,
|
||||
];
|
||||
|
||||
impl From<Body> for super::Body {
|
||||
@ -245,6 +247,7 @@ impl Body {
|
||||
Body::GoldOre => "gold_ore",
|
||||
Body::ClayRocket => "clay_rocket",
|
||||
Body::HaniwaSentry => "haniwa_sentry",
|
||||
Body::SeaLantern => "sea_lantern",
|
||||
}
|
||||
}
|
||||
|
||||
@ -336,6 +339,7 @@ impl Body {
|
||||
Body::GoldOre => 1000.0,
|
||||
Body::ClayRocket => 50.0,
|
||||
Body::HaniwaSentry => 300.0,
|
||||
Body::SeaLantern => 1000.0,
|
||||
};
|
||||
|
||||
Mass(m)
|
||||
|
@ -308,6 +308,9 @@ pub fn default_main_tool(body: &Body) -> Option<Item> {
|
||||
object::Body::HaniwaSentry => Some(Item::new_from_asset_expect(
|
||||
"common.items.npc_weapons.unique.haniwa_sentry",
|
||||
)),
|
||||
object::Body::SeaLantern => Some(Item::new_from_asset_expect(
|
||||
"common.items.npc_weapons.unique.tidal_totem",
|
||||
)),
|
||||
_ => None,
|
||||
},
|
||||
Body::BipedSmall(biped_small) => match (biped_small.species, biped_small.body_type) {
|
||||
|
@ -13,6 +13,7 @@ pub struct Properties {
|
||||
pub requires_ground: bool,
|
||||
pub duration: Duration,
|
||||
pub owner: Option<Uid>,
|
||||
pub specifier: FrontendSpecifier,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
@ -43,3 +44,10 @@ pub struct ShockwaveHitEntities {
|
||||
impl Component for ShockwaveHitEntities {
|
||||
type Storage = IdvStorage<Self>;
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum FrontendSpecifier {
|
||||
Ground,
|
||||
Fire,
|
||||
Water,
|
||||
}
|
||||
|
@ -44,6 +44,8 @@ pub struct StaticData {
|
||||
pub ability_info: AbilityInfo,
|
||||
/// What kind of damage the attack does
|
||||
pub damage_kind: DamageKind,
|
||||
/// Used to specify the shockwave to the frontend
|
||||
pub specifier: shockwave::FrontendSpecifier,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
@ -108,6 +110,7 @@ impl CharacterBehavior for Data {
|
||||
attack,
|
||||
requires_ground: self.static_data.requires_ground,
|
||||
owner: Some(*data.uid),
|
||||
specifier: self.static_data.specifier,
|
||||
};
|
||||
update.server_events.push_front(ServerEvent::Shockwave {
|
||||
properties,
|
||||
|
@ -112,6 +112,7 @@ pub enum Tactic {
|
||||
Turret,
|
||||
FixedTurret,
|
||||
RotatingTurret,
|
||||
RadialTurret,
|
||||
Mindflayer,
|
||||
BirdLargeBreathe,
|
||||
BirdLargeFire,
|
||||
@ -1607,6 +1608,7 @@ impl<'a> AgentData<'a> {
|
||||
"Minotaur" => Tactic::Minotaur,
|
||||
"Clay Golem" => Tactic::ClayGolem,
|
||||
"Tidal Warrior" => Tactic::TidalWarrior,
|
||||
"Tidal Totem" => Tactic::RadialTurret,
|
||||
_ => Tactic::Melee,
|
||||
},
|
||||
AbilitySpec::Tool(tool_kind) => tool_tactic(*tool_kind),
|
||||
@ -1856,6 +1858,13 @@ impl<'a> AgentData<'a> {
|
||||
&tgt_data,
|
||||
&read_data,
|
||||
),
|
||||
Tactic::RadialTurret => self.handle_radial_turret_attack(
|
||||
agent,
|
||||
controller,
|
||||
&attack_data,
|
||||
&tgt_data,
|
||||
&read_data,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@ -2860,6 +2869,26 @@ impl<'a> AgentData<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_radial_turret_attack(
|
||||
&self,
|
||||
_agent: &mut Agent,
|
||||
controller: &mut Controller,
|
||||
attack_data: &AttackData,
|
||||
tgt_data: &TargetData,
|
||||
read_data: &ReadData,
|
||||
) {
|
||||
if can_see_tgt(
|
||||
&*read_data.terrain,
|
||||
self.pos,
|
||||
tgt_data.pos,
|
||||
attack_data.dist_sqrd,
|
||||
) {
|
||||
controller
|
||||
.actions
|
||||
.push(ControlAction::basic_input(InputKind::Primary));
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_mindflayer_attack(
|
||||
&self,
|
||||
agent: &mut Agent,
|
||||
|
@ -80,6 +80,7 @@ pub enum ParticleMode {
|
||||
BigShrapnel = 27,
|
||||
Laser = 28,
|
||||
Bubbles = 29,
|
||||
Water = 30,
|
||||
}
|
||||
|
||||
impl ParticleMode {
|
||||
|
@ -9,8 +9,8 @@ use crate::{
|
||||
use common::{
|
||||
assets::{AssetExt, DotVoxAsset},
|
||||
comp::{
|
||||
self, aura, beam, body, buff, item::Reagent, object, BeamSegment, Body, CharacterState,
|
||||
Ori, Pos, Shockwave, Vel,
|
||||
self, aura, beam, body, buff, item::Reagent, object, shockwave, BeamSegment, Body,
|
||||
CharacterState, Ori, Pos, Shockwave, Vel,
|
||||
},
|
||||
figure::Segment,
|
||||
outcome::Outcome,
|
||||
@ -1118,6 +1118,7 @@ impl ParticleMgr {
|
||||
let state = scene_data.state;
|
||||
let ecs = state.ecs();
|
||||
let time = state.get_time();
|
||||
let dt = scene_data.state.ecs().fetch::<DeltaTime>().0;
|
||||
|
||||
for (_entity, pos, ori, shockwave) in (
|
||||
&ecs.entities(),
|
||||
@ -1127,9 +1128,10 @@ impl ParticleMgr {
|
||||
)
|
||||
.join()
|
||||
{
|
||||
let elapsed = time - shockwave.creation.unwrap_or_default();
|
||||
let elapsed = time - shockwave.creation.unwrap_or(time);
|
||||
let speed = shockwave.properties.speed;
|
||||
|
||||
let distance = shockwave.properties.speed * elapsed as f32;
|
||||
let distance = speed * elapsed as f32;
|
||||
|
||||
let radians = shockwave.properties.angle.to_radians();
|
||||
|
||||
@ -1137,55 +1139,99 @@ impl ParticleMgr {
|
||||
let theta = ori_vec.y.atan2(ori_vec.x);
|
||||
let dtheta = radians / distance;
|
||||
|
||||
let heartbeats = self.scheduler.heartbeats(Duration::from_millis(2));
|
||||
// Number of particles derived from arc length (for new particles at least, old
|
||||
// can be converted later)
|
||||
let arc_length = distance * radians;
|
||||
|
||||
for heartbeat in 0..heartbeats {
|
||||
if shockwave.properties.requires_ground {
|
||||
// 1 / 3 the size of terrain voxel
|
||||
let scale = 1.0 / 3.0;
|
||||
use shockwave::FrontendSpecifier;
|
||||
match shockwave.properties.specifier {
|
||||
FrontendSpecifier::Ground => {
|
||||
let heartbeats = self.scheduler.heartbeats(Duration::from_millis(2));
|
||||
for heartbeat in 0..heartbeats {
|
||||
// 1 / 3 the size of terrain voxel
|
||||
let scale = 1.0 / 3.0;
|
||||
|
||||
let scaled_speed = shockwave.properties.speed * scale;
|
||||
let scaled_speed = speed * scale;
|
||||
|
||||
let sub_tick_interpolation = scaled_speed * 1000.0 * heartbeat as f32;
|
||||
let sub_tick_interpolation = scaled_speed * 1000.0 * heartbeat as f32;
|
||||
|
||||
let distance =
|
||||
shockwave.properties.speed * (elapsed as f32 - sub_tick_interpolation);
|
||||
let distance = speed * (elapsed as f32 - sub_tick_interpolation);
|
||||
|
||||
let particle_count_factor = radians / (3.0 * scale);
|
||||
let new_particle_count = distance * particle_count_factor;
|
||||
self.particles.reserve(new_particle_count as usize);
|
||||
let particle_count_factor = radians / (3.0 * scale);
|
||||
let new_particle_count = distance * particle_count_factor;
|
||||
self.particles.reserve(new_particle_count as usize);
|
||||
|
||||
for d in 0..(new_particle_count as i32) {
|
||||
let arc_position =
|
||||
theta - radians / 2.0 + dtheta * d as f32 / particle_count_factor;
|
||||
for d in 0..(new_particle_count as i32) {
|
||||
let arc_position =
|
||||
theta - radians / 2.0 + dtheta * d as f32 / particle_count_factor;
|
||||
|
||||
let position = pos.0
|
||||
+ distance * Vec3::new(arc_position.cos(), arc_position.sin(), 0.0);
|
||||
let position = pos.0
|
||||
+ distance * Vec3::new(arc_position.cos(), arc_position.sin(), 0.0);
|
||||
|
||||
let position_snapped = ((position / scale).floor() + 0.5) * scale;
|
||||
let position_snapped = ((position / scale).floor() + 0.5) * scale;
|
||||
|
||||
self.particles.push(Particle::new(
|
||||
Duration::from_millis(250),
|
||||
time,
|
||||
ParticleMode::GroundShockwave,
|
||||
position_snapped,
|
||||
));
|
||||
self.particles.push(Particle::new(
|
||||
Duration::from_millis(250),
|
||||
time,
|
||||
ParticleMode::GroundShockwave,
|
||||
position_snapped,
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for d in 0..3 * distance as i32 {
|
||||
let arc_position = theta - radians / 2.0 + dtheta * d as f32 / 3.0;
|
||||
},
|
||||
FrontendSpecifier::Fire => {
|
||||
let heartbeats = self.scheduler.heartbeats(Duration::from_millis(2));
|
||||
for _ in 0..heartbeats {
|
||||
for d in 0..3 * distance as i32 {
|
||||
let arc_position = theta - radians / 2.0 + dtheta * d as f32 / 3.0;
|
||||
|
||||
let position = pos.0
|
||||
+ distance * Vec3::new(arc_position.cos(), arc_position.sin(), 0.0);
|
||||
let position = pos.0
|
||||
+ distance * Vec3::new(arc_position.cos(), arc_position.sin(), 0.0);
|
||||
|
||||
self.particles.push(Particle::new(
|
||||
Duration::from_secs_f32((distance + 10.0) / 50.0),
|
||||
time,
|
||||
ParticleMode::FireShockwave,
|
||||
position,
|
||||
));
|
||||
self.particles.push(Particle::new(
|
||||
Duration::from_secs_f32((distance + 10.0) / 50.0),
|
||||
time,
|
||||
ParticleMode::FireShockwave,
|
||||
position,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
FrontendSpecifier::Water => {
|
||||
// 4 particles per unit length of arc
|
||||
let particles_per_length = (arc_length) as usize;
|
||||
let dtheta = radians / particles_per_length as f32;
|
||||
// Scales number of desired heartbeats from speed - thicker arc = higher speed =
|
||||
// lower duration = more particles
|
||||
let heartbeats = self
|
||||
.scheduler
|
||||
.heartbeats(Duration::from_secs_f32(1.0 / speed));
|
||||
|
||||
// Reserves capacity for new particles
|
||||
let new_particle_count = particles_per_length * heartbeats as usize;
|
||||
self.particles.reserve(new_particle_count);
|
||||
|
||||
for i in 0..particles_per_length {
|
||||
let angle = dtheta * i as f32;
|
||||
let direction = Vec3::new(angle.cos(), angle.sin(), 0.0);
|
||||
for j in 0..heartbeats {
|
||||
// Sub tick dt
|
||||
let dt = (j as f32 / heartbeats as f32) * dt;
|
||||
let distance = distance + speed * dt;
|
||||
let pos1 = pos.0 + distance * direction - Vec3::unit_z();
|
||||
let pos2 = pos1 + (Vec3::unit_z() + direction) * 3.0;
|
||||
let time = time + dt as f64;
|
||||
|
||||
self.particles.push(Particle::new_directed(
|
||||
Duration::from_secs_f32(0.5),
|
||||
time,
|
||||
ParticleMode::Water,
|
||||
pos1,
|
||||
pos2,
|
||||
));
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user