use fmc::{
bevy::math::DVec3,
interfaces::{InterfaceEventRegistration, InterfaceEvents, RegisterInterfaceNode},
items::ItemStack,
networking::{NetworkMessage, Server},
physics::Physics,
players::Player,
prelude::*,
protocol::messages,
utils::Rng,
};
use serde::{Deserialize, Serialize};
use crate::items::DroppedItem;
use super::{movement::MovementPluginPacket, Equipment, GameMode, Inventory, RespawnEvent};
pub struct HealthPlugin;
impl Plugin for HealthPlugin {
fn build(&self, app: &mut App) {
app.add_event::<PlayerDamageEvent>()
.add_event::<HealEvent>()
.add_systems(
Update,
(
register_death_interface,
change_health,
fall_damage.before(change_health),
death_interface.after(InterfaceEventRegistration),
),
);
}
}
#[derive(Default, Bundle)]
pub struct HealthBundle {
pub health: Health,
fall_damage: FallDamage,
}
impl HealthBundle {
pub fn from_health(health: Health) -> Self {
Self {
health,
..default()
}
}
}
#[derive(Component, Serialize, Deserialize, Clone)]
pub struct Health {
invincibility: Option<Timer>,
hearts: u32,
max: u32,
}
impl Default for Health {
fn default() -> Self {
Self {
invincibility: None,
hearts: 20,
max: 20,
}
}
}
impl Health {
fn build_interface(&self) -> messages::InterfaceNodeVisibilityUpdate {
let mut image_update = messages::InterfaceNodeVisibilityUpdate::default();
for i in 0..self.hearts {
image_update.set_visible(format!("health/{}", i + 1));
}
for i in self.hearts..self.max {
image_update.set_hidden(format!("health/{}", i + 1));
}
image_update
}
fn take_damage(&mut self, damage: u32) -> messages::InterfaceNodeVisibilityUpdate {
let old_hearts = self.hearts;
self.hearts = self.hearts.saturating_sub(damage);
let mut image_update = messages::InterfaceNodeVisibilityUpdate::default();
for i in self.hearts..old_hearts {
image_update.set_hidden(format!("health/{}", i + 1));
}
image_update
}
fn heal(&mut self, healing: u32) -> messages::InterfaceNodeVisibilityUpdate {
let old_hearts = self.hearts;
self.hearts = self.hearts.saturating_add(healing).min(self.max);
let mut image_update = messages::InterfaceNodeVisibilityUpdate::default();
for i in old_hearts..self.hearts {
image_update.set_visible(format!("health/{}", i + 1));
}
image_update
}
pub fn is_dead(&self) -> bool {
self.hearts == 0
}
pub fn is_invincible(&self) -> bool {
self.invincibility.is_some()
}
}
#[derive(Event)]
pub struct PlayerDamageEvent {
pub player_entity: Entity,
pub damage: u32,
pub knock_back: Option<DVec3>,
}
#[derive(Event)]
pub struct HealEvent {
pub player_entity: Entity,
pub healing: u32,
}
#[derive(Component)]
struct FallDamage {
hearts: u32,
last_position: DVec3,
last_update: std::time::Instant,
}
impl Default for FallDamage {
fn default() -> Self {
Self {
hearts: 0,
last_position: DVec3::MIN,
last_update: std::time::Instant::now(),
}
}
}
fn fall_damage(
mut fall_damage_query: Query<(&mut FallDamage, &GameMode), With<Player>>,
mut position_events: EventReader<NetworkMessage<messages::PlayerPosition>>,
mut damage_events: EventWriter<PlayerDamageEvent>,
) {
for position_update in position_events.read() {
let (mut fall_damage, game_mode) = fall_damage_query
.get_mut(position_update.player_entity)
.unwrap();
if *game_mode != GameMode::Survival {
continue;
}
let now = std::time::Instant::now();
let velocity = (position_update.position.y - fall_damage.last_position.y)
/ now.duration_since(fall_damage.last_update).as_secs_f64();
if velocity > -0.1 {
if fall_damage.hearts.saturating_sub(3) != 0 {
damage_events.send(PlayerDamageEvent {
player_entity: position_update.player_entity,
damage: fall_damage.hearts - 3,
knock_back: None,
});
}
fall_damage.hearts = 0;
} else if velocity > -3.5 {
fall_damage.hearts = 0;
} else {
let blocks_fallen =
(fall_damage.last_position.floor() - position_update.position.y.floor()).y;
fall_damage.hearts += blocks_fallen.max(0.0) as u32;
}
fall_damage.last_position = position_update.position;
fall_damage.last_update = now;
}
}
fn change_health(
mut commands: Commands,
net: Res<Server>,
time: Res<Time>,
mut health_query: Query<(
Entity,
&Transform,
&mut Inventory,
Mut<Equipment>,
Mut<Health>,
)>,
mut damage_events: EventReader<PlayerDamageEvent>,
mut heal_events: EventReader<HealEvent>,
mut rng: Local<Rng>,
) {
for (player_entity, _, _, _, mut health) in health_query.iter_mut() {
if let Some(invincibility_timer) = &mut health.invincibility {
invincibility_timer.tick(time.delta());
if invincibility_timer.just_finished() {
health.invincibility = None;
}
}
if health.is_added() {
net.send_one(player_entity, health.build_interface());
if health.is_dead() {
net.send_one(
player_entity,
messages::InterfaceVisibilityUpdate {
interface_path: "death".to_owned(),
visible: true,
},
);
}
}
}
for damage_event in damage_events.read() {
let (_, transform, mut inventory, mut equipment, mut health) =
health_query.get_mut(damage_event.player_entity).unwrap();
if health.is_dead() || health.is_invincible() {
continue;
}
health.invincibility = Some(Timer::from_seconds(0.5, TimerMode::Once));
if let Some(knock_back) = damage_event.knock_back {
net.send_one(
damage_event.player_entity,
messages::PluginData {
plugin: "movement".to_string(),
data: bincode::serialize(&MovementPluginPacket::Velocity(knock_back.as_vec3()))
.unwrap(),
},
);
}
let interface_update = health.take_damage(damage_event.damage);
net.send_one(damage_event.player_entity, interface_update);
net.broadcast(messages::Sound {
position: Some(transform.translation),
volume: 1.0,
speed: 1.0,
sound: "player_damage.ogg".to_owned(),
});
if health.is_dead() {
let equipment = equipment.into_inner();
for item_stack in inventory.iter_mut().chain([
&mut equipment.helmet,
&mut equipment.chestplate,
&mut equipment.leggings,
&mut equipment.boots,
]) {
if item_stack.is_empty() {
continue;
}
let random_direction = (rng.next_f32() * std::f32::consts::TAU) as f64;
let velocity_x = random_direction.sin() as f64 * 15.0 * rng.next_f32() as f64;
let velocity_z = random_direction.cos() as f64 * 15.0 * rng.next_f32() as f64;
let velocity_y = 6.5;
let mut new_item_stack = ItemStack::default();
item_stack.swap(&mut new_item_stack);
commands.spawn((
DroppedItem::new(new_item_stack),
transform.clone(),
Physics {
velocity: DVec3::new(velocity_x, velocity_y, velocity_z),
..default()
},
));
}
net.send_one(
damage_event.player_entity,
messages::InterfaceVisibilityUpdate {
interface_path: "death".to_owned(),
visible: true,
},
);
}
}
for heal_event in heal_events.read() {
let (_, _, _, _, mut health) = health_query.get_mut(heal_event.player_entity).unwrap();
let interface_update = health.heal(heal_event.healing);
net.send_one(heal_event.player_entity, interface_update);
}
}
#[derive(Component)]
struct DeathInterface;
fn register_death_interface(
mut commands: Commands,
new_player_query: Query<Entity, Added<Player>>,
mut registration_events: EventWriter<RegisterInterfaceNode>,
) {
for player_entity in new_player_query.iter() {
commands.entity(player_entity).with_children(|parent| {
let death_interface_entity = parent.spawn(DeathInterface).id();
registration_events.send(RegisterInterfaceNode {
player_entity,
node_path: String::from("death/respawn_button"),
node_entity: death_interface_entity,
});
});
}
}
fn death_interface(
net: Res<Server>,
mut interface_query: Query<
&mut InterfaceEvents,
(Changed<InterfaceEvents>, With<DeathInterface>),
>,
mut respawn_events: EventWriter<RespawnEvent>,
) {
for mut interface_events in interface_query.iter_mut() {
for interface_interaction in interface_events.read() {
if !matches!(
*interface_interaction,
messages::InterfaceInteraction::Button { .. }
) {
continue;
}
respawn_events.send(RespawnEvent {
player_entity: interface_interaction.player_entity,
});
net.send_one(
interface_interaction.player_entity,
messages::InterfaceVisibilityUpdate {
interface_path: "death".to_owned(),
visible: false,
},
);
}
}
}