You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

262 lines
7.5 KiB

use std::{
collections::HashMap,
sync::{atomic::Ordering, Mutex},
};
use crate::{combat::CombatState, utils::get_death_message, MessageQueue};
use once_cell::sync::Lazy;
use valence::{
client::misc::Respawn,
ecs as bevy_ecs,
entity::{living::Health, OnGround},
packet::{
encode::WritePacket,
s2c::play::{
synchronize_tags::{Tag, TagGroup},
ChunkRenderDistanceCenterS2c, DamageTiltS2c, SynchronizeTagsS2c,
},
var_int::VarInt,
Encode,
},
prelude::*,
};
use crate::PLAYER_COUNT;
#[derive(Component, Debug)]
pub struct OldHeight(pub f64);
#[derive(Component, Debug)]
pub struct Alive(pub bool);
pub fn init_clients(
mut new_clients: Query<
(
&mut Position,
Entity,
&mut Client,
&mut GameMode,
&Username,
&mut HasRespawnScreen,
&mut Location,
),
Added<Client>,
>,
mut message_queue: ResMut<MessageQueue>,
instances: Query<Entity, With<Instance>>,
mut commands: Commands,
) {
for (mut pos, entity, mut client, mut game_mode, username, mut has_respawn_screen, mut loc) in
&mut new_clients
{
// initialize client
has_respawn_screen.0 = true;
*game_mode = GameMode::Creative;
loc.0 = instances.single();
pos.set(DVec3 {
x: -214.0,
y: 83.0,
z: 276.0,
});
commands.entity(entity).insert((
OldHeight(-1.0),
Alive(true),
CombatState {
last_attacked_tick: 0,
has_bonus_knockback: false,
},
));
// send packet that magically fixes chunks
// TODO: make this configurable as different maps will have different chunks we need to force load
client.write_packet(&ChunkRenderDistanceCenterS2c {
chunk_x: VarInt(-14),
chunk_z: VarInt(17),
});
// send block tags packet
client.write_packet(&SynchronizeTagsS2c {
tags: vec![
TagGroup {
kind: ident!("block").into(),
tags: vec![Tag {
name: ident!("climbable").into(),
entries: vec![
VarInt(BlockKind::Vine.to_raw().into()),
VarInt(BlockKind::Ladder.to_raw().into()),
],
}],
},
TagGroup {
kind: ident!("fluid").into(),
tags: vec![Tag {
name: ident!("water").into(),
entries: vec![VarInt(1), VarInt(2)],
}],
},
],
});
// send server brand
let mut buf = Vec::new();
"Valence".encode(&mut buf).unwrap();
client.send_custom_payload(ident!("brand"), &buf);
// add player to count
PLAYER_COUNT.fetch_add(1, Ordering::SeqCst);
//tracing::info!("{username} joined the game");
message_queue.0.push(
format!("{username} joined the game")
.italic()
.color(Color::GRAY),
)
}
}
// NOTE: this could be a resource but since it's only used here there isn't much of a point imo
static USERNAME_CACHE: Lazy<Mutex<HashMap<Entity, String>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
pub fn update_username_cache(query: Query<(Entity, &Username)>) {
for (entity, username) in query.iter() {
let _ = USERNAME_CACHE
.lock()
.unwrap()
.insert(entity, username.0.clone());
}
}
pub fn despawn_disconnected_clients(
mut commands: Commands,
mut message_queue: ResMut<MessageQueue>,
mut disconnected_clients: RemovedComponents<Client>,
) {
let mut username_cache = USERNAME_CACHE.lock().unwrap();
for entity in disconnected_clients.iter() {
if let Some(mut entity_commands) = commands.get_entity(entity) {
let username = username_cache
.remove(&entity)
.unwrap_or("[Unknown username]".to_string());
message_queue.0.push(
format!("{username} left the game")
.italic()
.color(Color::GRAY),
);
entity_commands.insert(Despawned);
PLAYER_COUNT.fetch_sub(1, Ordering::SeqCst);
}
}
}
fn apply_damage_tilt(mut client: Mut<Client>) {
client.write_packet(&DamageTiltS2c {
entity_id: VarInt(0),
yaw: 0.0,
})
}
pub fn fall_damage(
mut clients: Query<
(
&mut Client,
&OnGround,
&mut Health,
&GameMode,
&Position,
&mut OldHeight,
),
Changed<Position>,
>,
instances: Query<&Instance>,
) {
let instance = instances.single();
for (client, on_ground, mut health, gamemode, position, mut historical_position) in &mut clients
{
let current_block = instance
.block(BlockPos {
x: position.0.x.floor() as i32,
y: position.0.y.floor() as i32 - 1,
z: position.0.z.floor() as i32,
})
.unwrap();
if *gamemode == GameMode::Survival {
// hack that potentially makes water not apply fall damage?
if current_block.state().to_kind() == BlockKind::Vine
|| current_block.state().to_kind() == BlockKind::SlimeBlock
|| current_block.state().is_liquid()
{
historical_position.0 = position.0.y;
}
let delta_y = historical_position.0 - position.0.y;
if on_ground.0 {
if delta_y > 3.0 {
health.0 -= (delta_y - 3.0) as f32;
apply_damage_tilt(client);
}
historical_position.0 = position.0.y;
} else if delta_y < 0.0 {
historical_position.0 = position.0.y;
}
}
}
}
pub fn death(
mut clients: Query<(&mut Client, &Health, &Username, &mut Alive)>,
mut message_queue: ResMut<MessageQueue>,
) {
for (mut client, health, username, mut is_alive) in &mut clients {
if health.0 <= 0.0 && is_alive.0 {
tracing::debug!("Trying to kill player {}", username);
let death_message = get_death_message(username.to_string());
client.kill(None, death_message.clone());
is_alive.0 = false;
message_queue
.0
.push(death_message.italic().color(Color::GRAY));
}
}
}
pub fn respawn(
mut clients: Query<(
&mut Position,
&mut Look,
&mut Health,
&mut Alive,
&mut Location,
&mut HasRespawnScreen,
)>,
mut events: EventReader<Respawn>,
instances: Query<Entity, With<Instance>>,
) {
for event in events.iter() {
if let Ok((mut pos, mut look, mut health, mut is_alive, mut loc, mut has_respawn_screen)) =
clients.get_mut(event.client)
{
tracing::debug!("Trying to respawn player...");
pos.set(DVec3 {
x: -214.0,
y: 83.0,
z: 276.0,
});
loc.0 = instances.single();
look.yaw = 0.0;
look.pitch = 0.0;
health.0 = 20.0;
has_respawn_screen.0 = false;
is_alive.0 = true;
}
}
}