mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Merge pull request #7354 from Xmilon/native/render-name-tag
🤖 Agent: queued atb7ca31b03a.a8cdcce9dkeeps the no-mod path free (no player-id set, no dispatch without a native mod). Its head merged with today's main passes xtask mod-api --check and the stamp tests, and the native lib tests passed on that merge.
This commit is contained in:
5 files changed
+159
-21
No files matched your search
@@ -8,8 +8,8 @@
|
||||
//! and the player's own in third person with the `ownNameTag` extra.
|
||||
use super::{Game, view_vector};
|
||||
use horizon_client::{
|
||||
block_entity_render::vertex_light, hud::HudBatch, mesher::Vertex, mobs::Mob, save::uuid_text,
|
||||
scoreboard as sb, text::Text,
|
||||
block_entity_render::vertex_light, hud::HudBatch, mesher::Vertex, mobs::Mob, mod_api,
|
||||
save::uuid_text, scoreboard as sb, text::Text,
|
||||
};
|
||||
|
||||
/// The world size of one text pixel in a name tag: `Matrix4f.scale(0.025F, -0.025F, 0.025F)`.
|
||||
@@ -103,15 +103,27 @@ impl Game<'_> {
|
||||
}
|
||||
/// Each name tag this frame.
|
||||
fn name_tags(&self, camera: glam::DVec3, alpha: f64) -> Vec<NameTag> {
|
||||
let hooks = !mod_api::mods().is_empty();
|
||||
let target = self.mob_hit().map(|(i, _)| self.mobs[i].id);
|
||||
let viewer = self.player_team();
|
||||
let mut tags = Vec::new();
|
||||
let players = self.other_players();
|
||||
let player_ids = hooks.then(|| {
|
||||
players
|
||||
.iter()
|
||||
.map(|(player, _)| player.id)
|
||||
.collect::<std::collections::HashSet<u32>>()
|
||||
});
|
||||
let named = self.mobs.iter().filter_map(|m| {
|
||||
if player_ids.as_ref().is_some_and(|ids| ids.contains(&m.id)) {
|
||||
return None;
|
||||
}
|
||||
// A mannequin with no name still shows its description.
|
||||
let name = match &m.custom_name {
|
||||
Some(name) => name.clone(),
|
||||
None if m.species() == horizon_client::mobs::Species::Mannequin => String::new(),
|
||||
None if hooks || m.species() == horizon_client::mobs::Species::Mannequin => {
|
||||
String::new()
|
||||
}
|
||||
None => return None,
|
||||
};
|
||||
// The tag's 64 block distance is measured to the entity: copy the
|
||||
@@ -180,7 +192,22 @@ impl Game<'_> {
|
||||
let name_shown = sb::name_shown(team, viewer, view)
|
||||
&& (player || m.name_visible || Some(m.id) == target)
|
||||
&& (player || m.custom_name.is_some());
|
||||
if !name_shown {
|
||||
let mut visible = name_shown;
|
||||
let mut text = None;
|
||||
if hooks {
|
||||
self.native_render_name_tag(
|
||||
m.id,
|
||||
if player {
|
||||
"minecraft:player"
|
||||
} else {
|
||||
&m.kind.id
|
||||
},
|
||||
name_shown,
|
||||
&mut visible,
|
||||
&mut text,
|
||||
);
|
||||
}
|
||||
if !visible {
|
||||
if let Some(text) = description {
|
||||
let l = self.world.light(
|
||||
m.position.x.floor() as i32,
|
||||
@@ -211,6 +238,9 @@ impl Game<'_> {
|
||||
None => member,
|
||||
}
|
||||
.resolve_legacy();
|
||||
let shown = text
|
||||
.map(|text| Text::literal(text).resolve_legacy())
|
||||
.unwrap_or(shown);
|
||||
let mut lines = Vec::new();
|
||||
// EntityRenderer.extractNameTags: the score shows within the
|
||||
// below-name distance (10), under the name.
|
||||
@@ -239,14 +269,17 @@ impl Game<'_> {
|
||||
discrete,
|
||||
});
|
||||
}
|
||||
drop(player_ids);
|
||||
// Armor stands name themselves: an invisible one with a visible name
|
||||
// is a hologram (its model is skipped, its name tag is not).
|
||||
for a in &self.armor_stands {
|
||||
let Some(name) = &a.custom_name else {
|
||||
continue;
|
||||
};
|
||||
if !a.name_visible {
|
||||
continue;
|
||||
if !hooks {
|
||||
let Some(_) = &a.custom_name else {
|
||||
continue;
|
||||
};
|
||||
if !a.name_visible {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let height = match (a.marker, a.small) {
|
||||
(true, _) => 0.0,
|
||||
@@ -268,14 +301,31 @@ impl Game<'_> {
|
||||
is_camera: false,
|
||||
is_vehicle: false,
|
||||
};
|
||||
if !sb::name_shown(team, viewer, view) {
|
||||
let name_shown =
|
||||
a.custom_name.is_some() && a.name_visible && sb::name_shown(team, viewer, view);
|
||||
let mut visible = name_shown;
|
||||
let mut text = None;
|
||||
if hooks {
|
||||
self.native_render_name_tag(
|
||||
a.id,
|
||||
"minecraft:armor_stand",
|
||||
name_shown,
|
||||
&mut visible,
|
||||
&mut text,
|
||||
);
|
||||
}
|
||||
if !visible {
|
||||
continue;
|
||||
}
|
||||
let name = a.custom_name.clone().unwrap_or_else(Text::empty);
|
||||
let shown = match team {
|
||||
Some(t) => t.formatted_name(name.clone()),
|
||||
None => name.clone(),
|
||||
}
|
||||
.resolve_legacy();
|
||||
let shown = text
|
||||
.map(|text| Text::literal(text).resolve_legacy())
|
||||
.unwrap_or(shown);
|
||||
let mut lines = Vec::new();
|
||||
let mut lift = 0.0;
|
||||
let holder = self.persist.uuids.get(&a.id).map(|u| uuid_text(*u));
|
||||
@@ -302,19 +352,43 @@ impl Game<'_> {
|
||||
}
|
||||
// ItemFrameRenderer.shouldShowName/getNameTag: only the targeted
|
||||
// frame's custom-named item, with the HUD visible (also glow frames).
|
||||
if !self.ui.hide_gui
|
||||
&& let Some((i, distance)) = self.frame_hit()
|
||||
&& self.before_block(distance)
|
||||
&& self.mob_hit().is_none_or(|(_, d)| d > distance)
|
||||
&& let Some(name) = self.item_frames[i]
|
||||
.item
|
||||
.as_ref()
|
||||
.and_then(|item| item.custom_name())
|
||||
{
|
||||
let frame = &self.item_frames[i];
|
||||
let targeted_frame = (hooks || !self.ui.hide_gui)
|
||||
.then(|| self.frame_hit())
|
||||
.flatten()
|
||||
.filter(|(_, distance)| {
|
||||
self.before_block(*distance) && self.mob_hit().is_none_or(|(_, d)| d > *distance)
|
||||
})
|
||||
.map(|(index, _)| index);
|
||||
let frames = if hooks {
|
||||
0..self.item_frames.len()
|
||||
} else {
|
||||
targeted_frame.map_or(0..0, |index| index..index + 1)
|
||||
};
|
||||
for index in frames {
|
||||
let frame = &self.item_frames[index];
|
||||
let name = frame.item.as_ref().and_then(|item| item.custom_name());
|
||||
if !hooks && name.is_none() {
|
||||
continue;
|
||||
}
|
||||
let (lo, hi) = frame.bounds();
|
||||
let center = glam::DVec3::from_array(std::array::from_fn(|a| (lo[a] + hi[a]) / 2.0));
|
||||
if center.distance_squared(camera) < 64.0 * 64.0 {
|
||||
let name_shown =
|
||||
!self.ui.hide_gui && targeted_frame == Some(index) && name.is_some();
|
||||
let mut visible = name_shown;
|
||||
let mut text = None;
|
||||
if hooks {
|
||||
self.native_render_name_tag(
|
||||
frame.id,
|
||||
frame.kind(),
|
||||
name_shown,
|
||||
&mut visible,
|
||||
&mut text,
|
||||
);
|
||||
}
|
||||
if !visible {
|
||||
continue;
|
||||
}
|
||||
// NAME_TAG is at the entity height (0.5), then the name
|
||||
// submitter lifts it 0.5. ItemFrameRenderer offsets rendering
|
||||
// 0.3 toward its facing and -0.25 vertically.
|
||||
@@ -326,7 +400,10 @@ impl Game<'_> {
|
||||
0.75,
|
||||
f64::from(step[2] as f32 * 0.3),
|
||||
),
|
||||
lines: vec![(Text::literal(name).resolve_legacy(), 0.0)],
|
||||
lines: vec![(
|
||||
Text::literal(text.or(name).unwrap_or_default()).resolve_legacy(),
|
||||
0.0,
|
||||
)],
|
||||
light: self.world.light(
|
||||
center.x.floor() as i32,
|
||||
center.y.floor() as i32,
|
||||
|
||||
@@ -2493,6 +2493,25 @@ impl Game<'_> {
|
||||
pub(super) fn native_camera_shake(&self, partial: f32, shake: &mut NativeShake) {
|
||||
mod_api::dispatch_camera_shake(self, mod_api::mods(), partial, shake);
|
||||
}
|
||||
/// Name tag visibility and optional replacement text. API @API:render_name_tag@.
|
||||
pub(super) fn native_render_name_tag(
|
||||
&self,
|
||||
entity_id: u32,
|
||||
kind: &str,
|
||||
vanilla_visible: bool,
|
||||
visible: &mut bool,
|
||||
text: &mut Option<String>,
|
||||
) {
|
||||
mod_api::dispatch_render_name_tag(
|
||||
self,
|
||||
mod_api::mods(),
|
||||
entity_id,
|
||||
kind,
|
||||
vanilla_visible,
|
||||
visible,
|
||||
text,
|
||||
);
|
||||
}
|
||||
/// Before one first-person hand and its displayed stack are drawn (RenderHandEvent).
|
||||
/// Equip progress is the inverse arm height, including the item's swap animation scale.
|
||||
/// Cancel skips that hand's default geometry, including its map and arm geometry.
|
||||
|
||||
+35
@@ -4558,6 +4558,11 @@ pub trait Hooks: Send + Sync {
|
||||
let _ = event;
|
||||
Flow::Continue
|
||||
}
|
||||
/// Name tag visibility and optional replacement text. API @API:render_name_tag@.
|
||||
fn render_name_tag(&self, host: &dyn Host, event: &mut RenderNameTagEvent<'_>) {
|
||||
let _ = host;
|
||||
let _ = event;
|
||||
}
|
||||
/// A screen was observed closing, at most once per frame.
|
||||
fn screen_closing(&self, host: &dyn Host, event: &mut ScreenClosingEvent<'_>) {
|
||||
let _ = host;
|
||||
@@ -14032,6 +14037,36 @@ pub fn dispatch_render_item_in_frame(
|
||||
}
|
||||
}
|
||||
}
|
||||
/// What [`Hooks::render_name_tag`] is told. Fields may be added: the struct is `#[non_exhaustive]`.
|
||||
#[non_exhaustive]
|
||||
pub struct RenderNameTagEvent<'a> {
|
||||
pub entity_id: u32,
|
||||
pub kind: &'a str,
|
||||
pub vanilla_visible: bool,
|
||||
pub visible: bool,
|
||||
pub text: &'a mut Option<String>,
|
||||
}
|
||||
pub fn dispatch_render_name_tag(
|
||||
host: &dyn Host,
|
||||
mods: &[Box<dyn Hooks>],
|
||||
entity_id: u32,
|
||||
kind: &str,
|
||||
vanilla_visible: bool,
|
||||
visible: &mut bool,
|
||||
text: &mut Option<String>,
|
||||
) {
|
||||
let mut event = RenderNameTagEvent {
|
||||
entity_id,
|
||||
kind,
|
||||
vanilla_visible,
|
||||
visible: *visible,
|
||||
text,
|
||||
};
|
||||
for mod_hook in super::entered(mods, "render_name_tag") {
|
||||
mod_hook.render_name_tag(host, &mut event);
|
||||
}
|
||||
*visible = event.visible;
|
||||
}
|
||||
/// What [`Hooks::screen_closing`] is told. Fields may be added: the struct is `#[non_exhaustive]`.
|
||||
#[non_exhaustive]
|
||||
pub struct ScreenClosingEvent<'a> {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
API @API:compute_fov_modifier@ adds `native_compute_fov_modifier(player, base, modifier)` on the client each tick for the local player's FOV scale, after vanilla effects and before zoom and camera easing. `player` contains the calculation inputs and `base` is the original scale. Non-finite modifiers or values outside 0.01..=10 fall back to `base`. It does not affect a spectator camera on a mob. Bundles need "hooks": @API:compute_fov_modifier@.
|
||||
|
||||
The client hook `native_render_name_tag(entity_id, kind, vanilla_visible, visible, text)` runs at player and mob, armor stand, and item frame name decisions before shared geometry. Set `visible` to override vanilla and `text` to `Some` literal replacement text (NeoForge `RenderNameTagEvent`). Distance and entity availability still apply; the local player's tag is considered only when its own camera and settings gates allow it. Bundles need "hooks": @API:render_name_tag@.
|
||||
|
||||
Native mods are **mixin bundles** that change the game's own Rust source before it is compiled, with
|
||||
[horizon-mixins](https://github.com/horizon-rust/horizon-mixins) (MIT, by levzzz5154, made for Horizon). A bundle
|
||||
is a `horizon-mixin.toml` and a few `.rs` helpers that hook into named functions of the game (HEAD, BEFORE_CALL,
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"hooks": [{"name":"render_name_tag", "target":"horizon_client::game::native_hooks::Game<'_>::native_render_name_tag", "neoforge":"RenderNameTagEvent", "side":"client", "cancellable":false, "mod_api":{"contract":"listen"}}],
|
||||
"rust_summary":"a name tag hook (native_render_name_tag).",
|
||||
"doc_summary":"a name tag hook (native_render_name_tag), API @API:render_name_tag@."
|
||||
}
|
||||
Reference in new issue
Block a user