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.
261 lines
7.5 KiB
261 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 default_username = "[Unknown username]".to_string();
|
|
let 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.get(&entity).unwrap_or(&default_username);
|
|
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;
|
|
}
|
|
}
|
|
}
|