mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Merge pull request #9246 from horizon-rust/pvp/hud-info
🤖 Agent: after martipops's review, the combo counts a hit only when the server's damage event names this player as the direct entity (spear jabs included, teammates' hits and fire ticks not), the off server widget no longer allocates each frame, and ping shows nothing before the first latency update. Verify passes at 3842a7aee.
This commit is contained in:
6 files changed
+338
-12
No files matched your search
@@ -462,6 +462,19 @@ pub struct Hud {
|
||||
/// How many of the held item the inventory holds (arrows with a bow or crossbow).
|
||||
pub held: bool,
|
||||
pub held_corner: Corner,
|
||||
/// The latency the server reports for this player (the tab list's bars), in multiplayer.
|
||||
pub ping: bool,
|
||||
pub ping_corner: Corner,
|
||||
/// The block position and the direction faced, as F3 shows them; hidden when F3 is
|
||||
/// reduced (`reducedDebugInfo`).
|
||||
pub coords: bool,
|
||||
pub coords_corner: Corner,
|
||||
/// The server's address, in multiplayer.
|
||||
pub server: bool,
|
||||
pub server_corner: Corner,
|
||||
/// Hits in a row the server confirmed, until this player is hurt or 3 seconds pass.
|
||||
pub combo: bool,
|
||||
pub combo_corner: Corner,
|
||||
/// A dark box behind each widget.
|
||||
pub background: bool,
|
||||
}
|
||||
@@ -475,6 +488,14 @@ impl Default for Hud {
|
||||
effects_corner: Corner::TopRight,
|
||||
held: false,
|
||||
held_corner: Corner::BottomRight,
|
||||
ping: false,
|
||||
ping_corner: Corner::TopLeft,
|
||||
coords: false,
|
||||
coords_corner: Corner::TopLeft,
|
||||
server: false,
|
||||
server_corner: Corner::TopLeft,
|
||||
combo: false,
|
||||
combo_corner: Corner::TopLeft,
|
||||
background: true,
|
||||
}
|
||||
}
|
||||
@@ -542,6 +563,14 @@ impl Pvp {
|
||||
"hudHeld" => u.held = flag(value, u.held),
|
||||
"hudHeldCorner" => u.held_corner = Corner::parse(value, u.held_corner),
|
||||
"hudBackground" => u.background = flag(value, u.background),
|
||||
"hudPing" => u.ping = flag(value, u.ping),
|
||||
"hudPingCorner" => u.ping_corner = Corner::parse(value, u.ping_corner),
|
||||
"hudCoords" => u.coords = flag(value, u.coords),
|
||||
"hudCoordsCorner" => u.coords_corner = Corner::parse(value, u.coords_corner),
|
||||
"hudServer" => u.server = flag(value, u.server),
|
||||
"hudServerCorner" => u.server_corner = Corner::parse(value, u.server_corner),
|
||||
"hudCombo" => u.combo = flag(value, u.combo),
|
||||
"hudComboCorner" => u.combo_corner = Corner::parse(value, u.combo_corner),
|
||||
_ => return false,
|
||||
}
|
||||
true
|
||||
@@ -589,6 +618,18 @@ impl Pvp {
|
||||
u.held_corner.name(),
|
||||
u.background,
|
||||
);
|
||||
let _ = write!(
|
||||
out,
|
||||
"hudPing:{}\nhudPingCorner:{}\nhudCoords:{}\nhudCoordsCorner:{}\nhudServer:{}\nhudServerCorner:{}\nhudCombo:{}\nhudComboCorner:{}\n",
|
||||
u.ping,
|
||||
u.ping_corner.name(),
|
||||
u.coords,
|
||||
u.coords_corner.name(),
|
||||
u.server,
|
||||
u.server_corner.name(),
|
||||
u.combo,
|
||||
u.combo_corner.name(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -649,6 +690,16 @@ opts! {
|
||||
HudHeld, "hud_held", "Held item count",
|
||||
"How many of the item in your hand your inventory holds; arrows with a bow or crossbow.";
|
||||
HudHeldCorner, "hud_held_corner", "Held count corner", "Where the count shows.";
|
||||
HudPing, "hud_ping", "Ping", "The latency the server reports for you, in milliseconds. Multiplayer only.";
|
||||
HudPingCorner, "hud_ping_corner", "Ping corner", "Where the ping shows.";
|
||||
HudCoords, "hud_coords", "Coordinates",
|
||||
"Your block position and the direction you face, as F3 shows them. Hidden when the world or server reduces F3.";
|
||||
HudCoordsCorner, "hud_coords_corner", "Coordinates corner", "Where the coordinates show.";
|
||||
HudServer, "hud_server", "Server address", "The address of the server you play on. Multiplayer only.";
|
||||
HudServerCorner, "hud_server_corner", "Server corner", "Where the address shows.";
|
||||
HudCombo, "hud_combo", "Combo counter",
|
||||
"Hits in a row the server confirmed, until you are hurt or 3 seconds pass without one.";
|
||||
HudComboCorner, "hud_combo_corner", "Combo corner", "Where the combo shows.";
|
||||
HudBackground, "hud_background", "Widget backgrounds", "A dark box behind each widget.";
|
||||
}
|
||||
/// The PvP & HUD page: headers (language keys) and options.
|
||||
@@ -692,6 +743,14 @@ pub fn rows() -> Vec<Result<Opt, &'static str>> {
|
||||
HudEffectsCorner,
|
||||
HudHeld,
|
||||
HudHeldCorner,
|
||||
HudPing,
|
||||
HudPingCorner,
|
||||
HudCoords,
|
||||
HudCoordsCorner,
|
||||
HudServer,
|
||||
HudServerCorner,
|
||||
HudCombo,
|
||||
HudComboCorner,
|
||||
HudBackground,
|
||||
]
|
||||
.map(Ok),
|
||||
@@ -706,7 +765,12 @@ impl Opt {
|
||||
Crosshair | Outline | Hit => None,
|
||||
OutlineColour | OutlineOpacity | OutlineThickness | OutlineFill => Some(Outline),
|
||||
HitColour | HitOpacity => Some(Hit),
|
||||
HudArmour | HudEffects | HudHeld | HudBackground => None,
|
||||
HudArmour | HudEffects | HudHeld | HudPing | HudCoords | HudServer | HudCombo
|
||||
| HudBackground => None,
|
||||
HudPingCorner => Some(HudPing),
|
||||
HudCoordsCorner => Some(HudCoords),
|
||||
HudServerCorner => Some(HudServer),
|
||||
HudComboCorner => Some(HudCombo),
|
||||
HudArmourCorner | HudArmourPercent => Some(HudArmour),
|
||||
HudEffectsCorner => Some(HudEffects),
|
||||
HudHeldCorner => Some(HudHeld),
|
||||
@@ -726,6 +790,10 @@ impl Opt {
|
||||
Opt::HudEffects => p.hud.effects,
|
||||
Opt::HudHeld => p.hud.held,
|
||||
Opt::HudBackground => p.hud.background,
|
||||
Opt::HudPing => p.hud.ping,
|
||||
Opt::HudCoords => p.hud.coords,
|
||||
Opt::HudServer => p.hud.server,
|
||||
Opt::HudCombo => p.hud.combo,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
@@ -754,6 +822,10 @@ impl Opt {
|
||||
Opt::HudArmourCorner => corner_label(p.hud.armour_corner),
|
||||
Opt::HudEffectsCorner => corner_label(p.hud.effects_corner),
|
||||
Opt::HudHeldCorner => corner_label(p.hud.held_corner),
|
||||
Opt::HudPingCorner => corner_label(p.hud.ping_corner),
|
||||
Opt::HudCoordsCorner => corner_label(p.hud.coords_corner),
|
||||
Opt::HudServerCorner => corner_label(p.hud.server_corner),
|
||||
Opt::HudComboCorner => corner_label(p.hud.combo_corner),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
@@ -805,6 +877,14 @@ impl Opt {
|
||||
Opt::HudHeld => u.held = !u.held,
|
||||
Opt::HudHeldCorner => u.held_corner = corner(u.held_corner),
|
||||
Opt::HudBackground => u.background = !u.background,
|
||||
Opt::HudPing => u.ping = !u.ping,
|
||||
Opt::HudPingCorner => u.ping_corner = corner(u.ping_corner),
|
||||
Opt::HudCoords => u.coords = !u.coords,
|
||||
Opt::HudCoordsCorner => u.coords_corner = corner(u.coords_corner),
|
||||
Opt::HudServer => u.server = !u.server,
|
||||
Opt::HudServerCorner => u.server_corner = corner(u.server_corner),
|
||||
Opt::HudCombo => u.combo = !u.combo,
|
||||
Opt::HudComboCorner => u.combo_corner = corner(u.combo_corner),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +157,63 @@ pub fn durability_colour(damage: u32, max: u32) -> u32 {
|
||||
0xFF00_0000 | (r as u32) << 16 | (g as u32) << 8
|
||||
}
|
||||
|
||||
/// `Direction.fromYRot`: the horizontal direction a yaw faces, as F3's "Facing" names it.
|
||||
pub fn facing(yaw: f32) -> &'static str {
|
||||
match (f64::from(yaw) / 90.0 + 0.5).floor() as i64 & 3 {
|
||||
0 => "south",
|
||||
1 => "west",
|
||||
2 => "north",
|
||||
_ => "east",
|
||||
}
|
||||
}
|
||||
|
||||
/// The combo counter: hits in a row on what this player attacked, each counted when the
|
||||
/// server reports that entity hurt (`ClientboundDamageEventPacket`) soon after the attack;
|
||||
/// back to 0 when this player is hurt or no hit lands for [`Combo::EXPIRE`] seconds.
|
||||
#[derive(Default)]
|
||||
pub struct Combo {
|
||||
count: u32,
|
||||
/// The entity attacked last and when.
|
||||
target: Option<(i32, f64)>,
|
||||
last_hit: f64,
|
||||
}
|
||||
impl Combo {
|
||||
/// How long after an attack the server's hurt report still counts it.
|
||||
pub const CONFIRM: f64 = 1.0;
|
||||
pub const EXPIRE: f64 = 3.0;
|
||||
pub fn attacked(&mut self, id: i32, now: f64) {
|
||||
self.target = Some((id, now));
|
||||
}
|
||||
/// The server reports entity `id` hurt. `by_me`: whether this player is
|
||||
/// the damage's direct entity, when the report names one (melee and
|
||||
/// spear hits do; Horizon's LAN hub names none for player victims). A
|
||||
/// report that names nobody counts only as the answer to the last swing.
|
||||
pub fn hurt(&mut self, id: i32, by_me: Option<bool>, now: f64) {
|
||||
let confirmed = match by_me {
|
||||
Some(mine) => mine,
|
||||
None => self
|
||||
.target
|
||||
.is_some_and(|(t, at)| t == id && now - at <= Self::CONFIRM),
|
||||
};
|
||||
if confirmed {
|
||||
self.target = None;
|
||||
self.count += 1;
|
||||
self.last_hit = now;
|
||||
}
|
||||
}
|
||||
/// This player was hurt.
|
||||
pub fn broken(&mut self) {
|
||||
self.count = 0;
|
||||
self.target = None;
|
||||
}
|
||||
pub fn count(&mut self, now: f64) -> u32 {
|
||||
if now - self.last_hit > Self::EXPIRE {
|
||||
self.count = 0;
|
||||
}
|
||||
self.count
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -194,6 +251,52 @@ mod tests {
|
||||
assert_eq!(durability_colour(50, 100), 0xFFFF_FF00);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn facing_follows_direction_from_y_rot() {
|
||||
// Yaw 0 looks toward +Z (south); turning right (yaw up) goes west, north, east.
|
||||
let names: Vec<_> = [0.0, 44.9, 45.0, 90.0, 180.0, -90.0, 270.0, -180.0, 720.0]
|
||||
.map(facing)
|
||||
.to_vec();
|
||||
assert_eq!(
|
||||
names,
|
||||
[
|
||||
"south", "south", "west", "west", "north", "east", "east", "north", "south"
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn combos_count_confirmed_hits_until_this_player_is_hurt() {
|
||||
let mut c = Combo::default();
|
||||
c.attacked(7, 0.0);
|
||||
c.hurt(7, None, 0.1);
|
||||
c.attacked(7, 0.6);
|
||||
c.hurt(7, None, 0.7);
|
||||
assert_eq!(c.count(0.8), 2);
|
||||
// Another entity hurt, or a report too late after the swing, adds nothing.
|
||||
c.attacked(7, 1.0);
|
||||
c.hurt(9, None, 1.1);
|
||||
c.hurt(7, None, 2.5);
|
||||
assert_eq!(c.count(2.5), 2);
|
||||
// One report per swing: a second one (thorns, fire) for the same attack doesn't count.
|
||||
c.attacked(7, 3.0);
|
||||
c.hurt(7, None, 3.1);
|
||||
c.hurt(7, None, 3.2);
|
||||
assert_eq!(c.count(3.2), 3);
|
||||
c.broken();
|
||||
assert_eq!(c.count(3.3), 0);
|
||||
c.attacked(7, 4.0);
|
||||
c.hurt(7, None, 4.1);
|
||||
assert_eq!(c.count(7.0), 1);
|
||||
assert_eq!(c.count(7.2), 0, "more than 3 s since the last hit");
|
||||
// A report that names who hit decides alone: a spear jab (no swing
|
||||
// remembered) counts, a teammate's hit right after a swing doesn't.
|
||||
c.hurt(7, Some(true), 8.0);
|
||||
c.attacked(7, 8.5);
|
||||
c.hurt(7, Some(false), 8.6);
|
||||
assert_eq!(c.count(8.7), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_widget_rebuilds_only_when_what_it_shows_changes() {
|
||||
let mut w: Widget<Vec<u32>> = Widget::default();
|
||||
|
||||
@@ -633,5 +633,29 @@
|
||||
"horizon.pvp.corner.top_left": "Top left",
|
||||
"horizon.pvp.corner.top_right": "Top right",
|
||||
"horizon.pvp.corner.bottom_left": "Bottom left",
|
||||
"horizon.pvp.corner.bottom_right": "Bottom right"
|
||||
"horizon.pvp.corner.bottom_right": "Bottom right",
|
||||
"horizon.pvp.hud_ping": "Ping",
|
||||
"horizon.pvp.hud_ping.tooltip": "The latency the server reports for you, in milliseconds. Multiplayer only.",
|
||||
"horizon.pvp.hud_ping_corner": "Ping corner",
|
||||
"horizon.pvp.hud_ping_corner.tooltip": "Where the ping shows.",
|
||||
"horizon.pvp.hud_coords": "Coordinates",
|
||||
"horizon.pvp.hud_coords.tooltip": "Your block position and the direction you face, as F3 shows them. Hidden when the world or server reduces F3.",
|
||||
"horizon.pvp.hud_coords_corner": "Coordinates corner",
|
||||
"horizon.pvp.hud_coords_corner.tooltip": "Where the coordinates show.",
|
||||
"horizon.pvp.hud_server": "Server address",
|
||||
"horizon.pvp.hud_server.tooltip": "The address of the server you play on. Multiplayer only.",
|
||||
"horizon.pvp.hud_server_corner": "Server corner",
|
||||
"horizon.pvp.hud_server_corner.tooltip": "Where the address shows.",
|
||||
"horizon.pvp.hud_combo": "Combo counter",
|
||||
"horizon.pvp.hud_combo.tooltip": "Hits in a row the server confirmed, until you are hurt or 3 seconds pass without one.",
|
||||
"horizon.pvp.hud_combo_corner": "Combo corner",
|
||||
"horizon.pvp.hud_combo_corner.tooltip": "Where the combo shows.",
|
||||
"horizon.pvp.hud.ping": "Ping: %s ms",
|
||||
"horizon.pvp.hud.coords": "XYZ: %s %s %s",
|
||||
"horizon.pvp.hud.facing": "Facing: %s",
|
||||
"horizon.pvp.hud.combo": "Combo: %s",
|
||||
"horizon.pvp.hud.direction.north": "North",
|
||||
"horizon.pvp.hud.direction.south": "South",
|
||||
"horizon.pvp.hud.direction.east": "East",
|
||||
"horizon.pvp.hud.direction.west": "West"
|
||||
}
|
||||
@@ -7,7 +7,7 @@ use horizon_client::{
|
||||
hud::{HudBatch, rgba},
|
||||
mesher::Vertex,
|
||||
pvp::{Colour, CrosshairCache},
|
||||
pvp_hud::{self, Durability, Icon, Line, Widget},
|
||||
pvp_hud::{self, Combo, Durability, Icon, Line, Widget},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -20,6 +20,12 @@ pub(super) struct PvpState {
|
||||
effects: Widget<Vec<(&'static str, i32, i32)>>,
|
||||
/// The counted item's slot and the total.
|
||||
held: Widget<Option<(usize, Arc<str>, u32)>>,
|
||||
ping: Widget<Option<i32>>,
|
||||
/// The block position and the direction faced.
|
||||
coords: Widget<Option<([i32; 3], &'static str)>>,
|
||||
server: Widget<Option<String>>,
|
||||
combo: Combo,
|
||||
combo_count: Widget<u32>,
|
||||
}
|
||||
/// `GuiGraphicsExtractor`'s grey for a detail line (an effect's time left).
|
||||
const DETAIL: u32 = 0xFF7F_7F7F;
|
||||
@@ -131,6 +137,21 @@ impl Game<'_> {
|
||||
}
|
||||
}
|
||||
}
|
||||
/// This player attacked entity `id` (the combo counter waits for the server's report).
|
||||
pub(super) fn pvp_attacked(&mut self, id: i32) {
|
||||
let now = self.started.elapsed().as_secs_f64();
|
||||
self.pvp.combo.attacked(id, now);
|
||||
}
|
||||
/// The server reports entity `id` hurt; `own` when it is this player,
|
||||
/// `by_me` whether this player was the damage's direct entity (if named).
|
||||
pub(super) fn pvp_hurt(&mut self, id: i32, own: bool, by_me: Option<bool>) {
|
||||
if own {
|
||||
self.pvp.combo.broken();
|
||||
} else {
|
||||
let now = self.started.elapsed().as_secs_f64();
|
||||
self.pvp.combo.hurt(id, by_me, now);
|
||||
}
|
||||
}
|
||||
/// Whether the effects widget replaces vanilla's effect icons (`Hud.extractEffects`).
|
||||
pub(super) fn pvp_effects_shown(&self) -> bool {
|
||||
self.settings.extras.pvp.hud.effects
|
||||
@@ -149,6 +170,18 @@ impl Game<'_> {
|
||||
Some((s.item.clone(), s.damage, max))
|
||||
});
|
||||
let held = if hud.held { self.held_count() } else { None };
|
||||
let multiplayer = self.remote.is_some() && !self.has_singleplayer_server();
|
||||
let ping = self.own_latency().filter(|_| multiplayer);
|
||||
let coords = self.player.as_ref().filter(|_| !self.reduced_debug_info());
|
||||
let coords = coords.map(|p| {
|
||||
let at = [p.position.x, p.position.y, p.position.z].map(|v| v.floor() as i32);
|
||||
(at, pvp_hud::facing(self.yaw))
|
||||
});
|
||||
// The address is copied only when it changed (`server_address` borrows the whole game).
|
||||
let server = self.server_address().filter(|_| multiplayer);
|
||||
let server = (hud.server && self.pvp.server.next.as_deref() != server)
|
||||
.then(|| server.map(str::to_owned));
|
||||
let now = self.started.elapsed().as_secs_f64();
|
||||
let atlas = &self.gui.atlas;
|
||||
let width = |s: &str| HudBatch::text_width(atlas, s);
|
||||
let state = &mut self.pvp;
|
||||
@@ -229,25 +262,88 @@ impl Game<'_> {
|
||||
} else {
|
||||
state.held.clear();
|
||||
}
|
||||
let text = |key: &str, args: &[&dyn std::fmt::Display]| {
|
||||
let line = horizon_client::lang::tr(key, args);
|
||||
Line::new(Icon::None, line, &width)
|
||||
};
|
||||
if hud.ping {
|
||||
state.ping.next = ping;
|
||||
state.ping.refresh(|ping, lines| {
|
||||
lines.extend(ping.map(|ms| text("horizon.pvp.hud.ping", &[&ms])));
|
||||
});
|
||||
} else {
|
||||
state.ping.clear();
|
||||
}
|
||||
if hud.coords {
|
||||
state.coords.next = coords;
|
||||
state.coords.refresh(|coords, lines| {
|
||||
if let Some(([x, y, z], facing)) = coords {
|
||||
lines.push(text("horizon.pvp.hud.coords", &[x, y, z]));
|
||||
let key = format!("horizon.pvp.hud.direction.{facing}");
|
||||
let name = horizon_client::lang::get_or(&key, facing);
|
||||
lines.push(text("horizon.pvp.hud.facing", &[&name]));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
state.coords.clear();
|
||||
}
|
||||
if hud.server {
|
||||
// A new String only when the address changes.
|
||||
let w = &mut state.server;
|
||||
if let Some(address) = server {
|
||||
w.next = address;
|
||||
}
|
||||
w.refresh(|address, lines| {
|
||||
if let Some(address) = address {
|
||||
let shown = horizon_client::net::shown_address(address);
|
||||
lines.push(Line::new(Icon::None, shown, &width));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
state.server.clear();
|
||||
}
|
||||
if hud.combo {
|
||||
state.combo_count.next = state.combo.count(now);
|
||||
state.combo_count.refresh(|count, lines| {
|
||||
lines.push(text("horizon.pvp.hud.combo", &[count]));
|
||||
});
|
||||
} else {
|
||||
state.combo_count.clear();
|
||||
}
|
||||
let background = hud.background;
|
||||
let p = &self.pvp;
|
||||
let widgets = [
|
||||
(
|
||||
hud.armour,
|
||||
hud.armour_corner,
|
||||
&self.pvp.armour.lines,
|
||||
self.pvp.armour.size,
|
||||
&p.armour.lines,
|
||||
p.armour.size,
|
||||
),
|
||||
(
|
||||
hud.effects,
|
||||
hud.effects_corner,
|
||||
&self.pvp.effects.lines,
|
||||
self.pvp.effects.size,
|
||||
&p.effects.lines,
|
||||
p.effects.size,
|
||||
),
|
||||
(hud.held, hud.held_corner, &p.held.lines, p.held.size),
|
||||
(hud.ping, hud.ping_corner, &p.ping.lines, p.ping.size),
|
||||
(
|
||||
hud.coords,
|
||||
hud.coords_corner,
|
||||
&p.coords.lines,
|
||||
p.coords.size,
|
||||
),
|
||||
(
|
||||
hud.held,
|
||||
hud.held_corner,
|
||||
&self.pvp.held.lines,
|
||||
self.pvp.held.size,
|
||||
hud.server,
|
||||
hud.server_corner,
|
||||
&p.server.lines,
|
||||
p.server.size,
|
||||
),
|
||||
(
|
||||
hud.combo,
|
||||
hud.combo_corner,
|
||||
&p.combo_count.lines,
|
||||
p.combo_count.size,
|
||||
),
|
||||
];
|
||||
for (on, corner, lines, size) in widgets {
|
||||
|
||||
@@ -4045,7 +4045,10 @@ impl Game<'_> {
|
||||
// This player's own hurt tilt and flash come from the server too
|
||||
// (sent to the player itself): its own moves never hurt it.
|
||||
C::DamageEvent(p) => {
|
||||
if self.remote.as_ref().is_some_and(|r| r.entity_id == p.id) {
|
||||
let me = self.remote.as_ref().map(|r| r.entity_id);
|
||||
let own = me == Some(p.id);
|
||||
self.pvp_hurt(p.id, own, p.direct.map(|d| Some(d) == me));
|
||||
if own {
|
||||
let at = self
|
||||
.player
|
||||
.as_ref()
|
||||
@@ -8952,6 +8955,7 @@ impl Game<'_> {
|
||||
self.send(S::Attack(play::Attack {
|
||||
id: self.mobs[i].id as i32,
|
||||
}));
|
||||
self.pvp_attacked(self.mobs[i].id as i32);
|
||||
self.remote_player_attack(i);
|
||||
self.reset_attack_strength();
|
||||
}
|
||||
@@ -9022,6 +9026,14 @@ impl Game<'_> {
|
||||
}
|
||||
self.stop_sprint_after_extra_knockback();
|
||||
}
|
||||
/// The latency the server's player list gives this player
|
||||
/// (`PlayerInfo.getLatency`). A new entry reads 0 until the server's first
|
||||
/// latency update (every 600 ticks), so 0 shows nothing rather than `0 ms`.
|
||||
pub(super) fn own_latency(&self) -> Option<i32> {
|
||||
let r = self.remote.as_ref()?;
|
||||
let me = r.tab_players.get(&r.profile)?;
|
||||
(me.latency > 0).then_some(me.latency)
|
||||
}
|
||||
|
||||
/// An attribute's value on this client's player, if its map has the attribute.
|
||||
fn own_attribute(&mut self, id: &str) -> Option<f64> {
|
||||
|
||||
+11
@@ -60,3 +60,14 @@ mods' boxes. Code: `crates/horizon-client-render/src/pvp_hud.rs` (lines, cache,
|
||||
Cost: a widget keeps its lines (`pvp_hud::Widget`) and rebuilds them only when what it shows changes (a piece
|
||||
takes damage, an effect's second ticks, the count changes); a frame reads a few slots, compares, and adds the
|
||||
box's quads. With nothing changing, a frame allocates nothing.
|
||||
- **Ping** (top left): the latency the server's player list reports for you (the tab list's bars as a number); nothing until the server's first latency update, up to 30 s after joining.
|
||||
Multiplayer only.
|
||||
- **Coordinates** (top left): block position and the direction faced (`Direction.fromYRot`), as F3 shows them.
|
||||
Hidden whenever F3 is reduced (`reducedDebugInfo`: the option, the game rule or the server), so it never shows
|
||||
what the server hides.
|
||||
- **Server address** (top left): the address you joined (`net::shown_address`: invite codes stay hidden).
|
||||
Multiplayer only.
|
||||
- **Combo counter** (top left): your hits in a row, each counted when the server reports an entity hurt with you
|
||||
as the damage's direct entity (`ClientboundDamageEventPacket`: melee and spear hits, not arrows or fire). A
|
||||
report that names no attacker counts once, within a second of a swing at that entity. Back to 0 when you are hurt or
|
||||
3 seconds pass without a hit. It counts what the game already shows (the target's hurt flash).
|
||||
Reference in new issue
Block a user