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"), (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"): ( Custom("Quad Med Quick"): (
primary: "common.abilities.custom.quadmedquick.triplestrike", primary: "common.abilities.custom.quadmedquick.triplestrike",
secondary: "common.abilities.custom.quadmedquick.dash", secondary: "common.abilities.custom.quadmedquick.dash",

View File

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

View File

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

View File

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

View File

@ -2,12 +2,11 @@ BasicSummon(
buildup_duration: 0.5, buildup_duration: 0.5,
cast_duration: 1.0, cast_duration: 1.0,
recover_duration: 0.5, recover_duration: 0.5,
summon_amount: 6, summon_amount: 1,
summon_info: ( summon_info: (
// If this is still HaniwaSentry, code reviewers open a comment. I'll know what to do. body: Object(SeaLantern),
body: Object(HaniwaSentry),
scale: None, scale: None,
health_scaling: 20, health_scaling: 0,
loadout_config: None, loadout_config: None,
skillset_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, requires_ground: false,
move_efficiency: 0.1, move_efficiency: 0.1,
damage_kind: Energy, 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 BIG_SHRAPNEL = 27;
const int LASER = 28; const int LASER = 28;
const int BUBBLES = 29; const int BUBBLES = 29;
const int WATER = 30;
// meters per second squared (acceleration) // meters per second squared (acceleration)
const float earth_gravity = 9.807; const float earth_gravity = 9.807;
@ -501,6 +502,17 @@ void main() {
spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9) spin_in_axis(vec3(rand6, rand7, rand8), percent() * 10 + 3 * rand9)
); );
break; 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: default:
attr = Attr( attr = Attr(
linear_motion( 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"), 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}, assets::{self, Asset},
combat::{self, CombatEffect, DamageKind, Knockback}, combat::{self, CombatEffect, DamageKind, Knockback},
comp::{ 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, skills, Body, CharacterState, EnergySource, LightEmitter, StateUpdate,
}, },
states::{ states::{
@ -233,6 +233,7 @@ pub enum CharacterAbility {
requires_ground: bool, requires_ground: bool,
move_efficiency: f32, move_efficiency: f32,
damage_kind: DamageKind, damage_kind: DamageKind,
specifier: comp::shockwave::FrontendSpecifier,
}, },
BasicBeam { BasicBeam {
buildup_duration: f32, buildup_duration: f32,
@ -1596,6 +1597,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState {
requires_ground, requires_ground,
move_efficiency, move_efficiency,
damage_kind, damage_kind,
specifier,
} => CharacterState::Shockwave(shockwave::Data { } => CharacterState::Shockwave(shockwave::Data {
static_data: shockwave::StaticData { static_data: shockwave::StaticData {
buildup_duration: Duration::from_secs_f32(*buildup_duration), buildup_duration: Duration::from_secs_f32(*buildup_duration),
@ -1612,6 +1614,7 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState {
move_efficiency: *move_efficiency, move_efficiency: *move_efficiency,
ability_info, ability_info,
damage_kind: *damage_kind, damage_kind: *damage_kind,
specifier: *specifier,
}, },
timer: Duration::default(), timer: Duration::default(),
stage_section: StageSection::Buildup, stage_section: StageSection::Buildup,

View File

@ -491,6 +491,7 @@ impl Body {
object::Body::TrainingDummy => 10000, object::Body::TrainingDummy => 10000,
object::Body::Crossbow => 800, object::Body::Crossbow => 800,
object::Body::HaniwaSentry => 600, object::Body::HaniwaSentry => 600,
object::Body::SeaLantern => 1000,
_ => 10000, _ => 10000,
}, },
Body::Golem(golem) => match golem.species { Body::Golem(golem) => match golem.species {

View File

@ -83,6 +83,7 @@ make_case_elim!(
SilverOre = 68, SilverOre = 68,
ClayRocket = 69, ClayRocket = 69,
HaniwaSentry = 70, 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::Arrow,
Body::Bomb, Body::Bomb,
Body::Scarecrow, Body::Scarecrow,
@ -165,6 +166,7 @@ pub const ALL_OBJECTS: [Body; 71] = [
Body::GoldOre, Body::GoldOre,
Body::ClayRocket, Body::ClayRocket,
Body::HaniwaSentry, Body::HaniwaSentry,
Body::SeaLantern,
]; ];
impl From<Body> for super::Body { impl From<Body> for super::Body {
@ -245,6 +247,7 @@ impl Body {
Body::GoldOre => "gold_ore", Body::GoldOre => "gold_ore",
Body::ClayRocket => "clay_rocket", Body::ClayRocket => "clay_rocket",
Body::HaniwaSentry => "haniwa_sentry", Body::HaniwaSentry => "haniwa_sentry",
Body::SeaLantern => "sea_lantern",
} }
} }
@ -336,6 +339,7 @@ impl Body {
Body::GoldOre => 1000.0, Body::GoldOre => 1000.0,
Body::ClayRocket => 50.0, Body::ClayRocket => 50.0,
Body::HaniwaSentry => 300.0, Body::HaniwaSentry => 300.0,
Body::SeaLantern => 1000.0,
}; };
Mass(m) 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( object::Body::HaniwaSentry => Some(Item::new_from_asset_expect(
"common.items.npc_weapons.unique.haniwa_sentry", "common.items.npc_weapons.unique.haniwa_sentry",
)), )),
object::Body::SeaLantern => Some(Item::new_from_asset_expect(
"common.items.npc_weapons.unique.tidal_totem",
)),
_ => None, _ => None,
}, },
Body::BipedSmall(biped_small) => match (biped_small.species, biped_small.body_type) { 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 requires_ground: bool,
pub duration: Duration, pub duration: Duration,
pub owner: Option<Uid>, pub owner: Option<Uid>,
pub specifier: FrontendSpecifier,
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
@ -43,3 +44,10 @@ pub struct ShockwaveHitEntities {
impl Component for ShockwaveHitEntities { impl Component for ShockwaveHitEntities {
type Storage = IdvStorage<Self>; 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, pub ability_info: AbilityInfo,
/// What kind of damage the attack does /// What kind of damage the attack does
pub damage_kind: DamageKind, pub damage_kind: DamageKind,
/// Used to specify the shockwave to the frontend
pub specifier: shockwave::FrontendSpecifier,
} }
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -108,6 +110,7 @@ impl CharacterBehavior for Data {
attack, attack,
requires_ground: self.static_data.requires_ground, requires_ground: self.static_data.requires_ground,
owner: Some(*data.uid), owner: Some(*data.uid),
specifier: self.static_data.specifier,
}; };
update.server_events.push_front(ServerEvent::Shockwave { update.server_events.push_front(ServerEvent::Shockwave {
properties, properties,

View File

@ -112,6 +112,7 @@ pub enum Tactic {
Turret, Turret,
FixedTurret, FixedTurret,
RotatingTurret, RotatingTurret,
RadialTurret,
Mindflayer, Mindflayer,
BirdLargeBreathe, BirdLargeBreathe,
BirdLargeFire, BirdLargeFire,
@ -1607,6 +1608,7 @@ impl<'a> AgentData<'a> {
"Minotaur" => Tactic::Minotaur, "Minotaur" => Tactic::Minotaur,
"Clay Golem" => Tactic::ClayGolem, "Clay Golem" => Tactic::ClayGolem,
"Tidal Warrior" => Tactic::TidalWarrior, "Tidal Warrior" => Tactic::TidalWarrior,
"Tidal Totem" => Tactic::RadialTurret,
_ => Tactic::Melee, _ => Tactic::Melee,
}, },
AbilitySpec::Tool(tool_kind) => tool_tactic(*tool_kind), AbilitySpec::Tool(tool_kind) => tool_tactic(*tool_kind),
@ -1856,6 +1858,13 @@ impl<'a> AgentData<'a> {
&tgt_data, &tgt_data,
&read_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( fn handle_mindflayer_attack(
&self, &self,
agent: &mut Agent, agent: &mut Agent,

View File

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

View File

@ -9,8 +9,8 @@ use crate::{
use common::{ use common::{
assets::{AssetExt, DotVoxAsset}, assets::{AssetExt, DotVoxAsset},
comp::{ comp::{
self, aura, beam, body, buff, item::Reagent, object, BeamSegment, Body, CharacterState, self, aura, beam, body, buff, item::Reagent, object, shockwave, BeamSegment, Body,
Ori, Pos, Shockwave, Vel, CharacterState, Ori, Pos, Shockwave, Vel,
}, },
figure::Segment, figure::Segment,
outcome::Outcome, outcome::Outcome,
@ -1118,6 +1118,7 @@ impl ParticleMgr {
let state = scene_data.state; let state = scene_data.state;
let ecs = state.ecs(); let ecs = state.ecs();
let time = state.get_time(); let time = state.get_time();
let dt = scene_data.state.ecs().fetch::<DeltaTime>().0;
for (_entity, pos, ori, shockwave) in ( for (_entity, pos, ori, shockwave) in (
&ecs.entities(), &ecs.entities(),
@ -1127,9 +1128,10 @@ impl ParticleMgr {
) )
.join() .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(); let radians = shockwave.properties.angle.to_radians();
@ -1137,19 +1139,23 @@ impl ParticleMgr {
let theta = ori_vec.y.atan2(ori_vec.x); let theta = ori_vec.y.atan2(ori_vec.x);
let dtheta = radians / distance; 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;
use shockwave::FrontendSpecifier;
match shockwave.properties.specifier {
FrontendSpecifier::Ground => {
let heartbeats = self.scheduler.heartbeats(Duration::from_millis(2));
for heartbeat in 0..heartbeats { for heartbeat in 0..heartbeats {
if shockwave.properties.requires_ground {
// 1 / 3 the size of terrain voxel // 1 / 3 the size of terrain voxel
let scale = 1.0 / 3.0; 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 = let distance = speed * (elapsed as f32 - sub_tick_interpolation);
shockwave.properties.speed * (elapsed as f32 - sub_tick_interpolation);
let particle_count_factor = radians / (3.0 * scale); let particle_count_factor = radians / (3.0 * scale);
let new_particle_count = distance * particle_count_factor; let new_particle_count = distance * particle_count_factor;
@ -1171,7 +1177,11 @@ impl ParticleMgr {
position_snapped, position_snapped,
)); ));
} }
} else { }
},
FrontendSpecifier::Fire => {
let heartbeats = self.scheduler.heartbeats(Duration::from_millis(2));
for _ in 0..heartbeats {
for d in 0..3 * distance as i32 { for d in 0..3 * distance as i32 {
let arc_position = theta - radians / 2.0 + dtheta * d as f32 / 3.0; let arc_position = theta - radians / 2.0 + dtheta * d as f32 / 3.0;
@ -1186,6 +1196,42 @@ impl ParticleMgr {
)); ));
} }
} }
},
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,
));
}
}
},
} }
} }
} }