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.
- Reworked mindflayer to have unique attacks
### Removed
### 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",
abilities: [
(None, "common.abilities.unique.mindflayer.dimensionaldoor"),
(None, "common.abilities.unique.mindflayer.raiseundead"),
(None, "common.abilities.unique.mindflayer.summonminions"),
],
),
Debug: (

View File

@ -256,6 +256,13 @@ pub enum CharacterAbility {
recover_duration: 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 {
@ -544,6 +551,17 @@ impl CharacterAbility {
*buildup_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
}
@ -570,7 +588,7 @@ impl CharacterAbility {
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(),
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),
/// A short teleport that targets either a position or entity
Blink(blink::Data),
/// Summons creatures that fight for the caster
BasicSummon(basic_summon::Data),
}
impl CharacterState {

View File

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

View File

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

View File

@ -4,9 +4,10 @@ use crate::comp::{
AxeSkill, BowSkill, HammerSkill, Skill, SkillGroupKind, SkillSet, StaffSkill, SwordSkill,
},
};
use serde::{Deserialize, Serialize};
use tracing::warn;
#[derive(Copy, Clone)]
#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, Debug)]
pub enum SkillSetConfig {
Adlet,
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
#[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 recovers for
pub recover_duration: Duration,
/// What the max range of the teleport is
pub max_range: f32,
/// Miscellaneous information about the ability
pub ability_info: AbilityInfo,
}

View File

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

View File

@ -47,7 +47,7 @@ pub struct StaticData {
pub num_spins: u32,
/// What key is used to press ability
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>,
}

View File

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

View File

@ -247,7 +247,8 @@ impl<'a> System<'a> for Sys {
| CharacterState::BasicBeam { .. }
| CharacterState::BasicAura { .. }
| CharacterState::HealingBeam { .. }
| CharacterState::Blink { .. } => {
| CharacterState::Blink { .. }
| CharacterState::BasicSummon { .. } => {
if energy.get_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::{
character::CharacterId,
comp::{
@ -6,15 +6,16 @@ use common::{
aura::{Aura, AuraKind, AuraTarget},
beam,
buff::{BuffCategory, BuffData, BuffKind, BuffSource},
group,
inventory::loadout::Loadout,
shockwave, Agent, Alignment, Body, Gravity, Health, HomeChunk, Inventory, Item, ItemDrop,
LightEmitter, Object, Ori, Poise, Pos, Projectile, Scale, Stats, Vel, WaypointArea,
},
outcome::Outcome,
rtsim::RtSimEntity,
uid::Uid,
util::Dir,
};
use common_net::{msg::ServerGeneral, sync::WorldSyncExt};
use specs::{Builder, Entity as EcsEntity, WorldExt};
use std::time::Duration;
use vek::{Rgb, Vec3};
@ -59,15 +60,6 @@ pub fn handle_create_npc(
home_chunk: Option<HomeChunk>,
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 entity = server
@ -76,12 +68,6 @@ pub fn handle_create_npc(
.with(scale)
.with(alignment);
let entity = if let Some(group) = group {
entity.with(group)
} else {
entity
};
let entity = if let Some(agent) = agent.into() {
entity.with(agent)
} else {
@ -106,7 +92,45 @@ pub fn handle_create_npc(
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)]