alex/beta-minecraft-rust
Minecraft Beta 1.7.3 Rewrite
BRANCH main
SNAPSHOT Draw terrain GPU-driven from packed per-section buffers with GPU occlusion culling, add a render distance slider (2-128) with a quad/disk shape, cut chunk bookkeeping at high render distances, and fix a light underflow at y=0
DOWNLOAD
root crates client src player.rs
//! The local player: ports of `EntityPlayerSP`, `EntityClientPlayerMP` and the camera parts
//! of `EntityRenderer`.
use std::f32::consts::PI;
use common::JavaRandom;
use common::entity::player::{self, MovementKeys, PlayerState};
use common::entity::{Entity, flags};
use common::math::{self, floor_f64};
use common::protocol::{Connection, Packet};
use common::world::shape::{self, BlockView};
use glam::{DVec3, Mat4, Vec3};
use crate::world::ClientWorld;
/// Where the vehicle seats its rider this tick, and how much it turned.
#[derive(Clone, Copy)]
pub struct Mount {
pub seat: DVec3,
pub turn: [f32; 2],
}
pub struct LocalPlayer {
pub entity: Entity,
pub keys: MovementKeys,
pub state: PlayerState,
// `EntityClientPlayerMP.sendMotionUpdates` state.
old_pos: DVec3,
old_min_y: f64,
old_yaw: f32,
old_pitch: f32,
was_on_ground: bool,
was_sneaking: bool,
time_since_moved: i32,
/// Whether the server placed the player yet.
pub started: bool,
pub third_person: bool,
pub view_bobbing: bool,
}
impl LocalPlayer {
pub fn new(seed: i64) -> Self {
Self {
entity: Entity::new_player(0, JavaRandom::new(seed)),
keys: MovementKeys::default(),
state: PlayerState::default(),
old_pos: DVec3::ZERO,
old_min_y: 0.0,
old_yaw: 0.0,
old_pitch: 0.0,
was_on_ground: false,
was_sneaking: false,
time_since_moved: 0,
started: false,
third_person: false,
view_bobbing: true,
}
}
/// `NetClientHandler.handleFlying`: the server moved the player; confirm the new position.
pub fn teleport(
&mut self,
connection: &Connection,
x: f64,
eye_y: f64,
z: f64,
yaw: f32,
pitch: f32,
) {
let e = &mut self.entity;
e.y_size = 0.0;
e.motion = DVec3::ZERO;
e.set_position_and_rotation(DVec3::new(x, eye_y, z), yaw, pitch);
connection.send(Packet::PlayerPositionLook {
x: e.pos.x,
y: e.bb.min.y,
stance: e.pos.y,
z: e.pos.z,
yaw,
pitch,
on_ground: false,
});
if !self.started {
e.prev_pos = e.pos;
e.last_tick_pos = e.pos;
self.started = true;
}
}
/// `EntityClientPlayerMP.onUpdate`: one tick of the player, then its motion packets.
/// `pushers` are the entities touching the player, which push it away: ids and positions.
pub fn tick(
&mut self,
world: &mut ClientWorld,
connection: &Connection,
pushers: &[(i32, DVec3)],
) {
let e = &self.entity;
if !self.started || !world.block_exists(floor_f64(e.pos.x), 64, floor_f64(e.pos.z)) {
return;
}
self.entity.last_tick_pos = self.entity.pos;
self.entity
.set_flag(flags::RIDING, self.entity.riding.is_some());
let portal = &mut self.entity.portal;
if portal.inside && portal.time == 0.0 {
let pitch = self.entity.rand.next_float() * 0.4 + 0.8;
world.sound_effects.push(("portal.trigger", 1.0, pitch));
}
self.entity.portal.tick();
player::tick(&mut self.entity, &mut self.state, self.keys, world);
// `EntityLiving.onLivingUpdate` pushes the player away from what it touches
for &(id, position) in pushers {
if let Some(push) = self.entity.collision_push(id, position, 0.0) {
self.entity.motion += push;
}
}
self.send_motion_updates(connection);
}
/// `EntityPlayer.updateRidden`: the tick of a rider, which then sits on its mount's `seat`.
pub fn tick_ridden(
&mut self,
world: &mut ClientWorld,
connection: &Connection,
mount: Option<Mount>,
) {
let Some(Mount { seat, turn }) = mount else {
self.entity.riding = None;
return;
};
self.entity.motion = DVec3::ZERO;
self.tick(world, connection, &[]);
if self.entity.riding.is_some() {
self.entity.follow_mount(seat, turn);
}
self.state.prev_camera_yaw = self.state.camera_yaw;
self.state.camera_yaw = 0.0;
}
/// `Entity.mountEntity` for an `AttachEntity` packet: gets on `vehicle`, or off onto `top`,
/// the top of the current vehicle.
pub fn attach(&mut self, vehicle: Option<i32>, top: Option<DVec3>) {
let e = &mut self.entity;
if vehicle.is_some() && vehicle != e.riding {
e.riding = vehicle;
} else {
if e.riding.is_some()
&& let Some(top) = top
{
let (yaw, pitch) = (e.yaw, e.pitch);
e.set_location_and_angles(top, yaw, pitch);
}
e.riding = None;
}
e.rider_turn = [0.0; 2];
}
/// `EntityClientPlayerMP.sendMotionUpdates`.
fn send_motion_updates(&mut self, connection: &Connection) {
let e = &self.entity;
let sneaking = e.is_sneaking();
if sneaking != self.was_sneaking {
connection.send(Packet::EntityAction {
entity: e.id,
action: if sneaking { 1 } else { 2 },
});
self.was_sneaking = sneaking;
}
if e.riding.is_some() {
// The original drops the look while turning; riders always send it
connection.send(Packet::PlayerPositionLook {
x: e.motion.x,
y: -999.0,
stance: -999.0,
z: e.motion.z,
yaw: e.yaw,
pitch: e.pitch,
on_ground: e.on_ground,
});
self.was_on_ground = e.on_ground;
self.old_yaw = e.yaw;
self.old_pitch = e.pitch;
return;
}
let dx = e.pos.x - self.old_pos.x;
let dmin_y = e.bb.min.y - self.old_min_y;
let dy = e.pos.y - self.old_pos.y;
let dz = e.pos.z - self.old_pos.z;
let dyaw = f64::from(e.yaw - self.old_yaw);
let dpitch = f64::from(e.pitch - self.old_pitch);
let moved = dmin_y != 0.0 || dy != 0.0 || dx != 0.0 || dz != 0.0;
let turned = dyaw != 0.0 || dpitch != 0.0;
let packet = match (moved, turned) {
(true, true) => Packet::PlayerPositionLook {
x: e.pos.x,
y: e.bb.min.y,
stance: e.pos.y,
z: e.pos.z,
yaw: e.yaw,
pitch: e.pitch,
on_ground: e.on_ground,
},
(true, false) => Packet::PlayerPosition {
x: e.pos.x,
y: e.bb.min.y,
stance: e.pos.y,
z: e.pos.z,
on_ground: e.on_ground,
},
(false, true) => Packet::PlayerLook {
yaw: e.yaw,
pitch: e.pitch,
on_ground: e.on_ground,
},
(false, false) => Packet::Flying {
on_ground: e.on_ground,
},
};
if moved || turned {
self.time_since_moved = 0;
} else if self.was_on_ground == e.on_ground && self.time_since_moved <= 200 {
self.time_since_moved += 1;
} else {
self.time_since_moved = 0;
}
connection.send(packet);
self.was_on_ground = e.on_ground;
if moved {
self.old_pos = e.pos;
self.old_min_y = e.bb.min.y;
}
if turned {
self.old_yaw = e.yaw;
self.old_pitch = e.pitch;
}
}
/// The interpolated eye position, which is lower when lying in bed.
pub fn eye(&self, partial: f32) -> DVec3 {
let e = &self.entity;
let partial = f64::from(partial);
let pos = e.prev_pos + (e.pos - e.prev_pos) * partial;
let lowered = if e.sleep.sleeping { 1.0 } else { 0.0 };
pos - DVec3::new(0.0, f64::from(e.y_offset - 1.62 + lowered), 0.0)
}
/// The direction of the bed the living player sleeps in (`getBedOrientationInDegrees`).
pub fn bed_direction(&self, world: &impl BlockView) -> Option<usize> {
let e = &self.entity;
let [x, y, z] = e.sleep.bed.filter(|_| {
e.sleep.sleeping && !e.dead && e.living.as_ref().is_some_and(|l| l.health > 0)
})?;
Some(common::world::bed::direction(world.metadata(x, y, z)))
}
fn camera_distance(&self, world: &impl BlockView, partial: f32) -> f32 {
if !self.third_person {
return 0.1;
}
let e = &self.entity;
let eye = self.eye(partial);
let yaw = e.prev_yaw + (e.yaw - e.prev_yaw) * partial;
let pitch = e.prev_pitch + (e.pitch - e.prev_pitch) * partial;
let behind = -common::interaction::look(yaw, pitch) * 4.0;
let mut distance = 4.0_f64;
for corner in 0..8 {
let offset = DVec3::new(
f64::from((corner & 1) * 2 - 1),
f64::from((corner >> 1 & 1) * 2 - 1),
f64::from((corner >> 2 & 1) * 2 - 1),
) * f64::from(0.1_f32);
let start = eye + offset;
if let Some(hit) = shape::ray_trace(world, start, start + behind, false, false) {
distance = distance.min(hit.point.distance(start));
}
}
distance as f32
}
/// `setupViewBobbing` and `orientCamera`, relative to the player's eye
pub fn view(&self, world: &impl BlockView, partial: f32, bobbing: bool, ticks: i32) -> Mat4 {
let e = &self.entity;
let effects = self.camera_effects(partial, bobbing) * self.portal_warp(partial, ticks);
if e.sleep.sleeping {
let [x, y, z] = e.pos.to_array().map(floor_f64);
let turn = if world.block(x, y, z) == common::world::Block::BED {
common::world::bed::direction(world.metadata(x, y, z)) as f32 * 90.0
} else {
0.0
};
return effects
* Mat4::from_translation(Vec3::new(0.0, 0.3, 0.0))
* Mat4::from_rotation_y(turn.to_radians());
}
let pitch = e.prev_pitch + (e.pitch - e.prev_pitch) * partial;
let yaw = e.prev_yaw + (e.yaw - e.prev_yaw) * partial;
effects
* Mat4::from_translation(Vec3::new(0.0, 0.0, -self.camera_distance(world, partial)))
* Mat4::from_rotation_x(pitch.to_radians())
* Mat4::from_rotation_y((yaw + 180.0).to_radians())
}
/// The swaying stretch of the world while standing in a portal (`setupCameraTransform`).
fn portal_warp(&self, partial: f32, ticks: i32) -> Mat4 {
let portal = &self.entity.portal;
let time = portal.prev_time + (portal.time - portal.prev_time) * partial;
if time <= 0.0 {
return Mat4::IDENTITY;
}
let scale = (5.0 / (time * time + 5.0) - time * 0.04).powi(2);
let axis = Vec3::new(0.0, 1.0, 1.0).normalize();
let angle = ((ticks as f32 + partial) * 20.0).to_radians();
Mat4::from_axis_angle(axis, angle)
* Mat4::from_scale(Vec3::new(1.0 / scale, 1.0, 1.0))
* Mat4::from_axis_angle(axis, -angle)
}
pub fn camera_effects(&self, partial: f32, bobbing: bool) -> Mat4 {
let e = &self.entity;
let mut view = Mat4::IDENTITY;
if let Some(living) = &e.living {
if living.health <= 0 {
view *= Mat4::from_rotation_z(
(40.0 - 8000.0 / (living.death_time as f32 + partial + 200.0)).to_radians(),
);
}
let hurt = living.hurt_time as f32 - partial;
if hurt >= 0.0 && living.max_hurt_time > 0 {
let roll = math::sin((hurt / living.max_hurt_time as f32).powi(4) * PI) * -14.0;
view *= Mat4::from_rotation_y(-living.attacked_at_yaw.to_radians())
* Mat4::from_rotation_z(roll.to_radians())
* Mat4::from_rotation_y(living.attacked_at_yaw.to_radians());
}
}
if bobbing {
let walked = e.distance_walked - e.prev_distance_walked;
let phase = -(e.distance_walked + walked * partial);
let bob = self.state.prev_camera_yaw
+ (self.state.camera_yaw - self.state.prev_camera_yaw) * partial;
let tilt = e.living.as_ref().map_or(0.0, |l| {
l.prev_camera_pitch + (l.camera_pitch - l.prev_camera_pitch) * partial
});
view *= Mat4::from_translation(Vec3::new(
math::sin(phase * PI) * bob * 0.5,
-(math::cos(phase * PI) * bob).abs(),
0.0,
));
view *= Mat4::from_rotation_z((math::sin(phase * PI) * bob * 3.0).to_radians());
view *= Mat4::from_rotation_x(
((math::cos(phase * PI - 0.2) * bob).abs() * 5.0).to_radians(),
);
view *= Mat4::from_rotation_x(tilt.to_radians());
}
view
}
/// `EntityRenderer.getFOVModifier`: narrower under water, closing in while dying.
pub fn fov(&self, world: &ClientWorld, partial: f32) -> f32 {
let fov = if self.entity.eyes_in(world, common::world::Material::Water) {
60.0
} else {
70.0
};
match &self.entity.living {
Some(living) if living.health <= 0 => {
let time = living.death_time as f32 + partial;
fov / ((1.0 - 500.0 / (time + 500.0)) * 2.0 + 1.0)
}
_ => fov,
}
}
}