Tornado summoning attack

This commit is contained in:
Snowram
2021-06-04 00:42:50 +02:00
committed by Robin Gilh
parent 97ce50e5d4
commit 3ba0500b90
18 changed files with 147 additions and 30 deletions

View File

@ -3,11 +3,16 @@ BasicSummon(
cast_duration: 1.0, cast_duration: 1.0,
recover_duration: 0.5, recover_duration: 0.5,
summon_amount: 6, summon_amount: 6,
summon_distance: (1, 5),
summon_info: ( summon_info: (
body: Object(Tornado), body: Object(Tornado),
scale: None, scale: None,
health_scaling: 80, health_scaling: None,
loadout_config: None, loadout_config: None,
skillset_config: None, skillset_config: None,
), ),
duration: Some((
secs: 5,
nanos: 0,
)),
) )

View File

@ -3,14 +3,16 @@ BasicSummon(
cast_duration: 1.0, cast_duration: 1.0,
recover_duration: 0.5, recover_duration: 0.5,
summon_amount: 6, summon_amount: 6,
summon_distance: (3, 3),
summon_info: ( summon_info: (
body: BipedSmall(( body: BipedSmall((
species: Husk, species: Husk,
body_type: Male, body_type: Male,
)), )),
scale: None, scale: None,
health_scaling: 80, health_scaling: Some(80),
loadout_config: Some(HuskSummon), loadout_config: Some(HuskSummon),
skillset_config: None, skillset_config: None,
), ),
duration: None,
) )

View File

@ -3,11 +3,13 @@ BasicSummon(
cast_duration: 1.0, cast_duration: 1.0,
recover_duration: 0.5, recover_duration: 0.5,
summon_amount: 1, summon_amount: 1,
summon_distance: (1, 1),
summon_info: ( summon_info: (
body: Object(SeaLantern), body: Object(SeaLantern),
scale: None, scale: None,
health_scaling: 0, health_scaling: Some(0),
loadout_config: None, loadout_config: None,
skillset_config: None, skillset_config: None,
), ),
duration: None,
) )

View File

@ -1,13 +1,13 @@
SpinMelee( SpinMelee(
buildup_duration: 0.2, buildup_duration: 0.0,
swing_duration: 0.6, swing_duration: 0.5,
recover_duration: 0.2, recover_duration: 0.0,
base_damage: 70, base_damage: 15000,
base_poise_damage: 25, base_poise_damage: 200,
knockback: ( strength: 0.0, direction: Away), knockback: ( strength: 200.0, direction: Away),
range: 3.5, range: 3.5,
damage_effect: None, damage_effect: None,
energy_cost: 100, energy_cost: 0,
is_infinite: true, is_infinite: true,
movement_behavior: AxeHover, movement_behavior: AxeHover,
is_interruptible: false, is_interruptible: false,

View File

@ -0,0 +1,19 @@
ItemDef(
name: "Tornado",
description: "Tornado weapon",
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.0,
crit_mult: 0.0,
)),
)),
quality: Low,
tags: [],
ability_spec: Some(Custom("Tornado")),
)

View File

@ -280,7 +280,9 @@ pub enum CharacterAbility {
cast_duration: f32, cast_duration: f32,
recover_duration: f32, recover_duration: f32,
summon_amount: u32, summon_amount: u32,
summon_distance: (f32, f32),
summon_info: basic_summon::SummonInfo, summon_info: basic_summon::SummonInfo,
duration: Option<Duration>,
}, },
SelfBuff { SelfBuff {
buildup_duration: f32, buildup_duration: f32,
@ -1737,15 +1739,19 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState {
cast_duration, cast_duration,
recover_duration, recover_duration,
summon_amount, summon_amount,
summon_distance,
summon_info, summon_info,
duration,
} => CharacterState::BasicSummon(basic_summon::Data { } => CharacterState::BasicSummon(basic_summon::Data {
static_data: basic_summon::StaticData { static_data: basic_summon::StaticData {
buildup_duration: Duration::from_secs_f32(*buildup_duration), buildup_duration: Duration::from_secs_f32(*buildup_duration),
cast_duration: Duration::from_secs_f32(*cast_duration), cast_duration: Duration::from_secs_f32(*cast_duration),
recover_duration: Duration::from_secs_f32(*recover_duration), recover_duration: Duration::from_secs_f32(*recover_duration),
summon_amount: *summon_amount, summon_amount: *summon_amount,
summon_distance: *summon_distance,
summon_info: *summon_info, summon_info: *summon_info,
ability_info, ability_info,
duration: *duration,
}, },
summon_count: 0, summon_count: 0,
timer: Duration::default(), timer: Duration::default(),

View File

@ -306,6 +306,9 @@ fn default_main_tool(body: &Body) -> Item {
object::Body::SeaLantern => Some(Item::new_from_asset_expect( object::Body::SeaLantern => Some(Item::new_from_asset_expect(
"common.items.npc_weapons.unique.tidal_totem", "common.items.npc_weapons.unique.tidal_totem",
)), )),
object::Body::Tornado => Some(Item::new_from_asset_expect(
"common.items.npc_weapons.unique.tornado",
)),
_ => 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

@ -117,7 +117,7 @@ pub enum ServerEvent {
pos: comp::Pos, pos: comp::Pos,
stats: comp::Stats, stats: comp::Stats,
skill_set: comp::SkillSet, skill_set: comp::SkillSet,
health: comp::Health, health: Option<comp::Health>,
poise: comp::Poise, poise: comp::Poise,
loadout: comp::inventory::loadout::Loadout, loadout: comp::inventory::loadout::Loadout,
body: comp::Body, body: comp::Body,
@ -127,6 +127,7 @@ pub enum ServerEvent {
home_chunk: Option<comp::HomeChunk>, home_chunk: Option<comp::HomeChunk>,
drop_item: Option<Item>, drop_item: Option<Item>,
rtsim_entity: Option<RtSimEntity>, rtsim_entity: Option<RtSimEntity>,
projectile: Option<comp::Projectile>,
}, },
CreateShip { CreateShip {
pos: comp::Pos, pos: comp::Pos,

View File

@ -2,7 +2,7 @@ use crate::{
comp::{ comp::{
self, self,
inventory::loadout_builder::{self, LoadoutBuilder}, inventory::loadout_builder::{self, LoadoutBuilder},
Behavior, BehaviorCapability, CharacterState, StateUpdate, Behavior, BehaviorCapability, CharacterState, Projectile, StateUpdate,
}, },
event::{LocalEvent, ServerEvent}, event::{LocalEvent, ServerEvent},
outcome::Outcome, outcome::Outcome,
@ -11,9 +11,13 @@ use crate::{
behavior::{CharacterBehavior, JoinData}, behavior::{CharacterBehavior, JoinData},
utils::*, utils::*,
}, },
terrain::Block,
vol::ReadVol,
}; };
use rand::Rng;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::time::Duration; use std::{f32::consts::PI, time::Duration};
use vek::*;
/// Separated out to condense update portions of character state /// Separated out to condense update portions of character state
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -26,10 +30,14 @@ pub struct StaticData {
pub recover_duration: Duration, pub recover_duration: Duration,
/// How many creatures the state should summon /// How many creatures the state should summon
pub summon_amount: u32, pub summon_amount: u32,
/// Range of the summons relative to the summonner
pub summon_distance: (f32, f32),
/// Information about the summoned creature /// Information about the summoned creature
pub summon_info: SummonInfo, pub summon_info: SummonInfo,
/// Miscellaneous information about the ability /// Miscellaneous information about the ability
pub ability_info: AbilityInfo, pub ability_info: AbilityInfo,
/// Duration of the summoned entity
pub duration: Option<Duration>,
} }
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -102,15 +110,67 @@ impl CharacterBehavior for Data {
}; };
let stats = comp::Stats::new("Summon".to_string()); let stats = comp::Stats::new("Summon".to_string());
let health_scaling = self
.static_data
.summon_info
.health_scaling
.map(|health_scaling| comp::Health::new(body, health_scaling));
// Ray cast to check where summon should happen
let summon_frac =
self.summon_count as f32 / self.static_data.summon_amount as f32;
let length = rand::thread_rng().gen_range(
self.static_data.summon_distance.0..self.static_data.summon_distance.1,
);
// Summon in a clockwise fashion
let ray_vector = Vec3::new(
(summon_frac * 2.0 * PI).sin() * length,
(summon_frac * 2.0 * PI).cos() * length,
data.body.eye_height(),
);
// Check for collision on the xy plane
let obstacle_xy = data
.terrain
.ray(data.pos.0, data.pos.0 + length * ray_vector)
.until(Block::is_solid)
.cast()
.0;
let collision_vector = Vec3::new(
data.pos.0.x + (summon_frac * 2.0 * PI).sin() * obstacle_xy,
data.pos.0.y + (summon_frac * 2.0 * PI).cos() * obstacle_xy,
data.pos.0.z,
);
// Check for collision in z up to 50 blocks
let obstacle_z = data
.terrain
.ray(collision_vector, collision_vector - Vec3::unit_z() * 50.0)
.until(Block::is_solid)
.cast()
.0;
// If a duration is specified, create a projectile componenent for the npc
let projectile = self.static_data.duration.map(|duration| Projectile {
hit_solid: Vec::new(),
hit_entity: Vec::new(),
time_left: duration,
owner: Some(*data.uid),
ignore_group: true,
is_sticky: false,
is_point: false,
});
// Send server event to create npc // Send server event to create npc
update.server_events.push_front(ServerEvent::CreateNpc { update.server_events.push_front(ServerEvent::CreateNpc {
pos: *data.pos, pos: comp::Pos(collision_vector - Vec3::unit_z() * obstacle_z),
stats, stats,
skill_set, skill_set,
health: comp::Health::new( health: health_scaling,
body,
self.static_data.summon_info.health_scaling,
),
poise: comp::Poise::new(body), poise: comp::Poise::new(body),
loadout, loadout,
body, body,
@ -129,6 +189,7 @@ impl CharacterBehavior for Data {
home_chunk: None, home_chunk: None,
drop_item: None, drop_item: None,
rtsim_entity: None, rtsim_entity: None,
projectile,
}); });
// Send local event used for frontend shenanigans // Send local event used for frontend shenanigans
@ -186,7 +247,7 @@ impl CharacterBehavior for Data {
pub struct SummonInfo { pub struct SummonInfo {
body: comp::Body, body: comp::Body,
scale: Option<comp::Scale>, scale: Option<comp::Scale>,
health_scaling: u16, health_scaling: Option<u16>,
// TODO: use assets for specifying skills and loadout? // TODO: use assets for specifying skills and loadout?
loadout_config: Option<loadout_builder::Preset>, loadout_config: Option<loadout_builder::Preset>,
skillset_config: Option<skillset_builder::Preset>, skillset_config: Option<skillset_builder::Preset>,

View File

@ -5,6 +5,7 @@ use crate::{
InventoryAction, Mass, Melee, Ori, PhysicsState, Pos, SkillSet, StateUpdate, Stats, Vel, InventoryAction, Mass, Melee, Ori, PhysicsState, Pos, SkillSet, StateUpdate, Stats, Vel,
}, },
resources::DeltaTime, resources::DeltaTime,
terrain::TerrainGrid,
uid::Uid, uid::Uid,
}; };
use specs::{ use specs::{
@ -96,6 +97,7 @@ pub struct JoinData<'a> {
pub msm: &'a MaterialStatManifest, pub msm: &'a MaterialStatManifest,
pub combo: &'a Combo, pub combo: &'a Combo,
pub alignment: Option<&'a comp::Alignment>, pub alignment: Option<&'a comp::Alignment>,
pub terrain: &'a TerrainGrid,
} }
type RestrictedMut<'a, C> = PairedStorage< type RestrictedMut<'a, C> = PairedStorage<
@ -128,6 +130,7 @@ pub struct JoinStruct<'a> {
pub skill_set: &'a SkillSet, pub skill_set: &'a SkillSet,
pub combo: &'a Combo, pub combo: &'a Combo,
pub alignment: Option<&'a comp::Alignment>, pub alignment: Option<&'a comp::Alignment>,
pub terrain: &'a TerrainGrid,
} }
impl<'a> JoinData<'a> { impl<'a> JoinData<'a> {
@ -161,6 +164,7 @@ impl<'a> JoinData<'a> {
msm, msm,
combo: j.combo, combo: j.combo,
alignment: j.alignment, alignment: j.alignment,
terrain: j.terrain,
} }
} }
} }

View File

@ -1,6 +1,6 @@
use specs::{ use specs::{
shred::ResourceId, Entities, Join, LazyUpdate, Read, ReadStorage, SystemData, World, Write, shred::ResourceId, Entities, Join, LazyUpdate, Read, ReadExpect, ReadStorage, SystemData,
WriteStorage, World, Write, WriteStorage,
}; };
use common::{ use common::{
@ -16,6 +16,7 @@ use common::{
self, self,
behavior::{CharacterBehavior, JoinData, JoinStruct}, behavior::{CharacterBehavior, JoinData, JoinStruct},
}, },
terrain::TerrainGrid,
uid::Uid, uid::Uid,
}; };
use common_ecs::{Job, Origin, Phase, System}; use common_ecs::{Job, Origin, Phase, System};
@ -67,6 +68,7 @@ pub struct ReadData<'a> {
msm: Read<'a, MaterialStatManifest>, msm: Read<'a, MaterialStatManifest>,
combos: ReadStorage<'a, Combo>, combos: ReadStorage<'a, Combo>,
alignments: ReadStorage<'a, comp::Alignment>, alignments: ReadStorage<'a, comp::Alignment>,
terrain: ReadExpect<'a, TerrainGrid>,
} }
/// ## Character Behavior System /// ## Character Behavior System
@ -280,6 +282,7 @@ impl<'a> System<'a> for Sys {
skill_set: &skill_set, skill_set: &skill_set,
combo: &combo, combo: &combo,
alignment: read_data.alignments.get(entity), alignment: read_data.alignments.get(entity),
terrain: &read_data.terrain,
}; };
for action in actions { for action in actions {

View File

@ -1014,7 +1014,7 @@ fn handle_spawn(
pos, pos,
comp::Stats::new(get_npc_name(id, npc::BodyType::from_body(body))), comp::Stats::new(get_npc_name(id, npc::BodyType::from_body(body))),
comp::SkillSet::default(), comp::SkillSet::default(),
comp::Health::new(body, 1), Some(comp::Health::new(body, 1)),
comp::Poise::new(body), comp::Poise::new(body),
inventory, inventory,
body, body,
@ -1116,7 +1116,7 @@ fn handle_spawn_training_dummy(
pos, pos,
stats, stats,
skill_set, skill_set,
health, Some(health),
poise, poise,
Inventory::new_empty(), Inventory::new_empty(),
body, body,

View File

@ -53,7 +53,7 @@ pub fn handle_create_npc(
pos: Pos, pos: Pos,
stats: Stats, stats: Stats,
skill_set: SkillSet, skill_set: SkillSet,
health: Health, health: Option<Health>,
poise: Poise, poise: Poise,
loadout: Loadout, loadout: Loadout,
body: Body, body: Body,
@ -63,6 +63,7 @@ pub fn handle_create_npc(
drop_item: Option<Item>, drop_item: Option<Item>,
home_chunk: Option<HomeChunk>, home_chunk: Option<HomeChunk>,
rtsim_entity: Option<RtSimEntity>, rtsim_entity: Option<RtSimEntity>,
projectile: Option<Projectile>,
) { ) {
let inventory = Inventory::new_with_loadout(loadout); let inventory = Inventory::new_with_loadout(loadout);
@ -96,6 +97,12 @@ pub fn handle_create_npc(
entity entity
}; };
let entity = if let Some(projectile) = projectile {
entity.with(projectile)
} else {
entity
};
let new_entity = entity.build(); let new_entity = entity.build();
// Add to group system if a pet // Add to group system if a pet

View File

@ -147,6 +147,7 @@ impl Server {
home_chunk, home_chunk,
drop_item, drop_item,
rtsim_entity, rtsim_entity,
projectile,
} => handle_create_npc( } => handle_create_npc(
self, self,
pos, pos,
@ -162,6 +163,7 @@ impl Server {
drop_item, drop_item,
home_chunk, home_chunk,
rtsim_entity, rtsim_entity,
projectile,
), ),
ServerEvent::CreateShip { ServerEvent::CreateShip {
pos, pos,

View File

@ -127,7 +127,7 @@ impl<'a> System<'a> for Sys {
pos: comp::Pos(spawn_pos), pos: comp::Pos(spawn_pos),
stats: comp::Stats::new(entity.get_name()), stats: comp::Stats::new(entity.get_name()),
skill_set: comp::SkillSet::default(), skill_set: comp::SkillSet::default(),
health: comp::Health::new(body, 10), health: Some(comp::Health::new(body, 10)),
loadout: match body { loadout: match body {
comp::Body::Humanoid(_) => entity.get_loadout(), comp::Body::Humanoid(_) => entity.get_loadout(),
_ => LoadoutBuilder::new().build(), _ => LoadoutBuilder::new().build(),
@ -146,6 +146,7 @@ impl<'a> System<'a> for Sys {
drop_item: None, drop_item: None,
home_chunk: None, home_chunk: None,
rtsim_entity, rtsim_entity,
projectile: None,
}, },
}; };
server_emitter.emit(event); server_emitter.emit(event);

View File

@ -39,7 +39,7 @@ pub trait StateExt {
pos: comp::Pos, pos: comp::Pos,
stats: comp::Stats, stats: comp::Stats,
skill_set: comp::SkillSet, skill_set: comp::SkillSet,
health: comp::Health, health: Option<comp::Health>,
poise: comp::Poise, poise: comp::Poise,
inventory: comp::Inventory, inventory: comp::Inventory,
body: comp::Body, body: comp::Body,
@ -175,7 +175,7 @@ impl StateExt for State {
pos: comp::Pos, pos: comp::Pos,
stats: comp::Stats, stats: comp::Stats,
skill_set: comp::SkillSet, skill_set: comp::SkillSet,
health: comp::Health, health: Option<comp::Health>,
poise: comp::Poise, poise: comp::Poise,
inventory: comp::Inventory, inventory: comp::Inventory,
body: comp::Body, body: comp::Body,
@ -215,7 +215,7 @@ impl StateExt for State {
)) ))
.with(stats) .with(stats)
.with(skill_set) .with(skill_set)
.with(health) .maybe_with(health)
.with(poise) .with(poise)
.with(comp::Alignment::Npc) .with(comp::Alignment::Npc)
.with(comp::CharacterState::default()) .with(comp::CharacterState::default())

View File

@ -239,7 +239,7 @@ impl<'a> System<'a> for Sys {
loadout_builder.build() loadout_builder.build()
}; };
let health = comp::Health::new(body, entity.level.unwrap_or(0)); let health = Some(comp::Health::new(body, entity.level.unwrap_or(0)));
let poise = comp::Poise::new(body); let poise = comp::Poise::new(body);
let can_speak = match body { let can_speak = match body {
@ -293,6 +293,7 @@ impl<'a> System<'a> for Sys {
home_chunk: Some(comp::HomeChunk(key)), home_chunk: Some(comp::HomeChunk(key)),
drop_item: entity.loot_drop, drop_item: entity.loot_drop,
rtsim_entity: None, rtsim_entity: None,
projectile: None,
}) })
} }

View File

@ -209,7 +209,7 @@ impl<'a> From<&'a Body> for SkeletonAttr {
feed: match (body.species, body.body_type) { feed: match (body.species, body.body_type) {
(Phoenix, _) => (-0.65), (Phoenix, _) => (-0.65),
(Cockatrice, _) => (-0.5), (Cockatrice, _) => (-0.5),
(Roc, _) => (-0.65), (Roc, _) => (-0.4),
}, },
} }
} }