Gave mindflayer AI.

Fixed particles and made them sync across network.
This commit is contained in:
Sam 2021-03-21 14:22:14 -04:00
parent a5b7477e96
commit 720482d994
8 changed files with 130 additions and 83 deletions

View File

@ -1,10 +1,10 @@
BasicBeam(
buildup_duration: 0.50,
recover_duration: 0.50,
beam_duration: 0.5,
damage: 200,
tick_rate: 2.0,
range: 10.0,
beam_duration: 1.0,
damage: 350,
tick_rate: 0.9,
range: 20.0,
max_angle: 15.0,
damage_effect: Some(Buff((
kind: Cursed,

View File

@ -35,6 +35,7 @@ pub enum Tactic {
Turret,
FixedTurret,
RotatingTurret,
Mindflayer,
}
#[derive(Copy, Clone, Debug, PartialEq)]

View File

@ -130,6 +130,9 @@ impl Health {
});
}
}
/// Returns the fraction of health an entity has remaining
pub fn fraction(&self) -> f32 { self.current as f32 / self.maximum as f32 }
}
#[cfg(not(target_arch = "wasm32"))]

View File

@ -92,6 +92,12 @@ impl CharacterBehavior for Data {
)
.build();
let alignment = if matches!(data.alignment, Some(comp::Alignment::Enemy)) {
comp::Alignment::Enemy
} else {
comp::Alignment::Owned(*data.uid)
};
update.server_events.push_front(ServerEvent::CreateNpc {
pos: *data.pos,
stats,
@ -103,7 +109,7 @@ impl CharacterBehavior for Data {
loadout,
body,
agent: Some(comp::Agent::new(None, false, None, &body, true)),
alignment: comp::Alignment::Owned(*data.uid),
alignment,
scale: self
.static_data
.summon_info

View File

@ -1,8 +1,8 @@
use crate::{
comp::{
item::MaterialStatManifest, Beam, Body, CharacterState, Combo, ControlAction, Controller,
ControllerInputs, Energy, Health, InputAttr, InputKind, Inventory, InventoryAction, Melee,
Ori, PhysicsState, Pos, StateUpdate, Stats, Vel,
self, item::MaterialStatManifest, Beam, Body, CharacterState, Combo, ControlAction,
Controller, ControllerInputs, Energy, Health, InputAttr, InputKind, Inventory,
InventoryAction, Melee, Ori, PhysicsState, Pos, StateUpdate, Stats, Vel,
},
resources::DeltaTime,
uid::Uid,
@ -93,6 +93,7 @@ pub struct JoinData<'a> {
pub stats: &'a Stats,
pub msm: &'a MaterialStatManifest,
pub combo: &'a Combo,
pub alignment: Option<&'a comp::Alignment>,
}
type RestrictedMut<'a, C> = PairedStorage<
@ -121,6 +122,7 @@ pub struct JoinStruct<'a> {
pub beam: Option<&'a Beam>,
pub stat: &'a Stats,
pub combo: &'a Combo,
pub alignment: Option<&'a comp::Alignment>,
}
impl<'a> JoinData<'a> {
@ -150,6 +152,7 @@ impl<'a> JoinData<'a> {
dt,
msm,
combo: j.combo,
alignment: j.alignment,
}
}
}

View File

@ -5,6 +5,7 @@ use specs::{
use common::{
comp::{
self,
inventory::{
item::MaterialStatManifest,
slot::{EquipSlot, Slot},
@ -71,6 +72,7 @@ pub struct ReadData<'a> {
stats: ReadStorage<'a, Stats>,
msm: Read<'a, MaterialStatManifest>,
combos: ReadStorage<'a, Combo>,
alignments: ReadStorage<'a, comp::Alignment>,
}
/// ## Character Behavior System
@ -255,6 +257,7 @@ impl<'a> System<'a> for Sys {
beam: read_data.beams.get(entity),
stat: &stat,
combo: &combo,
alignment: read_data.alignments.get(entity),
};
for action in actions {

View File

@ -60,6 +60,8 @@ struct AgentData<'a> {
light_emitter: Option<&'a LightEmitter>,
glider_equipped: bool,
is_gliding: bool,
health: &'a Health,
char_state: &'a CharacterState,
}
#[derive(SystemData)]
@ -145,14 +147,17 @@ impl<'a> System<'a> for Sys {
read_data.light_emitter.maybe(),
read_data.groups.maybe(),
read_data.mount_states.maybe(),
&read_data.char_states,
)
.par_join()
.filter(|(_, _, _, _, _, _, _, _, _, _, _, _, _, _, mount_state)| {
// Skip mounted entities
mount_state
.map(|ms| *ms == MountState::Unmounted)
.unwrap_or(true)
})
.filter(
|(_, _, _, _, _, _, _, _, _, _, _, _, _, _, mount_state, _)| {
// Skip mounted entities
mount_state
.map(|ms| *ms == MountState::Unmounted)
.unwrap_or(true)
},
)
.for_each_init(
|| {
prof_span!(guard, "agent rayon job");
@ -175,6 +180,7 @@ impl<'a> System<'a> for Sys {
light_emitter,
groups,
_,
char_state,
)| {
//// Hack, replace with better system when groups are more sophisticated
//// Override alignment if in a group unless entity is owned already
@ -269,6 +275,8 @@ impl<'a> System<'a> for Sys {
light_emitter,
glider_equipped,
is_gliding,
health,
char_state,
};
///////////////////////////////////////////////////////////
@ -1189,7 +1197,7 @@ impl<'a> AgentData<'a> {
tgt_pos: &Pos,
tgt_body: Option<&Body>,
dt: &DeltaTime,
_read_data: &ReadData,
read_data: &ReadData,
) {
let min_attack_dist = self.body.map_or(3.0, |b| b.radius() * self.scale + 2.0);
let tactic = match self
@ -1236,6 +1244,7 @@ impl<'a> AgentData<'a> {
Some(ToolKind::Unique(UniqueKind::TheropodBasic)) => Tactic::Theropod,
Some(ToolKind::Unique(UniqueKind::TheropodBird)) => Tactic::Theropod,
Some(ToolKind::Unique(UniqueKind::ObjectTurret)) => Tactic::Turret,
Some(ToolKind::Unique(UniqueKind::MindflayerStaff)) => Tactic::Mindflayer,
_ => Tactic::Melee,
};
@ -2193,6 +2202,37 @@ impl<'a> AgentData<'a> {
agent.target = None;
}
},
Tactic::Mindflayer => {
agent.action_timer += dt.0;
const MINDFLAYER_ATTACK_DIST: f32 = 15.0;
let mindflayer_is_far = dist_sqrd > MINDFLAYER_ATTACK_DIST.powi(2);
if mindflayer_is_far && agent.action_timer / self.health.fraction() > 5.0 {
if !self.char_state.is_attack() {
controller
.actions
.push(ControlAction::basic_input(InputKind::Ability(1)));
agent.action_timer = 0.0;
}
} else if mindflayer_is_far {
controller.actions.push(ControlAction::StartInput {
input: InputKind::Ability(0),
target_entity: agent
.target
.as_ref()
.and_then(|t| read_data.uids.get(t.target))
.copied(),
select_pos: None,
});
} else if self.health.fraction() < 0.5 {
controller
.actions
.push(ControlAction::basic_input(InputKind::Secondary));
} else {
controller
.actions
.push(ControlAction::basic_input(InputKind::Primary));
}
},
}
}

View File

@ -190,13 +190,12 @@ impl ParticleMgr {
// add new Particle
self.maintain_body_particles(scene_data);
self.maintain_boost_particles(scene_data);
self.maintain_char_state_particles(scene_data);
self.maintain_beam_particles(scene_data, lights);
self.maintain_block_particles(scene_data, terrain);
self.maintain_shockwave_particles(scene_data);
self.maintain_aura_particles(scene_data);
self.maintain_buff_particles(scene_data);
self.maintain_spin_melee_particles(scene_data);
} else {
// remove all particle lifespans
self.particles.clear();
@ -412,11 +411,11 @@ impl ParticleMgr {
}
}
fn maintain_boost_particles(&mut self, scene_data: &SceneData) {
fn maintain_char_state_particles(&mut self, scene_data: &SceneData) {
span!(
_guard,
"boost_particles",
"ParticleMgr::maintain_boost_particles"
"char_state_particles",
"ParticleMgr::maintain_char_state_particles"
);
let state = scene_data.state;
let ecs = state.ecs();
@ -431,19 +430,61 @@ impl ParticleMgr {
)
.join()
{
if let CharacterState::Boost(_) = character_state {
self.particles.resize_with(
self.particles.len()
+ usize::from(self.scheduler.heartbeats(Duration::from_millis(10))),
|| {
Particle::new(
Duration::from_secs(15),
time,
ParticleMode::CampfireSmoke,
pos.0 + vel.map_or(Vec3::zero(), |v| -v.0 * dt * rng.gen::<f32>()),
)
},
);
match character_state {
CharacterState::Boost(_) => {
self.particles.resize_with(
self.particles.len()
+ usize::from(self.scheduler.heartbeats(Duration::from_millis(10))),
|| {
Particle::new(
Duration::from_secs(15),
time,
ParticleMode::CampfireSmoke,
pos.0 + vel.map_or(Vec3::zero(), |v| -v.0 * dt * rng.gen::<f32>()),
)
},
);
},
CharacterState::SpinMelee(spin) => {
if let Some(specifier) = spin.static_data.specifier {
match specifier {
states::spin_melee::FrontendSpecifier::CultistVortex => {
if matches!(spin.stage_section, states::utils::StageSection::Swing)
{
let heartbeats =
self.scheduler.heartbeats(Duration::from_millis(3));
self.particles.resize_with(
self.particles.len()
+ spin.static_data.range.powi(2) as usize
* usize::from(heartbeats)
/ 150,
|| {
let rand_dist = spin.static_data.range
* (1.0 - rng.gen::<f32>().powi(10));
let init_pos = Vec3::new(
2.0 * rng.gen::<f32>() - 1.0,
2.0 * rng.gen::<f32>() - 1.0,
0.0,
)
.normalized()
* rand_dist
+ pos.0
+ Vec3::unit_z() * 0.05;
Particle::new_directed(
Duration::from_millis(900),
time,
ParticleMode::CultistFlame,
init_pos,
pos.0,
)
},
);
}
},
}
}
},
_ => {},
}
}
}
@ -863,56 +904,6 @@ impl ParticleMgr {
}
}
fn maintain_spin_melee_particles(&mut self, scene_data: &SceneData) {
let state = scene_data.state;
let ecs = state.ecs();
let time = state.get_time();
let mut rng = thread_rng();
for (pos, character_state) in (
&ecs.read_storage::<Pos>(),
&ecs.read_storage::<CharacterState>(),
)
.join()
{
if let CharacterState::SpinMelee(c) = character_state {
if let Some(specifier) = c.static_data.specifier {
match specifier {
states::spin_melee::FrontendSpecifier::CultistVortex => {
let heartbeats = self.scheduler.heartbeats(Duration::from_millis(3));
self.particles.resize_with(
self.particles.len()
+ c.static_data.range.powi(2) as usize
* usize::from(heartbeats)
/ 150,
|| {
let rand_dist =
c.static_data.range * (1.0 - rng.gen::<f32>().powi(10));
let init_pos = Vec3::new(
2.0 * rng.gen::<f32>() - 1.0,
2.0 * rng.gen::<f32>() - 1.0,
0.0,
)
.normalized()
* rand_dist
+ pos.0
+ Vec3::unit_z() * 0.05;
Particle::new_directed(
Duration::from_millis(900),
time,
ParticleMode::CultistFlame,
init_pos,
pos.0,
)
},
);
},
}
}
}
}
}
fn upload_particles(&mut self, renderer: &mut Renderer) {
span!(_guard, "upload_particles", "ParticleMgr::upload_particles");
let all_cpu_instances = self