mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-09 17:19:15 -10:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1340e54376 | ||
|
|
5ae9f06129 | ||
|
|
13061c109f | ||
|
|
40eb5c7fd7 | ||
|
|
04a8acc5a3 |
No files matched your search
@@ -21,7 +21,6 @@ use std::collections::BTreeMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod combat;
|
||||
pub mod prediction;
|
||||
pub mod timer;
|
||||
|
||||
/// What one check's violation level leads to.
|
||||
@@ -162,8 +161,8 @@ checks! {
|
||||
|
||||
/// Prediction: each move played again from the client's own inputs with vanilla's movement code,
|
||||
/// and compared (speed, step, fly, jesus...). Each player's client ticks are recorded
|
||||
/// (`prediction::Record`), with a ping a tick to place what the server changes, and played again
|
||||
/// (horizon-client's `lan::prediction`). A move that lands farther than `tolerance` from the
|
||||
/// (horizon-client's `lan::prediction::record`), with a ping a tick to place what the server changes,
|
||||
/// and played again (`lan::prediction`). A move that lands farther than `tolerance` from the
|
||||
/// replay flags, by how far it lands (one `tolerance` off weighs 1, at most 5 a tick), and the
|
||||
/// setback puts the player back where its moves last agreed. A tick near what the replay can't
|
||||
/// play yet (another entity, a block change or abilities the client hadn't seen, a moving piston,
|
||||
@@ -279,15 +278,6 @@ pub struct Player {
|
||||
pub timer: timer::Balance,
|
||||
/// The server tick and height where the ground last held the player.
|
||||
pub ground: Option<(u64, f64)>,
|
||||
/// The client ticks the prediction plays again (only while it runs).
|
||||
pub record: Option<Box<prediction::Record>>,
|
||||
/// Sprinting as the client's own commands left it (ServerboundPlayerCommandPacket's
|
||||
/// START_SPRINTING and STOP_SPRINTING: LocalPlayer.wasSprinting), which the record's ticks take.
|
||||
/// Not the server's player: a hit with extra knockback stops its sprint
|
||||
/// (Player.causeExtraKnockback), the client's own `Player.attack` doesn't (a mob's `hurtClient`
|
||||
/// is false), and once the server's flag reaches it LocalPlayer.aiStep starts the sprint again
|
||||
/// while the sprint key is held, with nothing to say: wasSprinting never changed.
|
||||
pub sprinting: bool,
|
||||
/// The attacks of the client tick being heard.
|
||||
pub attacks: combat::Attacks,
|
||||
}
|
||||
|
||||
@@ -66,8 +66,7 @@ impl Game<'_> {
|
||||
motion.item_use_speed = item_use.map(|(speed, _)| speed);
|
||||
motion.item_use_slows_sprint = item_use.is_some_and(|(_, slows)| slows);
|
||||
}
|
||||
let (Some(record), Some(server)) = (g.anticheat.record.as_deref_mut(), g.motion.as_deref())
|
||||
else {
|
||||
let (Some(record), Some(server)) = (g.record.as_deref_mut(), g.motion.as_deref()) else {
|
||||
return;
|
||||
};
|
||||
let mut run = |body: &mut PlayerMotion, keys, sprinting| {
|
||||
|
||||
@@ -157,11 +157,8 @@ impl Hub {
|
||||
};
|
||||
if ac.config.runs(Check::Prediction) {
|
||||
let g = &mut self.guests[i];
|
||||
let (input, sprinting) = (g.input, g.anticheat.sprinting);
|
||||
g.anticheat
|
||||
.record
|
||||
.get_or_insert_default()
|
||||
.tick_end(input, sprinting);
|
||||
let (input, sprinting) = (g.input, g.client_sprinting);
|
||||
g.record.get_or_insert_default().tick_end(input, sprinting);
|
||||
}
|
||||
if !ac.config.runs(Check::Timer) {
|
||||
return;
|
||||
@@ -249,7 +246,7 @@ impl Hub {
|
||||
flags: u8,
|
||||
) {
|
||||
if self.recording() {
|
||||
let record = self.guests[i].anticheat.record.get_or_insert_default();
|
||||
let record = self.guests[i].record.get_or_insert_default();
|
||||
record.moved(pos, rot, flags);
|
||||
}
|
||||
}
|
||||
@@ -259,7 +256,7 @@ impl Hub {
|
||||
pub(super) fn anticheat_pong(&mut self, i: usize, id: i32) {
|
||||
let ticks = self.ticks;
|
||||
if self.recording() {
|
||||
let record = self.guests[i].anticheat.record.get_or_insert_default();
|
||||
let record = self.guests[i].record.get_or_insert_default();
|
||||
// Ids are server ticks, cut to 32 bits.
|
||||
record.pong(u64::from(id as u32), ticks);
|
||||
}
|
||||
@@ -280,7 +277,7 @@ impl Hub {
|
||||
.get_mut()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner),
|
||||
);
|
||||
if let Some(record) = g.anticheat.record.as_deref_mut() {
|
||||
if let Some(record) = g.record.as_deref_mut() {
|
||||
for change in changes {
|
||||
record.changed(ticks, change);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! A remote player: connection, position, chunk and entity views, and the
|
||||
//! player state (inventory, health, game mode) that the host's gameplay code
|
||||
//! operates on when it acts for them.
|
||||
use super::prediction::record::{Change, Record};
|
||||
use super::{Joined, PlayerSender, entity::Tracker};
|
||||
use crate::{
|
||||
items::ItemStack,
|
||||
@@ -221,10 +222,19 @@ pub struct Guest {
|
||||
pub position_this_tick: bool,
|
||||
/// Violation levels and the checks' state (the anticheat, when on).
|
||||
pub anticheat: horizon_anticheat::Player,
|
||||
/// The client ticks the movement prediction plays again (only while it records).
|
||||
pub record: Option<Box<Record>>,
|
||||
/// Sprinting as the client's own commands left it (ServerboundPlayerCommandPacket's
|
||||
/// START_SPRINTING and STOP_SPRINTING: LocalPlayer.wasSprinting), which the record's ticks take.
|
||||
/// Not the server's player: a hit with extra knockback stops its sprint
|
||||
/// (Player.causeExtraKnockback), the client's own `Player.attack` doesn't (a mob's `hurtClient`
|
||||
/// is false), and once the server's flag reaches it LocalPlayer.aiStep starts the sprint again
|
||||
/// while the sprint key is held, with nothing to say: wasSprinting never changed.
|
||||
pub client_sprinting: bool,
|
||||
/// The movement prediction's replay of the client's own moves (only while it runs).
|
||||
pub replay: Option<Box<super::prediction::Replay>>,
|
||||
/// What the server sent this tick that changes how the client moves (only while it records).
|
||||
pub changes: std::sync::Mutex<Vec<horizon_anticheat::prediction::Change>>,
|
||||
pub changes: std::sync::Mutex<Vec<Change>>,
|
||||
/// It accepted a move since the last entity range checks
|
||||
/// (ServerChunkCache.move), which then check all of its pairs again.
|
||||
pub moved: bool,
|
||||
@@ -515,6 +525,8 @@ impl Guest {
|
||||
support_blocks_sent: 0,
|
||||
position_this_tick: false,
|
||||
anticheat: Default::default(),
|
||||
record: None,
|
||||
client_sprinting: false,
|
||||
replay: None,
|
||||
changes: Default::default(),
|
||||
moved: false,
|
||||
@@ -626,7 +638,7 @@ impl Guest {
|
||||
if self.fake && !super::conn::fake_wants_id(packet.id()) {
|
||||
return;
|
||||
}
|
||||
if self.anticheat.record.is_some() {
|
||||
if self.record.is_some() {
|
||||
self.note_change(packet);
|
||||
}
|
||||
self.sender.send(packet.encode());
|
||||
@@ -653,7 +665,6 @@ impl Guest {
|
||||
/// What `packet` changes about how the client moves, for the prediction's replay: kept until
|
||||
/// the tick's ping places it (`Hub::anticheat_ping`).
|
||||
fn note_change(&self, packet: &play::Clientbound) {
|
||||
use horizon_anticheat::prediction::Change;
|
||||
use play::Clientbound as C;
|
||||
let own = |id: i32| id == self.entity_id;
|
||||
let change = match packet {
|
||||
|
||||
@@ -3168,7 +3168,7 @@ impl Hub {
|
||||
// changed, so the one nearest the player stands for them.
|
||||
for &i in &viewers {
|
||||
let g = &mut self.guests[i];
|
||||
let Some(record) = g.anticheat.record.as_deref_mut() else {
|
||||
let Some(record) = g.record.as_deref_mut() else {
|
||||
continue;
|
||||
};
|
||||
let slack = super::prediction::slack(std::array::from_fn(|i| {
|
||||
@@ -3252,7 +3252,7 @@ impl Hub {
|
||||
if recording {
|
||||
for &i in &viewers {
|
||||
let g = &mut self.guests[i];
|
||||
let Some(record) = g.anticheat.record.as_deref_mut() else {
|
||||
let Some(record) = g.record.as_deref_mut() else {
|
||||
continue;
|
||||
};
|
||||
let slack = super::prediction::slack(std::array::from_fn(|i| {
|
||||
@@ -3643,7 +3643,7 @@ impl Hub {
|
||||
}));
|
||||
for at in gone.drain(..) {
|
||||
let block = at.map(|v| v.floor() as i32);
|
||||
if let Some(record) = g.anticheat.record.as_deref_mut()
|
||||
if let Some(record) = g.record.as_deref_mut()
|
||||
&& super::prediction::near(g.position, block, slack)
|
||||
{
|
||||
record.entity_gone(ticks);
|
||||
@@ -4045,7 +4045,7 @@ impl Hub {
|
||||
fn set_sprinting(&mut self, i: usize, sprinting: bool) {
|
||||
let g = &mut self.guests[i];
|
||||
g.sprinting = sprinting;
|
||||
g.anticheat.sprinting = sprinting;
|
||||
g.client_sprinting = sprinting;
|
||||
if let Some(m) = g.motion.as_mut() {
|
||||
let _ = m.set_sprint_modifier(sprinting);
|
||||
}
|
||||
@@ -4132,10 +4132,7 @@ impl Hub {
|
||||
// The prediction's replay starts again where the client now is.
|
||||
if recording {
|
||||
let kept = g.awaiting_keeps_motion;
|
||||
g.anticheat
|
||||
.record
|
||||
.get_or_insert_default()
|
||||
.teleported(at, kept);
|
||||
g.record.get_or_insert_default().teleported(at, kept);
|
||||
}
|
||||
let rot = (p.yaw, p.pitch);
|
||||
self.position_change(i, [p.x, p.y, p.z], rot, 0, ctx, events);
|
||||
@@ -5119,7 +5116,7 @@ impl Hub {
|
||||
// ClientPacketListener.handleRespawn without KEEP_ENTITY_DATA: the client's new player
|
||||
// starts with wasSprinting false. (A level change keeps it: the new player sprints on, or
|
||||
// says STOP.)
|
||||
g.anticheat.sprinting = false;
|
||||
g.client_sprinting = false;
|
||||
// The respawned player is a new one, without effects; so is the
|
||||
// client's (ClientPacketListener.handleRespawn).
|
||||
g.effects = Default::default();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! The movement prediction's replay (docs/anticheat.md): each client tick a player's record holds
|
||||
//! ([`horizon_anticheat::prediction::Record`]) is played again with the client's own movement code,
|
||||
//! ([`Record`]) is played again with the client's own movement code,
|
||||
//! from where the last one ended, and compared with where the client said it went.
|
||||
//!
|
||||
//! The inputs are known exactly: a vanilla client sends ServerboundPlayerInputPacket after its tick's
|
||||
@@ -21,8 +21,10 @@
|
||||
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use horizon_anticheat::prediction::{Change, ClientTick, KEPT, Record, Restart};
|
||||
pub mod record;
|
||||
|
||||
use horizon_core::geometry::Vec3;
|
||||
use record::{Change, ClientTick, KEPT, Record, Restart};
|
||||
|
||||
use crate::motion::PlayerMotion;
|
||||
use crate::sprint::Keys;
|
||||
@@ -650,13 +652,13 @@ fn sync(body: &mut PlayerMotion, server: &PlayerMotion) {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::record::Record;
|
||||
use super::*;
|
||||
use crate::{
|
||||
assets::Assets,
|
||||
motion::{MotionResources, MotionWorld},
|
||||
world::World,
|
||||
};
|
||||
use horizon_anticheat::prediction::Record;
|
||||
use std::path::Path;
|
||||
|
||||
/// Keys for each tick: 20 walking, 12 sprint-jumping, 12 strafing while turning, 6 still.
|
||||
|
||||
File renamed without changes.
@@ -1522,7 +1522,6 @@ fn the_prediction_records_each_client_tick_and_the_server_ticks_it_had_seen() {
|
||||
inbox.push_back((S::ClientTickEnd(Empty), 0));
|
||||
s.tick(&[]);
|
||||
let record = s.hub.guests[0]
|
||||
.anticheat
|
||||
.record
|
||||
.as_deref()
|
||||
.expect("the prediction records");
|
||||
@@ -1546,7 +1545,7 @@ fn the_prediction_records_each_client_tick_and_the_server_ticks_it_had_seen() {
|
||||
0,
|
||||
));
|
||||
s.tick(&[]);
|
||||
let record = s.hub.guests[0].anticheat.record.as_deref().unwrap();
|
||||
let record = s.hub.guests[0].record.as_deref().unwrap();
|
||||
assert_eq!(record.seen(), Some(pinged));
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user