176 lines
4.5 KiB
Rust
176 lines
4.5 KiB
Rust
use crate::{
|
|
GameState,
|
|
ai::Ai,
|
|
character::{AnimatedCharacter, HedzCharacter},
|
|
global_observer,
|
|
head::ActiveHead,
|
|
head_drop::HeadDrops,
|
|
heads::{ActiveHeads, HEAD_COUNT, HeadState},
|
|
heads_database::HeadsDatabase,
|
|
hitpoints::{Hitpoints, Kill},
|
|
keys::KeySpawn,
|
|
loading_assets::GameAssets,
|
|
protocol::{PlaySound, is_server},
|
|
tb_entities::EnemySpawn,
|
|
utils::Billboard,
|
|
};
|
|
use bevy::{light::NotShadowCaster, prelude::*};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
|
|
#[derive(Component, Reflect, PartialEq, Serialize, Deserialize)]
|
|
#[reflect(Component)]
|
|
#[require(HedzCharacter)]
|
|
pub struct Npc;
|
|
|
|
#[derive(Resource, Reflect, Default)]
|
|
#[reflect(Resource)]
|
|
struct NpcSpawning {
|
|
spawn_index: u32,
|
|
}
|
|
|
|
#[derive(Component, Reflect)]
|
|
#[reflect(Component)]
|
|
pub struct SpawningBeam(pub f32);
|
|
|
|
#[derive(Event)]
|
|
struct OnCheckSpawns;
|
|
|
|
#[derive(Event)]
|
|
pub struct SpawnCharacter(pub Vec3);
|
|
|
|
pub fn plugin(app: &mut App) {
|
|
app.init_resource::<NpcSpawning>();
|
|
|
|
app.add_systems(
|
|
FixedUpdate,
|
|
setup.run_if(in_state(GameState::Playing).and(is_server)),
|
|
);
|
|
app.add_systems(Update, update_beams.run_if(in_state(GameState::Playing)));
|
|
|
|
global_observer!(app, on_spawn_check);
|
|
global_observer!(app, on_spawn);
|
|
}
|
|
|
|
fn setup(mut commands: Commands, mut spawned: Local<bool>) {
|
|
if *spawned {
|
|
return;
|
|
}
|
|
|
|
commands.init_resource::<NpcSpawning>();
|
|
commands.trigger(OnCheckSpawns);
|
|
|
|
*spawned = true;
|
|
}
|
|
|
|
fn on_spawn_check(
|
|
_trigger: On<OnCheckSpawns>,
|
|
mut commands: Commands,
|
|
query: Query<(Entity, &EnemySpawn, &Transform), Without<Npc>>,
|
|
heads_db: Res<HeadsDatabase>,
|
|
spawning: Res<NpcSpawning>,
|
|
) {
|
|
//TODO: move into HeadsDatabase
|
|
let mut names: HashMap<String, usize> = HashMap::default();
|
|
for i in 0..HEAD_COUNT {
|
|
names.insert(heads_db.head_key(i).to_string(), i);
|
|
}
|
|
|
|
for (e, spawn, transform) in query.iter() {
|
|
use bevy_replicon::prelude::{Replicated, SendMode, ServerTriggerExt, ToClients};
|
|
|
|
if let Some(order) = spawn.spawn_order
|
|
&& order > spawning.spawn_index
|
|
{
|
|
continue;
|
|
}
|
|
|
|
let id = names[&spawn.head];
|
|
let mut ecommands = commands.entity(e);
|
|
ecommands
|
|
.insert((
|
|
Hitpoints::new(100),
|
|
Npc,
|
|
ActiveHead(id),
|
|
ActiveHeads::new([
|
|
Some(HeadState::new(id, heads_db.as_ref())),
|
|
None,
|
|
None,
|
|
None,
|
|
None,
|
|
]),
|
|
Replicated,
|
|
))
|
|
.insert_if(Ai, || !spawn.disable_ai)
|
|
.with_child((
|
|
Name::from("body-rig"),
|
|
AnimatedCharacter::new(id),
|
|
Replicated,
|
|
))
|
|
.observe(on_kill);
|
|
|
|
commands.trigger(SpawnCharacter(transform.translation));
|
|
commands.server_trigger(ToClients {
|
|
mode: SendMode::Broadcast,
|
|
message: PlaySound::Beaming,
|
|
});
|
|
}
|
|
}
|
|
|
|
fn on_kill(
|
|
trigger: On<Kill>,
|
|
mut commands: Commands,
|
|
query: Query<(&Transform, &EnemySpawn, &ActiveHead)>,
|
|
) {
|
|
let Ok((transform, enemy, head)) = query.get(trigger.event().entity) else {
|
|
return;
|
|
};
|
|
|
|
if let Some(order) = enemy.spawn_order {
|
|
commands.insert_resource(NpcSpawning {
|
|
spawn_index: order + 1,
|
|
});
|
|
}
|
|
|
|
commands.trigger(HeadDrops::new(transform.translation, head.0));
|
|
commands.trigger(OnCheckSpawns);
|
|
|
|
commands.entity(trigger.event().entity).despawn();
|
|
|
|
if !enemy.key.is_empty() {
|
|
commands.trigger(KeySpawn(transform.translation, enemy.key.clone()));
|
|
}
|
|
}
|
|
|
|
fn on_spawn(
|
|
trigger: On<SpawnCharacter>,
|
|
mut commands: Commands,
|
|
assets: Res<GameAssets>,
|
|
time: Res<Time>,
|
|
) {
|
|
commands.spawn((
|
|
Transform::from_translation(trigger.event().0 + Vec3::new(0., -2., 0.))
|
|
.with_scale(Vec3::new(1., 40., 1.)),
|
|
Billboard::XZ,
|
|
NotShadowCaster,
|
|
SpawningBeam(time.elapsed_secs()),
|
|
SceneRoot(assets.beaming.clone()),
|
|
));
|
|
}
|
|
|
|
fn update_beams(
|
|
mut commands: Commands,
|
|
mut query: Query<(Entity, &SpawningBeam, &mut Transform)>,
|
|
time: Res<Time>,
|
|
) {
|
|
for (entity, beam, mut transform) in query.iter_mut() {
|
|
let age = time.elapsed_secs() - beam.0;
|
|
|
|
transform.scale.x = age.sin() * 2.;
|
|
|
|
if age > 3. {
|
|
commands.entity(entity).despawn();
|
|
}
|
|
}
|
|
}
|