mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Merge pull request #6291 from levzzz5154/mods/native-render-player
Native mods: drawing a player and adding layers to entities, RenderPlayerEvent and AddLayers
This commit is contained in:
6 files changed
+191
-12
No files matched your search
@@ -2,7 +2,10 @@
|
||||
//! chunk-generation spawning (`NaturalSpawner`), mob events (attacks,
|
||||
//! breeding, conversions, drops), projectiles, player interactions
|
||||
//! (feeding, shearing, milking, spawn eggs, throwing) and rendering.
|
||||
use super::{Dim, Game, InteractTarget, dispenser, native_projectiles::Impact, view_vector};
|
||||
use super::{
|
||||
Dim, Game, InteractTarget, dispenser, native_mob_draw::NativePlayerRender,
|
||||
native_projectiles::Impact, view_vector,
|
||||
};
|
||||
use horizon_assets::item_model::DisplayContext;
|
||||
use horizon_client::{
|
||||
Result,
|
||||
@@ -6200,10 +6203,20 @@ impl Game<'_> {
|
||||
// A native mod's own model in place of the vanilla one
|
||||
// (`native_mob_submit`), before any of the vanilla model's parts
|
||||
// (a sulfur cube's swallowed block) are drawn.
|
||||
if !is_player
|
||||
&& !layers_only
|
||||
&& self.mod_mob_geometry(mob, alpha as f32, camera, packed, out)
|
||||
{
|
||||
// A player's own hook (`native_player_submit`) is asked for the
|
||||
// other players in view.
|
||||
let who = is_player
|
||||
.then(|| self.remote_player_name(mob.id))
|
||||
.flatten()
|
||||
.map(|name| NativePlayerRender { name, local: false });
|
||||
let modded = if layers_only {
|
||||
false
|
||||
} else if let Some(who) = who {
|
||||
self.mod_player_geometry(mob, who, alpha as f32, camera, packed, out)
|
||||
} else {
|
||||
!is_player && self.mod_mob_geometry(mob, alpha as f32, camera, packed, out)
|
||||
};
|
||||
if modded {
|
||||
if let Some(rgb) = outline {
|
||||
self.mark_outline(&out[entity_start..], rgb);
|
||||
if mob.invisible {
|
||||
@@ -6282,6 +6295,8 @@ impl Game<'_> {
|
||||
}
|
||||
// CustomHeadLayer (a pumpkin, skull or head item) included.
|
||||
self.mob_block_geometry(mob, camera, alpha, packed, out);
|
||||
// `native_mob_layer`: a native mod's layer, over the vanilla ones.
|
||||
self.mod_mob_layer_geometry(mob, who, alpha as f32, camera, packed, out);
|
||||
if let Some(rgb) = outline {
|
||||
self.mark_outline(&out[entity_start..], rgb);
|
||||
if mob.invisible {
|
||||
|
||||
@@ -279,7 +279,7 @@ use super::{
|
||||
execution_role::ExecutionRole,
|
||||
maps::{NativeMapDecoration, NativeMapInfo},
|
||||
native_block_entity_draw::BlockEntityDraw,
|
||||
native_mob_draw::NativeMobRender,
|
||||
native_mob_draw::{NativeMobRender, NativePlayerRender},
|
||||
native_panel_recipes::RecipePage,
|
||||
native_server::{NativeCommand, NativeServerPlayer},
|
||||
};
|
||||
@@ -1864,6 +1864,47 @@ impl Game<'_> {
|
||||
) {
|
||||
let _ = (mob, draw, drawn);
|
||||
}
|
||||
/// Whether a mod listens to `native_player_submit` or `native_mob_layer`: a
|
||||
/// mod that hooks either hooks this one too and sets `listened` (HEAD,
|
||||
/// `*listened = true`). Without it the game poses no player or mob for
|
||||
/// them, each frame. Client-side. API @API:player_render@.
|
||||
pub(super) fn native_player_render_listened(&self, listened: &mut bool) {
|
||||
let _ = listened;
|
||||
}
|
||||
/// Draw the player `player` (`who`: its profile name, and whether it is
|
||||
/// this window's own player) with a model of your own, once a frame while
|
||||
/// it is in view (`RenderPlayerEvent.Pre`), and set `drawn` to leave out its
|
||||
/// vanilla model, armour, held items, cape and the other layers. This
|
||||
/// window's player is drawn only in third person (and F5 front view); an
|
||||
/// invisible player is not asked. `draw` is the frame of `native_mob_submit`:
|
||||
/// at the player's feet, turned with the body. The name tag, shadow,
|
||||
/// flames and glowing outline draw as before. API @API:player_render@.
|
||||
pub(super) fn native_player_submit(
|
||||
&self,
|
||||
player: &NativeMobRender<'_>,
|
||||
who: &NativePlayerRender<'_>,
|
||||
draw: &mut BlockEntityDraw,
|
||||
drawn: &mut bool,
|
||||
) {
|
||||
let _ = (player, who, draw, drawn);
|
||||
}
|
||||
/// A layer on every mob and player in view, once a frame after its
|
||||
/// vanilla model and layers (`EntityRenderersEvent.AddLayers`, with
|
||||
/// `RenderLivingEvent.Post` and `RenderPlayerEvent.Post`): add geometry
|
||||
/// in the entity's frame, which starts at its feet turned with the body
|
||||
/// (`mob.head_yaw`, `mob.pitch` and the walk cycle place a part, the eyes
|
||||
/// being at the entity type's eye height). `who` is set for a player.
|
||||
/// Not asked for what a mod drew itself (`native_mob_submit`,
|
||||
/// `native_player_submit`) nor for a mob that isn't drawn at all.
|
||||
/// API @API:player_render@.
|
||||
pub(super) fn native_mob_layer(
|
||||
&self,
|
||||
mob: &NativeMobRender<'_>,
|
||||
who: Option<&NativePlayerRender<'_>>,
|
||||
draw: &mut BlockEntityDraw,
|
||||
) {
|
||||
let _ = (mob, who, draw);
|
||||
}
|
||||
/// Icons a mod puts on map `map` each time it is drawn: held, in an
|
||||
/// item frame or on the cartography table's screen (a map's pixels are
|
||||
/// its own; nothing is saved in the map). Push `NativeMapDecoration`s
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
//! it cancels) gets the mob's render state and a [`BlockEntityDraw`] posed at
|
||||
//! its feet and turned with its body, so a mod draws a model of its own with
|
||||
//! the helpers block entities draw with (`native_draw_cube`,
|
||||
//! `native_draw_quad`, `native_draw_block`, `native_draw_item`).
|
||||
//! `native_draw_quad`, `native_draw_block`, `native_draw_item`). A player
|
||||
//! has `native_player_submit` (`RenderPlayerEvent.Pre`), and every mob and
|
||||
//! player in view gets `native_mob_layer` after its vanilla model
|
||||
//! (`EntityRenderersEvent.AddLayers`), in the same frame.
|
||||
|
||||
use super::{Dim, Game, native_block_entity_draw::BlockEntityDraw};
|
||||
use glam::{DVec3, Mat4};
|
||||
@@ -77,6 +80,18 @@ impl<'a> NativeMobRender<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Which player a [`NativeMobRender`] is, for `native_player_submit` and
|
||||
/// `native_mob_layer`.
|
||||
// Native mods read it; nothing in Horizon does.
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct NativePlayerRender<'a> {
|
||||
/// The player's profile name.
|
||||
pub name: &'a str,
|
||||
/// Whether it is the player of this window, drawn in third person.
|
||||
pub local: bool,
|
||||
}
|
||||
|
||||
/// The pose a mod's mob model starts from: at the feet, `at` (relative to the
|
||||
/// camera), turned with the body so +z is the way it faces, +x its left and +y
|
||||
/// up, in blocks.
|
||||
@@ -96,6 +111,69 @@ impl Game<'_> {
|
||||
camera: DVec3,
|
||||
light: u32,
|
||||
out: &mut Vec<Vertex>,
|
||||
) -> bool {
|
||||
self.mod_entity_draw(mob, partial, camera, light, out, |render, draw| {
|
||||
let mut drawn = false;
|
||||
self.native_mob_submit(render, draw, &mut drawn);
|
||||
drawn
|
||||
})
|
||||
}
|
||||
/// `native_player_submit` for the player `mob` (`who` says which): true
|
||||
/// when a mod drew it and the vanilla model, armour, held items, cape and
|
||||
/// other layers are left out.
|
||||
pub(super) fn mod_player_geometry(
|
||||
&self,
|
||||
mob: &Mob,
|
||||
who: NativePlayerRender<'_>,
|
||||
partial: f32,
|
||||
camera: DVec3,
|
||||
light: u32,
|
||||
out: &mut Vec<Vertex>,
|
||||
) -> bool {
|
||||
// Posing a player for the mods costs, each frame: only when one listens.
|
||||
let mut listened = false;
|
||||
self.native_player_render_listened(&mut listened);
|
||||
if !listened {
|
||||
return false;
|
||||
}
|
||||
self.mod_entity_draw(mob, partial, camera, light, out, |render, draw| {
|
||||
let mut drawn = false;
|
||||
self.native_player_submit(render, &who, draw, &mut drawn);
|
||||
drawn
|
||||
})
|
||||
}
|
||||
/// `native_mob_layer` for `mob` (`who` set for a player): what a mod adds
|
||||
/// is kept, drawn after the vanilla model and its layers.
|
||||
pub(super) fn mod_mob_layer_geometry(
|
||||
&self,
|
||||
mob: &Mob,
|
||||
who: Option<NativePlayerRender<'_>>,
|
||||
partial: f32,
|
||||
camera: DVec3,
|
||||
light: u32,
|
||||
out: &mut Vec<Vertex>,
|
||||
) {
|
||||
// Every mob in view each frame: only when a mod listens.
|
||||
let mut listened = false;
|
||||
self.native_player_render_listened(&mut listened);
|
||||
if !listened {
|
||||
return;
|
||||
}
|
||||
self.mod_entity_draw(mob, partial, camera, light, out, |render, draw| {
|
||||
self.native_mob_layer(render, who.as_ref(), draw);
|
||||
true
|
||||
});
|
||||
}
|
||||
/// Gives `hook` the frame of `mob` and a draw posed at its feet; what it
|
||||
/// added to `out` stays when it answers true.
|
||||
fn mod_entity_draw(
|
||||
&self,
|
||||
mob: &Mob,
|
||||
partial: f32,
|
||||
camera: DVec3,
|
||||
light: u32,
|
||||
out: &mut Vec<Vertex>,
|
||||
hook: impl FnOnce(&NativeMobRender<'_>, &mut BlockEntityDraw) -> bool,
|
||||
) -> bool {
|
||||
let render = NativeMobRender::of(mob, partial);
|
||||
let a = f64::from(partial);
|
||||
@@ -111,13 +189,12 @@ impl Game<'_> {
|
||||
diffuse_lights(self.dim == Dim::Nether),
|
||||
std::mem::take(out),
|
||||
);
|
||||
let mut drawn = false;
|
||||
self.native_mob_submit(&render, &mut draw, &mut drawn);
|
||||
let keep = hook(&render, &mut draw);
|
||||
*out = draw.into_vertices();
|
||||
if !drawn {
|
||||
if !keep {
|
||||
out.truncate(start);
|
||||
}
|
||||
drawn
|
||||
keep
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! in front, cycled by F5), the detached camera (`Camera.setup` with
|
||||
//! `getMaxZoom`), and the local player drawn as a model: in third person
|
||||
//! and in the inventory (`InventoryScreen.extractEntityInInventoryFollowsMouse`).
|
||||
use super::{Game, Screen, shoulders::shoulder_variants};
|
||||
use super::{Game, Screen, native_mob_draw::NativePlayerRender, shoulders::shoulder_variants};
|
||||
use horizon_client::{
|
||||
blocks::BlockFacts,
|
||||
camera::{CameraEntity, LivingCamera},
|
||||
@@ -625,6 +625,17 @@ impl Game<'_> {
|
||||
let lookup = |name: &str| atlas.sprites.get(name).map(|r| [r.u0, r.v0, r.u1, r.v1]);
|
||||
let start = out.len();
|
||||
let body = self.own_body_visible();
|
||||
// `native_player_submit`: a native mod's own model for this player,
|
||||
// in place of the vanilla one and its layers (an invisible player is
|
||||
// not asked, as for the others).
|
||||
let name = self.own_profile_name();
|
||||
let who = NativePlayerRender {
|
||||
name: &name,
|
||||
local: true,
|
||||
};
|
||||
if body && self.mod_player_geometry(m, who, alpha as f32, camera, packed, out) {
|
||||
return;
|
||||
}
|
||||
// A spectator still draws its own model in third person: the poses'
|
||||
// visibility (set by player_model_visibility) hides body parts, and
|
||||
// translucent_body marks the body OIT for the ghostly look. Only first
|
||||
@@ -664,6 +675,8 @@ impl Game<'_> {
|
||||
self.held_item_geometry(m, camera, alpha, packed, true, out);
|
||||
// CustomHeadLayer: a pumpkin, skull or other head item.
|
||||
self.head_item_geometry(m, camera, alpha, packed, true, out);
|
||||
// `native_mob_layer`: a native mod's layer, over the vanilla ones.
|
||||
self.mod_mob_layer_geometry(m, Some(who), alpha as f32, camera, packed, out);
|
||||
// LivingEntity.isCurrentlyGlowing: the Glowing effect.
|
||||
if self.potions.effects.get("glowing").is_some() {
|
||||
let color = super::glowing::team_color(self.own_team());
|
||||
|
||||
@@ -1126,6 +1126,13 @@ items are left out (NeoForge's `RenderLivingEvent.Pre`, cancelled); its name tag
|
||||
- A mod's own creature is an ordinary mob (`native_spawn_mob`, API 37) drawn this way: a quiet villager, an allay or a bat
|
||||
gives it movement, sounds and saving, and the mod gives it a model and animations of its own. Tell yours apart by the
|
||||
ids the server half sends with mod messages (API 40), or by its custom name.
|
||||
- Players have their own hook, `native_player_submit(player, who, draw, drawn)` (`RenderPlayerEvent.Pre`): once a frame for
|
||||
each other player in view and for this window's player in third person, with the same frame and `who` (the profile name,
|
||||
and `local`). `drawn` leaves out the vanilla model, armour, held items, cape and other layers.
|
||||
- `native_mob_layer(mob, who, draw)` (`EntityRenderersEvent.AddLayers`, `RenderLivingEvent.Post`) adds to every mob and player
|
||||
after its vanilla model and layers, in the same frame (not for one a mod drew itself); `who` is set for a player. The
|
||||
geometry hangs from the entity's frame, not from a model bone: place a hat from `head_yaw`, `pitch` and the entity type's
|
||||
eye height.
|
||||
|
||||
### Advancement triggers
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"name": "player_submit",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_player_submit",
|
||||
"neoforge": "RenderPlayerEvent.Pre",
|
||||
"side": "client",
|
||||
"cancellable": true
|
||||
},
|
||||
{
|
||||
"name": "mob_layer",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_mob_layer",
|
||||
"neoforge": "EntityRenderersEvent.AddLayers",
|
||||
"side": "client",
|
||||
"cancellable": false
|
||||
},
|
||||
{
|
||||
"name": "player_render_listened",
|
||||
"target": "horizon_client::game::native_hooks::Game<'_>::native_player_render_listened",
|
||||
"side": "client",
|
||||
"cancellable": false
|
||||
}
|
||||
],
|
||||
"rust_summary": "a player drawn with a mod's own model, and layers added to mobs and players (`native_player_submit`, `native_mob_layer`, told while a mod sets `native_player_render_listened`).",
|
||||
"doc_summary": "`native_player_submit(player, who, draw, drawn)` fires once a frame for each other player in view and for this window's player in third person (not while invisible), with the `native_mob_submit` frame (`NativeMobRender`, a `BlockEntityDraw` at the feet turned with the body) and `who` (profile name, whether it is the local player); set `drawn` to leave out the vanilla model, armour, held items, cape and other layers (`RenderPlayerEvent.Pre`). `native_mob_layer(mob, who, draw)` fires once a frame after the vanilla model and layers of every mob and player in view that a mod did not draw itself, and what the mod adds in that frame is drawn with it (`EntityRenderersEvent.AddLayers`, `RenderLivingEvent.Post`); `who` is set for a player. Geometry attaches to the entity's frame, not to the model's bones: head yaw, pitch and the walk cycle are in `NativeMobRender`. A mod that hooks either also hooks `native_player_render_listened(listened)` and sets `*listened = true`: otherwise neither is asked. Bundles need \"hooks\": @API:player_render@."
|
||||
}
|
||||
Reference in new issue
Block a user