Run trigger logic client side (#86)

This commit is contained in:
extrawurst
2025-12-12 04:36:29 +01:00
committed by GitHub
parent e044558a93
commit e7ebff2029
6 changed files with 113 additions and 124 deletions

View File

@@ -1,12 +1,11 @@
use super::TriggerArrow; use super::TriggerArrow;
use crate::{ use crate::{
GameState, billboards::Billboard, global_observer, heads_database::HeadsDatabase, 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, utils::sprite_3d_animation::AnimationTimer,
}; };
use avian3d::prelude::*; use avian3d::prelude::*;
use bevy::{light::NotShadowCaster, prelude::*}; use bevy::{light::NotShadowCaster, prelude::*};
use bevy_replicon::prelude::{SendMode, ServerTriggerExt, ToClients};
use bevy_sprite3d::Sprite3d; use bevy_sprite3d::Sprite3d;
#[derive(Component)] #[derive(Component)]
@@ -45,10 +44,8 @@ fn on_trigger_arrow(
) { ) {
let state = trigger.0; let state = trigger.0;
commands.server_trigger(ToClients { #[cfg(feature = "client")]
mode: SendMode::Broadcast, commands.trigger(crate::protocol::PlaySound::Crossbow);
message: PlaySound::Crossbow,
});
let rotation = if let Some(target) = state.target { let rotation = if let Some(target) = state.target {
let t = query_transform let t = query_transform

View File

@@ -1,12 +1,11 @@
use super::TriggerGun; use super::TriggerGun;
use crate::{ use crate::{
GameState, billboards::Billboard, global_observer, heads_database::HeadsDatabase, 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, tb_entities::EnemySpawn,
tb_entities::EnemySpawn, utils::sprite_3d_animation::AnimationTimer, utils::sprite_3d_animation::AnimationTimer,
}; };
use avian3d::prelude::*; use avian3d::prelude::*;
use bevy::{light::NotShadowCaster, prelude::*}; use bevy::{light::NotShadowCaster, prelude::*};
use bevy_replicon::prelude::{SendMode, ServerTriggerExt, ToClients};
use bevy_sprite3d::Sprite3d; use bevy_sprite3d::Sprite3d;
#[derive(Component)] #[derive(Component)]
@@ -92,10 +91,8 @@ fn on_trigger_gun(
) { ) {
let state = trigger.0; let state = trigger.0;
commands.server_trigger(ToClients { #[cfg(feature = "client")]
mode: SendMode::Broadcast, commands.trigger(crate::protocol::PlaySound::Gun);
message: PlaySound::Gun,
});
let rotation = if let Some(t) = state let rotation = if let Some(t) = state
.target .target

View File

@@ -12,13 +12,12 @@ use crate::{
protocol::PlaySound, protocol::PlaySound,
utils::{billboards::Billboard, explosions::Explosion, sprite_3d_animation::AnimationTimer}, utils::{billboards::Billboard, explosions::Explosion, sprite_3d_animation::AnimationTimer},
}; };
#[cfg(feature = "server")]
use crate::{ use crate::{
aim::AimTarget, character::CharacterHierarchy, control::Inputs, head::ActiveHead, aim::AimTarget, character::CharacterHierarchy, control::Inputs, heads::ActiveHeads,
heads::ActiveHeads, heads_database::HeadsDatabase, player::Player, heads_database::HeadsDatabase, player::Player,
}; };
use bevy::{light::NotShadowCaster, prelude::*}; use bevy::{light::NotShadowCaster, prelude::*};
use bevy_replicon::prelude::{ClientState, SendMode, ServerTriggerExt, ToClients}; use bevy_replicon::prelude::{SendMode, ServerTriggerExt, ToClients};
use bevy_sprite3d::Sprite3d; use bevy_sprite3d::Sprite3d;
pub use healing::Healing; pub use healing::Healing;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -82,14 +81,14 @@ pub struct TriggerMissile(pub TriggerData);
#[derive(Event, Reflect)] #[derive(Event, Reflect)]
pub struct TriggerCurver(pub TriggerData); pub struct TriggerCurver(pub TriggerData);
#[derive(Resource, Default)] #[derive(Component, Default, Reflect)]
pub struct TriggerStateRes { #[reflect(Component)]
#[cfg(feature = "server")] pub struct PlayerTriggerState {
next_trigger_timestamp: f32, next_trigger_timestamp: f32,
active: bool, active: bool,
} }
impl TriggerStateRes { impl PlayerTriggerState {
pub fn is_active(&self) -> bool { pub fn is_active(&self) -> bool {
self.active self.active
} }
@@ -115,8 +114,6 @@ pub enum ExplodingProjectileSet {
} }
pub fn plugin(app: &mut App) { pub fn plugin(app: &mut App) {
app.init_resource::<TriggerStateRes>();
app.add_plugins(gun::plugin); app.add_plugins(gun::plugin);
app.add_plugins(thrown::plugin); app.add_plugins(thrown::plugin);
app.add_plugins(arrow::plugin); app.add_plugins(arrow::plugin);
@@ -130,9 +127,7 @@ pub fn plugin(app: &mut App) {
.after(ExplodingProjectileSet::Mark) .after(ExplodingProjectileSet::Mark)
.run_if(in_state(GameState::Playing)), .run_if(in_state(GameState::Playing)),
); );
app.add_systems(OnEnter(GameState::Playing), setup); app.add_systems(OnEnter(GameState::Playing), setup);
#[cfg(feature = "server")]
app.add_systems( app.add_systems(
FixedUpdate, FixedUpdate,
(on_trigger_state, update, update_heal_ability) (on_trigger_state, update, update_heal_ability)
@@ -141,9 +136,7 @@ pub fn plugin(app: &mut App) {
); );
app.add_systems( app.add_systems(
FixedUpdate, FixedUpdate,
explode_projectiles explode_projectiles.in_set(ExplodingProjectileSet::Explode),
.run_if(in_state(ClientState::Disconnected))
.in_set(ExplodingProjectileSet::Explode),
); );
global_observer!(app, build_explosion_sprite); global_observer!(app, build_explosion_sprite);
@@ -183,41 +176,41 @@ fn explode_projectiles(mut commands: Commands, query: Query<(Entity, &ExplodingP
} }
} }
#[cfg(feature = "server")] fn on_trigger_state(mut players: Query<(&mut PlayerTriggerState, &Inputs), With<Player>>) {
fn on_trigger_state( for (mut trigger_state, inputs) in players.iter_mut() {
mut res: ResMut<TriggerStateRes>, trigger_state.active = inputs.trigger;
players: Query<(&ActiveHead, &Inputs), With<Player>>,
) {
for (_, inputs) in players.iter() {
res.active = inputs.trigger;
} }
} }
#[cfg(feature = "server")]
fn update( fn update(
mut res: ResMut<TriggerStateRes>,
mut commands: Commands, mut commands: Commands,
player_query: Query<(Entity, &AimTarget, &Inputs), With<Player>>, mut query: Query<
mut active_heads: Single<&mut ActiveHeads, With<Player>>, (
Entity,
&mut ActiveHeads,
&mut PlayerTriggerState,
&AimTarget,
&Inputs,
),
With<Player>,
>,
query_transform: Query<&Transform>,
heads_db: Res<HeadsDatabase>, heads_db: Res<HeadsDatabase>,
time: Res<Time>, time: Res<Time>,
character: CharacterHierarchy, character: CharacterHierarchy,
) { ) {
if res.active && res.next_trigger_timestamp < time.elapsed_secs() { 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 { let Some(state) = active_heads.current() else {
return; return;
}; };
if !state.has_ammo() { let target = if let Some(target) = target.0
commands.server_trigger(ToClients { && query_transform.get(target).is_ok()
mode: SendMode::Broadcast, {
message: PlaySound::Invalid, Some(target)
}); } else {
return; None
}
let Some((player, target, inputs)) = player_query.iter().next() else {
return;
}; };
let Some(projectile_origin) = character let Some(projectile_origin) = character
@@ -235,12 +228,12 @@ fn update(
active_heads.use_ammo(time.elapsed_secs()); active_heads.use_ammo(time.elapsed_secs());
res.next_trigger_timestamp = time.elapsed_secs() + (1. / head.aps); trigger_state.next_trigger_timestamp = time.elapsed_secs() + (1. / head.aps);
let trigger_state = TriggerData { let trigger_state = TriggerData {
dir: Dir3::try_from(inputs.look_dir).unwrap_or(Dir3::NEG_Z), dir: Dir3::try_from(inputs.look_dir).unwrap_or(Dir3::NEG_Z),
pos: projectile_origin, pos: projectile_origin,
target: target.0, target,
target_layer: GameLayer::Npc, target_layer: GameLayer::Npc,
head: state.head, head: state.head,
}; };
@@ -255,21 +248,19 @@ fn update(
}; };
} }
} }
}
#[cfg(feature = "server")]
fn update_heal_ability( fn update_heal_ability(
res: Res<TriggerStateRes>,
mut commands: Commands, mut commands: Commands,
active_heads: Single<(Entity, &ActiveHeads), With<Player>>, players: Query<(Entity, &ActiveHeads, Ref<PlayerTriggerState>), With<Player>>,
heads_db: Res<HeadsDatabase>, heads_db: Res<HeadsDatabase>,
) { ) {
if res.is_changed() { for (player, active_heads, trigger_state) in players.iter() {
let Some(state) = active_heads.1.current() else { if trigger_state.is_changed() {
let Some(state) = active_heads.current() else {
return; 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) { if !matches!(head.ability, HeadAbility::Medic) {
@@ -277,7 +268,7 @@ fn update_heal_ability(
} }
use crate::abilities::healing::HealingState; use crate::abilities::healing::HealingState;
if res.active { if trigger_state.active {
use crate::abilities::healing::HealingStateChanged; use crate::abilities::healing::HealingStateChanged;
commands.trigger(HealingStateChanged { commands.trigger(HealingStateChanged {
@@ -294,6 +285,7 @@ fn update_heal_ability(
} }
} }
} }
}
#[derive(Resource)] #[derive(Resource)]
struct ShotAssets { struct ShotAssets {

View File

@@ -9,7 +9,7 @@ use crate::{
use avian3d::prelude::*; use avian3d::prelude::*;
use bevy::prelude::*; use bevy::prelude::*;
use bevy_ballistic::launch_velocity; use bevy_ballistic::launch_velocity;
use bevy_replicon::prelude::{Replicated, SendMode, ServerTriggerExt, ToClients}; use bevy_replicon::prelude::Replicated;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Component, Serialize, Deserialize, PartialEq)] #[derive(Component, Serialize, Deserialize, PartialEq)]
@@ -35,10 +35,8 @@ fn on_trigger_thrown(
) { ) {
let state = trigger.event().0; let state = trigger.event().0;
commands.server_trigger(ToClients { #[cfg(feature = "client")]
mode: SendMode::Broadcast, commands.trigger(PlaySound::Throw);
message: PlaySound::Throw,
});
const SPEED: f32 = 35.; const SPEED: f32 = 35.;

View File

@@ -1,7 +1,7 @@
use super::{ControllerSet, ControllerSwitchEvent}; use super::{ControllerSet, ControllerSwitchEvent};
use crate::{ use crate::{
GameState, GameState,
abilities::TriggerStateRes, abilities::PlayerTriggerState,
animation::AnimationFlags, animation::AnimationFlags,
control::{ControllerSettings, Inputs, SelectedController}, control::{ControllerSettings, Inputs, SelectedController},
physics_layers::GameLayer, physics_layers::GameLayer,
@@ -42,13 +42,17 @@ pub fn plugin(app: &mut App) {
} }
fn set_animation_flags( fn set_animation_flags(
trigger: Res<TriggerStateRes>,
mut player: Query< mut player: Query<
(&Grounding, &mut AnimationFlags, &Inputs), (
&Grounding,
&mut AnimationFlags,
&Inputs,
&PlayerTriggerState,
),
(With<Player>, Without<ConfirmHistory>), (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 direction = inputs.move_dir;
let deadzone = 0.2; let deadzone = 0.2;
@@ -60,7 +64,7 @@ fn set_animation_flags(
flags.any_direction = true; 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. // `apply_controls` sets the jump flag when the player actually jumps.
// Unset the flag on hitting the ground // Unset the flag on hitting the ground

View File

@@ -1,5 +1,6 @@
use crate::{ use crate::{
GameState, GameState,
abilities::PlayerTriggerState,
cash::{Cash, CashCollectEvent}, cash::{Cash, CashCollectEvent},
character::HedzCharacter, character::HedzCharacter,
protocol::PlayerId, protocol::PlayerId,
@@ -16,7 +17,7 @@ use happy_feet::debug::DebugInput;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Component, Default, Serialize, Deserialize, PartialEq)] #[derive(Component, Default, Serialize, Deserialize, PartialEq)]
#[require(HedzCharacter, DebugInput = DebugInput)] #[require(HedzCharacter, DebugInput = DebugInput, PlayerTriggerState)]
pub struct Player; pub struct Player;
#[cfg(feature = "client")] #[cfg(feature = "client")]