249 lines
7.1 KiB
Rust
249 lines
7.1 KiB
Rust
use crate::{
|
|
GameState,
|
|
abilities::PlayerTriggerState,
|
|
backpack::Backpack,
|
|
camera::{CameraArmRotation, CameraTarget},
|
|
cash::CashInventory,
|
|
character::{AnimatedCharacter, HedzCharacter},
|
|
control::{Inputs, LocalInputs, controller_common::PlayerCharacterController},
|
|
global_observer,
|
|
head::ActiveHead,
|
|
head_drop::HeadDrops,
|
|
heads::{ActiveHeads, HeadChanged, HeadState},
|
|
heads_database::HeadsDatabase,
|
|
hitpoints::{Hitpoints, Kill},
|
|
npc::SpawnCharacter,
|
|
protocol::{ClientHeadChanged, OwnedByClient, PlaySound, PlayerId},
|
|
tb_entities::SpawnPoint,
|
|
};
|
|
use bevy::{
|
|
input::common_conditions::input_just_pressed,
|
|
prelude::*,
|
|
window::{CursorGrabMode, CursorOptions, PrimaryWindow},
|
|
};
|
|
use bevy_replicon::prelude::{ClientId, Replicated, SendMode, ServerTriggerExt, ToClients};
|
|
use happy_feet::debug::DebugInput;
|
|
use rand::Rng;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Component, Default, Serialize, Deserialize, PartialEq)]
|
|
#[require(HedzCharacter, DebugInput = DebugInput, PlayerTriggerState)]
|
|
pub struct Player;
|
|
|
|
#[derive(Component, Debug, Reflect)]
|
|
#[reflect(Component)]
|
|
#[require(LocalInputs)]
|
|
pub struct LocalPlayer;
|
|
|
|
#[derive(Component, Default, Serialize, Deserialize, PartialEq)]
|
|
#[require(Transform, Visibility)]
|
|
pub struct PlayerBodyMesh;
|
|
|
|
/// Server-side only; inserted on each `client` (not the controller) to track player ids.
|
|
#[derive(Component, Clone, Copy)]
|
|
pub struct ClientPlayerId(pub PlayerId);
|
|
|
|
pub fn plugin(app: &mut App) {
|
|
app.add_systems(
|
|
OnEnter(GameState::Playing),
|
|
(toggle_cursor_system, cursor_recenter),
|
|
);
|
|
|
|
app.add_systems(
|
|
Update,
|
|
(
|
|
setup_animations_marker_for_player,
|
|
toggle_cursor_system.run_if(input_just_pressed(KeyCode::Escape)),
|
|
)
|
|
.run_if(in_state(GameState::Playing)),
|
|
);
|
|
|
|
global_observer!(app, on_update_head_mesh);
|
|
}
|
|
|
|
pub fn spawn(
|
|
mut commands: Commands,
|
|
owner: ClientId,
|
|
id: PlayerId,
|
|
query: Query<&Transform, With<SpawnPoint>>,
|
|
heads_db: Res<HeadsDatabase>,
|
|
) -> Option<Entity> {
|
|
let spawn = query.iter().next()?;
|
|
|
|
// This offset helps prevent players from getting stuck inside each other on spawn and causing a perpetual
|
|
// motion machine.
|
|
let random_offset = Vec3::new(
|
|
rand::thread_rng().gen_range(-0.01..0.01),
|
|
0.0,
|
|
rand::thread_rng().gen_range(-0.01..0.01),
|
|
);
|
|
let transform =
|
|
Transform::from_translation(spawn.translation + Vec3::new(0., 3., 0.) + random_offset);
|
|
|
|
let id = commands
|
|
.spawn((
|
|
(
|
|
Name::from("player"),
|
|
Player,
|
|
ActiveHead(0),
|
|
ActiveHeads::new([
|
|
Some(HeadState::new(0, heads_db.as_ref())),
|
|
Some(HeadState::new(3, heads_db.as_ref())),
|
|
Some(HeadState::new(6, heads_db.as_ref())),
|
|
Some(HeadState::new(10, heads_db.as_ref())),
|
|
Some(HeadState::new(9, heads_db.as_ref())),
|
|
]),
|
|
Hitpoints::new(100),
|
|
CashInventory::default(),
|
|
CameraTarget,
|
|
transform,
|
|
Visibility::default(),
|
|
PlayerCharacterController,
|
|
id,
|
|
),
|
|
Backpack::default(),
|
|
Inputs::default(),
|
|
Replicated,
|
|
))
|
|
.with_children(|c| {
|
|
c.spawn((
|
|
Name::new("player-rig"),
|
|
PlayerBodyMesh,
|
|
CameraArmRotation,
|
|
Replicated,
|
|
))
|
|
.with_child((
|
|
Name::new("player-animated-character"),
|
|
AnimatedCharacter::new(0),
|
|
Replicated,
|
|
));
|
|
})
|
|
.observe(on_kill)
|
|
.id();
|
|
|
|
if let Some(owner) = owner.entity() {
|
|
commands.entity(id).insert(OwnedByClient(owner));
|
|
}
|
|
|
|
commands.server_trigger(ToClients {
|
|
mode: SendMode::Broadcast,
|
|
message: PlaySound::Head("angry demonstrator".to_string()),
|
|
});
|
|
commands.trigger(SpawnCharacter(transform.translation));
|
|
|
|
Some(id)
|
|
}
|
|
|
|
fn on_kill(
|
|
trigger: On<Kill>,
|
|
mut commands: Commands,
|
|
mut query: Query<(
|
|
Entity,
|
|
&Transform,
|
|
&ActiveHead,
|
|
&mut ActiveHeads,
|
|
&mut Hitpoints,
|
|
)>,
|
|
) {
|
|
let Ok((player, transform, active, mut heads, mut hp)) = query.get_mut(trigger.event().entity)
|
|
else {
|
|
return;
|
|
};
|
|
|
|
commands.trigger(HeadDrops::new(transform.translation, active.0));
|
|
|
|
if let Some(new_head) = heads.loose_current() {
|
|
hp.set_health(heads.current().unwrap().health);
|
|
|
|
commands.trigger(HeadChanged {
|
|
entity: player,
|
|
head: new_head,
|
|
});
|
|
}
|
|
}
|
|
|
|
fn on_update_head_mesh(
|
|
trigger: On<HeadChanged>,
|
|
mut commands: Commands,
|
|
player_id: Query<&PlayerId, With<Player>>,
|
|
children: Query<&Children>,
|
|
player_body_mesh: Query<&PlayerBodyMesh>,
|
|
animated_characters: Query<&AnimatedCharacter>,
|
|
mut active_head: Query<&mut ActiveHead>,
|
|
) -> Result {
|
|
let player_id = *(player_id.get(trigger.entity)?);
|
|
|
|
let player_body_mesh = children
|
|
.get(trigger.entity)?
|
|
.iter()
|
|
.find(|child| player_body_mesh.get(*child).is_ok())
|
|
.unwrap();
|
|
|
|
let animated_character = children
|
|
.get(player_body_mesh)?
|
|
.iter()
|
|
.find(|child| animated_characters.get(*child).is_ok())
|
|
.unwrap();
|
|
|
|
{
|
|
let mut active_head = active_head.get_mut(trigger.entity)?;
|
|
active_head.0 = trigger.head;
|
|
}
|
|
|
|
commands
|
|
.entity(animated_character)
|
|
.insert(AnimatedCharacter::new(trigger.head));
|
|
|
|
commands.server_trigger(ToClients {
|
|
mode: SendMode::Broadcast,
|
|
message: ClientHeadChanged {
|
|
player: player_id,
|
|
head: trigger.head,
|
|
},
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn cursor_recenter(q_windows: Single<&mut Window, With<PrimaryWindow>>) {
|
|
let mut primary_window = q_windows;
|
|
let center = Vec2::new(
|
|
primary_window.resolution.width() / 2.,
|
|
primary_window.resolution.height() / 2.,
|
|
);
|
|
primary_window.set_cursor_position(Some(center));
|
|
}
|
|
|
|
fn toggle_grab_cursor(options: &mut CursorOptions) {
|
|
match options.grab_mode {
|
|
CursorGrabMode::None => {
|
|
options.grab_mode = CursorGrabMode::Confined;
|
|
options.visible = false;
|
|
}
|
|
_ => {
|
|
options.grab_mode = CursorGrabMode::None;
|
|
options.visible = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn toggle_cursor_system(mut window: Single<&mut CursorOptions, With<PrimaryWindow>>) {
|
|
toggle_grab_cursor(&mut window);
|
|
}
|
|
|
|
fn setup_animations_marker_for_player(
|
|
mut commands: Commands,
|
|
animation_handles: Query<Entity, Added<AnimationGraphHandle>>,
|
|
child_of: Query<&ChildOf>,
|
|
player_rig: Query<&ChildOf, With<PlayerBodyMesh>>,
|
|
) {
|
|
for animation_rig in animation_handles.iter() {
|
|
for ancestor in child_of.iter_ancestors(animation_rig) {
|
|
if let Ok(rig_child_of) = player_rig.get(ancestor) {
|
|
commands.entity(rig_child_of.parent());
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|