Run trigger logic client side (#86)
This commit is contained in:
@@ -1,12 +1,11 @@
|
||||
use super::TriggerArrow;
|
||||
use crate::{
|
||||
GameState, billboards::Billboard, global_observer, heads_database::HeadsDatabase,
|
||||
hitpoints::Hit, loading_assets::GameAssets, physics_layers::GameLayer, protocol::PlaySound,
|
||||
hitpoints::Hit, loading_assets::GameAssets, physics_layers::GameLayer,
|
||||
utils::sprite_3d_animation::AnimationTimer,
|
||||
};
|
||||
use avian3d::prelude::*;
|
||||
use bevy::{light::NotShadowCaster, prelude::*};
|
||||
use bevy_replicon::prelude::{SendMode, ServerTriggerExt, ToClients};
|
||||
use bevy_sprite3d::Sprite3d;
|
||||
|
||||
#[derive(Component)]
|
||||
@@ -45,10 +44,8 @@ fn on_trigger_arrow(
|
||||
) {
|
||||
let state = trigger.0;
|
||||
|
||||
commands.server_trigger(ToClients {
|
||||
mode: SendMode::Broadcast,
|
||||
message: PlaySound::Crossbow,
|
||||
});
|
||||
#[cfg(feature = "client")]
|
||||
commands.trigger(crate::protocol::PlaySound::Crossbow);
|
||||
|
||||
let rotation = if let Some(target) = state.target {
|
||||
let t = query_transform
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
use super::TriggerGun;
|
||||
use crate::{
|
||||
GameState, billboards::Billboard, global_observer, heads_database::HeadsDatabase,
|
||||
hitpoints::Hit, loading_assets::GameAssets, physics_layers::GameLayer, protocol::PlaySound,
|
||||
tb_entities::EnemySpawn, utils::sprite_3d_animation::AnimationTimer,
|
||||
hitpoints::Hit, loading_assets::GameAssets, physics_layers::GameLayer, tb_entities::EnemySpawn,
|
||||
utils::sprite_3d_animation::AnimationTimer,
|
||||
};
|
||||
use avian3d::prelude::*;
|
||||
use bevy::{light::NotShadowCaster, prelude::*};
|
||||
use bevy_replicon::prelude::{SendMode, ServerTriggerExt, ToClients};
|
||||
use bevy_sprite3d::Sprite3d;
|
||||
|
||||
#[derive(Component)]
|
||||
@@ -92,10 +91,8 @@ fn on_trigger_gun(
|
||||
) {
|
||||
let state = trigger.0;
|
||||
|
||||
commands.server_trigger(ToClients {
|
||||
mode: SendMode::Broadcast,
|
||||
message: PlaySound::Gun,
|
||||
});
|
||||
#[cfg(feature = "client")]
|
||||
commands.trigger(crate::protocol::PlaySound::Gun);
|
||||
|
||||
let rotation = if let Some(t) = state
|
||||
.target
|
||||
|
||||
@@ -12,13 +12,12 @@ use crate::{
|
||||
protocol::PlaySound,
|
||||
utils::{billboards::Billboard, explosions::Explosion, sprite_3d_animation::AnimationTimer},
|
||||
};
|
||||
#[cfg(feature = "server")]
|
||||
use crate::{
|
||||
aim::AimTarget, character::CharacterHierarchy, control::Inputs, head::ActiveHead,
|
||||
heads::ActiveHeads, heads_database::HeadsDatabase, player::Player,
|
||||
aim::AimTarget, character::CharacterHierarchy, control::Inputs, heads::ActiveHeads,
|
||||
heads_database::HeadsDatabase, player::Player,
|
||||
};
|
||||
use bevy::{light::NotShadowCaster, prelude::*};
|
||||
use bevy_replicon::prelude::{ClientState, SendMode, ServerTriggerExt, ToClients};
|
||||
use bevy_replicon::prelude::{SendMode, ServerTriggerExt, ToClients};
|
||||
use bevy_sprite3d::Sprite3d;
|
||||
pub use healing::Healing;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -82,14 +81,14 @@ pub struct TriggerMissile(pub TriggerData);
|
||||
#[derive(Event, Reflect)]
|
||||
pub struct TriggerCurver(pub TriggerData);
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
pub struct TriggerStateRes {
|
||||
#[cfg(feature = "server")]
|
||||
#[derive(Component, Default, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct PlayerTriggerState {
|
||||
next_trigger_timestamp: f32,
|
||||
active: bool,
|
||||
}
|
||||
|
||||
impl TriggerStateRes {
|
||||
impl PlayerTriggerState {
|
||||
pub fn is_active(&self) -> bool {
|
||||
self.active
|
||||
}
|
||||
@@ -115,8 +114,6 @@ pub enum ExplodingProjectileSet {
|
||||
}
|
||||
|
||||
pub fn plugin(app: &mut App) {
|
||||
app.init_resource::<TriggerStateRes>();
|
||||
|
||||
app.add_plugins(gun::plugin);
|
||||
app.add_plugins(thrown::plugin);
|
||||
app.add_plugins(arrow::plugin);
|
||||
@@ -130,9 +127,7 @@ pub fn plugin(app: &mut App) {
|
||||
.after(ExplodingProjectileSet::Mark)
|
||||
.run_if(in_state(GameState::Playing)),
|
||||
);
|
||||
|
||||
app.add_systems(OnEnter(GameState::Playing), setup);
|
||||
#[cfg(feature = "server")]
|
||||
app.add_systems(
|
||||
FixedUpdate,
|
||||
(on_trigger_state, update, update_heal_ability)
|
||||
@@ -141,9 +136,7 @@ pub fn plugin(app: &mut App) {
|
||||
);
|
||||
app.add_systems(
|
||||
FixedUpdate,
|
||||
explode_projectiles
|
||||
.run_if(in_state(ClientState::Disconnected))
|
||||
.in_set(ExplodingProjectileSet::Explode),
|
||||
explode_projectiles.in_set(ExplodingProjectileSet::Explode),
|
||||
);
|
||||
|
||||
global_observer!(app, build_explosion_sprite);
|
||||
@@ -183,114 +176,113 @@ fn explode_projectiles(mut commands: Commands, query: Query<(Entity, &ExplodingP
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn on_trigger_state(
|
||||
mut res: ResMut<TriggerStateRes>,
|
||||
players: Query<(&ActiveHead, &Inputs), With<Player>>,
|
||||
) {
|
||||
for (_, inputs) in players.iter() {
|
||||
res.active = inputs.trigger;
|
||||
fn on_trigger_state(mut players: Query<(&mut PlayerTriggerState, &Inputs), With<Player>>) {
|
||||
for (mut trigger_state, inputs) in players.iter_mut() {
|
||||
trigger_state.active = inputs.trigger;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn update(
|
||||
mut res: ResMut<TriggerStateRes>,
|
||||
mut commands: Commands,
|
||||
player_query: Query<(Entity, &AimTarget, &Inputs), With<Player>>,
|
||||
mut active_heads: Single<&mut ActiveHeads, With<Player>>,
|
||||
mut query: Query<
|
||||
(
|
||||
Entity,
|
||||
&mut ActiveHeads,
|
||||
&mut PlayerTriggerState,
|
||||
&AimTarget,
|
||||
&Inputs,
|
||||
),
|
||||
With<Player>,
|
||||
>,
|
||||
query_transform: Query<&Transform>,
|
||||
heads_db: Res<HeadsDatabase>,
|
||||
time: Res<Time>,
|
||||
character: CharacterHierarchy,
|
||||
) {
|
||||
if res.active && res.next_trigger_timestamp < time.elapsed_secs() {
|
||||
let Some(state) = active_heads.current() else {
|
||||
return;
|
||||
};
|
||||
for (player, mut active_heads, mut trigger_state, target, inputs) in query.iter_mut() {
|
||||
if trigger_state.active && trigger_state.next_trigger_timestamp < time.elapsed_secs() {
|
||||
let Some(state) = active_heads.current() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !state.has_ammo() {
|
||||
commands.server_trigger(ToClients {
|
||||
mode: SendMode::Broadcast,
|
||||
message: PlaySound::Invalid,
|
||||
});
|
||||
return;
|
||||
let target = if let Some(target) = target.0
|
||||
&& query_transform.get(target).is_ok()
|
||||
{
|
||||
Some(target)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let Some(projectile_origin) = character
|
||||
.projectile_origin(player)
|
||||
.map(|origin| origin.translation())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let head = heads_db.head_stats(state.head);
|
||||
|
||||
if matches!(head.ability, HeadAbility::None | HeadAbility::Medic) {
|
||||
return;
|
||||
}
|
||||
|
||||
active_heads.use_ammo(time.elapsed_secs());
|
||||
|
||||
trigger_state.next_trigger_timestamp = time.elapsed_secs() + (1. / head.aps);
|
||||
|
||||
let trigger_state = TriggerData {
|
||||
dir: Dir3::try_from(inputs.look_dir).unwrap_or(Dir3::NEG_Z),
|
||||
pos: projectile_origin,
|
||||
target,
|
||||
target_layer: GameLayer::Npc,
|
||||
head: state.head,
|
||||
};
|
||||
|
||||
match head.ability {
|
||||
HeadAbility::Thrown => commands.trigger(TriggerThrow(trigger_state)),
|
||||
HeadAbility::Gun => commands.trigger(TriggerGun(trigger_state)),
|
||||
HeadAbility::Missile => commands.trigger(TriggerMissile(trigger_state)),
|
||||
HeadAbility::Arrow => commands.trigger(TriggerArrow(trigger_state)),
|
||||
HeadAbility::Curver => commands.trigger(TriggerCurver(trigger_state)),
|
||||
_ => panic!("Unhandled head ability"),
|
||||
};
|
||||
}
|
||||
|
||||
let Some((player, target, inputs)) = player_query.iter().next() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(projectile_origin) = character
|
||||
.projectile_origin(player)
|
||||
.map(|origin| origin.translation())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let head = heads_db.head_stats(state.head);
|
||||
|
||||
if matches!(head.ability, HeadAbility::None | HeadAbility::Medic) {
|
||||
return;
|
||||
}
|
||||
|
||||
active_heads.use_ammo(time.elapsed_secs());
|
||||
|
||||
res.next_trigger_timestamp = time.elapsed_secs() + (1. / head.aps);
|
||||
|
||||
let trigger_state = TriggerData {
|
||||
dir: Dir3::try_from(inputs.look_dir).unwrap_or(Dir3::NEG_Z),
|
||||
pos: projectile_origin,
|
||||
target: target.0,
|
||||
target_layer: GameLayer::Npc,
|
||||
head: state.head,
|
||||
};
|
||||
|
||||
match head.ability {
|
||||
HeadAbility::Thrown => commands.trigger(TriggerThrow(trigger_state)),
|
||||
HeadAbility::Gun => commands.trigger(TriggerGun(trigger_state)),
|
||||
HeadAbility::Missile => commands.trigger(TriggerMissile(trigger_state)),
|
||||
HeadAbility::Arrow => commands.trigger(TriggerArrow(trigger_state)),
|
||||
HeadAbility::Curver => commands.trigger(TriggerCurver(trigger_state)),
|
||||
_ => panic!("Unhandled head ability"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn update_heal_ability(
|
||||
res: Res<TriggerStateRes>,
|
||||
mut commands: Commands,
|
||||
active_heads: Single<(Entity, &ActiveHeads), With<Player>>,
|
||||
players: Query<(Entity, &ActiveHeads, Ref<PlayerTriggerState>), With<Player>>,
|
||||
heads_db: Res<HeadsDatabase>,
|
||||
) {
|
||||
if res.is_changed() {
|
||||
let Some(state) = active_heads.1.current() else {
|
||||
return;
|
||||
};
|
||||
for (player, active_heads, trigger_state) in players.iter() {
|
||||
if trigger_state.is_changed() {
|
||||
let Some(state) = active_heads.current() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let player = active_heads.0;
|
||||
let head = heads_db.head_stats(state.head);
|
||||
|
||||
let head = heads_db.head_stats(state.head);
|
||||
if !matches!(head.ability, HeadAbility::Medic) {
|
||||
return;
|
||||
}
|
||||
|
||||
if !matches!(head.ability, HeadAbility::Medic) {
|
||||
return;
|
||||
}
|
||||
use crate::abilities::healing::HealingState;
|
||||
if trigger_state.active {
|
||||
use crate::abilities::healing::HealingStateChanged;
|
||||
|
||||
use crate::abilities::healing::HealingState;
|
||||
if res.active {
|
||||
use crate::abilities::healing::HealingStateChanged;
|
||||
commands.trigger(HealingStateChanged {
|
||||
state: HealingState::Started,
|
||||
entity: player,
|
||||
});
|
||||
} else {
|
||||
use crate::abilities::healing::HealingStateChanged;
|
||||
|
||||
commands.trigger(HealingStateChanged {
|
||||
state: HealingState::Started,
|
||||
entity: player,
|
||||
});
|
||||
} else {
|
||||
use crate::abilities::healing::HealingStateChanged;
|
||||
|
||||
commands.trigger(HealingStateChanged {
|
||||
state: HealingState::Stopped,
|
||||
entity: player,
|
||||
});
|
||||
commands.trigger(HealingStateChanged {
|
||||
state: HealingState::Stopped,
|
||||
entity: player,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::{
|
||||
use avian3d::prelude::*;
|
||||
use bevy::prelude::*;
|
||||
use bevy_ballistic::launch_velocity;
|
||||
use bevy_replicon::prelude::{Replicated, SendMode, ServerTriggerExt, ToClients};
|
||||
use bevy_replicon::prelude::Replicated;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Component, Serialize, Deserialize, PartialEq)]
|
||||
@@ -35,10 +35,8 @@ fn on_trigger_thrown(
|
||||
) {
|
||||
let state = trigger.event().0;
|
||||
|
||||
commands.server_trigger(ToClients {
|
||||
mode: SendMode::Broadcast,
|
||||
message: PlaySound::Throw,
|
||||
});
|
||||
#[cfg(feature = "client")]
|
||||
commands.trigger(PlaySound::Throw);
|
||||
|
||||
const SPEED: f32 = 35.;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::{ControllerSet, ControllerSwitchEvent};
|
||||
use crate::{
|
||||
GameState,
|
||||
abilities::TriggerStateRes,
|
||||
abilities::PlayerTriggerState,
|
||||
animation::AnimationFlags,
|
||||
control::{ControllerSettings, Inputs, SelectedController},
|
||||
physics_layers::GameLayer,
|
||||
@@ -42,13 +42,17 @@ pub fn plugin(app: &mut App) {
|
||||
}
|
||||
|
||||
fn set_animation_flags(
|
||||
trigger: Res<TriggerStateRes>,
|
||||
mut player: Query<
|
||||
(&Grounding, &mut AnimationFlags, &Inputs),
|
||||
(
|
||||
&Grounding,
|
||||
&mut AnimationFlags,
|
||||
&Inputs,
|
||||
&PlayerTriggerState,
|
||||
),
|
||||
(With<Player>, Without<ConfirmHistory>),
|
||||
>,
|
||||
) {
|
||||
for (grounding, mut flags, inputs) in player.iter_mut() {
|
||||
for (grounding, mut flags, inputs, trigger_state) in player.iter_mut() {
|
||||
let direction = inputs.move_dir;
|
||||
let deadzone = 0.2;
|
||||
|
||||
@@ -60,7 +64,7 @@ fn set_animation_flags(
|
||||
flags.any_direction = true;
|
||||
}
|
||||
|
||||
flags.shooting = trigger.is_active();
|
||||
flags.shooting = trigger_state.is_active();
|
||||
|
||||
// `apply_controls` sets the jump flag when the player actually jumps.
|
||||
// Unset the flag on hitting the ground
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::{
|
||||
GameState,
|
||||
abilities::PlayerTriggerState,
|
||||
cash::{Cash, CashCollectEvent},
|
||||
character::HedzCharacter,
|
||||
protocol::PlayerId,
|
||||
@@ -16,7 +17,7 @@ use happy_feet::debug::DebugInput;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Component, Default, Serialize, Deserialize, PartialEq)]
|
||||
#[require(HedzCharacter, DebugInput = DebugInput)]
|
||||
#[require(HedzCharacter, DebugInput = DebugInput, PlayerTriggerState)]
|
||||
pub struct Player;
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
|
||||
Reference in New Issue
Block a user