Added totem ability, totem ai, totem ability set, particles for totem abilities, and totem voxel model.

This commit is contained in:
Sam 2021-05-29 17:27:55 -05:00
parent ac2f097d80
commit 5bf99eac11
20 changed files with 213 additions and 46 deletions

View File

@ -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",

View File

@ -13,4 +13,5 @@ Shockwave(
requires_ground: false,
move_efficiency: 0.1,
damage_kind: Energy,
specifier: Fire,
)

View File

@ -13,4 +13,5 @@ Shockwave(
requires_ground: true,
move_efficiency: 0.0,
damage_kind: Crushing,
specifier: Ground,
)

View File

@ -13,4 +13,5 @@ Shockwave(
requires_ground: true,
move_efficiency: 0.05,
damage_kind: Crushing,
specifier: Ground,
)

View File

@ -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,
),

View 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,
)

View File

@ -13,4 +13,5 @@ Shockwave(
requires_ground: false,
move_efficiency: 0.1,
damage_kind: Energy,
specifier: Fire,
)

View 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")),
)

View File

@ -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

Binary file not shown.

View File

@ -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"),
)
),
})

View File

@ -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,

View File

@ -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 {

View File

@ -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)

View File

@ -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) {

View File

@ -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,
}

View File

@ -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,

View File

@ -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,

View File

@ -80,6 +80,7 @@ pub enum ParticleMode {
BigShrapnel = 27,
Laser = 28,
Bubbles = 29,
Water = 30,
}
impl ParticleMode {

View File

@ -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,
));
}
}
},
}
}
}