head database for aim and ai
This commit is contained in:
16
Cargo.lock
generated
16
Cargo.lock
generated
@@ -679,6 +679,19 @@ dependencies = [
|
||||
"wgpu-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy_common_assets"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3521990269672c442f2bf0fbed0fce9db719e3dd136dd4012a97809464a4389d"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bevy",
|
||||
"ron",
|
||||
"serde",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy_core"
|
||||
version = "0.15.3"
|
||||
@@ -2971,12 +2984,15 @@ dependencies = [
|
||||
"bevy-ui-gradients",
|
||||
"bevy_asset_loader",
|
||||
"bevy_ballistic",
|
||||
"bevy_common_assets",
|
||||
"bevy_debug_log",
|
||||
"bevy_polyline",
|
||||
"bevy_sprite3d",
|
||||
"bevy_trenchbroom",
|
||||
"nil",
|
||||
"rand",
|
||||
"ron",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -36,6 +36,9 @@ bevy-steamworks = "0.13.0"
|
||||
bevy_ballistic = "0.1.0"
|
||||
bevy-ui-gradients = "0.2.0"
|
||||
bevy_debug_log = "0.5.0"
|
||||
bevy_common_assets = { version = "0.12.0", features = ["ron"] }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
ron = "0.8"
|
||||
|
||||
[lints.clippy]
|
||||
too_many_arguments = "allow"
|
||||
|
||||
20
assets/all.headsdb.ron
Normal file
20
assets/all.headsdb.ron
Normal file
@@ -0,0 +1,20 @@
|
||||
([
|
||||
(key:"angry demonstrator", ability:Thrown, range:80),
|
||||
(key:"carnival knife thrower", range:60),
|
||||
(key:"chicago gangster", ability:Gun, range:60),
|
||||
(key:"commando", ability:Gun, range:60),
|
||||
(key:"field medic"),
|
||||
(key:"geisha"),
|
||||
(key:"goblin", ability:Arrow, range:60),
|
||||
(key:"green grocer", range:60),
|
||||
(key:"highland hammer thrower", ability:Thrown, range:80),
|
||||
(key:"legionnaire", ability:Gun, range:60),
|
||||
(key:"mig pilot", ability:Gun, range:60),
|
||||
(key:"nanny", ability:Thrown, range:60),
|
||||
(key:"panic attack"),
|
||||
(key:"salty sea dog"),
|
||||
(key:"snow plough operator"),
|
||||
(key:"soldier ant"),
|
||||
(key:"super market shopper", ability:Thrown, range:80),
|
||||
(key:"troll", ability:Thrown, range:80),
|
||||
])
|
||||
@@ -5,6 +5,7 @@ use crate::{
|
||||
GameState,
|
||||
aim::AimTarget,
|
||||
global_observer,
|
||||
head_asset::HeadsDatabase,
|
||||
heads::ActiveHeads,
|
||||
hitpoints::Hit,
|
||||
physics_layers::GameLayer,
|
||||
@@ -14,6 +15,7 @@ use crate::{
|
||||
};
|
||||
use avian3d::prelude::*;
|
||||
use bevy::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Projectile {
|
||||
@@ -29,8 +31,9 @@ pub enum TriggerState {
|
||||
#[derive(Event, Reflect)]
|
||||
pub struct TriggerCashHeal;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Reflect)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Reflect, Default, Serialize, Deserialize)]
|
||||
pub enum HeadAbility {
|
||||
#[default]
|
||||
None,
|
||||
Arrow,
|
||||
Thrown,
|
||||
@@ -112,6 +115,7 @@ fn on_trigger_state(
|
||||
player_rot: Query<&Transform, With<PlayerBodyMesh>>,
|
||||
player_query: Query<(&Transform, &AimTarget), With<Player>>,
|
||||
mut active_heads: ResMut<ActiveHeads>,
|
||||
heads_db: Res<HeadsDatabase>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
if matches!(trigger.event(), TriggerState::Active) {
|
||||
@@ -143,7 +147,8 @@ fn on_trigger_state(
|
||||
|
||||
active_heads.use_ammo(time.elapsed_secs());
|
||||
|
||||
match state.ability {
|
||||
let ability = heads_db.head_stats(state.head).ability;
|
||||
match ability {
|
||||
HeadAbility::Thrown => commands.trigger(TriggerThrow(trigger_state)),
|
||||
HeadAbility::Gun => commands.trigger(TriggerGun(trigger_state)),
|
||||
_ => (),
|
||||
|
||||
@@ -2,8 +2,9 @@ use bevy::prelude::*;
|
||||
|
||||
use crate::{
|
||||
GameState,
|
||||
abilities::{TriggerData, TriggerThrow},
|
||||
abilities::{HeadAbility, TriggerData, TriggerThrow},
|
||||
aim::AimTarget,
|
||||
head_asset::HeadsDatabase,
|
||||
npc::Npc,
|
||||
};
|
||||
|
||||
@@ -19,12 +20,20 @@ fn update(
|
||||
mut commands: Commands,
|
||||
mut query: Query<(&mut Npc, &AimTarget, &Transform), With<Ai>>,
|
||||
time: Res<Time>,
|
||||
heads_db: Res<HeadsDatabase>,
|
||||
) {
|
||||
for (mut npc, target, t) in query.iter_mut() {
|
||||
if target.0.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let ability = heads_db.head_stats(npc.head).ability;
|
||||
|
||||
//TODO: support other abilities
|
||||
if ability != HeadAbility::Thrown {
|
||||
continue;
|
||||
}
|
||||
|
||||
let can_shoot_again = npc.last_use + 1. < time.elapsed_secs();
|
||||
|
||||
if can_shoot_again && npc.has_ammo() {
|
||||
|
||||
@@ -4,6 +4,7 @@ mod target_ui;
|
||||
use crate::{
|
||||
GameState,
|
||||
head::ActiveHead,
|
||||
head_asset::HeadsDatabase,
|
||||
physics_layers::GameLayer,
|
||||
player::{Player, PlayerBodyMesh},
|
||||
tb_entities::EnemySpawn,
|
||||
@@ -53,15 +54,15 @@ fn add_aim(mut commands: Commands, query: Query<Entity, Added<ActiveHead>>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn head_change(mut query: Query<(&ActiveHead, &mut AimState), Changed<ActiveHead>>) {
|
||||
fn head_change(
|
||||
mut query: Query<(&ActiveHead, &mut AimState), Changed<ActiveHead>>,
|
||||
heads_db: Res<HeadsDatabase>,
|
||||
) {
|
||||
for (head, mut state) in query.iter_mut() {
|
||||
// info!("head changed: {}", head.0);
|
||||
// state.max_angle = if head.0 == 0 { PI / 8. } else { PI / 2. }
|
||||
state.range = match head.0 {
|
||||
0 => 80.,
|
||||
3 => 60.,
|
||||
_ => 40.,
|
||||
};
|
||||
let stats = heads_db.head_stats(head.0);
|
||||
state.range = stats.range;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
54
src/head_asset.rs
Normal file
54
src/head_asset.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use crate::abilities::HeadAbility;
|
||||
use bevy::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Default, Reflect, Serialize, Deserialize)]
|
||||
pub struct HeadStats {
|
||||
pub key: String,
|
||||
#[serde(default)]
|
||||
pub ability: HeadAbility,
|
||||
#[serde(default)]
|
||||
pub range: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Asset, Reflect, Serialize, Deserialize)]
|
||||
pub struct HeadDatabaseAsset(pub Vec<HeadStats>);
|
||||
|
||||
#[derive(Debug, Resource, Reflect)]
|
||||
#[reflect(Resource)]
|
||||
pub struct HeadsDatabase {
|
||||
pub heads: Vec<HeadStats>,
|
||||
}
|
||||
|
||||
impl HeadsDatabase {
|
||||
pub fn head_key(&self, id: usize) -> &str {
|
||||
&self.heads[id].key
|
||||
}
|
||||
|
||||
pub fn head_stats(&self, id: usize) -> &HeadStats {
|
||||
&self.heads[id]
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod test {
|
||||
// use super::*;
|
||||
|
||||
// #[test]
|
||||
// fn test_serialize() {
|
||||
// let asset = HeadDatabaseAsset(vec![
|
||||
// HeadStats {
|
||||
// key: String::from("foo"),
|
||||
// range: 90.,
|
||||
// ..Default::default()
|
||||
// },
|
||||
// HeadStats {
|
||||
// key: String::from("bar"),
|
||||
// ability: HeadAbility::Gun,
|
||||
// range: 0.,
|
||||
// },
|
||||
// ]);
|
||||
|
||||
// std::fs::write("assets/test.headsb.ron", ron::to_string(&asset).unwrap()).unwrap();
|
||||
// }
|
||||
// }
|
||||
@@ -2,11 +2,11 @@ mod heads_ui;
|
||||
|
||||
use crate::{
|
||||
GameState,
|
||||
abilities::HeadAbility,
|
||||
backpack::{BackbackSwapEvent, Backpack},
|
||||
global_observer,
|
||||
head_asset::HeadsDatabase,
|
||||
hitpoints::Hitpoints,
|
||||
player::{Player, head_id_to_str},
|
||||
player::Player,
|
||||
sounds::PlaySound,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
@@ -22,7 +22,6 @@ pub struct HeadsImages {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Reflect)]
|
||||
pub struct HeadState {
|
||||
pub head: usize,
|
||||
pub ability: HeadAbility,
|
||||
pub health: u32,
|
||||
pub health_max: u32,
|
||||
pub ammo: u32,
|
||||
@@ -39,17 +38,11 @@ impl HeadState {
|
||||
health_max: 100,
|
||||
ammo,
|
||||
ammo_max: ammo,
|
||||
ability: HeadAbility::None,
|
||||
reload_duration: 5.,
|
||||
last_use: 0.,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_ability(mut self, ability: HeadAbility) -> Self {
|
||||
self.ability = ability;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn has_ammo(&self) -> bool {
|
||||
self.ammo > 0
|
||||
}
|
||||
@@ -119,11 +112,11 @@ pub fn plugin(app: &mut App) {
|
||||
|
||||
app.insert_resource(ActiveHeads {
|
||||
heads: [
|
||||
Some(HeadState::new(0, 10).with_ability(HeadAbility::Thrown)),
|
||||
Some(HeadState::new(3, 10).with_ability(HeadAbility::Gun)),
|
||||
Some(HeadState::new(6, 10).with_ability(HeadAbility::Arrow)),
|
||||
Some(HeadState::new(8, 10).with_ability(HeadAbility::Thrown)),
|
||||
Some(HeadState::new(9, 10).with_ability(HeadAbility::Gun)),
|
||||
Some(HeadState::new(0, 10)),
|
||||
Some(HeadState::new(3, 10)),
|
||||
Some(HeadState::new(6, 10)),
|
||||
Some(HeadState::new(8, 10)),
|
||||
Some(HeadState::new(9, 10)),
|
||||
],
|
||||
current_slot: 0,
|
||||
});
|
||||
@@ -138,10 +131,10 @@ pub fn plugin(app: &mut App) {
|
||||
global_observer!(app, on_swap_backpack);
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
fn setup(mut commands: Commands, asset_server: Res<AssetServer>, heads: Res<HeadsDatabase>) {
|
||||
// TODO: load via asset loader
|
||||
let heads = (0usize..HEAD_COUNT)
|
||||
.map(|i| asset_server.load(format!("ui/heads/{}.png", head_id_to_str(i))))
|
||||
.map(|i| asset_server.load(format!("ui/heads/{}.png", heads.head_key(i))))
|
||||
.collect();
|
||||
|
||||
commands.insert_resource(HeadsImages { heads });
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use crate::GameState;
|
||||
use crate::{
|
||||
GameState,
|
||||
head_asset::{HeadDatabaseAsset, HeadsDatabase},
|
||||
};
|
||||
use bevy::{prelude::*, utils::HashMap};
|
||||
use bevy_asset_loader::prelude::*;
|
||||
|
||||
@@ -43,6 +46,12 @@ pub struct AudioAssets {
|
||||
pub head: HashMap<AssetFileName, Handle<AudioSource>>,
|
||||
}
|
||||
|
||||
#[derive(AssetCollection, Resource)]
|
||||
struct HeadsAssets {
|
||||
#[asset(path = "all.headsdb.ron")]
|
||||
heads: Handle<HeadDatabaseAsset>,
|
||||
}
|
||||
|
||||
#[derive(AssetCollection, Resource)]
|
||||
pub struct UIAssets {
|
||||
#[asset(path = "font.ttf")]
|
||||
@@ -98,12 +107,26 @@ pub struct GameAssets {
|
||||
pub struct LoadingPlugin;
|
||||
impl Plugin for LoadingPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(OnExit(GameState::AssetLoading), on_exit);
|
||||
app.add_loading_state(
|
||||
LoadingState::new(GameState::AssetLoading)
|
||||
.continue_to_state(GameState::MapLoading)
|
||||
.load_collection::<AudioAssets>()
|
||||
.load_collection::<GameAssets>()
|
||||
.load_collection::<HeadsAssets>()
|
||||
.load_collection::<UIAssets>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_exit(
|
||||
mut cmds: Commands,
|
||||
res: Res<HeadsAssets>,
|
||||
mut assets: ResMut<Assets<HeadDatabaseAsset>>,
|
||||
) {
|
||||
let asset = assets
|
||||
.remove(res.heads.id())
|
||||
.expect("headsdb failed to load");
|
||||
|
||||
cmds.insert_resource(HeadsDatabase { heads: asset.0 });
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ mod cutscene;
|
||||
mod debug;
|
||||
mod gates;
|
||||
mod head;
|
||||
mod head_asset;
|
||||
mod heads;
|
||||
mod hitpoints;
|
||||
mod keys;
|
||||
@@ -33,6 +34,7 @@ use bevy::{
|
||||
prelude::*,
|
||||
render::view::ColorGrading,
|
||||
};
|
||||
use bevy_common_assets::ron::RonAssetPlugin;
|
||||
use bevy_polyline::PolylinePlugin;
|
||||
use bevy_sprite3d::Sprite3dPlugin;
|
||||
use bevy_steamworks::{FriendFlags, SteamworksClient, SteamworksPlugin};
|
||||
@@ -40,6 +42,7 @@ use bevy_trenchbroom::prelude::*;
|
||||
use bevy_ui_gradients::UiGradientsPlugin;
|
||||
use camera::MainCamera;
|
||||
use control::controller::CharacterControllerPlugin;
|
||||
use head_asset::HeadDatabaseAsset;
|
||||
use loading_assets::AudioAssets;
|
||||
use std::io::{Read, Write};
|
||||
use utils::{billboards, sprite_3d_animation, squish_animation};
|
||||
@@ -108,6 +111,7 @@ fn main() {
|
||||
app.add_plugins(Sprite3dPlugin);
|
||||
app.add_plugins(TrenchBroomPlugin(TrenchBroomConfig::new("hedz")));
|
||||
app.add_plugins(UiGradientsPlugin);
|
||||
app.add_plugins(RonAssetPlugin::<HeadDatabaseAsset>::new(&["headsdb.ron"]));
|
||||
|
||||
#[cfg(feature = "dbg")]
|
||||
{
|
||||
|
||||
10
src/npc.rs
10
src/npc.rs
@@ -2,10 +2,10 @@ use crate::{
|
||||
GameState,
|
||||
ai::Ai,
|
||||
head::ActiveHead,
|
||||
head_asset::HeadsDatabase,
|
||||
heads::{HEAD_COUNT, HeadState},
|
||||
hitpoints::{Hitpoints, Kill},
|
||||
keys::KeySpawn,
|
||||
player::head_id_to_str,
|
||||
tb_entities::EnemySpawn,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
@@ -19,18 +19,20 @@ pub fn plugin(app: &mut App) {
|
||||
app.add_systems(OnEnter(GameState::Playing), init);
|
||||
}
|
||||
|
||||
fn init(mut commands: Commands, query: Query<(Entity, &EnemySpawn)>) {
|
||||
fn init(mut commands: Commands, query: Query<(Entity, &EnemySpawn)>, heads_db: Res<HeadsDatabase>) {
|
||||
//TODO: move into HeadsDatabase
|
||||
let mut names: HashMap<String, usize> = HashMap::default();
|
||||
for i in 0..HEAD_COUNT {
|
||||
names.insert(head_id_to_str(i).to_string(), i);
|
||||
names.insert(heads_db.head_key(i).to_string(), i);
|
||||
}
|
||||
|
||||
for (e, spawn) in query.iter() {
|
||||
let id = names[&spawn.head];
|
||||
commands
|
||||
.entity(e)
|
||||
.insert((
|
||||
Hitpoints::new(100),
|
||||
Npc(HeadState::new(id, 10).with_ability(crate::abilities::HeadAbility::Thrown)),
|
||||
Npc(HeadState::new(id, 10)),
|
||||
ActiveHead(id),
|
||||
Ai,
|
||||
))
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::{
|
||||
},
|
||||
global_observer,
|
||||
head::ActiveHead,
|
||||
head_asset::HeadsDatabase,
|
||||
heads::HeadChanged,
|
||||
hitpoints::Hitpoints,
|
||||
loading_assets::GameAssets,
|
||||
@@ -251,6 +252,7 @@ fn on_update_head(
|
||||
asset_server: Res<AssetServer>,
|
||||
head: Query<Entity, With<PlayerHeadMesh>>,
|
||||
mut player_head: Query<&mut ActiveHead, With<Player>>,
|
||||
head_db: Res<HeadsDatabase>,
|
||||
) {
|
||||
let Ok(head) = head.get_single() else {
|
||||
return;
|
||||
@@ -262,7 +264,7 @@ fn on_update_head(
|
||||
|
||||
player.0 = trigger.0;
|
||||
|
||||
let head_str = head_id_to_str(trigger.0);
|
||||
let head_str = head_db.head_key(trigger.0);
|
||||
|
||||
commands.trigger(PlaySound::Head(head_str.to_string()));
|
||||
|
||||
@@ -271,27 +273,3 @@ fn on_update_head(
|
||||
|
||||
commands.entity(head).insert(SceneRoot(mesh));
|
||||
}
|
||||
|
||||
pub fn head_id_to_str(head: usize) -> &'static str {
|
||||
match head {
|
||||
0 => "angry demonstrator",
|
||||
1 => "carnival knife thrower",
|
||||
2 => "chicago gangster",
|
||||
3 => "commando",
|
||||
4 => "field medic",
|
||||
5 => "geisha",
|
||||
6 => "goblin",
|
||||
7 => "green grocer",
|
||||
8 => "highland hammer thrower",
|
||||
9 => "legionnaire",
|
||||
10 => "mig pilot",
|
||||
11 => "nanny",
|
||||
12 => "panic attack",
|
||||
13 => "salty sea dog",
|
||||
14 => "snow plough operator",
|
||||
15 => "soldier ant",
|
||||
16 => "super market shopper",
|
||||
17 => "troll",
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user