mirror of
https://github.com/italicsjenga/valence.git
synced 2024-12-23 22:41:30 +11:00
Add combat example
This commit is contained in:
parent
865ab76699
commit
b604dafe73
|
@ -455,7 +455,7 @@ const LIVING_ENTITY: Class = Class {
|
||||||
typ: Type::OptBlockPos(None),
|
typ: Type::OptBlockPos(None),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
events: &[],
|
events: &[Event::Hurt],
|
||||||
};
|
};
|
||||||
|
|
||||||
const MOB: Class = Class {
|
const MOB: Class = Class {
|
||||||
|
|
278
examples/combat.rs
Normal file
278
examples/combat.rs
Normal file
|
@ -0,0 +1,278 @@
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
|
use log::LevelFilter;
|
||||||
|
use valence::block::{BlockPos, BlockState};
|
||||||
|
use valence::client::Event::{self};
|
||||||
|
use valence::client::{ClientId, GameMode, Hand, InteractWithEntityKind};
|
||||||
|
use valence::config::{Config, ServerListPing};
|
||||||
|
use valence::dimension::DimensionId;
|
||||||
|
use valence::entity::state::Pose;
|
||||||
|
use valence::entity::{EntityId, EntityKind, EntityState};
|
||||||
|
use valence::server::{Server, SharedServer, ShutdownResult};
|
||||||
|
use valence::text::{Color, TextFormat};
|
||||||
|
use valence::{async_trait, Ticks};
|
||||||
|
use vek::Vec3;
|
||||||
|
|
||||||
|
pub fn main() -> ShutdownResult {
|
||||||
|
env_logger::Builder::new()
|
||||||
|
.filter_module("valence", LevelFilter::Trace)
|
||||||
|
.parse_default_env()
|
||||||
|
.init();
|
||||||
|
|
||||||
|
valence::start_server(
|
||||||
|
Game {
|
||||||
|
player_count: AtomicUsize::new(0),
|
||||||
|
},
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Game {
|
||||||
|
player_count: AtomicUsize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct ClientData {
|
||||||
|
/// The client's player entity.
|
||||||
|
player: EntityId,
|
||||||
|
/// The extra knockback on the first hit while sprinting.
|
||||||
|
has_extra_knockback: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct EntityData {
|
||||||
|
client: ClientId,
|
||||||
|
attacked: bool,
|
||||||
|
attacker_pos: Vec3<f64>,
|
||||||
|
extra_knockback: bool,
|
||||||
|
last_attack_time: Ticks,
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_PLAYERS: usize = 10;
|
||||||
|
|
||||||
|
const SPAWN_POS: BlockPos = BlockPos::new(0, 20, 0);
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl Config for Game {
|
||||||
|
type ChunkData = ();
|
||||||
|
type ClientData = ClientData;
|
||||||
|
type EntityData = EntityData;
|
||||||
|
type ServerData = ();
|
||||||
|
type WorldData = ();
|
||||||
|
|
||||||
|
fn max_connections(&self) -> usize {
|
||||||
|
// We want status pings to be successful even if the server is full.
|
||||||
|
MAX_PLAYERS + 64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn online_mode(&self) -> bool {
|
||||||
|
// You'll want this to be true on real servers.
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn server_list_ping(
|
||||||
|
&self,
|
||||||
|
_server: &SharedServer<Self>,
|
||||||
|
_remote_addr: SocketAddr,
|
||||||
|
) -> ServerListPing {
|
||||||
|
ServerListPing::Respond {
|
||||||
|
online_players: self.player_count.load(Ordering::SeqCst) as i32,
|
||||||
|
max_players: MAX_PLAYERS as i32,
|
||||||
|
description: "Hello Valence!".color(Color::AQUA),
|
||||||
|
favicon_png: Some(include_bytes!("../assets/favicon.png")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(&self, server: &mut Server<Self>) {
|
||||||
|
let (_, world) = server.worlds.create(DimensionId::default(), ());
|
||||||
|
world.meta.set_flat(true);
|
||||||
|
|
||||||
|
let min_y = server.shared.dimension(DimensionId::default()).min_y;
|
||||||
|
|
||||||
|
// Create circular arena.
|
||||||
|
let size = 2;
|
||||||
|
for chunk_z in -size - 2..size + 2 {
|
||||||
|
for chunk_x in -size - 2..size + 2 {
|
||||||
|
let chunk = world.chunks.create([chunk_x, chunk_z], ());
|
||||||
|
let r = -size..size;
|
||||||
|
if r.contains(&chunk_x) && r.contains(&chunk_z) {
|
||||||
|
for z in 0..16 {
|
||||||
|
for x in 0..16 {
|
||||||
|
let block_x = chunk_x * 16 + x as i32;
|
||||||
|
let block_z = chunk_z * 16 + z as i32;
|
||||||
|
if f64::hypot(block_x as f64, block_z as f64) <= size as f64 * 16.0 {
|
||||||
|
for y in 0..(SPAWN_POS.y - min_y + 1) as usize {
|
||||||
|
chunk.set_block_state(x, y, z, BlockState::STONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
world.chunks.set_block_state(SPAWN_POS, BlockState::BEDROCK);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&self, server: &mut Server<Self>) {
|
||||||
|
let (world_id, world) = server.worlds.iter_mut().next().unwrap();
|
||||||
|
|
||||||
|
let current_tick = server.shared.current_tick();
|
||||||
|
|
||||||
|
server.clients.retain(|client_id, client| {
|
||||||
|
if client.created_tick() == current_tick {
|
||||||
|
if self
|
||||||
|
.player_count
|
||||||
|
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |count| {
|
||||||
|
(count < MAX_PLAYERS).then_some(count + 1)
|
||||||
|
})
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
client.disconnect("The server is full!".color(Color::RED));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.spawn(world_id);
|
||||||
|
client.set_game_mode(GameMode::Survival);
|
||||||
|
client.teleport(
|
||||||
|
[
|
||||||
|
SPAWN_POS.x as f64 + 0.5,
|
||||||
|
SPAWN_POS.y as f64 + 1.0,
|
||||||
|
SPAWN_POS.z as f64 + 0.5,
|
||||||
|
],
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
world.meta.player_list_mut().insert(
|
||||||
|
client.uuid(),
|
||||||
|
client.username().to_owned(),
|
||||||
|
client.textures().cloned(),
|
||||||
|
client.game_mode(),
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let (player_id, player) = server
|
||||||
|
.entities
|
||||||
|
.create_with_uuid(EntityKind::Player, client.uuid(), EntityData::default())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
client.data.player = player_id;
|
||||||
|
client.data.has_extra_knockback = true;
|
||||||
|
|
||||||
|
player.data.client = client_id;
|
||||||
|
player.data.last_attack_time = 0;
|
||||||
|
|
||||||
|
client.send_message("Welcome to the arena.".italic());
|
||||||
|
if self.player_count.load(Ordering::SeqCst) <= 1 {
|
||||||
|
client.send_message("Have another player join the game with you.".italic());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if client.is_disconnected() {
|
||||||
|
self.player_count.fetch_sub(1, Ordering::SeqCst);
|
||||||
|
server.entities.delete(client.data.player);
|
||||||
|
world.meta.player_list_mut().remove(client.uuid());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(event) = client.pop_event() {
|
||||||
|
match event {
|
||||||
|
Event::StartSprinting => {
|
||||||
|
client.data.has_extra_knockback = true;
|
||||||
|
}
|
||||||
|
Event::InteractWithEntity {
|
||||||
|
id,
|
||||||
|
kind: InteractWithEntityKind::Attack,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
if let Some(target) = server.entities.get_mut(id) {
|
||||||
|
if !target.data.attacked
|
||||||
|
&& current_tick - target.data.last_attack_time >= 10
|
||||||
|
&& id != client.data.player
|
||||||
|
{
|
||||||
|
target.data.attacked = true;
|
||||||
|
target.data.attacker_pos = client.position();
|
||||||
|
target.data.extra_knockback = client.data.has_extra_knockback;
|
||||||
|
target.data.last_attack_time = current_tick;
|
||||||
|
|
||||||
|
client.data.has_extra_knockback = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event::ArmSwing(hand) => {
|
||||||
|
let player = server.entities.get_mut(client.data.player).unwrap();
|
||||||
|
|
||||||
|
if let EntityState::Player(e) = &mut player.state {
|
||||||
|
match hand {
|
||||||
|
Hand::Main => e.trigger_swing_main_arm(),
|
||||||
|
Hand::Off => e.trigger_swing_offhand(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if client.position().y <= 0.0 {
|
||||||
|
client.teleport(
|
||||||
|
[
|
||||||
|
SPAWN_POS.x as f64 + 0.5,
|
||||||
|
SPAWN_POS.y as f64 + 1.0,
|
||||||
|
SPAWN_POS.z as f64 + 0.5,
|
||||||
|
],
|
||||||
|
client.yaw(),
|
||||||
|
client.pitch(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let player = server.entities.get_mut(client.data.player).unwrap();
|
||||||
|
|
||||||
|
player.set_world(client.world());
|
||||||
|
player.set_position(client.position());
|
||||||
|
player.set_yaw(client.yaw());
|
||||||
|
player.set_head_yaw(client.yaw());
|
||||||
|
player.set_pitch(client.pitch());
|
||||||
|
player.set_on_ground(client.on_ground());
|
||||||
|
|
||||||
|
if let EntityState::Player(player) = &mut player.state {
|
||||||
|
if client.is_sneaking() {
|
||||||
|
player.set_pose(Pose::Sneaking);
|
||||||
|
} else {
|
||||||
|
player.set_pose(Pose::Standing);
|
||||||
|
}
|
||||||
|
|
||||||
|
player.set_sprinting(client.is_sprinting());
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
});
|
||||||
|
|
||||||
|
for (_, e) in server.entities.iter_mut() {
|
||||||
|
if e.data.attacked {
|
||||||
|
e.data.attacked = false;
|
||||||
|
let victim = server.clients.get_mut(e.data.client).unwrap();
|
||||||
|
|
||||||
|
let mut vel = (victim.position() - e.data.attacker_pos).normalized();
|
||||||
|
|
||||||
|
let knockback_xz = if e.data.extra_knockback { 18.0 } else { 8.0 };
|
||||||
|
let knockback_y = if e.data.extra_knockback { 8.432 } else { 6.432 };
|
||||||
|
|
||||||
|
vel.x *= knockback_xz;
|
||||||
|
vel.y = knockback_y;
|
||||||
|
vel.z *= knockback_xz;
|
||||||
|
|
||||||
|
victim.set_velocity(victim.velocity() / 2.0 + vel.as_());
|
||||||
|
|
||||||
|
if let EntityState::Player(e) = &mut e.state {
|
||||||
|
e.trigger_take_damage();
|
||||||
|
e.trigger_hurt();
|
||||||
|
}
|
||||||
|
victim.player_mut().trigger_take_damage();
|
||||||
|
victim.player_mut().trigger_hurt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -145,14 +145,12 @@ impl Config for Game {
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
let player_id = server
|
client.data.player = server
|
||||||
.entities
|
.entities
|
||||||
.create_with_uuid(EntityKind::Player, client.uuid(), ())
|
.create_with_uuid(EntityKind::Player, client.uuid(), ())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.0;
|
.0;
|
||||||
|
|
||||||
client.data = ClientData { player: player_id };
|
|
||||||
|
|
||||||
client.send_message("Welcome to Conway's game of life in Minecraft!".italic());
|
client.send_message("Welcome to Conway's game of life in Minecraft!".italic());
|
||||||
client.send_message("Hold the left mouse button to bring blocks to life.".italic());
|
client.send_message("Hold the left mouse button to bring blocks to life.".italic());
|
||||||
}
|
}
|
||||||
|
@ -164,12 +162,12 @@ impl Config for Game {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
|
||||||
});
|
|
||||||
|
|
||||||
for (_, client) in server.clients.iter_mut() {
|
|
||||||
let player = server.entities.get_mut(client.data.player).unwrap();
|
let player = server.entities.get_mut(client.data.player).unwrap();
|
||||||
|
|
||||||
|
if client.position().y <= 0.0 {
|
||||||
|
client.teleport(spawn_pos, client.yaw(), client.pitch());
|
||||||
|
}
|
||||||
|
|
||||||
while let Some(event) = client.pop_event() {
|
while let Some(event) = client.pop_event() {
|
||||||
match event {
|
match event {
|
||||||
Event::Digging { position, .. } => {
|
Event::Digging { position, .. } => {
|
||||||
|
@ -181,40 +179,6 @@ impl Config for Game {
|
||||||
true;
|
true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Movement { .. } => {
|
|
||||||
if client.position().y <= 0.0 {
|
|
||||||
client.teleport(spawn_pos, client.yaw(), client.pitch());
|
|
||||||
}
|
|
||||||
|
|
||||||
player.set_world(client.world());
|
|
||||||
player.set_position(client.position());
|
|
||||||
player.set_yaw(client.yaw());
|
|
||||||
player.set_head_yaw(client.yaw());
|
|
||||||
player.set_pitch(client.pitch());
|
|
||||||
player.set_on_ground(client.on_ground());
|
|
||||||
}
|
|
||||||
Event::StartSneaking => {
|
|
||||||
if let EntityState::Player(e) = &mut player.state {
|
|
||||||
e.set_crouching(true);
|
|
||||||
e.set_pose(Pose::Sneaking);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::StopSneaking => {
|
|
||||||
if let EntityState::Player(e) = &mut player.state {
|
|
||||||
e.set_pose(Pose::Standing);
|
|
||||||
e.set_crouching(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::StartSprinting => {
|
|
||||||
if let EntityState::Player(e) = &mut player.state {
|
|
||||||
e.set_sprinting(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::StopSprinting => {
|
|
||||||
if let EntityState::Player(e) = &mut player.state {
|
|
||||||
e.set_sprinting(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::ArmSwing(hand) => {
|
Event::ArmSwing(hand) => {
|
||||||
if let EntityState::Player(e) = &mut player.state {
|
if let EntityState::Player(e) = &mut player.state {
|
||||||
match hand {
|
match hand {
|
||||||
|
@ -226,8 +190,27 @@ impl Config for Game {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
player.set_world(client.world());
|
||||||
|
player.set_position(client.position());
|
||||||
|
player.set_yaw(client.yaw());
|
||||||
|
player.set_head_yaw(client.yaw());
|
||||||
|
player.set_pitch(client.pitch());
|
||||||
|
player.set_on_ground(client.on_ground());
|
||||||
|
|
||||||
|
if let EntityState::Player(player) = &mut player.state {
|
||||||
|
if client.is_sneaking() {
|
||||||
|
player.set_pose(Pose::Sneaking);
|
||||||
|
} else {
|
||||||
|
player.set_pose(Pose::Standing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
player.set_sprinting(client.is_sprinting());
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
});
|
||||||
|
|
||||||
if server.shared.current_tick() % 4 != 0 {
|
if server.shared.current_tick() % 4 != 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ use crate::slotmap::{Key, SlotMap};
|
||||||
use crate::text::Text;
|
use crate::text::Text;
|
||||||
use crate::util::{chunks_in_view_distance, is_chunk_in_view_distance};
|
use crate::util::{chunks_in_view_distance, is_chunk_in_view_distance};
|
||||||
use crate::world::{WorldId, Worlds};
|
use crate::world::{WorldId, Worlds};
|
||||||
use crate::{ident, Ticks, LIBRARY_NAMESPACE};
|
use crate::{ident, Ticks, LIBRARY_NAMESPACE, STANDARD_TPS};
|
||||||
|
|
||||||
/// A container for all [`Client`]s on a [`Server`](crate::server::Server).
|
/// A container for all [`Client`]s on a [`Server`](crate::server::Server).
|
||||||
///
|
///
|
||||||
|
@ -175,6 +175,8 @@ pub struct Client<C: Config> {
|
||||||
world: WorldId,
|
world: WorldId,
|
||||||
new_position: Vec3<f64>,
|
new_position: Vec3<f64>,
|
||||||
old_position: Vec3<f64>,
|
old_position: Vec3<f64>,
|
||||||
|
/// Measured in m/s.
|
||||||
|
velocity: Vec3<f32>,
|
||||||
/// Measured in degrees
|
/// Measured in degrees
|
||||||
yaw: f32,
|
yaw: f32,
|
||||||
/// Measured in degrees
|
/// Measured in degrees
|
||||||
|
@ -228,7 +230,8 @@ pub(crate) struct ClientFlags {
|
||||||
hardcore: bool,
|
hardcore: bool,
|
||||||
attack_speed_modified: bool,
|
attack_speed_modified: bool,
|
||||||
movement_speed_modified: bool,
|
movement_speed_modified: bool,
|
||||||
#[bits(5)]
|
velocity_modified: bool,
|
||||||
|
#[bits(4)]
|
||||||
_pad: u8,
|
_pad: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,6 +255,7 @@ impl<C: Config> Client<C> {
|
||||||
world: WorldId::default(),
|
world: WorldId::default(),
|
||||||
new_position: Vec3::default(),
|
new_position: Vec3::default(),
|
||||||
old_position: Vec3::default(),
|
old_position: Vec3::default(),
|
||||||
|
velocity: Vec3::default(),
|
||||||
yaw: 0.0,
|
yaw: 0.0,
|
||||||
pitch: 0.0,
|
pitch: 0.0,
|
||||||
teleport_id_counter: 0,
|
teleport_id_counter: 0,
|
||||||
|
@ -294,6 +298,16 @@ impl<C: Config> Client<C> {
|
||||||
&self.username
|
&self.username
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the sneaking state of this client.
|
||||||
|
pub fn is_sneaking(&self) -> bool {
|
||||||
|
self.flags.sneaking()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the sprinting state of this client.
|
||||||
|
pub fn is_sprinting(&self) -> bool {
|
||||||
|
self.flags.sprinting()
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets the player textures of this client. If the client does not have
|
/// Gets the player textures of this client. If the client does not have
|
||||||
/// a skin, then `None` is returned.
|
/// a skin, then `None` is returned.
|
||||||
pub fn textures(&self) -> Option<&SignedPlayerTextures> {
|
pub fn textures(&self) -> Option<&SignedPlayerTextures> {
|
||||||
|
@ -333,9 +347,9 @@ impl<C: Config> Client<C> {
|
||||||
/// If you want to change the client's world, use [`Self::spawn`].
|
/// If you want to change the client's world, use [`Self::spawn`].
|
||||||
pub fn teleport(&mut self, pos: impl Into<Vec3<f64>>, yaw: f32, pitch: f32) {
|
pub fn teleport(&mut self, pos: impl Into<Vec3<f64>>, yaw: f32, pitch: f32) {
|
||||||
self.new_position = pos.into();
|
self.new_position = pos.into();
|
||||||
|
|
||||||
self.yaw = yaw;
|
self.yaw = yaw;
|
||||||
self.pitch = pitch;
|
self.pitch = pitch;
|
||||||
|
self.velocity = Vec3::default();
|
||||||
|
|
||||||
if !self.flags.teleported_this_tick() {
|
if !self.flags.teleported_this_tick() {
|
||||||
self.flags.set_teleported_this_tick(true);
|
self.flags.set_teleported_this_tick(true);
|
||||||
|
@ -353,11 +367,18 @@ impl<C: Config> Client<C> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the velocity of this client in m/s.
|
||||||
|
///
|
||||||
|
/// The velocity of a client is derived from their current and previous
|
||||||
|
/// position.
|
||||||
|
pub fn velocity(&self) -> Vec3<f32> {
|
||||||
|
self.velocity
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the client's velocity in m/s.
|
||||||
pub fn set_velocity(&mut self, velocity: impl Into<Vec3<f32>>) {
|
pub fn set_velocity(&mut self, velocity: impl Into<Vec3<f32>>) {
|
||||||
self.send_packet(SetEntityMotion {
|
self.velocity = velocity.into();
|
||||||
entity_id: VarInt(0),
|
self.flags.set_velocity_modified(true);
|
||||||
velocity: velocity_to_packet_units(velocity.into()),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets this client's yaw.
|
/// Gets this client's yaw.
|
||||||
|
@ -610,14 +631,25 @@ impl<C: Config> Client<C> {
|
||||||
if client.pending_teleports == 0 {
|
if client.pending_teleports == 0 {
|
||||||
// TODO: validate movement using swept AABB collision with the blocks.
|
// TODO: validate movement using swept AABB collision with the blocks.
|
||||||
// TODO: validate that the client is actually inside/outside the vehicle?
|
// TODO: validate that the client is actually inside/outside the vehicle?
|
||||||
|
|
||||||
|
// Movement packets should be coming in at a rate of STANDARD_TPS.
|
||||||
|
let new_velocity = (new_position - client.new_position).as_() * STANDARD_TPS as f32;
|
||||||
|
|
||||||
let event = Event::Movement {
|
let event = Event::Movement {
|
||||||
position: client.new_position,
|
old_position: client.new_position,
|
||||||
yaw: client.yaw,
|
old_velocity: client.velocity,
|
||||||
pitch: client.pitch,
|
old_yaw: client.yaw,
|
||||||
on_ground: client.flags.on_ground(),
|
old_pitch: client.pitch,
|
||||||
|
old_on_ground: client.flags.on_ground(),
|
||||||
|
new_position,
|
||||||
|
new_velocity,
|
||||||
|
new_yaw,
|
||||||
|
new_pitch,
|
||||||
|
new_on_ground,
|
||||||
};
|
};
|
||||||
|
|
||||||
client.new_position = new_position;
|
client.new_position = new_position;
|
||||||
|
client.velocity = new_velocity;
|
||||||
client.yaw = new_yaw;
|
client.yaw = new_yaw;
|
||||||
client.pitch = new_pitch;
|
client.pitch = new_pitch;
|
||||||
client.flags.set_on_ground(new_on_ground);
|
client.flags.set_on_ground(new_on_ground);
|
||||||
|
@ -787,21 +819,34 @@ impl<C: Config> Client<C> {
|
||||||
// - Can't jump with a horse if not on a horse
|
// - Can't jump with a horse if not on a horse
|
||||||
// - Can't open horse inventory if not on a horse.
|
// - Can't open horse inventory if not on a horse.
|
||||||
// - Can't fly with elytra if not wearing an elytra.
|
// - Can't fly with elytra if not wearing an elytra.
|
||||||
|
// - Can't jump with horse while already jumping & vice versa?
|
||||||
self.events.push_back(match e.action_id {
|
self.events.push_back(match e.action_id {
|
||||||
PlayerCommandId::StartSneaking => {
|
PlayerCommandId::StartSneaking => {
|
||||||
|
if self.flags.sneaking() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
self.flags.set_sneaking(true);
|
self.flags.set_sneaking(true);
|
||||||
Event::StartSneaking
|
Event::StartSneaking
|
||||||
}
|
}
|
||||||
PlayerCommandId::StopSneaking => {
|
PlayerCommandId::StopSneaking => {
|
||||||
|
if !self.flags.sneaking() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
self.flags.set_sneaking(false);
|
self.flags.set_sneaking(false);
|
||||||
Event::StopSneaking
|
Event::StopSneaking
|
||||||
}
|
}
|
||||||
PlayerCommandId::LeaveBed => Event::LeaveBed,
|
PlayerCommandId::LeaveBed => Event::LeaveBed,
|
||||||
PlayerCommandId::StartSprinting => {
|
PlayerCommandId::StartSprinting => {
|
||||||
|
if self.flags.sprinting() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
self.flags.set_sprinting(true);
|
self.flags.set_sprinting(true);
|
||||||
Event::StartSprinting
|
Event::StartSprinting
|
||||||
}
|
}
|
||||||
PlayerCommandId::StopSprinting => {
|
PlayerCommandId::StopSprinting => {
|
||||||
|
if !self.flags.sprinting() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
self.flags.set_sprinting(false);
|
self.flags.set_sprinting(false);
|
||||||
Event::StopSprinting
|
Event::StopSprinting
|
||||||
}
|
}
|
||||||
|
@ -914,6 +959,7 @@ impl<C: Config> Client<C> {
|
||||||
self.teleport(self.position(), self.yaw(), self.pitch());
|
self.teleport(self.position(), self.yaw(), self.pitch());
|
||||||
} else {
|
} else {
|
||||||
if self.flags.spawn() {
|
if self.flags.spawn() {
|
||||||
|
self.flags.set_spawn(false);
|
||||||
self.loaded_entities.clear();
|
self.loaded_entities.clear();
|
||||||
self.loaded_chunks.clear();
|
self.loaded_chunks.clear();
|
||||||
|
|
||||||
|
@ -1117,6 +1163,17 @@ impl<C: Config> Client<C> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set velocity. Do this after teleporting since teleporting sets velocity to
|
||||||
|
// zero.
|
||||||
|
if self.flags.velocity_modified() {
|
||||||
|
self.flags.set_velocity_modified(false);
|
||||||
|
|
||||||
|
self.send_packet(SetEntityMotion {
|
||||||
|
entity_id: VarInt(0),
|
||||||
|
velocity: velocity_to_packet_units(self.velocity),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Send chat messages.
|
// Send chat messages.
|
||||||
for msg in self.msgs_to_send.drain(..) {
|
for msg in self.msgs_to_send.drain(..) {
|
||||||
send_packet(
|
send_packet(
|
||||||
|
@ -1244,7 +1301,6 @@ impl<C: Config> Client<C> {
|
||||||
metadata: RawBytes(data),
|
metadata: RawBytes(data),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
self.player_data.clear_modifications();
|
|
||||||
|
|
||||||
// Spawn new entities within the view distance.
|
// Spawn new entities within the view distance.
|
||||||
let pos = self.position();
|
let pos = self.position();
|
||||||
|
@ -1253,7 +1309,7 @@ impl<C: Config> Client<C> {
|
||||||
|id, _| {
|
|id, _| {
|
||||||
let entity = entities
|
let entity = entities
|
||||||
.get(id)
|
.get(id)
|
||||||
.expect("entities in spatial index should be valid");
|
.expect("entity IDs in spatial index should be valid at this point");
|
||||||
if entity.kind() != EntityKind::Marker
|
if entity.kind() != EntityKind::Marker
|
||||||
&& entity.uuid() != self.uuid
|
&& entity.uuid() != self.uuid
|
||||||
&& self.loaded_entities.insert(id)
|
&& self.loaded_entities.insert(id)
|
||||||
|
@ -1288,8 +1344,8 @@ impl<C: Config> Client<C> {
|
||||||
// any effect.
|
// any effect.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.player_data.clear_modifications();
|
||||||
self.old_position = self.new_position;
|
self.old_position = self.new_position;
|
||||||
self.flags.set_spawn(false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,17 +24,28 @@ pub enum Event {
|
||||||
/// Settings were changed. The value in this variant is the _previous_
|
/// Settings were changed. The value in this variant is the _previous_
|
||||||
/// client settings.
|
/// client settings.
|
||||||
SettingsChanged(Option<Settings>),
|
SettingsChanged(Option<Settings>),
|
||||||
/// The client moved. The values in this
|
/// The client moved.
|
||||||
/// variant are the _previous_ position and look.
|
|
||||||
Movement {
|
Movement {
|
||||||
/// Absolute coordinates of the previous position.
|
/// Absolute coordinates of the previous position.
|
||||||
position: Vec3<f64>,
|
old_position: Vec3<f64>,
|
||||||
|
/// Previous velocity in m/s.
|
||||||
|
old_velocity: Vec3<f32>,
|
||||||
/// The previous yaw (in degrees).
|
/// The previous yaw (in degrees).
|
||||||
yaw: f32,
|
old_yaw: f32,
|
||||||
/// The previous pitch (in degrees).
|
/// The previous pitch (in degrees).
|
||||||
pitch: f32,
|
old_pitch: f32,
|
||||||
/// If the client was previously on the ground.
|
/// If the client was previously on the ground.
|
||||||
on_ground: bool,
|
old_on_ground: bool,
|
||||||
|
/// Absolute coodinates of the new position.
|
||||||
|
new_position: Vec3<f64>,
|
||||||
|
/// New velocity in m/s.
|
||||||
|
new_velocity: Vec3<f32>,
|
||||||
|
/// The new yaw (in degrees).
|
||||||
|
new_yaw: f32,
|
||||||
|
/// The new pitch (in degrees).
|
||||||
|
new_pitch: f32,
|
||||||
|
/// If the client is now on the ground.
|
||||||
|
new_on_ground: bool,
|
||||||
},
|
},
|
||||||
StartSneaking,
|
StartSneaking,
|
||||||
StopSneaking,
|
StopSneaking,
|
||||||
|
|
|
@ -10,7 +10,7 @@ use crate::biome::Biome;
|
||||||
use crate::dimension::Dimension;
|
use crate::dimension::Dimension;
|
||||||
use crate::server::{NewClientData, Server, SharedServer};
|
use crate::server::{NewClientData, Server, SharedServer};
|
||||||
use crate::text::Text;
|
use crate::text::Text;
|
||||||
use crate::Ticks;
|
use crate::{Ticks, STANDARD_TPS};
|
||||||
|
|
||||||
/// A trait for the configuration of a server.
|
/// A trait for the configuration of a server.
|
||||||
///
|
///
|
||||||
|
@ -65,9 +65,9 @@ pub trait Config: 'static + Sized + Send + Sync + UnwindSafe + RefUnwindSafe {
|
||||||
///
|
///
|
||||||
/// # Default Implementation
|
/// # Default Implementation
|
||||||
///
|
///
|
||||||
/// Returns `20`, which is the same as Minecraft's official server.
|
/// Returns [`STANDARD_TPS`].
|
||||||
fn tick_rate(&self) -> Ticks {
|
fn tick_rate(&self) -> Ticks {
|
||||||
20
|
STANDARD_TPS
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Called once at startup to get the "online mode" option, which determines
|
/// Called once at startup to get the "online mode" option, which determines
|
||||||
|
|
|
@ -22,6 +22,7 @@ use crate::protocol_inner::{ByteAngle, RawBytes, VarInt};
|
||||||
use crate::slotmap::{Key, SlotMap};
|
use crate::slotmap::{Key, SlotMap};
|
||||||
use crate::util::aabb_from_bottom_and_size;
|
use crate::util::aabb_from_bottom_and_size;
|
||||||
use crate::world::WorldId;
|
use crate::world::WorldId;
|
||||||
|
use crate::STANDARD_TPS;
|
||||||
|
|
||||||
/// A container for all [`Entity`]s on a [`Server`](crate::server::Server).
|
/// A container for all [`Entity`]s on a [`Server`](crate::server::Server).
|
||||||
///
|
///
|
||||||
|
@ -583,7 +584,7 @@ impl<C: Config> Entity<C> {
|
||||||
|
|
||||||
pub(crate) fn velocity_to_packet_units(vel: Vec3<f32>) -> Vec3<i16> {
|
pub(crate) fn velocity_to_packet_units(vel: Vec3<f32>) -> Vec3<i16> {
|
||||||
// The saturating cast to i16 is desirable.
|
// The saturating cast to i16 is desirable.
|
||||||
(vel * 400.0).as_()
|
(8000.0 / STANDARD_TPS as f32 * vel).as_()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) enum EntitySpawnPacket {
|
pub(crate) enum EntitySpawnPacket {
|
||||||
|
|
10
src/lib.rs
10
src/lib.rs
|
@ -168,11 +168,15 @@ pub const VERSION_NAME: &str = "1.19";
|
||||||
/// [identifiers](crate::ident::Ident).
|
/// [identifiers](crate::ident::Ident).
|
||||||
///
|
///
|
||||||
/// You should avoid using this namespace in your own identifiers.
|
/// You should avoid using this namespace in your own identifiers.
|
||||||
const LIBRARY_NAMESPACE: &str = "valence";
|
pub const LIBRARY_NAMESPACE: &str = "valence";
|
||||||
|
|
||||||
/// A discrete unit of time where 1 tick is the duration of a
|
/// A discrete unit of time where 1 tick is the duration of a
|
||||||
/// single game update.
|
/// single game update.
|
||||||
///
|
///
|
||||||
/// The duration of a game update depends on the current configuration, which
|
/// The duration of a game update on a Valence server depends on the current
|
||||||
/// may or may not be the same as Minecraft's standard 20 ticks/second.
|
/// configuration. In some contexts, "ticks" refer to the configured tick rate
|
||||||
|
/// while others refer to Minecraft's [standard TPS](STANDARD_TPS).
|
||||||
pub type Ticks = i64;
|
pub type Ticks = i64;
|
||||||
|
|
||||||
|
/// Minecraft's standard ticks per second (TPS).
|
||||||
|
pub const STANDARD_TPS: Ticks = 20;
|
||||||
|
|
Loading…
Reference in a new issue