Added summon minions ability to mindflayer

This commit is contained in:
Sam 2021-03-21 01:53:39 -04:00
parent 30da614e89
commit a5b7477e96
16 changed files with 292 additions and 26 deletions

View File

@ -23,6 +23,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Seperated character randomization buttons into appearance and name. - Seperated character randomization buttons into appearance and name.
- Reworked mindflayer to have unique attacks
### Removed ### Removed
### Fixed ### Fixed

View File

@ -1 +0,0 @@
BasicBlock

View File

@ -0,0 +1,16 @@
BasicSummon(
buildup_duration: 0.5,
cast_duration: 1.0,
recover_duration: 0.5,
summon_amount: 4,
summon_info: (
body: QuadrupedMedium((
species: Darkhound,
body_type: Male,
)),
scale: None,
health_scaling: 0,
loadout_config: None,
skillset_config: None,
),
)

View File

@ -199,7 +199,7 @@
secondary: "common.abilities.unique.mindflayer.necroticvortex", secondary: "common.abilities.unique.mindflayer.necroticvortex",
abilities: [ abilities: [
(None, "common.abilities.unique.mindflayer.dimensionaldoor"), (None, "common.abilities.unique.mindflayer.dimensionaldoor"),
(None, "common.abilities.unique.mindflayer.raiseundead"), (None, "common.abilities.unique.mindflayer.summonminions"),
], ],
), ),
Debug: ( Debug: (

View File

@ -256,6 +256,13 @@ pub enum CharacterAbility {
recover_duration: f32, recover_duration: f32,
max_range: f32, max_range: f32,
}, },
BasicSummon {
buildup_duration: f32,
cast_duration: f32,
recover_duration: f32,
summon_amount: u32,
summon_info: basic_summon::SummonInfo,
},
} }
impl Default for CharacterAbility { impl Default for CharacterAbility {
@ -544,6 +551,17 @@ impl CharacterAbility {
*buildup_duration /= speed; *buildup_duration /= speed;
*recover_duration /= speed; *recover_duration /= speed;
}, },
BasicSummon {
ref mut buildup_duration,
ref mut cast_duration,
ref mut recover_duration,
..
} => {
// TODO: Figure out how/if power should affect this
*buildup_duration /= speed;
*cast_duration /= speed;
*recover_duration /= speed;
},
} }
self self
} }
@ -570,7 +588,7 @@ impl CharacterAbility {
0 0
} }
}, },
BasicBlock | Boost { .. } | ComboMelee { .. } | Blink { .. } => 0, BasicBlock | Boost { .. } | ComboMelee { .. } | Blink { .. } | BasicSummon { .. } => 0,
} }
} }
@ -1586,6 +1604,25 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState {
timer: Duration::default(), timer: Duration::default(),
stage_section: StageSection::Buildup, stage_section: StageSection::Buildup,
}), }),
CharacterAbility::BasicSummon {
buildup_duration,
cast_duration,
recover_duration,
summon_amount,
summon_info,
} => CharacterState::BasicSummon(basic_summon::Data {
static_data: basic_summon::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),
summon_amount: *summon_amount,
summon_info: *summon_info,
ability_info,
},
summon_count: 0,
timer: Duration::default(),
stage_section: StageSection::Buildup,
}),
} }
} }
} }

View File

@ -95,6 +95,8 @@ pub enum CharacterState {
HealingBeam(healing_beam::Data), HealingBeam(healing_beam::Data),
/// A short teleport that targets either a position or entity /// A short teleport that targets either a position or entity
Blink(blink::Data), Blink(blink::Data),
/// Summons creatures that fight for the caster
BasicSummon(basic_summon::Data),
} }
impl CharacterState { impl CharacterState {

View File

@ -34,7 +34,7 @@ pub enum HealthSource {
Unknown, Unknown,
} }
#[derive(Clone, Copy, Debug, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct Health { pub struct Health {
current: u32, current: u32,
base_max: u32, base_max: u32,

View File

@ -12,6 +12,7 @@ use crate::{
trade::{Good, SiteInformation}, trade::{Good, SiteInformation},
}; };
use rand::Rng; use rand::Rng;
use serde::{Deserialize, Serialize};
/// Builder for character Loadouts, containing weapon and armour items belonging /// Builder for character Loadouts, containing weapon and armour items belonging
/// to a character, along with some helper methods for loading Items and /// to a character, along with some helper methods for loading Items and
@ -34,7 +35,7 @@ use rand::Rng;
#[derive(Clone)] #[derive(Clone)]
pub struct LoadoutBuilder(Loadout); pub struct LoadoutBuilder(Loadout);
#[derive(Copy, Clone)] #[derive(Copy, Clone, PartialEq, Serialize, Deserialize, Debug)]
pub enum LoadoutConfig { pub enum LoadoutConfig {
Adlet, Adlet,
Gnarling, Gnarling,

View File

@ -4,9 +4,10 @@ use crate::comp::{
AxeSkill, BowSkill, HammerSkill, Skill, SkillGroupKind, SkillSet, StaffSkill, SwordSkill, AxeSkill, BowSkill, HammerSkill, Skill, SkillGroupKind, SkillSet, StaffSkill, SwordSkill,
}, },
}; };
use serde::{Deserialize, Serialize};
use tracing::warn; use tracing::warn;
#[derive(Copy, Clone)] #[derive(Copy, Clone, PartialEq, Serialize, Deserialize, Debug)]
pub enum SkillSetConfig { pub enum SkillSetConfig {
Adlet, Adlet,
Gnarling, Gnarling,

View File

@ -0,0 +1,176 @@
use crate::{
comp::{
self,
inventory::loadout_builder::{LoadoutBuilder, LoadoutConfig},
CharacterState, StateUpdate,
},
event::ServerEvent,
skillset_builder::{SkillSetBuilder, SkillSetConfig},
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 the state builds up for
pub buildup_duration: Duration,
/// How long the state is casting for
pub cast_duration: Duration,
/// How long the state recovers for
pub recover_duration: Duration,
/// How many creatures the state should summon
pub summon_amount: u32,
/// Information about the summoned creature
pub summon_info: SummonInfo,
/// Miscellaneous information about the 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,
/// How many creatures have been summoned
pub summon_count: u32,
/// 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);
match self.stage_section {
StageSection::Buildup => {
if self.timer < self.static_data.buildup_duration {
// Build up
update.character = CharacterState::BasicSummon(Data {
timer: self
.timer
.checked_add(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default(),
..*self
});
} else {
// Transitions to recover section of stage
update.character = CharacterState::BasicSummon(Data {
timer: Duration::default(),
stage_section: StageSection::Cast,
..*self
});
}
},
StageSection::Cast => {
if self.timer < self.static_data.cast_duration
|| self.summon_count < self.static_data.summon_amount
{
if self.timer
> self.static_data.cast_duration * self.summon_count
/ self.static_data.summon_amount
{
let body = self.static_data.summon_info.body;
let mut stats = comp::Stats::new("Summon".to_string());
stats.skill_set = SkillSetBuilder::build_skillset(
&None,
self.static_data.summon_info.skillset_config,
)
.build();
let loadout = LoadoutBuilder::build_loadout(
body,
None,
self.static_data.summon_info.loadout_config,
None,
)
.build();
update.server_events.push_front(ServerEvent::CreateNpc {
pos: *data.pos,
stats,
health: comp::Health::new(
body,
self.static_data.summon_info.health_scaling,
),
poise: comp::Poise::new(body),
loadout,
body,
agent: Some(comp::Agent::new(None, false, None, &body, true)),
alignment: comp::Alignment::Owned(*data.uid),
scale: self
.static_data
.summon_info
.scale
.unwrap_or(comp::Scale(1.0)),
home_chunk: None,
drop_item: None,
rtsim_entity: None,
});
update.character = CharacterState::BasicSummon(Data {
timer: self
.timer
.checked_add(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default(),
summon_count: self.summon_count + 1,
..*self
});
} else {
// Cast
update.character = CharacterState::BasicSummon(Data {
timer: self
.timer
.checked_add(Duration::from_secs_f32(data.dt.0))
.unwrap_or_default(),
..*self
});
}
} else {
// Transitions to recover section of stage
update.character = CharacterState::BasicSummon(Data {
timer: Duration::default(),
stage_section: StageSection::Recover,
..*self
});
}
},
StageSection::Recover => {
if self.timer < self.static_data.recover_duration {
// Recovery
update.character = CharacterState::BasicSummon(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;
},
}
update
}
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SummonInfo {
body: comp::Body,
scale: Option<comp::Scale>,
health_scaling: u16,
loadout_config: Option<LoadoutConfig>,
skillset_config: Option<SkillSetConfig>,
}

View File

@ -12,9 +12,13 @@ use std::time::Duration;
/// 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)]
pub struct StaticData { pub struct StaticData {
/// How long the state builds up for
pub buildup_duration: Duration, pub buildup_duration: Duration,
/// How long the state recovers for
pub recover_duration: Duration, pub recover_duration: Duration,
/// What the max range of the teleport is
pub max_range: f32, pub max_range: f32,
/// Miscellaneous information about the ability
pub ability_info: AbilityInfo, pub ability_info: AbilityInfo,
} }

View File

@ -3,6 +3,7 @@ pub mod basic_beam;
pub mod basic_block; pub mod basic_block;
pub mod basic_melee; pub mod basic_melee;
pub mod basic_ranged; pub mod basic_ranged;
pub mod basic_summon;
pub mod behavior; pub mod behavior;
pub mod blink; pub mod blink;
pub mod boost; pub mod boost;

View File

@ -47,7 +47,7 @@ pub struct StaticData {
pub num_spins: u32, pub num_spins: u32,
/// What key is used to press ability /// What key is used to press ability
pub ability_info: AbilityInfo, pub ability_info: AbilityInfo,
/// Used to specify the beam to the frontend /// Used to specify the melee attack to the frontend
pub specifier: Option<FrontendSpecifier>, pub specifier: Option<FrontendSpecifier>,
} }

View File

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

View File

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

View File

@ -1,4 +1,4 @@
use crate::{sys, Server, StateExt}; use crate::{client::Client, sys, Server, StateExt};
use common::{ use common::{
character::CharacterId, character::CharacterId,
comp::{ comp::{
@ -6,15 +6,16 @@ use common::{
aura::{Aura, AuraKind, AuraTarget}, aura::{Aura, AuraKind, AuraTarget},
beam, beam,
buff::{BuffCategory, BuffData, BuffKind, BuffSource}, buff::{BuffCategory, BuffData, BuffKind, BuffSource},
group,
inventory::loadout::Loadout, inventory::loadout::Loadout,
shockwave, Agent, Alignment, Body, Gravity, Health, HomeChunk, Inventory, Item, ItemDrop, shockwave, Agent, Alignment, Body, Gravity, Health, HomeChunk, Inventory, Item, ItemDrop,
LightEmitter, Object, Ori, Poise, Pos, Projectile, Scale, Stats, Vel, WaypointArea, LightEmitter, Object, Ori, Poise, Pos, Projectile, Scale, Stats, Vel, WaypointArea,
}, },
outcome::Outcome, outcome::Outcome,
rtsim::RtSimEntity, rtsim::RtSimEntity,
uid::Uid,
util::Dir, util::Dir,
}; };
use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
use specs::{Builder, Entity as EcsEntity, WorldExt}; use specs::{Builder, Entity as EcsEntity, WorldExt};
use std::time::Duration; use std::time::Duration;
use vek::{Rgb, Vec3}; use vek::{Rgb, Vec3};
@ -59,15 +60,6 @@ pub fn handle_create_npc(
home_chunk: Option<HomeChunk>, home_chunk: Option<HomeChunk>,
rtsim_entity: Option<RtSimEntity>, rtsim_entity: Option<RtSimEntity>,
) { ) {
let group = match alignment {
Alignment::Wild => None,
Alignment::Passive => None,
Alignment::Enemy => Some(group::ENEMY),
Alignment::Npc | Alignment::Tame => Some(group::NPC),
// TODO: handle
Alignment::Owned(_) => None,
};
let inventory = Inventory::new_with_loadout(loadout); let inventory = Inventory::new_with_loadout(loadout);
let entity = server let entity = server
@ -76,12 +68,6 @@ pub fn handle_create_npc(
.with(scale) .with(scale)
.with(alignment); .with(alignment);
let entity = if let Some(group) = group {
entity.with(group)
} else {
entity
};
let entity = if let Some(agent) = agent.into() { let entity = if let Some(agent) = agent.into() {
entity.with(agent) entity.with(agent)
} else { } else {
@ -106,7 +92,45 @@ pub fn handle_create_npc(
entity entity
}; };
entity.build(); let new_entity = entity.build();
// Add to group system if a pet
if let comp::Alignment::Owned(owner_uid) = alignment {
let state = server.state();
let clients = state.ecs().read_storage::<Client>();
let uids = state.ecs().read_storage::<Uid>();
let mut group_manager = state.ecs().write_resource::<comp::group::GroupManager>();
if let Some(owner) = state.ecs().entity_from_uid(owner_uid.into()) {
group_manager.new_pet(
new_entity,
owner,
&mut state.ecs().write_storage(),
&state.ecs().entities(),
&state.ecs().read_storage(),
&uids,
&mut |entity, group_change| {
clients
.get(entity)
.and_then(|c| {
group_change
.try_map(|e| uids.get(e).copied())
.map(|g| (g, c))
})
.map(|(g, c)| {
c.send_fallible(ServerGeneral::GroupUpdate(g));
});
},
);
}
} else if let Some(group) = match alignment {
comp::Alignment::Wild => None,
comp::Alignment::Passive => None,
comp::Alignment::Enemy => Some(comp::group::ENEMY),
comp::Alignment::Npc | comp::Alignment::Tame => Some(comp::group::NPC),
comp::Alignment::Owned(_) => unreachable!(),
} {
let _ = server.state.ecs().write_storage().insert(new_entity, group);
}
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]