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),
|
||||
},
|
||||
],
|
||||
events: &[],
|
||||
events: &[Event::Hurt],
|
||||
};
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
let player_id = server
|
||||
client.data.player = server
|
||||
.entities
|
||||
.create_with_uuid(EntityKind::Player, client.uuid(), ())
|
||||
.unwrap()
|
||||
.0;
|
||||
|
||||
client.data = ClientData { player: player_id };
|
||||
|
||||
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());
|
||||
}
|
||||
|
@ -164,12 +162,12 @@ impl Config for Game {
|
|||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
});
|
||||
|
||||
for (_, client) in server.clients.iter_mut() {
|
||||
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() {
|
||||
match event {
|
||||
Event::Digging { position, .. } => {
|
||||
|
@ -181,40 +179,6 @@ impl Config for Game {
|
|||
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) => {
|
||||
if let EntityState::Player(e) = &mut player.state {
|
||||
match hand {
|
||||
|
@ -226,7 +190,26 @@ 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 {
|
||||
return;
|
||||
|
|
|
@ -41,7 +41,7 @@ use crate::slotmap::{Key, SlotMap};
|
|||
use crate::text::Text;
|
||||
use crate::util::{chunks_in_view_distance, is_chunk_in_view_distance};
|
||||
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).
|
||||
///
|
||||
|
@ -175,6 +175,8 @@ pub struct Client<C: Config> {
|
|||
world: WorldId,
|
||||
new_position: Vec3<f64>,
|
||||
old_position: Vec3<f64>,
|
||||
/// Measured in m/s.
|
||||
velocity: Vec3<f32>,
|
||||
/// Measured in degrees
|
||||
yaw: f32,
|
||||
/// Measured in degrees
|
||||
|
@ -228,7 +230,8 @@ pub(crate) struct ClientFlags {
|
|||
hardcore: bool,
|
||||
attack_speed_modified: bool,
|
||||
movement_speed_modified: bool,
|
||||
#[bits(5)]
|
||||
velocity_modified: bool,
|
||||
#[bits(4)]
|
||||
_pad: u8,
|
||||
}
|
||||
|
||||
|
@ -252,6 +255,7 @@ impl<C: Config> Client<C> {
|
|||
world: WorldId::default(),
|
||||
new_position: Vec3::default(),
|
||||
old_position: Vec3::default(),
|
||||
velocity: Vec3::default(),
|
||||
yaw: 0.0,
|
||||
pitch: 0.0,
|
||||
teleport_id_counter: 0,
|
||||
|
@ -294,6 +298,16 @@ impl<C: Config> Client<C> {
|
|||
&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
|
||||
/// a skin, then `None` is returned.
|
||||
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`].
|
||||
pub fn teleport(&mut self, pos: impl Into<Vec3<f64>>, yaw: f32, pitch: f32) {
|
||||
self.new_position = pos.into();
|
||||
|
||||
self.yaw = yaw;
|
||||
self.pitch = pitch;
|
||||
self.velocity = Vec3::default();
|
||||
|
||||
if !self.flags.teleported_this_tick() {
|
||||
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>>) {
|
||||
self.send_packet(SetEntityMotion {
|
||||
entity_id: VarInt(0),
|
||||
velocity: velocity_to_packet_units(velocity.into()),
|
||||
});
|
||||
self.velocity = velocity.into();
|
||||
self.flags.set_velocity_modified(true);
|
||||
}
|
||||
|
||||
/// Gets this client's yaw.
|
||||
|
@ -610,14 +631,25 @@ impl<C: Config> Client<C> {
|
|||
if client.pending_teleports == 0 {
|
||||
// TODO: validate movement using swept AABB collision with the blocks.
|
||||
// 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 {
|
||||
position: client.new_position,
|
||||
yaw: client.yaw,
|
||||
pitch: client.pitch,
|
||||
on_ground: client.flags.on_ground(),
|
||||
old_position: client.new_position,
|
||||
old_velocity: client.velocity,
|
||||
old_yaw: client.yaw,
|
||||
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.velocity = new_velocity;
|
||||
client.yaw = new_yaw;
|
||||
client.pitch = new_pitch;
|
||||
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 open horse inventory if not on a horse.
|
||||
// - 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 {
|
||||
PlayerCommandId::StartSneaking => {
|
||||
if self.flags.sneaking() {
|
||||
return;
|
||||
}
|
||||
self.flags.set_sneaking(true);
|
||||
Event::StartSneaking
|
||||
}
|
||||
PlayerCommandId::StopSneaking => {
|
||||
if !self.flags.sneaking() {
|
||||
return;
|
||||
}
|
||||
self.flags.set_sneaking(false);
|
||||
Event::StopSneaking
|
||||
}
|
||||
PlayerCommandId::LeaveBed => Event::LeaveBed,
|
||||
PlayerCommandId::StartSprinting => {
|
||||
if self.flags.sprinting() {
|
||||
return;
|
||||
}
|
||||
self.flags.set_sprinting(true);
|
||||
Event::StartSprinting
|
||||
}
|
||||
PlayerCommandId::StopSprinting => {
|
||||
if !self.flags.sprinting() {
|
||||
return;
|
||||
}
|
||||
self.flags.set_sprinting(false);
|
||||
Event::StopSprinting
|
||||
}
|
||||
|
@ -914,6 +959,7 @@ impl<C: Config> Client<C> {
|
|||
self.teleport(self.position(), self.yaw(), self.pitch());
|
||||
} else {
|
||||
if self.flags.spawn() {
|
||||
self.flags.set_spawn(false);
|
||||
self.loaded_entities.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.
|
||||
for msg in self.msgs_to_send.drain(..) {
|
||||
send_packet(
|
||||
|
@ -1244,7 +1301,6 @@ impl<C: Config> Client<C> {
|
|||
metadata: RawBytes(data),
|
||||
});
|
||||
}
|
||||
self.player_data.clear_modifications();
|
||||
|
||||
// Spawn new entities within the view distance.
|
||||
let pos = self.position();
|
||||
|
@ -1253,7 +1309,7 @@ impl<C: Config> Client<C> {
|
|||
|id, _| {
|
||||
let entity = entities
|
||||
.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
|
||||
&& entity.uuid() != self.uuid
|
||||
&& self.loaded_entities.insert(id)
|
||||
|
@ -1288,8 +1344,8 @@ impl<C: Config> Client<C> {
|
|||
// any effect.
|
||||
}
|
||||
|
||||
self.player_data.clear_modifications();
|
||||
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_
|
||||
/// client settings.
|
||||
SettingsChanged(Option<Settings>),
|
||||
/// The client moved. The values in this
|
||||
/// variant are the _previous_ position and look.
|
||||
/// The client moved.
|
||||
Movement {
|
||||
/// 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).
|
||||
yaw: f32,
|
||||
old_yaw: f32,
|
||||
/// The previous pitch (in degrees).
|
||||
pitch: f32,
|
||||
old_pitch: f32,
|
||||
/// 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,
|
||||
StopSneaking,
|
||||
|
|
|
@ -10,7 +10,7 @@ use crate::biome::Biome;
|
|||
use crate::dimension::Dimension;
|
||||
use crate::server::{NewClientData, Server, SharedServer};
|
||||
use crate::text::Text;
|
||||
use crate::Ticks;
|
||||
use crate::{Ticks, STANDARD_TPS};
|
||||
|
||||
/// A trait for the configuration of a server.
|
||||
///
|
||||
|
@ -65,9 +65,9 @@ pub trait Config: 'static + Sized + Send + Sync + UnwindSafe + RefUnwindSafe {
|
|||
///
|
||||
/// # Default Implementation
|
||||
///
|
||||
/// Returns `20`, which is the same as Minecraft's official server.
|
||||
/// Returns [`STANDARD_TPS`].
|
||||
fn tick_rate(&self) -> Ticks {
|
||||
20
|
||||
STANDARD_TPS
|
||||
}
|
||||
|
||||
/// 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::util::aabb_from_bottom_and_size;
|
||||
use crate::world::WorldId;
|
||||
use crate::STANDARD_TPS;
|
||||
|
||||
/// 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> {
|
||||
// The saturating cast to i16 is desirable.
|
||||
(vel * 400.0).as_()
|
||||
(8000.0 / STANDARD_TPS as f32 * vel).as_()
|
||||
}
|
||||
|
||||
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).
|
||||
///
|
||||
/// 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
|
||||
/// single game update.
|
||||
///
|
||||
/// The duration of a game update depends on the current configuration, which
|
||||
/// may or may not be the same as Minecraft's standard 20 ticks/second.
|
||||
/// The duration of a game update on a Valence server depends on the current
|
||||
/// 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;
|
||||
|
||||
/// Minecraft's standard ticks per second (TPS).
|
||||
pub const STANDARD_TPS: Ticks = 20;
|
||||
|
|
Loading…
Reference in a new issue