mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Compare commits
96
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e05a39a92 | ||
|
|
02ef54254b | ||
|
|
bb5bcf52dd | ||
|
|
12f1b2cc00 | ||
|
|
7565ea2f17 | ||
|
|
1a90ff4382 | ||
|
|
02b0a7bc65 | ||
|
|
2a62069c2f | ||
|
|
fdfe30e026 | ||
|
|
af7dc5fae0 | ||
|
|
ecc61a5421 | ||
|
|
7e46f5ec8d | ||
|
|
5c4cbda32e | ||
|
|
41d5dade66 | ||
|
|
1205c6abdf | ||
|
|
62a23ba025 | ||
|
|
38da7d8d6e | ||
|
|
5e6bed2e42 | ||
|
|
8bb23aa1f7 | ||
|
|
558904775f | ||
|
|
191b66303a | ||
|
|
5a1c095724 | ||
|
|
bc78cba5b9 | ||
|
|
479d10e794 | ||
|
|
98c0883c8f | ||
|
|
b5b762b840 | ||
|
|
a5a0e1bb98 | ||
|
|
0dcc9a15fd | ||
|
|
a20aaf5396 | ||
|
|
1d01a2d696 | ||
|
|
49cf77a312 | ||
|
|
479f53624c | ||
|
|
1eb34fa68e | ||
|
|
d7662a97da | ||
|
|
4623d2e59f | ||
|
|
0f4880bacd | ||
|
|
3451e4e29a | ||
|
|
25e39a2f2e | ||
|
|
630f3feec5 | ||
|
|
451720bb56 | ||
|
|
cef862471f | ||
|
|
8ce20ba5bd | ||
|
|
f7300bd96b | ||
|
|
326719539d | ||
|
|
d3559e2a5f | ||
|
|
c90893c29c | ||
|
|
b85a9e8350 | ||
|
|
2f95ce431c | ||
|
|
0536ebbe98 | ||
|
|
dd130f1902 | ||
|
|
59538c7dc1 | ||
|
|
24e316855e | ||
|
|
1a9040ad79 | ||
|
|
2c10febff1 | ||
|
|
a32e0ac4ec | ||
|
|
fd8f1a02a1 | ||
|
|
a1cf544de7 | ||
|
|
8d8737a266 | ||
|
|
c045245e8e | ||
|
|
91358d8088 | ||
|
|
969749da3d | ||
|
|
5a8b9687e5 | ||
|
|
0f7036f9b1 | ||
|
|
12c503e154 | ||
|
|
2dd963a539 | ||
|
|
cd42938b88 | ||
|
|
ef304f30c9 | ||
|
|
e36fd24008 | ||
|
|
345f64e47c | ||
|
|
8f84c2cd48 | ||
|
|
e0a48b3f66 | ||
|
|
d35efa25ec | ||
|
|
b8b15b1682 | ||
|
|
05f8ec650d | ||
|
|
1944dd45e4 | ||
|
|
44448d941f | ||
|
|
fece4c6a76 | ||
|
|
a0b19a4820 | ||
|
|
02b1d1f430 | ||
|
|
b910cdd257 | ||
|
|
3934833369 | ||
|
|
621473f60e | ||
|
|
2b2dc1929f | ||
|
|
052dac817a | ||
|
|
42388d2512 | ||
|
|
2deaab2602 | ||
|
|
797482183c | ||
|
|
ca28a0edad | ||
|
|
694568a89c | ||
|
|
20d7d111e5 | ||
|
|
fb769e55e0 | ||
|
|
01d0170821 | ||
|
|
e71f98adde | ||
|
|
e319dd578f | ||
|
|
0f596d9e3c | ||
|
|
7671219f1a |
No files matched your search
@@ -1,6 +1,6 @@
|
||||
//! Text components from commands: `ComponentArgument` reads an SNBT (or JSON)
|
||||
//! value, `ComponentSerialization` turns it into styled text.
|
||||
use crate::text::{Style, Text, named_color};
|
||||
use crate::text::{InlineObject, Style, Text, named_color};
|
||||
use horizon_world::nbt::Tag;
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
@@ -296,6 +296,10 @@ pub fn to_text_with(
|
||||
let style = object_style(o);
|
||||
let content = if let Some(t) = contents(o) {
|
||||
t
|
||||
} else if let Some(object) = inline_object(o, resolve, contents) {
|
||||
let mut text = Text::literal("\u{fffc}");
|
||||
text.spans[0].style.inline_object = Some(object);
|
||||
text
|
||||
} else if let Some(t) = o.get("text") {
|
||||
match t {
|
||||
Value::String(s) => Text::literal(s.clone()),
|
||||
@@ -332,12 +336,74 @@ pub fn to_text_with(
|
||||
{
|
||||
out.push(to_text_with(extra, resolve, contents).with_style(&style));
|
||||
}
|
||||
out
|
||||
if o.contains_key("object") || o.contains_key("player") || o.contains_key("sprite") {
|
||||
out.with_component(Value::Object(o.clone()))
|
||||
} else {
|
||||
out
|
||||
}
|
||||
}
|
||||
Value::Null => Text::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
fn inline_object(
|
||||
object: &serde_json::Map<String, Value>,
|
||||
resolve: &mut dyn FnMut(&str) -> Text,
|
||||
contents: &mut dyn FnMut(&serde_json::Map<String, Value>) -> Option<Text>,
|
||||
) -> Option<InlineObject> {
|
||||
let kind = object.get("object").and_then(Value::as_str);
|
||||
let fallback = object
|
||||
.get("fallback")
|
||||
.map(|value| to_text_with(value, resolve, contents).plain());
|
||||
match kind {
|
||||
Some("player") | None if object.contains_key("player") => {
|
||||
let profile = object.get("player")?.clone();
|
||||
// PlayerSprite.defaultFallback.
|
||||
let fallback = fallback.unwrap_or_else(|| {
|
||||
match profile
|
||||
.as_str()
|
||||
.or_else(|| profile.get("name").and_then(Value::as_str))
|
||||
{
|
||||
Some(name) => format!("[{name} head]"),
|
||||
None => "[unknown player head]".to_owned(),
|
||||
}
|
||||
});
|
||||
Some(InlineObject::Player {
|
||||
profile,
|
||||
hat: object.get("hat").and_then(Value::as_bool).unwrap_or(true),
|
||||
fallback,
|
||||
})
|
||||
}
|
||||
Some("atlas") | None if object.contains_key("sprite") => {
|
||||
let atlas = object
|
||||
.get("atlas")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("minecraft:blocks")
|
||||
.to_owned();
|
||||
let sprite = object.get("sprite")?.as_str()?.to_owned();
|
||||
// AtlasSprite.defaultFallback: short names, the atlas unless blocks.
|
||||
let fallback = fallback.unwrap_or_else(|| {
|
||||
let short = |id: &str| match id.split_once(':') {
|
||||
Some(("minecraft", path)) => path.to_owned(),
|
||||
Some(_) => id.to_owned(),
|
||||
None => id.to_owned(),
|
||||
};
|
||||
if short(&atlas) == "blocks" {
|
||||
format!("[{}]", short(&sprite))
|
||||
} else {
|
||||
format!("[{}@{}]", short(&sprite), short(&atlas))
|
||||
}
|
||||
});
|
||||
Some(InlineObject::Atlas {
|
||||
atlas,
|
||||
sprite,
|
||||
fallback,
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// `CustomName` as text: a string keeps its legacy formatting codes, anything
|
||||
/// else is a component (`ComponentSerialization`).
|
||||
pub fn name_text(tag: &Tag) -> Text {
|
||||
@@ -351,6 +417,44 @@ pub fn name_text(tag: &Tag) -> Text {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn player_object_survives_text_parsing_and_keeps_its_siblings() {
|
||||
let component = serde_json::json!({
|
||||
"text": "<",
|
||||
"extra": [
|
||||
{"object": "player", "player": {"name": "elyapluggin"}, "hat": false,
|
||||
"fallback": "[head]"},
|
||||
{"text": " >", "color": "gold"}
|
||||
]
|
||||
});
|
||||
let text = to_text(&component, &mut |_| Text::empty());
|
||||
assert_eq!(text.plain(), "<[head] >");
|
||||
assert!(matches!(
|
||||
&text.spans[1].style.inline_object,
|
||||
Some(InlineObject::Player { hat: false, .. })
|
||||
));
|
||||
assert!(text.spans[2].style.inline_object.is_none());
|
||||
assert_eq!(text.component(), component);
|
||||
let sprite = serde_json::json!({"object": "atlas", "sprite": "minecraft:item/porkchop"});
|
||||
let text = to_text(&sprite, &mut |_| Text::empty());
|
||||
assert!(matches!(
|
||||
&text.spans[0].style.inline_object,
|
||||
Some(InlineObject::Atlas { sprite, .. }) if sprite == "minecraft:item/porkchop"
|
||||
));
|
||||
// ObjectContents.visit without a fallback: the sprite's defaultFallback.
|
||||
assert_eq!(text.plain(), "[item/porkchop]");
|
||||
let head = serde_json::json!({"object": "player", "player": {"name": "Steve"}});
|
||||
assert_eq!(
|
||||
to_text(&head, &mut |_| Text::empty()).plain(),
|
||||
"[Steve head]"
|
||||
);
|
||||
let gui = serde_json::json!({"atlas": "gui", "sprite": "hud/heart/full"});
|
||||
assert_eq!(
|
||||
to_text(&gui, &mut |_| Text::empty()).plain(),
|
||||
"[hud/heart/full@gui]"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snbt_and_json_components() {
|
||||
let input = "{text:'Hi ',color:red,extra:[{text:\"there\",bold:1b}]} rest";
|
||||
|
||||
@@ -58,6 +58,28 @@ pub enum ClickEvent {
|
||||
OpenFile(String),
|
||||
}
|
||||
|
||||
/// A non-character `minecraft:object` embedded in a text component.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum InlineObject {
|
||||
Player {
|
||||
profile: Value,
|
||||
hat: bool,
|
||||
fallback: String,
|
||||
},
|
||||
Atlas {
|
||||
atlas: String,
|
||||
sprite: String,
|
||||
fallback: String,
|
||||
},
|
||||
}
|
||||
impl InlineObject {
|
||||
pub fn fallback(&self) -> &str {
|
||||
match self {
|
||||
Self::Player { fallback, .. } | Self::Atlas { fallback, .. } => fallback,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Text style; `None` fields inherit from the enclosing style.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Style {
|
||||
@@ -77,6 +99,8 @@ pub struct Style {
|
||||
/// The font (`FontDescription.Resource`), a full id like
|
||||
/// `minecraft:alt`; the default font when unset.
|
||||
pub font: Option<String>,
|
||||
/// Content metadata for a single U+FFFC object span; never inherited.
|
||||
pub inline_object: Option<InlineObject>,
|
||||
}
|
||||
/// A style's `font` field: an identifier (`alt` is `minecraft:alt`).
|
||||
pub fn font_id(v: &Value) -> Option<String> {
|
||||
@@ -337,7 +361,13 @@ impl Text {
|
||||
}
|
||||
/// Component.getString: the plain characters.
|
||||
pub fn plain(&self) -> String {
|
||||
self.spans.iter().map(|s| s.text.as_str()).collect()
|
||||
self.spans
|
||||
.iter()
|
||||
.map(|s| match &s.style.inline_object {
|
||||
Some(object) if s.text == "\u{fffc}" => object.fallback(),
|
||||
_ => s.text.as_str(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.spans.iter().all(|s| s.text.is_empty())
|
||||
|
||||
@@ -653,6 +653,42 @@ pub struct Commands<'a> {
|
||||
pub registries: &'a Registries,
|
||||
}
|
||||
|
||||
/// `CommandSuggestions`' constructor arguments: how a screen's suggestions
|
||||
/// parse and where they are drawn.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct SuggestLayout {
|
||||
/// `commandsOnly`: the input is a command without a leading `/`.
|
||||
pub commands_only: bool,
|
||||
/// `onlyShowIfCursorPastError`: completions from the command's start,
|
||||
/// not only past the `/`.
|
||||
pub only_past_error: bool,
|
||||
/// `suggestionLineLimit`.
|
||||
pub line_limit: usize,
|
||||
/// `anchorToBottom`: the list and usage over a box at the bottom, else
|
||||
/// under one at the top (y 72).
|
||||
pub anchor_to_bottom: bool,
|
||||
/// `fillColor`: behind the list and the usage lines.
|
||||
pub fill: i32,
|
||||
}
|
||||
impl SuggestLayout {
|
||||
/// ChatScreen.init's `CommandSuggestions`.
|
||||
pub const CHAT: Self = Self {
|
||||
commands_only: false,
|
||||
only_past_error: false,
|
||||
line_limit: 10,
|
||||
anchor_to_bottom: true,
|
||||
fill: FILL,
|
||||
};
|
||||
/// AbstractCommandBlockEditScreen.init's `CommandSuggestions`.
|
||||
pub const COMMAND_BLOCK: Self = Self {
|
||||
commands_only: true,
|
||||
only_past_error: true,
|
||||
line_limit: 7,
|
||||
anchor_to_bottom: false,
|
||||
fill: i32::MIN,
|
||||
};
|
||||
}
|
||||
|
||||
/// The open chat screen: vanilla's `ChatScreen` with its `EditBox` and
|
||||
/// `CommandSuggestions` (argument colouring, usage lines, parse errors,
|
||||
/// the suggestion list, Tab completion, history).
|
||||
@@ -680,6 +716,7 @@ pub struct ChatScreen {
|
||||
pub auto_suggestions: bool,
|
||||
/// `CommandSuggestions.setRestrictions`: what the Chat option allows.
|
||||
pub abilities: ChatAbilities,
|
||||
pub layout: SuggestLayout,
|
||||
}
|
||||
/// Local completions waiting for the server's reply to request `id`.
|
||||
struct Awaiting {
|
||||
@@ -750,6 +787,43 @@ impl ChatScreen {
|
||||
keep_suggestions: false,
|
||||
auto_suggestions: true,
|
||||
abilities,
|
||||
layout: SuggestLayout::CHAT,
|
||||
};
|
||||
s.update_command_info(m, c);
|
||||
s
|
||||
}
|
||||
/// `AbstractCommandBlockEditScreen.init`: the command box at the top,
|
||||
/// focused, holding `command`, with suggestions from the start.
|
||||
pub fn command_block(
|
||||
m: &Measure,
|
||||
c: &Commands,
|
||||
command: &str,
|
||||
width: i32,
|
||||
height: i32,
|
||||
) -> Self {
|
||||
let mut input = TextField::new(width / 2 - 150, 50, 300, 20);
|
||||
input.max_length = 32500;
|
||||
input.focused = true;
|
||||
input.set_value(m, command);
|
||||
let mut s = Self {
|
||||
input,
|
||||
width,
|
||||
height,
|
||||
history_buffer: String::new(),
|
||||
history_pos: 0,
|
||||
parse: None,
|
||||
pending: None,
|
||||
awaiting: None,
|
||||
request: None,
|
||||
suggestions: None,
|
||||
usage: Vec::new(),
|
||||
usage_position: 0,
|
||||
usage_width: 0,
|
||||
allow_suggestions: true,
|
||||
keep_suggestions: false,
|
||||
auto_suggestions: true,
|
||||
abilities: ChatAbilities::new(ChatVisibility::Full),
|
||||
layout: SuggestLayout::COMMAND_BLOCK,
|
||||
};
|
||||
s.update_command_info(m, c);
|
||||
s
|
||||
@@ -768,7 +842,11 @@ impl ChatScreen {
|
||||
let value = self.input.value.clone();
|
||||
let auto = self.auto_suggestions;
|
||||
let abilities = self.abilities;
|
||||
*self = Self::new(m, c, &value, self.history_pos, width, height, abilities);
|
||||
*self = if self.layout == SuggestLayout::CHAT {
|
||||
Self::new(m, c, &value, self.history_pos, width, height, abilities)
|
||||
} else {
|
||||
Self::command_block(m, c, &value, width, height)
|
||||
};
|
||||
self.auto_suggestions = auto;
|
||||
}
|
||||
}
|
||||
@@ -814,11 +892,18 @@ impl ChatScreen {
|
||||
self.usage.clear();
|
||||
let cursor = self.input.cursor;
|
||||
let cursor_b = byte_at(&command, cursor);
|
||||
if command.starts_with('/') {
|
||||
let slash = command.starts_with('/');
|
||||
if self.layout.commands_only || slash {
|
||||
let start = usize::from(slash);
|
||||
if self.parse.is_none() {
|
||||
self.parse = Some(c.dispatcher.parse_at(&command, 1, c.registries));
|
||||
self.parse = Some(c.dispatcher.parse_at(&command, start, c.registries));
|
||||
}
|
||||
if cursor >= 1 && (self.suggestions.is_none() || !self.keep_suggestions) {
|
||||
let from = if self.layout.only_past_error {
|
||||
start
|
||||
} else {
|
||||
1
|
||||
};
|
||||
if cursor >= from && (self.suggestions.is_none() || !self.keep_suggestions) {
|
||||
let parse = self.parse.as_ref().expect("parsed above");
|
||||
let (range, list, ask) =
|
||||
c.dispatcher
|
||||
@@ -1002,7 +1087,11 @@ impl ChatScreen {
|
||||
0,
|
||||
self.input.screen_x(m, 0) + self.input.inner_width() - width,
|
||||
);
|
||||
let y = self.height - 12;
|
||||
let y = if self.layout.anchor_to_bottom {
|
||||
self.height - 12
|
||||
} else {
|
||||
72
|
||||
};
|
||||
// sortSuggestions: those starting with the word at the cursor first.
|
||||
let partial = &value[..byte_at(&value, self.input.cursor)];
|
||||
let word = partial[last_word_index(partial)..].to_lowercase();
|
||||
@@ -1011,9 +1100,16 @@ impl ChatScreen {
|
||||
s.text.starts_with(&word) || s.text.starts_with(&format!("minecraft:{word}"))
|
||||
});
|
||||
first.extend(rest);
|
||||
let n = first.len().min(10) as i32;
|
||||
// SuggestionsList: beside a bordered box, or over an unbordered one.
|
||||
let n = first.len().min(self.layout.line_limit) as i32;
|
||||
let bordered = i32::from(self.input.bordered);
|
||||
let top = if self.layout.anchor_to_bottom {
|
||||
y - 3 - n * 12
|
||||
} else {
|
||||
y - bordered
|
||||
};
|
||||
let mut l = SuggestionList {
|
||||
rect: [x - 1, y - 3 - n * 12, width + 1, n * 12],
|
||||
rect: [x - (1 - bordered), top, width + 1, n * 12],
|
||||
original: value,
|
||||
list: first,
|
||||
offset: 0,
|
||||
@@ -1042,11 +1138,12 @@ impl ChatScreen {
|
||||
}
|
||||
fn cycle(&mut self, l: &mut SuggestionList, dir: isize) {
|
||||
self.select(l, l.current as isize + dir);
|
||||
let max = l.list.len().saturating_sub(10);
|
||||
let limit = self.layout.line_limit;
|
||||
let max = l.list.len().saturating_sub(limit);
|
||||
if l.current < l.offset {
|
||||
l.offset = l.current.min(max);
|
||||
} else if l.current > l.offset + 9 {
|
||||
l.offset = (l.current as isize + 1 - 10).clamp(0, max as isize) as usize;
|
||||
} else if l.current >= l.offset + limit {
|
||||
l.offset = (l.current as isize + 1 - limit as isize).clamp(0, max as isize) as usize;
|
||||
}
|
||||
}
|
||||
/// `SuggestionsList.useSuggestion`.
|
||||
@@ -1074,7 +1171,57 @@ impl ChatScreen {
|
||||
ctrl: bool,
|
||||
) -> ChatAction {
|
||||
use winit::keyboard::KeyCode as K;
|
||||
// CommandSuggestions.keyPressed.
|
||||
if self.suggestions_key(m, c, key, shift) {
|
||||
return ChatAction::None;
|
||||
}
|
||||
match key {
|
||||
K::Escape => return ChatAction::Close,
|
||||
K::Enter | K::NumpadEnter => {
|
||||
// ChatScreen.keyPressed: without `hasAllowedInput` the input
|
||||
// stays, unsent, and the screen stays open.
|
||||
if !self.abilities.allows(&self.input.value) {
|
||||
return ChatAction::None;
|
||||
}
|
||||
return ChatAction::Submit(self.normalized());
|
||||
}
|
||||
K::ArrowUp => self.move_in_history(m, c, history, -1),
|
||||
K::ArrowDown => self.move_in_history(m, c, history, 1),
|
||||
_ => {
|
||||
self.field_key(m, c, key, shift, ctrl);
|
||||
}
|
||||
}
|
||||
ChatAction::None
|
||||
}
|
||||
/// The input box's own keys (`EditBox.keyPressed`), the suggestions
|
||||
/// following an edit: whether the box took the key.
|
||||
pub fn field_key(
|
||||
&mut self,
|
||||
m: &Measure,
|
||||
c: &Commands,
|
||||
key: winit::keyboard::KeyCode,
|
||||
shift: bool,
|
||||
ctrl: bool,
|
||||
) -> bool {
|
||||
match self.input.key(m, key, shift, ctrl) {
|
||||
Some(changed) => {
|
||||
if changed {
|
||||
self.edited(m, c);
|
||||
}
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
/// `CommandSuggestions.keyPressed`: the list's arrows, Tab and Escape,
|
||||
/// and Tab to show it. True when the suggestions took the key.
|
||||
pub fn suggestions_key(
|
||||
&mut self,
|
||||
m: &Measure,
|
||||
c: &Commands,
|
||||
key: winit::keyboard::KeyCode,
|
||||
shift: bool,
|
||||
) -> bool {
|
||||
use winit::keyboard::KeyCode as K;
|
||||
if let Some(mut l) = self.suggestions.take() {
|
||||
let handled = match key {
|
||||
K::ArrowUp | K::ArrowDown => {
|
||||
@@ -1091,37 +1238,18 @@ impl ChatScreen {
|
||||
}
|
||||
K::Escape => {
|
||||
self.input.suggestion = None;
|
||||
return ChatAction::None;
|
||||
return true;
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
self.suggestions = Some(l);
|
||||
if handled {
|
||||
return ChatAction::None;
|
||||
}
|
||||
handled
|
||||
} else if key == K::Tab {
|
||||
self.show_suggestions(m);
|
||||
return ChatAction::None;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
match key {
|
||||
K::Escape => return ChatAction::Close,
|
||||
K::Enter | K::NumpadEnter => {
|
||||
// ChatScreen.keyPressed: without `hasAllowedInput` the input
|
||||
// stays, unsent, and the screen stays open.
|
||||
if !self.abilities.allows(&self.input.value) {
|
||||
return ChatAction::None;
|
||||
}
|
||||
return ChatAction::Submit(self.normalized());
|
||||
}
|
||||
K::ArrowUp => self.move_in_history(m, c, history, -1),
|
||||
K::ArrowDown => self.move_in_history(m, c, history, 1),
|
||||
_ => {
|
||||
if self.input.key(m, key, shift, ctrl) == Some(true) {
|
||||
self.edited(m, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
ChatAction::None
|
||||
}
|
||||
/// `ChatScreen.moveInHistory`.
|
||||
fn move_in_history(&mut self, m: &Measure, c: &Commands, history: &[String], dir: isize) {
|
||||
@@ -1175,7 +1303,7 @@ impl ChatScreen {
|
||||
if !l.contains(mouse.0, mouse.1) {
|
||||
return false;
|
||||
}
|
||||
let max = l.list.len().saturating_sub(10) as i32;
|
||||
let max = l.list.len().saturating_sub(self.layout.line_limit) as i32;
|
||||
l.offset = ((l.offset as f64 - amount.clamp(-1.0, 1.0)) as i32).clamp(0, max) as usize;
|
||||
true
|
||||
}
|
||||
@@ -1246,7 +1374,9 @@ impl ChatScreen {
|
||||
) -> Vec<Draw> {
|
||||
let (w, h) = (self.width, self.height);
|
||||
let mut out = Vec::new();
|
||||
d::fill(&mut out, 2, h - 14, w - 2, h - 2, background);
|
||||
if self.layout.anchor_to_bottom {
|
||||
d::fill(&mut out, 2, h - 14, w - 2, h - 2, background);
|
||||
}
|
||||
let format = |s: &str, o: usize| self.format(s, o);
|
||||
self.input.draw(&mut out, m, blink_ms, Some(&format));
|
||||
if let Some(mut l) = self.suggestions.take() {
|
||||
@@ -1254,14 +1384,18 @@ impl ChatScreen {
|
||||
self.suggestions = Some(l);
|
||||
} else {
|
||||
for (i, line) in self.usage.iter().enumerate() {
|
||||
let y = h - 27 - 12 * i as i32;
|
||||
let y = if self.layout.anchor_to_bottom {
|
||||
h - 27 - 12 * i as i32
|
||||
} else {
|
||||
72 + 12 * i as i32
|
||||
};
|
||||
d::fill(
|
||||
&mut out,
|
||||
self.usage_position - 1,
|
||||
y,
|
||||
self.usage_position + self.usage_width + 1,
|
||||
y + 12,
|
||||
FILL,
|
||||
self.layout.fill,
|
||||
);
|
||||
d::text(&mut out, line.clone(), self.usage_position, y + 2, -1, true);
|
||||
}
|
||||
@@ -1275,7 +1409,7 @@ impl ChatScreen {
|
||||
l: &mut SuggestionList,
|
||||
mouse: (i32, i32),
|
||||
) {
|
||||
let limit = l.list.len().min(10);
|
||||
let limit = l.list.len().min(self.layout.line_limit);
|
||||
let [rx, ry, rw, rh] = l.rect;
|
||||
let has_prev = l.offset > 0;
|
||||
let has_next = l.list.len() > l.offset + limit;
|
||||
@@ -1284,8 +1418,8 @@ impl ChatScreen {
|
||||
l.last_mouse = mouse;
|
||||
}
|
||||
if has_prev || has_next {
|
||||
d::fill(out, rx, ry - 1, rx + rw, ry, FILL);
|
||||
d::fill(out, rx, ry + rh, rx + rw, ry + rh + 1, FILL);
|
||||
d::fill(out, rx, ry - 1, rx + rw, ry, self.layout.fill);
|
||||
d::fill(out, rx, ry + rh, rx + rw, ry + rh + 1, self.layout.fill);
|
||||
if has_prev {
|
||||
for x in (0..rw).step_by(2) {
|
||||
d::fill(out, rx + x, ry - 1, rx + x + 1, ry, -1);
|
||||
@@ -1300,7 +1434,7 @@ impl ChatScreen {
|
||||
let mut hovered = false;
|
||||
for i in 0..limit {
|
||||
let y = ry + 12 * i as i32;
|
||||
d::fill(out, rx, y, rx + rw, y + 12, FILL);
|
||||
d::fill(out, rx, y, rx + rw, y + 12, self.layout.fill);
|
||||
if mouse.0 > rx && mouse.0 < rx + rw && mouse.1 > y && mouse.1 < y + 12 {
|
||||
if moved {
|
||||
self.select(l, (i + l.offset) as isize);
|
||||
|
||||
@@ -74,9 +74,28 @@ pub enum BlockEntity {
|
||||
/// `DaylightDetectorBlockEntity`: no data, only the ticker
|
||||
/// (`DaylightDetectorBlock.tickEntity`).
|
||||
DaylightDetector,
|
||||
/// Suspicious sand and gravel: saved loot and the transient brushed face.
|
||||
Brushable(Box<Brushable>),
|
||||
/// A shelf holds three displayed item stacks.
|
||||
Shelf(Box<Shelf>),
|
||||
/// `ChiseledBookShelfBlockEntity`: a `Container` of six one-book slots.
|
||||
ChiseledBookshelf(Box<ChiseledBookshelf>),
|
||||
}
|
||||
/// `BrushableBlockEntity`: the loot table is resolved on the first brush.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Brushable {
|
||||
pub loot_table: Option<horizon_world::nbt::Tag>,
|
||||
pub loot_seed: Option<horizon_world::nbt::Tag>,
|
||||
pub item: Option<ItemStack>,
|
||||
/// Direction's legacy network id; not saved to disk by vanilla.
|
||||
pub hit_direction: Option<i32>,
|
||||
}
|
||||
/// `ShelfBlockEntity`'s displayed slots and alignment flag.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Shelf {
|
||||
pub items: [Option<ItemStack>; 3],
|
||||
pub align_to_bottom: bool,
|
||||
}
|
||||
/// `ChiseledBookShelfBlockEntity`: six slots and `lastInteractedSlot`.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ChiseledBookshelf {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
//! kinds, ticking as `AbstractFurnaceBlockEntity.serverTick` with fuel times
|
||||
//! from the data-driven `context_int_provider`/`context_float_provider` files.
|
||||
pub use crate::chunk::block_entity::{
|
||||
Banner, Beacon, BlockEntity, Campfire, ChiseledBookshelf, Crafter, CreakingHeart, EndGateway,
|
||||
Furnace, FurnaceKind, Jukebox, Lectern, Skull, SkullSkin,
|
||||
Banner, Beacon, BlockEntity, Brushable, Campfire, ChiseledBookshelf, Crafter, CreakingHeart,
|
||||
EndGateway, Furnace, FurnaceKind, Jukebox, Lectern, Shelf, Skull, SkullSkin,
|
||||
};
|
||||
use crate::items::ItemStack;
|
||||
use std::collections::HashMap;
|
||||
@@ -230,6 +230,14 @@ impl BlockEntity {
|
||||
recipes_used: HashMap::new(),
|
||||
}));
|
||||
}
|
||||
if matches!(
|
||||
name,
|
||||
"minecraft:suspicious_sand"
|
||||
| "minecraft:suspicious_gravel"
|
||||
| "minecraft:brushable_block"
|
||||
) {
|
||||
return Some(Self::Brushable(Box::default()));
|
||||
}
|
||||
if name == "minecraft:creaking_heart" {
|
||||
return Some(Self::CreakingHeart(Box::default()));
|
||||
}
|
||||
@@ -292,6 +300,9 @@ impl BlockEntity {
|
||||
if name == "minecraft:campfire" || name == "minecraft:soul_campfire" {
|
||||
return Some(Self::Campfire(Box::default()));
|
||||
}
|
||||
if name == "minecraft:shelf" || name.ends_with("_shelf") {
|
||||
return Some(Self::Shelf(Box::default()));
|
||||
}
|
||||
if name == "minecraft:chiseled_bookshelf" {
|
||||
return Some(Self::ChiseledBookshelf(Box::default()));
|
||||
}
|
||||
@@ -334,8 +345,10 @@ impl BlockEntity {
|
||||
Self::Skull(_) => "minecraft:skull",
|
||||
Self::EndGateway(_) => "minecraft:end_gateway",
|
||||
Self::DaylightDetector => "minecraft:daylight_detector",
|
||||
Self::Brushable(_) => "minecraft:brushable_block",
|
||||
Self::Campfire(_) => "minecraft:campfire",
|
||||
Self::ChiseledBookshelf(_) => "minecraft:chiseled_bookshelf",
|
||||
Self::Shelf(_) => "minecraft:shelf",
|
||||
}
|
||||
}
|
||||
pub fn contents(&self) -> Vec<ItemStack> {
|
||||
@@ -354,13 +367,15 @@ impl BlockEntity {
|
||||
| Self::CommandBlock(_)
|
||||
| Self::EndGateway(_)
|
||||
| Self::Skull(_)
|
||||
| Self::DaylightDetector => Vec::new(),
|
||||
| Self::DaylightDetector
|
||||
| Self::Brushable(_) => Vec::new(),
|
||||
Self::Lectern(l) => l.book.iter().cloned().collect(),
|
||||
Self::Sculk(_) => Vec::new(),
|
||||
Self::Banner(_) => Vec::new(),
|
||||
Self::Crafter(c) => c.items.iter().flatten().cloned().collect(),
|
||||
Self::Campfire(c) => c.items.iter().flatten().cloned().collect(),
|
||||
Self::ChiseledBookshelf(b) => b.items.iter().flatten().cloned().collect(),
|
||||
Self::Shelf(b) => b.items.iter().flatten().cloned().collect(),
|
||||
}
|
||||
}
|
||||
pub fn slots(&self) -> &[Option<ItemStack>] {
|
||||
@@ -376,9 +391,11 @@ impl BlockEntity {
|
||||
| Self::Banner(_)
|
||||
| Self::EndGateway(_)
|
||||
| Self::Skull(_)
|
||||
| Self::DaylightDetector => &[],
|
||||
| Self::DaylightDetector
|
||||
| Self::Brushable(_) => &[],
|
||||
Self::Crafter(c) => &c.items,
|
||||
Self::ChiseledBookshelf(b) => &b.items,
|
||||
Self::Shelf(b) => &b.items,
|
||||
Self::CreakingHeart(_)
|
||||
| Self::Spawner(_)
|
||||
| Self::TrialSpawner(_)
|
||||
@@ -402,9 +419,11 @@ impl BlockEntity {
|
||||
| Self::Banner(_)
|
||||
| Self::EndGateway(_)
|
||||
| Self::Skull(_)
|
||||
| Self::DaylightDetector => &mut [],
|
||||
| Self::DaylightDetector
|
||||
| Self::Brushable(_) => &mut [],
|
||||
Self::Crafter(c) => &mut c.items,
|
||||
Self::ChiseledBookshelf(b) => &mut b.items,
|
||||
Self::Shelf(b) => &mut b.items,
|
||||
Self::CreakingHeart(_)
|
||||
| Self::Spawner(_)
|
||||
| Self::TrialSpawner(_)
|
||||
|
||||
@@ -308,6 +308,25 @@ fn deliver(home: &Path, report: Report) -> Option<PathBuf> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The report for a hung server tick, written by the watchdog thread
|
||||
/// (`ServerWatchdog`): the stuck thread's stack is not available to Rust, so
|
||||
/// the report carries none.
|
||||
pub fn report_watchdog(home: &Path, side: &'static str, message: String) -> Option<PathBuf> {
|
||||
deliver(
|
||||
home,
|
||||
Report {
|
||||
time: Local::now(),
|
||||
description: "Watching Server".into(),
|
||||
error: format!("{message} (Watching Server)"),
|
||||
stack: String::new(),
|
||||
thread: thread_name(),
|
||||
side,
|
||||
details: system_details(),
|
||||
log: crate::logging::recent_lines(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn panic_message(info: &std::panic::PanicHookInfo) -> String {
|
||||
let payload = info.payload();
|
||||
let text = payload
|
||||
|
||||
@@ -125,6 +125,114 @@ pub fn discover(world: &Path) -> Vec<DataPack> {
|
||||
packs
|
||||
}
|
||||
|
||||
// ---- Create World ------------------------------------------------------------
|
||||
|
||||
/// Create World's Data Packs screen (`PackSelectionScreen` on vanilla's
|
||||
/// `mcworld-` folder): the folder's packs (`PackSource.WORLD`) and the
|
||||
/// built-in one (`resources` holds its `pack.png`), which can be unselected
|
||||
/// and stays at the bottom. Horizon has no experiments, so their feature
|
||||
/// packs are not offered.
|
||||
pub fn screen_packs(dir: &Path, resources: &Path) -> Vec<crate::pack_screen::PackInfo> {
|
||||
use crate::{
|
||||
pack_screen::{Compat, PackInfo, Source, load_icon},
|
||||
packs::Position,
|
||||
text::{Style, Text},
|
||||
};
|
||||
let format = horizon_core::version::DATA_PACK_FORMAT;
|
||||
let mut infos: Vec<PackInfo> = crate::packs::folder(dir, format)
|
||||
.into_iter()
|
||||
.map(|p| PackInfo {
|
||||
icon: p.icon_png().as_deref().and_then(load_icon),
|
||||
id: p.id,
|
||||
title: p.title,
|
||||
description: p.raw_description,
|
||||
description_style: p.description_style,
|
||||
source: Source::World,
|
||||
compat: p.compatibility,
|
||||
required: false,
|
||||
position: Position::Top,
|
||||
fixed: false,
|
||||
})
|
||||
.collect();
|
||||
infos.push(PackInfo {
|
||||
id: "vanilla".into(),
|
||||
title: Text::translate("dataPack.vanilla.name", &[]),
|
||||
description: Text::translate("dataPack.vanilla.description", &[]),
|
||||
description_style: Style::default(),
|
||||
source: Source::BuiltIn,
|
||||
compat: Compat::Compatible,
|
||||
icon: std::fs::read(resources.join("pack.png"))
|
||||
.ok()
|
||||
.as_deref()
|
||||
.and_then(load_icon),
|
||||
required: false,
|
||||
position: Position::Bottom,
|
||||
fixed: false,
|
||||
});
|
||||
infos
|
||||
}
|
||||
/// A new empty folder for Create World's packs
|
||||
/// (`Files.createTempDirectory("mcworld-")`).
|
||||
pub fn temp_folder() -> std::io::Result<PathBuf> {
|
||||
let nanos = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_nanos();
|
||||
let dir = std::env::temp_dir().join(format!("mcworld-{}-{nanos}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir)?;
|
||||
Ok(dir)
|
||||
}
|
||||
/// What's in a folder of packs, to notice packs added or removed while the
|
||||
/// screen shows it (`PackSelectionScreen.Watcher`).
|
||||
pub fn folder_stamp(dir: &Path) -> Vec<(String, Option<std::time::SystemTime>)> {
|
||||
let mut stamp: Vec<_> = std::fs::read_dir(dir)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.flatten()
|
||||
.map(|e| {
|
||||
let modified = e.metadata().and_then(|m| m.modified()).ok();
|
||||
(e.file_name().to_string_lossy().into_owned(), modified)
|
||||
})
|
||||
.collect();
|
||||
stamp.sort();
|
||||
stamp
|
||||
}
|
||||
/// Create World's packs into the new world before it opens
|
||||
/// (`copyTempDataPackDirToNewWorld`): every pack of `dir` goes to
|
||||
/// `<world>/datapacks`, and `datapacks.json` enables those `selected`
|
||||
/// (lowest priority first) and disables the rest.
|
||||
pub fn install(dir: &Path, world: &Path, selected: &[String]) -> Result<(), String> {
|
||||
let err = |p: &Path, e: std::io::Error| format!("{}: {e}", p.display());
|
||||
let target = world.join("datapacks");
|
||||
std::fs::create_dir_all(&target).map_err(|e| err(&target, e))?;
|
||||
let mut disabled = Vec::new();
|
||||
for pack in crate::packs::folder(dir, horizon_core::version::DATA_PACK_FORMAT) {
|
||||
let (crate::packs::Source::Dir(from) | crate::packs::Source::Zip(from)) = &pack.source;
|
||||
copy(from, &target.join(pack.id.trim_start_matches("file/")))?;
|
||||
if !selected.contains(&pack.id) {
|
||||
disabled.push(pack.id);
|
||||
}
|
||||
}
|
||||
if !selected.iter().any(|id| id == "vanilla") {
|
||||
disabled.push("vanilla".into());
|
||||
}
|
||||
let json = serde_json::json!({ "enabled": selected, "disabled": disabled });
|
||||
let file = world.join("datapacks.json");
|
||||
std::fs::write(&file, json.to_string()).map_err(|e| err(&file, e))
|
||||
}
|
||||
/// A file, or a folder and everything in it.
|
||||
fn copy(from: &Path, to: &Path) -> Result<(), String> {
|
||||
let err = |e: std::io::Error| format!("{} -> {}: {e}", from.display(), to.display());
|
||||
if !from.is_dir() {
|
||||
return std::fs::copy(from, to).map(drop).map_err(err);
|
||||
}
|
||||
std::fs::create_dir_all(to).map_err(err)?;
|
||||
for e in std::fs::read_dir(from).map_err(err)?.flatten() {
|
||||
copy(&e.path(), &to.join(e.file_name()))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ---- functions ---------------------------------------------------------------
|
||||
|
||||
/// A `.mcfunction`: plain command lines and macro lines.
|
||||
@@ -566,4 +674,51 @@ mod tests {
|
||||
assert_eq!(p.list_kinds(&kinds), wanted);
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_world_installs_its_packs() {
|
||||
let root = std::env::temp_dir().join(format!("hz-datapack-install-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
let (dir, world) = (root.join("mcworld"), root.join("world"));
|
||||
let write = |pack: &str, p: &str, t: &str| {
|
||||
let path = dir.join(pack).join(p);
|
||||
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
std::fs::write(path, t).unwrap();
|
||||
};
|
||||
write("fns", "pack.mcmeta", r#"{"pack":{"pack_format":121}}"#);
|
||||
write("fns", "data/x/function/f.mcfunction", "say hi");
|
||||
write("off", "pack.mcmeta", r#"{"pack":{"pack_format":121}}"#);
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let mut zip = zip::ZipWriter::new(std::fs::File::create(dir.join("old.zip")).unwrap());
|
||||
zip.start_file("pack.mcmeta", zip::write::SimpleFileOptions::default())
|
||||
.unwrap();
|
||||
std::io::Write::write_all(&mut zip, br#"{"pack":{"pack_format":48}}"#).unwrap();
|
||||
zip.finish().unwrap();
|
||||
let infos = screen_packs(&dir, &root);
|
||||
let ids: Vec<&str> = infos.iter().map(|p| p.id.as_str()).collect();
|
||||
assert_eq!(ids, ["file/fns", "file/off", "file/old.zip", "vanilla"]);
|
||||
let compat: Vec<_> = infos.iter().map(|p| p.compat).collect();
|
||||
use crate::pack_screen::Compat;
|
||||
assert_eq!(
|
||||
compat,
|
||||
[
|
||||
Compat::Compatible,
|
||||
Compat::Compatible,
|
||||
Compat::TooOld,
|
||||
Compat::Compatible
|
||||
]
|
||||
);
|
||||
let selected = ["vanilla", "file/old.zip", "file/fns"].map(String::from);
|
||||
install(&dir, &world, &selected).unwrap();
|
||||
let packs = discover(&world);
|
||||
let ids: Vec<&str> = packs.iter().map(|p| p.id.as_str()).collect();
|
||||
assert_eq!(ids, ["file/fns", "file/off", "file/old.zip"]);
|
||||
let saved: serde_json::Value =
|
||||
serde_json::from_slice(&std::fs::read(world.join("datapacks.json")).unwrap()).unwrap();
|
||||
assert_eq!(
|
||||
saved,
|
||||
serde_json::json!({"enabled": selected, "disabled": ["file/off"]})
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
}
|
||||
}
|
||||
@@ -896,6 +896,9 @@ impl Font {
|
||||
std::mem::take(&mut self.cache.borrow_mut().region.pending)
|
||||
}
|
||||
pub fn advance(&self, c: char, style: &Style) -> f32 {
|
||||
if c == '\u{fffc}' && style.inline_object.is_some() {
|
||||
return 8.0;
|
||||
}
|
||||
let g = self.glyph_styled(c, style);
|
||||
g.advance + if style.is_bold() { g.offset } else { 0.0 }
|
||||
}
|
||||
|
||||
@@ -59,6 +59,8 @@ pub struct HudAtlas {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub sprites: HashMap<String, Rect>,
|
||||
/// Resolved player-profile names and UUIDs for inline head components.
|
||||
pub object_skins: HashMap<String, String>,
|
||||
/// Sprites with a 0 < alpha < 255 texel (`SpriteContents.transparency().hasTranslucent`),
|
||||
/// which puts their particles on vanilla's translucent layer.
|
||||
pub translucent: HashSet<String>,
|
||||
@@ -252,6 +254,24 @@ fn atlas_sprites(root: &Path, packed: &[PackedImage]) -> Vec<PackedImage> {
|
||||
out.into_iter().map(|(_, image)| image).collect()
|
||||
}
|
||||
impl HudAtlas {
|
||||
/// Keep a ready profile skin when another source only has its default.
|
||||
pub fn set_object_skin(&mut self, key: String, skin: String) {
|
||||
use std::collections::hash_map::Entry;
|
||||
if self.object_skins.len() >= 1024 && !self.object_skins.contains_key(&key) {
|
||||
self.object_skins.clear();
|
||||
}
|
||||
match self.object_skins.entry(key) {
|
||||
Entry::Occupied(entry)
|
||||
if skin.starts_with("entity/player/") && self.sprites.contains_key(entry.get()) => {
|
||||
}
|
||||
Entry::Occupied(mut entry) => {
|
||||
entry.insert(skin);
|
||||
}
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(skin);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// A font glyph's atlas cell (8x8 GUI pixels) and advance.
|
||||
pub fn glyph(&self, ch: char) -> Option<(Rect, f32)> {
|
||||
let g = self.font.glyph(ch);
|
||||
@@ -592,6 +612,7 @@ impl HudAtlas {
|
||||
width,
|
||||
height,
|
||||
sprites,
|
||||
object_skins: HashMap::new(),
|
||||
translucent,
|
||||
flags,
|
||||
sprite_animations,
|
||||
@@ -641,6 +662,8 @@ pub struct HudBatch {
|
||||
pub glint_alpha: f32,
|
||||
/// Screen's first vertex: the preceding HUD is part of its blurred background.
|
||||
pub blur_at: u32,
|
||||
/// Player object profiles encountered while painting text this frame.
|
||||
pub inline_profiles: Vec<serde_json::Value>,
|
||||
}
|
||||
/// How a run of HUD vertices blends and tests depth.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
@@ -692,6 +715,7 @@ impl HudBatch {
|
||||
end_portal: EndPortalFill::default(),
|
||||
glint_alpha: 0.75,
|
||||
blur_at: 0,
|
||||
inline_profiles: Vec::new(),
|
||||
}
|
||||
}
|
||||
/// Draw the vertices added since `start` with the inverting blend.
|
||||
@@ -1042,22 +1066,43 @@ impl HudBatch {
|
||||
let mut cursor = x;
|
||||
let mut previous = None;
|
||||
for (i, (ch, style)) in text.chars().enumerate() {
|
||||
let object = (ch == '\u{fffc}')
|
||||
.then_some(style.inline_object.as_ref())
|
||||
.flatten();
|
||||
if let Some(crate::text::InlineObject::Player { profile, .. }) = object
|
||||
&& self.inline_profiles.len() < 256
|
||||
&& !self.inline_profiles.contains(profile)
|
||||
{
|
||||
self.inline_profiles.push(profile.clone());
|
||||
}
|
||||
// Font.PreparedTextBuilder.accept: lines start 1 left of a
|
||||
// sequence's first character. Language.getVisualOrder makes a
|
||||
// sequence of each run of one style, which world text has.
|
||||
let first = i == 0 || world && previous != Some(style);
|
||||
previous = Some(style);
|
||||
// The style's font (`Style.getFont`), the default one unless set.
|
||||
let mut g = font.glyph_styled(ch, style);
|
||||
let mut g = if object.is_some() {
|
||||
crate::font::Glyph {
|
||||
rect: None,
|
||||
left: 0.0,
|
||||
top: 0.0,
|
||||
right: 0.0,
|
||||
bottom: 8.0,
|
||||
advance: 8.0,
|
||||
offset: 0.0,
|
||||
}
|
||||
} else {
|
||||
font.glyph_styled(ch, style)
|
||||
};
|
||||
// Font.StringRenderOutput.accept: obfuscated characters draw a
|
||||
// random glyph of the same width each frame.
|
||||
if style.obfuscated == Some(true) && ch != ' ' {
|
||||
if object.is_none() && style.obfuscated == Some(true) && ch != ' ' {
|
||||
g = font.random_glyph(style, g.advance);
|
||||
}
|
||||
let c = style.color.map_or(color, |rgb| {
|
||||
rgba((rgb >> 16) as u8, (rgb >> 8) as u8, rgb as u8, alpha)
|
||||
});
|
||||
let bold = style.is_bold();
|
||||
let bold = object.is_none() && style.is_bold();
|
||||
let advance = g.advance + if bold { g.offset } else { 0.0 };
|
||||
glyphs.push((
|
||||
cursor,
|
||||
@@ -1069,6 +1114,7 @@ impl HudBatch {
|
||||
style.strikethrough == Some(true),
|
||||
first,
|
||||
advance,
|
||||
object.cloned(),
|
||||
));
|
||||
cursor += advance;
|
||||
}
|
||||
@@ -1086,20 +1132,26 @@ impl HudBatch {
|
||||
)
|
||||
};
|
||||
for &is_shadow in passes {
|
||||
for &(gx, g, c, bold, italic, underline, strike, first, advance) in &glyphs {
|
||||
for (gx, g, c, bold, italic, underline, strike, first, advance, object) in &glyphs {
|
||||
if let Some(object) = object {
|
||||
if !is_shadow {
|
||||
self.draw_inline_object(atlas, object, *gx, y, alpha);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// In the world a glyph whose shadow is 0 has none (hasShadow).
|
||||
let has_shadow = shadow && !(world && shade(c) == 0);
|
||||
let has_shadow = shadow && !(world && shade(*c) == 0);
|
||||
if is_shadow && !has_shadow {
|
||||
continue;
|
||||
}
|
||||
let (dx, col) = if is_shadow {
|
||||
(g.offset, shade(c))
|
||||
(g.offset, shade(*c))
|
||||
} else {
|
||||
(0.0, c)
|
||||
(0.0, *c)
|
||||
};
|
||||
let (px, py) = (gx + dx, y + dx);
|
||||
let shear = |v: f32| if italic { 1.0 - 0.25 * v } else { 0.0 };
|
||||
let thick = if bold { 0.1 } else { 0.0 };
|
||||
let (px, py) = (*gx + dx, y + dx);
|
||||
let shear = |v: f32| if *italic { 1.0 - 0.25 * v } else { 0.0 };
|
||||
let thick = if *bold { 0.1 } else { 0.0 };
|
||||
let draw = |batch: &mut Self, ox: f32| match g.rect {
|
||||
Some(r) => {
|
||||
let (x0, x1, y0, y1) = (
|
||||
@@ -1144,24 +1196,86 @@ impl HudBatch {
|
||||
let start = self.vertices.len();
|
||||
draw(self, 0.0);
|
||||
lift(self, start, base);
|
||||
if bold {
|
||||
if *bold {
|
||||
let start = self.vertices.len();
|
||||
draw(self, g.offset);
|
||||
lift(self, start, base + 0.001);
|
||||
}
|
||||
let x0 = if first { px - 1.0 } else { px };
|
||||
let x0 = if *first { px - 1.0 } else { px };
|
||||
let start = self.vertices.len();
|
||||
if strike {
|
||||
self.rect_uv(x0, py + 3.5, px + advance - x0, 1.0, white, col);
|
||||
if *strike {
|
||||
self.rect_uv(x0, py + 3.5, px + *advance - x0, 1.0, white, col);
|
||||
}
|
||||
if underline {
|
||||
self.rect_uv(x0, py + 8.0, px + advance - x0, 1.0, white, col);
|
||||
if *underline {
|
||||
self.rect_uv(x0, py + 8.0, px + *advance - x0, 1.0, white, col);
|
||||
}
|
||||
lift(self, start, base + 0.01);
|
||||
}
|
||||
}
|
||||
cursor - x
|
||||
}
|
||||
|
||||
fn draw_inline_object(
|
||||
&mut self,
|
||||
atlas: &HudAtlas,
|
||||
object: &crate::text::InlineObject,
|
||||
x: f32,
|
||||
y: f32,
|
||||
alpha: u8,
|
||||
) {
|
||||
use crate::text::InlineObject;
|
||||
let tint = rgba(255, 255, 255, alpha);
|
||||
match object {
|
||||
InlineObject::Player { profile, hat, .. } => {
|
||||
let id = crate::skins::parse_profile(profile).id;
|
||||
let name = inline_profile_name(profile);
|
||||
let by_id = atlas.object_skins.get(&format!("id:{id:032x}"));
|
||||
let by_name = name.and_then(|name| {
|
||||
atlas
|
||||
.object_skins
|
||||
.get(&format!("name:{}", name.to_ascii_lowercase()))
|
||||
});
|
||||
let resolved = if profile.get("id").is_some() {
|
||||
by_id.or(by_name)
|
||||
} else {
|
||||
by_name.or(by_id)
|
||||
};
|
||||
let default = crate::tab_list::default_skin(id);
|
||||
let skin = resolved
|
||||
.filter(|path| atlas.sprites.contains_key(*path))
|
||||
.map_or(default, String::as_str);
|
||||
let Some(rect) = atlas.sprites.get(skin).copied() else {
|
||||
return;
|
||||
};
|
||||
for u in [8.0, 40.0].into_iter().take(if *hat { 2 } else { 1 }) {
|
||||
let du = (rect.u1 - rect.u0) / 64.0;
|
||||
let dv = (rect.v1 - rect.v0) / 64.0;
|
||||
self.rect_uv(
|
||||
x,
|
||||
y,
|
||||
8.0,
|
||||
8.0,
|
||||
Rect {
|
||||
u0: rect.u0 + du * u,
|
||||
v0: rect.v0 + dv * 8.0,
|
||||
u1: rect.u0 + du * (u + 8.0),
|
||||
v1: rect.v0 + dv * 16.0,
|
||||
w: 8,
|
||||
h: 8,
|
||||
},
|
||||
tint,
|
||||
);
|
||||
}
|
||||
}
|
||||
InlineObject::Atlas { sprite, .. } => {
|
||||
let key = sprite.strip_prefix("minecraft:").unwrap_or(sprite);
|
||||
if let Some(rect) = atlas.sprites.get(key).copied() {
|
||||
self.rect_uv(x, y, 8.0, 8.0, rect, tint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// SubmitNodeCollection.submitNameTag's text in its own space (x right,
|
||||
/// y down, in GUI pixels): centred at `-Font.width / 2`, drawn as
|
||||
/// Font.prepareText draws any text (styles, fonts and glyph boxes; no
|
||||
@@ -1190,6 +1304,13 @@ impl HudBatch {
|
||||
behind
|
||||
}
|
||||
}
|
||||
|
||||
fn inline_profile_name(profile: &serde_json::Value) -> Option<&str> {
|
||||
profile
|
||||
.as_str()
|
||||
.or_else(|| profile.get("name").and_then(serde_json::Value::as_str))
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct HudUniform {
|
||||
@@ -1904,6 +2025,56 @@ mod tests {
|
||||
assert!(missing.rect.is_none() && missing.right == 5.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn player_object_draws_an_inline_head() {
|
||||
let root = two_glyph_font("inline-head");
|
||||
write_png(&root, "entity/player/slim/alex", 64, 64);
|
||||
write_png(&root, "entity/player/wide/steve", 64, 64);
|
||||
let mut atlas = HudAtlas::build(&root).unwrap();
|
||||
let text = crate::component::to_text(
|
||||
&serde_json::json!({"object":"player","player":{"id":"00000000-0000-0000-0000-000000000000"}}),
|
||||
&mut |_| crate::text::Text::empty(),
|
||||
);
|
||||
let mut batch = HudBatch::new([320, 240]);
|
||||
assert_eq!(
|
||||
batch.text_styled(&atlas, &text, 10.0, 10.0, u32::MAX, false),
|
||||
8.0
|
||||
);
|
||||
assert_eq!(batch.vertices.len(), 12); // face and hat, six vertices each
|
||||
assert_eq!(batch.inline_profiles.len(), 1);
|
||||
atlas.set_object_skin("name:elyapluggin".into(), "entity/player/wide/steve".into());
|
||||
let named = crate::component::to_text(
|
||||
&serde_json::json!({"object":"player","player":{"name":"elyapluggin"}}),
|
||||
&mut |_| crate::text::Text::empty(),
|
||||
);
|
||||
batch.vertices.clear();
|
||||
batch.text_styled(&atlas, &named, 10.0, 10.0, u32::MAX, false);
|
||||
let skin = atlas.sprites["entity/player/wide/steve"];
|
||||
assert_eq!(batch.vertices[0].uv[0], skin.u0 + (skin.u1 - skin.u0) / 8.0);
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn atlas_object_draws_an_item_sprite_inline() {
|
||||
let root = two_glyph_font("inline-item");
|
||||
write_png(&root, "item/porkchop", 16, 16);
|
||||
let atlas = HudAtlas::build(&root).unwrap();
|
||||
let text = crate::component::to_text(
|
||||
&serde_json::json!({
|
||||
"text": "a",
|
||||
"extra": [{"object": "atlas", "sprite": "minecraft:item/porkchop"}]
|
||||
}),
|
||||
&mut |_| crate::text::Text::empty(),
|
||||
);
|
||||
let mut batch = HudBatch::new([320, 240]);
|
||||
let width = batch.text_styled(&atlas, &text, 10.0, 10.0, u32::MAX, false);
|
||||
assert_eq!(width, atlas.font.glyph('a').advance + 8.0);
|
||||
assert_eq!(batch.vertices.len(), 12); // glyph and one sprite
|
||||
let sprite = atlas.sprites["item/porkchop"];
|
||||
assert_eq!(batch.vertices[6].uv, [sprite.u0, sprite.v0]);
|
||||
fs::remove_dir_all(root).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn name_tags_draw_bold_text_and_tall_glyphs_like_any_text() {
|
||||
// `ascii.png`'s 8x8 'a' (4 pixels wide) and `accented.png`'s 9x12
|
||||
@@ -2149,6 +2320,7 @@ mod tests {
|
||||
end_portal: EndPortalFill::default(),
|
||||
glint_alpha: 0.75,
|
||||
blur_at: 0,
|
||||
inline_profiles: Vec::new(),
|
||||
};
|
||||
for (image_width, image_height) in [(195, 136), (176, 166), (176, 222)] {
|
||||
assert_eq!(
|
||||
|
||||
@@ -87,6 +87,9 @@ const MODEL_PARTS: [&str; 7] = [
|
||||
];
|
||||
const GRAPHICS_BACKEND: &[&str] = &["default", "opengl", "vulkan"];
|
||||
const GRAPHICS_PRESET: &[&str] = &["fast", "fancy", "fabulous", "custom"];
|
||||
/// Horizon's render distance cap (vanilla stops at 32): the server protocol's
|
||||
/// view distance tops out at 127 anyway.
|
||||
pub const MAX_RENDER_DISTANCE: i32 = 128;
|
||||
const INACTIVITY: &[&str] = &["minimized", "afk"];
|
||||
const CLOUDS: &[&str] = &["false", "fast", "true"];
|
||||
const MAIN_HAND: &[&str] = &["left", "right"];
|
||||
@@ -159,7 +162,7 @@ static TABLE: LazyLock<Vec<Def>> = LazyLock::new(|| {
|
||||
b("reducedDebugInfo", false),
|
||||
def("renderClouds", O(Clouds), V::Int(2)),
|
||||
i("cloudRange", 2, 128, 128),
|
||||
i("renderDistance", 2, 64, 12),
|
||||
i("renderDistance", 2, MAX_RENDER_DISTANCE, 12),
|
||||
i("simulationDistance", 5, 32, 12),
|
||||
u("screenEffectScale", 1.0),
|
||||
def("soundDevice", O(AnyStr), V::Str(String::new())),
|
||||
|
||||
@@ -278,7 +278,11 @@ impl Opt {
|
||||
| Opt::GlintStrength
|
||||
| Opt::PanoramaSpeed => Kind::Slider(s.file.double(self.file_key().unwrap()) as f32),
|
||||
Opt::Fov => unit(s.fov, 30.0, 110.0),
|
||||
Opt::RenderDistance => unit(s.render_distance as f32, 2.0, 64.0),
|
||||
Opt::RenderDistance => unit(
|
||||
s.render_distance as f32,
|
||||
2.0,
|
||||
crate::options_file::MAX_RENDER_DISTANCE as f32,
|
||||
),
|
||||
Opt::SimulationDistance => unit(s.simulation_distance as f32, 5.0, 32.0),
|
||||
Opt::Sensitivity => Kind::Slider(s.sensitivity),
|
||||
Opt::WheelSensitivity => unit(s.mouse_wheel_sensitivity, 0.01, 10.0),
|
||||
@@ -647,7 +651,10 @@ impl Opt {
|
||||
crate::options_file::V::Double(f64::from(v)),
|
||||
),
|
||||
Opt::Fov => s.fov = step(30.0, 110.0),
|
||||
Opt::RenderDistance => s.render_distance = step(2.0, 64.0) as i32,
|
||||
Opt::RenderDistance => {
|
||||
s.render_distance =
|
||||
step(2.0, crate::options_file::MAX_RENDER_DISTANCE as f32) as i32
|
||||
}
|
||||
Opt::SimulationDistance => s.simulation_distance = step(5.0, 32.0) as i32,
|
||||
Opt::Sensitivity => s.sensitivity = v,
|
||||
Opt::WheelSensitivity => s.mouse_wheel_sensitivity = 0.01 + v * 9.99,
|
||||
@@ -1012,7 +1019,11 @@ pub enum Event {
|
||||
Language(String),
|
||||
/// The pack selection changed (reload resources).
|
||||
Packs(Vec<String>),
|
||||
/// Create World's Data Packs screen closed with a new selection.
|
||||
DataPacks(Vec<String>),
|
||||
OpenPackFolder,
|
||||
/// Data Packs' Open Pack Folder: create the folder if needed and open it.
|
||||
OpenDataPackFolder(std::path::PathBuf),
|
||||
OpenModFolder,
|
||||
/// The Shader Packs page opened: list `shaderpacks/` into `shader_packs`.
|
||||
ListShaderPacks,
|
||||
@@ -1078,6 +1089,13 @@ pub struct OptionsUi {
|
||||
pub pack_selection: Vec<String>,
|
||||
/// The open pack screen, made at its first frame.
|
||||
pub pack_screen: Option<crate::pack_screen::PackScreen>,
|
||||
/// The Packs page is Create World's Data Packs screen on this folder.
|
||||
data_pack_dir: Option<std::path::PathBuf>,
|
||||
/// The game's resources, for the built-in data pack's icon.
|
||||
data_pack_resources: std::path::PathBuf,
|
||||
/// The folder's entries when its packs were listed, and when they were
|
||||
/// last compared with it.
|
||||
data_pack_stamp: (Vec<(String, Option<std::time::SystemTime>)>, Instant),
|
||||
/// A reload failure, dismissed before returning to the current page.
|
||||
pub resource_error: Option<String>,
|
||||
/// When the pack screen's search box took the focus (its cursor blink).
|
||||
@@ -1112,6 +1130,9 @@ impl Default for OptionsUi {
|
||||
pack_infos: Vec::new(),
|
||||
pack_selection: Vec::new(),
|
||||
pack_screen: None,
|
||||
data_pack_dir: None,
|
||||
data_pack_resources: Default::default(),
|
||||
data_pack_stamp: (Vec::new(), Instant::now()),
|
||||
resource_error: None,
|
||||
pack_focus: Instant::now(),
|
||||
in_world: false,
|
||||
@@ -1219,9 +1240,48 @@ impl OptionsUi {
|
||||
pub fn open(&mut self, in_world: bool) {
|
||||
self.open_at(in_world, Page::Main);
|
||||
}
|
||||
/// Create World's Data Packs (`openDataPackSelectionScreen`): the Packs
|
||||
/// page on the new world's pack folder `dir`, `selected` chosen;
|
||||
/// `resources` holds the built-in pack's icon.
|
||||
pub fn open_data_packs(
|
||||
&mut self,
|
||||
dir: std::path::PathBuf,
|
||||
selected: Vec<String>,
|
||||
resources: std::path::PathBuf,
|
||||
) {
|
||||
self.open_at(false, Page::Packs);
|
||||
self.pack_infos = crate::datapack::screen_packs(&dir, &resources);
|
||||
self.data_pack_resources = resources;
|
||||
self.pack_selection = selected;
|
||||
self.data_pack_stamp = (crate::datapack::folder_stamp(&dir), Instant::now());
|
||||
self.data_pack_dir = Some(dir);
|
||||
}
|
||||
/// `PackSelectionScreen.tick`'s watcher: once a second, the Data Packs
|
||||
/// folder's packs are listed again if it changed, the selection kept
|
||||
/// (the search text and scroll start over).
|
||||
fn watch_data_packs(&mut self, atlas: &HudAtlas, size: (f32, f32)) {
|
||||
let Some(dir) = &self.data_pack_dir else {
|
||||
return;
|
||||
};
|
||||
if self.data_pack_stamp.1.elapsed().as_secs() < 1 {
|
||||
return;
|
||||
}
|
||||
let stamp = crate::datapack::folder_stamp(dir);
|
||||
let changed = stamp != self.data_pack_stamp.0;
|
||||
self.data_pack_stamp = (stamp, Instant::now());
|
||||
if !changed {
|
||||
return;
|
||||
}
|
||||
self.pack_infos = crate::datapack::screen_packs(dir, &self.data_pack_resources);
|
||||
if let Some(p) = self.pack_screen.take() {
|
||||
self.pack_selection = p.commit();
|
||||
self.packs(atlas, size).changed = p.changed;
|
||||
}
|
||||
}
|
||||
/// Open a page whose Done/Escape returns directly to the calling screen.
|
||||
pub fn open_at(&mut self, in_world: bool, page: Page) {
|
||||
self.stack.clear();
|
||||
self.data_pack_dir = None;
|
||||
self.push(page);
|
||||
self.in_world = in_world;
|
||||
self.selected_key = None;
|
||||
@@ -2273,6 +2333,7 @@ impl OptionsUi {
|
||||
return;
|
||||
}
|
||||
if self.page() == Page::Packs {
|
||||
self.watch_data_packs(atlas, (w, h));
|
||||
ui::menu_background(batch, atlas, 0.0, 0.0, w, h, self.in_world);
|
||||
let blink = self.pack_focus.elapsed().as_millis() as u64;
|
||||
let m = crate::screen_draw::Measure(&atlas.font);
|
||||
@@ -2568,7 +2629,10 @@ impl OptionsUi {
|
||||
self.pack_focus = Instant::now();
|
||||
}
|
||||
match action {
|
||||
crate::pack_screen::Action::OpenFolder => events.push(Event::OpenPackFolder),
|
||||
crate::pack_screen::Action::OpenFolder => events.push(match &self.data_pack_dir {
|
||||
Some(dir) => Event::OpenDataPackFolder(dir.clone()),
|
||||
None => Event::OpenPackFolder,
|
||||
}),
|
||||
crate::pack_screen::Action::Done => events.extend(self.back(s)),
|
||||
crate::pack_screen::Action::None => {}
|
||||
}
|
||||
@@ -2820,8 +2884,14 @@ impl OptionsUi {
|
||||
}
|
||||
None => {
|
||||
self.pack_focus = Instant::now();
|
||||
let title = if self.data_pack_dir.is_some() {
|
||||
"dataPack.title"
|
||||
} else {
|
||||
"resourcePack.title"
|
||||
};
|
||||
self.pack_screen = Some(crate::pack_screen::PackScreen::new(
|
||||
&m,
|
||||
crate::text::Text::translate(title, &[]),
|
||||
w,
|
||||
h,
|
||||
self.in_world,
|
||||
@@ -2853,13 +2923,18 @@ impl OptionsUi {
|
||||
}
|
||||
Page::Packs => {
|
||||
// PackSelectionScreen.onClose: PackSelectionModel.commit.
|
||||
let data = self.data_pack_dir.take();
|
||||
if let Some(p) = self.pack_screen.take()
|
||||
&& p.changed
|
||||
{
|
||||
let ids = p.commit();
|
||||
s.resource_packs = ids.clone();
|
||||
self.pack_selection = ids.clone();
|
||||
events.push(Event::Packs(ids));
|
||||
if data.is_some() {
|
||||
events.push(Event::DataPacks(ids));
|
||||
} else {
|
||||
s.resource_packs = ids.clone();
|
||||
self.pack_selection = ids.clone();
|
||||
events.push(Event::Packs(ids));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
@@ -3158,7 +3233,7 @@ mod tests {
|
||||
#[test]
|
||||
fn render_distance_slider_round_trips_through_extended_options_range() {
|
||||
let mut s = Settings::default();
|
||||
for (position, distance) in [(0.0, 2), (0.5, 33), (1.0, 64)] {
|
||||
for (position, distance) in [(0.0, 2), (0.5, 65), (1.0, 128)] {
|
||||
set_slider(Opt::RenderDistance, &mut s, position);
|
||||
assert_eq!(s.render_distance, distance);
|
||||
let reloaded = Settings::parse(&s.serialize());
|
||||
@@ -3169,10 +3244,10 @@ mod tests {
|
||||
);
|
||||
}
|
||||
let mut file = crate::options_file::OptionsFile::default();
|
||||
file.set_text("renderDistance", "64").unwrap();
|
||||
assert!(file.set_text("renderDistance", "65").is_err());
|
||||
file.set_text("renderDistance", "128").unwrap();
|
||||
assert!(file.set_text("renderDistance", "129").is_err());
|
||||
assert!(file.set_text("renderDistance", "1").is_err());
|
||||
assert_eq!(file.int("renderDistance"), 64);
|
||||
assert_eq!(file.int("renderDistance"), 128);
|
||||
}
|
||||
fn atlas() -> HudAtlas {
|
||||
HudAtlas::build(std::path::Path::new("/nonexistent/horizon-test-assets")).unwrap()
|
||||
|
||||
@@ -318,6 +318,8 @@ pub enum Action {
|
||||
/// The pack screen and its state.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PackScreen {
|
||||
/// `resourcePack.title`, or `dataPack.title` from Create World.
|
||||
pub title: Text,
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
pub in_world: bool,
|
||||
@@ -342,6 +344,7 @@ impl PackScreen {
|
||||
/// `PackSelectionScreen.init`: the lists filled, the search box focused.
|
||||
pub fn new(
|
||||
m: &Measure,
|
||||
title: Text,
|
||||
width: i32,
|
||||
height: i32,
|
||||
in_world: bool,
|
||||
@@ -360,6 +363,7 @@ impl PackScreen {
|
||||
.filter_map(|p| Some((icon_texture(&p.id), p.icon.clone()?)))
|
||||
.collect();
|
||||
let mut s = Self {
|
||||
title,
|
||||
width,
|
||||
height,
|
||||
in_world,
|
||||
@@ -382,7 +386,7 @@ impl PackScreen {
|
||||
}
|
||||
/// The header layout: title, drop info and search box, centred.
|
||||
fn header(&self, m: &Measure) -> [(i32, i32); 3] {
|
||||
let tw = m.width(&tr("resourcePack.title"));
|
||||
let tw = m.width(&self.title);
|
||||
let dw = m.width(&tr("pack.dropInfo"));
|
||||
let col = tw.max(dw).max(200);
|
||||
let frame = self.width.max(col);
|
||||
@@ -837,7 +841,7 @@ impl PackScreen {
|
||||
return out;
|
||||
}
|
||||
let [(tx, ty), (dx, dy), _] = self.header(m);
|
||||
d::string_widget(&mut out, m, &tr("resourcePack.title"), tx, ty, 0);
|
||||
d::string_widget(&mut out, m, &self.title, tx, ty, 0);
|
||||
d::string_widget(
|
||||
&mut out,
|
||||
m,
|
||||
@@ -1105,7 +1109,7 @@ mod icon_tests {
|
||||
pack("high_contrast", Some(contrast.clone())),
|
||||
pack("file/Iconless", None),
|
||||
];
|
||||
let screen = PackScreen::new(&m, 480, 320, false, packs, &[]);
|
||||
let screen = PackScreen::new(&m, tr("resourcePack.title"), 480, 320, false, packs, &[]);
|
||||
let mut draws = screen.draw(&m, (0, 0), 0);
|
||||
let mut patches = Vec::new();
|
||||
screen.bind_icons(&mut draws, &atlas, &mut patches);
|
||||
@@ -1152,7 +1156,8 @@ mod click_sound_tests {
|
||||
crate::hud::HudAtlas::build(std::path::Path::new("/nonexistent/horizon-test-assets"))
|
||||
.unwrap();
|
||||
let m = Measure(&atlas.font);
|
||||
let mut screen = PackScreen::new(&m, 480, 320, false, Vec::new(), &[]);
|
||||
let title = tr("resourcePack.title");
|
||||
let mut screen = PackScreen::new(&m, title, 480, 320, false, Vec::new(), &[]);
|
||||
assert!(!screen.button_at(&m, 0.0, 0.0));
|
||||
assert!(!screen.button_at(
|
||||
&m,
|
||||
|
||||
@@ -181,9 +181,15 @@ pub fn compatible(meta: &serde_json::Value) -> bool {
|
||||
/// `PackCompatibility.forVersion`: too old, too new, compatible, or unknown
|
||||
/// when the metadata names no format.
|
||||
pub fn compatibility(meta: &serde_json::Value) -> crate::pack_screen::Compat {
|
||||
compatibility_for(meta, RESOURCE_FORMAT)
|
||||
}
|
||||
/// `compatibility` against `current`, the resource or the data pack format.
|
||||
pub fn compatibility_for(
|
||||
meta: &serde_json::Value,
|
||||
current: (i64, i64),
|
||||
) -> crate::pack_screen::Compat {
|
||||
use crate::pack_screen::Compat;
|
||||
let pack = &meta["pack"];
|
||||
let current = RESOURCE_FORMAT;
|
||||
let range = if let (Some(min), Some(max)) = (
|
||||
format_of(&pack["min_format"]),
|
||||
format_of(&pack["max_format"]),
|
||||
@@ -275,6 +281,10 @@ fn read_meta(source: &Source) -> Option<serde_json::Value> {
|
||||
serde_json::from_slice(&bytes).ok()
|
||||
}
|
||||
fn pack(id: String, title: Text, source: Source) -> Option<Pack> {
|
||||
pack_for(id, title, source, RESOURCE_FORMAT)
|
||||
}
|
||||
/// A pack whose compatibility is against `format`.
|
||||
fn pack_for(id: String, title: Text, source: Source, format: (i64, i64)) -> Option<Pack> {
|
||||
let meta = read_meta(&source)?;
|
||||
let (raw_description, description_style) = component_root(&meta["pack"]["description"]);
|
||||
Some(Pack {
|
||||
@@ -284,7 +294,7 @@ fn pack(id: String, title: Text, source: Source) -> Option<Pack> {
|
||||
raw_description,
|
||||
description_style,
|
||||
compatible: compatible(&meta),
|
||||
compatibility: compatibility(&meta),
|
||||
compatibility: compatibility_for(&meta, format),
|
||||
overlays: overlays_of(&meta),
|
||||
filter: filter_of(&meta),
|
||||
source,
|
||||
@@ -305,7 +315,13 @@ pub fn discover(resources: &Path, packs_dir: &Path) -> Vec<Pack> {
|
||||
out.push(p);
|
||||
}
|
||||
}
|
||||
let mut user: Vec<Pack> = fs::read_dir(packs_dir)
|
||||
out.extend(folder(packs_dir, RESOURCE_FORMAT));
|
||||
out
|
||||
}
|
||||
/// The packs (folders and zips) in `dir`, by name, as `FolderRepositorySource`
|
||||
/// finds them; their compatibility is against `format`.
|
||||
pub fn folder(dir: &Path, format: (i64, i64)) -> Vec<Pack> {
|
||||
let mut user: Vec<Pack> = fs::read_dir(dir)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.flatten()
|
||||
@@ -319,12 +335,11 @@ pub fn discover(resources: &Path, packs_dir: &Path) -> Vec<Pack> {
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
pack(format!("file/{name}"), Text::literal(name), source)
|
||||
pack_for(format!("file/{name}"), Text::literal(name), source, format)
|
||||
})
|
||||
.collect();
|
||||
user.sort_by(|a, b| a.id.cmp(&b.id));
|
||||
out.extend(user);
|
||||
out
|
||||
user
|
||||
}
|
||||
/// A file a pack provides: its path (folder packs) and its name inside the pack.
|
||||
type Provided = (Option<PathBuf>, String);
|
||||
|
||||
@@ -58,6 +58,8 @@ pub enum Ticking {
|
||||
Bee { bee: u32, aggressive: bool },
|
||||
/// `GuardianAttackSoundInstance`.
|
||||
GuardianAttack { guardian: u32 },
|
||||
/// `ElytraOnPlayerSoundInstance`: ticks since it started.
|
||||
Elytra { time: i32 },
|
||||
}
|
||||
/// What a minecart's sound instances read about it.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -95,6 +97,12 @@ pub struct TickContext {
|
||||
pub mobs: Vec<MobSound>,
|
||||
/// `!tickRateManager().runsNormally()`.
|
||||
pub frozen: bool,
|
||||
/// The player's position.
|
||||
pub player_position: [f64; 3],
|
||||
/// `getDeltaMovement().lengthSqr()` of the player.
|
||||
pub player_speed_sqr: f64,
|
||||
/// `isFallFlying()` of the player.
|
||||
pub player_fall_flying: bool,
|
||||
}
|
||||
/// `AbstractSoundInstance` and its subclasses' fields.
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -390,6 +398,24 @@ impl SoundInstance {
|
||||
};
|
||||
s
|
||||
}
|
||||
/// `new ElytraOnPlayerSoundInstance(player)`: looping at volume 0.1 until
|
||||
/// its first tick.
|
||||
pub fn elytra(position: [f64; 3], random: LegacyRandom) -> Self {
|
||||
let mut s = Self::new(
|
||||
"minecraft:item.elytra.flying",
|
||||
PLAYERS,
|
||||
0.1,
|
||||
1.0,
|
||||
random,
|
||||
true,
|
||||
0,
|
||||
Attenuation::Linear,
|
||||
position,
|
||||
false,
|
||||
);
|
||||
s.ticking = Ticking::Elytra { time: 0 };
|
||||
s
|
||||
}
|
||||
pub fn tickable(&self) -> bool {
|
||||
self.ticking != Ticking::No
|
||||
}
|
||||
@@ -494,6 +520,37 @@ impl SoundInstance {
|
||||
self.volume = 0.0;
|
||||
}
|
||||
}
|
||||
Ticking::Elytra { time } => {
|
||||
*time += 1;
|
||||
let time = *time;
|
||||
if ctx.player_removed || (time > 20 && !ctx.player_fall_flying) {
|
||||
self.stop();
|
||||
return;
|
||||
}
|
||||
let [x, y, z] = ctx.player_position;
|
||||
self.position = [
|
||||
f64::from(x as f32),
|
||||
f64::from(y as f32),
|
||||
f64::from(z as f32),
|
||||
];
|
||||
let speed = ctx.player_speed_sqr as f32;
|
||||
self.volume = if speed >= 1.0e-7 {
|
||||
(speed / 4.0).clamp(0.0, 1.0)
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
// Silent for a second, then a second's fade in.
|
||||
if time < 20 {
|
||||
self.volume = 0.0;
|
||||
} else if time < 40 {
|
||||
self.volume *= (time - 20) as f32 / 20.0;
|
||||
}
|
||||
self.pitch = if self.volume > 0.8 {
|
||||
1.0 + (self.volume - 0.8)
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
}
|
||||
Ticking::GuardianAttack { guardian } => {
|
||||
let id = *guardian;
|
||||
// The client's guardian never has a target of its own, so
|
||||
|
||||
@@ -126,6 +126,7 @@ pub fn main() -> horizon_client::Result<()> {
|
||||
.collect();
|
||||
let mut screen = PackScreen::new(
|
||||
&m,
|
||||
Text::translate(c["title"].as_str().unwrap_or("resourcePack.title"), &[]),
|
||||
int(&c["width"]),
|
||||
int(&c["height"]),
|
||||
c["inWorld"] == true,
|
||||
|
||||
@@ -164,6 +164,8 @@ pub struct ArrowState {
|
||||
/// `AbstractArrow.firedFromWeapon`: the crossbow or bow it was shot from.
|
||||
pub fired_from_weapon: Option<crate::items::ItemStack>,
|
||||
pub no_physics: bool,
|
||||
/// `Arrow.ID_EFFECT_COLOR` as a client has it from its server (-1: none).
|
||||
pub effect_color: i32,
|
||||
/// `AbstractArrow.getPierceLevel`.
|
||||
pub pierce: u8,
|
||||
/// `piercingIgnoreEntityIds`: the entities a piercing arrow has gone
|
||||
@@ -214,6 +216,7 @@ impl ArrowState {
|
||||
pickup_item: crate::items::ItemStack::new("minecraft:arrow", 1),
|
||||
fired_from_weapon: None,
|
||||
no_physics: false,
|
||||
effect_color: -1,
|
||||
pierce: 0,
|
||||
pierced: Vec::new(),
|
||||
fire_ticks: 0,
|
||||
|
||||
@@ -425,7 +425,7 @@ pub const COMMANDS: &[Cmd] = &[
|
||||
&[BOX[0], BOX[1], BOX[2], BOX[3], BOX[4], BOX[5], p("block", Ty::Str, "block state")], mcp: true, local: false),
|
||||
cmd!("container", World, "container X Y Z [ITEM COUNT]", "list a container's items, or add items to it", "", ["horizon-ctl container 3 64 1", "horizon-ctl container 3 64 1 diamond 3"],
|
||||
&[BLOCK_XYZ[0], BLOCK_XYZ[1], BLOCK_XYZ[2], opt("item", Ty::Str, "item to add"), opt("count", Ty::Int, "how many")], mcp: true, local: false),
|
||||
cmd!("render-distance", World, "render-distance N", "chunks, 2..64", "", ["horizon-ctl render-distance 8"], &[p("chunks", Ty::Int, "render distance")], mcp: false, local: false),
|
||||
cmd!("render-distance", World, "render-distance N", "chunks, 2..128", "", ["horizon-ctl render-distance 8"], &[p("chunks", Ty::Int, "render distance")], mcp: false, local: false),
|
||||
cmd!("wait", World, "wait TICKS", "wait until TICKS game ticks have passed (20 per second)",
|
||||
"Keys and mouse buttons held with key ... down or mouse ... down stay held while it waits. Release them with key ... up, release or stop.",
|
||||
["horizon-ctl wait 40", "horizon-ctl key w down && horizon-ctl wait 40 && horizon-ctl key w up"],
|
||||
|
||||
@@ -27,7 +27,7 @@ impl Ingredient {
|
||||
pub fn of(items: Vec<String>) -> Self {
|
||||
Self(items.iter().cloned().collect(), items)
|
||||
}
|
||||
fn parse(v: &Value, items: &Items) -> Option<Self> {
|
||||
pub(crate) fn parse(v: &Value, items: &Items) -> Option<Self> {
|
||||
let mut list: Vec<String> = Vec::new();
|
||||
let mut add = |s: &str| {
|
||||
let more: Vec<String> = match s.strip_prefix('#') {
|
||||
@@ -43,14 +43,6 @@ impl Ingredient {
|
||||
match v {
|
||||
Value::String(s) => add(s),
|
||||
Value::Array(a) => a.iter().filter_map(Value::as_str).for_each(&mut add),
|
||||
Value::Object(o) => {
|
||||
if let Some(s) = o.get("item").and_then(Value::as_str) {
|
||||
add(s);
|
||||
}
|
||||
if let Some(t) = o.get("tag").and_then(Value::as_str) {
|
||||
add(&format!("#{t}"));
|
||||
}
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
(!list.is_empty()).then(|| Self::of(list))
|
||||
|
||||
@@ -174,6 +174,7 @@ mod server_mods;
|
||||
mod server_packs;
|
||||
mod server_travel;
|
||||
mod shader_packs;
|
||||
mod shelf;
|
||||
mod shield;
|
||||
mod shoulders;
|
||||
mod sign_edit;
|
||||
@@ -199,6 +200,7 @@ mod trading;
|
||||
mod trial_spawner;
|
||||
mod trial_visuals;
|
||||
mod trident;
|
||||
mod tutorial;
|
||||
mod tweaks;
|
||||
mod ui_control;
|
||||
mod vault;
|
||||
@@ -394,7 +396,7 @@ impl Options {
|
||||
match arg.as_str() {
|
||||
"--help" | "-h" => {
|
||||
println!(
|
||||
"Horizon — Minecraft Java Edition {} in Rust\n\n --resources PATH Prepared resources (horizon-prepare)\n --render-distance N Chunks, 2..64 (default 12)\n --world NAME World folder under saves/ (default \"New World\")\n --seed N World seed for new worlds (default 0)\n --width N, --height N Window size (default 1280 x 720)\n --creative Creative mode (default survival)\n --test-terrain Simple test terrain instead of vanilla generation\n --frames N Exit after N frames (testing)\n --world-frames N Exit after N frames drawn in the world (testing)\n --smoke-report PATH Write a JSON report on exit\n --benchmark SCENARIO Run a benchmark (standard, village, forest, ocean, mountain), write its report to benchmarks/ and quit\n --control Accept horizon-ctl commands on a local socket\n --control-socket PATH Control socket path (default $XDG_RUNTIME_DIR/horizon-control.sock)\n --server HOST[:PORT] Join a multiplayer server (offline mode) at start\n --quickPlaySingleplayer WORLD Open this world of saves/ at start, only if it exists (vanilla's)\n --quickPlayMultiplayer HOST[:PORT] The same as --server (vanilla's name)\n --name NAME Player name for multiplayer (default: generated once, kept in horizon-player.txt)\n --session-file PATH Signed-in account from the launcher (read once, then deleted)\n --dev-mod PATH A Horizon mod (.hmod or folder) to develop: always on, reloaded when rebuilt\n --in-frame Run single player's world in the frame, not on its server thread\n --pregen Add Horizon's /pregen command (docs/pregeneration.md)\n\nControls: mouse look, WASD move, Space jump, Left Shift sneak, Left Ctrl sprint,\nleft click break, right click place/use, 1-9/scroll hotbar, E inventory,\nQ drop, F3 debug, Esc pause.",
|
||||
"Horizon — Minecraft Java Edition {} in Rust\n\n --resources PATH Prepared resources (horizon-prepare)\n --render-distance N Chunks, 2..128 (default 12)\n --world NAME World folder under saves/ (default \"New World\")\n --seed N World seed for new worlds (default 0)\n --width N, --height N Window size (default 1280 x 720)\n --creative Creative mode (default survival)\n --test-terrain Simple test terrain instead of vanilla generation\n --frames N Exit after N frames (testing)\n --world-frames N Exit after N frames drawn in the world (testing)\n --smoke-report PATH Write a JSON report on exit\n --benchmark SCENARIO Run a benchmark (standard, village, forest, ocean, mountain), write its report to benchmarks/ and quit\n --control Accept horizon-ctl commands on a local socket\n --control-socket PATH Control socket path (default $XDG_RUNTIME_DIR/horizon-control.sock)\n --server HOST[:PORT] Join a multiplayer server (offline mode) at start\n --quickPlaySingleplayer WORLD Open this world of saves/ at start, only if it exists (vanilla's)\n --quickPlayMultiplayer HOST[:PORT] The same as --server (vanilla's name)\n --name NAME Player name for multiplayer (default: generated once, kept in horizon-player.txt)\n --session-file PATH Signed-in account from the launcher (read once, then deleted)\n --dev-mod PATH A Horizon mod (.hmod or folder) to develop: always on, reloaded when rebuilt\n --in-frame Run single player's world in the frame, not on its server thread\n --pregen Add Horizon's /pregen command (docs/pregeneration.md)\n\nControls: mouse look, WASD move, Space jump, Left Shift sneak, Left Ctrl sprint,\nleft click break, right click place/use, 1-9/scroll hotbar, E inventory,\nQ drop, F3 debug, Esc pause.",
|
||||
horizon_core::VANILLA_VERSION
|
||||
);
|
||||
return Ok(None);
|
||||
@@ -422,8 +424,9 @@ impl Options {
|
||||
"--render-distance" | "--radius" => {
|
||||
o.distance = value.parse()?;
|
||||
o.distance_override = Some(o.distance);
|
||||
if !(2..=64).contains(&o.distance) {
|
||||
return Err("render distance must be 2..64".into());
|
||||
let max = horizon_client::options_file::MAX_RENDER_DISTANCE;
|
||||
if !(2..=max).contains(&o.distance) {
|
||||
return Err(format!("render distance must be 2..{max}").into());
|
||||
}
|
||||
}
|
||||
"--seed" => {
|
||||
@@ -1511,6 +1514,9 @@ struct Game<'a> {
|
||||
cursor_icon: horizon_client::widgets::Cursor,
|
||||
captured: bool,
|
||||
focused: bool,
|
||||
/// When the window lost focus, while it is still without
|
||||
/// (`Minecraft.lastActiveTime`: pausing waits 500 ms).
|
||||
focus_lost: Option<Instant>,
|
||||
/// The keyboard's modifier state (`KeyEvent.modifiers`).
|
||||
modifiers: winit::keyboard::ModifiersState,
|
||||
/// The window's fullscreen request and reported state.
|
||||
@@ -1585,6 +1591,8 @@ struct Game<'a> {
|
||||
cart_sounds: HashSet<u32>,
|
||||
/// The minecart the riding sounds were started for (`LocalPlayer.startRiding`).
|
||||
riding_cart_sounds: Option<u32>,
|
||||
/// `LocalPlayer.wasFallFlying` for the elytra's flying sound.
|
||||
was_fall_flying: bool,
|
||||
/// Bees whose buzz is playing, and whether it is the aggressive one.
|
||||
bee_sounds: HashMap<u32, bool>,
|
||||
/// Guardians whose attack hum is playing.
|
||||
@@ -1686,6 +1694,8 @@ struct Game<'a> {
|
||||
attack_clicks: u32,
|
||||
hand_strength: horizon_client::attack_strength::AttackStrength,
|
||||
settings: Settings,
|
||||
tutorial: tutorial::Tutorial,
|
||||
tutorial_toasts: Vec<tutorial::HintToast>,
|
||||
/// Where the options screen returns: true = title, false = pause menu.
|
||||
options_from_title: bool,
|
||||
dragging: bool,
|
||||
@@ -2370,6 +2380,12 @@ impl Game<'_> {
|
||||
}
|
||||
/// Inventory.add: returns the stack that did not fit.
|
||||
fn give(&mut self, mut stack: ItemStack) -> Option<ItemStack> {
|
||||
let tutorial_item = matches!(
|
||||
self.tutorial.step,
|
||||
tutorial::Step::FindTree | tutorial::Step::PunchTree | tutorial::Step::CraftPlanks
|
||||
)
|
||||
.then(|| stack.clone());
|
||||
let mut added = false;
|
||||
let max = self
|
||||
.res
|
||||
.items()
|
||||
@@ -2394,7 +2410,9 @@ impl Game<'_> {
|
||||
let moved = (max - slot.count).min(stack.count);
|
||||
slot.count += moved;
|
||||
stack.count -= moved;
|
||||
added |= moved > 0;
|
||||
if stack.count == 0 {
|
||||
self.tutorial_get_item(tutorial_item.as_ref());
|
||||
return None;
|
||||
}
|
||||
}
|
||||
@@ -2407,11 +2425,16 @@ impl Game<'_> {
|
||||
..stack.clone()
|
||||
});
|
||||
stack.count -= moved;
|
||||
added |= moved > 0;
|
||||
if stack.count == 0 {
|
||||
self.tutorial_get_item(tutorial_item.as_ref());
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
if added {
|
||||
self.tutorial_get_item(tutorial_item.as_ref());
|
||||
}
|
||||
Some(stack)
|
||||
}
|
||||
/// The block's first up face (or any face) and its fixed tint, for block
|
||||
@@ -4350,6 +4373,14 @@ impl Game<'_> {
|
||||
self.swing_arm();
|
||||
return Ok(true);
|
||||
}
|
||||
if let Some(swings) = self.use_shelf(hit) {
|
||||
let used = held.unwrap_or_else(|| ItemStack::new("minecraft:air", 0));
|
||||
self.trigger_block("minecraft:item_used_on_block", hit.pos, &used);
|
||||
if swings {
|
||||
self.swing_arm();
|
||||
}
|
||||
return Ok(true);
|
||||
}
|
||||
if let Some(swings) = self.use_chiseled_bookshelf(hit, held.as_ref()) {
|
||||
if swings {
|
||||
self.swing_arm();
|
||||
@@ -5209,6 +5240,7 @@ impl Game<'_> {
|
||||
}
|
||||
self.tick_debug_keys()?;
|
||||
self.tick_debug_charts();
|
||||
self.tick_tutorial();
|
||||
if self.remote.is_some() {
|
||||
return self.tick_remote();
|
||||
}
|
||||
@@ -5789,6 +5821,7 @@ impl Game<'_> {
|
||||
.mouse
|
||||
.handle_accumulated_movement(time, active, grabbed, look)
|
||||
{
|
||||
self.tutorial.mouse(xo, yo);
|
||||
let mut rotation = Rotation {
|
||||
x_rot: self.pitch,
|
||||
y_rot: self.yaw,
|
||||
@@ -5831,6 +5864,26 @@ impl Game<'_> {
|
||||
) || self.book.is_some()
|
||||
|| self.win_screen_pauses())
|
||||
}
|
||||
/// `Minecraft.pauseIfInactive`: a window without focus for more than 500 ms
|
||||
/// pauses the game. An open screen (books, signs, the pack prompt, a server
|
||||
/// dialog) stays instead of pausing (`Gui.setPauseScreen`).
|
||||
fn pause_if_inactive(&mut self) {
|
||||
if self
|
||||
.focus_lost
|
||||
.is_some_and(|t| t.elapsed() > Duration::from_millis(500))
|
||||
&& self.control.is_none()
|
||||
&& self.screen == Screen::Playing
|
||||
&& self.player.is_some()
|
||||
&& !self.options.no_pause
|
||||
&& self.settings.file.bool("pauseOnLostFocus")
|
||||
&& self.book.is_none()
|
||||
&& self.sign_edit.is_none()
|
||||
&& self.server_packs.prompt.is_none()
|
||||
&& !self.dialog_open()
|
||||
{
|
||||
self.screen = Screen::Paused;
|
||||
}
|
||||
}
|
||||
/// `MouseHandler.grabMouse` / `releaseMouse`. Both put the pointer at
|
||||
/// the window's centre, so a screen opened from gameplay (inventory,
|
||||
/// chest, pause, chat, sign, book, death) starts with the cursor in the
|
||||
@@ -6100,6 +6153,7 @@ impl Game<'_> {
|
||||
self.frame_graph
|
||||
.draw(self.settings.horizon.overlay, &mut batch, &self.gui.atlas);
|
||||
self.draw_pad_cursor(&mut batch);
|
||||
self.resolve_inline_profiles(&batch.inline_profiles);
|
||||
batch
|
||||
}
|
||||
fn hud_layers(&mut self, size: [u32; 2]) -> HudBatch {
|
||||
@@ -6390,6 +6444,7 @@ impl Game<'_> {
|
||||
self.draw_saving_indicator(&mut batch);
|
||||
// ToastManager.extractRenderState: F1 hides the toasts too.
|
||||
self.draw_toasts(&mut batch, !show_hud);
|
||||
self.draw_tutorial_toasts(&mut batch, !show_hud);
|
||||
self.draw_pack_toasts(&mut batch, !show_hud);
|
||||
if self.server_packs.prompt.is_none() {
|
||||
self.remote_hud(&mut batch);
|
||||
@@ -6451,7 +6506,9 @@ impl Game<'_> {
|
||||
| BlockEntity::Skull(_)
|
||||
| BlockEntity::DaylightDetector
|
||||
| BlockEntity::Campfire(_)
|
||||
| BlockEntity::ChiseledBookshelf(_),
|
||||
| BlockEntity::ChiseledBookshelf(_)
|
||||
| BlockEntity::Brushable(_)
|
||||
| BlockEntity::Shelf(_),
|
||||
) => {}
|
||||
None => {
|
||||
v.extend((0..self.craft_size * self.craft_size).map(|i| 100 + i));
|
||||
@@ -6735,7 +6792,9 @@ impl Game<'_> {
|
||||
| BlockEntity::Skull(_)
|
||||
| BlockEntity::DaylightDetector
|
||||
| BlockEntity::Campfire(_)
|
||||
| BlockEntity::ChiseledBookshelf(_),
|
||||
| BlockEntity::ChiseledBookshelf(_)
|
||||
| BlockEntity::Brushable(_)
|
||||
| BlockEntity::Shelf(_),
|
||||
) => {}
|
||||
None if self.mount_active() => self.draw_mount_background(batch, ox, oy),
|
||||
None => {
|
||||
@@ -8212,6 +8271,8 @@ impl Game<'_> {
|
||||
// `CampfireRenderer`.
|
||||
self.mark_entity(out.len(), "");
|
||||
self.campfire_geometry(camera, &mut out);
|
||||
self.brushable_geometry(camera, &mut out);
|
||||
self.shelf_geometry(camera, &mut out);
|
||||
self.mark_entity(out.len(), "minecraft:ominous_item_spawner");
|
||||
self.ominous_item_spawner_geometry(camera, alpha, &mut out);
|
||||
// Mobs and their projectiles (a span each).
|
||||
@@ -8223,6 +8284,7 @@ impl Game<'_> {
|
||||
self.guest_model_geometry(camera, alpha, &mut out);
|
||||
self.mark_entity(out.len(), "minecraft:evoker_fangs");
|
||||
self.fangs_geometry(camera, alpha, &mut out);
|
||||
self.block_item_display_geometry(camera, alpha, &mut out);
|
||||
let mut see_through = std::mem::take(&mut self.see_through_vertices);
|
||||
see_through.clear();
|
||||
self.name_tag_geometry(camera, alpha, &mut out, &mut see_through);
|
||||
@@ -9145,6 +9207,15 @@ impl Game<'_> {
|
||||
return Ok(());
|
||||
}
|
||||
self.join_world_rest()?;
|
||||
// Create World's data packs, in the world before it opens.
|
||||
if let Some(dir) = create.and_then(|o| o.pack_dir.as_deref()) {
|
||||
let world = std::env::current_dir()?.join("saves").join(folder);
|
||||
let packs = create.map_or(&[][..], |o| &o.data_packs[..]);
|
||||
if let Err(e) = horizon_client::datapack::install(dir, &world, packs) {
|
||||
log::warn!("Horizon: data packs for {folder}: {e}");
|
||||
}
|
||||
let _ = std::fs::remove_dir_all(dir);
|
||||
}
|
||||
if self
|
||||
.options
|
||||
.execution_role
|
||||
@@ -9183,6 +9254,7 @@ impl Game<'_> {
|
||||
}
|
||||
|
||||
fn finish_open_world(&mut self, prepared: world_loading::PreparedWorld) -> Result<()> {
|
||||
self.reset_tutorial();
|
||||
let world_loading::PreparedWorld {
|
||||
save_root,
|
||||
level,
|
||||
@@ -9629,6 +9701,7 @@ impl Game<'_> {
|
||||
));
|
||||
if let Some(title) = &mut self.title {
|
||||
title.last_server = self.settings.file.string("lastServer");
|
||||
title.skip_multiplayer_warning = self.settings.file.bool("skipMultiplayerWarning");
|
||||
}
|
||||
self.capture(false);
|
||||
Ok(())
|
||||
@@ -12438,6 +12511,9 @@ impl Game<'_> {
|
||||
Screen::Playing => {
|
||||
// key.inventory while riding a tamed mount opens its inventory.
|
||||
if !self.open_ridden_mount_menu() {
|
||||
if self.tutorial.open_inventory() {
|
||||
self.persist_tutorial_step();
|
||||
}
|
||||
if self.creative {
|
||||
self.screen = Screen::Creative;
|
||||
self.creative_screen.reopen();
|
||||
@@ -13022,24 +13098,12 @@ impl ApplicationHandler for Game<'_> {
|
||||
}
|
||||
WindowEvent::Focused(focused) => {
|
||||
self.focused = focused;
|
||||
self.focus_lost = (!focused).then(Instant::now);
|
||||
if !focused && self.control.is_none() {
|
||||
self.keys.clear();
|
||||
self.left = false;
|
||||
self.right = false;
|
||||
self.capture(false);
|
||||
// Gui.setPauseScreen: an open screen (books, signs, the
|
||||
// pack prompt, a server dialog) stays instead of pausing.
|
||||
if self.screen == Screen::Playing
|
||||
&& self.player.is_some()
|
||||
&& !self.options.no_pause
|
||||
&& self.settings.file.bool("pauseOnLostFocus")
|
||||
&& self.book.is_none()
|
||||
&& self.sign_edit.is_none()
|
||||
&& self.server_packs.prompt.is_none()
|
||||
&& !self.dialog_open()
|
||||
{
|
||||
self.screen = Screen::Paused;
|
||||
}
|
||||
}
|
||||
}
|
||||
WindowEvent::CursorMoved { position, .. } => {
|
||||
@@ -13139,6 +13203,9 @@ impl ApplicationHandler for Game<'_> {
|
||||
if self.screen == Screen::Chat && d != 0.0 {
|
||||
self.chat_scroll(d);
|
||||
}
|
||||
if self.screen == Screen::CommandBlock && d != 0.0 {
|
||||
self.command_block_scroll(d);
|
||||
}
|
||||
if self.screen == Screen::Options && d != 0.0 {
|
||||
self.options_scroll(d);
|
||||
}
|
||||
@@ -13267,6 +13334,13 @@ impl ApplicationHandler for Game<'_> {
|
||||
Action::Connect { address } => {
|
||||
self.connect_server(&address, &self.options.player_name())?
|
||||
}
|
||||
Action::DataPacks { dir, selected } => {
|
||||
self.keys.clear();
|
||||
self.screen = Screen::Options;
|
||||
self.options_from_title = true;
|
||||
let resources = self.vanilla_root();
|
||||
self.ui.options.open_data_packs(dir, selected, resources);
|
||||
}
|
||||
Action::None => {}
|
||||
}
|
||||
}
|
||||
@@ -13906,6 +13980,7 @@ impl ApplicationHandler for Game<'_> {
|
||||
return;
|
||||
}
|
||||
self.poll_pads(event_loop);
|
||||
self.pause_if_inactive();
|
||||
if self.options.frames.is_some() && self.started.elapsed() > self.options.timeout {
|
||||
self.error = Some("frame-limited run timed out".into());
|
||||
event_loop.exit();
|
||||
@@ -13984,6 +14059,9 @@ impl<'a> Game<'a> {
|
||||
.map(Ok)
|
||||
.unwrap_or_else(|| GameCpuResources::load(&root))?;
|
||||
let world = World::new(res.facts().air, 0);
|
||||
let tutorial = tutorial::Tutorial::new(tutorial::Step::from_option(
|
||||
settings.file.int("tutorialStep"),
|
||||
));
|
||||
let creative = options.creative;
|
||||
let inventory = vec![None; 36];
|
||||
let now = Instant::now();
|
||||
@@ -14063,6 +14141,7 @@ impl<'a> Game<'a> {
|
||||
cursor_icon: horizon_client::widgets::Cursor::Arrow,
|
||||
captured: false,
|
||||
focused: true,
|
||||
focus_lost: None,
|
||||
modifiers: winit::keyboard::ModifiersState::empty(),
|
||||
fullscreen: horizon_client::window::FullscreenState::new(false),
|
||||
mouse: MouseLook::default(),
|
||||
@@ -14113,6 +14192,7 @@ impl<'a> Game<'a> {
|
||||
sound_paused: false,
|
||||
cart_sounds: HashSet::new(),
|
||||
riding_cart_sounds: None,
|
||||
was_fall_flying: false,
|
||||
bee_sounds: HashMap::new(),
|
||||
guardian_sounds: HashSet::new(),
|
||||
dropped: Vec::new(),
|
||||
@@ -14172,6 +14252,8 @@ impl<'a> Game<'a> {
|
||||
attack_clicks: 0,
|
||||
hand_strength: Default::default(),
|
||||
settings,
|
||||
tutorial,
|
||||
tutorial_toasts: Vec::new(),
|
||||
options_from_title: false,
|
||||
dragging: false,
|
||||
avatar: Default::default(),
|
||||
@@ -15267,6 +15349,7 @@ fn start_game<'a>(
|
||||
&game.options.resources,
|
||||
);
|
||||
title.last_server = game.settings.file.string("lastServer");
|
||||
title.skip_multiplayer_warning = game.settings.file.bool("skipMultiplayerWarning");
|
||||
game.title = Some(title);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1549,7 +1549,7 @@ impl Game<'_> {
|
||||
.iter()
|
||||
.filter_map(|t| t.slot)
|
||||
.chain(packs)
|
||||
.fold(0, |used, slot| used | 1 << slot)
|
||||
.fold(self.tutorial_toast_slots(), |used, slot| used | 1 << slot)
|
||||
}
|
||||
/// The `AdvancementToast` or `RecipeToast` at `index` in its slot at `y`.
|
||||
fn draw_toast(&mut self, batch: &mut HudBatch, index: usize, y: f32, multiplier: f64) {
|
||||
|
||||
@@ -611,6 +611,9 @@ impl Game<'_> {
|
||||
json!({ "mode": "multiplayer", "disconnected": reason })
|
||||
}
|
||||
Mode::Multiplayer(_) => json!({ "mode": "multiplayer" }),
|
||||
Mode::MultiplayerWarning { check } => {
|
||||
json!({ "mode": "multiplayer-warning", "check": check })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -801,6 +801,7 @@ impl Game<'_> {
|
||||
self.screen = super::Screen::Inventory;
|
||||
self.capture(false);
|
||||
self.right = false;
|
||||
self.swing_arm();
|
||||
return true;
|
||||
}
|
||||
if sneaking
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
//! Brushing suspicious sand and gravel: `BrushItem.onUseTick` and
|
||||
//! `BrushableBlockEntity`. The block's loot table stays in its saved block
|
||||
//! entity (like a decorated pot's) until the first counted stroke rolls it.
|
||||
use super::{Game, Hit, decorated_pot, spill, view_vector};
|
||||
use super::{Game, Hit, decorated_pot, view_vector};
|
||||
use horizon_client::{
|
||||
block_entity_render::vertex_light,
|
||||
containers::BlockEntity,
|
||||
mesher::Vertex,
|
||||
particles::{ParticleOptions, ParticleType},
|
||||
save,
|
||||
};
|
||||
@@ -114,6 +117,20 @@ impl Game<'_> {
|
||||
let time = self.world.game_time;
|
||||
let mut brush = self.brushing.remove(&pos).unwrap_or_default();
|
||||
brush.hit_face.get_or_insert(face);
|
||||
let direction = match brush.hit_face.unwrap() {
|
||||
[0, -1, 0] => 0,
|
||||
[0, 1, 0] => 1,
|
||||
[0, 0, -1] => 2,
|
||||
[0, 0, 1] => 3,
|
||||
[-1, 0, 0] => 4,
|
||||
[1, 0, 0] => 5,
|
||||
_ => return false,
|
||||
};
|
||||
if let Some(BlockEntity::Brushable(b)) =
|
||||
self.world.block_entities.get_mut(&(pos[0], pos[1], pos[2]))
|
||||
{
|
||||
b.hit_direction = Some(direction);
|
||||
}
|
||||
brush.resets_at = time + 40;
|
||||
if time < brush.cooldown_ends_at {
|
||||
self.brushing.insert(pos, brush);
|
||||
@@ -139,13 +156,19 @@ impl Game<'_> {
|
||||
/// `BrushableBlockEntity.unpackLootTable`: the table's roll becomes the
|
||||
/// block entity's item.
|
||||
fn unpack_brush_loot(&mut self, pos: [i32; 3]) {
|
||||
let Some(mut tag) = spill::saved_entity(&self.world, pos) else {
|
||||
let key = (pos[0], pos[1], pos[2]);
|
||||
let Some(BlockEntity::Brushable(b)) = self.world.block_entities.get(&key) else {
|
||||
return;
|
||||
};
|
||||
let Some(horizon_world::nbt::Tag::String(table)) = tag.get("LootTable") else {
|
||||
let Some(horizon_world::nbt::Tag::String(table)) = &b.loot_table else {
|
||||
return;
|
||||
};
|
||||
let table = table.to_string_lossy();
|
||||
let mut tag = save::encode_block_entity(
|
||||
key,
|
||||
&BlockEntity::Brushable(b.clone()),
|
||||
"minecraft:brushable_block",
|
||||
);
|
||||
self.trigger_loot_table(&table);
|
||||
let loot = horizon_client::structure_blocks::loot_data(&self.options.resources);
|
||||
decorated_pot::unpack_loot(
|
||||
@@ -155,7 +178,12 @@ impl Game<'_> {
|
||||
&mut self.rand_sequences,
|
||||
self.level.seed,
|
||||
);
|
||||
spill::put_saved_entity(&mut self.world, pos, tag);
|
||||
if let Some(BlockEntity::Brushable(b)) = self.world.block_entities.get_mut(&key) {
|
||||
b.loot_table = tag.get("LootTable").cloned();
|
||||
b.loot_seed = tag.get("LootTableSeed").cloned();
|
||||
b.item = tag.get("item").and_then(save::item_from_nbt);
|
||||
}
|
||||
self.world.modified.insert((pos[0] >> 4, pos[2] >> 4));
|
||||
}
|
||||
|
||||
/// `BrushableBlockEntity.brushingCompleted`: the item drops out of the
|
||||
@@ -168,8 +196,10 @@ impl Game<'_> {
|
||||
_ => "minecraft:air",
|
||||
};
|
||||
// dropContent: the item at the middle of the block beside that face.
|
||||
let item = spill::saved_entity(&self.world, pos)
|
||||
.and_then(|tag| tag.get("item").and_then(save::item_from_nbt));
|
||||
let item = match self.world.block_entities.get(&(pos[0], pos[1], pos[2])) {
|
||||
Some(BlockEntity::Brushable(b)) => b.item.clone(),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(mut item) = item {
|
||||
let face = face.unwrap_or([0, 1, 0]);
|
||||
let take = 10 + self.world.random.next_int_bound(21).unwrap_or(0);
|
||||
@@ -223,12 +253,70 @@ impl Game<'_> {
|
||||
if brush.count != 0 {
|
||||
self.brushing.insert(pos, brush);
|
||||
self.schedule_brushable_tick(pos);
|
||||
} else if let Some(BlockEntity::Brushable(b)) =
|
||||
self.world.block_entities.get_mut(&(pos[0], pos[1], pos[2]))
|
||||
{
|
||||
b.hit_direction = None;
|
||||
}
|
||||
if before != after {
|
||||
self.set_dusted(pos, after);
|
||||
}
|
||||
}
|
||||
|
||||
/// `BrushableBlockRenderer`: reveal the item from the first brushed face.
|
||||
pub(super) fn brushable_geometry(&self, camera: glam::DVec3, out: &mut Vec<Vertex>) {
|
||||
use horizon_assets::item_model::DisplayContext;
|
||||
for (key, entity) in &self.world.block_entities {
|
||||
let BlockEntity::Brushable(b) = entity else {
|
||||
continue;
|
||||
};
|
||||
let (Some(item), Some(face)) = (&b.item, b.hit_direction) else {
|
||||
continue;
|
||||
};
|
||||
let at = glam::DVec3::new(f64::from(key.0), f64::from(key.1), f64::from(key.2));
|
||||
if at.distance_squared(camera) >= 64.0 * 64.0 {
|
||||
continue;
|
||||
}
|
||||
let state = self.world.block(key.0, key.1, key.2);
|
||||
let progress = self
|
||||
.pack
|
||||
.assets
|
||||
.registry
|
||||
.state(state)
|
||||
.and_then(|s| s.properties.get("dusted"))
|
||||
.and_then(|s| s.parse::<u8>().ok())
|
||||
.unwrap_or(0);
|
||||
if progress == 0 {
|
||||
continue;
|
||||
}
|
||||
let offset = f32::from(progress) * 0.075;
|
||||
let (translation, adjacent, east_west) = match face {
|
||||
0 => (glam::Vec3::new(0.5, -0.23 - offset, 0.5), [0, -1, 0], false),
|
||||
1 => (glam::Vec3::new(0.5, 0.25 + offset, 0.5), [0, 1, 0], false),
|
||||
2 => (glam::Vec3::new(0.5, 0.0, 0.25 - offset), [0, 0, -1], false),
|
||||
3 => (glam::Vec3::new(0.5, 0.0, 0.73 + offset), [0, 0, 1], false),
|
||||
4 => (glam::Vec3::new(0.25 - offset, 0.0, 0.5), [-1, 0, 0], true),
|
||||
5 => (glam::Vec3::new(0.73 + offset, 0.0, 0.5), [1, 0, 0], true),
|
||||
_ => continue,
|
||||
};
|
||||
let item = self.resolve_item(item, DisplayContext::Fixed, None, 0);
|
||||
if item.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let pose = glam::Mat4::from_translation(
|
||||
(at - camera).as_vec3() + glam::Vec3::new(0.0, 0.5, 0.0) + translation,
|
||||
) * glam::Mat4::from_rotation_y(
|
||||
(86.0_f32 + if east_west { 90.0 } else { 0.0 }).to_radians(),
|
||||
) * glam::Mat4::from_scale(glam::Vec3::splat(0.5));
|
||||
let light = self.world.light(
|
||||
key.0 + adjacent[0],
|
||||
key.1 + adjacent[1],
|
||||
key.2 + adjacent[2],
|
||||
);
|
||||
self.submit_item_world(&item, pose, vertex_light(light), out);
|
||||
}
|
||||
}
|
||||
|
||||
/// Level event 3008 (`LevelEventHandler`): the finished block's sound
|
||||
/// and break particles.
|
||||
pub(super) fn brush_complete_event(&mut self, pos: [i32; 3], data: i32) {
|
||||
|
||||
@@ -20,7 +20,7 @@ use std::{
|
||||
|
||||
/// The tree the chat parses and completes with: a server's, once it has
|
||||
/// sent one (ClientPacketListener.commands), else the game's own.
|
||||
fn chat_tree<'a>(
|
||||
pub(super) fn chat_tree<'a>(
|
||||
remote: &'a Option<super::remote::Remote>,
|
||||
mods: &'a super::mods::ModsState,
|
||||
) -> &'a horizon_client::command::Dispatcher {
|
||||
|
||||
@@ -4,18 +4,21 @@
|
||||
//! `CommandBlockEditScreen` with what `handleSetCommandBlock` does with it.
|
||||
use super::{Game, Screen, commands::execute::Source, lan::LanHost};
|
||||
use horizon_client::{
|
||||
chat::{ChatScreen, Commands},
|
||||
command_block::{self, CommandBlock, Mode},
|
||||
containers::BlockEntity,
|
||||
hud::{HudBatch, rgba},
|
||||
screen_draw::Measure,
|
||||
text::{self, Text},
|
||||
ui::{self, EditBox},
|
||||
world::World,
|
||||
};
|
||||
use horizon_core::position::BlockPos;
|
||||
use horizon_protocol::packets::play::{self, Serverbound as S};
|
||||
use horizon_protocol::packets::play::{self, Clientbound as C, Serverbound as S};
|
||||
use horizon_world::{
|
||||
chunk::BlockState,
|
||||
level::flags,
|
||||
nbt::Tag,
|
||||
registry::{BlockRegistry, BlockStateId},
|
||||
};
|
||||
use winit::{
|
||||
@@ -28,7 +31,10 @@ type Pos = (i32, i32, i32);
|
||||
/// The open `CommandBlockEditScreen`.
|
||||
pub(super) struct CommandBlockScreen {
|
||||
pos: Pos,
|
||||
command: EditBox,
|
||||
/// The command box with its `CommandSuggestions`.
|
||||
command: ChatScreen,
|
||||
/// When the command box was focused, for its cursor's blink.
|
||||
opened: std::time::Instant,
|
||||
previous: EditBox,
|
||||
track_output: bool,
|
||||
mode: Mode,
|
||||
@@ -93,6 +99,9 @@ impl CommandBlockScreen {
|
||||
Button::Cancel => tr("gui.cancel"),
|
||||
}
|
||||
}
|
||||
pub(super) fn pos(&self) -> Pos {
|
||||
self.pos
|
||||
}
|
||||
/// `updatePreviousOutput`.
|
||||
fn show_previous(&mut self, block: &CommandBlock) {
|
||||
let shown = if self.track_output {
|
||||
@@ -446,10 +455,14 @@ impl Game<'_> {
|
||||
let Some(state) = self.block_state_at(pos) else {
|
||||
return;
|
||||
};
|
||||
let mut command = EditBox::new(0.0, 50.0, 300.0, 20.0);
|
||||
command.max_length = 32500;
|
||||
command.set_value(&block.command);
|
||||
command.focused = true;
|
||||
let (w, h) = self.gui_size();
|
||||
let m = Measure(&self.gui.atlas.font);
|
||||
let c = Commands {
|
||||
dispatcher: super::chat::chat_tree(&self.remote, &self.mods),
|
||||
registries: &self.ui.registries,
|
||||
};
|
||||
let mut command = ChatScreen::command_block(&m, &c, &block.command, w as i32, h as i32);
|
||||
command.auto_suggestions = self.settings.auto_suggestions;
|
||||
let mut previous = EditBox::new(0.0, 135.0, 276.0, 20.0);
|
||||
previous.max_length = 32500;
|
||||
previous.text_color = UNEDITABLE;
|
||||
@@ -457,6 +470,7 @@ impl Game<'_> {
|
||||
let mut screen = CommandBlockScreen {
|
||||
pos,
|
||||
command,
|
||||
opened: std::time::Instant::now(),
|
||||
previous,
|
||||
track_output: block.track_output,
|
||||
mode: Mode::of_block(&state.name).unwrap_or(Mode::Redstone),
|
||||
@@ -485,7 +499,14 @@ impl Game<'_> {
|
||||
let atlas = &self.gui.atlas;
|
||||
let (w, h) = (batch.width, batch.height);
|
||||
let mid = (w / 2.0).floor();
|
||||
ui::menu_background(batch, atlas, 0.0, 0.0, w, h, true);
|
||||
// Screen.extractBackground of an isInGameUi screen: the dimmed world,
|
||||
// with no blur and no menu background.
|
||||
batch.fill_gradient(
|
||||
atlas,
|
||||
(0.0, 0.0, w, h),
|
||||
rgba(0x10, 0x10, 0x10, 0xC0),
|
||||
rgba(0x10, 0x10, 0x10, 0xD0),
|
||||
);
|
||||
let title = Text::translate("advMode.setCommand", &[]);
|
||||
let tw = atlas.font.width_text(&title);
|
||||
batch.text_styled(
|
||||
@@ -499,8 +520,6 @@ impl Game<'_> {
|
||||
let grey = rgba(160, 160, 160, 255);
|
||||
let label = Text::translate("advMode.command", &[]);
|
||||
batch.text_styled(atlas, &label, mid - 149.0, 40.0, grey, true);
|
||||
s.command.x = mid - 150.0;
|
||||
s.command.draw(batch, atlas, None);
|
||||
if !s.previous.value.is_empty() {
|
||||
let label = Text::translate("advMode.previousOutput", &[]);
|
||||
batch.text_styled(atlas, &label, mid - 149.0, 125.0, grey, true);
|
||||
@@ -511,12 +530,65 @@ impl Game<'_> {
|
||||
let hover = ui::hovered(self.cursor, x, y, bw, 20.0);
|
||||
ui::button(batch, atlas, x, y, bw, 20.0, &s.label(b), true, hover);
|
||||
}
|
||||
// The command box, then its suggestions over the widgets.
|
||||
let m = Measure(&atlas.font);
|
||||
let c = Commands {
|
||||
dispatcher: super::chat::chat_tree(&self.remote, &self.mods),
|
||||
registries: &self.ui.registries,
|
||||
};
|
||||
s.command.resize(&m, &c, w as i32, h as i32);
|
||||
let mouse = (self.cursor.0.floor() as i32, self.cursor.1.floor() as i32);
|
||||
let blink = s.opened.elapsed().as_millis() as u64;
|
||||
let draws = s.command.draw(&m, mouse, blink, 0);
|
||||
horizon_client::screen_draw::paint(batch, atlas, &draws);
|
||||
self.command_block_screen = Some(s);
|
||||
}
|
||||
/// The wheel over the suggestion list (`CommandSuggestions.mouseScrolled`).
|
||||
pub(super) fn command_block_scroll(&mut self, amount: f32) {
|
||||
let mouse = (self.cursor.0.floor() as i32, self.cursor.1.floor() as i32);
|
||||
if let Some(s) = self.command_block_screen.as_mut() {
|
||||
s.command.scroll(mouse, f64::from(amount));
|
||||
}
|
||||
}
|
||||
/// The command box's completion request for an `ask_server` node.
|
||||
pub(super) fn command_block_suggestion_request(&mut self) -> Option<(i32, String)> {
|
||||
self.command_block_screen.as_mut()?.command.take_request()
|
||||
}
|
||||
/// The server's completions for the command box's `ask_server` request.
|
||||
pub(super) fn command_block_server_suggestions(
|
||||
&mut self,
|
||||
id: i32,
|
||||
range: (usize, usize),
|
||||
list: Vec<(String, Option<String>)>,
|
||||
) {
|
||||
let Some(mut s) = self.command_block_screen.take() else {
|
||||
return;
|
||||
};
|
||||
let m = Measure(&self.gui.atlas.font);
|
||||
let c = Commands {
|
||||
dispatcher: super::chat::chat_tree(&self.remote, &self.mods),
|
||||
registries: &self.ui.registries,
|
||||
};
|
||||
s.command.server_suggestions(&m, &c, id, range, list);
|
||||
self.command_block_screen = Some(s);
|
||||
}
|
||||
pub(super) fn command_block_click(&mut self) {
|
||||
let Some(mut s) = self.command_block_screen.take() else {
|
||||
return;
|
||||
};
|
||||
// CommandSuggestions.mouseClicked first, then the command box.
|
||||
{
|
||||
let m = Measure(&self.gui.atlas.font);
|
||||
let c = Commands {
|
||||
dispatcher: super::chat::chat_tree(&self.remote, &self.mods),
|
||||
registries: &self.ui.registries,
|
||||
};
|
||||
let (x, y) = (f64::from(self.cursor.0), f64::from(self.cursor.1));
|
||||
if s.command.click(&m, &c, x, y) {
|
||||
self.command_block_screen = Some(s);
|
||||
return;
|
||||
}
|
||||
}
|
||||
let size = self
|
||||
.renderer
|
||||
.as_ref()
|
||||
@@ -552,8 +624,8 @@ impl Game<'_> {
|
||||
}
|
||||
self.command_block_screen = Some(s);
|
||||
}
|
||||
/// `AbstractCommandBlockEditScreen.keyPressed`: Enter is Done, Escape
|
||||
/// cancels, the rest edits the command.
|
||||
/// `AbstractCommandBlockEditScreen.keyPressed`: the suggestions first,
|
||||
/// then Enter is Done, Escape cancels, the rest edits the command.
|
||||
pub(super) fn command_block_key(&mut self, event: &KeyEvent) {
|
||||
if event.state != ElementState::Pressed {
|
||||
return;
|
||||
@@ -561,27 +633,56 @@ impl Game<'_> {
|
||||
let PhysicalKey::Code(key) = event.physical_key else {
|
||||
return;
|
||||
};
|
||||
let shortcut = self.edit_shortcut(event);
|
||||
self.command_block_key_code(key, shortcut, event.text.as_deref());
|
||||
}
|
||||
/// A key pressed on the command block screen, with the edit shortcut it
|
||||
/// makes and the text it types.
|
||||
pub(super) fn command_block_key_code(
|
||||
&mut self,
|
||||
key: KeyCode,
|
||||
shortcut: Option<horizon_client::clipboard::EditShortcut>,
|
||||
text: Option<&str>,
|
||||
) {
|
||||
let ctrl = self.keys.contains(&KeyCode::ControlLeft);
|
||||
let shift = self.keys.contains(&KeyCode::ShiftLeft);
|
||||
let Some(mut s) = self.command_block_screen.take() else {
|
||||
return;
|
||||
};
|
||||
let m = Measure(&self.gui.atlas.font);
|
||||
let c = Commands {
|
||||
dispatcher: super::chat::chat_tree(&self.remote, &self.mods),
|
||||
registries: &self.ui.registries,
|
||||
};
|
||||
if s.command.suggestions_key(&m, &c, key, shift) {
|
||||
self.command_block_screen = Some(s);
|
||||
return;
|
||||
}
|
||||
let typed = match key {
|
||||
KeyCode::Escape | KeyCode::Enter | KeyCode::NumpadEnter => false,
|
||||
_ => {
|
||||
if let Some(shortcut) = shortcut {
|
||||
s.command.shortcut(&m, &c, shortcut, &mut self.clipboard);
|
||||
false
|
||||
} else {
|
||||
!s.command.field_key(&m, &c, key, shift, ctrl) && !ctrl
|
||||
}
|
||||
}
|
||||
};
|
||||
self.command_block_screen = Some(s);
|
||||
if typed {
|
||||
self.command_block_type(text.unwrap_or(""));
|
||||
}
|
||||
match key {
|
||||
KeyCode::Escape => return self.close_command_block(),
|
||||
KeyCode::Escape => self.close_command_block(),
|
||||
KeyCode::Enter | KeyCode::NumpadEnter => {
|
||||
if let Some(s) = self.command_block_screen.take() {
|
||||
self.set_command_block(&s);
|
||||
}
|
||||
return self.close_command_block();
|
||||
self.close_command_block();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let ctrl = self.keys.contains(&KeyCode::ControlLeft);
|
||||
let shift = self.keys.contains(&KeyCode::ShiftLeft);
|
||||
let shortcut = self.edit_shortcut(event);
|
||||
let Some(s) = self.command_block_screen.as_mut() else {
|
||||
return;
|
||||
};
|
||||
if let Some(shortcut) = shortcut {
|
||||
s.command.shortcut(shortcut, &mut self.clipboard);
|
||||
} else if !s.command.key(key, ctrl, shift) && !ctrl {
|
||||
self.command_block_type(event.text.as_deref().unwrap_or(""));
|
||||
}
|
||||
}
|
||||
/// Characters typed into the command box (`StringUtil.isAllowedChatCharacter`).
|
||||
pub(super) fn command_block_type(&mut self, typed: &str) {
|
||||
@@ -589,11 +690,19 @@ impl Game<'_> {
|
||||
.chars()
|
||||
.filter(|&c| c != '\u{a7}' && c >= ' ' && c != '\u{7f}')
|
||||
.collect();
|
||||
if let Some(s) = self.command_block_screen.as_mut()
|
||||
&& !allowed.is_empty()
|
||||
{
|
||||
s.command.insert_text(&allowed);
|
||||
if allowed.is_empty() {
|
||||
return;
|
||||
}
|
||||
let Some(mut s) = self.command_block_screen.take() else {
|
||||
return;
|
||||
};
|
||||
let m = Measure(&self.gui.atlas.font);
|
||||
let c = Commands {
|
||||
dispatcher: super::chat::chat_tree(&self.remote, &self.mods),
|
||||
registries: &self.ui.registries,
|
||||
};
|
||||
s.command.type_text(&m, &c, &allowed);
|
||||
self.command_block_screen = Some(s);
|
||||
}
|
||||
/// The edit screen's Done: a server's client sends the packet, the
|
||||
/// window's own world applies it (`handleSetCommandBlock`).
|
||||
@@ -604,7 +713,7 @@ impl Game<'_> {
|
||||
}
|
||||
let edit = CommandBlockEdit {
|
||||
pos: s.pos,
|
||||
command: s.command.value.clone(),
|
||||
command: s.command.value().to_owned(),
|
||||
track_output: s.track_output,
|
||||
mode: s.mode,
|
||||
conditional: s.conditional,
|
||||
@@ -653,6 +762,46 @@ impl Game<'_> {
|
||||
}
|
||||
});
|
||||
}
|
||||
/// `ServerPlayer.openCommandBlock`: the command block's data
|
||||
/// (`saveCustomOnly`) goes to the guest whose use opened it, as the
|
||||
/// chunk's update tag leaves it out and the edit screen shows it.
|
||||
pub(super) fn lan_open_command_block(&mut self, host: &mut LanHost, conn: u32, pos: Pos) {
|
||||
if !host
|
||||
.hub
|
||||
.guest(conn)
|
||||
.is_some_and(|g| g.creative() && g.permission >= 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
let Some(block) = self.command_block(pos) else {
|
||||
return;
|
||||
};
|
||||
let tag = Tag::compound(block.save());
|
||||
let state = self.world.block(pos.0, pos.1, pos.2).0;
|
||||
let Some(kind) = host.hub.encoder.entity_type(state) else {
|
||||
return;
|
||||
};
|
||||
host.hub.send_to(
|
||||
conn,
|
||||
&C::BlockEntityData(play::BlockEntityData {
|
||||
pos: [pos.0, pos.1, pos.2],
|
||||
kind,
|
||||
tag,
|
||||
}),
|
||||
);
|
||||
}
|
||||
/// `ClientPacketListener.handleBlockEntityData`: new data for the command
|
||||
/// block the edit screen shows refills it (`CommandBlockEditScreen.updateGui`).
|
||||
pub(super) fn command_block_data_arrived(&mut self, pos: Pos) {
|
||||
let open = self.screen == Screen::CommandBlock
|
||||
&& self
|
||||
.command_block_screen
|
||||
.as_ref()
|
||||
.is_some_and(|s| s.pos == pos);
|
||||
if open {
|
||||
self.open_command_block(pos);
|
||||
}
|
||||
}
|
||||
/// What `handleSetCommandBlock` does once the player may edit the block.
|
||||
fn apply_command_block(&mut self, s: &CommandBlockEdit) {
|
||||
let pos = s.pos;
|
||||
|
||||
@@ -22,7 +22,7 @@ const HELP: &[&str] = &[
|
||||
"tp X Y Z teleport the player",
|
||||
"hotbar 1-9 select a hotbar slot",
|
||||
"give ITEM [COUNT] add items to the inventory",
|
||||
"render-distance N chunks, 2..64",
|
||||
"render-distance N chunks, 2..128",
|
||||
"lod-distance N far surface chunks, 0 disables, up to1024",
|
||||
"time TICKS set the time of day",
|
||||
"summon KIND [X Y Z] [baby] spawn a mob (in front of the player without a position)",
|
||||
@@ -349,7 +349,10 @@ impl Game<'_> {
|
||||
self.native_key_event(physical, false, false);
|
||||
}
|
||||
// Text screens consume raw presses; releases follow key binds.
|
||||
let typing = matches!(self.screen, Screen::Chat | Screen::Options);
|
||||
let typing = matches!(
|
||||
self.screen,
|
||||
Screen::Chat | Screen::Options | Screen::CommandBlock
|
||||
);
|
||||
let key = if typing && arg(2)? != "up" {
|
||||
key
|
||||
} else {
|
||||
@@ -517,8 +520,9 @@ impl Game<'_> {
|
||||
}
|
||||
"render-distance" => {
|
||||
let n: i32 = arg(1)?.parse()?;
|
||||
if !(2..=64).contains(&n) {
|
||||
return Err("render distance must be 2..64".into());
|
||||
let max = horizon_client::options_file::MAX_RENDER_DISTANCE;
|
||||
if !(2..=max).contains(&n) {
|
||||
return Err(format!("render distance must be 2..{max}").into());
|
||||
}
|
||||
self.settings.render_distance = n;
|
||||
self.apply_settings();
|
||||
@@ -855,6 +859,10 @@ impl Game<'_> {
|
||||
if self.screen == Screen::Options {
|
||||
return self.options_key(key, None);
|
||||
}
|
||||
if self.screen == Screen::CommandBlock {
|
||||
self.command_block_key_code(key, None, None);
|
||||
return Ok(());
|
||||
}
|
||||
if self.keys.insert(key) {
|
||||
self.key_pressed(key)?;
|
||||
}
|
||||
|
||||
@@ -4651,7 +4651,15 @@ impl Game<'_> {
|
||||
let Some(model) = self.arrow_model.as_ref() else {
|
||||
continue;
|
||||
};
|
||||
let Some(r) = atlas.sprites.get("entity/projectiles/arrow").copied() else {
|
||||
// SpectralArrowRenderer, and TippableArrowRenderer's `getColor() > 0`.
|
||||
let texture = if a.is_spectral_arrow() {
|
||||
"entity/projectiles/arrow_spectral"
|
||||
} else if a.arrow.as_ref().is_some_and(|s| s.effect_color > 0) {
|
||||
"entity/projectiles/arrow_tipped"
|
||||
} else {
|
||||
"entity/projectiles/arrow"
|
||||
};
|
||||
let Some(r) = atlas.sprites.get(texture).copied() else {
|
||||
continue;
|
||||
};
|
||||
let m = glam::Mat4::from_translation(c)
|
||||
|
||||
@@ -26,12 +26,105 @@ use horizon_world::nbt::{JavaString, Tag};
|
||||
use std::{
|
||||
io::Write,
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex, OnceLock, mpsc},
|
||||
sync::{
|
||||
Arc, Mutex, OnceLock,
|
||||
atomic::{AtomicBool, AtomicU64, Ordering},
|
||||
mpsc,
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
mod status;
|
||||
|
||||
/// What the tick loop tells `ServerWatchdog`: when the tick in progress was
|
||||
/// due (`getNextTickTime`), how long a tick is, and whether the server runs.
|
||||
struct Watchdog {
|
||||
base: Instant,
|
||||
next_nanos: AtomicU64,
|
||||
tick_nanos: AtomicU64,
|
||||
running: AtomicBool,
|
||||
}
|
||||
|
||||
impl Watchdog {
|
||||
/// `DedicatedServer.initServer`: a thread that stops a server whose tick
|
||||
/// runs `max_tick_time` ms past its due time; none when that is 0 or less.
|
||||
fn start(max_tick_time: i64) -> Option<Arc<Self>> {
|
||||
let max = Duration::from_millis(u64::try_from(max_tick_time).ok().filter(|m| *m > 0)?);
|
||||
let watchdog = Arc::new(Self {
|
||||
base: Instant::now(),
|
||||
next_nanos: AtomicU64::new(0),
|
||||
tick_nanos: AtomicU64::new(50_000_000),
|
||||
running: AtomicBool::new(true),
|
||||
});
|
||||
let shared = watchdog.clone();
|
||||
std::thread::Builder::new()
|
||||
.name("Server Watchdog".into())
|
||||
.spawn(move || shared.run(max))
|
||||
.ok()?;
|
||||
Some(watchdog)
|
||||
}
|
||||
/// The tick loop is about to run the tick due at `next`.
|
||||
fn tick_due(&self, next: Instant, tick: Duration) {
|
||||
let nanos = next.saturating_duration_since(self.base).as_nanos();
|
||||
self.next_nanos
|
||||
.store(u64::try_from(nanos).unwrap_or(u64::MAX), Ordering::Relaxed);
|
||||
let tick = u64::try_from(tick.as_nanos()).unwrap_or(u64::MAX);
|
||||
self.tick_nanos.store(tick, Ordering::Relaxed);
|
||||
}
|
||||
/// `ServerWatchdog.run`.
|
||||
fn run(&self, max: Duration) {
|
||||
let max = u64::try_from(max.as_nanos()).unwrap_or(u64::MAX);
|
||||
while self.running.load(Ordering::Relaxed) {
|
||||
let next = self.next_nanos.load(Ordering::Relaxed);
|
||||
let now = u64::try_from(self.base.elapsed().as_nanos()).unwrap_or(u64::MAX);
|
||||
let behind = now.saturating_sub(next);
|
||||
if behind > max {
|
||||
let took = format!("{:.2}", behind as f32 / 1e9);
|
||||
let should = format!(
|
||||
"{:.2}",
|
||||
self.tick_nanos.load(Ordering::Relaxed) as f32 / 1e9
|
||||
);
|
||||
log_from(
|
||||
"Server Watchdog",
|
||||
"ERROR",
|
||||
&format!("A single server tick took {took} seconds (should be max {should})"),
|
||||
);
|
||||
log_from(
|
||||
"Server Watchdog",
|
||||
"ERROR",
|
||||
"Considering it to be crashed, server will forcibly shutdown.",
|
||||
);
|
||||
let message = format!(
|
||||
"ServerWatchdog detected that a single server tick took {took} seconds (should be max {should})"
|
||||
);
|
||||
match horizon_client::crash::report_watchdog(
|
||||
std::path::Path::new("."),
|
||||
"server",
|
||||
message,
|
||||
) {
|
||||
Some(path) => log_from(
|
||||
"Server Watchdog",
|
||||
"ERROR",
|
||||
&format!(
|
||||
"This crash report has been saved to: {}",
|
||||
path.canonicalize().unwrap_or(path).display()
|
||||
),
|
||||
),
|
||||
None => log_from(
|
||||
"Server Watchdog",
|
||||
"ERROR",
|
||||
"We were unable to save this crash report to disk.",
|
||||
),
|
||||
}
|
||||
std::process::exit(1);
|
||||
}
|
||||
// Thread.sleep(nextTickTime + maxTickTime - now).
|
||||
let wait = (next + max).saturating_sub(now).max(1_000_000);
|
||||
std::thread::sleep(Duration::from_nanos(wait));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Double.toString for the magnitudes positions have (no exponent form
|
||||
/// between 1e-3 and 1e7).
|
||||
pub(super) fn java_double(v: f64) -> String {
|
||||
@@ -704,8 +797,13 @@ impl Game<'_> {
|
||||
let pause_after = props.pause_when_empty_seconds * 20;
|
||||
let idle_timeout = props.player_idle_timeout;
|
||||
let mut next = Instant::now();
|
||||
self.fns.permission = Some(props.function_permission_level.clamp(0, 4) as u8);
|
||||
let watchdog = Watchdog::start(props.max_tick_time);
|
||||
while !state.stop {
|
||||
let tick_start = Instant::now();
|
||||
if let Some(watchdog) = &watchdog {
|
||||
watchdog.tick_due(next, self.clock.tick_duration());
|
||||
}
|
||||
while let Ok(line) = state.console.try_recv() {
|
||||
self.console_line(&mut state, &line);
|
||||
}
|
||||
@@ -746,6 +844,9 @@ impl Game<'_> {
|
||||
state.ticked(Instant::now());
|
||||
self.wait_for_next_server_tick(&mut next, paused);
|
||||
}
|
||||
if let Some(watchdog) = &watchdog {
|
||||
watchdog.running.store(false, Ordering::Relaxed);
|
||||
}
|
||||
self.stop_dedicated()
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,8 @@ pub(super) struct FnState {
|
||||
load_pending: bool,
|
||||
pub schedule: Vec<Scheduled>,
|
||||
next_seq: u64,
|
||||
/// A dedicated server's `function-permission-level`.
|
||||
pub permission: Option<u8>,
|
||||
}
|
||||
/// What a function reported through `return`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -45,6 +47,16 @@ pub(super) enum Outcome {
|
||||
Fail,
|
||||
}
|
||||
|
||||
impl FnState {
|
||||
/// The level function files compile at (`ServerFunctionLibrary`'s
|
||||
/// `functionCompilationPermissions`): `function-permission-level` on a
|
||||
/// dedicated server, game master (2) otherwise. Whoever runs a function,
|
||||
/// its commands need no more than this.
|
||||
fn command_level(&self) -> u8 {
|
||||
self.permission.unwrap_or(2)
|
||||
}
|
||||
}
|
||||
|
||||
impl Game<'_> {
|
||||
// ---- loading -----------------------------------------------------------
|
||||
|
||||
@@ -278,6 +290,7 @@ impl Game<'_> {
|
||||
}
|
||||
self.exec.function_depth += 1;
|
||||
let outer = self.exec.returned.take();
|
||||
let level = std::mem::replace(&mut self.ui.registries.permission, self.fns.command_level());
|
||||
let inner = Source {
|
||||
silent: true,
|
||||
store: None,
|
||||
@@ -292,6 +305,7 @@ impl Game<'_> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.ui.registries.permission = level;
|
||||
let returned = std::mem::replace(&mut self.exec.returned, outer);
|
||||
self.exec.function_depth -= 1;
|
||||
Ok(match returned {
|
||||
|
||||
@@ -61,6 +61,11 @@ fn attack_range_contains(range: &serde_json::Value, distance: f64, creative: boo
|
||||
impl Game<'_> {
|
||||
/// Opening a screen stops breaking before the next game tick (which may pause).
|
||||
pub(super) fn stop_mining(&mut self) {
|
||||
if self.clicks.destroying.active {
|
||||
let pos = self.clicks.destroying.pos;
|
||||
let state = self.world.block(pos[0], pos[1], pos[2]);
|
||||
self.tutorial_destroy(state, -1.0);
|
||||
}
|
||||
let mut destroying = std::mem::take(&mut self.clicks.destroying);
|
||||
destroying.stop(&mut GameHands {
|
||||
game: self,
|
||||
@@ -141,6 +146,12 @@ impl Game<'_> {
|
||||
progress: d.progress,
|
||||
ticks: d.ticks as u32,
|
||||
});
|
||||
if let Some(mining) = &self.mining {
|
||||
let pos = mining.pos;
|
||||
let progress = mining.progress;
|
||||
let state = self.world.block(pos[0], pos[1], pos[2]);
|
||||
self.tutorial_destroy(state, progress);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// `BlockBehaviour.getDestroyProgress` with `Player.getDestroySpeed`.
|
||||
@@ -578,6 +589,8 @@ impl HandWorld for GameHands<'_, '_> {
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let state = self.game.world.block(pos[0], pos[1], pos[2]);
|
||||
self.game.tutorial_destroy(state, 1.0);
|
||||
let r = self.game.break_block(pos);
|
||||
self.keep(r);
|
||||
true
|
||||
|
||||
@@ -626,7 +626,9 @@ impl Game<'_> {
|
||||
}) => Some(key),
|
||||
_ => None,
|
||||
};
|
||||
self.command_block_screen = host_command_block;
|
||||
// A command block the guest's use opened (Player.openCommandBlock).
|
||||
let guest_command_block =
|
||||
std::mem::replace(&mut self.command_block_screen, host_command_block).map(|s| s.pos());
|
||||
// A mount inventory the guest's action opened (shift + use, or the
|
||||
// inventory key while riding).
|
||||
let guest_mount = std::mem::replace(&mut self.mount_menu, host_mount_menu).opened_mob();
|
||||
@@ -779,6 +781,9 @@ impl Game<'_> {
|
||||
if let Some(key) = guest_lectern {
|
||||
self.lan_open_lectern(host, conn, key);
|
||||
}
|
||||
if let Some(pos) = guest_command_block {
|
||||
self.lan_open_command_block(host, conn, pos);
|
||||
}
|
||||
if let Some(pos) = opened_table {
|
||||
self.lan_open_crafting(host, conn, pos);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use horizon_client::lan::{
|
||||
hub::Hub,
|
||||
};
|
||||
use horizon_protocol::entity_data::{Value, serializer};
|
||||
use horizon_world::nbt::Tag;
|
||||
use horizon_world::{nbt::Tag, registry::BlockStateId};
|
||||
|
||||
fn state(id: u32, kind: &str, position: [f64; 3], yaw: f32, on_ground: bool) -> EntityState {
|
||||
EntityState {
|
||||
@@ -276,18 +276,100 @@ impl Game<'_> {
|
||||
}
|
||||
out.push(e);
|
||||
}
|
||||
// DATA_ITEM, `ItemStack.EMPTY` by default.
|
||||
for o in self.ominous_spawners.iter().filter(|o| o.id != 0) {
|
||||
let mut e = state(
|
||||
o.id,
|
||||
"minecraft:ominous_item_spawner",
|
||||
o.position,
|
||||
0.0,
|
||||
false,
|
||||
);
|
||||
e.meta.push((
|
||||
8,
|
||||
serializer::ITEM_STACK,
|
||||
Value::Item(hub.stack(&Some(o.item.clone()))),
|
||||
));
|
||||
out.push(e);
|
||||
}
|
||||
self.lan_projectile_states(host_entity, hub, out);
|
||||
for o in &self.others {
|
||||
let p = o.position;
|
||||
let mut e = state(o.id, &o.kind, [p.x, p.y, p.z], o.yaw, false);
|
||||
e.pitch = o.pitch;
|
||||
e.head_yaw = o.yaw;
|
||||
if let Some(tag) = self.persist.raw.get(&o.id) {
|
||||
self.lan_display_data(tag, hub, &mut e);
|
||||
}
|
||||
out.push(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Game<'_> {
|
||||
/// The synched data of a text, block or item display kept unsimulated,
|
||||
/// from its compound (`Display.readAdditionalSaveData`), with the
|
||||
/// defaults to send when a value goes back to one.
|
||||
fn lan_display_data(&self, tag: &Tag, hub: &mut Hub, e: &mut EntityState) {
|
||||
use horizon_client::text_display::{display_data, text_display_data};
|
||||
let empty = Tag::Compound(Default::default());
|
||||
let (meta, defaults) = match e.kind.as_str() {
|
||||
"minecraft:text_display" => (
|
||||
text_display_data(tag, false),
|
||||
text_display_data(&empty, true),
|
||||
),
|
||||
"minecraft:block_display" => {
|
||||
// DATA_BLOCK_STATE_ID, air by default.
|
||||
let state = |t: Option<BlockStateId>| {
|
||||
(
|
||||
23,
|
||||
serializer::BLOCK_STATE,
|
||||
Value::Id(t.map_or(0, |s| s.0 as i32)),
|
||||
)
|
||||
};
|
||||
let air = self.pack.assets.registry.default_state("minecraft:air");
|
||||
let shown = tag.get("block_state").and_then(|t| self.state_from_nbt(t));
|
||||
(vec![state(shown.or(air))], vec![state(air)])
|
||||
}
|
||||
"minecraft:item_display" => {
|
||||
// DATA_ITEM_STACK and DATA_ITEM_DISPLAY_ID (`ItemDisplayContext`).
|
||||
let item = tag
|
||||
.get("item")
|
||||
.and_then(horizon_client::save::item_from_nbt);
|
||||
let context = match tag.get("item_display") {
|
||||
Some(Tag::String(s)) => match s.to_string_lossy().as_str() {
|
||||
"thirdperson_lefthand" => 1,
|
||||
"thirdperson_righthand" => 2,
|
||||
"firstperson_lefthand" => 3,
|
||||
"firstperson_righthand" => 4,
|
||||
"head" => 5,
|
||||
"gui" => 6,
|
||||
"ground" => 7,
|
||||
"fixed" => 8,
|
||||
"on_shelf" => 9,
|
||||
_ => 0,
|
||||
},
|
||||
_ => 0,
|
||||
};
|
||||
(
|
||||
vec![
|
||||
(23, serializer::ITEM_STACK, Value::Item(hub.stack(&item))),
|
||||
(24, serializer::BYTE, Value::Byte(context)),
|
||||
],
|
||||
vec![
|
||||
(23, serializer::ITEM_STACK, Value::Item(None)),
|
||||
(24, serializer::BYTE, Value::Byte(0)),
|
||||
],
|
||||
)
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
e.meta = display_data(tag, false);
|
||||
e.meta.extend(meta);
|
||||
e.defaults = display_data(&empty, true);
|
||||
e.defaults.extend(defaults);
|
||||
}
|
||||
|
||||
/// Gives the entities Horizon keeps without an id one (`Entity.id`, from
|
||||
/// the same counter as the rest) before they are first tracked.
|
||||
pub(super) fn lan_assign_ids(&mut self) {
|
||||
@@ -326,6 +408,9 @@ impl Game<'_> {
|
||||
self.rockets.iter_mut().for_each(|r| give(&mut r.id));
|
||||
self.crystals.iter_mut().for_each(|c| give(&mut c.id));
|
||||
self.bolts.iter_mut().for_each(|b| give(&mut b.id));
|
||||
self.ominous_spawners
|
||||
.iter_mut()
|
||||
.for_each(|o| give(&mut o.id));
|
||||
self.potions.thrown_ids().for_each(&mut give);
|
||||
self.end.eye_ids().for_each(&mut give);
|
||||
self.breath_clouds.iter_mut().for_each(|b| give(&mut b.id));
|
||||
@@ -377,7 +462,12 @@ impl Game<'_> {
|
||||
}
|
||||
for p in self.creatures.projectiles.iter().filter(|p| p.id != 0) {
|
||||
use horizon_client::projectiles::ProjectileKind as K;
|
||||
let kind = p.kind.id();
|
||||
// A spectral arrow is its own entity type.
|
||||
let kind = if p.is_spectral_arrow() {
|
||||
"minecraft:spectral_arrow"
|
||||
} else {
|
||||
p.kind.id()
|
||||
};
|
||||
let mut e = state(p.id, kind, at(p.position), p.yaw, false);
|
||||
e.pitch = p.pitch;
|
||||
e.velocity = at(p.velocity);
|
||||
@@ -396,6 +486,11 @@ impl Game<'_> {
|
||||
a.map_or(0, |a| a.pierce),
|
||||
a.is_some_and(|a| a.in_ground) || p.stuck,
|
||||
));
|
||||
// Arrow.ID_EFFECT_COLOR, -1 by default.
|
||||
let color = p.arrow_effect_color();
|
||||
if color != -1 {
|
||||
e.meta.push((11, serializer::INT, Value::Int(color)));
|
||||
}
|
||||
}
|
||||
// ThrownTrident.ID_LOYALTY (none for a mob's) and ID_FOIL, on
|
||||
// AbstractArrow's flags.
|
||||
|
||||
@@ -151,10 +151,14 @@ impl Containers<'_> {
|
||||
});
|
||||
}
|
||||
let entity = self.world.block_entities.get(&pos)?;
|
||||
// A beehive keeps bees, not items, and a campfire cooks its own: neither
|
||||
// is a `Container` (HopperBlockEntity.getContainerAt), so a dropper drops
|
||||
// in front of them and a hopper leaves them alone.
|
||||
if matches!(entity, BlockEntity::Beehive(_) | BlockEntity::Campfire(_)) {
|
||||
// A beehive keeps bees, not items, a campfire cooks its own and
|
||||
// suspicious sand or gravel hides its loot: none is a `Container`
|
||||
// (HopperBlockEntity.getContainerAt), so a dropper drops in front of
|
||||
// them and a hopper leaves them alone.
|
||||
if matches!(
|
||||
entity,
|
||||
BlockEntity::Beehive(_) | BlockEntity::Campfire(_) | BlockEntity::Brushable(_)
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
let kind = match (entity, name) {
|
||||
@@ -422,15 +426,17 @@ pub(super) fn shelf_signal(
|
||||
_ => 0,
|
||||
});
|
||||
}
|
||||
let tag = super::spill::saved_entity(world, [pos.x, pos.y, pos.z]);
|
||||
// Only read from the back: a bit for each of the three slots in use.
|
||||
let facing = info.properties.get("facing").and_then(|f| dir_from_name(f));
|
||||
if facing.map(Direction::opposite) != Some(direction) {
|
||||
return Some(0);
|
||||
}
|
||||
let items = horizon_client::save::decode_items(tag.as_ref().and_then(|t| t.get("Items")), 3);
|
||||
let Some(BlockEntity::Shelf(shelf)) = world.block_entities.get(&(pos.x, pos.y, pos.z)) else {
|
||||
return Some(0);
|
||||
};
|
||||
Some(
|
||||
items
|
||||
shelf
|
||||
.items
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(slot, item)| i32::from(item.is_some()) << slot)
|
||||
@@ -473,6 +479,12 @@ impl Game<'_> {
|
||||
return;
|
||||
}
|
||||
self.chiseled_bookshelf_synced(pos);
|
||||
if matches!(
|
||||
self.world.block_entities.get(&pos),
|
||||
Some(BlockEntity::Shelf(_))
|
||||
) {
|
||||
self.mark_block_entity_updated(pos);
|
||||
}
|
||||
// A double chest's other half changes with it.
|
||||
let halves: Vec<Pos> = std::iter::once(pos)
|
||||
.chain(self.chest_other_half(pos))
|
||||
|
||||
@@ -4,7 +4,7 @@ use super::Game;
|
||||
use horizon_client::{
|
||||
blocks::Fluid,
|
||||
music::{BackgroundMusic, Music, MusicFrequency, Situation},
|
||||
sound_engine::TickContext,
|
||||
sound_engine::{SoundEngine, SoundInstance, Sounds, TickContext},
|
||||
};
|
||||
|
||||
impl Game<'_> {
|
||||
@@ -35,6 +35,22 @@ impl Game<'_> {
|
||||
}
|
||||
let (vehicle, carts) = self.start_cart_sounds();
|
||||
let mobs = self.start_mob_sounds();
|
||||
let (player_position, player_speed_sqr, player_fall_flying) =
|
||||
self.player.as_ref().map_or(([0.0; 3], 0.0, false), |p| {
|
||||
let at = p.position;
|
||||
(
|
||||
[at.x, at.y, at.z],
|
||||
p.velocity.length_squared(),
|
||||
p.fall_flying,
|
||||
)
|
||||
});
|
||||
// LocalPlayer.onSyncedDataUpdated: starting to glide starts the
|
||||
// elytra's flying sound.
|
||||
if player_fall_flying && !self.was_fall_flying {
|
||||
let instance = SoundInstance::elytra(player_position, SoundEngine::unseeded());
|
||||
self.audio.play_instance(instance, false);
|
||||
}
|
||||
self.was_fall_flying = player_fall_flying;
|
||||
let ctx = TickContext {
|
||||
player_underwater: self.ambience.was_underwater(),
|
||||
player_removed: self.player.is_none(),
|
||||
@@ -42,6 +58,9 @@ impl Game<'_> {
|
||||
carts,
|
||||
mobs,
|
||||
frozen: !self.clock.runs_normally(),
|
||||
player_position,
|
||||
player_speed_sqr,
|
||||
player_fall_flying,
|
||||
};
|
||||
self.audio.tick(self.pause, &ctx);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ const TICKS_SCALING: f32 = 50.0;
|
||||
const ROTATION_SPEED: f32 = 40.0;
|
||||
|
||||
pub(super) struct OminousItemSpawner {
|
||||
/// The entity id a server tracks it by (0 until it is tracked).
|
||||
pub(super) id: u32,
|
||||
pub(super) item: ItemStack,
|
||||
pub(super) position: [f64; 3],
|
||||
pub(super) spawn_item_after: i64,
|
||||
@@ -65,6 +67,7 @@ impl Game<'_> {
|
||||
delay: i32,
|
||||
) {
|
||||
self.ominous_spawners.push(OminousItemSpawner {
|
||||
id: 0,
|
||||
item,
|
||||
position,
|
||||
spawn_item_after: i64::from(delay),
|
||||
@@ -82,6 +85,7 @@ impl Game<'_> {
|
||||
return;
|
||||
};
|
||||
self.ominous_spawners.push(OminousItemSpawner {
|
||||
id: 0,
|
||||
item,
|
||||
position: [at.x, at.y, at.z],
|
||||
spawn_item_after,
|
||||
@@ -121,6 +125,32 @@ impl Game<'_> {
|
||||
kept.append(&mut self.ominous_spawners);
|
||||
self.ominous_spawners = kept;
|
||||
}
|
||||
/// `handleAddEntity` for a spawner a server sent: its item comes with the
|
||||
/// entity data (`ItemStack.EMPTY` until then, which draws nothing).
|
||||
pub(super) fn add_remote_ominous_item_spawner(&mut self, id: u32, position: [f64; 3]) {
|
||||
self.ominous_spawners.push(OminousItemSpawner {
|
||||
id,
|
||||
item: ItemStack::new("minecraft:air", 1),
|
||||
position,
|
||||
spawn_item_after: 0,
|
||||
tick_count: 0,
|
||||
uuid: [0; 4],
|
||||
raw: None,
|
||||
});
|
||||
}
|
||||
/// `OminousItemSpawner.tick` on a client: `tickCount` and `tickClient`,
|
||||
/// the particles every 5 game ticks; only the server spawns the item.
|
||||
pub(super) fn tick_remote_ominous_item_spawners(&mut self) {
|
||||
let at: Vec<[f64; 3]> = self.ominous_spawners.iter().map(|s| s.position).collect();
|
||||
for s in &mut self.ominous_spawners {
|
||||
s.tick_count += 1;
|
||||
}
|
||||
if self.world.game_time % 5 == 0 {
|
||||
for position in at {
|
||||
self.ominous_spawner_particles(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// `spawnItem`: a projectile shot down, or an item entity; level event
|
||||
/// 3021 and the entity_place game event.
|
||||
fn spawn_ominous_item(&mut self, s: &OminousItemSpawner) {
|
||||
|
||||
@@ -19,12 +19,18 @@ pub(super) const CONTROL_HELP: &[&str] = &[
|
||||
impl Game<'_> {
|
||||
pub(super) fn save_title_options(&mut self) {
|
||||
if let Some(title) = &self.title
|
||||
&& title.last_server != self.settings.file.string("lastServer")
|
||||
&& (title.last_server != self.settings.file.string("lastServer")
|
||||
|| title.skip_multiplayer_warning
|
||||
!= self.settings.file.bool("skipMultiplayerWarning"))
|
||||
{
|
||||
self.settings.file.set(
|
||||
"lastServer",
|
||||
horizon_client::options_file::V::Str(title.last_server.clone()),
|
||||
);
|
||||
self.settings.file.set(
|
||||
"skipMultiplayerWarning",
|
||||
horizon_client::options_file::V::Bool(title.skip_multiplayer_warning),
|
||||
);
|
||||
self.settings.save(&Self::options_path());
|
||||
}
|
||||
}
|
||||
@@ -232,6 +238,15 @@ impl Game<'_> {
|
||||
let _ = std::fs::create_dir_all(&dir);
|
||||
super::packs::open_folder(&dir);
|
||||
}
|
||||
Event::DataPacks(ids) => {
|
||||
if let Some(title) = &mut self.title {
|
||||
title.set_data_packs(ids);
|
||||
}
|
||||
}
|
||||
Event::OpenDataPackFolder(dir) => {
|
||||
let _ = std::fs::create_dir_all(&dir);
|
||||
super::packs::open_folder(&dir);
|
||||
}
|
||||
Event::OpenModFolder => {
|
||||
let dir = Self::options_path().with_file_name(horizon_client::mod_list::DIR);
|
||||
let _ = std::fs::create_dir_all(&dir);
|
||||
|
||||
@@ -631,7 +631,7 @@ impl Game<'_> {
|
||||
*self.persist.uuids.entry(id).or_insert_with(random_uuid)
|
||||
}
|
||||
/// `BlockState.CODEC`: a block name or `{id, properties}` (or legacy `{Name, Properties}`).
|
||||
fn state_from_nbt(&self, tag: &Tag) -> Option<BlockStateId> {
|
||||
pub(super) fn state_from_nbt(&self, tag: &Tag) -> Option<BlockStateId> {
|
||||
let registry = self.pack.assets.registry;
|
||||
let state = match tag.get("Name") {
|
||||
Some(Tag::String(name)) => BlockState {
|
||||
|
||||
@@ -39,6 +39,7 @@ use horizon_protocol::{
|
||||
text::{Language, plain},
|
||||
};
|
||||
use horizon_world::{
|
||||
level::InteractionResult,
|
||||
nbt::Tag,
|
||||
registry::BlockStateId,
|
||||
stepped_interpolation::{Path, PosRot, Start, SteppedInterpolation},
|
||||
@@ -325,7 +326,9 @@ impl Game<'_> {
|
||||
&self.options.resources,
|
||||
));
|
||||
}
|
||||
let skip = self.settings.file.bool("skipMultiplayerWarning");
|
||||
if let Some(title) = &mut self.title {
|
||||
title.skip_multiplayer_warning = skip;
|
||||
title.failed(error);
|
||||
}
|
||||
self.capture(false);
|
||||
@@ -347,6 +350,7 @@ impl Game<'_> {
|
||||
local: Option<horizon_protocol::connection::Pipe>,
|
||||
cookies: Option<horizon_client::net::Cookies>,
|
||||
) -> Result<()> {
|
||||
self.reset_tutorial();
|
||||
self.perf.world_opening();
|
||||
let root = self.options.resources.clone();
|
||||
let builtin = Builtin::find(&root)
|
||||
@@ -556,8 +560,10 @@ impl Game<'_> {
|
||||
if let Some(mut r) = self.remote.take() {
|
||||
r.session.disconnect();
|
||||
}
|
||||
self.gui.atlas.object_skins.clear();
|
||||
self.server_packs_disconnect();
|
||||
self.code_of_conduct = None;
|
||||
self.reset_tutorial();
|
||||
self.close_dialogs();
|
||||
// Gui.setScreen -> KeyMapping.releaseAll: the title or disconnect
|
||||
// screen releases every held key. Its release goes to that screen,
|
||||
@@ -604,6 +610,7 @@ impl Game<'_> {
|
||||
let saves = std::env::current_dir()?.join("saves");
|
||||
let mut title = horizon_client::title::Title::new(saves.clone(), &self.options.resources);
|
||||
title.last_server = self.settings.file.string("lastServer");
|
||||
title.skip_multiplayer_warning = self.settings.file.bool("skipMultiplayerWarning");
|
||||
if single_player && reason.is_none() {
|
||||
self.title = Some(title);
|
||||
self.capture(false);
|
||||
@@ -1008,7 +1015,12 @@ impl Game<'_> {
|
||||
if self.remote.is_none() {
|
||||
return;
|
||||
}
|
||||
let Some((id, command)) = self.ui.chat_screen.as_mut().and_then(|s| s.take_request())
|
||||
let Some((id, command)) = self
|
||||
.ui
|
||||
.chat_screen
|
||||
.as_mut()
|
||||
.and_then(|s| s.take_request())
|
||||
.or_else(|| self.command_block_suggestion_request())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
@@ -1048,7 +1060,8 @@ impl Game<'_> {
|
||||
})();
|
||||
if let Ok((id, start, length, list)) = decoded {
|
||||
let range = (start.max(0) as usize, length.max(0) as usize);
|
||||
self.chat_server_suggestions(id, range, list);
|
||||
self.chat_server_suggestions(id, range, list.clone());
|
||||
self.command_block_server_suggestions(id, range, list);
|
||||
}
|
||||
}
|
||||
fn set_registries(&mut self, registries: Arc<Dynamic>) {
|
||||
@@ -1243,10 +1256,14 @@ impl Game<'_> {
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let command_block = matches!(loaded, BlockEntity::CommandBlock(_));
|
||||
self.world.block_entities.insert(key, loaded);
|
||||
// The section is drawn again with what the block entity shows.
|
||||
let state = self.world.block(pos[0], pos[1], pos[2]);
|
||||
let _ = self.world.set(pos[0], pos[1], pos[2], state);
|
||||
if command_block {
|
||||
self.command_block_data_arrived(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// `handleAnimate`: a player wakes up, or an entity gets the critical
|
||||
@@ -2770,6 +2787,7 @@ impl Game<'_> {
|
||||
r.state_id = p.state;
|
||||
}
|
||||
let stack = self.to_stack(&p.item);
|
||||
self.tutorial_get_item(stack.as_ref());
|
||||
// ClientPacketListener.handleContainerSetSlot: a hotbar slot that
|
||||
// gains items squeezes for 5 ticks.
|
||||
if p.container == 0
|
||||
@@ -2788,9 +2806,14 @@ impl Game<'_> {
|
||||
self.set_menu_slot(p.container, i32::from(p.slot), stack);
|
||||
self.remote_synced_since(&before);
|
||||
}
|
||||
C::SetCursorItem(p) => self.carried = self.to_stack(&p.item),
|
||||
C::SetCursorItem(p) => {
|
||||
let stack = self.to_stack(&p.item);
|
||||
self.tutorial_get_item(stack.as_ref());
|
||||
self.carried = stack;
|
||||
}
|
||||
C::SetPlayerInventory(p) => {
|
||||
let stack = self.to_stack(&p.item);
|
||||
self.tutorial_get_item(stack.as_ref());
|
||||
let before = self.creative_stacks();
|
||||
match p.slot {
|
||||
0..=35 => self.inventory[p.slot as usize] = stack,
|
||||
@@ -2978,7 +3001,10 @@ impl Game<'_> {
|
||||
let text = |t: &Tag| {
|
||||
let json = horizon_client::scoreboard::tag_to_json(t);
|
||||
let text = horizon_client::scoreboard::comp_text(&json);
|
||||
(!text.plain().is_empty()).then_some(text)
|
||||
text.spans
|
||||
.iter()
|
||||
.any(|span| !span.text.is_empty())
|
||||
.then_some(text)
|
||||
};
|
||||
self.tab_list.header = text(&p.header);
|
||||
self.tab_list.footer = text(&p.footer);
|
||||
@@ -3224,6 +3250,13 @@ impl Game<'_> {
|
||||
.as_ref()
|
||||
.and_then(|r| r.skins.get(&p.uuid).cloned())
|
||||
.unwrap_or_else(|| horizon_client::tab_list::default_skin(p.uuid).to_owned());
|
||||
self.gui
|
||||
.atlas
|
||||
.set_object_skin(format!("id:{:032x}", p.uuid), p.skin.clone());
|
||||
self.gui.atlas.set_object_skin(
|
||||
format!("name:{}", p.name.to_ascii_lowercase()),
|
||||
p.skin.clone(),
|
||||
);
|
||||
}
|
||||
let r = self.remote.as_ref()?;
|
||||
let upside_down = players
|
||||
@@ -3236,6 +3269,32 @@ impl Game<'_> {
|
||||
.collect();
|
||||
Some((players, r.online_mode, upside_down))
|
||||
}
|
||||
|
||||
/// Resolve inline `object:player` profiles through the same skin cache as
|
||||
/// player heads on items. The next frame uses the uploaded atlas texture.
|
||||
pub(super) fn resolve_inline_profiles(&mut self, profiles: &[serde_json::Value]) {
|
||||
use horizon_client::skins;
|
||||
for profile in profiles {
|
||||
let skin = skins::head_skin(
|
||||
profile,
|
||||
&mut self.skin_profiles.borrow_mut(),
|
||||
&mut self.skin_downloads.borrow_mut(),
|
||||
|location| self.gui.atlas.sprites.contains_key(location),
|
||||
);
|
||||
let id = skins::parse_profile(profile).id;
|
||||
self.gui
|
||||
.atlas
|
||||
.set_object_skin(format!("id:{id:032x}"), skin.clone());
|
||||
let name = profile
|
||||
.as_str()
|
||||
.or_else(|| profile.get("name").and_then(serde_json::Value::as_str));
|
||||
if let Some(name) = name {
|
||||
self.gui
|
||||
.atlas
|
||||
.set_object_skin(format!("name:{}", name.to_ascii_lowercase()), skin);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// The signed-in account's `textures`, once the services answered.
|
||||
fn own_textures(&self) -> Option<horizon_client::skins::Textures> {
|
||||
use horizon_client::skins::{self, NameOrId, Resolve};
|
||||
@@ -4146,6 +4205,7 @@ impl Game<'_> {
|
||||
fn tick_remote_entities(&mut self, running: bool) {
|
||||
if running {
|
||||
self.tick_text_displays();
|
||||
self.tick_remote_ominous_item_spawners();
|
||||
}
|
||||
// EntityFluidInteraction.update for the entities this window only displays: the
|
||||
// server never sends the fluid contact, and the renderers read it (cod, salmon and
|
||||
@@ -4583,6 +4643,8 @@ impl Game<'_> {
|
||||
/// the attack strength again.
|
||||
pub(super) fn stop_remote_mining(&mut self) {
|
||||
if let Some((pos, _, _)) = self.remote.as_mut().and_then(|r| r.destroying.take()) {
|
||||
let state = self.world.block(pos[0], pos[1], pos[2]);
|
||||
self.tutorial_destroy(state, -1.0);
|
||||
self.send(S::PlayerAction(play::PlayerAction {
|
||||
action: 2,
|
||||
pos,
|
||||
@@ -4653,6 +4715,8 @@ impl Game<'_> {
|
||||
}
|
||||
if !same {
|
||||
if let Some((old, _, _)) = self.remote.as_mut().and_then(|r| r.destroying.take()) {
|
||||
let old_state = self.world.block(old[0], old[1], old[2]);
|
||||
self.tutorial_destroy(old_state, -1.0);
|
||||
self.send(S::PlayerAction(play::PlayerAction {
|
||||
action: 2,
|
||||
pos: old,
|
||||
@@ -4667,6 +4731,7 @@ impl Game<'_> {
|
||||
direction: face,
|
||||
sequence: seq,
|
||||
}));
|
||||
self.tutorial_destroy(state, 0.0);
|
||||
if progress >= 1.0 {
|
||||
// startDestroyBlock: instant survival breaks have no destroy delay.
|
||||
self.predict_break(hit.pos, state, seq);
|
||||
@@ -4690,6 +4755,7 @@ impl Game<'_> {
|
||||
return;
|
||||
};
|
||||
let done = done + progress;
|
||||
self.tutorial_destroy(state, done.clamp(0.0, 1.0));
|
||||
let ticks = self.mining.as_ref().map_or(0, |m| m.ticks) + 1;
|
||||
if done >= 1.0 {
|
||||
let seq = self.next_sequence();
|
||||
@@ -4726,6 +4792,7 @@ impl Game<'_> {
|
||||
}
|
||||
/// MultiPlayerGameMode.destroyBlock: the predicted break.
|
||||
fn predict_break(&mut self, pos: [i32; 3], state: BlockStateId, sequence: i32) {
|
||||
self.tutorial_destroy(state, 1.0);
|
||||
// BlockStatePredictionHandler.retainKnownServerState.
|
||||
if let Some(r) = self.remote.as_mut() {
|
||||
r.predictions
|
||||
@@ -4848,18 +4915,32 @@ impl Game<'_> {
|
||||
.and_then(|h| self.res.items().get(&h.item))
|
||||
.is_some_and(|i| i.block.is_some());
|
||||
if let Some(hit) = self.pick() {
|
||||
// ShelfBlock.useItemOn consumes a nonempty main-hand click client-side
|
||||
// without predicting the inventory swap or swinging.
|
||||
let shelf_reacts = hand == Hand::Main
|
||||
&& held.is_some()
|
||||
&& !(self.keys.contains(&KeyCode::ShiftLeft)
|
||||
&& (self.stack_in(self.selected).is_some() || self.offhand.is_some()))
|
||||
&& self.shelf_hit(&hit).is_some();
|
||||
let spectator = self.spectator();
|
||||
let (reacts, changes) = if hand == Hand::Main && !spectator {
|
||||
let (result, changes) = if hand == Hand::Main && !spectator {
|
||||
self.remote_block_reacts(&hit, held.as_ref())
|
||||
} else {
|
||||
// useItemOn runs for the off hand too (useWithoutItem doesn't):
|
||||
// RespawnAnchorBlock.useItemOn charges an anchor with its glowstone.
|
||||
// RespawnAnchorBlock.useItemOn charges an anchor with its
|
||||
// glowstone, SUCCESS.
|
||||
let sneaking = self.keys.contains(&KeyCode::ShiftLeft);
|
||||
let hands_full = self.stack_in(self.selected).is_some() || self.offhand.is_some();
|
||||
let charges =
|
||||
!(sneaking && hands_full) && self.anchor_takes(hit.pos, held.as_ref());
|
||||
(charges, DryUse::default())
|
||||
let result = if charges {
|
||||
InteractionResult::Success
|
||||
} else {
|
||||
InteractionResult::Pass
|
||||
};
|
||||
(result, DryUse::default())
|
||||
};
|
||||
let reacts = result.consumes_action();
|
||||
let seq = self.next_sequence();
|
||||
let cursor = [
|
||||
(hit.point[0] - f64::from(hit.pos[0])) as f32,
|
||||
@@ -4887,9 +4968,17 @@ impl Game<'_> {
|
||||
if let Some(r) = self.remote.as_mut() {
|
||||
r.use_target = Some(hit.pos);
|
||||
}
|
||||
if shelf_reacts {
|
||||
return true;
|
||||
}
|
||||
if reacts {
|
||||
self.predict_use(&changes, seq);
|
||||
self.open_command_block_if_editable((hit.pos[0], hit.pos[1], hit.pos[2]));
|
||||
// MultiPlayerGameMode.useItemOn: SUCCESS SwingSource.CLIENT
|
||||
// (a chest, barrel, furnace, …). CONSUME does not swing.
|
||||
if result == InteractionResult::Success {
|
||||
self.with_hand(hand, Game::swing_arm);
|
||||
}
|
||||
} else if self.predict_campfire_douse(hit.pos, held.as_ref(), seq) {
|
||||
// CampfireBlock.useItemOn succeeded: this hand swings and the
|
||||
// click ends, so a shield in the other hand is not raised.
|
||||
@@ -4928,7 +5017,7 @@ impl Game<'_> {
|
||||
// server's resend of the target it refused is no placement.
|
||||
r.placing = None;
|
||||
}
|
||||
if reacts || placed.is_some() {
|
||||
if placed.is_some() {
|
||||
self.with_hand(hand, Game::swing_arm);
|
||||
}
|
||||
// BlockItem.place returns FAIL when placement is rejected:
|
||||
@@ -5020,20 +5109,20 @@ impl Game<'_> {
|
||||
&mut self,
|
||||
hit: &super::Hit,
|
||||
held: Option<&ItemStack>,
|
||||
) -> (bool, DryUse) {
|
||||
) -> (InteractionResult, DryUse) {
|
||||
// Player.isSecondaryUseActive with something in either hand.
|
||||
let hands_full = self.stack_in(self.selected).is_some() || self.offhand.is_some();
|
||||
if self.keys.contains(&KeyCode::ShiftLeft) && hands_full {
|
||||
return (false, DryUse::default());
|
||||
return (InteractionResult::Pass, DryUse::default());
|
||||
}
|
||||
if self.jukebox_takes(hit.pos, held) {
|
||||
return (true, DryUse::default());
|
||||
return (InteractionResult::Success, DryUse::default());
|
||||
}
|
||||
// RespawnAnchorBlock.useItemOn: PASS when the off hand's glowstone is to
|
||||
// charge the anchor, so useWithoutItem doesn't run and the off hand's turn
|
||||
// comes (glowstone in this hand charges it in `use_item_on`).
|
||||
if !self.anchor_takes(hit.pos, held) && self.anchor_takes(hit.pos, self.offhand.as_ref()) {
|
||||
return (false, DryUse::default());
|
||||
return (InteractionResult::Pass, DryUse::default());
|
||||
}
|
||||
// BeehiveBlock.useItemOn: shears or a glass bottle on a full hive
|
||||
// (honey level 5) is SUCCESS, so the click ends after UseItemOn and
|
||||
@@ -5043,7 +5132,7 @@ impl Game<'_> {
|
||||
.hive_state(hit.pos)
|
||||
.is_some_and(|(honey, _)| honey == Some(5))
|
||||
{
|
||||
return (true, DryUse::default());
|
||||
return (InteractionResult::Success, DryUse::default());
|
||||
}
|
||||
use horizon_core::geometry::Direction;
|
||||
let face = match hit.face {
|
||||
@@ -5057,18 +5146,16 @@ impl Game<'_> {
|
||||
let player = self.use_context();
|
||||
self.with_level(|b, level, _| {
|
||||
let mut level = DryLevel::new(level);
|
||||
let reacts = b
|
||||
.use_item_on(
|
||||
&mut level,
|
||||
&mut horizon_world::level::Ticks::default(),
|
||||
horizon_core::position::BlockPos::new(hit.pos[0], hit.pos[1], hit.pos[2]),
|
||||
face,
|
||||
horizon_core::geometry::Vec3::new(hit.point[0], hit.point[1], hit.point[2]),
|
||||
player,
|
||||
held.map(|s| &*s.item),
|
||||
)
|
||||
.consumes_action();
|
||||
(reacts, (level.changed.into_iter().collect(), level.sounds))
|
||||
let result = b.use_item_on(
|
||||
&mut level,
|
||||
&mut horizon_world::level::Ticks::default(),
|
||||
horizon_core::position::BlockPos::new(hit.pos[0], hit.pos[1], hit.pos[2]),
|
||||
face,
|
||||
horizon_core::geometry::Vec3::new(hit.point[0], hit.point[1], hit.point[2]),
|
||||
player,
|
||||
held.map(|s| &*s.item),
|
||||
);
|
||||
(result, (level.changed.into_iter().collect(), level.sounds))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5696,6 +5783,9 @@ impl Game<'_> {
|
||||
}
|
||||
KeyCode::KeyE => {
|
||||
// The inventory opens without telling the server.
|
||||
if self.tutorial.open_inventory() {
|
||||
self.persist_tutorial_step();
|
||||
}
|
||||
self.recipe_ui.reset_screen();
|
||||
if self.creative {
|
||||
self.screen = Screen::Creative;
|
||||
|
||||
@@ -35,11 +35,18 @@ impl Game<'_> {
|
||||
m.extra.leash = Some(Holder::Knot(block));
|
||||
}
|
||||
}
|
||||
} else if kind == "minecraft:text_display" {
|
||||
} else if let Some(shows) = match kind {
|
||||
"minecraft:text_display" => Some(super::text_displays::Shows::Text),
|
||||
"minecraft:block_display" => Some(super::text_displays::Shows::Block),
|
||||
"minecraft:item_display" => Some(super::text_displays::Shows::Item),
|
||||
_ => None,
|
||||
} {
|
||||
self.text_displays.insert(
|
||||
id as i32,
|
||||
super::text_displays::RemoteTextDisplay::new(position, yaw),
|
||||
super::text_displays::RemoteTextDisplay::new(shows, position, yaw),
|
||||
);
|
||||
} else if kind == "minecraft:ominous_item_spawner" {
|
||||
self.add_remote_ominous_item_spawner(id, position);
|
||||
} else if let Some(cart) = super::minecarts::CartKind::from_id(kind) {
|
||||
let mut c = super::minecarts::Minecart::new(id, cart, at);
|
||||
c.yaw = yaw;
|
||||
@@ -119,13 +126,33 @@ impl Game<'_> {
|
||||
|
||||
/// `handleSetEntityData` for an object: what changes how it looks.
|
||||
pub(super) fn remote_object_data(&mut self, id: i32, values: &[(u8, i32, Value)]) {
|
||||
if let Some(display) = self.text_displays.get_mut(&id) {
|
||||
display.metadata(values);
|
||||
if self.text_displays.contains_key(&id) {
|
||||
// ItemDisplay.DATA_ITEM_STACK, read with the server's registries.
|
||||
let item = values
|
||||
.iter()
|
||||
.find_map(|(index, _, value)| match (index, value) {
|
||||
(23, Value::Item(stack)) => Some(self.to_stack(stack)),
|
||||
_ => None,
|
||||
});
|
||||
if let Some(display) = self.text_displays.get_mut(&id) {
|
||||
display.show(values, item);
|
||||
display.metadata(values);
|
||||
}
|
||||
return;
|
||||
}
|
||||
let id = id as u32;
|
||||
for (index, _, value) in values {
|
||||
if self.item_frames.iter().any(|f| f.id == id) {
|
||||
if let (8, Value::Item(stack)) = (index, value)
|
||||
&& self.ominous_spawners.iter().any(|s| s.id == id)
|
||||
{
|
||||
// OminousItemSpawner.DATA_ITEM.
|
||||
if let (Some(item), Some(s)) = (
|
||||
self.to_stack(stack),
|
||||
self.ominous_spawners.iter_mut().find(|s| s.id == id),
|
||||
) {
|
||||
s.item = item;
|
||||
}
|
||||
} else if self.item_frames.iter().any(|f| f.id == id) {
|
||||
// HangingEntity.DATA_DIRECTION, ItemFrame.DATA_ITEM and DATA_ROTATION.
|
||||
let item = match (index, value) {
|
||||
(9, Value::Item(stack)) => Some(self.to_stack(stack)),
|
||||
@@ -319,6 +346,7 @@ impl Game<'_> {
|
||||
/// Every object a server sent (a new connection, level or dimension).
|
||||
pub(super) fn clear_remote_objects(&mut self) {
|
||||
self.text_displays.clear();
|
||||
self.ominous_spawners.clear();
|
||||
self.remote_knots.clear();
|
||||
self.carts.clear();
|
||||
self.boats.clear();
|
||||
@@ -330,6 +358,7 @@ impl Game<'_> {
|
||||
|
||||
pub(super) fn remove_remote_object(&mut self, id: u32) {
|
||||
self.text_displays.remove(&(id as i32));
|
||||
self.ominous_spawners.retain(|s| s.id != id);
|
||||
self.remote_knots.remove(&id);
|
||||
self.carts.retain(|c| c.id != id);
|
||||
self.boats.retain(|b| b.id != id);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
//! and `handleSetEntityData`): the server simulates them, the client draws
|
||||
//! them where it says and interpolates their moves.
|
||||
use super::{FallStep, FallingBlock, Game, fireworks::fireworks_of, view_vector};
|
||||
use horizon_client::items::ItemStack;
|
||||
use horizon_client::projectiles::{Hit, Projectile, ProjectileKind, ProjectileWorld};
|
||||
use horizon_client::{
|
||||
collisions::SceneCollisions,
|
||||
@@ -113,6 +114,10 @@ fn projectile_kind(kind: &str) -> Option<ProjectileKind> {
|
||||
fn remote_arrow_tick(p: &mut Projectile, w: &impl ProjectileWorld) {
|
||||
p.previous = p.position;
|
||||
p.age += 1;
|
||||
// AbstractArrow.tick: `inGroundTime` counts the ticks in a block.
|
||||
if let Some(a) = p.arrow.as_mut() {
|
||||
a.in_ground_time = if p.stuck { a.in_ground_time + 1 } else { 0 };
|
||||
}
|
||||
if p.stuck {
|
||||
return;
|
||||
}
|
||||
@@ -154,6 +159,51 @@ pub(super) fn has_smoke_trail(kind: &ProjectileKind) -> bool {
|
||||
}
|
||||
|
||||
impl Game<'_> {
|
||||
/// `Arrow.tick` and `SpectralArrow.tick` on a client: a tipped arrow
|
||||
/// sheds two entity-effect swirls in its colour while flying, one every
|
||||
/// fifth tick in a block; a spectral arrow an effect sparkle at its
|
||||
/// position while flying.
|
||||
fn arrow_effect_particles(&mut self) {
|
||||
// The arrow's box: `EntityType.ARROW` is 0.5 x 0.5.
|
||||
const SIZE: f64 = 0.5;
|
||||
for p in &self.creatures.projectiles {
|
||||
let Some(a) = p.arrow.as_ref().filter(|_| p.is_arrow()) else {
|
||||
continue;
|
||||
};
|
||||
let at = p.position;
|
||||
if p.is_spectral_arrow() {
|
||||
if !p.stuck {
|
||||
let options = ParticleOptions::simple(ParticleType::Effect);
|
||||
self.particles.add(&options, [at.x, at.y, at.z], [0.0; 3]);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let color = a.effect_color;
|
||||
let count = match (p.stuck, a.in_ground_time % 5) {
|
||||
(true, 0) => 1,
|
||||
(true, _) => 0,
|
||||
(false, _) => 2,
|
||||
};
|
||||
if color == -1 {
|
||||
continue;
|
||||
}
|
||||
// ColorParticleOption.create(ENTITY_EFFECT, colour): ARGB as floats.
|
||||
let channel = |shift: u32| (color as u32 >> shift & 255) as f32 / 255.0;
|
||||
let options = ParticleOptions::colored(
|
||||
ParticleType::EntityEffect,
|
||||
[channel(16), channel(8), channel(0), channel(24)],
|
||||
);
|
||||
for _ in 0..count {
|
||||
// getRandomX(0.5), getRandomY, getRandomZ(0.5).
|
||||
let r = self.particles.random();
|
||||
let x = at.x + SIZE * (2.0 * r.next_double() - 1.0) * 0.5;
|
||||
let y = at.y + SIZE * r.next_double();
|
||||
let z = at.z + SIZE * (2.0 * r.next_double() - 1.0) * 0.5;
|
||||
self.particles.add(&options, [x, y, z], [0.0; 3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `AbstractHurtingProjectile.createParticleTrail`: one smoke puff half a
|
||||
/// block above each position.
|
||||
pub(super) fn smoke_trails(&mut self, at: impl IntoIterator<Item = Vec3>) {
|
||||
@@ -253,6 +303,12 @@ impl Game<'_> {
|
||||
.llama_spit_burst(position, [velocity.x, velocity.y, velocity.z]);
|
||||
}
|
||||
let mut p = Projectile::new(k, at, None);
|
||||
// SpectralArrow: its own type; its pickup item says so.
|
||||
if kind == "minecraft:spectral_arrow"
|
||||
&& let Some(a) = p.arrow.as_mut()
|
||||
{
|
||||
a.pickup_item = ItemStack::new("minecraft:spectral_arrow", 1);
|
||||
}
|
||||
p.id = id;
|
||||
p.velocity = velocity;
|
||||
p.yaw = yaw;
|
||||
@@ -370,6 +426,12 @@ impl Game<'_> {
|
||||
(ProjectileKind::Arrow { .. }, 10, Value::Bool(in_ground)) => {
|
||||
p.stuck = *in_ground;
|
||||
}
|
||||
// Arrow.ID_EFFECT_COLOR.
|
||||
(ProjectileKind::Arrow { .. }, 11, Value::Int(color)) => {
|
||||
if let Some(a) = p.arrow.as_mut() {
|
||||
a.effect_color = *color;
|
||||
}
|
||||
}
|
||||
// WitherSkull.DATA_DANGEROUS.
|
||||
(ProjectileKind::WitherSkull { dangerous }, 8, Value::Bool(d)) => {
|
||||
*dangerous = *d;
|
||||
@@ -471,6 +533,7 @@ impl Game<'_> {
|
||||
}
|
||||
self.creatures.projectiles = projectiles;
|
||||
self.tridents = tridents;
|
||||
self.arrow_effect_particles();
|
||||
let trails: Vec<Vec3> = self
|
||||
.creatures
|
||||
.projectiles
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
//! The displayed items of `ShelfBlockEntity`.
|
||||
use super::{Game, Hit, machines::dir_from_name};
|
||||
use horizon_assets::item_model::DisplayContext;
|
||||
use horizon_client::{block_entity_render::vertex_light, containers::BlockEntity, mesher::Vertex};
|
||||
use horizon_core::{
|
||||
geometry::{Direction, Vec3},
|
||||
position::BlockPos,
|
||||
};
|
||||
use horizon_world::level::hit_slot;
|
||||
|
||||
impl Game<'_> {
|
||||
/// Slot and facing for a click on a shelf's front face.
|
||||
pub(super) fn shelf_hit(&self, hit: &Hit) -> Option<(usize, Direction, bool)> {
|
||||
let p = hit.pos;
|
||||
if !matches!(
|
||||
self.world.block_entities.get(&(p[0], p[1], p[2])),
|
||||
Some(BlockEntity::Shelf(_))
|
||||
) {
|
||||
return None;
|
||||
}
|
||||
let state = self.world.block(p[0], p[1], p[2]);
|
||||
let data = self.pack.assets.registry.state(state)?;
|
||||
let facing = data
|
||||
.properties
|
||||
.get("facing")
|
||||
.and_then(|s| dir_from_name(s))?;
|
||||
let face = Direction::ALL
|
||||
.into_iter()
|
||||
.find(|d| d.normal() == hit.face)?;
|
||||
let slot = hit_slot(
|
||||
facing,
|
||||
face,
|
||||
BlockPos::new(p[0], p[1], p[2]),
|
||||
Vec3::new(hit.point[0], hit.point[1], hit.point[2]),
|
||||
1,
|
||||
3,
|
||||
)?;
|
||||
Some((
|
||||
slot,
|
||||
facing,
|
||||
data.properties.get("powered").is_some_and(|s| s == "true"),
|
||||
))
|
||||
}
|
||||
|
||||
/// `SideChainPartBlock.getAllBlocksConnectedTo`: left to right, at most three.
|
||||
fn shelf_chain(&self, pos: [i32; 3], facing: Direction) -> Vec<[i32; 3]> {
|
||||
let left = match facing {
|
||||
Direction::North => Direction::East,
|
||||
Direction::East => Direction::South,
|
||||
Direction::South => Direction::West,
|
||||
Direction::West => Direction::North,
|
||||
_ => return Vec::new(),
|
||||
};
|
||||
let state = self.world.block(pos[0], pos[1], pos[2]);
|
||||
let behaviours = self.res.behaviours();
|
||||
if !behaviours
|
||||
.tag("minecraft:wooden_shelves")
|
||||
.is_some_and(|tag| tag.contains(behaviours.block(state)))
|
||||
{
|
||||
return Vec::new();
|
||||
}
|
||||
let mut result = vec![pos];
|
||||
for (dir, end, prepend) in [(left, "left", true), (left.opposite(), "right", false)] {
|
||||
let normal = dir.normal();
|
||||
for step in 1..3 {
|
||||
let p = [pos[0] + normal[0] * step, pos[1], pos[2] + normal[2] * step];
|
||||
let state = self.world.block(p[0], p[1], p[2]);
|
||||
let Some(info) = self.pack.assets.registry.state(state) else {
|
||||
break;
|
||||
};
|
||||
let behaviours = self.res.behaviours();
|
||||
let connectable = behaviours
|
||||
.tag("minecraft:wooden_shelves")
|
||||
.is_some_and(|tag| tag.contains(behaviours.block(state)))
|
||||
&& info.properties.get("powered").is_some_and(|s| s == "true")
|
||||
&& info.properties.get("facing").and_then(|s| dir_from_name(s)) == Some(facing);
|
||||
if !connectable {
|
||||
break;
|
||||
}
|
||||
let part = info
|
||||
.properties
|
||||
.get("side_chain")
|
||||
.map(String::as_str)
|
||||
.unwrap_or("unconnected");
|
||||
if part == "center" || part == end {
|
||||
if prepend {
|
||||
result.insert(0, p);
|
||||
} else {
|
||||
result.push(p);
|
||||
}
|
||||
}
|
||||
if part != "center" {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// `ShelfBlock.useItemOn`: a single swap, or a connected hotbar swap while powered.
|
||||
pub(super) fn use_shelf(&mut self, hit: &Hit) -> Option<bool> {
|
||||
use horizon_client::first_person::Hand;
|
||||
if self.hand != Hand::Main {
|
||||
return None;
|
||||
}
|
||||
let (slot, facing, powered) = self.shelf_hit(hit)?;
|
||||
let pos = hit.pos;
|
||||
let state = self.world.block(pos[0], pos[1], pos[2]);
|
||||
if powered {
|
||||
let chain = self.shelf_chain(pos, facing);
|
||||
let mut swapped = false;
|
||||
let count = chain.len();
|
||||
for (i, p) in chain.into_iter().enumerate() {
|
||||
let key = (p[0], p[1], p[2]);
|
||||
let Some(BlockEntity::Shelf(shelf)) = self.world.block_entities.get_mut(&key)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
for n in 0..3 {
|
||||
let index = 9_i32 - (count as i32 - i as i32) * 3 + n as i32;
|
||||
if !(0..9).contains(&index) {
|
||||
continue;
|
||||
}
|
||||
let index = index as usize;
|
||||
swapped |= self.inventory[index].is_some() || shelf.items[n].is_some();
|
||||
std::mem::swap(&mut self.inventory[index], &mut shelf.items[n]);
|
||||
}
|
||||
self.container_changed(key);
|
||||
let s = self.world.block(p[0], p[1], p[2]);
|
||||
self.post_block_game_event("minecraft:entity_interact", p, None, Some(s));
|
||||
}
|
||||
if !swapped {
|
||||
return Some(false);
|
||||
}
|
||||
self.audio.play(
|
||||
"minecraft:block.shelf.multi_swap",
|
||||
Some(pos.map(|n| f64::from(n) + 0.5)),
|
||||
1.0,
|
||||
1.0,
|
||||
);
|
||||
Some(true)
|
||||
} else {
|
||||
let held = self.inventory[self.selected].clone();
|
||||
let key = (pos[0], pos[1], pos[2]);
|
||||
let Some(BlockEntity::Shelf(shelf)) = self.world.block_entities.get_mut(&key) else {
|
||||
return None;
|
||||
};
|
||||
let removed = std::mem::replace(&mut shelf.items[slot], held.clone());
|
||||
self.inventory[self.selected] = if self.creative && removed.is_none() {
|
||||
held.clone()
|
||||
} else {
|
||||
removed.clone()
|
||||
};
|
||||
self.container_changed(key);
|
||||
let vibrations = self.inventory[self.selected].as_ref().is_none_or(|stack| {
|
||||
let default = self
|
||||
.res
|
||||
.items()
|
||||
.get(&stack.item)
|
||||
.is_none_or(|item| item.interact_vibrations);
|
||||
let Some(data) = stack.data() else {
|
||||
return default;
|
||||
};
|
||||
if data
|
||||
.other
|
||||
.iter()
|
||||
.any(|(key, _)| key == "!minecraft:use_effects")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
data.other
|
||||
.iter()
|
||||
.find(|(key, _)| key == "minecraft:use_effects")
|
||||
.map_or(default, |(_, tag)| {
|
||||
horizon_client::save::number(tag.get("interact_vibrations")).unwrap_or(1)
|
||||
!= 0
|
||||
})
|
||||
});
|
||||
if vibrations {
|
||||
self.post_block_game_event(
|
||||
"minecraft:item_interact_finish",
|
||||
pos,
|
||||
None,
|
||||
Some(state),
|
||||
);
|
||||
}
|
||||
let sound = match (held.is_some(), removed.is_some()) {
|
||||
(false, false) => return None,
|
||||
(true, false) => "minecraft:block.shelf.place_item",
|
||||
(false, true) => "minecraft:block.shelf.take_item",
|
||||
(true, true) => "minecraft:block.shelf.single_swap",
|
||||
};
|
||||
self.audio
|
||||
.play(sound, Some(pos.map(|n| f64::from(n) + 0.5)), 1.0, 1.0);
|
||||
Some(true)
|
||||
}
|
||||
}
|
||||
|
||||
/// `ShelfRenderer`: three items face out, aligned to the bottom or centre.
|
||||
pub(super) fn shelf_geometry(&self, camera: glam::DVec3, out: &mut Vec<Vertex>) {
|
||||
for (key, entity) in &self.world.block_entities {
|
||||
let BlockEntity::Shelf(shelf) = entity else {
|
||||
continue;
|
||||
};
|
||||
let at = glam::DVec3::new(f64::from(key.0), f64::from(key.1), f64::from(key.2));
|
||||
if at.distance_squared(camera) >= 64.0 * 64.0 {
|
||||
continue;
|
||||
}
|
||||
let block = self.world.block(key.0, key.1, key.2);
|
||||
let facing = self
|
||||
.pack
|
||||
.assets
|
||||
.registry
|
||||
.state(block)
|
||||
.and_then(|s| s.properties.get("facing"))
|
||||
.map(String::as_str)
|
||||
.unwrap_or("north");
|
||||
let yaw: f32 = match facing {
|
||||
"south" => 0.0,
|
||||
"west" => -90.0,
|
||||
"north" => -180.0,
|
||||
"east" => -270.0,
|
||||
_ => 180.0,
|
||||
};
|
||||
let packed = BlockPos::new(key.0, key.1, key.2).pack() as u64;
|
||||
let seed = (packed ^ (packed >> 32)) as i32;
|
||||
let light = vertex_light(self.world.light(key.0, key.1, key.2));
|
||||
for (slot, stack) in shelf.items.iter().enumerate() {
|
||||
let Some(stack) = stack else { continue };
|
||||
let item = self.resolve_item(
|
||||
stack,
|
||||
DisplayContext::OnShelf,
|
||||
None,
|
||||
seed.wrapping_add(slot as i32),
|
||||
);
|
||||
if item.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let (lo, hi) = item.bounding_box();
|
||||
let offset_y = -lo.y
|
||||
- if shelf.align_to_bottom {
|
||||
0.0
|
||||
} else {
|
||||
(hi.y - lo.y) / 2.0
|
||||
};
|
||||
let pose =
|
||||
glam::Mat4::from_translation((at - camera).as_vec3() + glam::Vec3::splat(0.5))
|
||||
* glam::Mat4::from_rotation_y(yaw.to_radians())
|
||||
* glam::Mat4::from_translation(glam::Vec3::new(
|
||||
(slot as f32 - 1.0) * 0.3125,
|
||||
if shelf.align_to_bottom { -0.25 } else { 0.0 },
|
||||
-0.25,
|
||||
))
|
||||
* glam::Mat4::from_scale(glam::Vec3::splat(0.25))
|
||||
* glam::Mat4::from_translation(glam::Vec3::new(0.0, offset_y, 0.0));
|
||||
self.submit_item_world(&item, pose, light, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,7 @@ impl StartupMenu {
|
||||
&self.root,
|
||||
);
|
||||
title.last_server = settings.file.string("lastServer");
|
||||
title.skip_multiplayer_warning = settings.file.bool("skipMultiplayerWarning");
|
||||
self.title = Some(title);
|
||||
}
|
||||
}
|
||||
@@ -304,12 +305,11 @@ impl Loading {
|
||||
}
|
||||
|
||||
fn save_menu_address(&mut self) {
|
||||
if self
|
||||
.menu
|
||||
.title
|
||||
.as_ref()
|
||||
.is_some_and(|title| title.last_server != self.settings.file.string("lastServer"))
|
||||
{
|
||||
if self.menu.title.as_ref().is_some_and(|title| {
|
||||
title.last_server != self.settings.file.string("lastServer")
|
||||
|| title.skip_multiplayer_warning
|
||||
!= self.settings.file.bool("skipMultiplayerWarning")
|
||||
}) {
|
||||
self.save_menu_settings();
|
||||
}
|
||||
}
|
||||
@@ -320,6 +320,10 @@ impl Loading {
|
||||
"lastServer",
|
||||
horizon_client::options_file::V::Str(title.last_server.clone()),
|
||||
);
|
||||
self.settings.file.set(
|
||||
"skipMultiplayerWarning",
|
||||
horizon_client::options_file::V::Bool(title.skip_multiplayer_warning),
|
||||
);
|
||||
}
|
||||
self.settings.save(&Game::options_path());
|
||||
}
|
||||
@@ -365,6 +369,15 @@ impl Loading {
|
||||
let _ = std::fs::create_dir_all(&dir);
|
||||
packs::open_folder(&dir);
|
||||
}
|
||||
Event::DataPacks(ids) => {
|
||||
if let Some(title) = &mut self.menu.title {
|
||||
title.set_data_packs(ids);
|
||||
}
|
||||
}
|
||||
Event::OpenDataPackFolder(dir) => {
|
||||
let _ = std::fs::create_dir_all(&dir);
|
||||
packs::open_folder(&dir);
|
||||
}
|
||||
Event::OpenModFolder => {
|
||||
let dir = Game::options_path().with_file_name(horizon_client::mod_list::DIR);
|
||||
let _ = std::fs::create_dir_all(&dir);
|
||||
@@ -426,6 +439,11 @@ impl Loading {
|
||||
Action::Options => self.open_menu_options(Page::Main),
|
||||
Action::Accessibility => self.open_menu_options(Page::Accessibility),
|
||||
Action::Language => self.open_menu_options(Page::Language),
|
||||
Action::DataPacks { dir, selected } => {
|
||||
let resources = self.vanilla_root.clone();
|
||||
self.menu.options.open_data_packs(dir, selected, resources);
|
||||
self.menu.options_open = true;
|
||||
}
|
||||
action => {
|
||||
let name = self
|
||||
.options
|
||||
|
||||
@@ -1,15 +1,34 @@
|
||||
//! Server text displays: metadata, interpolation, billboard transforms and world text.
|
||||
use super::Game;
|
||||
use horizon_assets::item_model::DisplayContext;
|
||||
use horizon_client::{
|
||||
gfx::TEXT_POLYGON_OFFSET_OVERLAY,
|
||||
hud::HudBatch,
|
||||
items::ItemStack,
|
||||
mesher::Vertex,
|
||||
text_display::{self, Lines, TextDisplayData},
|
||||
};
|
||||
use horizon_protocol::entity_data::Value;
|
||||
use horizon_world::registry::BlockStateId;
|
||||
|
||||
/// What a display shows besides its transformation: its text (in `data`),
|
||||
/// a block state (`BlockDisplay`) or an item and how it is displayed
|
||||
/// (`ItemDisplay`).
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) enum Shows {
|
||||
Text,
|
||||
Block,
|
||||
Item,
|
||||
}
|
||||
|
||||
pub(super) struct RemoteTextDisplay {
|
||||
pub data: TextDisplayData,
|
||||
shows: Shows,
|
||||
/// `BlockDisplay.DATA_BLOCK_STATE_ID`.
|
||||
block: Option<BlockStateId>,
|
||||
/// `ItemDisplay.DATA_ITEM_STACK` and `DATA_ITEM_DISPLAY_ID`.
|
||||
item: Option<ItemStack>,
|
||||
item_display: u8,
|
||||
from: TextDisplayData,
|
||||
shown: TextDisplayData,
|
||||
position: glam::DVec3,
|
||||
@@ -41,9 +60,13 @@ fn renders(id: u8) -> bool {
|
||||
}
|
||||
|
||||
impl RemoteTextDisplay {
|
||||
pub fn new(position: [f64; 3], yaw: f32) -> Self {
|
||||
pub fn new(shows: Shows, position: [f64; 3], yaw: f32) -> Self {
|
||||
Self {
|
||||
data: Default::default(),
|
||||
shows,
|
||||
block: None,
|
||||
item: None,
|
||||
item_display: 0,
|
||||
from: Default::default(),
|
||||
shown: Default::default(),
|
||||
position: position.into(),
|
||||
@@ -71,6 +94,24 @@ impl RemoteTextDisplay {
|
||||
(((self.age - self.start) as f32 + partial) / self.duration as f32).clamp(0.0, 1.0)
|
||||
}
|
||||
}
|
||||
/// The entries after `Display`'s: a block state, or an item with its
|
||||
/// display context (the stack comes converted by the caller).
|
||||
pub fn show(&mut self, values: &[(u8, i32, Value)], item: Option<Option<ItemStack>>) {
|
||||
for (id, _, value) in values {
|
||||
match (self.shows, id, value) {
|
||||
(Shows::Block, 23, Value::Id(state)) => {
|
||||
self.block = Some(BlockStateId(u32::try_from(*state).unwrap_or(0)));
|
||||
}
|
||||
(Shows::Item, 24, Value::Byte(context)) => self.item_display = *context as u8,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if let Some(item) = item
|
||||
&& self.shows == Shows::Item
|
||||
{
|
||||
self.item = item;
|
||||
}
|
||||
}
|
||||
pub fn metadata(&mut self, values: &[(u8, i32, Value)]) {
|
||||
self.start_dirty |= values.iter().any(|v| v.0 == 8);
|
||||
self.duration_dirty |= values.iter().any(|v| v.0 == 9);
|
||||
@@ -143,6 +184,96 @@ impl Game<'_> {
|
||||
display.tick();
|
||||
}
|
||||
}
|
||||
/// The camera's yaw and pitch as `DisplayRenderer` reads them (the
|
||||
/// front view turns around).
|
||||
fn display_camera_angles(&self) -> [f32; 2] {
|
||||
if self.camera_type == super::perspective::CameraType::ThirdPersonFront {
|
||||
[self.yaw + 180.0, -self.pitch]
|
||||
} else {
|
||||
[self.yaw, self.pitch]
|
||||
}
|
||||
}
|
||||
/// `DisplayRenderer.getBlockLightLevel` and `getSkyLightLevel`: the
|
||||
/// brightness override, else the light at the entity.
|
||||
fn display_light(
|
||||
world: &horizon_client::world::World,
|
||||
d: &TextDisplayData,
|
||||
position: glam::DVec3,
|
||||
) -> u32 {
|
||||
if d.brightness == -1 {
|
||||
let world_light = world.light(
|
||||
position.x.floor() as i32,
|
||||
position.y.floor() as i32,
|
||||
position.z.floor() as i32,
|
||||
);
|
||||
horizon_client::block_entity_render::vertex_light(world_light)
|
||||
} else {
|
||||
((d.brightness as u32 >> 20 & 15) * 16) | (((d.brightness as u32 >> 4 & 15) * 16) << 8)
|
||||
}
|
||||
}
|
||||
/// `DisplayRenderer.BlockDisplayRenderer` and `ItemDisplayRenderer`: the
|
||||
/// block model or the item under the display's transformation, lit like
|
||||
/// any entity and never tinted by a hurt overlay.
|
||||
pub(super) fn block_item_display_geometry(
|
||||
&mut self,
|
||||
camera: glam::DVec3,
|
||||
partial: f64,
|
||||
out: &mut Vec<Vertex>,
|
||||
) {
|
||||
let angles = self.display_camera_angles();
|
||||
let scale = self.view_scale();
|
||||
let mut displays = std::mem::take(&mut self.text_displays);
|
||||
for shows in [Shows::Block, Shows::Item] {
|
||||
let kind = if shows == Shows::Block {
|
||||
"minecraft:block_display"
|
||||
} else {
|
||||
"minecraft:item_display"
|
||||
};
|
||||
self.mark_entity(out.len(), kind);
|
||||
for (id, display) in displays.iter_mut().filter(|(_, d)| d.shows == shows) {
|
||||
display.last_progress = display.progress(partial as f32);
|
||||
let d = display.shown.blend(&display.from, display.last_progress);
|
||||
let position = display.previous.lerp(display.position, partial);
|
||||
let range = f64::from(d.view_range) * 64.0 * scale;
|
||||
if position.distance_squared(camera) >= range * range {
|
||||
continue;
|
||||
}
|
||||
let rotation = std::array::from_fn(|i| {
|
||||
display.old_rotation[i]
|
||||
+ angle(display.rotation[i] - display.old_rotation[i]) * partial as f32
|
||||
});
|
||||
let matrix = glam::Mat4::from_translation((position - camera).as_vec3())
|
||||
* d.matrix(rotation, angles);
|
||||
if !matrix.is_finite() {
|
||||
continue;
|
||||
}
|
||||
let light = Self::display_light(&self.world, &d, position);
|
||||
if let Some(state) = display.block {
|
||||
// BlockModelResolver.update: model seed 42.
|
||||
self.block_model_vertices(state, 42, matrix, light, true, out);
|
||||
} else if let Some(stack) = &display.item {
|
||||
let context = match display.item_display {
|
||||
1 => DisplayContext::ThirdPersonLeftHand,
|
||||
2 => DisplayContext::ThirdPersonRightHand,
|
||||
3 => DisplayContext::FirstPersonLeftHand,
|
||||
4 => DisplayContext::FirstPersonRightHand,
|
||||
5 => DisplayContext::Head,
|
||||
6 => DisplayContext::Gui,
|
||||
7 => DisplayContext::Ground,
|
||||
8 => DisplayContext::Fixed,
|
||||
9 => DisplayContext::OnShelf,
|
||||
_ => DisplayContext::None,
|
||||
};
|
||||
// updateForNonLiving: seeded by the entity id.
|
||||
let state = self.resolve_item(stack, context, None, *id);
|
||||
let pose = matrix * glam::Mat4::from_rotation_y(std::f32::consts::PI);
|
||||
self.submit_item_world(&state, pose, light, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.mark_entity(out.len(), "");
|
||||
self.text_displays = displays;
|
||||
}
|
||||
pub(super) fn text_display_geometry(
|
||||
&mut self,
|
||||
camera: glam::DVec3,
|
||||
@@ -153,14 +284,12 @@ impl Game<'_> {
|
||||
self.text_display_vertices = 0;
|
||||
self.mark_translucent_entity(normal.len(), "minecraft:text_display");
|
||||
let atlas = &self.gui.atlas;
|
||||
let front = self.camera_type == super::perspective::CameraType::ThirdPersonFront;
|
||||
let angles = if front {
|
||||
[self.yaw + 180.0, -self.pitch]
|
||||
} else {
|
||||
[self.yaw, self.pitch]
|
||||
};
|
||||
let angles = self.display_camera_angles();
|
||||
let scale = self.view_scale();
|
||||
for display in self.text_displays.values_mut() {
|
||||
if display.shows != Shows::Text {
|
||||
continue;
|
||||
}
|
||||
display.last_progress = display.progress(partial as f32);
|
||||
let d = display.shown.blend(&display.from, display.last_progress);
|
||||
let position = display.previous.lerp(display.position, partial);
|
||||
@@ -189,17 +318,7 @@ impl Game<'_> {
|
||||
lines,
|
||||
self.settings.text_background(0.25),
|
||||
);
|
||||
let world_light = self.world.light(
|
||||
position.x.floor() as i32,
|
||||
position.y.floor() as i32,
|
||||
position.z.floor() as i32,
|
||||
);
|
||||
let light = if d.brightness == -1 {
|
||||
horizon_client::block_entity_render::vertex_light(world_light)
|
||||
} else {
|
||||
((d.brightness as u32 >> 20 & 15) * 16)
|
||||
| (((d.brightness as u32 >> 4 & 15) * 16) << 8)
|
||||
};
|
||||
let light = Self::display_light(&self.world, &d, position);
|
||||
// Font.DisplayMode: SEE_THROUGH over everything, else POLYGON_OFFSET.
|
||||
let through = d.flags & 2 != 0;
|
||||
let (out, layer) = if through {
|
||||
@@ -235,7 +354,7 @@ mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn metadata_and_position_interpolation_keep_their_separate_clocks() {
|
||||
let mut d = RemoteTextDisplay::new([0.0; 3], 0.0);
|
||||
let mut d = RemoteTextDisplay::new(Shows::Text, [0.0; 3], 0.0);
|
||||
d.tick();
|
||||
d.metadata(&[
|
||||
(8, 1, Value::Int(0)),
|
||||
|
||||
@@ -0,0 +1,561 @@
|
||||
//! Client tutorial progression (`net.minecraft.client.tutorial`).
|
||||
|
||||
/// The numeric values are the order in `options_file::TUTORIAL_STEPS`.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(super) enum Step {
|
||||
Movement = 0,
|
||||
FindTree = 1,
|
||||
PunchTree = 2,
|
||||
OpenInventory = 3,
|
||||
CraftPlanks = 4,
|
||||
None = 5,
|
||||
}
|
||||
|
||||
impl Step {
|
||||
pub(super) fn from_option(value: i32) -> Self {
|
||||
match value {
|
||||
0 => Self::Movement,
|
||||
1 => Self::FindTree,
|
||||
2 => Self::PunchTree,
|
||||
3 => Self::OpenInventory,
|
||||
4 => Self::CraftPlanks,
|
||||
_ => Self::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(super) enum Hint {
|
||||
Move,
|
||||
Look,
|
||||
FindTree,
|
||||
PunchTree,
|
||||
OpenInventory,
|
||||
CraftPlanks,
|
||||
}
|
||||
|
||||
/// The observations needed on the first tick of each step.
|
||||
#[derive(Default)]
|
||||
pub(super) struct Previous {
|
||||
pub(super) tree_item: bool,
|
||||
pub(super) log: bool,
|
||||
pub(super) plank: bool,
|
||||
pub(super) mined_tree: bool,
|
||||
pub(super) crafted_plank: bool,
|
||||
}
|
||||
|
||||
pub(super) struct Tutorial {
|
||||
pub(super) step: Step,
|
||||
pub(super) waiting: u32,
|
||||
moved: bool,
|
||||
turned: bool,
|
||||
move_ticks: u32,
|
||||
look_ticks: u32,
|
||||
move_finished: Option<u32>,
|
||||
look_finished: bool,
|
||||
reset_count: u32,
|
||||
pub(super) break_progress: f32,
|
||||
}
|
||||
|
||||
impl Tutorial {
|
||||
pub(super) fn new(step: Step) -> Self {
|
||||
Self {
|
||||
step,
|
||||
waiting: 0,
|
||||
moved: false,
|
||||
turned: false,
|
||||
move_ticks: 0,
|
||||
look_ticks: 0,
|
||||
move_finished: None,
|
||||
look_finished: false,
|
||||
reset_count: 0,
|
||||
break_progress: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the persisted step changed.
|
||||
fn set_step(&mut self, step: Step) -> bool {
|
||||
if self.step == step {
|
||||
return false;
|
||||
}
|
||||
*self = Self::new(step);
|
||||
true
|
||||
}
|
||||
|
||||
pub(super) fn input(&mut self) {
|
||||
self.moved = true;
|
||||
}
|
||||
pub(super) fn mouse(&mut self, x: f64, y: f64) {
|
||||
self.turned |= x.abs() > 0.01 || y.abs() > 0.01;
|
||||
}
|
||||
|
||||
pub(super) fn tick(&mut self, survival: bool, previous: &Previous) -> bool {
|
||||
self.waiting = self.waiting.saturating_add(1);
|
||||
if self.step == Step::Movement {
|
||||
self.move_ticks += u32::from(std::mem::take(&mut self.moved));
|
||||
self.look_ticks += u32::from(std::mem::take(&mut self.turned));
|
||||
if self.move_ticks > 40 && self.move_finished.is_none() {
|
||||
self.move_finished = Some(self.waiting);
|
||||
}
|
||||
self.look_finished |= self.look_ticks > 40;
|
||||
if self.move_finished.is_some() && self.look_finished {
|
||||
return self.set_step(if survival { Step::FindTree } else { Step::None });
|
||||
}
|
||||
} else if self.step != Step::None && !survival {
|
||||
return self.set_step(Step::None);
|
||||
} else if self.waiting == 1 {
|
||||
match self.step {
|
||||
Step::FindTree if previous.tree_item || previous.mined_tree => {
|
||||
return self.set_step(Step::CraftPlanks);
|
||||
}
|
||||
Step::PunchTree if previous.log || previous.mined_tree => {
|
||||
return self.set_step(Step::CraftPlanks);
|
||||
}
|
||||
Step::CraftPlanks if previous.plank || previous.crafted_plank => {
|
||||
return self.set_step(Step::None);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub(super) fn look_at_tree(&mut self) -> bool {
|
||||
self.step == Step::FindTree && self.set_step(Step::PunchTree)
|
||||
}
|
||||
pub(super) fn destroy(&mut self, is_log: bool, progress: f32) -> bool {
|
||||
if self.step != Step::PunchTree {
|
||||
return false;
|
||||
}
|
||||
if is_log && progress > 0.0 {
|
||||
self.break_progress = progress;
|
||||
if progress >= 1.0 {
|
||||
return self.set_step(Step::OpenInventory);
|
||||
}
|
||||
} else {
|
||||
self.break_progress = 0.0;
|
||||
if is_log {
|
||||
self.reset_count = self.reset_count.saturating_add(1);
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
pub(super) fn open_inventory(&mut self) -> bool {
|
||||
self.step == Step::OpenInventory && self.set_step(Step::CraftPlanks)
|
||||
}
|
||||
pub(super) fn get_item(&mut self, tree: bool, log: bool, plank: bool) -> bool {
|
||||
match self.step {
|
||||
Step::FindTree if tree => self.set_step(Step::CraftPlanks),
|
||||
Step::PunchTree if log => self.set_step(Step::CraftPlanks),
|
||||
Step::CraftPlanks if plank => self.set_step(Step::None),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
pub(super) fn hint(&self) -> Option<(Hint, Option<f32>)> {
|
||||
match self.step {
|
||||
Step::Movement if self.waiting >= 100 && self.move_finished.is_none() => {
|
||||
Some((Hint::Move, Some(self.move_ticks as f32 / 40.0)))
|
||||
}
|
||||
Step::Movement
|
||||
if !self.look_finished
|
||||
&& self
|
||||
.move_finished
|
||||
.is_some_and(|t| self.waiting.saturating_sub(t) >= 20)
|
||||
&& self.waiting >= 100 =>
|
||||
{
|
||||
Some((Hint::Look, Some(self.look_ticks as f32 / 40.0)))
|
||||
}
|
||||
Step::FindTree if self.waiting >= 6000 => Some((Hint::FindTree, None)),
|
||||
Step::PunchTree if self.waiting >= 600 || self.reset_count > 3 => {
|
||||
Some((Hint::PunchTree, Some(self.break_progress)))
|
||||
}
|
||||
Step::OpenInventory if self.waiting >= 600 => Some((Hint::OpenInventory, None)),
|
||||
Step::CraftPlanks if self.waiting >= 1200 => Some((Hint::CraftPlanks, None)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Game<'_> {
|
||||
/// `Tutorial.setStep` persists immediately so it survives leaving a world.
|
||||
pub(super) fn persist_tutorial_step(&mut self) {
|
||||
self.settings.file.set(
|
||||
"tutorialStep",
|
||||
horizon_client::options_file::V::Int(self.tutorial.step as i32),
|
||||
);
|
||||
self.settings.save(&Self::options_path());
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Game<'_> {
|
||||
pub(super) fn reset_tutorial(&mut self) {
|
||||
self.tutorial = Tutorial::new(Step::from_option(self.settings.file.int("tutorialStep")));
|
||||
self.tutorial_toasts.clear();
|
||||
}
|
||||
|
||||
fn tutorial_tagged(&self, item: bool, tag: &str, id: &str) -> bool {
|
||||
let items = self.res.items();
|
||||
let tags = if item {
|
||||
&items.item_tags
|
||||
} else {
|
||||
&items.block_tags
|
||||
};
|
||||
tags.get(tag).is_some_and(|members| members.contains(id))
|
||||
}
|
||||
|
||||
pub(super) fn tutorial_get_item(&mut self, stack: Option<&horizon_client::items::ItemStack>) {
|
||||
// The server thread's settings are a stale copy of the window's: its
|
||||
// guests' tutorial is their own client's to record.
|
||||
if self.options.dedicated.is_some()
|
||||
|| self.serving.is_some()
|
||||
|| self.title.is_some()
|
||||
|| self.player.is_none()
|
||||
{
|
||||
return;
|
||||
}
|
||||
let Some(stack) = stack else { return };
|
||||
let id = &*stack.item;
|
||||
let tree = self.tutorial_tagged(true, "minecraft:completes_find_tree_tutorial", id);
|
||||
let log = self.tutorial_tagged(true, "minecraft:logs", id);
|
||||
let plank = self.tutorial_tagged(true, "minecraft:planks", id);
|
||||
if self.tutorial.get_item(tree, log, plank) {
|
||||
self.persist_tutorial_step();
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn tutorial_destroy(
|
||||
&mut self,
|
||||
state: horizon_world::registry::BlockStateId,
|
||||
progress: f32,
|
||||
) {
|
||||
// The server thread's settings are a stale copy of the window's: its
|
||||
// guests' tutorial is their own client's to record.
|
||||
if self.options.dedicated.is_some()
|
||||
|| self.serving.is_some()
|
||||
|| self.title.is_some()
|
||||
|| self.player.is_none()
|
||||
{
|
||||
return;
|
||||
}
|
||||
let name = &self.res.facts().get(state).name;
|
||||
let is_log = self.tutorial_tagged(false, "minecraft:logs", name);
|
||||
if self.tutorial.destroy(is_log, progress) {
|
||||
self.persist_tutorial_step();
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn tick_tutorial(&mut self) {
|
||||
use winit::keyboard::KeyCode;
|
||||
if self.title.is_some()
|
||||
|| self.player.is_none()
|
||||
|| self.options.dedicated.is_some()
|
||||
|| self.serving.is_some()
|
||||
{
|
||||
return;
|
||||
}
|
||||
if self.screen == super::Screen::Playing
|
||||
&& ([
|
||||
KeyCode::KeyW,
|
||||
KeyCode::KeyA,
|
||||
KeyCode::KeyS,
|
||||
KeyCode::KeyD,
|
||||
KeyCode::Space,
|
||||
]
|
||||
.iter()
|
||||
.any(|key| self.keys.contains(key))
|
||||
|| self
|
||||
.pad_move()
|
||||
.is_some_and(|[left, forward]| left.abs() > 0.3 || forward.abs() > 0.3))
|
||||
{
|
||||
self.tutorial.input();
|
||||
}
|
||||
let items = self.res.items();
|
||||
let in_tag =
|
||||
|tag: &str, id: &str| items.item_tags.get(tag).is_some_and(|set| set.contains(id));
|
||||
let mut previous = Previous::default();
|
||||
if self.tutorial.waiting == 0 && !matches!(self.tutorial.step, Step::Movement | Step::None)
|
||||
{
|
||||
for stack in self
|
||||
.inventory
|
||||
.iter()
|
||||
.chain(self.armor.iter())
|
||||
.flatten()
|
||||
.chain(self.offhand.iter())
|
||||
{
|
||||
previous.tree_item |= in_tag("minecraft:completes_find_tree_tutorial", &stack.item);
|
||||
previous.log |= in_tag("minecraft:logs", &stack.item);
|
||||
previous.plank |= in_tag("minecraft:planks", &stack.item);
|
||||
}
|
||||
if let Some(mined) = self.stats.values.get("minecraft:mined")
|
||||
&& let Some(tree) = items
|
||||
.block_tags
|
||||
.get("minecraft:completes_find_tree_tutorial")
|
||||
{
|
||||
previous.mined_tree = mined
|
||||
.iter()
|
||||
.any(|(id, count)| *count > 0 && tree.contains(id));
|
||||
}
|
||||
if let Some(crafted) = self.stats.values.get("minecraft:crafted")
|
||||
&& let Some(planks) = items.item_tags.get("minecraft:planks")
|
||||
{
|
||||
previous.crafted_plank = crafted
|
||||
.iter()
|
||||
.any(|(id, count)| *count > 0 && planks.contains(id));
|
||||
}
|
||||
}
|
||||
let survival = self.mode == horizon_client::command::args::GameMode::Survival;
|
||||
if self.tutorial.tick(survival, &previous) {
|
||||
self.persist_tutorial_step();
|
||||
}
|
||||
if self.tutorial.step == Step::FindTree
|
||||
&& self.pick_entity_hit().is_none()
|
||||
&& let Some(hit) = self.pick()
|
||||
{
|
||||
let name = &self
|
||||
.res
|
||||
.facts()
|
||||
.get(self.world.block(hit.pos[0], hit.pos[1], hit.pos[2]))
|
||||
.name;
|
||||
if self.tutorial_tagged(false, "minecraft:completes_find_tree_tutorial", name)
|
||||
&& self.tutorial.look_at_tree()
|
||||
{
|
||||
self.persist_tutorial_step();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct HintToast {
|
||||
hint: Hint,
|
||||
lines: Vec<(horizon_client::text::Text, bool)>,
|
||||
title_lines: usize,
|
||||
progress: Option<f32>,
|
||||
smooth: f32,
|
||||
last_smooth: i64,
|
||||
wanted: bool,
|
||||
slot: Option<usize>,
|
||||
slots: usize,
|
||||
slide: horizon_client::toasts::Slide,
|
||||
}
|
||||
|
||||
impl super::Game<'_> {
|
||||
pub(super) fn tutorial_toast_slots(&self) -> u32 {
|
||||
self.tutorial_toasts
|
||||
.iter()
|
||||
.filter_map(|t| t.slot.map(|s| ((1u32 << t.slots) - 1) << s))
|
||||
.fold(0, |a, b| a | b)
|
||||
}
|
||||
|
||||
fn tutorial_toast(&self, hint: Hint, progress: Option<f32>) -> HintToast {
|
||||
use horizon_client::{keybinds, text::Text};
|
||||
let key = |mapping: &str| keybinds::display_name(self.settings.keys.get(mapping)).plain();
|
||||
let (title_key, fallback, description_key, description, icon_args) = match hint {
|
||||
Hint::Move => (
|
||||
"tutorial.move.title",
|
||||
"Move with %1$s, %2$s, %3$s, %4$s",
|
||||
"tutorial.move.description",
|
||||
"Jump with %1$s",
|
||||
vec![
|
||||
key("key.forward"),
|
||||
key("key.left"),
|
||||
key("key.back"),
|
||||
key("key.right"),
|
||||
],
|
||||
),
|
||||
Hint::Look => (
|
||||
"tutorial.look.title",
|
||||
"Look around",
|
||||
"tutorial.look.description",
|
||||
"Use your mouse to turn",
|
||||
vec![],
|
||||
),
|
||||
Hint::FindTree => (
|
||||
"tutorial.find_tree.title",
|
||||
"Find a tree",
|
||||
"tutorial.find_tree.description",
|
||||
"Punch it to collect wood",
|
||||
vec![],
|
||||
),
|
||||
Hint::PunchTree => (
|
||||
"tutorial.punch_tree.title",
|
||||
"Punch the tree",
|
||||
"tutorial.punch_tree.description",
|
||||
"Hold %1$s to break",
|
||||
vec![],
|
||||
),
|
||||
Hint::OpenInventory => (
|
||||
"tutorial.open_inventory.title",
|
||||
"Open inventory",
|
||||
"tutorial.open_inventory.description",
|
||||
"Press %1$s",
|
||||
vec![],
|
||||
),
|
||||
Hint::CraftPlanks => (
|
||||
"tutorial.craft_planks.title",
|
||||
"Craft wooden planks",
|
||||
"tutorial.craft_planks.description",
|
||||
"The recipe book can help",
|
||||
vec![],
|
||||
),
|
||||
};
|
||||
let substitute = |mut text: String, values: &[String]| {
|
||||
for (i, value) in values.iter().enumerate() {
|
||||
text = text.replace(&format!("%{}$s", i + 1), value);
|
||||
text = text.replacen("%s", value, 1);
|
||||
}
|
||||
text
|
||||
};
|
||||
let title = substitute(self.translate(title_key, fallback), &icon_args);
|
||||
let desc_args = match hint {
|
||||
Hint::Move => vec![key("key.jump")],
|
||||
Hint::PunchTree => vec![key("key.attack")],
|
||||
Hint::OpenInventory => vec![key("key.inventory")],
|
||||
_ => vec![],
|
||||
};
|
||||
let description = substitute(self.translate(description_key, description), &desc_args);
|
||||
let font = &self.gui.atlas.font;
|
||||
let mut lines = font.split_lines(&Text::literal(title), 126.0);
|
||||
let title_lines = lines.len();
|
||||
lines.extend(font.split_lines(&Text::literal(description), 126.0));
|
||||
let height = 10 + 11 * lines.len().max(2);
|
||||
let slots = height.div_ceil(32);
|
||||
HintToast {
|
||||
hint,
|
||||
lines,
|
||||
title_lines,
|
||||
progress,
|
||||
smooth: 0.0,
|
||||
last_smooth: 0,
|
||||
wanted: true,
|
||||
slot: None,
|
||||
slots,
|
||||
slide: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn draw_tutorial_toasts(
|
||||
&mut self,
|
||||
batch: &mut horizon_client::hud::HudBatch,
|
||||
hidden: bool,
|
||||
) {
|
||||
let wanted = self.tutorial.hint();
|
||||
for t in &mut self.tutorial_toasts {
|
||||
t.wanted = wanted.is_some_and(|(hint, _)| hint == t.hint);
|
||||
if t.wanted {
|
||||
t.progress = wanted.and_then(|(_, progress)| progress);
|
||||
}
|
||||
}
|
||||
if let Some((hint, progress)) = wanted
|
||||
&& !self
|
||||
.tutorial_toasts
|
||||
.iter()
|
||||
.any(|t| t.wanted && t.hint == hint)
|
||||
{
|
||||
let toast = self.tutorial_toast(hint, progress);
|
||||
self.tutorial_toasts.push(toast);
|
||||
}
|
||||
let mut used = self.toast_slots_used();
|
||||
for t in &mut self.tutorial_toasts {
|
||||
if t.slot.is_none() && t.wanted && t.slots <= Self::TOAST_SLOTS {
|
||||
let mask = (1u32 << t.slots) - 1;
|
||||
if let Some(slot) =
|
||||
(0..=Self::TOAST_SLOTS - t.slots).find(|s| used & (mask << s) == 0)
|
||||
{
|
||||
t.slot = Some(slot);
|
||||
used |= mask << slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
let atlas = &self.gui.atlas;
|
||||
let mut i = 0;
|
||||
while i < self.tutorial_toasts.len() {
|
||||
let t = &mut self.tutorial_toasts[i];
|
||||
if t.slot.is_none() {
|
||||
if !t.wanted {
|
||||
self.tutorial_toasts.remove(i);
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
let (sound, finished) = t.slide.update_now(|_| t.wanted);
|
||||
if let Some(show) = sound {
|
||||
let event = if show { "ui.toast.in" } else { "ui.toast.out" };
|
||||
self.audio
|
||||
.play(&format!("minecraft:{event}"), None, 1.0, 1.0);
|
||||
}
|
||||
if finished {
|
||||
self.tutorial_toasts.remove(i);
|
||||
continue;
|
||||
}
|
||||
if let Some(progress) = t.progress {
|
||||
let delta = ((t.slide.fully_for - t.last_smooth) as f32 / 100.0).clamp(0.0, 1.0);
|
||||
t.smooth += (progress - t.smooth) * delta;
|
||||
t.last_smooth = t.slide.fully_for;
|
||||
}
|
||||
if !hidden {
|
||||
let height = (10 + 11 * t.lines.len().max(2)) as f32;
|
||||
let x = batch.width - 160.0 * t.slide.portion;
|
||||
let y = 32.0 * t.slot.unwrap_or_default() as f32;
|
||||
horizon_client::ui::nine_slice(
|
||||
batch,
|
||||
atlas,
|
||||
"gui/sprites/toast/tutorial",
|
||||
x,
|
||||
y,
|
||||
160.0,
|
||||
height,
|
||||
u32::MAX,
|
||||
);
|
||||
let icon = match t.hint {
|
||||
Hint::Move => "movement_keys",
|
||||
Hint::Look => "mouse",
|
||||
Hint::FindTree | Hint::PunchTree => "tree",
|
||||
Hint::OpenInventory => "recipe_book",
|
||||
Hint::CraftPlanks => "wooden_planks",
|
||||
};
|
||||
batch.sprite_scaled(
|
||||
atlas,
|
||||
&format!("gui/sprites/toast/{icon}"),
|
||||
x + 6.0,
|
||||
y + 6.0,
|
||||
20.0,
|
||||
20.0,
|
||||
u32::MAX,
|
||||
);
|
||||
let text_y = y + 7.0 + ((t.lines.len().max(2) - t.lines.len()) * 11) as f32 / 2.0;
|
||||
for (n, (line, _)) in t.lines.iter().enumerate() {
|
||||
batch.text_styled(
|
||||
atlas,
|
||||
line,
|
||||
x + 30.0,
|
||||
text_y + 11.0 * n as f32,
|
||||
if n < t.title_lines {
|
||||
0xFF50_0050
|
||||
} else {
|
||||
0xFF00_0000
|
||||
},
|
||||
false,
|
||||
);
|
||||
}
|
||||
if let Some(progress) = t.progress {
|
||||
batch.fill(atlas, x + 3.0, y + height - 4.0, 154.0, 1.0, u32::MAX);
|
||||
let color = if progress >= t.smooth {
|
||||
0xFF00_5500
|
||||
} else {
|
||||
0xFF55_0000
|
||||
};
|
||||
batch.fill(
|
||||
atlas,
|
||||
x + 3.0,
|
||||
y + height - 4.0,
|
||||
154.0 * t.smooth.clamp(0.0, 1.0),
|
||||
1.0,
|
||||
color,
|
||||
);
|
||||
}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -940,6 +940,23 @@ pub fn entity_tag(entity: &BlockEntity) -> Option<Tag> {
|
||||
match entity {
|
||||
BlockEntity::Sign(sign) => Some(Tag::compound(sign.encode())),
|
||||
BlockEntity::Spawner(s) => Some(s.update_tag()),
|
||||
BlockEntity::Brushable(b) => {
|
||||
let mut entries = Vec::new();
|
||||
if let Some(face) = b.hit_direction {
|
||||
entries.push(("hit_direction", Tag::Byte(face as i8)));
|
||||
}
|
||||
if let Some(item) = &b.item {
|
||||
entries.push(("item", crate::save::item_to_nbt(item)));
|
||||
}
|
||||
Some(Tag::compound(entries))
|
||||
}
|
||||
BlockEntity::Shelf(s) => Some(Tag::compound([
|
||||
("Items", crate::save::encode_items(&s.items)),
|
||||
(
|
||||
"align_items_to_bottom",
|
||||
Tag::Byte(i8::from(s.align_to_bottom)),
|
||||
),
|
||||
])),
|
||||
// BannerBlockEntity.getUpdateTag: saveWithoutMetadata, so the patterns,
|
||||
// the custom name and the components.
|
||||
BlockEntity::Banner(b) => {
|
||||
|
||||
@@ -32,9 +32,12 @@ use std::{
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
/// ServerCommonPacketListenerImpl.KEEPALIVE_TIME and the disconnect timeout.
|
||||
/// ServerCommonPacketListenerImpl.keepConnectionAlive: a keep-alive every 15 s,
|
||||
/// and a client that has not answered the last one by the next is dropped.
|
||||
const KEEP_ALIVE_EVERY: Duration = Duration::from_secs(15);
|
||||
const KEEP_ALIVE_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
/// ReadTimeoutHandler(30) in ServerConnectionListener: a connection that sends
|
||||
/// nothing for 30 s.
|
||||
const READ_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
/// Packets queued for one client beyond this many bytes disconnect it.
|
||||
const MAX_PENDING: usize = 96 << 20;
|
||||
/// Vanilla's server-side disconnect during login is a JSON component.
|
||||
@@ -957,7 +960,7 @@ fn offer_pack(
|
||||
fn play(c: Connection, shared: &Arc<Shared>, parts: Parts, address: std::net::SocketAddr) {
|
||||
let conn_id = shared.next_id();
|
||||
let events = shared.events.clone();
|
||||
let _ = c.reader.set_read_timeout(Some(KEEP_ALIVE_TIMEOUT));
|
||||
let _ = c.reader.set_read_timeout(Some(READ_TIMEOUT));
|
||||
let (mut reader, writer) = c.split();
|
||||
let (tx, rx) = channel();
|
||||
let pending = Arc::new(AtomicUsize::new(0));
|
||||
@@ -1172,7 +1175,7 @@ fn write_loop(
|
||||
let old = latency.load(Ordering::Relaxed);
|
||||
latency.store((old * 3 + ms) / 4, Ordering::Relaxed);
|
||||
sent_at = None;
|
||||
} else if at.elapsed() > KEEP_ALIVE_TIMEOUT {
|
||||
} else if at.elapsed() >= KEEP_ALIVE_EVERY {
|
||||
let _ = writer.send(
|
||||
&play::Clientbound::Disconnect(cfg::Disconnect {
|
||||
reason: translatable("disconnect.timeout", &[]),
|
||||
|
||||
@@ -269,6 +269,38 @@ impl Projectile {
|
||||
pub fn is_arrow(&self) -> bool {
|
||||
matches!(self.kind, ProjectileKind::Arrow { .. })
|
||||
}
|
||||
/// Whether it is a `SpectralArrow` (its own entity type): a spectral
|
||||
/// arrow's pickup item, or the glowing effect a dispenser shoots with.
|
||||
pub fn is_spectral_arrow(&self) -> bool {
|
||||
let ProjectileKind::Arrow { effect, .. } = &self.kind else {
|
||||
return false;
|
||||
};
|
||||
effect.is_some_and(|(id, _)| id == "minecraft:glowing")
|
||||
|| self
|
||||
.arrow
|
||||
.as_ref()
|
||||
.is_some_and(|a| &*a.pickup_item.item == "minecraft:spectral_arrow")
|
||||
}
|
||||
/// `Arrow.ID_EFFECT_COLOR` (`updateColor`): the opaque colour of the
|
||||
/// potion the arrow carries, from its tipped pickup item or the effect a
|
||||
/// stray, bogged or parched adds; -1 for none.
|
||||
pub fn arrow_effect_color(&self) -> i32 {
|
||||
let ProjectileKind::Arrow { effect, .. } = &self.kind else {
|
||||
return -1;
|
||||
};
|
||||
if self.is_spectral_arrow() {
|
||||
return -1;
|
||||
}
|
||||
let rgb = match (effect, &self.arrow) {
|
||||
(Some((id, _)), _) => crate::brewing::effect(id).map(|e| e.color),
|
||||
(None, Some(a)) if &*a.pickup_item.item == "minecraft:tipped_arrow" => a
|
||||
.pickup_item
|
||||
.potion()
|
||||
.map(|p| crate::brewing::potion_color(Some(p))),
|
||||
_ => None,
|
||||
};
|
||||
rgb.map_or(-1, |rgb| (rgb | 0xFF00_0000) as i32)
|
||||
}
|
||||
pub fn is_fireball(&self) -> bool {
|
||||
matches!(
|
||||
self.kind,
|
||||
|
||||
@@ -483,6 +483,13 @@ pub fn encode_block_entity(
|
||||
}
|
||||
}
|
||||
BlockEntity::Sculk(s) => entries.extend(s.to_nbt()),
|
||||
BlockEntity::Shelf(shelf) => {
|
||||
entries.push(("Items", encode_items(&shelf.items)));
|
||||
entries.push((
|
||||
"align_items_to_bottom",
|
||||
Tag::Byte(i8::from(shelf.align_to_bottom)),
|
||||
));
|
||||
}
|
||||
// ChiseledBookShelfBlockEntity.saveAdditional.
|
||||
BlockEntity::ChiseledBookshelf(b) => {
|
||||
entries.push(("Items", encode_items(&b.items)));
|
||||
@@ -547,6 +554,16 @@ pub fn encode_block_entity(
|
||||
BlockEntity::Vault(v) => entries.extend(v.save()),
|
||||
BlockEntity::Beehive(h) => entries.extend(h.to_nbt()),
|
||||
BlockEntity::CommandBlock(c) => entries.extend(c.save()),
|
||||
BlockEntity::Brushable(b) => {
|
||||
if let Some(table) = &b.loot_table {
|
||||
entries.push(("LootTable", table.clone()));
|
||||
if let Some(seed) = &b.loot_seed {
|
||||
entries.push(("LootTableSeed", seed.clone()));
|
||||
}
|
||||
} else if let Some(item) = &b.item {
|
||||
entries.push(("item", item_to_nbt(item)));
|
||||
}
|
||||
}
|
||||
BlockEntity::DaylightDetector => {}
|
||||
}
|
||||
Tag::compound(entries)
|
||||
@@ -724,8 +741,24 @@ pub fn decode_block_entity(tag: &Tag) -> Option<((i32, i32, i32), crate::contain
|
||||
_ => 1.0,
|
||||
};
|
||||
}
|
||||
BlockEntity::Brushable(b) => {
|
||||
b.loot_table = tag.get("LootTable").cloned();
|
||||
b.loot_seed = tag.get("LootTableSeed").cloned();
|
||||
b.item = if b.loot_table.is_none() {
|
||||
tag.get("item").and_then(item_from_nbt)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
b.hit_direction = int("hit_direction").filter(|v| (0..=5).contains(v));
|
||||
}
|
||||
BlockEntity::Sign(sign) => sign.decode(tag),
|
||||
BlockEntity::Sculk(s) => s.load(tag),
|
||||
BlockEntity::Shelf(shelf) => {
|
||||
for (slot, item) in decode_items(tag.get("Items"), 3).into_iter().enumerate() {
|
||||
shelf.items[slot] = item;
|
||||
}
|
||||
shelf.align_to_bottom = int("align_items_to_bottom").unwrap_or(0) != 0;
|
||||
}
|
||||
// ChiseledBookShelfBlockEntity.loadAdditional.
|
||||
BlockEntity::ChiseledBookshelf(b) => {
|
||||
for (slot, item) in decode_items(tag.get("Items"), 6).into_iter().enumerate() {
|
||||
|
||||
@@ -481,26 +481,7 @@ pub struct StationRecipes {
|
||||
pub trim_materials: Vec<(String, String)>,
|
||||
}
|
||||
fn ingredient(v: &Value, items: &Items) -> Ingredient {
|
||||
let mut list: Vec<String> = Vec::new();
|
||||
for s in match v {
|
||||
Value::String(s) => vec![s.clone()],
|
||||
Value::Array(a) => a
|
||||
.iter()
|
||||
.filter_map(|x| x.as_str().map(str::to_owned))
|
||||
.collect(),
|
||||
_ => vec![],
|
||||
} {
|
||||
let more = match s.strip_prefix('#') {
|
||||
Some(tag) => items.item_tag_order.get(tag).cloned().unwrap_or_default(),
|
||||
None => vec![s],
|
||||
};
|
||||
for i in more {
|
||||
if !list.contains(&i) {
|
||||
list.push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ingredient::of(list)
|
||||
Ingredient::parse(v, items).unwrap_or_else(|| Ingredient::of(Vec::new()))
|
||||
}
|
||||
impl StationRecipes {
|
||||
pub fn load(root: &Path, items: &Items) -> Self {
|
||||
@@ -1014,4 +995,18 @@ mod tests {
|
||||
let xp = grindstone_experience(&e, &mut r, Some(&s), None);
|
||||
assert!((12..24).contains(&xp));
|
||||
}
|
||||
#[test]
|
||||
fn smithing_ingredient_parses_holders() {
|
||||
let items = sample_items();
|
||||
let a = ingredient(&serde_json::json!("minecraft:diamond_sword"), &items);
|
||||
assert_eq!(a.1, vec!["minecraft:diamond_sword"]);
|
||||
let b = ingredient(
|
||||
&serde_json::json!(["minecraft:diamond_sword", "minecraft:diamond_axe"]),
|
||||
&items,
|
||||
);
|
||||
assert_eq!(
|
||||
b.1,
|
||||
vec!["minecraft:diamond_sword", "minecraft:diamond_axe"]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
//! Display/TextDisplay synced data (26.3), checked against vanilla accessors
|
||||
//! and ClientboundSetEntityDataPacket's own stream codec.
|
||||
use crate::text::Text;
|
||||
use horizon_protocol::entity_data::Value;
|
||||
use crate::{
|
||||
save::{float, number},
|
||||
text::Text,
|
||||
};
|
||||
use horizon_protocol::entity_data::{Value, serializer};
|
||||
use horizon_world::nbt::Tag;
|
||||
use serde::Serialize;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -154,6 +158,197 @@ impl TextDisplayData {
|
||||
}
|
||||
}
|
||||
|
||||
/// One synched data entry: kept when it differs from its default, or always
|
||||
/// with `all` (the defaults a client is sent when a value goes back to one).
|
||||
fn entry(
|
||||
out: &mut Vec<(u8, i32, Value)>,
|
||||
all: bool,
|
||||
id: u8,
|
||||
kind: i32,
|
||||
value: Value,
|
||||
default: Value,
|
||||
) {
|
||||
if all || value != default {
|
||||
out.push((id, kind, if all { default } else { value }));
|
||||
}
|
||||
}
|
||||
|
||||
/// `Display.readAdditionalSaveData`: the synched data entries (ids 8 to 22)
|
||||
/// of a saved display, or with `all` their defaults. A matrix `transformation`
|
||||
/// (`MATRIX4F`) is not decomposed: it stays the identity.
|
||||
pub fn display_data(tag: &Tag, all: bool) -> Vec<(u8, i32, Value)> {
|
||||
let int = |key: &str| number(tag.get(key)).map(|v| v as i32);
|
||||
let real = |key: &str| float(tag.get(key)).map(|v| v as f32);
|
||||
let mut out = Vec::new();
|
||||
let mut set = |id: u8, kind: i32, value: Value, default: Value| {
|
||||
entry(&mut out, all, id, kind, value, default);
|
||||
};
|
||||
let defaults = TextDisplayData::default();
|
||||
// The start delay, the transformation duration, the teleport duration (0 to 59).
|
||||
let ints = [
|
||||
(8, "start_interpolation", 0, i32::MIN, i32::MAX),
|
||||
(9, "interpolation_duration", 0, i32::MIN, i32::MAX),
|
||||
(10, "teleport_duration", 0, 0, 59),
|
||||
(22, "glow_color_override", -1, i32::MIN, i32::MAX),
|
||||
];
|
||||
for (id, key, default, low, high) in ints {
|
||||
let value = int(key).unwrap_or(default).clamp(low, high);
|
||||
set(id, serializer::INT, Value::Int(value), Value::Int(default));
|
||||
}
|
||||
// `Transformation.CODEC`: all four parts, else the identity.
|
||||
let t = tag.get("transformation").and_then(transformation);
|
||||
let (translation, left, scale, right) = t.unwrap_or((
|
||||
defaults.translation,
|
||||
defaults.left_rotation,
|
||||
defaults.scale,
|
||||
defaults.right_rotation,
|
||||
));
|
||||
let parts = [
|
||||
(
|
||||
11,
|
||||
serializer::VECTOR3,
|
||||
translation.to_vec(),
|
||||
defaults.translation.to_vec(),
|
||||
),
|
||||
(
|
||||
12,
|
||||
serializer::VECTOR3,
|
||||
scale.to_vec(),
|
||||
defaults.scale.to_vec(),
|
||||
),
|
||||
(
|
||||
13,
|
||||
serializer::QUATERNION,
|
||||
left.to_vec(),
|
||||
defaults.left_rotation.to_vec(),
|
||||
),
|
||||
(
|
||||
14,
|
||||
serializer::QUATERNION,
|
||||
right.to_vec(),
|
||||
defaults.right_rotation.to_vec(),
|
||||
),
|
||||
];
|
||||
for (id, kind, value, default) in parts {
|
||||
set(id, kind, Value::Floats(value), Value::Floats(default));
|
||||
}
|
||||
let billboard = match tag.get("billboard") {
|
||||
Some(Tag::String(s)) => match s.to_string_lossy().as_str() {
|
||||
"vertical" => 1,
|
||||
"horizontal" => 2,
|
||||
"center" => 3,
|
||||
_ => 0,
|
||||
},
|
||||
_ => 0,
|
||||
};
|
||||
set(15, serializer::BYTE, Value::Byte(billboard), Value::Byte(0));
|
||||
// `Brightness.pack`: block light in bits 4 to 7, sky light in bits 20 to 23.
|
||||
let brightness = tag.get("brightness").and_then(|b| {
|
||||
let light = |key: &str| number(b.get(key)).filter(|v| (0..=15).contains(v));
|
||||
Some((light("block")? as i32) << 4 | (light("sky")? as i32) << 20)
|
||||
});
|
||||
set(
|
||||
16,
|
||||
serializer::INT,
|
||||
Value::Int(brightness.unwrap_or(-1)),
|
||||
Value::Int(-1),
|
||||
);
|
||||
for (id, key, default) in [
|
||||
(17, "view_range", 1.0),
|
||||
(18, "shadow_radius", 0.0),
|
||||
(19, "shadow_strength", 1.0),
|
||||
(20, "width", 0.0),
|
||||
(21, "height", 0.0),
|
||||
] {
|
||||
let value = real(key).unwrap_or(default);
|
||||
set(
|
||||
id,
|
||||
serializer::FLOAT,
|
||||
Value::Float(value),
|
||||
Value::Float(default),
|
||||
);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// `TextDisplay.readAdditionalSaveData`: the text, line width, background,
|
||||
/// opacity and style flags (ids 23 to 27), or with `all` their defaults.
|
||||
pub fn text_display_data(tag: &Tag, all: bool) -> Vec<(u8, i32, Value)> {
|
||||
let int = |key: &str| number(tag.get(key)).map(|v| v as i32);
|
||||
let mut out = Vec::new();
|
||||
let mut set = |id: u8, kind: i32, value: Value, default: Value| {
|
||||
entry(&mut out, all, id, kind, value, default);
|
||||
};
|
||||
let empty = Tag::String("".into());
|
||||
set(
|
||||
23,
|
||||
serializer::COMPONENT,
|
||||
Value::Text(tag.get("text").unwrap_or(&empty).clone()),
|
||||
Value::Text(empty.clone()),
|
||||
);
|
||||
set(
|
||||
24,
|
||||
serializer::INT,
|
||||
Value::Int(int("line_width").unwrap_or(200)),
|
||||
Value::Int(200),
|
||||
);
|
||||
let background = Value::Int(int("background").unwrap_or(0x4000_0000));
|
||||
set(25, serializer::INT, background, Value::Int(0x4000_0000));
|
||||
let opacity = Value::Byte(int("text_opacity").unwrap_or(-1) as i8);
|
||||
set(26, serializer::BYTE, opacity, Value::Byte(-1));
|
||||
// Shadow 1, see-through 2, default background 4, then the alignment: left 8, right 16.
|
||||
let flag = |key: &str, bit: i8| {
|
||||
if int(key).is_some_and(|v| v != 0) {
|
||||
bit
|
||||
} else {
|
||||
0
|
||||
}
|
||||
};
|
||||
let align = match tag.get("alignment") {
|
||||
Some(Tag::String(s)) => match s.to_string_lossy().as_str() {
|
||||
"left" => 8,
|
||||
"right" => 16,
|
||||
_ => 0,
|
||||
},
|
||||
_ => 0,
|
||||
};
|
||||
let flags = flag("shadow", 1) | flag("see_through", 2) | flag("default_background", 4) | align;
|
||||
set(27, serializer::BYTE, Value::Byte(flags), Value::Byte(0));
|
||||
out
|
||||
}
|
||||
|
||||
/// `Transformation.CODEC`'s translation, left rotation, scale and right
|
||||
/// rotation; None unless all four are there (the matrix form is not read).
|
||||
fn transformation(tag: &Tag) -> Option<([f32; 3], [f32; 4], [f32; 3], [f32; 4])> {
|
||||
let list = |tag: Option<&Tag>| -> Option<Vec<f32>> {
|
||||
match tag? {
|
||||
Tag::List(l) => l.iter().map(|v| float(Some(v)).map(|v| v as f32)).collect(),
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
let vector = |key: &str| -> Option<[f32; 3]> { list(tag.get(key))?.try_into().ok() };
|
||||
// `ExtraCodecs.QUATERNIONF`: four components (normalized) or an axis and an angle.
|
||||
let rotation = |key: &str| -> Option<[f32; 4]> {
|
||||
let value = tag.get(key)?;
|
||||
if let Some(q) = list(Some(value)) {
|
||||
let [x, y, z, w]: [f32; 4] = q.try_into().ok()?;
|
||||
let length = (x * x + y * y + z * z + w * w).sqrt();
|
||||
return Some([x / length, y / length, z / length, w / length]);
|
||||
}
|
||||
let angle = float(value.get("angle"))? as f32;
|
||||
let [x, y, z]: [f32; 3] = list(value.get("axis"))?.try_into().ok()?;
|
||||
let (sin, cos) = (angle * 0.5).sin_cos();
|
||||
let inverse = 1.0 / (x * x + y * y + z * z).sqrt();
|
||||
Some([x * inverse * sin, y * inverse * sin, z * inverse * sin, cos])
|
||||
};
|
||||
Some((
|
||||
vector("translation")?,
|
||||
rotation("left_rotation")?,
|
||||
vector("scale")?,
|
||||
rotation("right_rotation")?,
|
||||
))
|
||||
}
|
||||
|
||||
/// `TextDisplay.CachedInfo`: each line and its width, and the widest.
|
||||
pub struct Lines {
|
||||
pub lines: Vec<(Text, f32)>,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Title, world selection and world creation screens (TitleScreen,
|
||||
//! SelectWorldScreen, CreateWorldScreen layouts), drawn as HUD quads.
|
||||
use crate::{
|
||||
dialog_screen::BOX,
|
||||
game_rules_screen::{Exit, RulesScreen},
|
||||
hud::{HudAtlas, HudBatch, rgba},
|
||||
lang,
|
||||
@@ -33,6 +34,12 @@ fn edit_top(h: f32) -> f32 {
|
||||
pub(crate) fn confirm_top(h: f32, lines: usize) -> f32 {
|
||||
((h.ceil() - 61.0 - lines as f32 * 9.0) / 2.0).floor()
|
||||
}
|
||||
/// The SafetyScreen's column (WarningScreen: title, 12 px around the message
|
||||
/// box of its lines and 4 px padding, the checkbox, then the buttons, 8 px
|
||||
/// apart): the title's top.
|
||||
fn warning_top(h: f32, lines: usize) -> f32 {
|
||||
((h.ceil() - 102.0 - lines as f32 * 9.0) / 2.0).floor()
|
||||
}
|
||||
|
||||
fn translate(key: &str, fallback: &str, args: &[&dyn std::fmt::Display]) -> String {
|
||||
let args: Vec<Text> = args
|
||||
@@ -119,6 +126,11 @@ pub enum Mode {
|
||||
return_to: Box<Mode>,
|
||||
},
|
||||
Multiplayer(crate::multiplayer::Screen),
|
||||
/// SafetyScreen: the warning before the first Multiplayer click, with its
|
||||
/// "Do not show this screen again" box.
|
||||
MultiplayerWarning {
|
||||
check: bool,
|
||||
},
|
||||
/// CreateFlatWorldScreen over Create New World: the fields that screen
|
||||
/// keeps and the superflat editor on top of them.
|
||||
CreateFlat {
|
||||
@@ -150,11 +162,18 @@ pub enum Action {
|
||||
Connect {
|
||||
address: String,
|
||||
},
|
||||
/// Create World's Data Packs screen on the new world's pack folder.
|
||||
DataPacks {
|
||||
dir: PathBuf,
|
||||
selected: Vec<String>,
|
||||
},
|
||||
}
|
||||
pub struct Title {
|
||||
pub mode: Mode,
|
||||
/// Direct Connection's address, synchronized with `options.txt`'s `lastServer`.
|
||||
pub last_server: String,
|
||||
/// `skipMultiplayerWarning`, synchronized with `options.txt` the same way.
|
||||
pub skip_multiplayer_warning: bool,
|
||||
pub saves: PathBuf,
|
||||
/// The prepared resource root: the flat preset list and block registry.
|
||||
resources: PathBuf,
|
||||
@@ -166,6 +185,8 @@ pub struct Title {
|
||||
scroll: Cell<crate::ui::Scroll>,
|
||||
create_return_to_main: bool,
|
||||
message_lines: Cell<usize>,
|
||||
/// The SafetyScreen's checkbox as last drawn: x, y, width, height.
|
||||
warning_check: Cell<[f32; 4]>,
|
||||
backup: Option<mpsc::Receiver<Result<u64, String>>>,
|
||||
backup_shown: Cell<bool>,
|
||||
/// The backup a `BackupBeforeUpgrade` asked for opens its world when done.
|
||||
@@ -292,6 +313,7 @@ impl Title {
|
||||
Self {
|
||||
mode,
|
||||
last_server: String::new(),
|
||||
skip_multiplayer_warning: false,
|
||||
saves,
|
||||
resources: resources.to_owned(),
|
||||
started: now,
|
||||
@@ -301,6 +323,7 @@ impl Title {
|
||||
scroll: Cell::default(),
|
||||
create_return_to_main: false,
|
||||
message_lines: Cell::new(1),
|
||||
warning_check: Cell::new([0.0; 4]),
|
||||
backup: None,
|
||||
backup_shown: Cell::new(false),
|
||||
backup_then_open: false,
|
||||
@@ -384,6 +407,12 @@ impl Title {
|
||||
row
|
||||
}
|
||||
fn cancel_create(&mut self) {
|
||||
// removeTempDataPackDir.
|
||||
if let Mode::Create { options, .. } = &self.mode
|
||||
&& let Some(dir) = &options.pack_dir
|
||||
{
|
||||
let _ = std::fs::remove_dir_all(dir);
|
||||
}
|
||||
if self.create_return_to_main {
|
||||
self.mode = Mode::Main;
|
||||
} else {
|
||||
@@ -448,6 +477,13 @@ impl Title {
|
||||
};
|
||||
}
|
||||
}
|
||||
/// The Data Packs screen's Done (`applyNewPackConfig`): its selection for
|
||||
/// the new world.
|
||||
pub fn set_data_packs(&mut self, ids: Vec<String>) {
|
||||
if let Mode::Create { options, .. } = &mut self.mode {
|
||||
options.data_packs = ids;
|
||||
}
|
||||
}
|
||||
fn matches_search(&self, world: &WorldEntry) -> bool {
|
||||
let filter = self.search.to_lowercase();
|
||||
world.name.to_lowercase().contains(&filter) || world.folder.to_lowercase().contains(&filter)
|
||||
@@ -1016,6 +1052,28 @@ impl Title {
|
||||
),
|
||||
]
|
||||
}
|
||||
Mode::MultiplayerWarning { .. } => {
|
||||
let lines = self.message_lines.get();
|
||||
let y = warning_top(h, lines) + 82.0 + lines as f32 * 9.0;
|
||||
vec![
|
||||
b(
|
||||
(w / 2.0).floor() - 154.0,
|
||||
y,
|
||||
150.0,
|
||||
&lang::get_or("gui.proceed", "Proceed"),
|
||||
true,
|
||||
70,
|
||||
),
|
||||
b(
|
||||
(w / 2.0).floor() + 4.0,
|
||||
y,
|
||||
150.0,
|
||||
&lang::get_or("gui.back", "Back"),
|
||||
true,
|
||||
71,
|
||||
),
|
||||
]
|
||||
}
|
||||
Mode::Backup { .. } => Vec::new(),
|
||||
Mode::Multiplayer(screen) => screen
|
||||
.buttons(w, h)
|
||||
@@ -1347,6 +1405,74 @@ impl Title {
|
||||
centered(atlas, batch, line, top + 17.0 + i as f32 * 9.0, u32::MAX);
|
||||
}
|
||||
}
|
||||
Mode::MultiplayerWarning { check } => {
|
||||
crate::ui::menu_background(batch, atlas, 0.0, 0.0, w, h, false);
|
||||
let message = lang::get_or(
|
||||
"multiplayerWarning.message",
|
||||
"Caution: Online play is offered by third-party servers that are not owned, operated, or supervised by Mojang Studios or Microsoft. During online play, you may be exposed to unmoderated chat messages or other types of user-generated content that may not be suitable for everyone.",
|
||||
);
|
||||
// The message sits in a text box `width - 100` wide with 4 px
|
||||
// inside, left aligned (FittingMultiLineTextWidget).
|
||||
let box_w = w - 100.0;
|
||||
let lines = wrapped(atlas, &message, box_w - 8.0);
|
||||
self.message_lines.set(lines.len());
|
||||
let top = warning_top(h, lines.len());
|
||||
let header = Text::translate("multiplayerWarning.header", &[]).bold();
|
||||
batch.text_styled(
|
||||
atlas,
|
||||
&header,
|
||||
(w / 2.0).floor() - (atlas.font.width_text(&header) / 2.0).floor(),
|
||||
top,
|
||||
u32::MAX,
|
||||
true,
|
||||
);
|
||||
let box_x = ((w - box_w) / 2.0).floor();
|
||||
let box_y = top + 29.0;
|
||||
let box_h = lines.len() as f32 * 9.0 + 8.0;
|
||||
crate::ui::nine_slice(
|
||||
batch,
|
||||
atlas,
|
||||
"gui/sprites/widget/text_field",
|
||||
box_x,
|
||||
box_y,
|
||||
box_w,
|
||||
box_h,
|
||||
u32::MAX,
|
||||
);
|
||||
for (i, line) in lines.iter().enumerate() {
|
||||
batch.text(
|
||||
atlas,
|
||||
line,
|
||||
box_x + 4.0,
|
||||
box_y + 4.0 + i as f32 * 9.0,
|
||||
u32::MAX,
|
||||
true,
|
||||
);
|
||||
}
|
||||
// Checkbox: the box, its label right of it (as the dialogs draw theirs).
|
||||
let label =
|
||||
lang::get_or("multiplayerWarning.check", "Do not show this screen again");
|
||||
let width = BOX + 4.0 + HudBatch::text_width(atlas, &label).ceil();
|
||||
let x = (w / 2.0).floor() - (width / 2.0).floor();
|
||||
let y = top + 57.0 + lines.len() as f32 * 9.0;
|
||||
self.warning_check.set([x, y, width, BOX]);
|
||||
let hover = (x..x + width).contains(&cursor.0) && (y..y + BOX).contains(&cursor.1);
|
||||
let sprite = match (*check, hover) {
|
||||
(true, true) => "gui/sprites/widget/checkbox_selected_highlighted",
|
||||
(true, false) => "gui/sprites/widget/checkbox_selected",
|
||||
(false, true) => "gui/sprites/widget/checkbox_highlighted",
|
||||
(false, false) => "gui/sprites/widget/checkbox",
|
||||
};
|
||||
batch.sprite_scaled(atlas, sprite, x, y, BOX, BOX, u32::MAX);
|
||||
batch.text(
|
||||
atlas,
|
||||
&label,
|
||||
x + BOX + 4.0,
|
||||
y + 4.0,
|
||||
rgba(224, 224, 224, 255),
|
||||
true,
|
||||
);
|
||||
}
|
||||
Mode::Multiplayer(screen) => screen.draw(
|
||||
atlas,
|
||||
batch,
|
||||
@@ -1454,6 +1580,14 @@ impl Title {
|
||||
}
|
||||
return (Action::None, changed);
|
||||
}
|
||||
// The SafetyScreen's box toggles where it was last drawn.
|
||||
if let Mode::MultiplayerWarning { check } = &mut self.mode {
|
||||
let [bx, by, bw, bh] = self.warning_check.get();
|
||||
if (bx..bx + bw).contains(&x) && (by..by + bh).contains(&y) {
|
||||
*check = !*check;
|
||||
return (Action::None, true);
|
||||
}
|
||||
}
|
||||
let hit = self
|
||||
.buttons(w, h)
|
||||
.into_iter()
|
||||
@@ -1476,9 +1610,26 @@ impl Title {
|
||||
}
|
||||
(Mode::Main, Some(2)) => Action::Quit,
|
||||
(Mode::Main, Some(4)) => {
|
||||
// TitleScreen.init: SafetyScreen first, unless it was turned off.
|
||||
self.mode = if self.skip_multiplayer_warning {
|
||||
Mode::Multiplayer(crate::multiplayer::Screen::list(&self.saves))
|
||||
} else {
|
||||
Mode::MultiplayerWarning { check: false }
|
||||
};
|
||||
Action::None
|
||||
}
|
||||
(Mode::MultiplayerWarning { check }, Some(70)) => {
|
||||
// SafetyScreen Proceed: the box saves the choice.
|
||||
if *check {
|
||||
self.skip_multiplayer_warning = true;
|
||||
}
|
||||
self.mode = Mode::Multiplayer(crate::multiplayer::Screen::list(&self.saves));
|
||||
Action::None
|
||||
}
|
||||
(Mode::MultiplayerWarning { .. }, Some(71)) => {
|
||||
self.mode = Mode::Main;
|
||||
Action::None
|
||||
}
|
||||
(Mode::Multiplayer(_), hit) => match self.multiplayer_click(hit, x, y, (w, h)) {
|
||||
crate::multiplayer::Outcome::Back => {
|
||||
self.mode = Mode::Main;
|
||||
@@ -1669,11 +1820,26 @@ impl Title {
|
||||
creative, options, ..
|
||||
},
|
||||
Some(id),
|
||||
) if id != 21 && id != 22 => {
|
||||
) if !matches!(id, 21 | 22 | 31) => {
|
||||
options.press(id, creative);
|
||||
Action::None
|
||||
}
|
||||
(Mode::Create { .. }, Some(21)) => self.create_action(),
|
||||
(Mode::Create { options, .. }, Some(31)) => {
|
||||
if options.pack_dir.is_none() {
|
||||
match crate::datapack::temp_folder() {
|
||||
Ok(dir) => options.pack_dir = Some(dir),
|
||||
Err(e) => {
|
||||
log::warn!("Horizon: no folder for data packs: {e}");
|
||||
return Action::None;
|
||||
}
|
||||
}
|
||||
}
|
||||
Action::DataPacks {
|
||||
dir: options.pack_dir.clone().unwrap_or_default(),
|
||||
selected: options.data_packs.clone(),
|
||||
}
|
||||
}
|
||||
(Mode::Create { .. }, Some(22)) => {
|
||||
self.cancel_create();
|
||||
Action::None
|
||||
|
||||
@@ -38,6 +38,11 @@ pub struct WorldOptions {
|
||||
pub bonus_chest: bool,
|
||||
/// `getGameRules`: the Game Rules screen's values, saved with the world.
|
||||
pub rules: crate::gamerules::GameRules,
|
||||
/// The Data Packs screen's selection, lowest priority first.
|
||||
pub data_packs: Vec<String>,
|
||||
/// The folder of packs for the new world (vanilla's `mcworld-`
|
||||
/// temporary folder), made when Data Packs first opens.
|
||||
pub pack_dir: Option<std::path::PathBuf>,
|
||||
}
|
||||
impl Default for WorldOptions {
|
||||
fn default() -> Self {
|
||||
@@ -51,6 +56,8 @@ impl Default for WorldOptions {
|
||||
structures: true,
|
||||
bonus_chest: false,
|
||||
rules: crate::gamerules::GameRules::default(),
|
||||
data_packs: vec!["vanilla".into()],
|
||||
pack_dir: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -286,7 +293,7 @@ pub(super) fn buttons(w: f32, h: f32, creative: bool, o: &WorldOptions) -> Vec<B
|
||||
28,
|
||||
),
|
||||
],
|
||||
// Experiments and data packs have no screens yet.
|
||||
// Experiments have no screen yet.
|
||||
_ => [
|
||||
"selectWorld.gameRules",
|
||||
"selectWorld.experiments",
|
||||
@@ -300,7 +307,7 @@ pub(super) fn buttons(w: f32, h: f32, creative: bool, o: &WorldOptions) -> Vec<B
|
||||
gy + 28.0 * i as f32,
|
||||
210.0,
|
||||
tr(key),
|
||||
i == 0,
|
||||
i != 1,
|
||||
29 + i as u8,
|
||||
)
|
||||
})
|
||||
|
||||
@@ -88,6 +88,9 @@ fn direct_connection_remembers_the_address_on_cancel_escape_and_join() {
|
||||
let mut f = Fixture::new();
|
||||
f.title.last_server = "localhost:25566".into();
|
||||
f.click("Multiplayer");
|
||||
// SafetyScreen comes first while its box was never ticked.
|
||||
assert!(matches!(f.title.mode, Mode::MultiplayerWarning { .. }));
|
||||
f.click("Proceed");
|
||||
f.click("Direct Connection");
|
||||
assert_eq!(f.title.focused_field().unwrap().1, "localhost:25566");
|
||||
|
||||
@@ -100,6 +103,7 @@ fn direct_connection_remembers_the_address_on_cancel_escape_and_join() {
|
||||
f.title.escape();
|
||||
f.title.escape();
|
||||
f.click("Multiplayer");
|
||||
f.click("Proceed");
|
||||
f.click("Direct Connection");
|
||||
assert_eq!(f.title.focused_field().unwrap().1, "[::1]:25568");
|
||||
|
||||
@@ -122,6 +126,7 @@ fn direct_connection_remembers_the_address_on_cancel_escape_and_join() {
|
||||
fn f5_reloads_the_server_list() {
|
||||
let mut f = Fixture::new();
|
||||
f.click("Multiplayer");
|
||||
f.click("Proceed");
|
||||
let file = crate::multiplayer::servers_file(&f.title.saves);
|
||||
horizon_protocol::servers::save(
|
||||
&file,
|
||||
@@ -850,12 +855,29 @@ fn create_world_tabs_follow_vanilla_and_their_choices_reach_the_action() {
|
||||
bonus_chest: true
|
||||
}
|
||||
);
|
||||
// The More tab's Experiments and Data Packs screens don't exist yet.
|
||||
// The More tab: Experiments has no screen yet; Data
|
||||
// Packs opens on a new pack folder, and its selection reaches the action.
|
||||
f.title.click(54.0 + 248.0 + 10.0, 10.0, size);
|
||||
let more = labels(&f);
|
||||
assert_eq!(
|
||||
labels(&f)[..3].iter().map(|b| b.3).collect::<Vec<_>>(),
|
||||
[true, false, false]
|
||||
more[..3].iter().map(|b| b.3).collect::<Vec<_>>(),
|
||||
[true, false, true]
|
||||
);
|
||||
let (action, sound) = f.title.click(more[2].1 + 10.0, more[2].2 + 10.0, size);
|
||||
let Action::DataPacks { dir, selected } = action else {
|
||||
panic!("{action:?}")
|
||||
};
|
||||
assert!(sound && dir.is_dir() && selected == ["vanilla"]);
|
||||
f.title
|
||||
.set_data_packs(vec!["vanilla".into(), "file/ttm.zip".into()]);
|
||||
let Action::Create { options, .. } = f.title.create_action() else {
|
||||
panic!()
|
||||
};
|
||||
assert_eq!(options.data_packs, ["vanilla", "file/ttm.zip"]);
|
||||
assert_eq!(options.pack_dir.as_ref(), Some(&dir));
|
||||
// Cancel removes the folder (removeTempDataPackDir).
|
||||
f.title.escape();
|
||||
assert!(!dir.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -641,7 +641,7 @@ impl ChunkGenerator for VanillaTerrain {
|
||||
crate::structure_blocks::block_entities(&chunk, self.loot.as_deref());
|
||||
out.block_entities = block_entities;
|
||||
// The block entities Horizon does not model (decorated pots with their
|
||||
// sherds and loot table, brushable blocks) stay as placed, like a
|
||||
// sherds and loot table) stay as placed, like a
|
||||
// loaded chunk's.
|
||||
let mut unmodelled: Vec<Tag> = chunk
|
||||
.block_entities
|
||||
|
||||
@@ -107,6 +107,8 @@ pub struct ItemInfo {
|
||||
pub weapon_damage: Option<u32>,
|
||||
/// `use_effects.speed_multiplier` (UseEffects.DEFAULT: 0.2).
|
||||
pub use_speed: f32,
|
||||
/// `use_effects.interact_vibrations` (default true).
|
||||
pub interact_vibrations: bool,
|
||||
/// `glider` (elytra).
|
||||
pub glider: bool,
|
||||
/// `equippable.swappable` and `equip_sound`.
|
||||
@@ -663,6 +665,9 @@ impl Items {
|
||||
use_speed: c["minecraft:use_effects"]["speed_multiplier"]
|
||||
.as_f64()
|
||||
.unwrap_or(0.2) as f32,
|
||||
interact_vibrations: c["minecraft:use_effects"]["interact_vibrations"]
|
||||
.as_bool()
|
||||
.unwrap_or(true),
|
||||
blocks_attacks: BlocksAttacks::parse(&c["minecraft:blocks_attacks"]),
|
||||
disable_blocking_seconds: c["minecraft:weapon"]["disable_blocking_for_seconds"]
|
||||
.as_f64()
|
||||
|
||||
@@ -504,6 +504,72 @@ impl<'r> BlockSection<'r> {
|
||||
}
|
||||
Ok(old)
|
||||
}
|
||||
/// Write `len` consecutive values at (`x`, `y_start..y_start+len`, `z`) with
|
||||
/// `state`, all inside this section. Resolves the palette id once, then writes
|
||||
/// the rest directly; returns how many previous blocks were `air` so the
|
||||
/// caller can update its non-air count. The final palette and counts match
|
||||
/// `len` calls to `set` in the same top-down order.
|
||||
pub fn fill_run(
|
||||
&mut self,
|
||||
x: i32,
|
||||
y_start: i32,
|
||||
z: i32,
|
||||
len: i32,
|
||||
state: BlockStateId,
|
||||
air: BlockStateId,
|
||||
) -> io::Result<u16> {
|
||||
if !(0..16).contains(&x)
|
||||
|| !(0..16).contains(&z)
|
||||
|| len <= 0
|
||||
|| y_start < 0
|
||||
|| y_start + len > 16
|
||||
{
|
||||
return Err(invalid("section fill outside its bounds"));
|
||||
}
|
||||
let index = |y: i32| ((y as usize) << 8) | ((z as usize) << 4) | x as usize;
|
||||
let top = y_start + len - 1;
|
||||
let mut added = 0u16;
|
||||
// The first write takes the general path: it may add the state to the
|
||||
// palette, allocate storage, or find the section already uniform.
|
||||
let old = self.set(index(top), state)?;
|
||||
if old == air {
|
||||
added += 1;
|
||||
}
|
||||
let Some(id) = id_in(&self.palette, &self.counts, &mut self.last, state) else {
|
||||
return Ok(added);
|
||||
};
|
||||
let Some(storage) = self.storage.as_mut() else {
|
||||
return Ok(added);
|
||||
};
|
||||
for y in (y_start..top).rev() {
|
||||
let i = index(y);
|
||||
let old_id = storage.read(i);
|
||||
let old_state = self.palette[old_id as usize];
|
||||
if old_state == state {
|
||||
continue;
|
||||
}
|
||||
storage.write(i, id);
|
||||
self.counts[old_id as usize] -= 1;
|
||||
self.counts[id as usize] += 1;
|
||||
if old_state == air {
|
||||
added += 1;
|
||||
}
|
||||
if self.counts[old_id as usize] == 0 {
|
||||
self.free.push(old_id);
|
||||
if self.last == Some((old_state, old_id)) {
|
||||
self.last = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.counts[id as usize] == 4096 {
|
||||
self.palette = vec![state];
|
||||
self.last = Some((state, 0));
|
||||
self.counts = vec![4096];
|
||||
self.free.clear();
|
||||
self.storage = None;
|
||||
}
|
||||
Ok(added)
|
||||
}
|
||||
pub fn compact(&mut self) -> io::Result<()> {
|
||||
let values: Vec<_> = (0..4096).map(|i| self.get(i).unwrap()).collect();
|
||||
self.palette.clear();
|
||||
|
||||
@@ -567,6 +567,7 @@ props! {
|
||||
creaking_heart_state = "creaking_heart_state",
|
||||
cracked = "cracked",
|
||||
has_book = "has_book",
|
||||
has_record = "has_record",
|
||||
signal_fire = "signal_fire",
|
||||
orientation = "orientation",
|
||||
crafting = "crafting",
|
||||
|
||||
@@ -397,9 +397,11 @@ impl<L: LevelAccess + ?Sized> Sim<'_, L> {
|
||||
| Class::BrewingStand
|
||||
| Class::CartographyTable
|
||||
| Class::Chest
|
||||
| Class::CopperChest
|
||||
| Class::Crafter
|
||||
| Class::CraftingTable
|
||||
| Class::Dispenser
|
||||
| Class::Dropper
|
||||
| Class::EnchantingTable
|
||||
| Class::EnderChest
|
||||
| Class::Grindstone
|
||||
@@ -409,10 +411,16 @@ impl<L: LevelAccess + ?Sized> Sim<'_, L> {
|
||||
| Class::Sign
|
||||
| Class::SmithingTable
|
||||
| Class::Stonecutter
|
||||
| Class::TrappedChest
|
||||
| Class::Lectern => {
|
||||
self.player_effect(pos, state, PlayerEffect::OpenMenu);
|
||||
Success
|
||||
}
|
||||
// DecoratedPotBlock.useWithoutItem: the wobble always succeeds.
|
||||
Class::DecoratedPot => Success,
|
||||
// JukeboxBlock.useWithoutItem: ejecting a disc is SUCCESS (the
|
||||
// insert is useItemOn). An empty jukebox passes.
|
||||
Class::Jukebox if table.bool(state, p.has_record) => Success,
|
||||
Class::Command | Class::Structure | Class::Jigsaw => {
|
||||
if player.game_master {
|
||||
self.player_effect(pos, state, PlayerEffect::OpenMenu);
|
||||
|
||||
@@ -1474,6 +1474,44 @@ fn big_dripleaf_tilts_under_an_entity_standing_on_it() {
|
||||
assert_eq!(w.prop(pos, "tilt"), "none");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn using_storage_and_interactable_blocks_succeeds() {
|
||||
let mut w = world!();
|
||||
let pos = BlockPos::new(0, 1, 0);
|
||||
let cases: &[(&str, &[(&str, &str)])] = &[
|
||||
("chest", &[("facing", "north"), ("type", "single")]),
|
||||
("trapped_chest", &[("facing", "north"), ("type", "single")]),
|
||||
("barrel", &[("facing", "up")]),
|
||||
("ender_chest", &[("facing", "north")]),
|
||||
("shulker_box", &[("facing", "up")]),
|
||||
("dispenser", &[("facing", "north")]),
|
||||
("dropper", &[("facing", "north")]),
|
||||
("hopper", &[("facing", "down")]),
|
||||
("crafter", &[("orientation", "north_up")]),
|
||||
("furnace", &[("facing", "north")]),
|
||||
("blast_furnace", &[("facing", "north")]),
|
||||
("smoker", &[("facing", "north")]),
|
||||
("brewing_stand", &[]),
|
||||
("beacon", &[]),
|
||||
("decorated_pot", &[]),
|
||||
("jukebox", &[("has_record", "true")]),
|
||||
];
|
||||
for &(name, props) in cases {
|
||||
w.put(pos, w.id(name, props));
|
||||
assert_eq!(
|
||||
w.use_block(pos),
|
||||
InteractionResult::Success,
|
||||
"{name} should succeed (client swing)"
|
||||
);
|
||||
}
|
||||
w.put(pos, w.id("jukebox", &[("has_record", "false")]));
|
||||
assert_eq!(
|
||||
w.use_block(pos),
|
||||
InteractionResult::Pass,
|
||||
"an empty jukebox passes"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn creative_breaking_piston_head_does_not_drop_piston() {
|
||||
for (piston_name, head_type) in [("piston", "normal"), ("sticky_piston", "sticky")] {
|
||||
|
||||
@@ -215,6 +215,22 @@ impl<P: FluidPicker, F: PositionalRandom, B: AquiferBlockStates> NoiseBasedAquif
|
||||
pub fn skip_sampling_above_y(&self) -> i32 {
|
||||
self.skip_above_y
|
||||
}
|
||||
/// The fill decided a block without this aquifer. Both old decisions
|
||||
/// (positive density and air above the skip height) cleared the pending
|
||||
/// fluid update, so the caller reproduces that side effect.
|
||||
pub fn note_skipped(&mut self) {
|
||||
self.schedule = false;
|
||||
}
|
||||
/// The y above which a non-positive density is the global fluid's air. The
|
||||
/// vanilla fluid rules depend only on y; a picker that varies with x/z keeps
|
||||
/// the per-block call by answering `None`.
|
||||
pub fn air_above_y(&self) -> Option<i32> {
|
||||
let center = self.picker.compute([0, i32::MAX, 0]);
|
||||
let east = self.picker.compute([i32::MAX, i32::MAX, i32::MAX]);
|
||||
let west = self.picker.compute([i32::MIN, i32::MAX, i32::MIN]);
|
||||
(center.level == east.level && center.level == west.level)
|
||||
.then(|| self.skip_above_y.max(center.level))
|
||||
}
|
||||
fn sample(
|
||||
&self,
|
||||
field: AquiferField,
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
//! Apply ordered carving-mask intervals to pre-lighting terrain.
|
||||
use super::{
|
||||
material_condition::{MaterialRule, MaterialSurfaceQueries},
|
||||
material_stage::{SurfaceError, TopMaterialInputs, top_material},
|
||||
material_condition::{
|
||||
MaterialConditions, MaterialDensitySources, MaterialRule, MaterialSurfaceQueries,
|
||||
},
|
||||
material_stage::{SurfaceError, TopMaterialInputs, top_material_cell},
|
||||
terrain::{TerrainAquifer, TerrainBlockStates, TerrainChunk, TerrainError},
|
||||
};
|
||||
use crate::{
|
||||
heightmap::HeightmapState,
|
||||
registry::{BlockRegistry, BlockStateId},
|
||||
};
|
||||
use horizon_core::{carving::CarvingMask, position::BlockPos, random::RandomSource};
|
||||
use horizon_core::{
|
||||
carving::CarvingMask, density::SamplerContext, noise::DensityVolume, position::BlockPos,
|
||||
random::RandomSource,
|
||||
};
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
/// Per-state predicates resolved once from the world's block registry and
|
||||
@@ -60,7 +65,7 @@ pub fn apply_carving_mask<R: RandomSource, S: MaterialSurfaceQueries, B: Terrain
|
||||
chunk: &mut TerrainChunk<'_, B>,
|
||||
mask: &CarvingMask,
|
||||
rule: MaterialRule,
|
||||
mut inputs: TopMaterialInputs<'_, R, S>,
|
||||
inputs: TopMaterialInputs<'_, R, S>,
|
||||
aquifer: &mut (impl TerrainAquifer + ?Sized),
|
||||
blocks: &CarvingBlocks,
|
||||
mut filter: impl FnMut([i32; 3]) -> bool,
|
||||
@@ -68,6 +73,27 @@ pub fn apply_carving_mask<R: RandomSource, S: MaterialSurfaceQueries, B: Terrain
|
||||
where
|
||||
R::Positional: Clone,
|
||||
{
|
||||
// Restoring the dirt under a carved cell used to build a fresh material
|
||||
// worker, compile the whole surface rule and allocate its bindings for every
|
||||
// cell. One worker now serves the pass; only the 1x1x1 bindings move.
|
||||
let TopMaterialInputs {
|
||||
generation,
|
||||
root,
|
||||
surface,
|
||||
preliminary_surface,
|
||||
density_context,
|
||||
} = inputs;
|
||||
let volume =
|
||||
DensityVolume::new([1; 3], [0; 3], [1; 3]).map_err(|_| SurfaceError::InvalidVolume)?;
|
||||
let sources = MaterialDensitySources::new(
|
||||
surface,
|
||||
volume,
|
||||
preliminary_surface,
|
||||
SamplerContext::default(),
|
||||
)?;
|
||||
let mut worker =
|
||||
MaterialConditions::<R, _>::with_possible_biomes(generation, root, sources, None);
|
||||
let rule = worker.add_rule(rule)?;
|
||||
let cx = chunk.position().x.wrapping_shl(4);
|
||||
let cz = chunk.position().z.wrapping_shl(4);
|
||||
for column in mask.columns() {
|
||||
@@ -84,7 +110,7 @@ where
|
||||
}
|
||||
found_surface |= flags & 2 != 0;
|
||||
let Some(state) = aquifer
|
||||
.compute_substance(inputs.density_context, [x, y, z], 0.)
|
||||
.compute_substance(density_context, [x, y, z], 0.)
|
||||
.map_err(TerrainError::from)?
|
||||
else {
|
||||
continue;
|
||||
@@ -96,16 +122,11 @@ where
|
||||
let below = y.wrapping_sub(1);
|
||||
if found_surface && blocks.flags(chunk.block(x, below, z))? & 4 != 0 {
|
||||
let submerged = chunk.block_properties(state).has_fluid();
|
||||
let replacement = top_material(
|
||||
worker.retarget_surface_densities([x, below, z])?;
|
||||
let replacement = top_material_cell(
|
||||
&mut worker,
|
||||
chunk,
|
||||
rule.clone(),
|
||||
TopMaterialInputs::<R, _> {
|
||||
generation: inputs.generation,
|
||||
root: inputs.root.clone(),
|
||||
surface: &mut inputs.surface,
|
||||
preliminary_surface: inputs.preliminary_surface.clone(),
|
||||
density_context: inputs.density_context,
|
||||
},
|
||||
rule,
|
||||
BlockPos::new(x, below, z),
|
||||
submerged,
|
||||
)?;
|
||||
|
||||
@@ -31,6 +31,36 @@ impl<S: MaterialSurfaceQueries> MaterialDensitySources<S> {
|
||||
pub fn sampler_context_mut(&mut self) -> &mut SamplerContext {
|
||||
&mut self.context
|
||||
}
|
||||
/// Re-target the per-cell bindings to one block. A carver reuses one worker
|
||||
/// across every restored dirt block, so the 1×1×1 volumes the bindings and
|
||||
/// the preliminary plane sample must follow the cell; the compiled rule is
|
||||
/// kept.
|
||||
pub fn retarget(&mut self, position: [i32; 3]) -> Result<(), DecodeError> {
|
||||
let volume = DensityVolume::new([1; 3], position, [1; 3])
|
||||
.map_err(|_| DecodeError::Field("material density volume"))?;
|
||||
let plane = DensityVolume::new([1; 3], [position[0], 0, position[2]], [1; 3])
|
||||
.map_err(|_| DecodeError::Field("material preliminary plane"))?;
|
||||
self.expected = volume;
|
||||
self.plane = plane;
|
||||
// The plane is sampled lazily at the new x/z.
|
||||
self.buffer = None;
|
||||
let mut sampled = vec![false; self.buffers.len()];
|
||||
let mut pending = Vec::new();
|
||||
for bound in &self.densities {
|
||||
let Some(buffer) = bound.buffer else { continue };
|
||||
if sampled.get(buffer).copied().unwrap_or(true) {
|
||||
continue;
|
||||
}
|
||||
sampled[buffer] = true;
|
||||
pending.push((bound.sampler.clone(), buffer));
|
||||
}
|
||||
for (sampler, buffer) in pending {
|
||||
let out = &mut self.buffers[buffer];
|
||||
out.resize(volume.size(), 0.0);
|
||||
sampler.sample_volume(&mut self.context, out, volume)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn into_parts(self) -> (S, SamplerContext) {
|
||||
(self.surface, self.context)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//! Compiled material rule composition, sharing the condition worker's context.
|
||||
use super::{
|
||||
Compiled, Context, Facts, MaterialCondition, MaterialConditions, MaterialSurfaceQueries,
|
||||
density::MaterialDensitySources,
|
||||
};
|
||||
use crate::{registry::BlockStateId, worldgen::DecodeError};
|
||||
use horizon_core::{
|
||||
@@ -366,6 +367,12 @@ where
|
||||
})
|
||||
}
|
||||
}
|
||||
impl<R: RandomSource, T: MaterialSurfaceQueries> MaterialConditions<R, MaterialDensitySources<T>> {
|
||||
/// Re-target the per-cell density bindings for a reused worker.
|
||||
pub fn retarget_surface_densities(&mut self, position: [i32; 3]) -> Result<(), DecodeError> {
|
||||
self.context.surface.retarget(position)
|
||||
}
|
||||
}
|
||||
impl<R: RandomSource, S: MaterialSurfaceQueries> MaterialConditions<R, S> {
|
||||
/// Compile all children in order, even after an unconditional block rule.
|
||||
/// Failed compilation never adds a partial rule to this worker.
|
||||
|
||||
@@ -84,17 +84,65 @@ struct Column<'a, 'r, B: TerrainBlockStates> {
|
||||
z: i32,
|
||||
min: i32,
|
||||
max: i32,
|
||||
/// Resolved block ids for `low..=high`, reused across columns and updated by
|
||||
/// `set_block`. The surface walk reads a column twice (top to bottom, then the
|
||||
/// stone-depth look-behind); the palette decode is paid once per column.
|
||||
cache: &'a mut Vec<BlockStateId>,
|
||||
low: i32,
|
||||
high: i32,
|
||||
}
|
||||
impl<'a, 'r, B: TerrainBlockStates> Column<'a, 'r, B> {
|
||||
fn new(
|
||||
chunk: &'a mut TerrainChunk<'r, B>,
|
||||
x: i32,
|
||||
z: i32,
|
||||
min: i32,
|
||||
max: i32,
|
||||
top: i32,
|
||||
cache: &'a mut Vec<BlockStateId>,
|
||||
) -> Self {
|
||||
let chunk_min = chunk.min_y();
|
||||
let chunk_max = chunk_min.wrapping_add(chunk.height()).wrapping_sub(1);
|
||||
let high = top.min(chunk_max);
|
||||
let low = min.max(chunk_min);
|
||||
cache.clear();
|
||||
if high >= low {
|
||||
cache.reserve((high - low + 1) as usize);
|
||||
for y in low..=high {
|
||||
cache.push(chunk.block(x, y, z));
|
||||
}
|
||||
}
|
||||
Self {
|
||||
chunk,
|
||||
x,
|
||||
z,
|
||||
min,
|
||||
max,
|
||||
cache,
|
||||
low,
|
||||
high,
|
||||
}
|
||||
}
|
||||
fn cache_index(&self, y: i32) -> Option<usize> {
|
||||
(y >= self.low && y <= self.high).then(|| (y - self.low) as usize)
|
||||
}
|
||||
}
|
||||
impl<B: TerrainBlockStates> MaterialColumn for Column<'_, '_, B> {
|
||||
type Error = SurfaceError;
|
||||
fn block(&mut self, y: i32) -> Result<BlockStateId, SurfaceError> {
|
||||
Ok(self.chunk.block(self.x, y, self.z))
|
||||
Ok(match self.cache_index(y) {
|
||||
Some(index) => self.cache[index],
|
||||
None => self.chunk.block(self.x, y, self.z),
|
||||
})
|
||||
}
|
||||
fn set_block(&mut self, y: i32, state: BlockStateId) -> Result<(), SurfaceError> {
|
||||
if y >= self.min && y <= self.max {
|
||||
// Most of the Nether's rule writes netherrack over netherrack.
|
||||
if self.chunk.block(self.x, y, self.z) != state {
|
||||
if self.block(y)? != state {
|
||||
self.chunk.set_block_worldgen(self.x, y, self.z, state)?;
|
||||
if let Some(index) = self.cache_index(y) {
|
||||
self.cache[index] = state;
|
||||
}
|
||||
}
|
||||
if self.chunk.block_properties(state).has_fluid() {
|
||||
self.chunk.mark_postprocessing(self.x, y, self.z);
|
||||
@@ -132,6 +180,45 @@ fn tick(remaining: &mut usize) -> Result<(), SurfaceError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Apply a compiled top-material rule at one position with a worker the caller
|
||||
/// built once. Carvers restore many cells per pass, so the rule is compiled and
|
||||
/// the density bindings created once instead of per restored block.
|
||||
pub(crate) fn top_material_cell<
|
||||
R: RandomSource,
|
||||
S: MaterialSurfaceQueries,
|
||||
B: TerrainBlockStates,
|
||||
>(
|
||||
worker: &mut MaterialConditions<R, S>,
|
||||
chunk: &mut TerrainChunk<'_, B>,
|
||||
rule: usize,
|
||||
position: BlockPos,
|
||||
submerged: bool,
|
||||
) -> Result<Option<BlockStateId>, SurfaceError> {
|
||||
let (lx, lz) = (position.x & 15, position.z & 15);
|
||||
let gx = first_available(chunk, (lx + 1).min(15), lz)?.wrapping_sub(first_available(
|
||||
chunk,
|
||||
(lx - 1).max(0),
|
||||
lz,
|
||||
)?);
|
||||
let gz = first_available(chunk, lx, (lz + 1).min(15))?.wrapping_sub(first_available(
|
||||
chunk,
|
||||
lx,
|
||||
(lz - 1).max(0),
|
||||
)?);
|
||||
worker.update_xz(position.x, position.z, gx, gz);
|
||||
worker.update_y(
|
||||
1,
|
||||
1,
|
||||
if submerged {
|
||||
position.y.wrapping_add(1)
|
||||
} else {
|
||||
i32::MIN
|
||||
},
|
||||
position.y,
|
||||
);
|
||||
worker.try_apply(rule, position).map_err(SurfaceError::from)
|
||||
}
|
||||
|
||||
/// Evaluate the replacement surface under a carved block. Each call binds a
|
||||
/// fresh one-cell material context, without restricting possible biomes, and
|
||||
/// uses one stone block above/below. The caller applies the returned state.
|
||||
@@ -161,29 +248,7 @@ pub fn top_material<R: RandomSource, S: MaterialSurfaceQueries, B: TerrainBlockS
|
||||
);
|
||||
let result = (|| {
|
||||
let rule = worker.add_rule(rule)?;
|
||||
let (lx, lz) = (position.x & 15, position.z & 15);
|
||||
let gx = first_available(chunk, (lx + 1).min(15), lz)?.wrapping_sub(first_available(
|
||||
chunk,
|
||||
(lx - 1).max(0),
|
||||
lz,
|
||||
)?);
|
||||
let gz = first_available(chunk, lx, (lz + 1).min(15))?.wrapping_sub(first_available(
|
||||
chunk,
|
||||
lx,
|
||||
(lz - 1).max(0),
|
||||
)?);
|
||||
worker.update_xz(position.x, position.z, gx, gz);
|
||||
worker.update_y(
|
||||
1,
|
||||
1,
|
||||
if submerged {
|
||||
position.y.wrapping_add(1)
|
||||
} else {
|
||||
i32::MIN
|
||||
},
|
||||
position.y,
|
||||
);
|
||||
worker.try_apply(rule, position).map_err(SurfaceError::from)
|
||||
top_material_cell(&mut worker, chunk, rule, position, submerged)
|
||||
})();
|
||||
let (_, mut context) = worker.into_surface().into_parts();
|
||||
std::mem::swap(&mut context, inputs.density_context);
|
||||
@@ -255,6 +320,8 @@ where
|
||||
let variants = worker.surface_variants(rule)?;
|
||||
let cx = chunk.position().x.wrapping_shl(4);
|
||||
let cz = chunk.position().z.wrapping_shl(4);
|
||||
// One block-id buffer, refilled per column and kept across columns.
|
||||
let mut cache = Vec::new();
|
||||
for lx in 0..16 {
|
||||
for lz in 0..16 {
|
||||
let x = cx.wrapping_add(lx);
|
||||
@@ -265,13 +332,15 @@ where
|
||||
.ok_or(MaterialRuleError::MissingBiome)?;
|
||||
if biome.as_ref() == "minecraft:eroded_badlands" {
|
||||
extensions.eroded_badlands(
|
||||
&mut Column {
|
||||
&mut Column::new(
|
||||
chunk,
|
||||
x,
|
||||
z,
|
||||
min: write_min_y,
|
||||
max: write_max_y,
|
||||
},
|
||||
write_min_y,
|
||||
write_max_y,
|
||||
original_top,
|
||||
&mut cache,
|
||||
),
|
||||
x,
|
||||
z,
|
||||
original_top,
|
||||
@@ -295,13 +364,7 @@ where
|
||||
let mut bottom = i32::MAX;
|
||||
let mut y = top;
|
||||
let mut budget = 1 << 20;
|
||||
let mut column = Column {
|
||||
chunk,
|
||||
x,
|
||||
z,
|
||||
min: write_min_y,
|
||||
max: write_max_y,
|
||||
};
|
||||
let mut column = Column::new(chunk, x, z, write_min_y, write_max_y, top, &mut cache);
|
||||
while y >= min {
|
||||
tick(&mut budget)?;
|
||||
let state = column.block(y)?;
|
||||
|
||||
@@ -31,6 +31,15 @@ pub trait TerrainAquifer {
|
||||
density: f64,
|
||||
) -> Result<Option<BlockStateId>, AquiferError>;
|
||||
fn should_schedule_fluid_update(&self) -> bool;
|
||||
/// The y above which every non-positive density is the aquifer's air. The
|
||||
/// fill skips those blocks without asking the aquifer; `None` keeps the
|
||||
/// per-block call for aquifers whose global fluid can vary with x/z.
|
||||
fn air_above_y(&self) -> Option<i32> {
|
||||
None
|
||||
}
|
||||
/// The fill decided this block without the aquifer (positive density or air
|
||||
/// above its skip height). Both old paths cleared the pending fluid update.
|
||||
fn note_skipped(&mut self) {}
|
||||
}
|
||||
impl<P: FluidPicker> TerrainAquifer for DisabledAquifer<P> {
|
||||
fn compute_substance(
|
||||
@@ -59,6 +68,12 @@ impl<P: FluidPicker, F: PositionalRandom, B: AquiferBlockStates> TerrainAquifer
|
||||
fn should_schedule_fluid_update(&self) -> bool {
|
||||
NoiseBasedAquifer::should_schedule_fluid_update(self)
|
||||
}
|
||||
fn air_above_y(&self) -> Option<i32> {
|
||||
NoiseBasedAquifer::air_above_y(self)
|
||||
}
|
||||
fn note_skipped(&mut self) {
|
||||
NoiseBasedAquifer::note_skipped(self);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -430,6 +445,44 @@ impl<'r, B: TerrainBlockStates> TerrainChunk<'r, B> {
|
||||
}
|
||||
Ok(old)
|
||||
}
|
||||
/// Write a vertical run of one non-air state from `y_top` downward, all
|
||||
/// inside the chunk. Returns how many blocks changed from air; section
|
||||
/// non-air counts are updated here and the caller owns the heightmaps.
|
||||
pub fn fill_solid_run(
|
||||
&mut self,
|
||||
x: i32,
|
||||
y_top: i32,
|
||||
z: i32,
|
||||
len: i32,
|
||||
state: BlockStateId,
|
||||
) -> Result<u16, TerrainError> {
|
||||
let air = self.blocks.air;
|
||||
let mut added = 0u16;
|
||||
let mut top = y_top;
|
||||
let mut left = len;
|
||||
while left > 0 {
|
||||
let section = self
|
||||
.blocks
|
||||
.section_index(top)
|
||||
.ok_or(TerrainError::OutsideBuildHeight)?;
|
||||
let local_top = top & 15;
|
||||
let take = (local_top + 1).min(left);
|
||||
let local_start = local_top - take + 1;
|
||||
let now = self.blocks.sections[section].fill_run(
|
||||
x & 15,
|
||||
local_start,
|
||||
z & 15,
|
||||
take,
|
||||
state,
|
||||
air,
|
||||
)?;
|
||||
self.blocks.non_air[section] = self.blocks.non_air[section].saturating_add(now);
|
||||
added = added.saturating_add(now);
|
||||
top -= take;
|
||||
left -= take;
|
||||
}
|
||||
Ok(added)
|
||||
}
|
||||
pub fn update_heightmap(
|
||||
&mut self,
|
||||
kind: HeightmapType,
|
||||
@@ -517,6 +570,15 @@ pub fn fill_noise<B: TerrainBlockStates>(
|
||||
chunk.ensure_heightmap(HeightmapType::WorldSurfaceWg);
|
||||
let mut density = vec![0.; volume.size()];
|
||||
final_density.sample_volume(context, &mut density, volume)?;
|
||||
let air_above = aquifer.air_above_y();
|
||||
// A non-air, non-fluid default block may be written as a run without asking
|
||||
// the aquifer or touching the heightmap below the run's top.
|
||||
let solid_runs = default_block != chunk.blocks.air
|
||||
&& !chunk
|
||||
.blocks
|
||||
.properties
|
||||
.heightmap_state(default_block)
|
||||
.has_fluid();
|
||||
const MAPS: [HeightmapType; 2] = [HeightmapType::OceanFloorWg, HeightmapType::WorldSurfaceWg];
|
||||
for z in 0..sz {
|
||||
let world_z = bz.wrapping_add(z as i32);
|
||||
@@ -531,12 +593,58 @@ pub fn fill_noise<B: TerrainBlockStates>(
|
||||
.first_available(x as i32, z as i32)
|
||||
.map(|first| first.wrapping_sub(2))
|
||||
});
|
||||
for y in (0..sy).rev() {
|
||||
let mut y = sy;
|
||||
while y > 0 {
|
||||
y -= 1;
|
||||
let world_y = by.wrapping_add(y as i32);
|
||||
let value = density[volume.index([x, y, z]).expect("valid density coordinates")];
|
||||
let state = aquifer
|
||||
.compute_substance(context, [world_x, world_y, world_z], f64::from(value))?
|
||||
.unwrap_or(default_block);
|
||||
// A positive-density run is entirely the default block. Write it
|
||||
// through the section once instead of per block; the heightmap
|
||||
// only changes at the run's top.
|
||||
if solid_runs && value > 0.0 {
|
||||
let mut run = 1usize;
|
||||
while run <= y
|
||||
&& density[volume
|
||||
.index([x, y - run, z])
|
||||
.expect("valid density coordinates")]
|
||||
> 0.0
|
||||
{
|
||||
run += 1;
|
||||
}
|
||||
aquifer.note_skipped();
|
||||
chunk.fill_solid_run(x as i32, world_y, z as i32, run as i32, default_block)?;
|
||||
for (kind, settled) in MAPS.into_iter().zip(&mut settled) {
|
||||
if matches!(settled, Ok(bound) if world_y <= *bound) {
|
||||
continue;
|
||||
}
|
||||
chunk.update_heightmap(kind, x as i32, world_y, z as i32, default_block)?;
|
||||
*settled = chunk.heightmaps[kind as usize]
|
||||
.as_ref()
|
||||
.expect("ensured heightmap")
|
||||
.first_available(x as i32, z as i32)
|
||||
.map(|first| first.wrapping_sub(2));
|
||||
}
|
||||
y -= run - 1;
|
||||
continue;
|
||||
}
|
||||
// The aquifer answers `None` for positive density, which the fill
|
||||
// writes as the default block, and air above its skip/fluid level.
|
||||
// Both are decided here so the per-block call is not made at all,
|
||||
// with the same pending-update side effect the call had.
|
||||
let solid = value > 0.0;
|
||||
if solid {
|
||||
aquifer.note_skipped();
|
||||
}
|
||||
let state = if solid {
|
||||
default_block
|
||||
} else if air_above.is_some_and(|limit| world_y > limit) {
|
||||
aquifer.note_skipped();
|
||||
continue;
|
||||
} else {
|
||||
aquifer
|
||||
.compute_substance(context, [world_x, world_y, world_z], f64::from(value))?
|
||||
.unwrap_or(default_block)
|
||||
};
|
||||
if state == chunk.blocks.air {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ Every command `horizon-ctl` takes, its MCP tool in `horizon-ctl mcp` (the launch
|
||||
| `potion ITEM POTION` | `potion` | Give a potion item | `item` (potion, splash_potion, lingering_potion, tipped_arrow, required): which item<br>`potion` (string, required): potion id |
|
||||
| `fill X1 Y1 Z1 X2 Y2 Z2 BLOCK` | `fill` (destructive) | Set a box of loaded blocks | `x1` (integer, required): corner X<br>`y1` (integer, required): corner Y<br>`z1` (integer, required): corner Z<br>`x2` (integer, required): opposite X<br>`y2` (integer, required): opposite Y<br>`z2` (integer, required): opposite Z<br>`block` (string, required): block state |
|
||||
| `container X Y Z [ITEM COUNT]` | `container` | List a container's items, or add items to it | `x` (integer, required): block X<br>`y` (integer, required): block Y<br>`z` (integer, required): block Z<br>`item` (string): item to add<br>`count` (integer): how many |
|
||||
| `render-distance N` | CLI only | Chunks, 2..64 | `chunks` (integer, required): render distance |
|
||||
| `render-distance N` | CLI only | Chunks, 2..128 | `chunks` (integer, required): render distance |
|
||||
| `wait TICKS` | `wait` | Wait until TICKS game ticks have passed (20 per second) | `ticks` (integer, required): game ticks |
|
||||
|
||||
## Scripting and benchmarks
|
||||
|
||||
@@ -48,7 +48,7 @@ Development builds that link quickly: `cargo build --profile fast -p horizon-cli
|
||||
| `--world NAME` | World folder under `saves/` (default "New World"); reopened if it exists |
|
||||
| `--seed N` | Seed for a new world (numbers or text, like vanilla) |
|
||||
| `--creative` | Creative mode (instant breaking, pick block) |
|
||||
| `--render-distance N` | Chunks, 2..64 (default 12) |
|
||||
| `--render-distance N` | Chunks, 2..128 (default 12) |
|
||||
| `--resources PATH` | Prepared resource directory |
|
||||
|
||||
| Control | Action |
|
||||
|
||||
@@ -123,6 +123,16 @@ at a time, with each class's Java source, home files, and Rust functions in them
|
||||
for whoever (or whichever agent) writes the next batch file. Set `PORT_LEDGER_OUT` to build into another
|
||||
directory, so several people can check their own rows at once.
|
||||
|
||||
## Parity harness coverage
|
||||
|
||||
A mapped method says where the port is, not that it behaves like vanilla; the parity harnesses check that
|
||||
(`tools/test_*_parity.py`: a vanilla oracle in `mods/horizon/src/test/java/dev/horizonlab/parity/` and a Rust
|
||||
fixture or probe under `crates/*/examples/` run on the same inputs). The ledger links them: a harness covers a
|
||||
ported method when its fixture reaches the port and the vanilla side is in reach (the oracle calls into the
|
||||
method, or the harness or oracle names its class). `where` lists a method's harnesses, and
|
||||
`gaps --kind untested [--package world/entity]` lists the sure pairs (cited or mapped) with behaviour of their
|
||||
own that no harness covers, most-called first: where a new harness would catch the most drift.
|
||||
|
||||
## Bug reports and branches
|
||||
|
||||
`issue N` reads a GitHub issue (`gh issue view`), or takes the report's text (`-` reads stdin), drops the
|
||||
|
||||
@@ -309,7 +309,7 @@ public final class VanillaPackScreenOracle {
|
||||
List<String> initial = new ArrayList<>();
|
||||
for (var s : c.getAsJsonArray("selected")) initial.add(s.getAsString());
|
||||
repository.setSelected(initial);
|
||||
var screen = new PackSelectionScreen(repository, r -> { }, Path.of(c.get("dir").getAsString()), Component.translatable("resourcePack.title"));
|
||||
var screen = new PackSelectionScreen(repository, r -> { }, Path.of(c.get("dir").getAsString()), Component.translatable(c.get("title").getAsString()));
|
||||
gui.setScreen(screen);
|
||||
// PackSelectionScreen.loadPackIcon: a pack with a pack.png gets its own texture.
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -102,6 +102,8 @@ impl Game {
|
||||
self.audio.play("hit", None, 1.0, 1.0);
|
||||
}
|
||||
}
|
||||
// Tutorial.onDestroyBlock (tutorial.rs): the hints play no part in mining.
|
||||
fn tutorial_destroy<T>(&mut self, _: T, _: f32) {}
|
||||
METHOD
|
||||
}
|
||||
fn main() {
|
||||
|
||||
@@ -80,7 +80,7 @@ class RepositoryRoadmap(unittest.TestCase):
|
||||
|
||||
def test_generated_files_match_the_toml(self):
|
||||
for path, text in roadmap.outputs(roadmap.load()).items():
|
||||
self.assertEqual(path.read_text(), text, f"{path.name} is out of date: run `python3 tools/roadmap.py build`")
|
||||
self.assertEqual(path.read_text(encoding="utf-8"), text, f"{path.name} is out of date: run `python3 tools/roadmap.py build`")
|
||||
|
||||
def test_site_data_has_every_item_in_one_line_text(self):
|
||||
data = json.loads(roadmap.site_data(tomllib.loads(SAMPLE)))
|
||||
|
||||
+92
-2
@@ -1031,6 +1031,70 @@ def registry_aliases(sources, van):
|
||||
return out
|
||||
|
||||
|
||||
ORACLE_DIR = ROOT / "mods" / "horizon" / "src" / "test" / "java" / "dev" / "horizonlab" / "parity"
|
||||
|
||||
|
||||
def harness_coverage(van, methods, fns, edges, known):
|
||||
"""The parity harnesses that cover each ported method. A harness (tools/test_*_parity.py) runs a vanilla oracle
|
||||
(mods/horizon/src/test/java/.../parity/*.java) and a Rust fixture (crates/*/examples/**/*_fixture.rs) on the same
|
||||
inputs. It covers a method when its fixture reaches the method's port and the vanilla side is in reach too: the
|
||||
oracle calls into the method, or the harness or oracle names the method's class (oracles often reach vanilla
|
||||
through an interface, a block state for the block, which a call graph can't follow)."""
|
||||
oracles = {p.stem: p for p in ORACLE_DIR.glob("*.java")} if ORACLE_DIR.is_dir() else {}
|
||||
fixture_fns = defaultdict(list) # the probes' example files: *_fixture.rs, *_probe.rs and the like
|
||||
for fn in fns:
|
||||
if "/examples/" in fn["file"]:
|
||||
fixture_fns[fn["file"].rsplit("/", 1)[-1][:-3]].append(fn["id"])
|
||||
|
||||
def java_reach(names, depth=3):
|
||||
keys = set()
|
||||
for name in names:
|
||||
text = oracles[name].read_text(encoding="utf-8", errors="replace")
|
||||
imported = {m.group(2): m.group(1).replace(".", "/") + "/" + m.group(2)
|
||||
for m in re.finditer(r"^import\s+((?:net|com)\.minecraft[\w.]*|com\.mojang[\w.]*)\.([A-Z]\w*);", text, re.M)}
|
||||
called = set(re.findall(r"\.(\w+)\s*\(", text)) | {m for _, m in re.findall(r"\b([A-Z]\w*)::(\w+)", text)}
|
||||
for cp in imported.values():
|
||||
if cp not in van.classes:
|
||||
continue
|
||||
for meth in called:
|
||||
decl, _ = van.find_method(cp, meth)
|
||||
if decl:
|
||||
keys.add(key(decl, meth))
|
||||
frontier = set(keys)
|
||||
for _ in range(depth):
|
||||
frontier = {c for k in frontier for c in methods.get(k, {}).get("callees", ())} - keys
|
||||
keys |= frontier
|
||||
return keys
|
||||
|
||||
def rust_reach(starts, depth=5):
|
||||
seen = set(starts)
|
||||
frontier = set(starts)
|
||||
for _ in range(depth):
|
||||
frontier = {c for f in frontier for c in edges.get(f, ())} - seen
|
||||
seen |= frontier
|
||||
return seen
|
||||
|
||||
covered = defaultdict(list)
|
||||
for path in sorted((ROOT / "tools").glob("test_*_parity.py")):
|
||||
text = path.read_text(encoding="utf-8", errors="replace")
|
||||
tokens = set(re.findall(r"\w+", text))
|
||||
names = tokens & oracles.keys()
|
||||
fixtures = {n for n in tokens & fixture_fns.keys() if n.endswith(("_fixture", "_probe"))}
|
||||
if not names or not fixtures:
|
||||
continue
|
||||
vkeys = java_reach(names)
|
||||
rfns = rust_reach([f for n in fixtures for f in fixture_fns[n]])
|
||||
words = set(re.findall(r"\b[A-Z]\w+", text + "".join(oracles[n].read_text(encoding="utf-8", errors="replace") for n in names)))
|
||||
harness_name = path.stem.removeprefix("test_").removesuffix("_parity")
|
||||
for k, fids in known.items():
|
||||
if not fids & rfns:
|
||||
continue
|
||||
simple = methods[k]["class"].rsplit("/", 1)[-1] if k in methods else ""
|
||||
if k in vkeys or simple.split("$")[0] in words:
|
||||
covered[k].append(harness_name)
|
||||
return dict(covered)
|
||||
|
||||
|
||||
def build(sources, jars, jobs):
|
||||
if not sources and not jars:
|
||||
sys.exit("No vanilla code: run `python3 tools/lab.py decompile`, or pass --sources DIR / --jar JAR.")
|
||||
@@ -1616,6 +1680,8 @@ def build(sources, jars, jobs):
|
||||
for k, v in structural.items():
|
||||
known.setdefault(k, set()).add(v["fn"])
|
||||
|
||||
harness = harness_coverage(van, methods, fns, edges, known)
|
||||
|
||||
def reach(fid, depth=5):
|
||||
"""Names of the fns `fid` reaches within `depth` calls, and of the calls left unresolved."""
|
||||
seen, frontier = {fid}, {fid}
|
||||
@@ -1807,6 +1873,7 @@ def build(sources, jars, jobs):
|
||||
"absent": absent,
|
||||
"homes": homes,
|
||||
"aliases": aliases,
|
||||
"harness": harness,
|
||||
"inferred": inferred,
|
||||
"class_cites": {k: sorted(set(v)) for k, v in class_cites.items()},
|
||||
"file_class_cites": {k: sorted(v) for k, v in file_class_cites.items()},
|
||||
@@ -1924,6 +1991,7 @@ def method_view(led, k):
|
||||
[f"skip:{led['skipped'][k]['reason']} {led['skipped'][k]['note']} ({led['skipped'][k]['src']})"] if k in led.get("skipped", {}) else
|
||||
[f"missing {led['absent'][k]['note']} ({led['absent'][k]['src']})"] if k in led.get("absent", {}) else []),
|
||||
"class_home": led.get("homes", {}).get(m["class"]) or led.get("homes", {}).get(m["class"].split("$")[0], []),
|
||||
"parity_harnesses": led.get("harness", {}).get(k, []),
|
||||
"inferred": [f"{fn_ref(led, i['fn'])} (score {i['score']}, shares {', '.join(i['shared'])})" for i in led["inferred"].get(k, [])],
|
||||
"client_effect": m["client"],
|
||||
"calls": [f"{short(led['methods'][c]['class'], led['methods'][c]['name'])} [{status_of(led, c)}]" for c in m["callees"] if c in led["methods"]][:40],
|
||||
@@ -2061,6 +2129,24 @@ def gap_rank(g):
|
||||
g.get("vanilla") or g.get("vanilla_class") or g.get("file", ""))
|
||||
|
||||
|
||||
def untested(led, package=None, limit=50):
|
||||
"""Ported methods no parity harness covers, most-called first: where a harness would catch the most drift."""
|
||||
harness = led.get("harness", {})
|
||||
rows = []
|
||||
for k, m in led["methods"].items():
|
||||
if m["class"].startswith(SKIP_PACKAGES) or k in harness or m["name"].startswith("<"):
|
||||
continue
|
||||
if package and not m["class"].startswith("net/minecraft/" + package):
|
||||
continue
|
||||
# Sure pairs only (cited, or answered in the map), and methods with behaviour of their own.
|
||||
if status_of(led, k) not in ("ported", "mapped") or m.get("accessor") or m.get("size", 0) <= 8:
|
||||
continue
|
||||
rows.append((len(led["callers"].get(k, [])), k))
|
||||
rows.sort(reverse=True)
|
||||
return [{"vanilla": short(led["methods"][k]["class"], led["methods"][k]["name"]), "callers": n,
|
||||
"port": fn_ref(led, port_of(led, k)[0]) if port_of(led, k) else ""} for n, k in rows[:limit]]
|
||||
|
||||
|
||||
def unported(led, package=None, limit=50):
|
||||
rows = []
|
||||
for k, m in led["methods"].items():
|
||||
@@ -2084,6 +2170,9 @@ def cmd_gaps(led, kind, package, limit, as_json):
|
||||
if kind == "unported":
|
||||
emit(unported(led, package, limit), as_json, "Nothing unported there.")
|
||||
return
|
||||
if kind == "untested":
|
||||
emit(untested(led, package, limit), as_json, "Every port there has a parity harness.")
|
||||
return
|
||||
gaps = [g for g in led["gaps"] if (not kind or g["kind"] == kind)]
|
||||
if package:
|
||||
gaps = [g for g in gaps if (g.get("vanilla") or g.get("vanilla_class") or "").startswith("net/minecraft/" + package)
|
||||
@@ -2670,7 +2759,7 @@ def main():
|
||||
c.add_argument("--package")
|
||||
c.add_argument("--depth", type=int, default=2)
|
||||
g = sub.add_parser("gaps")
|
||||
g.add_argument("--kind", choices=["seam", "client", "drift", "class", "callee", "stale", "map", "unported"])
|
||||
g.add_argument("--kind", choices=["seam", "client", "drift", "class", "callee", "stale", "map", "unported", "untested"])
|
||||
g.add_argument("--package")
|
||||
g.add_argument("--limit", type=int, default=50)
|
||||
sub.add_parser("report")
|
||||
@@ -2697,7 +2786,8 @@ def main():
|
||||
tot.update(r)
|
||||
print(f"{len(led['methods'])} vanilla methods, {len(led['fns'])} Rust functions; of {tot['total']} outside the "
|
||||
f"skipped packages: " + ", ".join(f"{tot[k]} {k}" for k in COVERAGE_COLS)
|
||||
+ f" ({found_pct(tot):.0f}% of those worth porting found, {classified_pct(tot):.0f}% of all answered); gaps: "
|
||||
+ f" ({found_pct(tot):.0f}% of those worth porting found, {classified_pct(tot):.0f}% of all answered;"
|
||||
f" {len(led.get('harness', {}))} covered by a parity harness); gaps: "
|
||||
+ ", ".join(f"{k} {v}" for k, v in Counter(gg['kind'] for gg in led['gaps']).most_common())
|
||||
+ f" -> {LEDGER}")
|
||||
return
|
||||
|
||||
@@ -2,7 +2,7 @@ AgeableWaterCreature.handleAirSupply inline crates/horizon-client/src/mobs/ai.rs
|
||||
AgeableWaterCreature.checkSurfaceAgeableWaterCreatureSpawnRules port crates/horizon-client/src/mobs/spawn_rules.rs::surface_water
|
||||
Animal.readAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::mob_from_nbt InLove is read; LoveCause is not kept
|
||||
Animal.canMate port crates/horizon-client/src/mobs/animal_brain.rs::can_mate_with brain breeding; the Breed goal in goals.rs does the same for goal-driven animals
|
||||
Animal.handleEntityEvent port crates/horizon-client/src/particles.rs::entity_event_burst event 18 love hearts
|
||||
Animal.handleEntityEvent port crates/horizon-client-render/src/particles.rs::entity_event_burst event 18 love hearts
|
||||
Animal.getDismountLocationForPassenger port crates/horizon-client/src/game/mounts.rs::Game::dismount_mount gap: tries both sides by the mob's yaw, then on top; not DismountHelper's offsets and pose boxes
|
||||
Allay.createAttributes skip:data attributes come from entity-types.json
|
||||
Allay.createNavigation port crates/horizon-client/src/mobs/allay.rs::register flying navigation, required path length 48
|
||||
@@ -21,8 +21,8 @@ Allay.readAdditionalSaveData port crates/horizon-client/src/entity_nbt.rs::mob_f
|
||||
Allay.updateDuplicationCooldown inline crates/horizon-client/src/mobs/allay.rs::ai_step egg_time counts down
|
||||
Allay.setDuplicationCooldown inline crates/horizon-client/src/game/allays.rs::Game::interact_allay 6000 set here; can_duplicate is derived from egg_time in mob_meta
|
||||
Allay.getLeashOffset missing lead hangs at the default offset (eye height), not 0.6 of it; leash::leash_offset only special-cases llamas
|
||||
Allay.handleEntityEvent port crates/horizon-client/src/particles.rs::entity_event_burst event 18, three hearts
|
||||
Allay.spawnHeartParticle port crates/horizon-client/src/particles.rs::around_self one heart's random placement
|
||||
Allay.handleEntityEvent port crates/horizon-client-render/src/particles.rs::entity_event_burst event 18, three hearts
|
||||
Allay.spawnHeartParticle port crates/horizon-client-render/src/particles.rs::around_self one heart's random placement
|
||||
Allay.VibrationUser.canReceiveVibration missing allays never listen for vibrations: a note block played near one is not remembered
|
||||
AllayAi.initCoreActivity inline crates/horizon-client/src/mobs/allay.rs::register panic goal; the cooldown countdowns are plain ints
|
||||
AllayAi.initIdleActivity inline crates/horizon-client/src/mobs/allay.rs::register fetch, deliver, stay close, stroll and look as goals instead of a brain
|
||||
|
||||
|
@@ -50,7 +50,7 @@ BannerDuplicateRecipe.getRemainingItems port crates/horizon-client/src/crafting.
|
||||
BannerDuplicateRecipe.matches port crates/horizon-client/src/crafting.rs::Special::matches
|
||||
BlastingRecipe.recipeBookCategory skip:data book category comes from the recipe JSON (Recipes::load keeps recipes.meta)
|
||||
BrewingInput.getItem skip:java
|
||||
BrewingRecipe.matches port crates/horizon-client/src/brewing.rs::Brewing::find input and reagent both tested
|
||||
BrewingRecipe.matches port crates/horizon-client-render/src/brewing.rs::Brewing::find input and reagent both tested
|
||||
CraftingRecipe.recipeBookCategory skip:java
|
||||
DecoratedPotRecipe.assemble port crates/horizon-client/src/crafting.rs::Special::assemble
|
||||
DecoratedPotRecipe.matches port crates/horizon-client/src/crafting.rs::Special::matches four sherds or bricks in the four pattern cells
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 1 and column 84.
|
@@ -4,9 +4,9 @@ VillagerTrades skip:data trade definitions are data; read from data/minecraft/t
|
||||
BlockCollisions.<init> port crates/horizon-world/src/block_collision_scan.rs::BlockCollisionScan::new scan set-up over the box
|
||||
BlockCollisions.computeNext port crates/horizon-world/src/block_collision_scan.rs::BlockCollisionScan::find_next next block shape along the box
|
||||
BlockCollisions.getChunk inline crates/horizon-world/src/block_collision_scan.rs::BlockCollisionScan::find_next chunk fetch through collision_chunk inside find_next
|
||||
CardinalLighting.byFace port crates/horizon-client/src/mesher/lighting.rs::cardinal per-face directional shade; nether flag handled in the same fn
|
||||
CardinalLighting.byFace port crates/horizon-client-render/src/mesher/lighting.rs::cardinal per-face directional shade; nether flag handled in the same fn
|
||||
ClipContext.getFluidShape inline crates/horizon-world/src/world_ray.rs::clip_world pick_fluid decides canPick; the shape is clipped in clip_world
|
||||
ColorMapColorUtil.get port crates/horizon-client/src/biome.rs::sample colormap lookup by temperature and downfall
|
||||
ColorMapColorUtil.get port crates/horizon-client-render/src/biome.rs::sample colormap lookup by temperature and downfall
|
||||
EntityBasedExplosionDamageCalculator.getBlockExplosionResistance missing unsure: source-entity overrides (wither skull, minecart TNT, primed TNT keeping nether portals) are not read; explosion_hit_block uses the block's own resistance
|
||||
ExplosionDamageCalculator.getEntityDamageAmount inline crates/horizon-client/src/game/explosion.rs::Game::explosion_hurt_entities damage formula inline; the player copy is in explosion_hurt_player
|
||||
LevelSettings.copy skip:java immutable record copy; Horizon keeps level fields on its own struct
|
||||
@@ -103,7 +103,7 @@ BlastFurnaceBlock.openContainer port crates/horizon-world/src/level/interact.rs:
|
||||
BonemealableFeaturePlacerBlock.performBonemeal port crates/horizon-world/src/level/bonemeal.rs::Behaviours::place_feature places the configured feature
|
||||
BrewingStandBlock.animateTick port crates/horizon-client/src/animate_tick.rs::block_animate_tick brewing bubbles
|
||||
BrewingStandBlock.createBlockStateDefinition skip:data block state definition is data (blocks table in horizon-items)
|
||||
BrewingStandBlock.getTicker port crates/horizon-client/src/brewing.rs::BrewingStand::brew the stand's brewing step
|
||||
BrewingStandBlock.getTicker port crates/horizon-client-render/src/brewing.rs::BrewingStand::brew the stand's brewing step
|
||||
BuddingAmethystBlock.randomTick port crates/horizon-world/src/level/tick.rs::Sim::budding_amethyst_tick
|
||||
BushBlock.performBonemeal port crates/horizon-world/src/level/bonemeal.rs::Sim::perform_bonemeal
|
||||
CactusFlowerBlock.mayPlaceOn port crates/horizon-world/src/level/survive.rs::Sim::may_place_on
|
||||
|
||||
|
@@ -145,7 +145,7 @@ CopperGolemStatueBlockEntity.createStatue port crates/horizon-client/src/game/co
|
||||
CopperGolemStatueBlockEntity.getItem port crates/horizon-client/src/game/block_items.rs::Game::picked_stack statue pose on the picked item
|
||||
CopperGolemStatueBlockEntity.initCopperGolem port crates/horizon-client/src/game/copper_golems.rs::Game::use_golem_statue axe on an unaffected statue brings the golem back
|
||||
DecoratedPotPatterns skip:data bootstrap and asset registration
|
||||
EnchantingTableBlockEntity.bookAnimationTick port crates/horizon-client/src/block_entity_anim.rs::EnchantingBook::tick
|
||||
EnchantingTableBlockEntity.bookAnimationTick port crates/horizon-client-render/src/block_entity_anim.rs::EnchantingBook::tick
|
||||
ListBackedContainer skip:java slot helpers over a plain list
|
||||
PotDecorations.addSideDetailsToTooltip port crates/horizon-client/src/item_tooltip.rs::TooltipDisplay::detail_lines sherd lines of the decorated pot tooltip
|
||||
PotDecorations.allBrick skip:data constant brick decoration set
|
||||
@@ -165,10 +165,10 @@ RandomizableContainerBlockEntity.applyImplicitComponents missing minor: a chest
|
||||
RandomizableContainerBlockEntity.canOpen missing unsure: minor: the spectator and loot-table open checks were not found
|
||||
RandomizableContainerBlockEntity.collectImplicitComponents missing minor: a container does not save its loot table onto the item it drops
|
||||
RandomizableContainerBlockEntity.createMenu port crates/horizon-client/src/game.rs::Game::open_menu gap: minor: the spectator and loot-table checks are not ported here
|
||||
SculkSensorBlockEntity.loadAdditional port crates/horizon-client/src/sculk.rs::SculkEntity::load
|
||||
SculkSensorBlockEntity.saveAdditional port crates/horizon-client/src/sculk.rs::SculkEntity::to_nbt
|
||||
SculkSensorBlockEntity.VibrationUser.canReceiveVibration port crates/horizon-client/src/sculk.rs::valid_vibration
|
||||
SculkSensorBlockEntity.VibrationUser.onReceiveVibration port crates/horizon-client/src/sculk.rs::Data::schedule
|
||||
SculkSensorBlockEntity.loadAdditional port crates/horizon-client-render/src/sculk.rs::SculkEntity::load
|
||||
SculkSensorBlockEntity.saveAdditional port crates/horizon-client-render/src/sculk.rs::SculkEntity::to_nbt
|
||||
SculkSensorBlockEntity.VibrationUser.canReceiveVibration port crates/horizon-client-render/src/sculk.rs::valid_vibration
|
||||
SculkSensorBlockEntity.VibrationUser.onReceiveVibration port crates/horizon-client-render/src/sculk.rs::Data::schedule
|
||||
TestBlockEntity missing minor: game-test block entity; reset, trigger and logging are not ported, NBT only
|
||||
TestInstanceBlockEntity missing minor: game-test structure tooling; export and chunk forcing are not ported
|
||||
TrialSpawnerConfig.calculateTargetSimultaneousMobs port crates/horizon-client/src/mobs/trial_spawner.rs::TrialConfig::target_simultaneous_mobs
|
||||
@@ -204,7 +204,7 @@ LinearPalette skip:java palette storage; Horizon's chunk.rs keeps block ids in
|
||||
PalettedContainerFactory skip:java factory for palette storage; Horizon's chunk.rs keeps block ids in plain arrays
|
||||
SingleValuePalette skip:java palette storage; Horizon's chunk.rs keeps block ids in plain arrays
|
||||
UpgradeData missing unsure: minor: block upgrades of old chunks on load are not ported
|
||||
ChunkDependencies.getRadiusOf inline crates/horizon-client/src/streaming.rs::Ranges::margins fixed margins instead of a per-status radius
|
||||
ChunkDependencies.getRadiusOf inline crates/horizon-client-render/src/streaming.rs::Ranges::margins fixed margins instead of a per-status radius
|
||||
ChunkPyramid.Builder skip:java builder for the status pyramid; Horizon uses fixed margins
|
||||
ChunkStatusTasks.buildTerrain inline crates/horizon-world/src/worldgen/generator.rs::Pipeline::generate_with_context terrain pass
|
||||
ChunkStatusTasks.collectPossibleBiomes inline crates/horizon-world/src/worldgen/generator.rs::Pipeline::biome_chunk biome set folded into the biome pass
|
||||
@@ -214,11 +214,11 @@ ChunkStatusTasks.generateFeatures port crates/horizon-world/src/worldgen/generat
|
||||
ChunkStatusTasks.generateSpawn port crates/horizon-world/src/worldgen/spawn_finder.rs::find_spawn_position
|
||||
ChunkStatusTasks.generateStructureReferences port crates/horizon-world/src/worldgen/generator_structures.rs::StructureState::references
|
||||
ChunkStatusTasks.generateStructureStarts port crates/horizon-world/src/worldgen/generator_structures.rs::StructureState::starts_at
|
||||
ChunkStatusTasks.initializeLight port crates/horizon-client/src/light.rs::light_chunk
|
||||
ChunkStatusTasks.initializeLight port crates/horizon-client-render/src/light.rs::light_chunk
|
||||
ChunkStatusTasks.isLighted skip:other status check; streaming tracks which chunks are lit
|
||||
ChunkStep.apply inline crates/horizon-client/src/streaming.rs::Streaming::update stage advance
|
||||
ChunkStep.completeChunkGeneration inline crates/horizon-client/src/streaming.rs::Streaming::update final stage hand-over
|
||||
ChunkStep.getAccumulatedRadiusOf inline crates/horizon-client/src/streaming.rs::Ranges::margins fixed margins
|
||||
ChunkStep.apply inline crates/horizon-client-render/src/streaming.rs::Streaming::update stage advance
|
||||
ChunkStep.completeChunkGeneration inline crates/horizon-client-render/src/streaming.rs::Streaming::update final stage hand-over
|
||||
ChunkStep.getAccumulatedRadiusOf inline crates/horizon-client-render/src/streaming.rs::Ranges::margins fixed margins
|
||||
ChunkStep.Builder skip:java builder for chunk steps
|
||||
ChunkIOErrorReporter.createMisplacedChunkReport skip:other report text for a chunk saved at another position; Horizon logs its own load error
|
||||
RecreatingSimpleRegionStorage skip:java close: Rust closes the file on drop
|
||||
|
||||
|
@@ -44,9 +44,9 @@ EuclideanGameEventListenerRegistry.unregister port crates/horizon-client/src/gam
|
||||
EuclideanGameEventListenerRegistry.visitInRangeListeners port crates/horizon-client/src/game/sculk.rs::Game::post_game_event walks the index for listeners in range, then the wardens
|
||||
VibrationInfo.getEntity port crates/horizon-client/src/game/sculk.rs::Game::sculk_receive the source is held as a Who and read there
|
||||
VibrationInfo.getProjectileOwner port crates/horizon-client/src/game/sculk.rs::Game::sculk_receive the projectile owner is captured when posted (info.owner) and read there
|
||||
VibrationSelector.addCandidate port crates/horizon-client/src/sculk.rs::Selector::add_candidate
|
||||
VibrationSelector.chosenCandidate port crates/horizon-client/src/sculk.rs::Selector::chosen_candidate
|
||||
VibrationSelector.shouldReplaceVibration port crates/horizon-client/src/sculk.rs::Selector::should_replace
|
||||
VibrationSelector.addCandidate port crates/horizon-client-render/src/sculk.rs::Selector::add_candidate
|
||||
VibrationSelector.chosenCandidate port crates/horizon-client-render/src/sculk.rs::Selector::chosen_candidate
|
||||
VibrationSelector.shouldReplaceVibration port crates/horizon-client-render/src/sculk.rs::Selector::should_replace
|
||||
GameRuleCategory.register skip:data static category table; Horizon's rule categories are static strings (gamerules.rs)
|
||||
GameRuleMap.equals skip:java value equality of the map
|
||||
GameRuleMap.of port crates/horizon-client-core/src/gamerules.rs::GameRules::get no stored map: a rule that was never set reads its table default
|
||||
|
||||
|
@@ -132,32 +132,32 @@ GradientNoise.Gradient.dotXz inline crates/horizon-core/src/noise/perlin.rs::Sme
|
||||
LegacyFbmInitializer.createForLegacyNetherBiome port crates/horizon-core/src/noise/normal.rs::create_legacy_fbm
|
||||
NoiseUtils.biasTowardsExtreme skip:other no caller in the vanilla 26.3 sources
|
||||
NoiseUtils.parityNoiseOctaveConfigString skip:debug debug text
|
||||
BlockLightEngine.checkNode inline crates/horizon-client/src/light.rs::relight_layer neighbour checks in both passes
|
||||
BlockLightEngine.getEmission port crates/horizon-client/src/light.rs::LightTables::emission
|
||||
BlockLightEngine.propagateDecrease port crates/horizon-client/src/light.rs::relight_layer decrease pass, sky false
|
||||
BlockLightEngine.propagateIncrease port crates/horizon-client/src/light.rs::relight_layer increase pass, sky false
|
||||
BlockLightEngine.propagateLightSources inline crates/horizon-client/src/light.rs::relight_layer emission seeds for cleared cells
|
||||
BlockLightSectionStorage.getLightValue port crates/horizon-client/src/light.rs::level
|
||||
BlockLightEngine.checkNode inline crates/horizon-client-render/src/light.rs::relight_layer neighbour checks in both passes
|
||||
BlockLightEngine.getEmission port crates/horizon-client-render/src/light.rs::LightTables::emission
|
||||
BlockLightEngine.propagateDecrease port crates/horizon-client-render/src/light.rs::relight_layer decrease pass, sky false
|
||||
BlockLightEngine.propagateIncrease port crates/horizon-client-render/src/light.rs::relight_layer increase pass, sky false
|
||||
BlockLightEngine.propagateLightSources inline crates/horizon-client-render/src/light.rs::relight_layer emission seeds for cleared cells
|
||||
BlockLightSectionStorage.getLightValue port crates/horizon-client-render/src/light.rs::level
|
||||
DataLayerStorageMap skip:java cache of light data layers keyed by section
|
||||
DynamicGraphMinFixedPoint.checkEdge inline crates/horizon-client/src/light.rs::relight_layer edge test inside the passes
|
||||
DynamicGraphMinFixedPoint.checkNeighbor inline crates/horizon-client/src/light.rs::relight_layer neighbour test inside the passes
|
||||
DynamicGraphMinFixedPoint.checkEdge inline crates/horizon-client-render/src/light.rs::relight_layer edge test inside the passes
|
||||
DynamicGraphMinFixedPoint.checkNeighbor inline crates/horizon-client-render/src/light.rs::relight_layer neighbour test inside the passes
|
||||
DynamicGraphMinFixedPoint.removeFromQueue skip:java queue bookkeeping; the passes never remove queued entries
|
||||
DynamicGraphMinFixedPoint.removeIf skip:java queue bookkeeping
|
||||
DynamicGraphMinFixedPoint.runUpdates port crates/horizon-client/src/light.rs::relight_layer runs the decrease and increase passes
|
||||
DynamicGraphMinFixedPoint.runUpdates port crates/horizon-client-render/src/light.rs::relight_layer runs the decrease and increase passes
|
||||
LayerLightSectionStorage skip:other per-section light data bookkeeping; Horizon keeps light in chunk sections (light.rs apply_lighting, light_chunk)
|
||||
LayerLightSectionStorage.SectionState skip:other section state flags of the light storage; no Horizon equivalent needed
|
||||
LeveledPriorityQueue skip:java priority queue of light levels; relight_layer runs two FIFO passes
|
||||
SkyLightEngine.addSourcesAbove inline crates/horizon-client/src/light.rs::relight_region column sources loop
|
||||
SkyLightEngine.checkNode inline crates/horizon-client/src/light.rs::relight_layer neighbour checks in the sky passes
|
||||
SkyLightEngine.addSourcesAbove inline crates/horizon-client-render/src/light.rs::relight_region column sources loop
|
||||
SkyLightEngine.checkNode inline crates/horizon-client-render/src/light.rs::relight_layer neighbour checks in the sky passes
|
||||
SkyLightEngine.countEmptySectionsBelowIfAtBorder skip:java shortcut over empty sections; Horizon relights a region directly
|
||||
SkyLightEngine.crossedSectionEdge port crates/horizon-client/src/light.rs::LightChanges::class edge flags of a changed cell
|
||||
SkyLightEngine.crossedSectionEdge port crates/horizon-client-render/src/light.rs::LightChanges::class edge flags of a changed cell
|
||||
SkyLightEngine.getChunkSources skip:java per-chunk source cache; lowest_sky_source computes each column on demand
|
||||
SkyLightEngine.getLowestSourceY port crates/horizon-client/src/light.rs::lowest_sky_source
|
||||
SkyLightEngine.propagateDecrease port crates/horizon-client/src/light.rs::relight_layer decrease pass, sky true
|
||||
SkyLightEngine.propagateFromEmptySections inline crates/horizon-client/src/light.rs::relight_region sky seeds below the source in each column
|
||||
SkyLightEngine.propagateIncrease port crates/horizon-client/src/light.rs::relight_layer increase pass, sky true
|
||||
SkyLightEngine.propagateLightSources inline crates/horizon-client/src/light.rs::relight_layer sky sources inside the cleared area
|
||||
SkyLightEngine.removeSourcesBelow inline crates/horizon-client/src/light.rs::relight_region column sources loop
|
||||
SkyLightEngine.getLowestSourceY port crates/horizon-client-render/src/light.rs::lowest_sky_source
|
||||
SkyLightEngine.propagateDecrease port crates/horizon-client-render/src/light.rs::relight_layer decrease pass, sky true
|
||||
SkyLightEngine.propagateFromEmptySections inline crates/horizon-client-render/src/light.rs::relight_region sky seeds below the source in each column
|
||||
SkyLightEngine.propagateIncrease port crates/horizon-client-render/src/light.rs::relight_layer increase pass, sky true
|
||||
SkyLightEngine.propagateLightSources inline crates/horizon-client-render/src/light.rs::relight_layer sky sources inside the cleared area
|
||||
SkyLightEngine.removeSourcesBelow inline crates/horizon-client-render/src/light.rs::relight_region column sources loop
|
||||
SkyLightEngine.setLightEnabled skip:other light is computed when a chunk loads (light_chunk); no per-chunk enable flag
|
||||
SkyLightSectionStorage skip:other sky data layer bookkeeping per section; Horizon keeps light in chunk sections
|
||||
SkyLightSectionStorage.SkyDataLayerStorageMap skip:java copy of sky data layers; collection plumbing
|
||||
|
||||
|
@@ -71,7 +71,7 @@ floats/Minimum.getFloatUnsafe port crates/horizon-client/src/game/commands/compu
|
||||
floats/Modulus.getFloatUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_float mod arm
|
||||
floats/Product.getFloatUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_float mul arm
|
||||
floats/Quotient.getFloatUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_float div arm
|
||||
ResolvableFloat.getFromItem port crates/horizon-client/src/providers.rs::Providers::speed furnace fuel speed multiplier (COOKING_FUEL); gap: minor: only the furnace fuel component is read
|
||||
ResolvableFloat.getFromItem port crates/horizon-client-render/src/providers.rs::Providers::speed furnace fuel speed multiplier (COOKING_FUEL); gap: minor: only the furnace fuel component is read
|
||||
ResolvableFloat.wrap skip:java codec helper
|
||||
floats/StorageValue missing minor: no vanilla data uses storage providers
|
||||
floats/Sum.getFloatUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_float add arm
|
||||
@@ -83,7 +83,7 @@ ints/BinomialDistributionGenerator.getIntUnsafe port crates/horizon-world/src/lo
|
||||
ints/BinomialDistributionGenerator.validate skip:data datapack validation
|
||||
ints/ContextIntProviders.bootstrap skip:data registry bootstrap
|
||||
ints/ContextIntProviders.compostable port crates/horizon-client/src/game/composter.rs::Game::compostable the item's compostable layer provider; evaluated when the composter takes it (Game::composter_add)
|
||||
ints/ContextIntProviders.cooking port crates/horizon-client/src/providers.rs::Providers::burn_time fuel burn times (the cooking/time_* providers)
|
||||
ints/ContextIntProviders.cooking port crates/horizon-client-render/src/providers.rs::Providers::burn_time fuel burn times (the cooking/time_* providers)
|
||||
ints/Difference.getIntUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_int sub arm
|
||||
ints/EnvironmentAttributeValue missing minor: no vanilla data uses environment_attribute providers
|
||||
ints/FloorModulus.getIntUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_int floor_mod arm
|
||||
@@ -93,7 +93,7 @@ ints/Minimum.getIntUnsafe port crates/horizon-client/src/game/commands/compute.r
|
||||
ints/Modulus.getIntUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_int mod arm
|
||||
ints/Product.getIntUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_int mul arm
|
||||
ints/Quotient.getIntUnsafe port crates/horizon-client/src/game/commands/compute.rs::Game::provider_int div arm
|
||||
ResolvableInt.getFromItem port crates/horizon-client/src/providers.rs::Providers::burn_time furnace fuel burn time (COOKING_FUEL); gap: minor: only the furnace fuel component is read
|
||||
ResolvableInt.getFromItem port crates/horizon-client-render/src/providers.rs::Providers::burn_time furnace fuel burn time (COOKING_FUEL); gap: minor: only the furnace fuel component is read
|
||||
ResolvableInt.wrap skip:java codec helper
|
||||
ints/ScoreboardValue.getIntUnsafe missing minor: no vanilla data uses score providers
|
||||
ints/ScoreboardValue.getScoreInfo missing minor: no vanilla data uses score providers
|
||||
|
||||
|
@@ -10,9 +10,9 @@ LevelChunkTicks.poll port crates/horizon-core/src/ticks/mod.rs::ChunkTicks::poll
|
||||
LevelChunkTicks.removeIf port crates/horizon-core/src/ticks/mod.rs::ChunkTicks::remove_if
|
||||
LevelChunkTicks.scheduleUnchecked inline crates/horizon-core/src/ticks/mod.rs::ChunkTicks::schedule gap: minor: push only, no duplicate check (the unpack path is in ChunkTicks::unpack); the onTickAdded deadline update is in LevelTicks::schedule
|
||||
LevelChunkTicks.unpack port crates/horizon-core/src/ticks/mod.rs::ChunkTicks::unpack gap: minor: the deadline refresh is in LevelTicks::unpack_chunk
|
||||
AttributeTrack.bakeSampler inline crates/horizon-client/src/environment.rs::Environment::load_dimension gap: minor: builds the day timeline's tracks only (keyframes, easing, multiply); the clock holder is implicit
|
||||
AttributeTrack.bakeSampler inline crates/horizon-client-render/src/environment.rs::Environment::load_dimension gap: minor: builds the day timeline's tracks only (keyframes, easing, multiply); the clock holder is implicit
|
||||
AttributeTrack.createCodec skip:java codec; Horizon parses the timeline JSON in Environment::load_dimension and patrol.rs GameplayAttributes::load
|
||||
AttributeTrack.createCodecWithModifier skip:java codec; same parsing as createCodec
|
||||
AttributeTrack.validatePeriod skip:data load-time check of keyframes against period_ticks; Horizon reads the vanilla timeline files without it
|
||||
AttributeTrackSampler.applyTimeBased port crates/horizon-client/src/environment.rs::Environment::value gap: minor: multiply and override only; star_brightness 'maximum' reads as override (same value at base 0); booleans are in patrol.rs BoolTrack or hardcoded per consumer; no per-tick cache
|
||||
AttributeTrackSampler.applyTimeBased port crates/horizon-client-render/src/environment.rs::Environment::value gap: minor: multiply and override only; star_brightness 'maximum' reads as override (same value at base 0); booleans are in patrol.rs BoolTrack or hardcoded per consumer; no per-tick cache
|
||||
Timelines.bootstrap skip:data registers the timelines in code; Horizon reads data/minecraft/timeline/*.json, and the few day-cycle numbers it needs are in level/timeline.rs
|
||||
|
@@ -11,13 +11,13 @@ ClientAdvancements.setSelectedTab port crates/horizon-client/src/game/advancemen
|
||||
ClientAdvancements.setListener skip:ui listener plumbing for the advancements screen; Horizon's screen reads its state each frame
|
||||
ClientAdvancements.get port crates/horizon-client/src/advancements.rs::Tree::get returns the advancement by id; holder wrapper is not needed
|
||||
ClientChunkCache.isValidChunk skip:other chunks are stored by position, so there is no ring slot to check
|
||||
ClientChunkCache.drop port crates/horizon-client/src/streaming.rs::Streaming::forget gap: minor: no ChunkEvent unload hook; block entities and renderer columns are cleared in Game::handle_packet
|
||||
ClientChunkCache.drop port crates/horizon-client-render/src/streaming.rs::Streaming::forget gap: minor: no ChunkEvent unload hook; block entities and renderer columns are cleared in Game::handle_packet
|
||||
ClientChunkCache.getChunk skip:java lookup of a loaded chunk; Horizon reads world.chunks directly
|
||||
ClientChunkCache.replaceBiomes missing minor: ChunksBiomes (id 13) has no arm in Game::handle_packet, so server biome changes (e.g. /fillbiome) do not reach the client until the chunk is resent
|
||||
ClientChunkCache.flipUpdateTrackingSets skip:other double-buffered change lists for LevelRenderer; Horizon hands changed columns to the renderer directly
|
||||
ClientChunkCache.Storage.getIndex skip:java ring-buffer index into the view range; Horizon keys chunks by position
|
||||
ClientChunkCache.Storage.replace port crates/horizon-client/src/game/remote_chunks.rs::Game::remote_chunk gap: minor: old chunk unload is in Streaming::forget, not here
|
||||
ClientChunkCache.Storage.drop port crates/horizon-client/src/streaming.rs::Streaming::forget forgets the chunk at the index
|
||||
ClientChunkCache.Storage.drop port crates/horizon-client-render/src/streaming.rs::Streaming::forget forgets the chunk at the index
|
||||
ClientChunkCache.Storage.onSectionEmptinessChanged skip:java empty-section sets for the renderer's culling; Horizon's mesher works from block data
|
||||
ClientChunkCache.Storage.onChunkRemoved skip:java empty-section bookkeeping when a chunk leaves; see Storage.onSectionEmptinessChanged
|
||||
ClientChunkCache.Storage.onChunkAdded skip:java empty-section bookkeeping when a chunk arrives; see Storage.onSectionEmptinessChanged
|
||||
@@ -71,8 +71,8 @@ ClientDebugSubscriber.ValueMap.forEach skip:java iteration helper
|
||||
ClientDebugSubscriber.ValueMaps.purgeExpired missing minor: expiry of server debug values; none are received
|
||||
ClientDebugSubscriber.ValueMaps.dropChunkAndBlocks missing minor: drops server debug values per chunk; none are received
|
||||
ClientDebugSubscriber.ValueWrapper.hasExpired skip:java timestamp check on a debug value wrapper
|
||||
ClientExplosionTracker.track inline crates/horizon-client/src/particles.rs::ParticleEngine::explosion gap: minor: the debris is thrown at once instead of queued for later ticks
|
||||
ClientExplosionTracker.addParticle port crates/horizon-client/src/particles.rs::ParticleEngine::explosion_blocks gap: minor: always the default poof and smoke list; the explode packet's own block particle list is not read
|
||||
ClientExplosionTracker.track inline crates/horizon-client-render/src/particles.rs::ParticleEngine::explosion gap: minor: the debris is thrown at once instead of queued for later ticks
|
||||
ClientExplosionTracker.addParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::explosion_blocks gap: minor: always the default poof and smoke list; the explode packet's own block particle list is not read
|
||||
ClientHandshakePacketListenerImpl.switchState inline crates/horizon-client/src/net.rs::run login steps run in packet order in run(); no state machine
|
||||
ClientHandshakePacketListenerImpl.handleHello port crates/horizon-client/src/net.rs::run gap: minor: the session join and server check are in auth.rs (join_server); the encryption setup is in run
|
||||
ClientHandshakePacketListenerImpl.setEncryption port crates/horizon-client/src/net.rs::run enables the connection cipher
|
||||
@@ -107,10 +107,10 @@ ClientLevel.playPlayerSound skip:other no caller in the 26.3 client sources
|
||||
ClientLevel.playSound missing player: distance-delayed sounds (over 10 blocks, sqrt(distance)/40 s) play at once, so far fireworks and similar sounds arrive early; audio.rs has no delay
|
||||
ClientLevel.getRelativeTickSpeed skip:other no caller in the 26.3 client sources
|
||||
ClientLevel.destroyBlockProgress port crates/horizon-client/src/game.rs::Game::destroy_block_progress sets or clears another player's crack on a block
|
||||
ClientLevel.addAlwaysVisibleParticle port crates/horizon-client/src/particles.rs::ParticleEngine::add_with always-show flag set
|
||||
ClientLevel.doAddParticle port crates/horizon-client/src/particles.rs::ParticleEngine::add_with drops particles beyond 32 blocks and at minimal particle level
|
||||
ClientLevel.addAlwaysVisibleParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::add_with always-show flag set
|
||||
ClientLevel.doAddParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::add_with drops particles beyond 32 blocks and at minimal particle level
|
||||
ClientLevel.getSkyFlashTime inline crates/horizon-client/src/game.rs::Game::render the flash check with the hideLightningFlashes option
|
||||
ClientLevel.calculateBlockTint port crates/horizon-client/src/mesher.rs::MeshTables::terrain_particle_tint gap: minor: returns none for grass-block tints
|
||||
ClientLevel.calculateBlockTint port crates/horizon-client-render/src/mesher.rs::MeshTables::terrain_particle_tint gap: minor: returns none for grass-block tints
|
||||
ClientLevel.playBreakingSound port crates/horizon-client/src/game/level_particles.rs::Game::breaking_block_effects the block's hit sound while digging
|
||||
ClientLevel.getClientLeafTintColor port crates/horizon-client/src/animate_tick.rs::AnimateContext::foliage leaf tint for tinted leaves particles; the caller is TintedParticleLeavesBlock
|
||||
ClientLevel.EntityCallbacks.onTrackingStart inline crates/horizon-client/src/game/remote.rs::Game::spawn_entity minor: ender dragon parts are placed by the dragon module, with no tracked parts list
|
||||
|
||||
|
@@ -14,9 +14,9 @@ ClientPacketListener.setValuesFromPositionPacket port crates/horizon-client/src/
|
||||
ClientPacketListener.handleRotatePlayer port crates/horizon-client/src/game/remote.rs::Game::handle_packet PlayerRotation arm; gap: minor: no ServerboundMovePlayerPacket.Rot reply is sent
|
||||
ClientPacketListener.handleChunkBlocksUpdate port crates/horizon-client/src/game/remote.rs::Game::handle_packet SectionBlocksUpdate arm: each change through the block update
|
||||
ClientPacketListener.handleChunksBiomes missing player: biome updates for loaded chunks are dropped, so grass, leaves and water keep the old biome colour
|
||||
ClientPacketListener.enableChunkLight inline crates/horizon-client/src/streaming.rs::Streaming::insert_remote gap: minor: the air-only section status is not set; every section of the 3x3 columns is marked dirty
|
||||
ClientPacketListener.enableChunkLight inline crates/horizon-client-render/src/streaming.rs::Streaming::insert_remote gap: minor: the air-only section status is not set; every section of the 3x3 columns is marked dirty
|
||||
ClientPacketListener.handleForgetLevelChunk port crates/horizon-client/src/game/remote.rs::Game::handle_packet ForgetLevelChunk arm: Streaming::forget, block entities and renderer columns dropped
|
||||
ClientPacketListener.queueLightRemoval inline crates/horizon-client/src/streaming.rs::Streaming::forget gap: minor: the column's light state is dropped with the column; no per-section light clearing
|
||||
ClientPacketListener.queueLightRemoval inline crates/horizon-client-render/src/streaming.rs::Streaming::forget gap: minor: the column's light state is dropped with the column; no per-section light clearing
|
||||
ClientPacketListener.handleBlockUpdate port crates/horizon-client/src/game/remote.rs::Game::server_block
|
||||
ClientPacketListener.handleConfigurationStart port crates/horizon-client/src/net.rs::receive_play gap: player: no reconfiguring screen is shown and the chat state is not stored
|
||||
ClientPacketListener.handleSystemChat port crates/horizon-client/src/game/remote.rs::Game::handle_packet SystemChat arm: overlay to the action bar, otherwise the chat
|
||||
|
||||
|
@@ -18,8 +18,8 @@ SessionSearchTrees.rebuildAfterLanguageChange skip:java creative search reads n
|
||||
SessionSearchTrees.getTooltipLines inline crates/horizon-client/src/creative.rs::Creative::from_json search lines come pre-stripped from the export and are read here
|
||||
SessionSearchTrees.updateCreativeTags inline crates/horizon-client/src/creative.rs::Creative::from_json tag index comes from the export's tag lists
|
||||
SessionSearchTrees.updateCreativeTooltips inline crates/horizon-client/src/creative.rs::Creative::from_json name index comes from the export's tooltip lines
|
||||
ChatAbilities.selectVisibleMessages port crates/horizon-client/src/chat.rs::ChatAbilities::shows source filter: client lines always, player and server lines per the abilities
|
||||
ChatAbilities.Builder.build inline crates/horizon-client/src/chat.rs::ChatAbilities::new restrictions come from the Chat option alone; new() maps the visibility to them
|
||||
ChatAbilities.selectVisibleMessages port crates/horizon-client-render/src/chat.rs::ChatAbilities::shows source filter: client lines always, player and server lines per the abilities
|
||||
ChatAbilities.Builder.build inline crates/horizon-client-render/src/chat.rs::ChatAbilities::new restrictions come from the Chat option alone; new() maps the visibility to them
|
||||
ChatListener.tick missing player: the Chat Delay option does nothing; lines show at once instead of after the delay
|
||||
ChatListener.setMessageDelay missing player: no delayed-message queue, so Chat Delay has no effect
|
||||
ChatListener.flushQueue missing player: no queue to flush (part of Chat Delay)
|
||||
@@ -43,7 +43,7 @@ ChatTrustLevel.evaluate missing player: no Not Secure or Modified marking on ch
|
||||
ChatTrustLevel.isModified missing minor: only feeds the Modified marking
|
||||
ChatTrustLevel.containsModifiedStyle missing minor: only feeds the Modified marking
|
||||
ChatTrustLevel.createTag missing player: no grey Not Secure bar or Modified tag on player chat lines
|
||||
GuiMessage.splitLines port crates/horizon-client/src/chat.rs::Chat::add_lines gap: minor: no width reserved for a tag icon; Horizon draws no icon tags
|
||||
GuiMessage.splitLines port crates/horizon-client-render/src/chat.rs::Chat::add_lines gap: minor: no width reserved for a tag icon; Horizon draws no icon tags
|
||||
GuiMessageTag.chatModified missing minor: no Modified tag text or icon (needs the trust check)
|
||||
GuiMessageTag.Icon.extractRenderState missing minor: the chat_modified icon is never drawn
|
||||
LoggedChatMessage.Player.toContentComponent skip:service chat report view of a logged line
|
||||
@@ -133,7 +133,7 @@ CowRenderer.getTextureLocation port crates/horizon-client/src/mobs/render.rs::bo
|
||||
CowRenderer.extractRenderState port crates/horizon-client/src/mobs/render.rs::body variant is read there
|
||||
CowRenderer.submit port crates/horizon-client/src/mobs/render.rs::body model per variant and age
|
||||
CreakingRenderer.extractRenderState port crates/horizon-client/src/mobs/creaking.rs::animate attack, invulnerability and death clocks
|
||||
CreeperRenderer.extractRenderState port crates/horizon-client/src/overlay.rs::creeper_progress white flash from swelling; the charged layer is in render.rs (energy swirl)
|
||||
CreeperRenderer.extractRenderState port crates/horizon-client-render/src/overlay.rs::creeper_progress white flash from swelling; the charged layer is in render.rs (energy swirl)
|
||||
CushionRenderer.extractRenderState port crates/horizon-client/src/game/cushions.rs::Game::cushion_geometry yaw and colour texture are read there
|
||||
CushionRenderer.submit port crates/horizon-client/src/game/cushions.rs::Game::cushion_geometry turned to its facing and drawn
|
||||
DisplayRenderer.getSkyLightLevel port crates/horizon-client/src/game/text_displays.rs::Game::text_display_geometry brightness override
|
||||
|
||||
|
@@ -72,7 +72,7 @@ ItemFrameRenderer.submit port crates/horizon-client/src/game/frames.rs::Game::fr
|
||||
ItemFrameRenderer.getRenderOffset inline crates/horizon-client/src/game/frames.rs::Game::frame_geometry offset in front of the wall
|
||||
ItemFrameRenderer.extractRenderState inline crates/horizon-client/src/game/frames.rs::Game::frame_geometry direction, item, rotation, glow and map read there
|
||||
LeashKnotRenderer.submit port crates/horizon-client/src/game/leads.rs::Game::leash_geometry knot drawn with the rope pass
|
||||
LightningBoltRenderer.quad inline crates/horizon-client/src/gfx/lightning.rs::geometry quad built inline
|
||||
LightningBoltRenderer.quad inline crates/horizon-client-render/src/gfx/lightning.rs::geometry quad built inline
|
||||
LivingEntityRenderer.getBoundingBoxForCulling skip:other culling box only (dragon-head inflate)
|
||||
LivingEntityRenderer.isEntityUpsideDown port crates/horizon-client/src/mobs/render.rs::base_transform_at upside-down flip for Dinnerbone and Grumm
|
||||
LivingEntityRenderer.isUpsideDownName inline crates/horizon-client/src/mobs/render.rs::base_transform_at name check folded into the flip
|
||||
|
||||
|
@@ -35,7 +35,7 @@ DrownedOuterLayer.submit port crates/horizon-client/src/mobs/render.rs::layers d
|
||||
EnergySwirlLayer.submit port crates/horizon-client/src/mobs/render.rs::energy_swirl charged creeper and powered wither swirl, drawn in render_visible
|
||||
EquipmentLayerRenderer.renderLayers port crates/horizon-client/src/game/armor_stands.rs::Game::armor_layers gap: player: the leather overlay (layer 1 of the equipment asset) is never drawn, only layer 0
|
||||
EquipmentLayerRenderer.getColorForLayer port crates/horizon-client/src/game/armor_stands.rs::Game::armor_layers gap: player: a dyed leather piece is drawn in the undyed brown; the stack's dyed colour is not read
|
||||
EquipmentLayerRenderer.TrimTextureKey.createTextureWithNoPalette port crates/horizon-client/src/trims.rs::TrimTexture::generate a trim with no palette is used as its own texture
|
||||
EquipmentLayerRenderer.TrimTextureKey.createTextureWithNoPalette port crates/horizon-client-render/src/trims.rs::TrimTexture::generate a trim with no palette is used as its own texture
|
||||
EyesLayer.submit port crates/horizon-client/src/mobs/render.rs::layers spider, enderman and phantom eyes
|
||||
FoxHeldItemLayer.submit missing player: a fox's food in its mouth (berries, fish) is not drawn; the fox model has no arm bones for the hand lookup
|
||||
HorseMarkingLayer.submit port crates/horizon-client/src/mobs/render.rs::layers horse markings layer
|
||||
|
||||
|
@@ -93,7 +93,7 @@ ModelLayers.register skip:data layer id registry; Horizon uses string keys
|
||||
ModelLayers.registerArmorSet skip:data layer id registry; Horizon uses string keys
|
||||
ModelPart.storePose skip:other no callers in vanilla; a snapshot of the current pose that nothing reads
|
||||
ModelPart.loadPose port crates/horizon-client/src/mobs.rs::Part::parse reads the pose from the model data; the reset per frame is Poses::reset
|
||||
ModelPart.getChild port crates/horizon-client/src/block_entity_render.rs::ModelPart::child named child lookup (Option instead of a throw)
|
||||
ModelPart.getChild port crates/horizon-client-render/src/block_entity_render.rs::ModelPart::child named child lookup (Option instead of a throw)
|
||||
ModelPart.compile port crates/horizon-client/src/mobs/render.rs::draw_normals emits each polygon with the posed transform; skipDraw gating
|
||||
ModelPart.offsetRotation port crates/horizon-client/src/mobs/keyframes.rs::Animation::apply Target::Rotation channel adds to xRot/yRot/zRot
|
||||
ModelPart.offsetScale port crates/horizon-client/src/mobs/keyframes.rs::Animation::apply Target::Scale channel adds to the scale
|
||||
|
||||
|
@@ -59,7 +59,7 @@ BoatModel.createChestBoatModel skip:data layer baked into entity-models.json, l
|
||||
BoatModel.createWaterPatch skip:data layer baked into entity-models.json, loaded by key
|
||||
RaftModel.createRaftModel skip:data bamboo raft is boat/bamboo#main in entity-models.json
|
||||
RaftModel.createChestRaftModel skip:data bamboo chest raft is chest_boat/bamboo#main in entity-models.json
|
||||
BookModel.State.forAnimation port crates/horizon-client/src/block_entity_anim.rs::EnchantingBook::render openness and page flips computed in EnchantingBook::render
|
||||
BookModel.State.forAnimation port crates/horizon-client-render/src/block_entity_anim.rs::EnchantingBook::render openness and page flips computed in EnchantingBook::render
|
||||
MinecartModel.createBodyLayer skip:data layer baked into entity-models.json, loaded by key
|
||||
ChestModel.createSingleBodyLayer skip:data layer baked into entity-models.json, loaded by key
|
||||
ChestModel.createDoubleBodyRightLayer skip:data layer baked into entity-models.json, loaded by key
|
||||
@@ -77,11 +77,11 @@ TridentModel.createLayer skip:data layer baked into entity-models.json, loaded
|
||||
WindChargeModel.createBodyLayer skip:data layer baked into entity-models.json, loaded by key
|
||||
WindChargeModel.setupAnim port crates/horizon-client/src/game/wind.rs::Game::wind_charge_geometry wind and wind_charge parts spin in opposite directions
|
||||
DragonHeadModel.createHeadLayer skip:data layer baked into entity-models.json, loaded by key
|
||||
DragonHeadModel.setupAnim inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one jaw animation in the skull branch of render_one
|
||||
PiglinHeadModel.setupAnim inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one ear flap in the skull branch of render_one
|
||||
DragonHeadModel.setupAnim inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one jaw animation in the skull branch of render_one
|
||||
PiglinHeadModel.setupAnim inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one ear flap in the skull branch of render_one
|
||||
SkullModel.createHeadModel skip:data layer baked into entity-models.json, loaded by key
|
||||
SkullModel.createHumanoidHeadLayer skip:data layer baked into entity-models.json, loaded by key
|
||||
SkullModel.setupAnim inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one head yaw and pitch in the skull branch of render_one
|
||||
SkullModel.setupAnim inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one head yaw and pitch in the skull branch of render_one
|
||||
PlayerCapeModel.createCapeLayer missing player: capes never draw; skins.rs downloads the cape texture but nothing draws the cape layer
|
||||
PlayerCapeModel.setupAnim missing player: the cape does not lean or flap with movement, since no cape is drawn
|
||||
PlayerEarsModel.createEarsLayer missing minor: extra ears (showExtraEars accounts) are not drawn
|
||||
@@ -89,12 +89,12 @@ PlayerModel.createMesh skip:data layer baked into entity-models.json, loaded by
|
||||
PlayerModel.createArmorMeshSet skip:data layer baked into entity-models.json, loaded by key
|
||||
PlayerModel.setupAnim port crates/horizon-client/src/mobs/anim.rs::setup gap: player: hat, jacket, sleeve and pant toggles (modelPart options) are not read, so a hidden layer still draws; humanoid pose is in the Humanoid branch
|
||||
PlayerModel.translateToHand port crates/horizon-client/src/mobs/render.rs::hand_base gap: minor: slim-arm 0.5 pixel hand offset not applied
|
||||
BlockModelLighter.prepareQuadAmbientOcclusion port crates/horizon-client/src/mesher/lighting.rs::ambient_occlusion per-vertex AO brightness and smooth light from the corner samples
|
||||
BlockModelLighter.prepareQuadFlat port crates/horizon-client/src/mesher/lighting.rs::flat light only; the directional colour is applied in Mesher::face
|
||||
BlockModelLighter.prepareQuadShape port crates/horizon-client/src/mesher/lighting.rs::shape face bounds, partial and cubic flags
|
||||
BlockModelLighter.getDirectionalBrightness port crates/horizon-client/src/mesher/lighting.rs::cardinal face shade; the shade direction override is read in Mesher::face
|
||||
BlockModelLighter.Cache.getLightCoords port crates/horizon-client/src/mesher/lighting.rs::coords packed light for a position
|
||||
BlockModelLighter.Cache.getShadeBrightness port crates/horizon-client/src/mesher.rs::MeshTables::new shade per block state (Info.shade), not cached per position
|
||||
BlockModelLighter.prepareQuadAmbientOcclusion port crates/horizon-client-render/src/mesher/lighting.rs::ambient_occlusion per-vertex AO brightness and smooth light from the corner samples
|
||||
BlockModelLighter.prepareQuadFlat port crates/horizon-client-render/src/mesher/lighting.rs::flat light only; the directional colour is applied in Mesher::face
|
||||
BlockModelLighter.prepareQuadShape port crates/horizon-client-render/src/mesher/lighting.rs::shape face bounds, partial and cubic flags
|
||||
BlockModelLighter.getDirectionalBrightness port crates/horizon-client-render/src/mesher/lighting.rs::cardinal face shade; the shade direction override is read in Mesher::face
|
||||
BlockModelLighter.Cache.getLightCoords port crates/horizon-client-render/src/mesher/lighting.rs::coords packed light for a position
|
||||
BlockModelLighter.Cache.getShadeBrightness port crates/horizon-client-render/src/mesher.rs::MeshTables::new shade per block state (Info.shade), not cached per position
|
||||
BlockModelRenderState.clear skip:java reuse of a pooled render state object
|
||||
BlockModelRenderState.tintLayers port crates/horizon-client/src/game/minecarts.rs::Game::block_model_vertices gap: minor: the caller tints every face with one sprite tint, not per tint index
|
||||
BlockModelRenderState.setupModel port crates/horizon-client/src/game/minecarts.rs::Game::block_model_vertices model parts with the transform matrix
|
||||
@@ -102,43 +102,43 @@ BlockModelRenderState.submit port crates/horizon-client/src/game/minecarts.rs::G
|
||||
BlockModelRenderState.submitModel port crates/horizon-client/src/game/minecarts.rs::Game::block_model_vertices faces written to the vertex list
|
||||
BlockModelRenderState.scratchRandomSource port crates/horizon-client/src/game/minecarts.rs::Game::block_model_vertices seeded variant choice through collect_seeded
|
||||
BlockModelResolver.update port crates/horizon-client/src/game/minecarts.rs::Game::block_model_vertices resolves the seeded model for a block state; light is passed in by the caller
|
||||
BlockModelSet.createFallbackModel port crates/horizon-client/src/mesher.rs::MeshTables::new per-state tint sources (Info.tints) and the plain model
|
||||
BuiltInBlockModels.special port crates/horizon-client/src/block_entity_render.rs::classify special model name to Kind
|
||||
BuiltInBlockModels.createMobHead port crates/horizon-client/src/block_entity_render.rs::classify Skull kind for mob heads
|
||||
BuiltInBlockModels.createMobWallHead port crates/horizon-client/src/block_entity_render.rs::classify Skull kind for wall mob heads
|
||||
BuiltInBlockModels.createMobHeads port crates/horizon-client/src/block_entity_render.rs::classify Skull kind for the head set
|
||||
BuiltInBlockModels.createPlayerHead port crates/horizon-client/src/block_entity_render.rs::classify Skull kind for player heads
|
||||
BuiltInBlockModels.createPlayerWallHead port crates/horizon-client/src/block_entity_render.rs::classify Skull kind for wall player heads
|
||||
BuiltInBlockModels.createBanner port crates/horizon-client/src/block_entity_render.rs::classify Banner kind
|
||||
BuiltInBlockModels.createWallBanner port crates/horizon-client/src/block_entity_render.rs::classify Banner kind for wall banners
|
||||
BuiltInBlockModels.createDyedShulkerBox port crates/horizon-client/src/block_entity_render.rs::classify Shulker kind for dyed boxes
|
||||
BuiltInBlockModels.createChest port crates/horizon-client/src/block_entity_render.rs::classify Chest kind
|
||||
BuiltInBlockModels.createSingletonChest port crates/horizon-client/src/block_entity_render.rs::classify Chest kind, single chest
|
||||
BuiltInBlockModels.createXmasChest port crates/horizon-client/src/block_entity_render.rs::classify Chest kind with the Christmas texture
|
||||
BuiltInBlockModels.createCopperGolem port crates/horizon-client/src/block_entity_render.rs::classify CopperGolem kind
|
||||
BlockModelSet.createFallbackModel port crates/horizon-client-render/src/mesher.rs::MeshTables::new per-state tint sources (Info.tints) and the plain model
|
||||
BuiltInBlockModels.special port crates/horizon-client-render/src/block_entity_render.rs::classify special model name to Kind
|
||||
BuiltInBlockModels.createMobHead port crates/horizon-client-render/src/block_entity_render.rs::classify Skull kind for mob heads
|
||||
BuiltInBlockModels.createMobWallHead port crates/horizon-client-render/src/block_entity_render.rs::classify Skull kind for wall mob heads
|
||||
BuiltInBlockModels.createMobHeads port crates/horizon-client-render/src/block_entity_render.rs::classify Skull kind for the head set
|
||||
BuiltInBlockModels.createPlayerHead port crates/horizon-client-render/src/block_entity_render.rs::classify Skull kind for player heads
|
||||
BuiltInBlockModels.createPlayerWallHead port crates/horizon-client-render/src/block_entity_render.rs::classify Skull kind for wall player heads
|
||||
BuiltInBlockModels.createBanner port crates/horizon-client-render/src/block_entity_render.rs::classify Banner kind
|
||||
BuiltInBlockModels.createWallBanner port crates/horizon-client-render/src/block_entity_render.rs::classify Banner kind for wall banners
|
||||
BuiltInBlockModels.createDyedShulkerBox port crates/horizon-client-render/src/block_entity_render.rs::classify Shulker kind for dyed boxes
|
||||
BuiltInBlockModels.createChest port crates/horizon-client-render/src/block_entity_render.rs::classify Chest kind
|
||||
BuiltInBlockModels.createSingletonChest port crates/horizon-client-render/src/block_entity_render.rs::classify Chest kind, single chest
|
||||
BuiltInBlockModels.createXmasChest port crates/horizon-client-render/src/block_entity_render.rs::classify Chest kind with the Christmas texture
|
||||
BuiltInBlockModels.createCopperGolem port crates/horizon-client-render/src/block_entity_render.rs::classify CopperGolem kind
|
||||
BuiltInBlockModels.createFlowerBedModel missing minor: the 0.25 block shift for a flower bed shown by a copper golem is not applied
|
||||
BuiltInBlockModels.createEnchantingTable port crates/horizon-client/src/block_entity_render.rs::classify EnchantingTable kind
|
||||
BuiltInBlockModels.specialModelWithPropertyDispatch port crates/horizon-client/src/block_entity_render.rs::classify property dispatch by block state values
|
||||
BuiltInBlockModels.createEnchantingTable port crates/horizon-client-render/src/block_entity_render.rs::classify EnchantingTable kind
|
||||
BuiltInBlockModels.specialModelWithPropertyDispatch port crates/horizon-client-render/src/block_entity_render.rs::classify property dispatch by block state values
|
||||
BuiltInBlockModels.Builder.put skip:java builder map of special models
|
||||
FluidModel.Unbaked.bake port crates/horizon-client/src/mesher.rs::MeshTables::new gap: minor: water and lava layers hard-wired; translucency picked in fluid tesselate
|
||||
FluidModel.Unbaked.getAndValidateMaterial port crates/horizon-client/src/mesher.rs::MeshTables::new sprite lookup through layer(); no separate atlas check
|
||||
FluidRenderer.isFaceOccludedByState port crates/horizon-client/src/mesher/fluid.rs::occluded_by fluid face occlusion by the neighbour's face shape
|
||||
FluidRenderer.shouldRenderFace port crates/horizon-client/src/mesher/fluid.rs::tesselate render_up, render_down and side checks, neighbour-same-fluid and self occlusion
|
||||
FluidRenderer.addFace inline crates/horizon-client/src/mesher/fluid.rs::tesselate quad closure in tesselate
|
||||
FluidRenderer.calculateAverageHeight inline crates/horizon-client/src/mesher/fluid.rs::tesselate average closure in tesselate
|
||||
FluidRenderer.addWeightedHeight inline crates/horizon-client/src/mesher/fluid.rs::tesselate weighted sum inside the average closure
|
||||
FluidRenderer.vertex inline crates/horizon-client/src/mesher/fluid.rs::tesselate vertex built in the quad closure
|
||||
FluidStateModelSet.bake port crates/horizon-client/src/mesher.rs::MeshTables::new fluid layers and tints baked per fluid
|
||||
FluidModel.Unbaked.bake port crates/horizon-client-render/src/mesher.rs::MeshTables::new gap: minor: water and lava layers hard-wired; translucency picked in fluid tesselate
|
||||
FluidModel.Unbaked.getAndValidateMaterial port crates/horizon-client-render/src/mesher.rs::MeshTables::new sprite lookup through layer(); no separate atlas check
|
||||
FluidRenderer.isFaceOccludedByState port crates/horizon-client-render/src/mesher/fluid.rs::occluded_by fluid face occlusion by the neighbour's face shape
|
||||
FluidRenderer.shouldRenderFace port crates/horizon-client-render/src/mesher/fluid.rs::tesselate render_up, render_down and side checks, neighbour-same-fluid and self occlusion
|
||||
FluidRenderer.addFace inline crates/horizon-client-render/src/mesher/fluid.rs::tesselate quad closure in tesselate
|
||||
FluidRenderer.calculateAverageHeight inline crates/horizon-client-render/src/mesher/fluid.rs::tesselate average closure in tesselate
|
||||
FluidRenderer.addWeightedHeight inline crates/horizon-client-render/src/mesher/fluid.rs::tesselate weighted sum inside the average closure
|
||||
FluidRenderer.vertex inline crates/horizon-client-render/src/mesher/fluid.rs::tesselate vertex built in the quad closure
|
||||
FluidStateModelSet.bake port crates/horizon-client-render/src/mesher.rs::MeshTables::new fluid layers and tints baked per fluid
|
||||
LoadedBlockModels.bake port crates/horizon-assets/src/bake.rs::bake_block_model bakes each block model; the loop over the set is the caller's
|
||||
ModelBlockRenderer.tesselateBlock port crates/horizon-client/src/mesher.rs::Mesher::mesh per block: fluids, models, smooth-light choice
|
||||
ModelBlockRenderer.configureTintCache port crates/horizon-client/src/mesher.rs::Tints::new per section tint tables
|
||||
ModelBlockRenderer.resetTintCache inline crates/horizon-client/src/mesher.rs::Tints::new tables are built fresh per section, no reset
|
||||
ModelBlockRenderer.tesselateAmbientOcclusion port crates/horizon-client/src/mesher.rs::Mesher::face AO branch of face
|
||||
ModelBlockRenderer.tesselateFlat port crates/horizon-client/src/mesher.rs::Mesher::face flat branch of face
|
||||
ModelBlockRenderer.tesselateBlock port crates/horizon-client-render/src/mesher.rs::Mesher::mesh per block: fluids, models, smooth-light choice
|
||||
ModelBlockRenderer.configureTintCache port crates/horizon-client-render/src/mesher.rs::Tints::new per section tint tables
|
||||
ModelBlockRenderer.resetTintCache inline crates/horizon-client-render/src/mesher.rs::Tints::new tables are built fresh per section, no reset
|
||||
ModelBlockRenderer.tesselateAmbientOcclusion port crates/horizon-client-render/src/mesher.rs::Mesher::face AO branch of face
|
||||
ModelBlockRenderer.tesselateFlat port crates/horizon-client-render/src/mesher.rs::Mesher::face flat branch of face
|
||||
ModelBlockRenderer.shouldRenderFace port crates/horizon-world/src/block_render.rs::FaceVisibility::should_render_face per face visibility, called from Mesher::mesh
|
||||
ModelBlockRenderer.putQuadWithTint port crates/horizon-client/src/mesher.rs::Mesher::face tinted vertex write
|
||||
ModelBlockRenderer.getTintColor port crates/horizon-client/src/mesher.rs::Mesher::tint tint source index to colour
|
||||
ModelBlockRenderer.computeTintColor port crates/horizon-client/src/mesher.rs::Tints::get biome colour average over the blend radius
|
||||
ModelBlockRenderer.putQuadWithTint port crates/horizon-client-render/src/mesher.rs::Mesher::face tinted vertex write
|
||||
ModelBlockRenderer.getTintColor port crates/horizon-client-render/src/mesher.rs::Mesher::tint tint source index to colour
|
||||
ModelBlockRenderer.computeTintColor port crates/horizon-client-render/src/mesher.rs::Tints::get biome colour average over the blend radius
|
||||
MovingBlockRenderState.getBlockTint port crates/horizon-client/src/game/moving_pistons.rs::Game::moving_block_vertices gap: minor: tint is the particle colour at the source position, not the biome tint at the moved spot
|
||||
MovingBlockRenderState.getBlockState port crates/horizon-client/src/game/moving_pistons.rs::Game::moving_piston_blocks moved block and piston base
|
||||
SelectBlockModel.update missing minor: display-context switch (copper golem hand, flower bed) is not applied; block items use the normal transform
|
||||
|
||||
|
@@ -6,8 +6,8 @@ Variant.SimpleModelState.withUvLock skip:java copy-with setter of an immutable
|
||||
VariantMutator.then skip:java composes JSON field setters; Variant::decode reads all fields in one pass
|
||||
VariantSelector.predicate port crates/horizon-assets/src/blockstate/bind.rs::variant_predicate parses the property clauses; matching against states is in BlockStateDefinition::bind
|
||||
WeightedVariants.computeMaterialFlags skip:java OR of per-model render flags; Horizon keeps translucency per face (BakedFace in models/catalog.rs)
|
||||
WeightedVariants.Unbaked.bake inline crates/horizon-client/src/models/catalog.rs::StateModels::load each weighted variant baked once with its transform; the choice is kept in CompiledChoices
|
||||
WeightedVariants.Unbaked.resolveDependencies inline crates/horizon-client/src/models/catalog.rs::StateModels::load model names gathered from all definitions before the block library loads
|
||||
WeightedVariants.Unbaked.bake inline crates/horizon-client-render/src/models/catalog.rs::StateModels::load each weighted variant baked once with its transform; the choice is kept in CompiledChoices
|
||||
WeightedVariants.Unbaked.resolveDependencies inline crates/horizon-client-render/src/models/catalog.rs::StateModels::load model names gathered from all definitions before the block library loads
|
||||
CombinedCondition.instantiate port crates/horizon-assets/src/blockstate/bind.rs::BoundCondition::bind AND and OR branches; the test itself is BoundCondition::matches
|
||||
KeyValueCondition.instantiate port crates/horizon-assets/src/blockstate/bind.rs::BoundCondition::bind property lookup per key and the properties branch
|
||||
KeyValueCondition.Term.parse port crates/horizon-assets/src/blockstate.rs::Condition::decode reads the leading "!" as negation
|
||||
@@ -19,37 +19,37 @@ MultiPartModel.SharedBakedState.getFirstModel port crates/horizon-assets/src/blo
|
||||
MultiPartModel.SharedBakedState.computeMaterialFlags skip:java same OR of render flags as WeightedVariants; translucency is kept per face
|
||||
MultiPartModel.SharedBakedState.selectModels port crates/horizon-assets/src/blockstate/bind.rs::BlockStateDefinition::bind selected selector indices per state, computed once at load
|
||||
MultiPartModel.Unbaked.visualEqualityGroup skip:java key for sharing identical visual states; Horizon keys block states by id
|
||||
MultiPartModel.Unbaked.resolveDependencies inline crates/horizon-client/src/models/catalog.rs::StateModels::load model names gathered from every multipart selector
|
||||
MultiPartModel.Unbaked.bake inline crates/horizon-client/src/models/catalog.rs::StateModels::load shared bake per definition; per-state selection stored in states
|
||||
MultiPartModel.Unbaked.resolveDependencies inline crates/horizon-client-render/src/models/catalog.rs::StateModels::load model names gathered from every multipart selector
|
||||
MultiPartModel.Unbaked.bake inline crates/horizon-client-render/src/models/catalog.rs::StateModels::load shared bake per definition; per-state selection stored in states
|
||||
Selector.instantiate port crates/horizon-assets/src/blockstate/bind.rs::BlockStateDefinition::bind a selector without a condition applies to every state
|
||||
BlockStateModelWrapper.update port crates/horizon-client/src/game/minecarts.rs::Game::block_model_vertices gap: player: no tint layers, so carried or cart blocks with tint sources (grass, leaves) are drawn untinted
|
||||
BlockStateModelWrapper.updateTints missing player: carried and cart blocks with tint sources (grass, leaves, water) show untinted; Horizon's block_model_vertices has no tint step
|
||||
BlockStateModelWrapper.Unbaked.bake skip:java builds the wrapper object; Horizon passes the transform straight to block_model_vertices
|
||||
CompositeBlockModel.update missing player: only block display entities reach this and Horizon draws none, so a special block (chest and similar) shown that way never appears
|
||||
CompositeBlockModel.Unbaked.bake skip:java builds the composite object; no separate step in Horizon
|
||||
ConditionalBlockModel.update inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one Christmas chest texture chosen in the chest branch by extended_christmas()
|
||||
ConditionalBlockModel.update inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one Christmas chest texture chosen in the chest branch by extended_christmas()
|
||||
ConditionalBlockModel.Unbaked.bake skip:java builds the conditional object; the chest branch picks the texture when drawing
|
||||
SpecialBlockModelWrapper.Unbaked.bake skip:data special models come from exported model data in BlockEntityRenderer::load; no baking step
|
||||
AbstractEndPortalRenderer.submitSpecial port crates/horizon-client/src/block_entity_render.rs::portal_cube gap: minor: no outline pass (outlineColor) for the portal faces
|
||||
AbstractSignRenderer.submit port crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one front and back text drawn in the sign branch via sign_text_calls
|
||||
AbstractSignRenderer.isOutlineVisible port crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one gap: minor: only the 16-block distance is tested; spyglass scoping in first person does not turn the outline on
|
||||
AbstractSignRenderer.extractRenderState inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one gap: minor: the text filtering option is not read; filtered sign lines are not shown
|
||||
BannerRenderer.extractRenderState inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one flutter phase from position, game time and partial tick; standing or wall placement
|
||||
BannerRenderer.bannerModel inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one wall or standing banner model chosen in the banner branch
|
||||
BannerRenderer.flagModel inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one wall or standing flag model chosen in the banner branch
|
||||
AbstractEndPortalRenderer.submitSpecial port crates/horizon-client-render/src/block_entity_render.rs::portal_cube gap: minor: no outline pass (outlineColor) for the portal faces
|
||||
AbstractSignRenderer.submit port crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one front and back text drawn in the sign branch via sign_text_calls
|
||||
AbstractSignRenderer.isOutlineVisible port crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one gap: minor: only the 16-block distance is tested; spyglass scoping in first person does not turn the outline on
|
||||
AbstractSignRenderer.extractRenderState inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one gap: minor: the text filtering option is not read; filtered sign lines are not shown
|
||||
BannerRenderer.extractRenderState inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one flutter phase from position, game time and partial tick; standing or wall placement
|
||||
BannerRenderer.bannerModel inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one wall or standing banner model chosen in the banner branch
|
||||
BannerRenderer.flagModel inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one wall or standing flag model chosen in the banner branch
|
||||
BannerRenderer.submitSpecial port crates/horizon-client/src/item_render/special.rs::submit banner as a special item model: base, flag and pattern layers
|
||||
BannerRenderer.submitBanner port crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one base model, flag and pattern layers; the crumbling overlay is drawn by render_breaking
|
||||
BannerRenderer.submitPatternLayer inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one loop over up to 16 pattern layers in the banner branch
|
||||
BannerRenderer.modelTransformation inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one translate, turn by angle, scale in the banner branch
|
||||
BannerRenderer.submitBanner port crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one base model, flag and pattern layers; the crumbling overlay is drawn by render_breaking
|
||||
BannerRenderer.submitPatternLayer inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one loop over up to 16 pattern layers in the banner branch
|
||||
BannerRenderer.modelTransformation inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one translate, turn by angle, scale in the banner branch
|
||||
BeaconRenderer.extractRenderState inline crates/horizon-client/src/game/beacon.rs::Game::beacon_geometry animation time and beam radius scale computed there
|
||||
BeaconRenderer.extract inline crates/horizon-client/src/game/beacon.rs::Game::beacon_geometry same time and scale rules, from distance and scoping
|
||||
BeaconRenderer.renderQuad port crates/horizon-client/src/block_entity_render.rs::beam solid beam and glow quads per section
|
||||
BeaconRenderer.addVertex port crates/horizon-client/src/block_entity_render.rs::beam vertex with colour and full light, emitted by beam's inner emit
|
||||
BellRenderer.extractRenderState inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one bell ticks and shake direction read in the bell branch
|
||||
BellRenderer.submit port crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one gap: player: no crack overlay while a bell is broken (render_breaking draws only banners and chests)
|
||||
BlockEntityRenderDispatcher.getRenderer port crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::kind renderer chosen from the block state
|
||||
BlockEntityRenderDispatcher.tryExtractRenderState port crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render gap: minor: distance cull only (256 for gateways); no frustum, type-validity or off-screen test here
|
||||
BlockEntityRenderDispatcher.submit port crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one draws each kind; no crash report wrapper
|
||||
BeaconRenderer.renderQuad port crates/horizon-client-render/src/block_entity_render.rs::beam solid beam and glow quads per section
|
||||
BeaconRenderer.addVertex port crates/horizon-client-render/src/block_entity_render.rs::beam vertex with colour and full light, emitted by beam's inner emit
|
||||
BellRenderer.extractRenderState inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one bell ticks and shake direction read in the bell branch
|
||||
BellRenderer.submit port crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one gap: player: no crack overlay while a bell is broken (render_breaking draws only banners and chests)
|
||||
BlockEntityRenderDispatcher.getRenderer port crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::kind renderer chosen from the block state
|
||||
BlockEntityRenderDispatcher.tryExtractRenderState port crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render gap: minor: distance cull only (256 for gateways); no frustum, type-validity or off-screen test here
|
||||
BlockEntityRenderDispatcher.submit port crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one draws each kind; no crash report wrapper
|
||||
BlockEntityRenderDispatcher.onResourceManagerReload missing minor: block entity models load once in BlockEntityRenderer::load; no reload path found
|
||||
BlockEntityRenderers.createEntityRenderers skip:data renderer registration table; Horizon's classify table replaces it
|
||||
BlockEntityWithBoundingBoxRenderer.extractRenderState missing player: game-master players (creative or op) see no structure box, invisible-block or structure-void markers
|
||||
@@ -57,56 +57,56 @@ BlockEntityWithBoundingBoxRenderer.extract missing player: game-master players
|
||||
BlockEntityWithBoundingBoxRenderer.submit missing player: game-master players (creative or op) see no structure box, invisible-block or structure-void markers
|
||||
BlockEntityWithBoundingBoxRenderer.renderInvisibleBlocks missing player: game-master players (creative or op) see no barrier, light or air block markers
|
||||
BlockEntityWithBoundingBoxRenderer.renderStructureVoids missing player: game-master players (creative or op) see no structure void markers
|
||||
BrightnessCombiner.acceptDouble inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one brighter of the two chest halves per channel, in the chest branch
|
||||
BrightnessCombiner.acceptDouble inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one brighter of the two chest halves per channel, in the chest branch
|
||||
BrushableBlockRenderer.extractRenderState missing player: brushing a suspicious block shows no item on its face; the hit side and dust stage are not read
|
||||
BrushableBlockRenderer.submit missing player: the brushed item never slides out of the block while it is brushed
|
||||
BrushableBlockRenderer.translations missing player: the per-side slide offset for each dust stage is not computed
|
||||
CampfireRenderer.extractRenderState inline crates/horizon-client/src/game/campfire.rs::Game::campfire_geometry facing and per-slot item seed read there
|
||||
CampfireRenderer.submit port crates/horizon-client/src/game/campfire.rs::Game::campfire_geometry items on the four sides, drawn within 64 blocks
|
||||
ChestRenderer.createModelTransformation inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one chest turn about its centre in the chest branch
|
||||
ChestRenderer.createModelTransformation inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one chest turn about its centre in the chest branch
|
||||
ConduitRenderer.createEyeLayer skip:data model layer; Horizon loads the conduit parts from exported model data in BlockEntityRenderer::load
|
||||
ConduitRenderer.createWindLayer skip:data model layer; Horizon loads the conduit parts from exported model data in BlockEntityRenderer::load
|
||||
ConduitRenderer.createShellLayer skip:data model layer; Horizon loads the conduit parts from exported model data in BlockEntityRenderer::load
|
||||
ConduitRenderer.createCageLayer skip:data model layer; Horizon loads the conduit parts from exported model data in BlockEntityRenderer::load
|
||||
ConduitRenderer.submit port crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one gap: player: no crack overlay on the conduit (render_breaking draws only banners and chests)
|
||||
CopperGolemStatueBlockRenderer.extractRenderState inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one facing, pose and oxidation texture read in the golem branch
|
||||
CopperGolemStatueBlockRenderer.submit port crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one gap: player: no crack overlay on the statue (render_breaking draws only banners and chests)
|
||||
CopperGolemStatueBlockRenderer.createModelTransformation inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one rotation from the opposite facing in the golem branch
|
||||
ConduitRenderer.submit port crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one gap: player: no crack overlay on the conduit (render_breaking draws only banners and chests)
|
||||
CopperGolemStatueBlockRenderer.extractRenderState inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one facing, pose and oxidation texture read in the golem branch
|
||||
CopperGolemStatueBlockRenderer.submit port crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one gap: player: no crack overlay on the statue (render_breaking draws only banners and chests)
|
||||
CopperGolemStatueBlockRenderer.createModelTransformation inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one rotation from the opposite facing in the golem branch
|
||||
DecoratedPotRenderer.createBaseLayer skip:data pot base model; Horizon loads it from exported model data in BlockEntityRenderer::load
|
||||
DecoratedPotRenderer.createSidesLayer skip:data pot side models; Horizon loads them from exported model data in BlockEntityRenderer::load
|
||||
DecoratedPotRenderer.getSideSprite inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one sherd pattern texture per side from pot_patterns, blank side otherwise
|
||||
DecoratedPotRenderer.createModelTransformation inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one pot turn about its centre in the decorated pot branch
|
||||
DecoratedPotRenderer.getSideSprite inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one sherd pattern texture per side from pot_patterns, blank side otherwise
|
||||
DecoratedPotRenderer.createModelTransformation inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one pot turn about its centre in the decorated pot branch
|
||||
DecoratedPotRenderer.getExtents port crates/horizon-client/src/item_render/special.rs::extents decorated pot part extents for the item model bounds
|
||||
HangingSignRenderer.extractRenderState inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one gap: minor: the text filtering option is not read
|
||||
LecternRenderer.extractRenderState inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one book drawn only when has_book is set, turned by facing
|
||||
LecternRenderer.submit port crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one gap: player: no crack overlay on the lectern book (render_breaking draws only banners and chests)
|
||||
HangingSignRenderer.extractRenderState inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one gap: minor: the text filtering option is not read
|
||||
LecternRenderer.extractRenderState inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one book drawn only when has_book is set, turned by facing
|
||||
LecternRenderer.submit port crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one gap: player: no crack overlay on the lectern book (render_breaking draws only banners and chests)
|
||||
PistonHeadRenderer.submit port crates/horizon-client/src/game/moving_pistons.rs::Game::moving_piston_geometry head and base blocks drawn at their offsets
|
||||
PistonHeadRenderer.createMovingBlock port crates/horizon-client/src/game/moving_pistons.rs::Game::moving_block_vertices gap: minor: the tint is the particle sprite colour, not the block's tint sources
|
||||
ShelfRenderer.extractRenderState missing player: items placed on shelf blocks are not kept for drawing; only the comparator signal is handled (game/machines.rs::shelf_signal)
|
||||
ShelfRenderer.submit missing player: items on shelves are invisible
|
||||
ShelfRenderer.submitItem missing player: shelf item slots, offsets and scale are not drawn
|
||||
ShulkerBoxRenderer.extractRenderState inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one facing, colour texture and lid progress read in the shulker branch
|
||||
ShulkerBoxRenderer.submit port crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one gap: player: no crack overlay on shulker boxes (render_breaking draws only banners and chests)
|
||||
ShulkerBoxRenderer.createModelTransform inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one facing transform in the shulker branch
|
||||
ShulkerBoxRenderer.extractRenderState inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one facing, colour texture and lid progress read in the shulker branch
|
||||
ShulkerBoxRenderer.submit port crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one gap: player: no crack overlay on shulker boxes (render_breaking draws only banners and chests)
|
||||
ShulkerBoxRenderer.createModelTransform inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one facing transform in the shulker branch
|
||||
ShulkerBoxRenderer.getExtents port crates/horizon-client/src/item_render/special.rs::extents shulker lid pose extents for the item model bounds
|
||||
SkullBlockRenderer.createModel skip:data skull models; Horizon loads them from exported model data in BlockEntityRenderer::load
|
||||
SkullBlockRenderer.extractRenderState inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one type, wall facing and turn read from the skull kind; player skin via scene.skull
|
||||
SkullBlockRenderer.submit port crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one gap: player: no crack overlay on skulls and heads (render_breaking draws only banners and chests)
|
||||
SkullBlockRenderer.submitSkull port crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one gap: player: no crack overlay on skulls and heads (render_breaking draws only banners and chests)
|
||||
SkullBlockRenderer.resolveSkullRenderType inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one player head skin via scene.skull, otherwise the type's texture
|
||||
SkullBlockRenderer.createWallTransformation inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one wall skull offset and turn in the skull branch
|
||||
SkullBlockRenderer.createGroundTransformation inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one ground skull turn by segment in the skull branch
|
||||
SkullBlockRenderer.extractRenderState inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one type, wall facing and turn read from the skull kind; player skin via scene.skull
|
||||
SkullBlockRenderer.submit port crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one gap: player: no crack overlay on skulls and heads (render_breaking draws only banners and chests)
|
||||
SkullBlockRenderer.submitSkull port crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one gap: player: no crack overlay on skulls and heads (render_breaking draws only banners and chests)
|
||||
SkullBlockRenderer.resolveSkullRenderType inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one player head skin via scene.skull, otherwise the type's texture
|
||||
SkullBlockRenderer.createWallTransformation inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one wall skull offset and turn in the skull branch
|
||||
SkullBlockRenderer.createGroundTransformation inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one ground skull turn by segment in the skull branch
|
||||
SpawnerRenderer.extractRenderState port crates/horizon-client/src/mobs/spawner.rs::display_pose spin and scale of the display mob
|
||||
SpawnerRenderer.submit port crates/horizon-client/src/game/spawner.rs::Game::spawner_geometry display mob at the spawner's centre with spin, tilt and scale
|
||||
StandingSignRenderer.extractRenderState inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one gap: minor: the text filtering option is not read
|
||||
StandingSignRenderer.createTransformations inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one front and back text transforms per attachment
|
||||
StandingSignRenderer.extractRenderState inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one gap: minor: the text filtering option is not read
|
||||
StandingSignRenderer.createTransformations inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one front and back text transforms per attachment
|
||||
TestInstanceRenderer.extractRenderState missing player: creative players placing a test instance block see no beacon beam, box or error markers
|
||||
TestInstanceRenderer.submit missing player: creative players placing a test instance block see no beacon beam or box
|
||||
TestInstanceRenderer.submitErrorMarker missing player: red error markers and their text are not drawn
|
||||
TestInstanceRenderer.shouldRenderOffScreen missing minor: culling policy of a renderer Horizon lacks
|
||||
TestInstanceRenderer.shouldRender missing minor: culling policy of a renderer Horizon lacks
|
||||
TheEndGatewayRenderer.submit port crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one beam and visible cube faces in the end gateway branch
|
||||
TheEndPortalRenderer.submit port crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one portal slab in the end portal branch
|
||||
TheEndGatewayRenderer.submit port crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one beam and visible cube faces in the end gateway branch
|
||||
TheEndPortalRenderer.submit port crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one portal slab in the end portal branch
|
||||
TrialSpawnerRenderer.extractRenderState port crates/horizon-client/src/mobs/spawner.rs::display_pose same spin and scale rule as spawners
|
||||
TrialSpawnerRenderer.submit port crates/horizon-client/src/game/spawner.rs::Game::spawner_geometry trial spawner display mob drawn in the same pass
|
||||
VaultRenderer.extractRenderState inline crates/horizon-client/src/game/vault.rs::Game::vault_geometry display item and spin read there
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 13 and column 112.
|
@@ -1,24 +1,24 @@
|
||||
CloudRenderer.getSizeForCloudDistance skip:java GPU ring-buffer capacity for the cloud face buffer; Horizon sizes its vertex buffer from the built mesh
|
||||
CloudRenderer.packCellData inline crates/horizon-client/src/clouds.rs::Mesh::prepare cell presence is a bool test on the texture grid; no packed long
|
||||
CloudRenderer.isNorthEmpty inline crates/horizon-client/src/clouds.rs::Mesh::prepare neighbour-empty flags passed to face_mask in N,E,S,W order
|
||||
CloudRenderer.isEastEmpty inline crates/horizon-client/src/clouds.rs::Mesh::prepare neighbour-empty flags passed to face_mask in N,E,S,W order
|
||||
CloudRenderer.isSouthEmpty inline crates/horizon-client/src/clouds.rs::Mesh::prepare neighbour-empty flags passed to face_mask in N,E,S,W order
|
||||
CloudRenderer.isWestEmpty inline crates/horizon-client/src/clouds.rs::Mesh::prepare neighbour-empty flags passed to face_mask in N,E,S,W order
|
||||
CloudRenderer.render port crates/horizon-client/src/gfx.rs::Renderer::draw_frame gap: minor: one cloud pipeline; fancy vs flat is chosen by the mesh fast flag, not by pipeline
|
||||
CloudRenderer.packCellData inline crates/horizon-client-render/src/clouds.rs::Mesh::prepare cell presence is a bool test on the texture grid; no packed long
|
||||
CloudRenderer.isNorthEmpty inline crates/horizon-client-render/src/clouds.rs::Mesh::prepare neighbour-empty flags passed to face_mask in N,E,S,W order
|
||||
CloudRenderer.isEastEmpty inline crates/horizon-client-render/src/clouds.rs::Mesh::prepare neighbour-empty flags passed to face_mask in N,E,S,W order
|
||||
CloudRenderer.isSouthEmpty inline crates/horizon-client-render/src/clouds.rs::Mesh::prepare neighbour-empty flags passed to face_mask in N,E,S,W order
|
||||
CloudRenderer.isWestEmpty inline crates/horizon-client-render/src/clouds.rs::Mesh::prepare neighbour-empty flags passed to face_mask in N,E,S,W order
|
||||
CloudRenderer.render port crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame gap: minor: one cloud pipeline; fancy vs flat is chosen by the mesh fast flag, not by pipeline
|
||||
CloudRenderer.renderOit missing player: with Improved transparency on, clouds keep the classic blend; no OIT pass exists and the option is only saved
|
||||
CloudRenderer.buildMesh port crates/horizon-client/src/clouds.rs::Mesh::prepare cell ring walk via cells(radius), nearest first
|
||||
CloudRenderer.tryBuildCell inline crates/horizon-client/src/clouds.rs::Mesh::prepare skip empty cells, then build the cell's faces
|
||||
CloudRenderer.encodeFace inline crates/horizon-client/src/clouds.rs::Mesh::prepare 3-byte face records replaced by vertex quads
|
||||
CloudRenderer.buildExtrudedCell port crates/horizon-client/src/clouds.rs::face_mask top, bottom and side visibility; quads emitted in Mesh::prepare via faces()
|
||||
CubeMap.initializeVertices port crates/horizon-client/src/gfx/panorama.rs::Panorama::new 24-corner cube; Horizon draws 36 vertices from the shader in the pipeline built here
|
||||
CloudRenderer.buildMesh port crates/horizon-client-render/src/clouds.rs::Mesh::prepare cell ring walk via cells(radius), nearest first
|
||||
CloudRenderer.tryBuildCell inline crates/horizon-client-render/src/clouds.rs::Mesh::prepare skip empty cells, then build the cell's faces
|
||||
CloudRenderer.encodeFace inline crates/horizon-client-render/src/clouds.rs::Mesh::prepare 3-byte face records replaced by vertex quads
|
||||
CloudRenderer.buildExtrudedCell port crates/horizon-client-render/src/clouds.rs::face_mask top, bottom and side visibility; quads emitted in Mesh::prepare via faces()
|
||||
CubeMap.initializeVertices port crates/horizon-client-render/src/gfx/panorama.rs::Panorama::new 24-corner cube; Horizon draws 36 vertices from the shader in the pipeline built here
|
||||
DebugCrosshairRenderer.<init> skip:debug F3 3D axis gizmo at the view; Horizon has no debug crosshair
|
||||
DynamicGpuData.reset skip:other GPU per-frame ring reset; wgpu staging in Horizon's Uploads belt
|
||||
DynamicGpuData.close skip:other GPU buffer release; Rust drops the wgpu buffers
|
||||
DynamicGpuData.writeTransform skip:other per-draw uniform staging; Horizon writes its uniforms directly with queue writes
|
||||
DynamicGpuData.writeChunkSections port crates/horizon-client/src/gfx.rs::section_instance gap: player: section fade-in (vanilla default 0.75 s) is not sent; Horizon writes a fixed fourth value
|
||||
DynamicGpuData.writeChunkSectionsInstanced port crates/horizon-client/src/gfx.rs::section_instance gap: player: section fade-in (vanilla default 0.75 s) is not sent; same instance record as writeChunkSections
|
||||
DynamicGpuData.writeChunkSectionCommands port crates/horizon-client/src/gfx.rs::Renderer::draw_frame IndexedDraw records built and uploaded into the indirect buffer
|
||||
DynamicGpuData.ChunkSectionInfo.write port crates/horizon-client/src/gfx.rs::section_instance gap: player: packs origin only; the fade-in visibility float (default 0.75 s) is not in the record
|
||||
DynamicGpuData.writeChunkSections port crates/horizon-client-render/src/gfx.rs::section_instance gap: player: section fade-in (vanilla default 0.75 s) is not sent; Horizon writes a fixed fourth value
|
||||
DynamicGpuData.writeChunkSectionsInstanced port crates/horizon-client-render/src/gfx.rs::section_instance gap: player: section fade-in (vanilla default 0.75 s) is not sent; same instance record as writeChunkSections
|
||||
DynamicGpuData.writeChunkSectionCommands port crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame IndexedDraw records built and uploaded into the indirect buffer
|
||||
DynamicGpuData.ChunkSectionInfo.write port crates/horizon-client-render/src/gfx.rs::section_instance gap: player: packs origin only; the fade-in visibility float (default 0.75 s) is not in the record
|
||||
DynamicGpuData.IndexedDraw.write skip:other indirect draw args; Horizon keeps them as [u32;5] words uploaded in one copy
|
||||
DynamicGpuData.TerrainTransform.write skip:other terrain UBO layout; Horizon's terrain pass uses its own uniforms
|
||||
DynamicGpuData.Transform.write skip:other std140 layout of the transform UBO; wgpu bindings in Horizon
|
||||
@@ -50,12 +50,12 @@ FirstPersonHandsAndItemsRenderer.applyItemArmAttackTransform port crates/horizon
|
||||
FirstPersonHandsAndItemsRenderer.applyItemArmTransform port crates/horizon-client/src/first_person.rs::item_pose base item-in-arm translation
|
||||
FirstPersonHandsAndItemsRenderer.submitHandsWithItems port crates/horizon-client/src/game.rs::Game::hand_geometry sway, both hands, empty-hand arm and item pose
|
||||
FirstPersonHandsAndItemsRenderer.swingArm port crates/horizon-client/src/first_person.rs::item_pose attack swing translation and arm rotation
|
||||
GameRenderer.<init> port crates/horizon-client/src/gfx.rs::Renderer::new lightmap, hand target, debug crosshair and screen effects are built here
|
||||
GameRenderer.<init> port crates/horizon-client-render/src/gfx.rs::Renderer::new lightmap, hand target, debug crosshair and screen effects are built here
|
||||
GameRenderer.close skip:other GPU resource teardown; wgpu drops the renderer's objects
|
||||
GameRenderer.checkEntityPostEffect missing player: spectating a creeper, spider or enderman does not put their overlay shader on the screen
|
||||
GameRenderer.processBlurEffect port crates/horizon-client/src/menu_blur.rs::MenuBlur::apply menu background blur pass
|
||||
GameRenderer.processBlurEffect port crates/horizon-client-render/src/menu_blur.rs::MenuBlur::apply menu background blur pass
|
||||
GameRenderer.preloadUiShader skip:other GPU pipeline precompile at load; Horizon builds its pipelines in Renderer::new
|
||||
GameRenderer.update port crates/horizon-client/src/camera.rs::AvatarBob::update_bob gap: minor: the requested post-effect list (from /postEffect) is not built
|
||||
GameRenderer.update port crates/horizon-client-render/src/camera.rs::AvatarBob::update_bob gap: minor: the requested post-effect list (from /postEffect) is not built
|
||||
GameRenderer.extract skip:other render-state copy for the vanilla render thread; Horizon renders from game state
|
||||
GameRenderer.preparePostEffects missing minor: vanilla post chains from requested effects are not applied; shader packs use pack_post
|
||||
GameRenderer.applyPostEffects missing minor: vanilla post chains are not run; shader packs use pack_post
|
||||
@@ -75,37 +75,37 @@ GpuWarnlistManager.parseJson missing minor: no GPU driver warnlist JSON
|
||||
GpuWarnlistManager.Preparations.matchAny missing minor: no GPU driver warnlist matching
|
||||
LevelEventHandler.playJukeboxSong port crates/horizon-client/src/game/jukebox.rs::Game::start_song song starts and the now-playing overlay is shown
|
||||
LevelEventHandler.stopJukeboxSong port crates/horizon-client/src/game/jukebox.rs::Game::stop_song song sound stops for that jukebox
|
||||
LevelRenderer.<init> port crates/horizon-client/src/gfx.rs::Renderer::new targets, pipelines and buffers built once
|
||||
LevelRenderer.submitFeatures port crates/horizon-client/src/gfx.rs::Renderer::draw_frame entities, block entities, crumbling, particles, outline and gizmos are submitted and drawn in the frame
|
||||
LevelRenderer.repositionCamera port crates/horizon-client/src/gfx/resident.rs::camera_column camera section column that the section grid is centred on; gap: minor: world border invalidation is not part of it
|
||||
LevelRenderer.addMainPass port crates/horizon-client/src/gfx.rs::Renderer::draw_frame gap: minor: the Improved transparency targets (depth bounds, transmittance, accumulate) are not created
|
||||
LevelRenderer.<init> port crates/horizon-client-render/src/gfx.rs::Renderer::new targets, pipelines and buffers built once
|
||||
LevelRenderer.submitFeatures port crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame entities, block entities, crumbling, particles, outline and gizmos are submitted and drawn in the frame
|
||||
LevelRenderer.repositionCamera port crates/horizon-client-render/src/gfx/resident.rs::camera_column camera section column that the section grid is centred on; gap: minor: world border invalidation is not part of it
|
||||
LevelRenderer.addMainPass port crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame gap: minor: the Improved transparency targets (depth bounds, transmittance, accumulate) are not created
|
||||
LevelRenderer.executeAlwaysOnTop port crates/horizon-client/src/game.rs::Game::render always-on-top debug lines and crosshair drawn over the world
|
||||
LevelRenderer.executeSolid port crates/horizon-client/src/gfx.rs::Renderer::draw_sections solid section draws in arena page order
|
||||
LevelRenderer.prepareTranslucents port crates/horizon-client/src/gfx/translucency.rs::Sorter::sort translucent quads sorted from the camera each frame
|
||||
LevelRenderer.executeSolid port crates/horizon-client-render/src/gfx.rs::Renderer::draw_sections solid section draws in arena page order
|
||||
LevelRenderer.prepareTranslucents port crates/horizon-client-render/src/gfx/translucency.rs::Sorter::sort translucent quads sorted from the camera each frame
|
||||
LevelRenderer.executeOit missing player: with Improved transparency on, translucent blocks and water blend order-independently; Horizon has no OIT pass and the option does nothing
|
||||
LevelRenderer.executeDepthBoundsCull missing player: part of Improved transparency's pass; no depth-bounds cull exists in Horizon
|
||||
LevelRenderer.executeOitWaterMask missing player: part of Improved transparency; no water mask pass exists in Horizon
|
||||
LevelRenderer.extractSectionDrawGroups port crates/horizon-client/src/gfx.rs::Renderer::draw_frame section draws grouped per layer and arena page from the visible sections
|
||||
LevelRenderer.prepareChunkRenders port crates/horizon-client/src/gfx.rs::Renderer::draw_frame per-layer draw list built for the frame, reversed for translucent layers
|
||||
LevelRenderer.extractSectionDrawGroups port crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame section draws grouped per layer and arena page from the visible sections
|
||||
LevelRenderer.prepareChunkRenders port crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame per-layer draw list built for the frame, reversed for translucent layers
|
||||
LevelRenderer.performTransientBlockRemovals missing minor: removes transient blocks when their section recompiles; Horizon has no transient blocks
|
||||
LevelRenderer.prepareChunkRendersIndirect port crates/horizon-client/src/gfx.rs::Renderer::draw_frame indirect draw arguments written and multi-draw issued
|
||||
LevelRenderer.compileSections port crates/horizon-client/src/streaming.rs::Streaming::update section meshing jobs queued for dirty sections nearest the camera
|
||||
LevelRenderer.submitEntities port crates/horizon-client/src/gfx.rs::Renderer::draw_frame entity models drawn with their batches
|
||||
LevelRenderer.submitBlockEntities port crates/horizon-client/src/gfx.rs::Renderer::block_entities block entity list submitted for drawing
|
||||
LevelRenderer.submitBlockDestroyAnimation port crates/horizon-client/src/gfx.rs::Renderer::draw_frame crumbling overlay drawn on the block being broken
|
||||
LevelRenderer.prepareChunkRendersIndirect port crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame indirect draw arguments written and multi-draw issued
|
||||
LevelRenderer.compileSections port crates/horizon-client-render/src/streaming.rs::Streaming::update section meshing jobs queued for dirty sections nearest the camera
|
||||
LevelRenderer.submitEntities port crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame entity models drawn with their batches
|
||||
LevelRenderer.submitBlockEntities port crates/horizon-client-render/src/gfx.rs::Renderer::block_entities block entity list submitted for drawing
|
||||
LevelRenderer.submitBlockDestroyAnimation port crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame crumbling overlay drawn on the block being broken
|
||||
LevelRenderer.submitTransientBlocks missing player: a block the server sends as temporary (ClientboundAddTransientBlockPacket) does not appear; the packet is not handled
|
||||
LevelRenderer.submitHitOutline port crates/horizon-client/src/game.rs::Game::outline_boxes outline of the block under the crosshair
|
||||
LevelRenderer.close skip:other GPU resource teardown
|
||||
LevelRenderer.invalidateCompiledGeometry port crates/horizon-client/src/gfx.rs::Renderer::clear_sections all section meshes dropped and rebuilt when settings are applied
|
||||
LevelRenderer.scheduleTranslucentSectionResort port crates/horizon-client/src/gfx/translucency.rs::SortState::update resort scheduled when the camera point of view changes
|
||||
LevelRenderer.scheduleResort port crates/horizon-client/src/gfx/translucency.rs::SortState::update per-section resort gated by point-of-view change and translucent geometry
|
||||
LevelRenderer.resetLevelRenderData port crates/horizon-client/src/gfx.rs::Renderer::clear_sections section data released when a level is left
|
||||
LevelRenderer.invalidateCompiledGeometry port crates/horizon-client-render/src/gfx.rs::Renderer::clear_sections all section meshes dropped and rebuilt when settings are applied
|
||||
LevelRenderer.scheduleTranslucentSectionResort port crates/horizon-client-render/src/gfx/translucency.rs::SortState::update resort scheduled when the camera point of view changes
|
||||
LevelRenderer.scheduleResort port crates/horizon-client-render/src/gfx/translucency.rs::SortState::update per-section resort gated by point-of-view change and translucent geometry
|
||||
LevelRenderer.resetLevelRenderData port crates/horizon-client-render/src/gfx.rs::Renderer::clear_sections section data released when a level is left
|
||||
LevelRenderer.hasRenderedAllSections missing minor: only the world icon screenshot asks this, and that screenshot is missing
|
||||
LevelRenderer.isSectionCompiledAndVisible port crates/horizon-client/src/gfx/resident.rs::Resident::contains gap: minor: no section fade-in, so the 0.3 visibility threshold is not checked
|
||||
LevelRenderer.isSectionCompiledAndVisible port crates/horizon-client-render/src/gfx/resident.rs::Resident::contains gap: minor: no section fade-in, so the 0.3 visibility threshold is not checked
|
||||
LevelRenderer.finalizeGizmoCollection port crates/horizon-client/src/game/debug_keys.rs::Game::debug_gizmos debug gizmos collected for the frame
|
||||
LevelTargetBundle.replace skip:other render-target bundle for post chains; Horizon has no frame graph
|
||||
LevelTargetBundle.get skip:other render-target bundle for post chains; Horizon has no frame graph
|
||||
Lightmap.render port crates/horizon-client/src/gfx.rs::Renderer::update_lightmap lightmap pass from sky, block, darkness, gamma, night vision, flicker and boss darkening
|
||||
Lightmap.render port crates/horizon-client-render/src/gfx.rs::Renderer::update_lightmap lightmap pass from sky, block, darkness, gamma, night vision, flicker and boss darkening
|
||||
LightmapRenderStateExtractor.calculateDarknessScale port crates/horizon-client/src/game/sculk.rs::Game::darkness_lighting pulsing darkness scale from the darkness blend and the option
|
||||
LightmapRenderStateExtractor.extract port crates/horizon-client/src/game.rs::Game::render lightmap inputs gathered into the frame; the end flash is added to sky_light_factor in end.rs::end_flash_sky
|
||||
MapRenderer.extractRenderState port crates/horizon-client/src/game/maps.rs::Game::map_vertices map texture and decorations gathered for drawing
|
||||
@@ -113,7 +113,7 @@ MapRenderer.extractDecorationRenderState port crates/horizon-client/src/game/map
|
||||
MappableRingBuffer.currentBuffer skip:other GPU ring buffer bookkeeping
|
||||
MappableRingBuffer.rotate skip:other GPU ring buffer bookkeeping
|
||||
MappableRingBuffer.close skip:other GPU buffer release
|
||||
MultiblockChestResources.select inline crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_one single, left and right chest sprite chosen by ChestPart
|
||||
MultiblockChestResources.select inline crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_one single, left and right chest sprite chosen by ChestPart
|
||||
MultiblockChestResources.map skip:java record mapping of the three sprites
|
||||
Octree.isClose missing unsure: vanilla flags sections next to the camera so the occlusion walk keeps them; not traced in Horizon's occlusion path
|
||||
Octree.AxisSorting.getAxisSorting skip:java traversal-order helper; Resident::visible sorts visible sections by distance
|
||||
@@ -121,14 +121,14 @@ Octree.Branch.<init> skip:other octree node; Horizon keeps sections in a reside
|
||||
Octree.Branch.add skip:other octree insert; Horizon keeps sections in a resident grid instead
|
||||
Octree.Branch.getNodeIndex skip:other octree child index; Horizon keeps sections in a resident grid instead
|
||||
Octree.Branch.createChildBoundingBox skip:other octree child bounds; Horizon keeps sections in a resident grid instead
|
||||
Octree.Branch.visitNodes port crates/horizon-client/src/gfx/resident.rs::Resident::visible frustum and distance visit of sections, nearest first
|
||||
Octree.Branch.visitNodes port crates/horizon-client-render/src/gfx/resident.rs::Resident::visible frustum and distance visit of sections, nearest first
|
||||
Octree.Branch.getAABB skip:java no callers in vanilla 26.3
|
||||
Octree.Leaf.visitNodes port crates/horizon-client/src/gfx/resident.rs::Resident::visible leaf sections visited in the same frustum and distance walk
|
||||
Octree.Leaf.visitNodes port crates/horizon-client-render/src/gfx/resident.rs::Resident::visible leaf sections visited in the same frustum and distance walk
|
||||
OrderedSubmitNodeCollector.submitModel skip:other submit queue of vanilla's render thread; Horizon draws each model in place
|
||||
OrderedSubmitNodeCollector.submitCrumblingOverlay port crates/horizon-client/src/gfx.rs::Renderer::draw_frame crumbling overlay drawn on the block being broken
|
||||
OrderedSubmitNodeCollector.submitCrumblingOverlay port crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame crumbling overlay drawn on the block being broken
|
||||
OrderedSubmitNodeCollector.submitModelPart skip:other submit queue of vanilla's render thread; Horizon draws each model part in place
|
||||
Panorama.extractRenderState port crates/horizon-client/src/gfx/panorama.rs::Rotation::update spin accumulated from the panorama speed option; overlay blit is drawn by the title screen
|
||||
PlayerSkinRenderCache.createLookup port crates/horizon-client/src/skins.rs::lookup skin texture chosen from the profile, falling back to the default skin
|
||||
Panorama.extractRenderState port crates/horizon-client-render/src/gfx/panorama.rs::Rotation::update spin accumulated from the panorama speed option; overlay blit is drawn by the title screen
|
||||
PlayerSkinRenderCache.createLookup port crates/horizon-client-render/src/skins.rs::lookup skin texture chosen from the profile, falling back to the default skin
|
||||
PlayerSkinRenderCache.RenderInfo.renderType port crates/horizon-client/src/game/item_draw.rs::Host::player_skin skin texture path a head is drawn with; no separate render type in Horizon
|
||||
PlayerSkinRenderCache.RenderInfo.textureView skip:other GPU texture view cache; Horizon binds skin textures by atlas name
|
||||
PlayerSkinRenderCache.RenderInfo.glyphRenderTypes skip:other font render types for name tags; Horizon's text uses its own glyph pipeline
|
||||
@@ -137,14 +137,14 @@ PlayerSkinRenderCache.RenderInfo.hashCode skip:java hash for a cache key
|
||||
PostChain.load missing minor: no data-driven post chain loader; only the blur and entity outline chains exist, hard-coded
|
||||
PostChain.getReferencedExternalTargets skip:other config validation for data-driven post chains
|
||||
PostChain.createPass missing minor: no data-driven post pass creation; the outline and blur passes are fixed
|
||||
PostChain.addToFrame port crates/horizon-client/src/gfx/entity_outline.rs::EntityOutline::draw gap: minor: runs the entity outline chain only; other chains are not built
|
||||
PostChain.process port crates/horizon-client/src/menu_blur.rs::MenuBlur::apply gap: minor: runs the menu blur only; other chains are not run
|
||||
PostChain.addToFrame port crates/horizon-client-render/src/gfx/entity_outline.rs::EntityOutline::draw gap: minor: runs the entity outline chain only; other chains are not built
|
||||
PostChain.process port crates/horizon-client-render/src/menu_blur.rs::MenuBlur::apply gap: minor: runs the menu blur only; other chains are not run
|
||||
PostChain.getOrCreatePersistentTarget skip:other GPU persistent target cache
|
||||
PostChain.close skip:other GPU resource teardown
|
||||
PostChain.TargetBundle.getOrThrow skip:other frame-graph target lookup; Horizon has no frame graph
|
||||
PostChainConfig.Pass.referencedTargets skip:data config accessor for post chain JSON
|
||||
PostPass.<init> skip:other GPU pipeline creation for a post pass
|
||||
PostPass.addToFrame port crates/horizon-client/src/gfx/entity_outline.rs::EntityOutline::draw gap: minor: the pass runs inside the entity outline draw only
|
||||
PostPass.addToFrame port crates/horizon-client-render/src/gfx/entity_outline.rs::EntityOutline::draw gap: minor: the pass runs inside the entity outline draw only
|
||||
PostPass.close skip:other GPU resource teardown
|
||||
PostPass.TargetInput.getHandle skip:other frame-graph input handle
|
||||
PostPass.TargetInput.texture skip:other frame-graph input texture view
|
||||
|
@@ -1,38 +1,38 @@
|
||||
Projection.setupPerspective inline crates/horizon-client/src/camera.rs::perspective gap: minor: the dirty-flag cache and the stored fov/near/far are not kept; the matrix is built from the arguments per call
|
||||
Projection.setupPerspective inline crates/horizon-client-render/src/camera.rs::perspective gap: minor: the dirty-flag cache and the stored fov/near/far are not kept; the matrix is built from the arguments per call
|
||||
Projection.setupOrtho skip:ui GUI draws in pixels through Horizon's own HUD pipeline; no ortho projection state of its own
|
||||
Projection.getMatrixVersion skip:java matrix version counter for the dirty-check cache
|
||||
ProjectionMatrixBuffer.getBuffer skip:other GPU buffer handle getter; Horizon owns its uniform buffers in gfx.rs
|
||||
ProjectionMatrixBuffer.writeBuffer port crates/horizon-client/src/gfx/upload.rs::Uploads::buffer gap: minor: the caller decides which uniform receives the projection
|
||||
ProjectionMatrixBuffer.writeBuffer port crates/horizon-client-render/src/gfx/upload.rs::Uploads::buffer gap: minor: the caller decides which uniform receives the projection
|
||||
Rect2i.intersect skip:ui GUI clip rectangle math; Horizon clips each draw in hud.rs (clip_since), no rectangle type
|
||||
Rect2i.contains skip:ui GUI hit test on a rectangle; Horizon's widgets hit-test in their own UI code
|
||||
RenderPipelines.register skip:data pipeline registry; Horizon creates its pipelines in gfx.rs
|
||||
RunningTrimmedMean.registerValueAndGetMean skip:debug frame-time smoothing helper; no vanilla caller in this source tree
|
||||
ScreenEffectRenderer.submit port crates/horizon-client/src/screen_effects.rs::ScreenEffects::build gap: minor: the item activation is not built here (see renderItemActivationAnimation)
|
||||
ScreenEffectRenderer.submit port crates/horizon-client-render/src/screen_effects.rs::ScreenEffects::build gap: minor: the item activation is not built here (see renderItemActivationAnimation)
|
||||
ScreenEffectRenderer.renderItemActivationAnimation missing player: when a totem of undying saves the player, the totem does not pop up in front of the screen; Horizon only plays the totem particles and sound (game/totem.rs totem_event)
|
||||
ScreenEffectRenderer.submitWater inline crates/horizon-client/src/screen_effects.rs::ScreenEffects::build underwater overlay branch (misc/underwater)
|
||||
ScreenEffectRenderer.submitFire inline crates/horizon-client/src/screen_effects.rs::ScreenEffects::build first-person fire overlay branch (block/fire_1, two quads)
|
||||
ScreenEffectRenderer.buildFireQuad inline crates/horizon-client/src/screen_effects.rs::ScreenEffects::build the two fire quads' corners and offsets
|
||||
ScreenEffectRenderer.buildSpriteQuad inline crates/horizon-client/src/screen_effects.rs::ScreenEffects::build hand_quad closure: sprite rect and uv for overlay quads
|
||||
ScreenEffectRenderer.buildQuad inline crates/horizon-client/src/screen_effects.rs::ScreenEffects::build hand_quad closure: corners, uv and colour of one overlay quad
|
||||
ScreenEffectRenderer.submitWater inline crates/horizon-client-render/src/screen_effects.rs::ScreenEffects::build underwater overlay branch (misc/underwater)
|
||||
ScreenEffectRenderer.submitFire inline crates/horizon-client-render/src/screen_effects.rs::ScreenEffects::build first-person fire overlay branch (block/fire_1, two quads)
|
||||
ScreenEffectRenderer.buildFireQuad inline crates/horizon-client-render/src/screen_effects.rs::ScreenEffects::build the two fire quads' corners and offsets
|
||||
ScreenEffectRenderer.buildSpriteQuad inline crates/horizon-client-render/src/screen_effects.rs::ScreenEffects::build hand_quad closure: sprite rect and uv for overlay quads
|
||||
ScreenEffectRenderer.buildQuad inline crates/horizon-client-render/src/screen_effects.rs::ScreenEffects::build hand_quad closure: corners, uv and colour of one overlay quad
|
||||
SectionBufferBuilderPool.allocate skip:java heap-sized pool of section builders; Horizon's mesher uses its own worker threads
|
||||
SectionOcclusionGraph.waitAndReset port crates/horizon-client/src/gfx/occlusion.rs::Occlusion::clear gap: minor: clear discards a running search instead of waiting for it
|
||||
SectionOcclusionGraph.waitAndReset port crates/horizon-client-render/src/gfx/occlusion.rs::Occlusion::clear gap: minor: clear discards a running search instead of waiting for it
|
||||
SectionOcclusionGraph.expectedChunks missing unsure: Horizon's occlusion graph keeps no list of sections waiting on chunk loads (streaming.rs has its own waiting set)
|
||||
SectionOcclusionGraph.invalidateIfNeeded port crates/horizon-client/src/gfx/occlusion.rs::Occlusion::update gap: minor: the search is keyed on camera section and radius only; fov and smart-cull changes do not restart it
|
||||
SectionOcclusionGraph.addSectionsInFrustum port crates/horizon-client/src/gfx/occlusion.rs::Occlusion::contains gap: minor: the frustum test is not here; the caller builds the visible list
|
||||
SectionOcclusionGraph.schedulePropagationFrom port crates/horizon-client/src/gfx/occlusion.rs::Occlusion::changed queues the changed section; propagation runs in propagate_changes
|
||||
SectionOcclusionGraph.scheduleFullUpdate port crates/horizon-client/src/gfx/occlusion.rs::Occlusion::start gap: minor: the full search goes to a worker and is collected later (collect), not to a background executor
|
||||
SectionOcclusionGraph.runPartialUpdate port crates/horizon-client/src/gfx/occlusion.rs::Occlusion::propagate_changes gap: minor: chunk-load waiting is not handled here (see expectedChunks)
|
||||
SectionOcclusionGraph.initializeQueueForFullUpdate port crates/horizon-client/src/gfx/occlusion.rs::Graph::seed_outside gap: minor: seeds the sections around a camera outside the world; the camera-inside seed is in Graph::search
|
||||
SectionOcclusionGraph.runUpdates port crates/horizon-client/src/gfx/occlusion.rs::Graph::propagate gap: minor: BFS over face directions; vanilla's distant-section ray check for smart cull is not checked here
|
||||
SectionOcclusionGraph.getRelativeFrom inline crates/horizon-client/src/gfx/occlusion.rs::Graph::propagate neighbour lookup inside the BFS
|
||||
SectionOcclusionGraph.updateEmptySections port crates/horizon-client/src/gfx/occlusion.rs::Occlusion::set gap: minor: empty sections are not kept in a separate set; their visibility arrives through set
|
||||
SectionOcclusionGraph.invalidateIfNeeded port crates/horizon-client-render/src/gfx/occlusion.rs::Occlusion::update gap: minor: the search is keyed on camera section and radius only; fov and smart-cull changes do not restart it
|
||||
SectionOcclusionGraph.addSectionsInFrustum port crates/horizon-client-render/src/gfx/occlusion.rs::Occlusion::contains gap: minor: the frustum test is not here; the caller builds the visible list
|
||||
SectionOcclusionGraph.schedulePropagationFrom port crates/horizon-client-render/src/gfx/occlusion.rs::Occlusion::changed queues the changed section; propagation runs in propagate_changes
|
||||
SectionOcclusionGraph.scheduleFullUpdate port crates/horizon-client-render/src/gfx/occlusion.rs::Occlusion::start gap: minor: the full search goes to a worker and is collected later (collect), not to a background executor
|
||||
SectionOcclusionGraph.runPartialUpdate port crates/horizon-client-render/src/gfx/occlusion.rs::Occlusion::propagate_changes gap: minor: chunk-load waiting is not handled here (see expectedChunks)
|
||||
SectionOcclusionGraph.initializeQueueForFullUpdate port crates/horizon-client-render/src/gfx/occlusion.rs::Graph::seed_outside gap: minor: seeds the sections around a camera outside the world; the camera-inside seed is in Graph::search
|
||||
SectionOcclusionGraph.runUpdates port crates/horizon-client-render/src/gfx/occlusion.rs::Graph::propagate gap: minor: BFS over face directions; vanilla's distant-section ray check for smart cull is not checked here
|
||||
SectionOcclusionGraph.getRelativeFrom inline crates/horizon-client-render/src/gfx/occlusion.rs::Graph::propagate neighbour lookup inside the BFS
|
||||
SectionOcclusionGraph.updateEmptySections port crates/horizon-client-render/src/gfx/occlusion.rs::Occlusion::set gap: minor: empty sections are not kept in a separate set; their visibility arrives through set
|
||||
SectionOcclusionGraph.updateLoadedChunks missing unsure: no loaded-chunk set for the occlusion graph found in gfx/occlusion.rs
|
||||
SectionOcclusionGraph.Node.setDirections inline crates/horizon-client/src/gfx/occlusion.rs::Graph::propagate face direction bits kept per section in directions[]
|
||||
SectionOcclusionGraph.Node.hasDirection inline crates/horizon-client/src/gfx/occlusion.rs::Graph::propagate face direction bit test inside the BFS
|
||||
SectionOcclusionGraph.Node.addSourceDirection inline crates/horizon-client/src/gfx/occlusion.rs::Graph::propagate entered-face bits kept per section in entered[]
|
||||
SectionOcclusionGraph.Node.hasSourceDirection inline crates/horizon-client/src/gfx/occlusion.rs::Graph::propagate entered-face bit test inside the BFS
|
||||
SectionOcclusionGraph.Node.setDirections inline crates/horizon-client-render/src/gfx/occlusion.rs::Graph::propagate face direction bits kept per section in directions[]
|
||||
SectionOcclusionGraph.Node.hasDirection inline crates/horizon-client-render/src/gfx/occlusion.rs::Graph::propagate face direction bit test inside the BFS
|
||||
SectionOcclusionGraph.Node.addSourceDirection inline crates/horizon-client-render/src/gfx/occlusion.rs::Graph::propagate entered-face bits kept per section in entered[]
|
||||
SectionOcclusionGraph.Node.hasSourceDirection inline crates/horizon-client-render/src/gfx/occlusion.rs::Graph::propagate entered-face bit test inside the BFS
|
||||
SectionOcclusionGraph.Node.equals skip:java node equality for vanilla's hash sets; Horizon indexes sections by position
|
||||
SectionOcclusionGraph.SectionToNodeMap.get port crates/horizon-client/src/gfx/occlusion.rs::Graph::index gap: minor: a per-section array index instead of a map
|
||||
SectionOcclusionGraph.SectionToNodeMap.get port crates/horizon-client-render/src/gfx/occlusion.rs::Graph::index gap: minor: a per-section array index instead of a map
|
||||
ShaderDefines.withOverrides skip:other merges GLSL #define sets; Horizon's WGSL uses override constants
|
||||
ShaderDefines.asSourceDirectives skip:other emits GLSL #define text; Horizon has no GLSL source
|
||||
ShaderDefines.isEmpty skip:other helper for the define set; see ShaderDefines
|
||||
@@ -51,22 +51,22 @@ ShaderManager.getPostChain missing minor: spectator entity post effects (creepe
|
||||
ShaderManager.PostChainCache.getOrLoadPostChain missing minor: cache for the spectator entity post effects Horizon lacks
|
||||
ShaderManager.PostChainCache.loadPostChain missing minor: loads the spectator entity post effects Horizon lacks
|
||||
ShaderManager.PostChainCache.close skip:other releases post chains; Horizon has none
|
||||
Sheets.chooseSprite port crates/horizon-client/src/block_entity_render.rs::classify gap: minor: the Christmas chest texture is picked outside this fn (block_entity_render.rs extended_christmas)
|
||||
SkyRenderer.<init> inline crates/horizon-client/src/gfx/pack_sky.rs::PackSky::new gap: minor: builds the disc, sunset fan, stars and void up front; sun and moon quads are built per frame in game.rs celestial_geometry
|
||||
SkyRenderer.extractRenderState port crates/horizon-client/src/environment.rs::Environment::sample gap: minor: the End flash state is read in game/end.rs (end_flash_sky)
|
||||
SkyRenderer.render port crates/horizon-client/src/gfx.rs::Renderer::draw_frame sky pass; the frame's sky and dark-disc flags come from Game::render
|
||||
SkyRenderer.renderSkyDisc port crates/horizon-client/src/gfx/pack_sky.rs::disc top sky disc fan, sky colour from PackSky::prepare
|
||||
SkyRenderer.renderDarkDisc inline crates/horizon-client/src/gfx.rs::Renderer::draw_frame dark disc pipeline (fs_dark_disc) drawn when frame.dark_disc is set
|
||||
Sheets.chooseSprite port crates/horizon-client-render/src/block_entity_render.rs::classify gap: minor: the Christmas chest texture is picked outside this fn (block_entity_render.rs extended_christmas)
|
||||
SkyRenderer.<init> inline crates/horizon-client-render/src/gfx/pack_sky.rs::PackSky::new gap: minor: builds the disc, sunset fan, stars and void up front; sun and moon quads are built per frame in game.rs celestial_geometry
|
||||
SkyRenderer.extractRenderState port crates/horizon-client-render/src/environment.rs::Environment::sample gap: minor: the End flash state is read in game/end.rs (end_flash_sky)
|
||||
SkyRenderer.render port crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame sky pass; the frame's sky and dark-disc flags come from Game::render
|
||||
SkyRenderer.renderSkyDisc port crates/horizon-client-render/src/gfx/pack_sky.rs::disc top sky disc fan, sky colour from PackSky::prepare
|
||||
SkyRenderer.renderDarkDisc inline crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame dark disc pipeline (fs_dark_disc) drawn when frame.dark_disc is set
|
||||
SkyRenderer.renderSun port crates/horizon-client/src/game.rs::Game::celestial_geometry sun quad 100 blocks out, half-size 30, faded by rain
|
||||
SkyRenderer.renderMoon port crates/horizon-client/src/game.rs::Game::celestial_geometry moon quad 100 blocks out, half-size 20, faded by rain
|
||||
SkyRenderer.applyCelestialBodyTransform inline crates/horizon-client/src/game.rs::Game::celestial_geometry the body frame: rotate Y -90, then X by the body's angle
|
||||
SkyRenderer.drawCelestialBody inline crates/horizon-client/src/gfx.rs::Renderer::draw_frame draws the body quads with the celestial pipeline
|
||||
SkyRenderer.renderSunriseAndSunset port crates/horizon-client/src/gfx/pack_sky.rs::PackSky::prepare sunset fan turned to the sun's horizon and its alpha uploaded as sunrise
|
||||
SkyRenderer.renderEndSky port crates/horizon-client/src/gfx/end_sky.rs::EndSky::draw End sky cube
|
||||
SkyRenderer.buildSunriseFan port crates/horizon-client/src/gfx/pack_sky.rs::PackSky::new gap: minor: a unit 16-segment fan; the 120 and -40 block shape is not built here
|
||||
SkyRenderer.drawCelestialBody inline crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame draws the body quads with the celestial pipeline
|
||||
SkyRenderer.renderSunriseAndSunset port crates/horizon-client-render/src/gfx/pack_sky.rs::PackSky::prepare sunset fan turned to the sun's horizon and its alpha uploaded as sunrise
|
||||
SkyRenderer.renderEndSky port crates/horizon-client-render/src/gfx/end_sky.rs::EndSky::draw End sky cube
|
||||
SkyRenderer.buildSunriseFan port crates/horizon-client-render/src/gfx/pack_sky.rs::PackSky::new gap: minor: a unit 16-segment fan; the 120 and -40 block shape is not built here
|
||||
SkyRenderer.buildCelestialQuad port crates/horizon-client/src/game.rs::Game::celestial_geometry sprite quad for the sun and moon (body closure)
|
||||
SkyRenderer.buildMoonPhases port crates/horizon-client/src/game.rs::Game::celestial_geometry gap: minor: picks the phase from day time (day_time / 24000), not from the environment's moon phase value
|
||||
SkyRenderer.buildSkyDisc port crates/horizon-client/src/gfx/pack_sky.rs::disc 16-block disc fan (top and bottom)
|
||||
SkyRenderer.buildSkyDisc port crates/horizon-client-render/src/gfx/pack_sky.rs::disc 16-block disc fan (top and bottom)
|
||||
SkyRenderer.close skip:other releases sky buffers; Rust drops the wgpu buffers
|
||||
SpriteCoordinateExpander.addVertex skip:other no callers in this vanilla tree; UV wrapper for sprite quads
|
||||
SpriteCoordinateExpander.setColor skip:other no callers in this vanilla tree; see addVertex
|
||||
@@ -75,18 +75,18 @@ SpriteMapper.apply skip:other atlas id plumbing; Horizon looks sprites up by na
|
||||
StagedVertexBuffer.appendDraw skip:other staging draw object; Horizon's arena allocates vertex ranges directly
|
||||
StagedVertexBuffer.getVertexBuilder skip:other builder for the next mesh; Horizon's mesher writes straight into the section mesh
|
||||
StagedVertexBuffer.finishLastVertexBuilder skip:other closes the last mesh builder; see getVertexBuilder
|
||||
StagedVertexBuffer.upload port crates/horizon-client/src/gfx/arena.rs::Arena::alloc gap: minor: vertex ranges come from the arena; the copy is Uploads::buffer
|
||||
StagedVertexBuffer.uploadDrawsToBuffers inline crates/horizon-client/src/gfx/upload.rs::Uploads::buffer copies the staged vertex data into the buffers
|
||||
StagedVertexBuffer.decodeSortingPoints port crates/horizon-client/src/gfx/translucency.rs::quad_center gap: minor: the centre of one quad from two corners; reading the vertex buffer itself is not here
|
||||
StagedVertexBuffer.upload port crates/horizon-client-render/src/gfx/arena.rs::Arena::alloc gap: minor: vertex ranges come from the arena; the copy is Uploads::buffer
|
||||
StagedVertexBuffer.uploadDrawsToBuffers inline crates/horizon-client-render/src/gfx/upload.rs::Uploads::buffer copies the staged vertex data into the buffers
|
||||
StagedVertexBuffer.decodeSortingPoints port crates/horizon-client-render/src/gfx/translucency.rs::quad_center gap: minor: the centre of one quad from two corners; reading the vertex buffer itself is not here
|
||||
StagedVertexBuffer.getExecuteInfo skip:other describes a draw for execution; Horizon issues draws in gfx.rs::Renderer::draw_sections
|
||||
StagedVertexBuffer.requestIndexCount port crates/horizon-client/src/gfx/arena.rs::Arena::indices gap: minor: the index buffer is sized when created, not requested per draw
|
||||
StagedVertexBuffer.endFrame port crates/horizon-client/src/gfx/arena.rs::Arena::trim gap: minor: releases unused pages; no fence-based recycling
|
||||
StagedVertexBuffer.requestIndexCount port crates/horizon-client-render/src/gfx/arena.rs::Arena::indices gap: minor: the index buffer is sized when created, not requested per draw
|
||||
StagedVertexBuffer.endFrame port crates/horizon-client-render/src/gfx/arena.rs::Arena::trim gap: minor: releases unused pages; no fence-based recycling
|
||||
StagedVertexBuffer.close skip:other releases buffers; Rust drops them
|
||||
StagedVertexBuffer.Draw.append skip:other accumulates mesh counts for one draw; Horizon counts vertices in the arena
|
||||
StagedVertexBuffer.ExecuteInfo.indexBuffer skip:other picks the index buffer for a draw; Horizon keeps its own index buffers
|
||||
StagedVertexBuffer.GpuBufferPool.tryRecycleBuffers port crates/horizon-client/src/gfx/arena.rs::FreeList::give_all gap: minor: freed ranges return to the free list at once; no fence wait
|
||||
StagedVertexBuffer.GpuBufferPool.acquire port crates/horizon-client/src/gfx/arena.rs::Arena::alloc gap: minor: allocates a range in an existing page instead of a new buffer per size
|
||||
StagedVertexBuffer.GpuBufferPool.takeBestAvailable port crates/horizon-client/src/gfx/arena.rs::FreeList::take gap: minor: first fit, where vanilla takes the smallest buffer that fits
|
||||
StagedVertexBuffer.GpuBufferPool.tryRecycleBuffers port crates/horizon-client-render/src/gfx/arena.rs::FreeList::give_all gap: minor: freed ranges return to the free list at once; no fence wait
|
||||
StagedVertexBuffer.GpuBufferPool.acquire port crates/horizon-client-render/src/gfx/arena.rs::Arena::alloc gap: minor: allocates a range in an existing page instead of a new buffer per size
|
||||
StagedVertexBuffer.GpuBufferPool.takeBestAvailable port crates/horizon-client-render/src/gfx/arena.rs::FreeList::take gap: minor: first fit, where vanilla takes the smallest buffer that fits
|
||||
StagedVertexBuffer.GpuBufferPool.endFrame skip:other fence bookkeeping for GPU reuse; Horizon's arena releases ranges directly
|
||||
StagedVertexBuffer.GpuBufferPool.close skip:other releases buffers; Rust drops them
|
||||
StagedVertexBuffer.GpuBufferPool.PendingRecycle.tryRecycle skip:other fence wait for GPU reuse; Horizon has no fence here
|
||||
@@ -103,10 +103,10 @@ SubmitNodeCollection.submitLeash port crates/horizon-client/src/game/leads.rs::G
|
||||
SubmitNodeCollection.submitModel port crates/horizon-client/src/game/creatures.rs::Game::mob_geometry mob models
|
||||
SubmitNodeCollection.submitCrumblingOverlay port crates/horizon-client/src/game.rs::Game::cracks block break cracks (destroy_stage sprites) over a block
|
||||
SubmitNodeCollection.submitMovingBlock port crates/horizon-client/src/game/moving_pistons.rs::Game::moving_piston_geometry moving piston blocks
|
||||
SubmitNodeCollection.submitBlockModel port crates/horizon-client/src/block_entity_render.rs::BlockEntityRenderer::render_block gap: minor: block models driven by block entities only
|
||||
SubmitNodeCollection.getOutlineRenderType inline crates/horizon-client/src/gfx.rs::Renderer::draw_outline one outline pass; the render type choice is folded into it
|
||||
SubmitNodeCollection.submitBlockModel port crates/horizon-client-render/src/block_entity_render.rs::BlockEntityRenderer::render_block gap: minor: block models driven by block entities only
|
||||
SubmitNodeCollection.getOutlineRenderType inline crates/horizon-client-render/src/gfx.rs::Renderer::draw_outline one outline pass; the render type choice is folded into it
|
||||
SubmitNodeCollection.submitBreakingBlockModel port crates/horizon-client/src/game.rs::Game::cracks gap: minor: drawn as cracks; the block model itself is not redrawn
|
||||
SubmitNodeCollection.submitShapeOutline port crates/horizon-client/src/gfx.rs::Renderer::draw_outline block shape outline (selection box)
|
||||
SubmitNodeCollection.submitShapeOutline port crates/horizon-client-render/src/gfx.rs::Renderer::draw_outline block shape outline (selection box)
|
||||
SubmitNodeCollection.submitItem port crates/horizon-client/src/game/item_draw.rs::Game::dropped_item_geometry dropped item entities
|
||||
SubmitNodeCollection.submitCustomGeometry port crates/horizon-client/src/mobs/guardian.rs::beam_geometry gap: minor: the guardian beam only; screen overlay quads are built in ScreenEffects::build
|
||||
SubmitNodeCollection.submitGizmoPrimitives skip:debug debug gizmo overlay
|
||||
@@ -120,31 +120,31 @@ SubmitNodeStorage.submitCrumblingOverlay skip:other submit wrapper; see SubmitN
|
||||
SubmitNodeStorage.submitBlockModel skip:other submit wrapper; see SubmitNodeCollection.submitBlockModel
|
||||
SubmitNodeStorage.submitShapeOutline skip:other submit wrapper; see SubmitNodeCollection.submitShapeOutline
|
||||
SubmitNodeStorage.submitItem skip:other submit wrapper; see SubmitNodeCollection.submitItem
|
||||
SubmitNodeStorage.drainPhases inline crates/horizon-client/src/gfx.rs::Renderer::draw_frame the pass order that draws each phase
|
||||
ViewArea.releaseAllBuffers port crates/horizon-client/src/gfx.rs::Renderer::clear_sections drops every section's buffers
|
||||
ViewArea.repositionCamera inline crates/horizon-client/src/gfx/occlusion.rs::Occlusion::update gap: minor: the occlusion graph restarts on a camera section change; sections are keyed by absolute position, nothing is re-centred
|
||||
WeatherEffectRenderer.renderWeather inline crates/horizon-client/src/gfx/weather.rs::Weather::draw draws the rain and snow column ranges
|
||||
WeatherEffectRenderer.uploadVertexBuffer port crates/horizon-client/src/gfx/weather.rs::Weather::prepare gap: minor: the buffer grows to the next power of two
|
||||
WeatherEffectRenderer.prepare port crates/horizon-client/src/weather.rs::column_quad gap: minor: the column list comes from rain_column and snow_column; this fn builds the quads
|
||||
WeatherEffectRenderer.render port crates/horizon-client/src/gfx/weather.rs::Weather::draw draws the rain and snow columns
|
||||
WeatherEffectRenderer.createSnowColumnInstance port crates/horizon-client/src/weather.rs::snow_column snow drift and brightened light
|
||||
WeatherEffectRenderer.prepareInstances port crates/horizon-client/src/weather.rs::column_quad gap: minor: one column per call; the loop over columns is in the caller
|
||||
WorldBorderRenderer.renderOit inline crates/horizon-client/src/gfx.rs::Renderer::draw_frame border wall draw when frame.border is set
|
||||
WorldBorderRenderer.prepareRenderPass inline crates/horizon-client/src/border_render.rs::BorderRenderer::transforms the uniforms (colour, offset, texture scroll) for the wall
|
||||
WorldBorderRenderer.shouldRebuildWorldBorderBuffer port crates/horizon-client/src/border_render.rs::BorderRenderer::prepare rebuilds when the border moved or was invalidated
|
||||
FluidRenderer.getHeight inline crates/horizon-client/src/mesher/fluid.rs::tesselate corner heights from own_height and the neighbour average
|
||||
ChunkSectionLayer.byTransparency inline crates/horizon-client/src/mesher.rs::Mesher::face a face goes to the translucent list when it is translucent
|
||||
ChunkSectionsToRender.renderGroup port crates/horizon-client/src/gfx.rs::Renderer::draw_sections gap: minor: draws the given page runs; the terrain uniforms are bound by the caller
|
||||
ChunkSectionsToRender.renderOit port crates/horizon-client/src/gfx.rs::Renderer::draw_sections gap: minor: the OIT pass set-up is not in this fn
|
||||
ChunkSectionsToRender.renderLayers inline crates/horizon-client/src/gfx.rs::Renderer::draw_sections per-layer loop over the page runs
|
||||
ChunkSectionsToRender.DrawIndirect.render port crates/horizon-client/src/gfx.rs::Renderer::draw_sections multi-draw indirect branch
|
||||
ChunkSectionsToRender.DrawSeparate.render port crates/horizon-client/src/gfx.rs::Renderer::draw_sections per-draw branch without multi-draw
|
||||
RenderRegionCache.createRegion inline crates/horizon-client/src/mesher.rs::borrow gap: minor: borrows the 3x3 chunk columns around the section; nothing is copied
|
||||
SubmitNodeStorage.drainPhases inline crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame the pass order that draws each phase
|
||||
ViewArea.releaseAllBuffers port crates/horizon-client-render/src/gfx.rs::Renderer::clear_sections drops every section's buffers
|
||||
ViewArea.repositionCamera inline crates/horizon-client-render/src/gfx/occlusion.rs::Occlusion::update gap: minor: the occlusion graph restarts on a camera section change; sections are keyed by absolute position, nothing is re-centred
|
||||
WeatherEffectRenderer.renderWeather inline crates/horizon-client-render/src/gfx/weather.rs::Weather::draw draws the rain and snow column ranges
|
||||
WeatherEffectRenderer.uploadVertexBuffer port crates/horizon-client-render/src/gfx/weather.rs::Weather::prepare gap: minor: the buffer grows to the next power of two
|
||||
WeatherEffectRenderer.prepare port crates/horizon-client-render/src/weather.rs::column_quad gap: minor: the column list comes from rain_column and snow_column; this fn builds the quads
|
||||
WeatherEffectRenderer.render port crates/horizon-client-render/src/gfx/weather.rs::Weather::draw draws the rain and snow columns
|
||||
WeatherEffectRenderer.createSnowColumnInstance port crates/horizon-client-render/src/weather.rs::snow_column snow drift and brightened light
|
||||
WeatherEffectRenderer.prepareInstances port crates/horizon-client-render/src/weather.rs::column_quad gap: minor: one column per call; the loop over columns is in the caller
|
||||
WorldBorderRenderer.renderOit inline crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame border wall draw when frame.border is set
|
||||
WorldBorderRenderer.prepareRenderPass inline crates/horizon-client-render/src/border_render.rs::BorderRenderer::transforms the uniforms (colour, offset, texture scroll) for the wall
|
||||
WorldBorderRenderer.shouldRebuildWorldBorderBuffer port crates/horizon-client-render/src/border_render.rs::BorderRenderer::prepare rebuilds when the border moved or was invalidated
|
||||
FluidRenderer.getHeight inline crates/horizon-client-render/src/mesher/fluid.rs::tesselate corner heights from own_height and the neighbour average
|
||||
ChunkSectionLayer.byTransparency inline crates/horizon-client-render/src/mesher.rs::Mesher::face a face goes to the translucent list when it is translucent
|
||||
ChunkSectionsToRender.renderGroup port crates/horizon-client-render/src/gfx.rs::Renderer::draw_sections gap: minor: draws the given page runs; the terrain uniforms are bound by the caller
|
||||
ChunkSectionsToRender.renderOit port crates/horizon-client-render/src/gfx.rs::Renderer::draw_sections gap: minor: the OIT pass set-up is not in this fn
|
||||
ChunkSectionsToRender.renderLayers inline crates/horizon-client-render/src/gfx.rs::Renderer::draw_sections per-layer loop over the page runs
|
||||
ChunkSectionsToRender.DrawIndirect.render port crates/horizon-client-render/src/gfx.rs::Renderer::draw_sections multi-draw indirect branch
|
||||
ChunkSectionsToRender.DrawSeparate.render port crates/horizon-client-render/src/gfx.rs::Renderer::draw_sections per-draw branch without multi-draw
|
||||
RenderRegionCache.createRegion inline crates/horizon-client-render/src/mesher.rs::borrow gap: minor: borrows the 3x3 chunk columns around the section; nothing is copied
|
||||
RenderRegionCache.getSectionDataCopy skip:java per-thread copy cache; Horizon borrows the chunks
|
||||
RenderSectionRegion.getBlockState inline crates/horizon-client/src/mesher.rs::Mesher::mesh block reads of the mesh loop
|
||||
RenderSectionRegion.getFluidState port crates/horizon-client/src/mesher/fluid.rs::FlowView::fluid fluid read used by the fluid mesher
|
||||
RenderSectionRegion.getBlockState inline crates/horizon-client-render/src/mesher.rs::Mesher::mesh block reads of the mesh loop
|
||||
RenderSectionRegion.getFluidState port crates/horizon-client-render/src/mesher/fluid.rs::FlowView::fluid fluid read used by the fluid mesher
|
||||
RenderSectionRegion.getBlockEntity skip:other block entities are drawn from the world's list, not from the section region
|
||||
RenderSectionRegion.getSection skip:java section copy objects; Horizon borrows chunks
|
||||
RenderSectionRegion.index inline crates/horizon-client/src/mesher.rs::Mesher::mesh gap: minor: Horizon indexes 3x3 chunk columns, the section is picked by sy
|
||||
SectionCompiler.compile port crates/horizon-client/src/mesher.rs::Mesher::mesh gap: minor: block-entity tiles are not built here; they are drawn by block_entity_render
|
||||
SectionCompiler.getOrBeginLayer inline crates/horizon-client/src/mesher.rs::Mesher::mesh per-layer builders created on first use
|
||||
RenderSectionRegion.index inline crates/horizon-client-render/src/mesher.rs::Mesher::mesh gap: minor: Horizon indexes 3x3 chunk columns, the section is picked by sy
|
||||
SectionCompiler.compile port crates/horizon-client-render/src/mesher.rs::Mesher::mesh gap: minor: block-entity tiles are not built here; they are drawn by block_entity_render
|
||||
SectionCompiler.getOrBeginLayer inline crates/horizon-client-render/src/mesher.rs::Mesher::mesh per-layer builders created on first use
|
||||
|
@@ -1,47 +1,47 @@
|
||||
SectionCopy.getBlockState inline crates/horizon-client/src/mesher.rs::Padded::new the copy is Padded's block array; no debug-world barrier or DebugLevelSource blocks
|
||||
SectionCopy.getBlockState inline crates/horizon-client-render/src/mesher.rs::Padded::new the copy is Padded's block array; no debug-world barrier or DebugLevelSource blocks
|
||||
SectionRenderDispatcher.<init> skip:other builds the GPU arena and staging for chunk upload; Horizon creates its arena and uploads in gfx.rs with the renderer
|
||||
SectionRenderDispatcher.runTask port crates/horizon-client/src/streaming.rs::Streaming::new the worker loop pops the nearest queued job and runs it
|
||||
SectionRenderDispatcher.getRenderSectionSlice port crates/horizon-client/src/gfx.rs::Renderer::draw_sections the per-layer vertex and index slices of a section are read when drawing
|
||||
SectionRenderDispatcher.uploadTerrainBuffersToGpu port crates/horizon-client/src/gfx/upload.rs::Uploads::finish closes the frame's staging belt after the terrain copies
|
||||
SectionRenderDispatcher.schedule port crates/horizon-client/src/streaming.rs::Streaming::submit the mesh job goes into the priority queue by distance
|
||||
SectionRenderDispatcher.dispose port crates/horizon-client/src/gfx.rs::Renderer::clear_sections frees every section's GPU buffers
|
||||
SectionRenderDispatcher.runTask port crates/horizon-client-render/src/streaming.rs::Streaming::new the worker loop pops the nearest queued job and runs it
|
||||
SectionRenderDispatcher.getRenderSectionSlice port crates/horizon-client-render/src/gfx.rs::Renderer::draw_sections the per-layer vertex and index slices of a section are read when drawing
|
||||
SectionRenderDispatcher.uploadTerrainBuffersToGpu port crates/horizon-client-render/src/gfx/upload.rs::Uploads::finish closes the frame's staging belt after the terrain copies
|
||||
SectionRenderDispatcher.schedule port crates/horizon-client-render/src/streaming.rs::Streaming::submit the mesh job goes into the priority queue by distance
|
||||
SectionRenderDispatcher.dispose port crates/horizon-client-render/src/gfx.rs::Renderer::clear_sections frees every section's GPU buffers
|
||||
SectionRenderDispatcher.getStats skip:debug F3 text: compile queue size and free buffers
|
||||
SectionRenderDispatcher.RenderSection.getVisibility missing player: newly meshed terrain appears at full strength; vanilla fades each section in over the chunkSectionFadeInTime option, and block entities wait for 0.3
|
||||
SectionRenderDispatcher.RenderSection.setSectionNode port crates/horizon-client/src/gfx.rs::section_instance the section's render origin; its 16-block bounds are derived from the origin
|
||||
SectionRenderDispatcher.RenderSection.reset port crates/horizon-client/src/gfx.rs::Renderer::release_section drops the section's mesh and GPU slices
|
||||
SectionRenderDispatcher.RenderSection.resortTransparency port crates/horizon-client/src/gfx/translucency.rs::SortState::update translucent quads re-sorted for the camera; gap: minor: the new order goes to the next draw, not a newly uploaded index buffer
|
||||
SectionRenderDispatcher.RenderSection.setSectionNode port crates/horizon-client-render/src/gfx.rs::section_instance the section's render origin; its 16-block bounds are derived from the origin
|
||||
SectionRenderDispatcher.RenderSection.reset port crates/horizon-client-render/src/gfx.rs::Renderer::release_section drops the section's mesh and GPU slices
|
||||
SectionRenderDispatcher.RenderSection.resortTransparency port crates/horizon-client-render/src/gfx/translucency.rs::SortState::update translucent quads re-sorted for the camera; gap: minor: the new order goes to the next draw, not a newly uploaded index buffer
|
||||
SectionRenderDispatcher.RenderSection.transparencyResortingScheduled skip:other Horizon sorts during the frame, so there is no pending resort task to report
|
||||
SectionRenderDispatcher.RenderSection.cancelTasks inline crates/horizon-client/src/streaming.rs::Streaming::update a finished job whose seq no longer matches is dropped on arrival; queued jobs are not removed
|
||||
SectionRenderDispatcher.RenderSection.createCompileTask port crates/horizon-client/src/streaming.rs::Streaming::submit the mesh job is queued; the recompile flag is not kept
|
||||
SectionRenderDispatcher.RenderSection.compileSync port crates/horizon-client/src/mesher.rs::Mesher::mesh meshes on the calling thread with no queue
|
||||
SectionRenderDispatcher.RenderSection.setSectionMesh port crates/horizon-client/src/gfx.rs::Renderer::upload stores the new mesh for the section and hands back the old one
|
||||
SectionRenderDispatcher.RenderSection.releaseSectionMesh port crates/horizon-client/src/gfx.rs::Renderer::release_mesh frees the old mesh's GPU slices
|
||||
SectionRenderDispatcher.RenderSection.createVertexSorting port crates/horizon-client/src/gfx/translucency.rs::Sorter::sort sort by squared distance from the camera to each quad centre
|
||||
SectionRenderDispatcher.RenderSection.checkSectionMesh inline crates/horizon-client/src/gfx.rs::Renderer::upload uploads are synchronous, so there is no wait for every layer to land before publishing
|
||||
SectionRenderDispatcher.RenderSection.addSectionBuffersToUberBuffer port crates/horizon-client/src/gfx.rs::Renderer::upload_mesh copies a layer's vertex and index bytes into arena pages
|
||||
SectionRenderDispatcher.RenderSection.CompileTask.doTask port crates/horizon-client/src/mesher.rs::Mesher::mesh meshes the section; the empty result and upload are handled in streaming and Renderer::upload
|
||||
SectionRenderDispatcher.RenderSection.ResortTransparencyTask.doTask port crates/horizon-client/src/gfx/translucency.rs::SortState::update re-sorts translucent quads when the camera's point of view changes
|
||||
SectionTaskDynamicQueue.poll port crates/horizon-client/src/streaming.rs::Queued::cmp nearest job first; gap: minor: no recompile quota, new and remesh jobs share one priority
|
||||
SectionRenderDispatcher.RenderSection.cancelTasks inline crates/horizon-client-render/src/streaming.rs::Streaming::update a finished job whose seq no longer matches is dropped on arrival; queued jobs are not removed
|
||||
SectionRenderDispatcher.RenderSection.createCompileTask port crates/horizon-client-render/src/streaming.rs::Streaming::submit the mesh job is queued; the recompile flag is not kept
|
||||
SectionRenderDispatcher.RenderSection.compileSync port crates/horizon-client-render/src/mesher.rs::Mesher::mesh meshes on the calling thread with no queue
|
||||
SectionRenderDispatcher.RenderSection.setSectionMesh port crates/horizon-client-render/src/gfx.rs::Renderer::upload stores the new mesh for the section and hands back the old one
|
||||
SectionRenderDispatcher.RenderSection.releaseSectionMesh port crates/horizon-client-render/src/gfx.rs::Renderer::release_mesh frees the old mesh's GPU slices
|
||||
SectionRenderDispatcher.RenderSection.createVertexSorting port crates/horizon-client-render/src/gfx/translucency.rs::Sorter::sort sort by squared distance from the camera to each quad centre
|
||||
SectionRenderDispatcher.RenderSection.checkSectionMesh inline crates/horizon-client-render/src/gfx.rs::Renderer::upload uploads are synchronous, so there is no wait for every layer to land before publishing
|
||||
SectionRenderDispatcher.RenderSection.addSectionBuffersToUberBuffer port crates/horizon-client-render/src/gfx.rs::Renderer::upload_mesh copies a layer's vertex and index bytes into arena pages
|
||||
SectionRenderDispatcher.RenderSection.CompileTask.doTask port crates/horizon-client-render/src/mesher.rs::Mesher::mesh meshes the section; the empty result and upload are handled in streaming and Renderer::upload
|
||||
SectionRenderDispatcher.RenderSection.ResortTransparencyTask.doTask port crates/horizon-client-render/src/gfx/translucency.rs::SortState::update re-sorts translucent quads when the camera's point of view changes
|
||||
SectionTaskDynamicQueue.poll port crates/horizon-client-render/src/streaming.rs::Queued::cmp nearest job first; gap: minor: no recompile quota, new and remesh jobs share one priority
|
||||
SectionTaskDynamicQueue.clear skip:other Horizon has no queue clear; stale results are dropped by seq in Streaming::update
|
||||
TranslucencyPointOfView.set inline crates/horizon-client/src/gfx/translucency.rs::SortState::update the point of view: camera block minus section, clamped to -1..1 per axis
|
||||
TranslucencyPointOfView.isAxisAligned inline crates/horizon-client/src/gfx/translucency.rs::SortState::update the axis-aligned test: a zero component in the point of view
|
||||
TranslucencyPointOfView.equals inline crates/horizon-client/src/gfx/translucency.rs::SortState::update compares the last point of view with the new one
|
||||
TranslucencyPointOfView.set inline crates/horizon-client-render/src/gfx/translucency.rs::SortState::update the point of view: camera block minus section, clamped to -1..1 per axis
|
||||
TranslucencyPointOfView.isAxisAligned inline crates/horizon-client-render/src/gfx/translucency.rs::SortState::update the axis-aligned test: a zero component in the point of view
|
||||
TranslucencyPointOfView.equals inline crates/horizon-client-render/src/gfx/translucency.rs::SortState::update compares the last point of view with the new one
|
||||
TranslucencyPointOfView.hashCode skip:java hash for a map key
|
||||
VisGraph.setOpaque inline crates/horizon-client/src/mesher.rs::visibility opaque cells are marked in the seen map before the flood fill
|
||||
VisGraph.getIndex inline crates/horizon-client/src/mesher.rs::visibility cell index y<<8 | z<<4 | x in the seen map
|
||||
VisGraph.floodFill inline crates/horizon-client/src/mesher.rs::visibility flood fill from each open cell; gap: minor: no shortcut for sections with fewer than 256 opaque cells, which vanilla marks fully visible
|
||||
VisGraph.addEdges inline crates/horizon-client/src/mesher.rs::visibility the faces of the section a cell touches
|
||||
VisGraph.getNeighborIndexAtFace inline crates/horizon-client/src/mesher.rs::visibility the neighbour index inside the section, -1 at its border
|
||||
VisibilitySet.add inline crates/horizon-client/src/mesher.rs::visibility sets the bit for each pair of faces an open region touches (bit a*6+b)
|
||||
VisibilitySet.set inline crates/horizon-client/src/mesher.rs::visibility sets both bits of a face pair
|
||||
VisibilitySet.visibilityBetween port crates/horizon-client/src/gfx/occlusion.rs::Graph::propagate the faces a section connects are read from its visibility bits when the search moves on
|
||||
VisGraph.setOpaque inline crates/horizon-client-render/src/mesher.rs::visibility opaque cells are marked in the seen map before the flood fill
|
||||
VisGraph.getIndex inline crates/horizon-client-render/src/mesher.rs::visibility cell index y<<8 | z<<4 | x in the seen map
|
||||
VisGraph.floodFill inline crates/horizon-client-render/src/mesher.rs::visibility flood fill from each open cell; gap: minor: no shortcut for sections with fewer than 256 opaque cells, which vanilla marks fully visible
|
||||
VisGraph.addEdges inline crates/horizon-client-render/src/mesher.rs::visibility the faces of the section a cell touches
|
||||
VisGraph.getNeighborIndexAtFace inline crates/horizon-client-render/src/mesher.rs::visibility the neighbour index inside the section, -1 at its border
|
||||
VisibilitySet.add inline crates/horizon-client-render/src/mesher.rs::visibility sets the bit for each pair of faces an open region touches (bit a*6+b)
|
||||
VisibilitySet.set inline crates/horizon-client-render/src/mesher.rs::visibility sets both bits of a face pair
|
||||
VisibilitySet.visibilityBetween port crates/horizon-client-render/src/gfx/occlusion.rs::Graph::propagate the faces a section connects are read from its visibility bits when the search moves on
|
||||
VisibilitySet.toString skip:debug debug text
|
||||
Frustum.set skip:java copies the frustum; Horizon passes the Frustum value
|
||||
Frustum.offset missing minor: particles are not culled against a frustum pulled back 3 blocks (ParticleEngine.extract); Horizon has no particle frustum test
|
||||
Frustum.offsetToFullyIncludeCameraCube port crates/horizon-client/src/gfx/occlusion.rs::Graph::search the occlusion search starts in the camera's own section, so its cube is always included; gap: minor: no frustum offset
|
||||
Frustum.calculateFrustum port crates/horizon-client/src/gfx.rs::frustum the six planes from projection times view
|
||||
Frustum.isVisible port crates/horizon-client/src/gfx.rs::Frustum::intersects box against the planes; gap: minor: infinite bounds are not special-cased
|
||||
Frustum.cubeInFrustum port crates/horizon-client/src/gfx.rs::Frustum::intersects box test on camera-relative bounds
|
||||
Frustum.offsetToFullyIncludeCameraCube port crates/horizon-client-render/src/gfx/occlusion.rs::Graph::search the occlusion search starts in the camera's own section, so its cube is always included; gap: minor: no frustum offset
|
||||
Frustum.calculateFrustum port crates/horizon-client-render/src/gfx.rs::frustum the six planes from projection times view
|
||||
Frustum.isVisible port crates/horizon-client-render/src/gfx.rs::Frustum::intersects box against the planes; gap: minor: infinite bounds are not special-cased
|
||||
Frustum.cubeInFrustum port crates/horizon-client-render/src/gfx.rs::Frustum::intersects box test on camera-relative bounds
|
||||
Frustum.pointInFrustum missing minor: only particle culling uses it (QuadParticleGroup), and Horizon has no particle frustum test
|
||||
Frustum.getFrustumPoints skip:debug the eight frustum corners, for debug drawing only
|
||||
Frustum.getNearPlaneBounds missing player: the block texture overlay drawn when your eye is inside a block (view-blocking block) is never shown; ScreenEffectRenderer's block_overlay is never set outside tests
|
||||
@@ -84,14 +84,14 @@ LevelExtractor.extractVisibleBlockEntities inline crates/horizon-client/src/game
|
||||
LevelExtractor.extractBlockDestroyAnimation port crates/horizon-client/src/game.rs::Game::destroy_block_progress keeps the crumbling progress per breaker for 400 ticks; gap: minor: no 32-block distance cut here
|
||||
LevelExtractor.extractGizmos port crates/horizon-client/src/game/debug_keys.rs::Game::debug_gizmos the frame's debug gizmos
|
||||
LevelExtractor.extractPlayerState inline crates/horizon-client/src/game.rs::Game::render player and hands read from the game state at draw time; gap: minor: the view-blocking block is missing, see Frustum.getNearPlaneBounds
|
||||
LevelExtractor.applyFrustum port crates/horizon-client/src/gfx/resident.rs::Resident::visible the frustum decides which sections are visible
|
||||
LevelExtractor.applyFrustum port crates/horizon-client-render/src/gfx/resident.rs::Resident::visible the frustum decides which sections are visible
|
||||
LevelExtractor.setLevel inline crates/horizon-client/src/game.rs::Game::all_sections_changed a new level rebuilds every loaded section (allChanged); the null-level branch is not separate
|
||||
LevelExtractor.blockChanged port crates/horizon-client/src/world.rs::World::set a block edit marks its 3x3x3 neighbourhood; gap: minor: the player-changed flag (update bit 8) is not kept
|
||||
LevelExtractor.setBlockDirty port crates/horizon-client/src/world.rs::World::set marks the 3x3x3 section neighbourhood of an edited block
|
||||
LevelExtractor.setBlocksDirty inline crates/horizon-client/src/world.rs::World::set each block of the range goes through the same 3x3x3 marking
|
||||
LevelExtractor.setSectionDirtyWithNeighbors port crates/horizon-client/src/streaming.rs::Streaming::light_updated the section and its neighbours remesh
|
||||
LevelExtractor.setSectionRangeDirty inline crates/horizon-client/src/streaming.rs::Streaming::light_updated section range marked in world.dirty_sections
|
||||
LevelExtractor.setSectionDirty port crates/horizon-client/src/world.rs::DirtySections::insert marks one section for remesh
|
||||
LevelExtractor.blockChanged port crates/horizon-client-render/src/world.rs::World::set a block edit marks its 3x3x3 neighbourhood; gap: minor: the player-changed flag (update bit 8) is not kept
|
||||
LevelExtractor.setBlockDirty port crates/horizon-client-render/src/world.rs::World::set marks the 3x3x3 section neighbourhood of an edited block
|
||||
LevelExtractor.setBlocksDirty inline crates/horizon-client-render/src/world.rs::World::set each block of the range goes through the same 3x3x3 marking
|
||||
LevelExtractor.setSectionDirtyWithNeighbors port crates/horizon-client-render/src/streaming.rs::Streaming::light_updated the section and its neighbours remesh
|
||||
LevelExtractor.setSectionRangeDirty inline crates/horizon-client-render/src/streaming.rs::Streaming::light_updated section range marked in world.dirty_sections
|
||||
LevelExtractor.setSectionDirty port crates/horizon-client-render/src/world.rs::DirtySections::insert marks one section for remesh
|
||||
LevelExtractor.countRenderedSections skip:debug F3 count of sections with renderable layers; Horizon reports Renderer::section_count
|
||||
LevelExtractor.sectionStatistics skip:debug F3 text
|
||||
LevelExtractor.entityStatistics skip:debug F3 text
|
||||
@@ -100,5 +100,5 @@ CustomFeatureRenderer.buildGroup skip:other feature pass plumbing; Horizon has
|
||||
FeatureRenderDispatcher.<init> skip:other feature dispatcher set-up; Horizon has no feature dispatcher
|
||||
FeatureRenderDispatcher.prepareFrame skip:other feature dispatcher; the frame's draw order is fixed in Renderer::draw_frame
|
||||
FeatureRenderDispatcher.prepareFrameWithContext skip:other feature dispatcher; the frame's draw order is fixed in Renderer::draw_frame
|
||||
FeatureRenderDispatcher.renderAllFeatures port crates/horizon-client/src/gfx.rs::Renderer::draw_frame draws the frame's passes; gap: minor: the order is fixed, not taken from a feature list
|
||||
FeatureRenderDispatcher.renderAllFeatures port crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame draws the frame's passes; gap: minor: the order is fixed, not taken from a feature list
|
||||
FeatureRenderDispatcher.PhaseSubmitGrouper skip:other groups feature submissions per phase for vanilla's dispatcher; Horizon submits in fixed passes
|
||||
|
@@ -27,27 +27,27 @@ RenderTypeFeatureRenderer.Group skip:other GPU render-type batching, as above
|
||||
ShadowFeatureRenderer.buildGroup port crates/horizon-client/src/shadows.rs::shadow loop over shadow submits
|
||||
ShadowFeatureRenderer.prepare port crates/horizon-client/src/shadows.rs::shadow shadow quads per piece, clamped by the block below
|
||||
ShadowFeatureRenderer.shadowVertex inline crates/horizon-client/src/shadows.rs::shadow vertex push folded into shadow
|
||||
ShapeOutlineFeatureRenderer.buildGroup port crates/horizon-client/src/gfx.rs::outline_vertices block selection outline edges as line vertices
|
||||
ShapeOutlineFeatureRenderer.buildGroup port crates/horizon-client-render/src/gfx.rs::outline_vertices block selection outline edges as line vertices
|
||||
TextFeatureRenderer.buildGroup skip:other dispatch over text and background submits; each kind is covered below or by Horizon's text draw
|
||||
TextFeatureRenderer.renderText missing minor: unsure: no outlined-text path found (font.rs, hud.rs, names.rs); the outline colour of a text submit is not drawn
|
||||
TextFeatureRenderer.GlyphRenderer.acceptRenderable skip:other glyph sink for the text pass; Horizon's font pass draws the glyphs
|
||||
FeatureRenderPhase.Output.acceptFeatureGroup skip:other phase plumbing: hands sorted groups to the renderer pass
|
||||
SimpleFeatureRenderPhase skip:other submit, sort, shuffle (debug only) and clear of feature submits; Horizon sorts its own draws
|
||||
SimpleFeatureRenderPhase.FeatureSubmits skip:other batching bookkeeping of feature submits
|
||||
TranslucentFeatureRenderPhase.submit inline crates/horizon-client/src/gfx/translucency.rs::Sorter::sort collects submits; distances are taken in sort
|
||||
TranslucentFeatureRenderPhase.sortInto port crates/horizon-client/src/gfx/translucency.rs::Sorter::sort back-to-front by distance, equal distances keep submit order
|
||||
TranslucentFeatureRenderPhase.sortIndices port crates/horizon-client/src/gfx/translucency.rs::Sorter::sort far to near, stable on ties
|
||||
TranslucentSubmit.computeDistanceToCameraSq inline crates/horizon-client/src/gfx/translucency.rs::Sorter::sort distance from the quad centre to the camera, computed inside sort
|
||||
TranslucentFeatureRenderPhase.submit inline crates/horizon-client-render/src/gfx/translucency.rs::Sorter::sort collects submits; distances are taken in sort
|
||||
TranslucentFeatureRenderPhase.sortInto port crates/horizon-client-render/src/gfx/translucency.rs::Sorter::sort back-to-front by distance, equal distances keep submit order
|
||||
TranslucentFeatureRenderPhase.sortIndices port crates/horizon-client-render/src/gfx/translucency.rs::Sorter::sort far to near, stable on ties
|
||||
TranslucentSubmit.computeDistanceToCameraSq inline crates/horizon-client-render/src/gfx/translucency.rs::Sorter::sort distance from the quad centre to the camera, computed inside sort
|
||||
FogRenderer.getBuffer skip:other selects the fog uniform slice; Horizon hands its fog values to its own shader per frame
|
||||
FogRenderer.updateBuffer skip:other writes the fog uniform buffer; Horizon's frame_fog passes FogData to its own shader
|
||||
AtmosphericFogEnvironment.getBaseColor port crates/horizon-client/src/fog.rs::setup_fog gap: minor: sky fog end uses constant 512 instead of the SKY_FOG_END_DISTANCE attribute
|
||||
AtmosphericFogEnvironment.applyWeatherDarken inline crates/horizon-client/src/fog.rs::setup_fog rain and thunder darkening folded into the atmospheric colour
|
||||
BlindnessFogEnvironment.setupFog port crates/horizon-client/src/fog.rs::setup_fog blindness distance and sky/cloud ends
|
||||
BlindnessFogEnvironment.getModifiedDarkness inline crates/horizon-client/src/fog.rs::setup_fog blindness darkness folded into setup_fog
|
||||
LavaFogEnvironment.setupFog port crates/horizon-client/src/fog.rs::setup_fog gap: player: spectator (-8 start, half render distance) and fire resistance (0 to 5) are not applied in lava
|
||||
AtmosphericFogEnvironment.getBaseColor port crates/horizon-client-render/src/fog.rs::setup_fog gap: minor: sky fog end uses constant 512 instead of the SKY_FOG_END_DISTANCE attribute
|
||||
AtmosphericFogEnvironment.applyWeatherDarken inline crates/horizon-client-render/src/fog.rs::setup_fog rain and thunder darkening folded into the atmospheric colour
|
||||
BlindnessFogEnvironment.setupFog port crates/horizon-client-render/src/fog.rs::setup_fog blindness distance and sky/cloud ends
|
||||
BlindnessFogEnvironment.getModifiedDarkness inline crates/horizon-client-render/src/fog.rs::setup_fog blindness darkness folded into setup_fog
|
||||
LavaFogEnvironment.setupFog port crates/horizon-client-render/src/fog.rs::setup_fog gap: player: spectator (-8 start, half render distance) and fire resistance (0 to 5) are not applied in lava
|
||||
MobEffectFogEnvironment.isApplicable inline crates/horizon-client/src/game/render_tick.rs::Game::frame_fog effect gate is where frame_fog builds the blindness and darkness inputs
|
||||
PowderedSnowFogEnvironment.setupFog port crates/horizon-client/src/fog.rs::setup_fog gap: player: spectator case (-8 start, half render distance) is not applied
|
||||
WaterFogEnvironment.setupFog port crates/horizon-client/src/fog.rs::setup_fog gap: minor: water fog start is fixed at -8; vanilla reads the WATER_FOG_START_DISTANCE attribute
|
||||
PowderedSnowFogEnvironment.setupFog port crates/horizon-client-render/src/fog.rs::setup_fog gap: player: spectator case (-8 start, half render distance) is not applied
|
||||
WaterFogEnvironment.setupFog port crates/horizon-client-render/src/fog.rs::setup_fog gap: minor: water fog start is fixed at -8; vanilla reads the WATER_FOG_START_DISTANCE attribute
|
||||
DrawableGizmoPrimitives.addPoint missing minor: debug points are not drawn; Gizmos has no point kind
|
||||
DrawableGizmoPrimitives.addLine port crates/horizon-client/src/gizmos.rs::Gizmos::line
|
||||
DrawableGizmoPrimitives.addTriangleFan missing minor: debug triangle fans are not drawn
|
||||
|
||||
|
@@ -9,15 +9,15 @@ OitRenderPassProvider.createDepthBoundsPass missing minor: opt-in Improved Tran
|
||||
OitRenderPassProvider.createTransmittancePass missing minor: opt-in Improved Transparency (off by default), no OIT pipelines or passes in Horizon
|
||||
OitRenderPassProvider.createAccumulatePass missing minor: opt-in Improved Transparency (off by default), no OIT pipelines or passes in Horizon
|
||||
PreparedRenderType.drawFromBufferOit missing minor: opt-in Improved Transparency (off by default), no OIT pipelines or passes in Horizon
|
||||
PreparedRenderType.draw inline crates/horizon-client/src/gfx.rs::Renderer::draw_frame gap: minor: the indexed draw runs in the gfx/ pass modules that draw_frame calls; RenderSetup scissor not seen
|
||||
PreparedRenderType.draw inline crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame gap: minor: the indexed draw runs in the gfx/ pass modules that draw_frame calls; RenderSetup scissor not seen
|
||||
RenderSetup.toString skip:debug
|
||||
RenderSetup.prepareTextures inline crates/horizon-client/src/gfx.rs::Renderer::draw_frame overlay and lightmap samplers are bound per pass in gfx/
|
||||
RenderSetup.prepareTextures inline crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame overlay and lightmap samplers are bound per pass in gfx/
|
||||
RenderSetup.RenderSetupBuilder.withTexture skip:java builder
|
||||
RenderSetup.RenderSetupBuilder.setOutline skip:java builder setter; outline behaviour is in gfx/entity_outline.rs
|
||||
RenderSetup.RenderSetupBuilder.createRenderSetup skip:java builder
|
||||
RenderType.calculateHasBlending skip:other blend flag from the pipeline colour targets; Horizon picks blend per pipeline in gfx.rs
|
||||
RenderType.prepare inline crates/horizon-client/src/gfx.rs::Renderer::draw_frame gap: minor: texture and overlay/lightmap binding folded into the frame passes
|
||||
RenderType.writeDynamicTransforms inline crates/horizon-client/src/gfx.rs::Renderer::draw_frame gap: minor: no per-render-type layering offset found (only perspective layering scale)
|
||||
RenderType.prepare inline crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame gap: minor: texture and overlay/lightmap binding folded into the frame passes
|
||||
RenderType.writeDynamicTransforms inline crates/horizon-client-render/src/gfx.rs::Renderer::draw_frame gap: minor: no per-render-type layering offset found (only perspective layering scale)
|
||||
RenderTypes.createMovingBlockSetup inline crates/horizon-client/src/game/moving_pistons.rs::Game::moving_piston_geometry block atlas and lightmap for moving blocks; gap: minor: crumbling and outline flags not checked here
|
||||
RenderTypes.breezeWind inline crates/horizon-client/src/mobs/render.rs::layers gap: player: the breeze wind layer does not scroll; the UV offset is not applied (render.rs comment)
|
||||
BannerSpecialRenderer.submit port crates/horizon-client/src/item_render/special.rs::submit Banner arm: flag, base and up to 16 pattern layers
|
||||
@@ -41,7 +41,7 @@ DecoratedPotSpecialRenderer.submit port crates/horizon-client/src/item_render/sp
|
||||
EndCubeSpecialRenderer.Unbaked.bake missing minor: no bundled item uses the end cube model (special.rs notes it), so no end portal or gateway effect in hand
|
||||
PlayerHeadSpecialRenderer.submit port crates/horizon-client/src/item_render/special.rs::submit PlayerHead arm
|
||||
PlayerHeadSpecialRenderer.getExtents port crates/horizon-client/src/item_render/special.rs::extents PlayerHead arm
|
||||
PlayerHeadSpecialRenderer.extractArgument port crates/horizon-client/src/skins.rs::head_skin profile to skin lookup
|
||||
PlayerHeadSpecialRenderer.extractArgument port crates/horizon-client-render/src/skins.rs::head_skin profile to skin lookup
|
||||
PlayerHeadSpecialRenderer.Unbaked.bake inline crates/horizon-client/src/item_render/special.rs::load_parts models are baked once from entity-models.json
|
||||
ShieldSpecialRenderer.getExtents port crates/horizon-client/src/item_render/special.rs::extents Shield arm
|
||||
ShieldSpecialRenderer.Unbaked.bake inline crates/horizon-client/src/item_render/special.rs::load_parts models are baked once from entity-models.json
|
||||
@@ -89,50 +89,50 @@ TiledBlitRenderState.buildVertices skip:ui GUI tiled draw geometry
|
||||
TiledBlitRenderState.getBounds skip:ui GUI draw bounds
|
||||
PictureInPictureRenderState.getBounds skip:ui GUI draw bounds
|
||||
LevelRenderState.reset skip:java per-frame state clear
|
||||
ParticlesRenderState.submit inline crates/horizon-client/src/particles.rs::ParticleEngine::render submits particle groups; draws in render
|
||||
ParticlesRenderState.submit inline crates/horizon-client-render/src/particles.rs::ParticleEngine::render submits particle groups; draws in render
|
||||
PlayerRenderState.reset skip:java per-frame state clear
|
||||
QuadParticleRenderState.add port crates/horizon-client/src/particles.rs::ParticleEngine::add quad particle queued per layer
|
||||
QuadParticleRenderState.buildLayer port crates/horizon-client/src/particles.rs::ParticleEngine::render per-layer quad build and draw
|
||||
QuadParticleRenderState.renderVertex inline crates/horizon-client/src/particles.rs::ParticleEngine::render vertex math inside the render rotated quad
|
||||
QuadParticleRenderState.Storage.add port crates/horizon-client/src/particles.rs::ParticleEngine::push store one quad particle
|
||||
QuadParticleRenderState.Storage.forEachParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::render iterates stored particles in render
|
||||
QuadParticleRenderState.add port crates/horizon-client-render/src/particles.rs::ParticleEngine::add quad particle queued per layer
|
||||
QuadParticleRenderState.buildLayer port crates/horizon-client-render/src/particles.rs::ParticleEngine::render per-layer quad build and draw
|
||||
QuadParticleRenderState.renderVertex inline crates/horizon-client-render/src/particles.rs::ParticleEngine::render vertex math inside the render rotated quad
|
||||
QuadParticleRenderState.Storage.add port crates/horizon-client-render/src/particles.rs::ParticleEngine::push store one quad particle
|
||||
QuadParticleRenderState.Storage.forEachParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::render iterates stored particles in render
|
||||
QuadParticleRenderState.Storage.grow skip:java array capacity growth
|
||||
WeatherRenderState.reset skip:java per-frame state clear
|
||||
AbstractTexture.releaseTextures skip:java GPU handle release; Rust drop
|
||||
AbstractTexture.getTexture skip:java accessor
|
||||
AbstractTexture.getTextureView skip:java accessor
|
||||
CubeMapTexture.loadContents port crates/horizon-client/src/gfx/panorama.rs::Faces::try_load six faces read, flipped and stacked
|
||||
CubeMapTexture.doLoad port crates/horizon-client/src/gfx/panorama.rs::Panorama::new cube texture creation and upload
|
||||
CubeMapTexture.loadContents port crates/horizon-client-render/src/gfx/panorama.rs::Faces::try_load six faces read, flipped and stacked
|
||||
CubeMapTexture.doLoad port crates/horizon-client-render/src/gfx/panorama.rs::Panorama::new cube texture creation and upload
|
||||
DynamicAtlasTree.insertInner skip:other dynamic atlas packing; Horizon uses texture-array layers, not a rectangle packer
|
||||
DynamicTexture.createTexture skip:other GPU texture creation for CPU pixels; Horizon has no DynamicTexture class
|
||||
DynamicTexture.upload port crates/horizon-client/src/gfx.rs::Renderer::update_lightmap gap: minor: only the lightmap upload goes through here
|
||||
DynamicTexture.upload port crates/horizon-client-render/src/gfx.rs::Renderer::update_lightmap gap: minor: only the lightmap upload goes through here
|
||||
DynamicTexture.dumpContents skip:debug dump debug action
|
||||
MipmappedTexture.setSampler port crates/horizon-client/src/screen_effects.rs::sampling gap: minor: only blur and clamp bits; filters fixed in gfx
|
||||
MipmappedTexture.doLoad port crates/horizon-client/src/mipmap.rs::generate mip chain generation
|
||||
MipmappedTexture.clampMipLevel port crates/horizon-client/src/sprites.rs::sprite_mipmap_limit mip level cap from texture size
|
||||
MissingTextureAtlasSprite.create port crates/horizon-client/src/sprites.rs::Sprites::insert generated magenta and black missing texture
|
||||
MipmappedTexture.setSampler port crates/horizon-client-render/src/screen_effects.rs::sampling gap: minor: only blur and clamp bits; filters fixed in gfx
|
||||
MipmappedTexture.doLoad port crates/horizon-client-render/src/mipmap.rs::generate mip chain generation
|
||||
MipmappedTexture.clampMipLevel port crates/horizon-client-render/src/sprites.rs::sprite_mipmap_limit mip level cap from texture size
|
||||
MissingTextureAtlasSprite.create port crates/horizon-client-render/src/sprites.rs::Sprites::insert generated magenta and black missing texture
|
||||
OverlayTexture.pack skip:other packs u and v into one int for vertex data; overlay.rs::living uses one code
|
||||
ReloadableTexture.apply inline crates/horizon-client/src/sprites.rs::Sprites::load gap: minor: sampler flags read from the texture section; upload happens later in gfx
|
||||
ReloadableTexture.doLoad inline crates/horizon-client/src/sprites.rs::Sprites::finish gap: minor: lays out layers; the GPU upload is in gfx
|
||||
SkinTextureDownloader.downloadAndRegisterSkin port crates/horizon-client/src/skins.rs::Downloader::state starts the skin download and decode
|
||||
SkinTextureDownloader.downloadSkin port crates/horizon-client/src/skins.rs::Downloader::state disk cache read or HTTP fetch with cache write, in the spawned thread
|
||||
SkinTextureDownloader.registerTextureInManager inline crates/horizon-client/src/skins.rs::Downloader::queue ready skin queued for the atlas upload
|
||||
SkinTextureDownloader.doNotchTransparencyHack inline crates/horizon-client/src/skins.rs::process_legacy_skin hat layer cleared when fully opaque
|
||||
SkinTextureDownloader.setNoAlpha inline crates/horizon-client/src/skins.rs::process_legacy_skin legacy skin regions made opaque
|
||||
SpriteContents.increaseMipLevel port crates/horizon-client/src/mipmap.rs::generate mip chain generation
|
||||
SpriteContents.createAnimatedTexture port crates/horizon-client/src/sprites.rs::Sprites::load animation frames from mcmeta
|
||||
SpriteContents.createAnimationState inline crates/horizon-client/src/sprites.rs::Sprites::finish animation frames laid out per layer
|
||||
ReloadableTexture.apply inline crates/horizon-client-render/src/sprites.rs::Sprites::load gap: minor: sampler flags read from the texture section; upload happens later in gfx
|
||||
ReloadableTexture.doLoad inline crates/horizon-client-render/src/sprites.rs::Sprites::finish gap: minor: lays out layers; the GPU upload is in gfx
|
||||
SkinTextureDownloader.downloadAndRegisterSkin port crates/horizon-client-render/src/skins.rs::Downloader::state starts the skin download and decode
|
||||
SkinTextureDownloader.downloadSkin port crates/horizon-client-render/src/skins.rs::Downloader::state disk cache read or HTTP fetch with cache write, in the spawned thread
|
||||
SkinTextureDownloader.registerTextureInManager inline crates/horizon-client-render/src/skins.rs::Downloader::queue ready skin queued for the atlas upload
|
||||
SkinTextureDownloader.doNotchTransparencyHack inline crates/horizon-client-render/src/skins.rs::process_legacy_skin hat layer cleared when fully opaque
|
||||
SkinTextureDownloader.setNoAlpha inline crates/horizon-client-render/src/skins.rs::process_legacy_skin legacy skin regions made opaque
|
||||
SpriteContents.increaseMipLevel port crates/horizon-client-render/src/mipmap.rs::generate mip chain generation
|
||||
SpriteContents.createAnimatedTexture port crates/horizon-client-render/src/sprites.rs::Sprites::load animation frames from mcmeta
|
||||
SpriteContents.createAnimationState inline crates/horizon-client-render/src/sprites.rs::Sprites::finish animation frames laid out per layer
|
||||
SpriteContents.getAdditionalMetadata skip:java generic metadata lookup
|
||||
SpriteContents.close skip:java image release; Rust drop
|
||||
SpriteContents.toString skip:debug
|
||||
SpriteContents.isTransparent inline crates/horizon-client/src/sprites.rs::Sprites::region_opaque gap: minor: single-texel alpha query is not kept; region_opaque tests alpha 255 over a UV box
|
||||
SpriteContents.uploadFirstFrame inline crates/horizon-client/src/sprites.rs::Sprites::finish first frame copied into the layer pixels
|
||||
SpriteContents.AnimatedTexture.createAnimationState inline crates/horizon-client/src/sprites.rs::Sprites::finish animation frames laid out per layer
|
||||
SpriteContents.isTransparent inline crates/horizon-client-render/src/sprites.rs::Sprites::region_opaque gap: minor: single-texel alpha query is not kept; region_opaque tests alpha 255 over a UV box
|
||||
SpriteContents.uploadFirstFrame inline crates/horizon-client-render/src/sprites.rs::Sprites::finish first frame copied into the layer pixels
|
||||
SpriteContents.AnimatedTexture.createAnimationState inline crates/horizon-client-render/src/sprites.rs::Sprites::finish animation frames laid out per layer
|
||||
SpriteContents.AnimationState.needsToDraw skip:java redraw skip optimisation
|
||||
SpriteContents.AnimationState.close skip:java frame texture release; Rust drop
|
||||
SpriteLoader.stitch port crates/horizon-client/src/sprites.rs::sprite_mipmap_limit gap: minor: atlas stitching replaced by one texture-array layer per sprite
|
||||
SpriteLoader.stitch port crates/horizon-client-render/src/sprites.rs::sprite_mipmap_limit gap: minor: atlas stitching replaced by one texture-array layer per sprite
|
||||
SpriteLoader.runSpriteSuppliers skip:java parallel sprite loading
|
||||
SpriteLoader.loadAndStitch inline crates/horizon-client/src/sprites.rs::Sprites::insert gap: minor: sprites are read on first use, not as one batch
|
||||
SpriteLoader.loadAndStitch inline crates/horizon-client-render/src/sprites.rs::Sprites::insert gap: minor: sprites are read on first use, not as one batch
|
||||
SpriteLoader.getStitchedSprites skip:other maps sprites to atlas rectangles; Horizon uses array layers
|
||||
Stitcher.registerSprite skip:other atlas rectangle packing; Horizon uses one texture-array layer per sprite
|
||||
Stitcher.stitch skip:other atlas rectangle packing; Horizon uses one texture-array layer per sprite
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 31 and column 151.
|
+128
-128
@@ -1,150 +1,150 @@
|
||||
TextureAtlas.createTexture inline crates/horizon-client/src/sprites.rs::Sprites::finish gap: minor: no GPU texture objects or mip views; array size and mip count are set in finish
|
||||
TextureAtlas.upload inline crates/horizon-client/src/sprites.rs::Sprites::finish gap: minor: no GPU upload or animated UBOs; sprites are inserted before finish
|
||||
TextureAtlas.uploadInitialContents inline crates/horizon-client/src/sprites.rs::Sprites::finish static frames copied into the array pixels
|
||||
TextureAtlas.createTexture inline crates/horizon-client-render/src/sprites.rs::Sprites::finish gap: minor: no GPU texture objects or mip views; array size and mip count are set in finish
|
||||
TextureAtlas.upload inline crates/horizon-client-render/src/sprites.rs::Sprites::finish gap: minor: no GPU upload or animated UBOs; sprites are inserted before finish
|
||||
TextureAtlas.uploadInitialContents inline crates/horizon-client-render/src/sprites.rs::Sprites::finish static frames copied into the array pixels
|
||||
TextureAtlas.dumpContents skip:debug DEBUG_DUMP_TEXTURE_ATLAS PNG dump
|
||||
TextureAtlas.dumpSpriteNames skip:debug DEBUG_DUMP_TEXTURE_ATLAS name list
|
||||
TextureAtlas.cycleAnimationFrames port crates/horizon-client/src/gfx/animate.rs::SpriteAnimator::animate advances every animated layer to the frame for the tick
|
||||
TextureAtlas.uploadAnimationFrames port crates/horizon-client/src/gfx/animate.rs::mix_level per mip level frame mix (drawToAtlas)
|
||||
TextureAtlas.getSprite port crates/horizon-client/src/sprites.rs::Sprites::insert gap: minor: loads a texture on first lookup; vanilla returns the missing sprite for names not in the atlas
|
||||
TextureAtlas.cycleAnimationFrames port crates/horizon-client-render/src/gfx/animate.rs::SpriteAnimator::animate advances every animated layer to the frame for the tick
|
||||
TextureAtlas.uploadAnimationFrames port crates/horizon-client-render/src/gfx/animate.rs::mix_level per mip level frame mix (drawToAtlas)
|
||||
TextureAtlas.getSprite port crates/horizon-client-render/src/sprites.rs::Sprites::insert gap: minor: loads a texture on first lookup; vanilla returns the missing sprite for names not in the atlas
|
||||
TextureAtlas.clearTextureData skip:java frees sprite and UBO memory; Rust ownership
|
||||
TextureAtlas.releaseTextures skip:java frees GPU mip views; Rust ownership
|
||||
TextureAtlasSprite.getV inline crates/horizon-client/src/particles.rs::ParticleEngine::render gap: minor: only the particle quad V lerp is here; other getV callers keep their own lerp
|
||||
TextureAtlasSprite.getV inline crates/horizon-client-render/src/particles.rs::ParticleEngine::render gap: minor: only the particle quad V lerp is here; other getV callers keep their own lerp
|
||||
TextureAtlasSprite.toString skip:debug debug string
|
||||
TextureAtlasSprite.uploadSpriteUbo skip:other GPU uniform block for the atlas blit; Horizon samples its layer array and SpriteAnimator computes frames, so no UBO is needed
|
||||
TextureContents.load port crates/horizon-client/src/sprites.rs::Sprites::load reads the PNG and the .mcmeta animation section
|
||||
TextureManager.registerAndLoad port crates/horizon-client/src/sprites.rs::Sprites::insert loads the texture into a layer and registers it by name
|
||||
TextureManager.loadContentsSafe inline crates/horizon-client/src/sprites.rs::Sprites::insert missing texture fallback when a texture does not load
|
||||
TextureManager.register inline crates/horizon-client/src/sprites.rs::Sprites::insert adds the layer under its name
|
||||
TextureContents.load port crates/horizon-client-render/src/sprites.rs::Sprites::load reads the PNG and the .mcmeta animation section
|
||||
TextureManager.registerAndLoad port crates/horizon-client-render/src/sprites.rs::Sprites::insert loads the texture into a layer and registers it by name
|
||||
TextureManager.loadContentsSafe inline crates/horizon-client-render/src/sprites.rs::Sprites::insert missing texture fallback when a texture does not load
|
||||
TextureManager.register inline crates/horizon-client-render/src/sprites.rs::Sprites::insert adds the layer under its name
|
||||
TextureManager.safeClose skip:java closes a texture object; Rust ownership
|
||||
TextureManager.getTexture port crates/horizon-client/src/sprites.rs::Sprites::insert loads on first use, as SimpleTexture does
|
||||
TextureManager.tick port crates/horizon-client/src/gfx/animate.rs::SpriteAnimator::animate gap: minor: animated layers advance in the per frame animate pass; there is no tickable texture list
|
||||
TextureManager.getTexture port crates/horizon-client-render/src/sprites.rs::Sprites::insert loads on first use, as SimpleTexture does
|
||||
TextureManager.tick port crates/horizon-client-render/src/gfx/animate.rs::SpriteAnimator::animate gap: minor: animated layers advance in the per frame animate pass; there is no tickable texture list
|
||||
TextureManager.release skip:java drops one texture object; Rust ownership
|
||||
TextureManager.close skip:java drops all texture objects; Rust ownership
|
||||
TextureManager.reload port crates/horizon-client/src/assets.rs::Assets::reload gap: minor: rebuilds every render asset synchronously; no pending futures
|
||||
TextureManager.reload port crates/horizon-client-render/src/assets.rs::Assets::reload gap: minor: rebuilds every render asset synchronously; no pending futures
|
||||
TextureManager.dumpAllSheets skip:debug debug dump of every atlas
|
||||
TextureManager.scheduleLoad skip:other asynchronous texture preparation; Horizon loads each texture on the calling thread in Sprites::insert
|
||||
SpriteSource.Output.add port crates/horizon-client/src/atlas_sources.rs::list records a sprite id and its origin; a later source with the same id replaces it
|
||||
SpriteSourceList.load port crates/horizon-client/src/atlas_sources.rs::atlases gap: minor: reads one resource root's atlases/*.json, not every pack's stacked definitions
|
||||
DirectoryLister.run port crates/horizon-client/src/atlas_sources.rs::list Directory arm: textures under source/ get the prefix
|
||||
SpriteSource.Output.add port crates/horizon-client-render/src/atlas_sources.rs::list records a sprite id and its origin; a later source with the same id replaces it
|
||||
SpriteSourceList.load port crates/horizon-client-render/src/atlas_sources.rs::atlases gap: minor: reads one resource root's atlases/*.json, not every pack's stacked definitions
|
||||
DirectoryLister.run port crates/horizon-client-render/src/atlas_sources.rs::list Directory arm: textures under source/ get the prefix
|
||||
LazyLoadedImage.get skip:java shared image cache; generate reads the source texture each time it builds a sprite
|
||||
LazyLoadedImage.release skip:java releases the shared image cache
|
||||
PalettedPermutations.loadPaletteEntryFromImage inline crates/horizon-client/src/atlas_sources.rs::generate palette images are loaded inside the Paletted arm of generate
|
||||
PalettedPermutations.PalettedSpriteSupplier.get port crates/horizon-client/src/atlas_sources.rs::generate Paletted arm: base colours mapped through the palette pair (apply_palette)
|
||||
SingleFile.run port crates/horizon-client/src/atlas_sources.rs::list Single arm: the sprite named by the resource, or its sprite id
|
||||
Unstitcher.run port crates/horizon-client/src/atlas_sources.rs::list Unstitch arm: one sprite per region of the texture
|
||||
Unstitcher.RegionInstance.get port crates/horizon-client/src/atlas_sources.rs::generate Region arm: the region cut out of the texture
|
||||
AshParticle.Provider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make Ash arm
|
||||
AttackSweepParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle SweepAttack uses the HugeExplosion behaviour: age, then sprite from age
|
||||
AttackSweepParticle.Provider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make SweepAttack arm: size from xAux
|
||||
BaseAshSmokeParticle.<init> port crates/horizon-client/src/particles.rs::ParticleEngine::make Smoke, Ash, WhiteAsh and DustPlume arms: motion, GrowIn size, lifetime
|
||||
BreakingItemParticle.CobwebProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make ItemCobweb arm; the layer comes from item_sprites (game/item_particles.rs)
|
||||
BreakingItemParticle.ItemParticleProvider.getSprite port crates/horizon-client/src/particles.rs::ParticleEngine::make Item arms pick a layer with the engine random; ItemCobweb, ItemSlime and ItemSnowball read item_sprites
|
||||
BreakingItemParticle.Provider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make Item arm
|
||||
BreakingItemParticle.SlimeProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make ItemSlime arm
|
||||
BreakingItemParticle.SnowballProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make ItemSnowball arm
|
||||
BreakingItemParticle.SulfurCubeProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make SulfurCubeGoo arm: the first sprite of its set
|
||||
BubbleColumnUpParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle BubbleColumnUp arm: removed once it leaves water
|
||||
BubbleColumnUpParticle.Provider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make BubbleColumnUp arm
|
||||
BubbleParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle Bubble arm: rises, drag 0.85, removed out of water
|
||||
BubbleParticle.Provider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make Bubble arm
|
||||
BubblePopParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle BubblePop arm
|
||||
BubblePopParticle.Provider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make BubblePop arm
|
||||
CampfireSmokeParticle.CosyProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make CampfireCosySmoke arm, alpha 0.9
|
||||
CampfireSmokeParticle.SignalProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make CampfireSignalSmoke arm, alpha 0.95
|
||||
CritParticle.<init> port crates/horizon-client/src/particles.rs::ParticleEngine::make Crit arm; the constructor tick runs on the next engine tick (pre_tick)
|
||||
CritParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle Crit arm: green and blue fade
|
||||
CritParticle.DamageIndicatorProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make DamageIndicator arm; lifetime 20 from after_constructor_tick
|
||||
CritParticle.MagicProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make EnchantedHit arm; red and green scaled by after_constructor_tick
|
||||
CritParticle.Provider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make Crit arm
|
||||
DragonBreathParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle DragonBreath arm: hit-ground flag and friction
|
||||
DragonBreathParticle.getQuadSize port crates/horizon-client/src/particles.rs::Particle::quad_size GrowIn size curve
|
||||
DragonBreathParticle.Provider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make DragonBreath arm; power through set_power
|
||||
DripParticle.getLightCoords port crates/horizon-client/src/particles.rs::Particle::light_coords Block15 for the glowing tears; World for the rest
|
||||
DripParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle Behaviour::Drip: pre move, gravity and move, post move, fluid check
|
||||
DripParticle.preMoveUpdate port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle Drip: lifetime countdown and removal
|
||||
DripParticle.CoolingDripHangParticle.preMoveUpdate port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle Drip CoolingHang stage: red, green and blue fade
|
||||
DripParticle.DripHangParticle.preMoveUpdate port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle Drip Hang stage: spawns the falling drip when the countdown ends
|
||||
DripParticle.DripHangParticle.postMoveUpdate port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle Drip Hang stage: velocity times 0.02
|
||||
DripParticle.DripstoneFallAndLandParticle.postMoveUpdate port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle FallAndLand stage with the dripstone land sound
|
||||
DripParticle.DripstoneLavaFallProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make FallingDripstoneLava arm
|
||||
DripParticle.DripstoneLavaHangProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make DrippingDripstoneLava arm
|
||||
DripParticle.DripstoneWaterFallProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make FallingDripstoneWater arm
|
||||
DripParticle.DripstoneWaterHangProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make DrippingDripstoneWater arm
|
||||
DripParticle.FallAndLandParticle.postMoveUpdate port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle FallAndLand stage: removed on the ground, spawns the land particle
|
||||
DripParticle.HoneyFallAndLandParticle.postMoveUpdate port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle FallAndLand for FallingHoney: beehive drip sound
|
||||
DripParticle.LavaFallProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make FallingLava arm
|
||||
DripParticle.LavaHangProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make DrippingLava arm
|
||||
DripParticle.LavaLandProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make LandingLava arm
|
||||
DripParticle.ObsidianTearFallProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make FallingObsidianTear arm
|
||||
DripParticle.ObsidianTearHangProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make DrippingObsidianTear arm
|
||||
DripParticle.ObsidianTearLandProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make LandingObsidianTear arm
|
||||
DripParticle.SporeBlossomFallProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make FallingSporeBlossom arm
|
||||
DripParticle.WaterFallProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make FallingWater arm
|
||||
DripParticle.WaterHangProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make DrippingWater arm
|
||||
DustColorTransitionParticle.randomizeColor port crates/horizon-client/src/particles.rs::ParticleEngine::make DustColorTransition arm: the randomize closure for from and to colours
|
||||
DustColorTransitionParticle.Provider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make DustColorTransition arm
|
||||
DustParticle.Provider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make Dust arm
|
||||
DustParticleBase.randomizeColor port crates/horizon-client/src/particles.rs::ParticleEngine::make Dust and DustColorTransition arms: the randomize closure
|
||||
DustParticleBase.getQuadSize port crates/horizon-client/src/particles.rs::Particle::quad_size GrowIn size curve
|
||||
DustPlumeParticle.Provider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make DustPlume arm, base 1.0
|
||||
PalettedPermutations.loadPaletteEntryFromImage inline crates/horizon-client-render/src/atlas_sources.rs::generate palette images are loaded inside the Paletted arm of generate
|
||||
PalettedPermutations.PalettedSpriteSupplier.get port crates/horizon-client-render/src/atlas_sources.rs::generate Paletted arm: base colours mapped through the palette pair (apply_palette)
|
||||
SingleFile.run port crates/horizon-client-render/src/atlas_sources.rs::list Single arm: the sprite named by the resource, or its sprite id
|
||||
Unstitcher.run port crates/horizon-client-render/src/atlas_sources.rs::list Unstitch arm: one sprite per region of the texture
|
||||
Unstitcher.RegionInstance.get port crates/horizon-client-render/src/atlas_sources.rs::generate Region arm: the region cut out of the texture
|
||||
AshParticle.Provider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make Ash arm
|
||||
AttackSweepParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle SweepAttack uses the HugeExplosion behaviour: age, then sprite from age
|
||||
AttackSweepParticle.Provider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make SweepAttack arm: size from xAux
|
||||
BaseAshSmokeParticle.<init> port crates/horizon-client-render/src/particles.rs::ParticleEngine::make Smoke, Ash, WhiteAsh and DustPlume arms: motion, GrowIn size, lifetime
|
||||
BreakingItemParticle.CobwebProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make ItemCobweb arm; the layer comes from item_sprites (game/item_particles.rs)
|
||||
BreakingItemParticle.ItemParticleProvider.getSprite port crates/horizon-client-render/src/particles.rs::ParticleEngine::make Item arms pick a layer with the engine random; ItemCobweb, ItemSlime and ItemSnowball read item_sprites
|
||||
BreakingItemParticle.Provider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make Item arm
|
||||
BreakingItemParticle.SlimeProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make ItemSlime arm
|
||||
BreakingItemParticle.SnowballProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make ItemSnowball arm
|
||||
BreakingItemParticle.SulfurCubeProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make SulfurCubeGoo arm: the first sprite of its set
|
||||
BubbleColumnUpParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle BubbleColumnUp arm: removed once it leaves water
|
||||
BubbleColumnUpParticle.Provider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make BubbleColumnUp arm
|
||||
BubbleParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Bubble arm: rises, drag 0.85, removed out of water
|
||||
BubbleParticle.Provider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make Bubble arm
|
||||
BubblePopParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle BubblePop arm
|
||||
BubblePopParticle.Provider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make BubblePop arm
|
||||
CampfireSmokeParticle.CosyProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make CampfireCosySmoke arm, alpha 0.9
|
||||
CampfireSmokeParticle.SignalProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make CampfireSignalSmoke arm, alpha 0.95
|
||||
CritParticle.<init> port crates/horizon-client-render/src/particles.rs::ParticleEngine::make Crit arm; the constructor tick runs on the next engine tick (pre_tick)
|
||||
CritParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Crit arm: green and blue fade
|
||||
CritParticle.DamageIndicatorProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make DamageIndicator arm; lifetime 20 from after_constructor_tick
|
||||
CritParticle.MagicProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make EnchantedHit arm; red and green scaled by after_constructor_tick
|
||||
CritParticle.Provider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make Crit arm
|
||||
DragonBreathParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle DragonBreath arm: hit-ground flag and friction
|
||||
DragonBreathParticle.getQuadSize port crates/horizon-client-render/src/particles.rs::Particle::quad_size GrowIn size curve
|
||||
DragonBreathParticle.Provider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make DragonBreath arm; power through set_power
|
||||
DripParticle.getLightCoords port crates/horizon-client-render/src/particles.rs::Particle::light_coords Block15 for the glowing tears; World for the rest
|
||||
DripParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Behaviour::Drip: pre move, gravity and move, post move, fluid check
|
||||
DripParticle.preMoveUpdate port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Drip: lifetime countdown and removal
|
||||
DripParticle.CoolingDripHangParticle.preMoveUpdate port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Drip CoolingHang stage: red, green and blue fade
|
||||
DripParticle.DripHangParticle.preMoveUpdate port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Drip Hang stage: spawns the falling drip when the countdown ends
|
||||
DripParticle.DripHangParticle.postMoveUpdate port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Drip Hang stage: velocity times 0.02
|
||||
DripParticle.DripstoneFallAndLandParticle.postMoveUpdate port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle FallAndLand stage with the dripstone land sound
|
||||
DripParticle.DripstoneLavaFallProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make FallingDripstoneLava arm
|
||||
DripParticle.DripstoneLavaHangProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make DrippingDripstoneLava arm
|
||||
DripParticle.DripstoneWaterFallProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make FallingDripstoneWater arm
|
||||
DripParticle.DripstoneWaterHangProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make DrippingDripstoneWater arm
|
||||
DripParticle.FallAndLandParticle.postMoveUpdate port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle FallAndLand stage: removed on the ground, spawns the land particle
|
||||
DripParticle.HoneyFallAndLandParticle.postMoveUpdate port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle FallAndLand for FallingHoney: beehive drip sound
|
||||
DripParticle.LavaFallProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make FallingLava arm
|
||||
DripParticle.LavaHangProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make DrippingLava arm
|
||||
DripParticle.LavaLandProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make LandingLava arm
|
||||
DripParticle.ObsidianTearFallProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make FallingObsidianTear arm
|
||||
DripParticle.ObsidianTearHangProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make DrippingObsidianTear arm
|
||||
DripParticle.ObsidianTearLandProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make LandingObsidianTear arm
|
||||
DripParticle.SporeBlossomFallProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make FallingSporeBlossom arm
|
||||
DripParticle.WaterFallProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make FallingWater arm
|
||||
DripParticle.WaterHangProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make DrippingWater arm
|
||||
DustColorTransitionParticle.randomizeColor port crates/horizon-client-render/src/particles.rs::ParticleEngine::make DustColorTransition arm: the randomize closure for from and to colours
|
||||
DustColorTransitionParticle.Provider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make DustColorTransition arm
|
||||
DustParticle.Provider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make Dust arm
|
||||
DustParticleBase.randomizeColor port crates/horizon-client-render/src/particles.rs::ParticleEngine::make Dust and DustColorTransition arms: the randomize closure
|
||||
DustParticleBase.getQuadSize port crates/horizon-client-render/src/particles.rs::Particle::quad_size GrowIn size curve
|
||||
DustPlumeParticle.Provider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make DustPlume arm, base 1.0
|
||||
ElderGuardianParticleGroup.extractRenderState port crates/horizon-client/src/game/mob_blocks.rs::Game::elder_apparition_geometry gap: minor: one apparition timer that a new curse resets, so two curses show one ghost
|
||||
ElderGuardianParticleGroup.ElderGuardianParticleRenderState.fromParticle port crates/horizon-client/src/game/mob_blocks.rs::Game::elder_apparition_geometry fade, camera pose and offsets
|
||||
ElderGuardianParticleGroup.State.submit port crates/horizon-client/src/game/mob_blocks.rs::Game::elder_apparition_geometry draws the guardian model
|
||||
EmissiveRisingParticle.getLightCoords port crates/horizon-client/src/particles.rs::Particle::light_coords Block15 for sculk soul only (the SculkSoul arm)
|
||||
EmissiveRisingParticle.EmissiveProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make SculkSoul arm of the Soul arms
|
||||
EmissiveRisingParticle.Provider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make Soul arm
|
||||
EndRodParticle.move port crates/horizon-client/src/particles.rs::Particle::move_by free_move: moves the box without collision
|
||||
EndRodParticle.Provider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make EndRod arm
|
||||
ExplodeParticle.<init> port crates/horizon-client/src/particles.rs::ParticleEngine::make Poof and Spit arms: puff motion, size and lifetime
|
||||
ExplodeParticle.Provider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make Poof arm
|
||||
FallingDustParticle.getQuadSize port crates/horizon-client/src/particles.rs::Particle::quad_size GrowIn size curve
|
||||
FallingDustParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle FallingDust arm: roll and gravity clamp
|
||||
FallingDustParticle.Provider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make gap: minor: the invisible-block skip and the tint lookup happen in animate_tick (Animate::Falling), not here
|
||||
FallingLeavesParticle.CherryProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make CherryLeaves arm
|
||||
FallingLeavesParticle.PaleOakProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make PaleOakLeaves arm
|
||||
FallingLeavesParticle.PoplarProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make RedPoplarLeaves, OrangePoplarLeaves and YellowPoplarLeaves arms
|
||||
FallingLeavesParticle.TintedLeavesProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make TintedLeaves arm with the option colour
|
||||
FallingParticle.<init> port crates/horizon-client/src/particles.rs::ParticleEngine::make Leaves arm: size, gravity, start velocity, flow and swirl
|
||||
FallingParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle Leaves arm: wind, spin and removal on the ground
|
||||
FireflyParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle Firefly arm: removed in a solid block, fade, random drift
|
||||
FireflyParticle.FireflyProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make Firefly arm
|
||||
FireworkParticles.FlashProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make Flash arm
|
||||
FireworkParticles.SparkProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make Firework arm, alpha 0.99
|
||||
FireworkParticles.Starter.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_starters launch sound, a burst every 2 ticks, flash, twinkle sound at the end
|
||||
FireworkParticles.Starter.isFarAwayFromCamera inline crates/horizon-client/src/particles.rs::ParticleEngine::fireworks gap: minor: the distance is taken when the rocket is launched, not on each tick
|
||||
FireworkParticles.Starter.createParticleBall port crates/horizon-client/src/particles.rs::ParticleEngine::spark_ball
|
||||
FireworkParticles.Starter.createParticleShape port crates/horizon-client/src/particles.rs::ParticleEngine::spark_shape
|
||||
FireworkParticles.Starter.createParticleBurst inline crates/horizon-client/src/particles.rs::ParticleEngine::tick_starters Burst shape arm of tick_starters
|
||||
FlameParticle.move port crates/horizon-client/src/particles.rs::Particle::move_by free_move: moves the box without collision
|
||||
FlameParticle.getQuadSize port crates/horizon-client/src/particles.rs::Particle::quad_size Flame size curve
|
||||
FlameParticle.getLightCoords port crates/horizon-client/src/particles.rs::Particle::light_coords AgeEmission light
|
||||
FlameParticle.Provider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make Flame arm (also copper and soul fire flame)
|
||||
FlameParticle.SmallFlameProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make SmallFlame arm, scaled 0.5
|
||||
FlyStraightTowardsParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle FlyStraight arm: position and colour lerp
|
||||
FlyStraightTowardsParticle.OminousSpawnProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make OminousSpawning arm
|
||||
FlyTowardsPositionParticle.<init> port crates/horizon-client/src/particles.rs::ParticleEngine::make Enchant, Nautilus and VaultConnection arms
|
||||
FlyTowardsPositionParticle.move port crates/horizon-client/src/particles.rs::Particle::move_by free_move: moves the box without collision
|
||||
FlyTowardsPositionParticle.getLightCoords port crates/horizon-client/src/particles.rs::Particle::light_coords AgeEmission4 for enchant and nautilus; Block15 for vault connection
|
||||
FlyTowardsPositionParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle FlyTowards arm
|
||||
FlyTowardsPositionParticle.extract port crates/horizon-client/src/particles.rs::ParticleEngine::render alpha from the lifetime curve at draw time
|
||||
FlyTowardsPositionParticle.EnchantProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make Enchant arm
|
||||
FlyTowardsPositionParticle.NautilusProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make Nautilus arm
|
||||
FlyTowardsPositionParticle.VaultConnectionProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make VaultConnection arm: block light, lifetime alpha, scale 1.5
|
||||
EmissiveRisingParticle.getLightCoords port crates/horizon-client-render/src/particles.rs::Particle::light_coords Block15 for sculk soul only (the SculkSoul arm)
|
||||
EmissiveRisingParticle.EmissiveProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make SculkSoul arm of the Soul arms
|
||||
EmissiveRisingParticle.Provider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make Soul arm
|
||||
EndRodParticle.move port crates/horizon-client-render/src/particles.rs::Particle::move_by free_move: moves the box without collision
|
||||
EndRodParticle.Provider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make EndRod arm
|
||||
ExplodeParticle.<init> port crates/horizon-client-render/src/particles.rs::ParticleEngine::make Poof and Spit arms: puff motion, size and lifetime
|
||||
ExplodeParticle.Provider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make Poof arm
|
||||
FallingDustParticle.getQuadSize port crates/horizon-client-render/src/particles.rs::Particle::quad_size GrowIn size curve
|
||||
FallingDustParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle FallingDust arm: roll and gravity clamp
|
||||
FallingDustParticle.Provider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make gap: minor: the invisible-block skip and the tint lookup happen in animate_tick (Animate::Falling), not here
|
||||
FallingLeavesParticle.CherryProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make CherryLeaves arm
|
||||
FallingLeavesParticle.PaleOakProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make PaleOakLeaves arm
|
||||
FallingLeavesParticle.PoplarProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make RedPoplarLeaves, OrangePoplarLeaves and YellowPoplarLeaves arms
|
||||
FallingLeavesParticle.TintedLeavesProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make TintedLeaves arm with the option colour
|
||||
FallingParticle.<init> port crates/horizon-client-render/src/particles.rs::ParticleEngine::make Leaves arm: size, gravity, start velocity, flow and swirl
|
||||
FallingParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Leaves arm: wind, spin and removal on the ground
|
||||
FireflyParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Firefly arm: removed in a solid block, fade, random drift
|
||||
FireflyParticle.FireflyProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make Firefly arm
|
||||
FireworkParticles.FlashProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make Flash arm
|
||||
FireworkParticles.SparkProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make Firework arm, alpha 0.99
|
||||
FireworkParticles.Starter.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_starters launch sound, a burst every 2 ticks, flash, twinkle sound at the end
|
||||
FireworkParticles.Starter.isFarAwayFromCamera inline crates/horizon-client-render/src/particles.rs::ParticleEngine::fireworks gap: minor: the distance is taken when the rocket is launched, not on each tick
|
||||
FireworkParticles.Starter.createParticleBall port crates/horizon-client-render/src/particles.rs::ParticleEngine::spark_ball
|
||||
FireworkParticles.Starter.createParticleShape port crates/horizon-client-render/src/particles.rs::ParticleEngine::spark_shape
|
||||
FireworkParticles.Starter.createParticleBurst inline crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_starters Burst shape arm of tick_starters
|
||||
FlameParticle.move port crates/horizon-client-render/src/particles.rs::Particle::move_by free_move: moves the box without collision
|
||||
FlameParticle.getQuadSize port crates/horizon-client-render/src/particles.rs::Particle::quad_size Flame size curve
|
||||
FlameParticle.getLightCoords port crates/horizon-client-render/src/particles.rs::Particle::light_coords AgeEmission light
|
||||
FlameParticle.Provider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make Flame arm (also copper and soul fire flame)
|
||||
FlameParticle.SmallFlameProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make SmallFlame arm, scaled 0.5
|
||||
FlyStraightTowardsParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle FlyStraight arm: position and colour lerp
|
||||
FlyStraightTowardsParticle.OminousSpawnProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make OminousSpawning arm
|
||||
FlyTowardsPositionParticle.<init> port crates/horizon-client-render/src/particles.rs::ParticleEngine::make Enchant, Nautilus and VaultConnection arms
|
||||
FlyTowardsPositionParticle.move port crates/horizon-client-render/src/particles.rs::Particle::move_by free_move: moves the box without collision
|
||||
FlyTowardsPositionParticle.getLightCoords port crates/horizon-client-render/src/particles.rs::Particle::light_coords AgeEmission4 for enchant and nautilus; Block15 for vault connection
|
||||
FlyTowardsPositionParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle FlyTowards arm
|
||||
FlyTowardsPositionParticle.extract port crates/horizon-client-render/src/particles.rs::ParticleEngine::render alpha from the lifetime curve at draw time
|
||||
FlyTowardsPositionParticle.EnchantProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make Enchant arm
|
||||
FlyTowardsPositionParticle.NautilusProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make Nautilus arm
|
||||
FlyTowardsPositionParticle.VaultConnectionProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make VaultConnection arm: block light, lifetime alpha, scale 1.5
|
||||
GeyserBaseParticle.Provider.createParticle missing player: sulfur geysers do not erupt here: no water column, base puff or eruption sound (only the potent sulfur block state exists), and the geyser particle types are not in ParticleType
|
||||
GeyserEruptionParticle.tick missing player: the water column of a geyser eruption is not drawn; no geyser particle types exist here
|
||||
GeyserEruptionParticle.Provider.createParticle missing player: no geyser eruption particle is made
|
||||
GeyserPlumeParticle.<init> missing player: no water plume is made when a sulfur geyser erupts
|
||||
GeyserPlumeParticle.tick missing player: the plume does not rise, spray or fall back
|
||||
GeyserPlumeParticle.Provider.createParticle missing player: no water plume is made when a sulfur geyser erupts
|
||||
GlowParticle.getLightCoords port crates/horizon-client/src/particles.rs::Particle::light_coords AgeEmission light (glow, wax, electric spark)
|
||||
GlowParticle.ElectricSparkProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make ElectricSpark arm
|
||||
GlowParticle.GlowSquidProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make Glow arm
|
||||
GlowParticle.WaxOffProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make WaxOff arm
|
||||
GlowParticle.WaxOnProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make WaxOn arm
|
||||
GustParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle HugeExplosion behaviour: age, then sprite from age
|
||||
GustParticle.SmallProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make SmallGust arm, scaled 0.15
|
||||
GustSeedParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle GustSeed arm: a gust every tick or two, then removal
|
||||
GustSeedParticle.Provider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make GustEmitterLarge and GustEmitterSmall arms: scale, lifetime, delay
|
||||
HeartParticle.getQuadSize port crates/horizon-client/src/particles.rs::Particle::quad_size GrowIn size curve
|
||||
HeartParticle.AngryVillagerProvider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make AngryVillager arm, raised 0.5; the default colour is already white
|
||||
HeartParticle.Provider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make Heart arm
|
||||
GlowParticle.getLightCoords port crates/horizon-client-render/src/particles.rs::Particle::light_coords AgeEmission light (glow, wax, electric spark)
|
||||
GlowParticle.ElectricSparkProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make ElectricSpark arm
|
||||
GlowParticle.GlowSquidProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make Glow arm
|
||||
GlowParticle.WaxOffProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make WaxOff arm
|
||||
GlowParticle.WaxOnProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make WaxOn arm
|
||||
GustParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle HugeExplosion behaviour: age, then sprite from age
|
||||
GustParticle.SmallProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make SmallGust arm, scaled 0.15
|
||||
GustSeedParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle GustSeed arm: a gust every tick or two, then removal
|
||||
GustSeedParticle.Provider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make GustEmitterLarge and GustEmitterSmall arms: scale, lifetime, delay
|
||||
HeartParticle.getQuadSize port crates/horizon-client-render/src/particles.rs::Particle::quad_size GrowIn size curve
|
||||
HeartParticle.AngryVillagerProvider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make AngryVillager arm, raised 0.5; the default colour is already white
|
||||
HeartParticle.Provider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make Heart arm
|
||||
|
+100
-100
@@ -1,131 +1,131 @@
|
||||
HugeExplosionParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle Behaviour::HugeExplosion: age, sprite from age, removed at lifetime
|
||||
HugeExplosionParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make Explosion arm (SonicBoom shares it): quad size from xAux, full bright
|
||||
HugeExplosionSeedParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle Behaviour::ExplosionSeed: six EXPLOSION particles per tick, removed at lifetime
|
||||
HugeExplosionParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Behaviour::HugeExplosion: age, sprite from age, removed at lifetime
|
||||
HugeExplosionParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Explosion arm (SonicBoom shares it): quad size from xAux, full bright
|
||||
HugeExplosionSeedParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Behaviour::ExplosionSeed: six EXPLOSION particles per tick, removed at lifetime
|
||||
ItemPickupParticle.tick port crates/horizon-client/src/game/pickups.rs::Game::tick_pickups life to LIFE_TIME (3) then removed; follows the collector
|
||||
ItemPickupParticle.saveOldPosition inline crates/horizon-client/src/game/pickups.rs::Game::tick_pickups target_old = target, done inside tick_pickups
|
||||
ItemPickupParticleGroup.extractRenderState port crates/horizon-client/src/game/pickups.rs::position gap: minor: returns the flight positions only; each kind is drawn by dropped_item_geometry (item), entity_geometry (orb) and mob_geometry (arrow)
|
||||
ItemPickupParticleGroup.State.submit port crates/horizon-client/src/game/item_draw.rs::Game::dropped_item_geometry gap: minor: draws the item kind only; orb and arrow pickups are drawn in entity_geometry and mob_geometry
|
||||
LargeSmokeParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make Smoke arm, LargeSmoke branch (scale 2.5)
|
||||
LavaParticle.getQuadSize port crates/horizon-client/src/particles.rs::Particle::quad_size Size::Lava: size * (1 - t^2)
|
||||
LavaParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle Behaviour::Lava: smoke puffs while the random draw beats age/lifetime
|
||||
LavaParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make Lava arm: gravity 0.75, friction 0.999, lifetime 16 / (rand*0.8+0.2)
|
||||
NoteParticle.<init> inline crates/horizon-client/src/particles.rs::ParticleEngine::make Note arm: colour from the pitch, friction 0.66, lifetime 6, size x1.5
|
||||
NoteParticle.getQuadSize port crates/horizon-client/src/particles.rs::Particle::quad_size Size::GrowIn over 32 ticks
|
||||
NoteParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make Note arm
|
||||
LargeSmokeParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Smoke arm, LargeSmoke branch (scale 2.5)
|
||||
LavaParticle.getQuadSize port crates/horizon-client-render/src/particles.rs::Particle::quad_size Size::Lava: size * (1 - t^2)
|
||||
LavaParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Behaviour::Lava: smoke puffs while the random draw beats age/lifetime
|
||||
LavaParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Lava arm: gravity 0.75, friction 0.999, lifetime 16 / (rand*0.8+0.2)
|
||||
NoteParticle.<init> inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Note arm: colour from the pitch, friction 0.66, lifetime 6, size x1.5
|
||||
NoteParticle.getQuadSize port crates/horizon-client-render/src/particles.rs::Particle::quad_size Size::GrowIn over 32 ticks
|
||||
NoteParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Note arm
|
||||
NoxiousGasCloudParticle.tick missing player: potent sulfur in vanilla 26.3 gives off drifting noxious gas clouds; Horizon has no noxious_gas particle type and no PotentSulfurBlockEntity client code, so no cloud appears
|
||||
NoxiousGasCloudParticle.pickRandomParticleSpawnPoint missing player: potent sulfur in vanilla 26.3 gives off drifting noxious gas clouds; Horizon has no noxious_gas particle type and no PotentSulfurBlockEntity client code, so no cloud appears
|
||||
NoxiousGasCloudParticle.spawnNoxiousGasParticle missing player: potent sulfur in vanilla 26.3 gives off drifting noxious gas clouds; Horizon has no noxious_gas particle type and no PotentSulfurBlockEntity client code, so no cloud appears
|
||||
NoxiousGasParticle.tick missing player: potent sulfur in vanilla 26.3 gives off drifting noxious gas clouds; Horizon has no noxious_gas particle type and no PotentSulfurBlockEntity client code, so no cloud appears; the fade-out is missing with it
|
||||
NoxiousGasParticle.Provider.createParticle missing player: potent sulfur in vanilla 26.3 gives off drifting noxious gas clouds; Horizon has no noxious_gas particle type and no PotentSulfurBlockEntity client code, so no cloud appears
|
||||
Particle.<init> port crates/horizon-client/src/particles.rs::Particle::base_at 4-arg constructor (box and lifetime draw); the 7-arg one is Particle::base_moving
|
||||
Particle.<init> port crates/horizon-client-render/src/particles.rs::Particle::base_at 4-arg constructor (box and lifetime draw); the 7-arg one is Particle::base_moving
|
||||
Particle.toString skip:debug
|
||||
Particle.setLocationFromBoundingbox port crates/horizon-client/src/particles.rs::Particle::set_location_from_bb
|
||||
Particle.setLocationFromBoundingbox port crates/horizon-client-render/src/particles.rs::Particle::set_location_from_bb
|
||||
Particle.LifetimeAlpha.isOpaque inline crates/horizon-client-core/src/particle_type.rs::ParticleType::translucent_layer the layer is a fixed per-type table; VaultConnection is the non-opaque case
|
||||
ParticleDescription.fromJson port crates/horizon-client/src/particles.rs::ParticleEngine::load_sprites reads the textures list from particles/*.json
|
||||
ParticleEngine.makeParticle port crates/horizon-client/src/particles.rs::ParticleEngine::make gap: minor: VibrationSignal options return None here; ParticleEngine::vibration creates them
|
||||
ParticleDescription.fromJson port crates/horizon-client-render/src/particles.rs::ParticleEngine::load_sprites reads the textures list from particles/*.json
|
||||
ParticleEngine.makeParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::make gap: minor: VibrationSignal options return None here; ParticleEngine::vibration creates them
|
||||
ParticleEngine.createParticleGroup skip:other no render-type groups: ParticleEngine::render draws every particle in one pass, and the ITEM_PICKUP group is tick_pickups
|
||||
ParticleEngine.countParticles skip:debug
|
||||
ParticleEngine.hasSpaceInParticleLimit missing minor: no per-type particle cap; vanilla limits live SPORE_BLOSSOM air particles to 1000
|
||||
ParticleEngine.clearParticles port crates/horizon-client/src/particles.rs::ParticleEngine::clear clears emitters, live and pending particles; no limit counts to clear
|
||||
ParticleGroup.tickParticles port crates/horizon-client/src/particles.rs::ParticleEngine::tick ticks live particles and drops the removed ones
|
||||
ParticleEngine.clearParticles port crates/horizon-client-render/src/particles.rs::ParticleEngine::clear clears emitters, live and pending particles; no limit counts to clear
|
||||
ParticleGroup.tickParticles port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick ticks live particles and drops the removed ones
|
||||
ParticleGroup.tickParticle skip:debug crash-report wrapper around particle.tick
|
||||
ParticleResources.register skip:data provider table; Horizon matches particle types inside ParticleEngine::make
|
||||
ParticleResources.reload port crates/horizon-client/src/particles.rs::ParticleEngine::reload gap: minor: vanilla clears live particles on a resource reload; this keeps them
|
||||
ParticleResources.loadParticleDescription port crates/horizon-client/src/particles.rs::ParticleEngine::load_sprites
|
||||
ParticleResources.reload port crates/horizon-client-render/src/particles.rs::ParticleEngine::reload gap: minor: vanilla clears live particles on a resource reload; this keeps them
|
||||
ParticleResources.loadParticleDescription port crates/horizon-client-render/src/particles.rs::ParticleEngine::load_sprites
|
||||
ParticleResources.MutableSpriteSet.get skip:other index form of SpriteSet.get: no vanilla caller; Horizon picks a random set sprite (sprite_from_set)
|
||||
PlayerCloudParticle.<init> inline crates/horizon-client/src/particles.rs::ParticleEngine::make Cloud arm: friction 0.96, lifetime from the random draw x2.5, GrowIn
|
||||
PlayerCloudParticle.getQuadSize port crates/horizon-client/src/particles.rs::Particle::quad_size Size::GrowIn
|
||||
PlayerCloudParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle Behaviour::Cloud: sinks to the local player's feet within 2 blocks
|
||||
PlayerCloudParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make Cloud arm
|
||||
PlayerCloudParticle.SneezeProvider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make Sneeze arm: tint 0.22/1.0/0.53, alpha 0.4
|
||||
PortalParticle.move port crates/horizon-client/src/particles.rs::Particle::move_by free_move path: moves the box without block collision
|
||||
PortalParticle.getQuadSize port crates/horizon-client/src/particles.rs::Particle::quad_size Size::Portal
|
||||
PortalParticle.getLightCoords port crates/horizon-client/src/particles.rs::Particle::light_coords Light::AgeEmission4: block light plus age^4 emission
|
||||
PortalParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle Behaviour::Portal: eases from the start point along the velocity
|
||||
PortalParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make Portal arm
|
||||
QuadParticleGroup.extractRenderState port crates/horizon-client/src/particles.rs::ParticleEngine::render gap: minor: no frustum point test; every live particle is drawn
|
||||
ReversePortalParticle.getQuadSize port crates/horizon-client/src/particles.rs::Particle::quad_size Size::ReversePortal
|
||||
ReversePortalParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle Behaviour::ReversePortal
|
||||
ReversePortalParticle.ReversePortalProvider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make ReversePortal branch of the Portal arm
|
||||
RisingParticle.<init> inline crates/horizon-client/src/particles.rs::ParticleEngine::make rising arm (Soul, SculkSoul, flames): velocity scaled by 0.01, position jitter 0.05
|
||||
SculkChargeParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make SculkCharge arm: roll from the option, lifetime nextInt(12)+8
|
||||
SculkChargePopParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make SculkChargePop arm: lifetime nextInt(4)+6
|
||||
ShriekParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle Behaviour::Shriek: waits delay ticks, then base tick
|
||||
ShriekParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make Shriek arm: size 0.85, lifetime 30, delay from the option
|
||||
SimpleAnimatedParticle.setFadeColor inline crates/horizon-client/src/particles.rs::ParticleEngine::make EndRod arm: fade colour 0xF2DEC9; applied by Behaviour::SimpleAnimated
|
||||
SimpleVerticalParticle.PauseMobGrowthProvider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make PauseMobGrowth arm
|
||||
SimpleVerticalParticle.ResetMobGrowthProvider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make ResetMobGrowth arm
|
||||
SingleQuadParticle.extract port crates/horizon-client/src/particles.rs::ParticleEngine::render roll lerp and camera-facing rotation per particle
|
||||
SingleQuadParticle.extractRotatedQuad port crates/horizon-client/src/particles.rs::ParticleEngine::render quad centre from the lerped position minus the camera
|
||||
PlayerCloudParticle.<init> inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Cloud arm: friction 0.96, lifetime from the random draw x2.5, GrowIn
|
||||
PlayerCloudParticle.getQuadSize port crates/horizon-client-render/src/particles.rs::Particle::quad_size Size::GrowIn
|
||||
PlayerCloudParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Behaviour::Cloud: sinks to the local player's feet within 2 blocks
|
||||
PlayerCloudParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Cloud arm
|
||||
PlayerCloudParticle.SneezeProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Sneeze arm: tint 0.22/1.0/0.53, alpha 0.4
|
||||
PortalParticle.move port crates/horizon-client-render/src/particles.rs::Particle::move_by free_move path: moves the box without block collision
|
||||
PortalParticle.getQuadSize port crates/horizon-client-render/src/particles.rs::Particle::quad_size Size::Portal
|
||||
PortalParticle.getLightCoords port crates/horizon-client-render/src/particles.rs::Particle::light_coords Light::AgeEmission4: block light plus age^4 emission
|
||||
PortalParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Behaviour::Portal: eases from the start point along the velocity
|
||||
PortalParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Portal arm
|
||||
QuadParticleGroup.extractRenderState port crates/horizon-client-render/src/particles.rs::ParticleEngine::render gap: minor: no frustum point test; every live particle is drawn
|
||||
ReversePortalParticle.getQuadSize port crates/horizon-client-render/src/particles.rs::Particle::quad_size Size::ReversePortal
|
||||
ReversePortalParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Behaviour::ReversePortal
|
||||
ReversePortalParticle.ReversePortalProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make ReversePortal branch of the Portal arm
|
||||
RisingParticle.<init> inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make rising arm (Soul, SculkSoul, flames): velocity scaled by 0.01, position jitter 0.05
|
||||
SculkChargeParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make SculkCharge arm: roll from the option, lifetime nextInt(12)+8
|
||||
SculkChargePopParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make SculkChargePop arm: lifetime nextInt(4)+6
|
||||
ShriekParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Behaviour::Shriek: waits delay ticks, then base tick
|
||||
ShriekParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Shriek arm: size 0.85, lifetime 30, delay from the option
|
||||
SimpleAnimatedParticle.setFadeColor inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make EndRod arm: fade colour 0xF2DEC9; applied by Behaviour::SimpleAnimated
|
||||
SimpleVerticalParticle.PauseMobGrowthProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make PauseMobGrowth arm
|
||||
SimpleVerticalParticle.ResetMobGrowthProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make ResetMobGrowth arm
|
||||
SingleQuadParticle.extract port crates/horizon-client-render/src/particles.rs::ParticleEngine::render roll lerp and camera-facing rotation per particle
|
||||
SingleQuadParticle.extractRotatedQuad port crates/horizon-client-render/src/particles.rs::ParticleEngine::render quad centre from the lerped position minus the camera
|
||||
SingleQuadParticle.toString skip:debug
|
||||
SingleQuadParticle.Layer.bySprite port crates/horizon-client/src/particles.rs::ParticleEngine::reload gap: minor: one translucent flag per sprite from the atlas; vanilla also splits block and item atlases (TRANSLUCENT_TERRAIN, TRANSLUCENT_ITEMS)
|
||||
SmokeParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make Smoke arm, Smoke branch (scale 1.0)
|
||||
SnowflakeParticle.<init> inline crates/horizon-client/src/particles.rs::ParticleEngine::make Poof/Snowflake/Spit arm, Snowflake branch: gravity 0.225, friction 1.0
|
||||
SnowflakeParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle Behaviour::Snowflake: base tick, sprite from age, drag 0.95/0.9/0.95
|
||||
SnowflakeParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make Snowflake branch: colour 0.923/0.964/0.999
|
||||
SonicBoomParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make Explosion|SonicBoom arm, SonicBoom branch: lifetime 16, size 1.5
|
||||
SpellParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle gap: player: the spyglass check is missing: while scoping in first person, vanilla makes spell particles within 3 blocks of the eye fully transparent; Horizon only lerps alpha
|
||||
SingleQuadParticle.Layer.bySprite port crates/horizon-client-render/src/particles.rs::ParticleEngine::reload gap: minor: one translucent flag per sprite from the atlas; vanilla also splits block and item atlases (TRANSLUCENT_TERRAIN, TRANSLUCENT_ITEMS)
|
||||
SmokeParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Smoke arm, Smoke branch (scale 1.0)
|
||||
SnowflakeParticle.<init> inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Poof/Snowflake/Spit arm, Snowflake branch: gravity 0.225, friction 1.0
|
||||
SnowflakeParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Behaviour::Snowflake: base tick, sprite from age, drag 0.95/0.9/0.95
|
||||
SnowflakeParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Snowflake branch: colour 0.923/0.964/0.999
|
||||
SonicBoomParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Explosion|SonicBoom arm, SonicBoom branch: lifetime 16, size 1.5
|
||||
SpellParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle gap: player: the spyglass check is missing: while scoping in first person, vanilla makes spell particles within 3 blocks of the eye fully transparent; Horizon only lerps alpha
|
||||
SpellParticle.isCloseToScopingPlayer missing player: spell particles near the eye stay visible while the player looks through a spyglass in first person; vanilla hides them
|
||||
SpellParticle.InstantProvider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make Effect|InstantEffect arm: colour and power from the option
|
||||
SpellParticle.MobEffectProvider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make EntityEffect arm: colour and alpha from the option
|
||||
SpellParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make uncoloured spell arm (Infested, TrialOmen, RaidOmen)
|
||||
SpellParticle.WitchProvider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make Witch arm: purple brightness from the engine random
|
||||
SpitParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make Poof/Snowflake/Spit arm, Spit branch: gravity 0.5, Animated
|
||||
SplashParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make Rain|Splash arm, Splash branch: gravity 0.04
|
||||
SquidInkParticle.GlowInkProvider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make GlowSquidInk arm: colour (0.2, 0.8, 0.6)
|
||||
SquidInkParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make SquidInk arm: black
|
||||
SulfurBubbleParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle Behaviour::SulfurBubble: still-water check, bounds, growth
|
||||
SulfurBubbleParticle.randomHorizontalWiggling inline crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle SulfurBubble arm, the wiggle closure applied to x and z
|
||||
SulfurBubbleParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make SulfurBubbles arm
|
||||
SuspendedParticle.<init> inline crates/horizon-client/src/particles.rs::ParticleEngine::make Underwater/CrimsonSpore/WarpedSpore/SporeBlossomAir arm: spawned 0.125 below the point
|
||||
SuspendedParticle.CrimsonSporeProvider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make CrimsonSpore arm: gaussian drift, colour 0.9/0.4/0.5
|
||||
SuspendedParticle.SporeBlossomAirProvider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make gap: minor: SporeBlossomAir arm; the SPORE_BLOSSOM particle limit (1000) is not applied
|
||||
SuspendedParticle.UnderwaterProvider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make Underwater arm: colour 0.4/0.4/0.7
|
||||
SuspendedParticle.WarpedSporeProvider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make WarpedSpore arm: colour 0.1/0.1/0.3, size 0.001
|
||||
SuspendedTownParticle.move port crates/horizon-client/src/particles.rs::Particle::move_by free_move path: moves the box without block collision
|
||||
SuspendedTownParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle Behaviour::SuspendedTown: lifetime countdown, drift, 0.99 damping
|
||||
SuspendedTownParticle.ComposterFillProvider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make Composter arm: white, lifetime 3 + nextInt(5)
|
||||
SuspendedTownParticle.DolphinSpeedProvider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make Dolphin arm: blue, alpha from the engine random, half lifetime
|
||||
SuspendedTownParticle.EggCrackProvider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make EggCrack arm: white
|
||||
SuspendedTownParticle.HappyVillagerProvider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make HappyVillager arm: white
|
||||
SuspendedTownParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make plain arm (Mycelium): no colour change
|
||||
TerrainParticle.<init> port crates/horizon-client/src/particles.rs::Particle::terrain crumb from a quarter of the block sprite, tinted, half size, mirrored UV
|
||||
TerrainParticle.createTerrainParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make gap: minor: the air, moving-piston and noTerrainParticles checks are not in this arm; the callers' gating was not checked
|
||||
TerrainParticle.CrumblingProvider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make BlockCrumble arm: zero speed, lifetime nextInt(10)+1
|
||||
TerrainParticle.DustPillarProvider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make DustPillar arm: gaussian speed, lifetime nextInt(20)+20
|
||||
TotemParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make TotemOfUndying arm
|
||||
TrackingEmitter.tick port crates/horizon-client/src/particles.rs::ParticleEngine::emit 16 tries per tick; the countdown and removal are in ParticleEngine::tick
|
||||
TrailParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle Behaviour::Trail: eases towards the target over its lifetime
|
||||
TrailParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make Trail arm: target, colour jitter, duration
|
||||
TrialSpawnerDetectionParticle.<init> inline crates/horizon-client/src/particles.rs::ParticleEngine::make TrialSpawnerDetection arm: gravity -0.1, Block15 light, GrowIn
|
||||
TrialSpawnerDetectionParticle.getQuadSize port crates/horizon-client/src/particles.rs::Particle::quad_size Size::GrowIn
|
||||
TrialSpawnerDetectionParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make TrialSpawnerDetection arm with scale 1.5
|
||||
VibrationSignalParticle.Provider.createParticle port crates/horizon-client/src/particles.rs::ParticleEngine::vibration flies to the listener in the arrival ticks
|
||||
WakeParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make Fishing arm (WakeParticle)
|
||||
WaterCurrentDownParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle Behaviour::CurrentDown: steers by angle, ends off water or on ground
|
||||
WaterCurrentDownParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make CurrentDown arm
|
||||
WaterDropParticle.tick port crates/horizon-client/src/particles.rs::ParticleEngine::tick_particle Behaviour::WaterDrop: gravity, drag, ends on ground or in fluid
|
||||
WaterDropParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make Rain arm
|
||||
WhiteAshParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make WhiteAsh branch of the Smoke arm; the provider's motion is drawn from the engine random
|
||||
WhiteSmokeParticle.Provider.createParticle inline crates/horizon-client/src/particles.rs::ParticleEngine::make Smoke arm, WhiteSmoke branch (scale 1.0)
|
||||
SpellParticle.InstantProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Effect|InstantEffect arm: colour and power from the option
|
||||
SpellParticle.MobEffectProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make EntityEffect arm: colour and alpha from the option
|
||||
SpellParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make uncoloured spell arm (Infested, TrialOmen, RaidOmen)
|
||||
SpellParticle.WitchProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Witch arm: purple brightness from the engine random
|
||||
SpitParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Poof/Snowflake/Spit arm, Spit branch: gravity 0.5, Animated
|
||||
SplashParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Rain|Splash arm, Splash branch: gravity 0.04
|
||||
SquidInkParticle.GlowInkProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make GlowSquidInk arm: colour (0.2, 0.8, 0.6)
|
||||
SquidInkParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make SquidInk arm: black
|
||||
SulfurBubbleParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Behaviour::SulfurBubble: still-water check, bounds, growth
|
||||
SulfurBubbleParticle.randomHorizontalWiggling inline crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle SulfurBubble arm, the wiggle closure applied to x and z
|
||||
SulfurBubbleParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make SulfurBubbles arm
|
||||
SuspendedParticle.<init> inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Underwater/CrimsonSpore/WarpedSpore/SporeBlossomAir arm: spawned 0.125 below the point
|
||||
SuspendedParticle.CrimsonSporeProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make CrimsonSpore arm: gaussian drift, colour 0.9/0.4/0.5
|
||||
SuspendedParticle.SporeBlossomAirProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make gap: minor: SporeBlossomAir arm; the SPORE_BLOSSOM particle limit (1000) is not applied
|
||||
SuspendedParticle.UnderwaterProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Underwater arm: colour 0.4/0.4/0.7
|
||||
SuspendedParticle.WarpedSporeProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make WarpedSpore arm: colour 0.1/0.1/0.3, size 0.001
|
||||
SuspendedTownParticle.move port crates/horizon-client-render/src/particles.rs::Particle::move_by free_move path: moves the box without block collision
|
||||
SuspendedTownParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Behaviour::SuspendedTown: lifetime countdown, drift, 0.99 damping
|
||||
SuspendedTownParticle.ComposterFillProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Composter arm: white, lifetime 3 + nextInt(5)
|
||||
SuspendedTownParticle.DolphinSpeedProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Dolphin arm: blue, alpha from the engine random, half lifetime
|
||||
SuspendedTownParticle.EggCrackProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make EggCrack arm: white
|
||||
SuspendedTownParticle.HappyVillagerProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make HappyVillager arm: white
|
||||
SuspendedTownParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make plain arm (Mycelium): no colour change
|
||||
TerrainParticle.<init> port crates/horizon-client-render/src/particles.rs::Particle::terrain crumb from a quarter of the block sprite, tinted, half size, mirrored UV
|
||||
TerrainParticle.createTerrainParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make gap: minor: the air, moving-piston and noTerrainParticles checks are not in this arm; the callers' gating was not checked
|
||||
TerrainParticle.CrumblingProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make BlockCrumble arm: zero speed, lifetime nextInt(10)+1
|
||||
TerrainParticle.DustPillarProvider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make DustPillar arm: gaussian speed, lifetime nextInt(20)+20
|
||||
TotemParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make TotemOfUndying arm
|
||||
TrackingEmitter.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::emit 16 tries per tick; the countdown and removal are in ParticleEngine::tick
|
||||
TrailParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Behaviour::Trail: eases towards the target over its lifetime
|
||||
TrailParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Trail arm: target, colour jitter, duration
|
||||
TrialSpawnerDetectionParticle.<init> inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make TrialSpawnerDetection arm: gravity -0.1, Block15 light, GrowIn
|
||||
TrialSpawnerDetectionParticle.getQuadSize port crates/horizon-client-render/src/particles.rs::Particle::quad_size Size::GrowIn
|
||||
TrialSpawnerDetectionParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make TrialSpawnerDetection arm with scale 1.5
|
||||
VibrationSignalParticle.Provider.createParticle port crates/horizon-client-render/src/particles.rs::ParticleEngine::vibration flies to the listener in the arrival ticks
|
||||
WakeParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Fishing arm (WakeParticle)
|
||||
WaterCurrentDownParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Behaviour::CurrentDown: steers by angle, ends off water or on ground
|
||||
WaterCurrentDownParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make CurrentDown arm
|
||||
WaterDropParticle.tick port crates/horizon-client-render/src/particles.rs::ParticleEngine::tick_particle Behaviour::WaterDrop: gravity, drag, ends on ground or in fluid
|
||||
WaterDropParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Rain arm
|
||||
WhiteAshParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make WhiteAsh branch of the Smoke arm; the provider's motion is drawn from the engine random
|
||||
WhiteSmokeParticle.Provider.createParticle inline crates/horizon-client-render/src/particles.rs::ParticleEngine::make Smoke arm, WhiteSmoke branch (scale 1.0)
|
||||
ClientPackSource.findExplodedAssetPacks skip:other IDE-only: looks for a resourcepacks folder next to the asset dir in development runs
|
||||
ClientPackSource.createVanillaPackSource skip:data pack source assembly; Horizon reads its assets from folders directly
|
||||
ClientPackSource.getPackTitle skip:data pack display names
|
||||
ClientPackSource.populatePackList skip:data pack discovery
|
||||
DefaultPlayerSkin.create port crates/horizon-client/src/tab_list.rs::default_skin gap: minor: returns the texture path (the model is in the path); no PlayerSkin record
|
||||
DryFoliageColorReloadListener.prepare port crates/horizon-client/src/biome.rs::colormap PNG decode of colormap/dry_foliage; the caller loads the three colormaps
|
||||
FoliageColorReloadListener.prepare port crates/horizon-client/src/biome.rs::colormap PNG decode of colormap/foliage
|
||||
GrassColorReloadListener.prepare port crates/horizon-client/src/biome.rs::colormap PNG decode of colormap/grass
|
||||
DefaultPlayerSkin.create port crates/horizon-client-render/src/tab_list.rs::default_skin gap: minor: returns the texture path (the model is in the path); no PlayerSkin record
|
||||
DryFoliageColorReloadListener.prepare port crates/horizon-client-render/src/biome.rs::colormap PNG decode of colormap/dry_foliage; the caller loads the three colormaps
|
||||
FoliageColorReloadListener.prepare port crates/horizon-client-render/src/biome.rs::colormap PNG decode of colormap/foliage
|
||||
GrassColorReloadListener.prepare port crates/horizon-client-render/src/biome.rs::colormap PNG decode of colormap/grass
|
||||
IndexedAssetSource.createIndexFs skip:data legacy objects/ asset index; Horizon reads assets/ folders directly
|
||||
LegacyStuffWrapper.getPixels port crates/horizon-client/src/hud.rs::decode gap: minor: PNG decode to bytes; vanilla returns ARGB ints
|
||||
LegacyStuffWrapper.getPixels port crates/horizon-client-render/src/hud.rs::decode gap: minor: PNG decode to bytes; vanilla returns ARGB ints
|
||||
MapTextureManager.resetData skip:other frees GPU map textures on close; Horizon keeps its map atlas slots in Game::maps, no gameplay effect
|
||||
MapTextureManager.getOrCreateMapInstance port crates/horizon-client/src/game/maps.rs::Game::map_texture gives the map its atlas slot, allocated on first use
|
||||
MapTextureManager.MapInstance.replaceMapData port crates/horizon-client/src/game/maps.rs::MapData::update_color gap: minor: vanilla also flags an upload when the saved-data object is swapped; Horizon flags per changed colour
|
||||
MapTextureManager.MapInstance.updateTextureIfNeeded port crates/horizon-client/src/game/maps.rs::Game::map_texture uploads the 128x128 colours when dirty
|
||||
SkinManager.get port crates/horizon-client/src/skins.rs::lookup gap: minor: the packed textures come from ResolvedProfile::textures; the debug default-skin override is not ported
|
||||
SkinManager.registerTextures port crates/horizon-client/src/skins.rs::lookup gap: minor: returns the skin location only; cape and elytra are downloaded here but not returned
|
||||
SkinManager.TextureCache.getOrLoad port crates/horizon-client/src/skins.rs::Downloader::state one download per URL, then its state
|
||||
SkinManager.TextureCache.getTextureLocation port crates/horizon-client/src/skins.rs::skin_location gap: minor: only the skins/ location; capes and elytra have no location fn
|
||||
SkinManager.get port crates/horizon-client-render/src/skins.rs::lookup gap: minor: the packed textures come from ResolvedProfile::textures; the debug default-skin override is not ported
|
||||
SkinManager.registerTextures port crates/horizon-client-render/src/skins.rs::lookup gap: minor: returns the skin location only; cape and elytra are downloaded here but not returned
|
||||
SkinManager.TextureCache.getOrLoad port crates/horizon-client-render/src/skins.rs::Downloader::state one download per URL, then its state
|
||||
SkinManager.TextureCache.getTextureLocation port crates/horizon-client-render/src/skins.rs::skin_location gap: minor: only the skins/ location; capes and elytra have no location fn
|
||||
SplashManager.getSplash port crates/horizon-client/src/title.rs::choose_splash gap: player: no '<NAME> IS YOU' splash; vanilla sometimes shows the logged-in player's name instead of a splash on the title screen
|
||||
WaypointStyle.validate port crates/horizon-client/src/locator_bar.rs::WaypointStyle::parse validation is folded into the parse
|
||||
WaypointStyleManager.apply port crates/horizon-client/src/locator_bar.rs::WaypointStyles::load gap: minor: loads the arrow and dot sprites from the assets folder into one map
|
||||
|
||||
|
@@ -3,35 +3,35 @@ EquipmentClientInfo.Layer.onlyIfDyed skip:data equipment asset factory; Horizon
|
||||
EquipmentClientInfo.Layer.getTextureLocation port crates/horizon-client/src/game/armor_stands.rs::ArmorStandAssets::load builds entity/equipment/<kind>/<texture>; gap: minor: only humanoid, humanoid_leggings and wings are read here
|
||||
EquipmentClientInfo.TrimOverride.validate skip:java codec validator; TrimOverride::list keeps an override with neither texture nor palette (minor)
|
||||
EquipmentClientInfo.TrimPredicate.validate skip:java codec validator; TrimOverride::list keeps a predicate with neither material nor pattern (minor)
|
||||
EquipmentClientInfo.TrimPredicate.matches port crates/horizon-client/src/trims.rs::TrimOverride::matches material and pattern id comparison
|
||||
ModelBakery.bakeModels port crates/horizon-client/src/models/catalog.rs::StateModels::from_facts_with_models bakes block states; gap: minor: items and standalone models bake in separate fns (item_bake.rs::bake_item_model)
|
||||
EquipmentClientInfo.TrimPredicate.matches port crates/horizon-client-render/src/trims.rs::TrimOverride::matches material and pattern id comparison
|
||||
ModelBakery.bakeModels port crates/horizon-client-render/src/models/catalog.rs::StateModels::from_facts_with_models bakes block states; gap: minor: items and standalone models bake in separate fns (item_bake.rs::bake_item_model)
|
||||
ModelBakery.ModelBakerImpl.getModel port crates/horizon-assets/src/resolve.rs::ModelLibrary::resolve an unknown id falls back to the missing model
|
||||
ModelDiscovery.createAndQueueWrapper inline crates/horizon-client/src/model_library.rs::ParsedModels::block_models parent queue; no wrapper object is kept
|
||||
ModelDiscovery.createAndQueueWrapper inline crates/horizon-client-render/src/model_library.rs::ParsedModels::block_models parent queue; no wrapper object is kept
|
||||
ModelDiscovery.addSpecialModel inline crates/horizon-assets/src/resolve.rs::ModelLibrary::with_shared_item_generator gap: minor: only builtin/generated is registered; a non-root special model is not rejected
|
||||
ModelDiscovery.resolve port crates/horizon-assets/src/resolve.rs::ModelLibrary::resolve resolved per model id, not as a batch map
|
||||
ModelDiscovery.discoverDependencies inline crates/horizon-client/src/model_library.rs::ParsedModels::block_models pushes each model's parent until the set is complete
|
||||
ModelDiscovery.discoverDependencies inline crates/horizon-client-render/src/model_library.rs::ParsedModels::block_models pushes each model's parent until the set is complete
|
||||
ModelDiscovery.propagateValidity port crates/horizon-assets/src/resolve.rs::ModelLibrary::resolve a chain that reaches a cycle is an error when resolved
|
||||
ModelDiscovery.ModelWrapper.updateSlot skip:java per-model result cache
|
||||
ModelDiscovery.ModelWrapper.getSimpleProperty skip:java per-model result cache
|
||||
ModelDiscovery.ModelWrapper.resolveParticleMaterial port crates/horizon-client/src/models/catalog.rs::StateModels::from_facts_with_models block particle sprite; the item side is item_bake.rs::bake_item_model
|
||||
ModelDiscovery.ModelWrapper.resolveParticleMaterial port crates/horizon-client-render/src/models/catalog.rs::StateModels::from_facts_with_models block particle sprite; the item side is item_bake.rs::bake_item_model
|
||||
ModelDiscovery.ModelWrapper.bakeDefaultState skip:java cache of the identity-rotation geometry
|
||||
ModelDiscovery.ModelWrapper.bakeTopGeometry port crates/horizon-assets/src/bake.rs::bake_block_model bakes with the blockstate rotation; per-state cache skipped
|
||||
ModelGroupCollector.build skip:java groups states for shared baked geometry; Horizon has no model-group ids
|
||||
ModelGroupCollector.GroupKey.create skip:java group key of build
|
||||
ModelGroupCollector.GroupKey.getColoringValues skip:java group key of build
|
||||
ModelManager.reload port crates/horizon-client/src/assets.rs::RenderAssets::load one synchronous load: models, block states, item infos, then bake and store
|
||||
ModelManager.loadBlockModels port crates/horizon-client/src/model_library.rs::ParsedModels::load every models/**.json by id, parsed in parallel
|
||||
ModelManager.discoverModelDependencies port crates/horizon-client/src/model_library.rs::ParsedModels::block_models referenced models and their parents
|
||||
ModelManager.loadModels port crates/horizon-client/src/models/catalog.rs::StateModels::from_facts_with_models bakes block states and their models
|
||||
ModelManager.createBlockStateToModelDispatch inline crates/horizon-client/src/models/catalog.rs::StateModels::from_facts_with_models missing model for a state without a baked model
|
||||
ModelManager.reload port crates/horizon-client-render/src/assets.rs::RenderAssets::load one synchronous load: models, block states, item infos, then bake and store
|
||||
ModelManager.loadBlockModels port crates/horizon-client-render/src/model_library.rs::ParsedModels::load every models/**.json by id, parsed in parallel
|
||||
ModelManager.discoverModelDependencies port crates/horizon-client-render/src/model_library.rs::ParsedModels::block_models referenced models and their parents
|
||||
ModelManager.loadModels port crates/horizon-client-render/src/models/catalog.rs::StateModels::from_facts_with_models bakes block states and their models
|
||||
ModelManager.createBlockStateToModelDispatch inline crates/horizon-client-render/src/models/catalog.rs::StateModels::from_facts_with_models missing model for a state without a baked model
|
||||
ModelManager.buildModelGroups skip:java wrapper of ModelGroupCollector.build; render sharing only
|
||||
ModelManager.apply inline crates/horizon-client/src/assets.rs::RenderAssets::load the baked result is stored in the returned struct
|
||||
ModelManager.apply inline crates/horizon-client-render/src/assets.rs::RenderAssets::load the baked result is stored in the returned struct
|
||||
ModelManager.requiresRender skip:java render-skip optimisation for equal model groups; no visible effect
|
||||
ResolvedModel.findTopTextureSlots port crates/horizon-assets/src/resolve.rs::ModelLibrary::resolve texture slots merged along the parent chain
|
||||
ResolvedModel.findTopAmbientOcclusion port crates/horizon-assets/src/resolve.rs::ModelLibrary::resolve ambient occlusion from the first model that sets it, default true
|
||||
ResolvedModel.findTopGuiLight port crates/horizon-assets/src/resolve.rs::ModelLibrary::resolve gui_light from the first model that sets it, default side
|
||||
ResolvedModel.findTopGeometry port crates/horizon-assets/src/resolve.rs::ModelLibrary::resolve first element list up the parent chain
|
||||
ResolvedModel.resolveParticleMaterial port crates/horizon-client/src/models/catalog.rs::StateModels::from_facts_with_models block particle slot; the item side is item_bake.rs::bake_item_model
|
||||
ResolvedModel.resolveParticleMaterial port crates/horizon-client-render/src/models/catalog.rs::StateModels::from_facts_with_models block particle slot; the item side is item_bake.rs::bake_item_model
|
||||
ResolvedModel.findTopTransform port crates/horizon-assets/src/resolve.rs::ModelLibrary::resolve display transform per context, inherited up the chain
|
||||
ResolvedModel.findTopTransforms port crates/horizon-assets/src/resolve.rs::ModelLibrary::resolve display transforms for every context
|
||||
SimpleModelWrapper.bake port crates/horizon-assets/src/bake.rs::bake_block_model gap: minor: no rejection of block models that use non-block sprites
|
||||
@@ -60,7 +60,7 @@ CuboidRotation.scaleFactorForAxis inline crates/horizon-assets/src/bake.rs::Elem
|
||||
CuboidRotation.EulerXYZRotation.transformation port crates/horizon-assets/src/bake.rs::ElementMatrix::from_rotation Euler XYZ branch
|
||||
CuboidRotation.SingleAxisRotation.transformation port crates/horizon-assets/src/bake.rs::ElementMatrix::from_rotation single-axis branch
|
||||
FaceBakery.defaultFaceUV port crates/horizon-assets/src/bake.rs::default_uv default face UVs by direction
|
||||
FaceBakery.computeMaterialTransparency port crates/horizon-client/src/sprites.rs::Sprites::region_opaque gap: minor: only the opaque-region check; force_translucent is not applied here
|
||||
FaceBakery.computeMaterialTransparency port crates/horizon-client-render/src/sprites.rs::Sprites::region_opaque gap: minor: only the opaque-region check; force_translucent is not applied here
|
||||
FaceBakery.bakeVertex inline crates/horizon-assets/src/bake.rs::bake_face vertex position and UV inside bake_face
|
||||
FaceBakery.rotateVertexBy inline crates/horizon-assets/src/bake.rs::bake_face element rotation about its origin
|
||||
FaceBakery.calculateFacing port crates/horizon-assets/src/bake.rs::facing quad normal, then closest direction
|
||||
@@ -84,18 +84,18 @@ BakedQuad.MaterialInfo.of port crates/horizon-client/src/item_render.rs::FileSpr
|
||||
BakedQuad.MaterialInfo.flags skip:java translucent and animated bit flags used to skip work; Horizon ticks animated textures by its setting
|
||||
ItemQuads.split port crates/horizon-client/src/item_render.rs::submit_quads quads with translucent sprites go to the translucent pass
|
||||
QuadCollection.computeMaterialFlags skip:java flag bits of the quad list; see BakedQuad.MaterialInfo.flags
|
||||
QuadCollection.getQuads port crates/horizon-client/src/mesher.rs::Mesher::mesh quads filtered by cull direction
|
||||
QuadCollection.getQuads port crates/horizon-client-render/src/mesher.rs::Mesher::mesh quads filtered by cull direction
|
||||
QuadCollection.materialFlags skip:java cached flag bits
|
||||
QuadCollection.Builder.addAll skip:java collection builder
|
||||
QuadCollection.Builder.createFromSublists skip:java sublist layout of a quad collection
|
||||
AtlasManager.getAtlasOrThrow skip:java atlas map lookup; Horizon keeps one sprite table
|
||||
AtlasManager.forEach skip:java map iteration
|
||||
AtlasManager.close skip:java GPU release; freed with its owner
|
||||
AtlasManager.get port crates/horizon-client/src/sprites.rs::Sprites::insert sprite layer added on first use; missing texture when it fails to load
|
||||
AtlasManager.get port crates/horizon-client-render/src/sprites.rs::Sprites::insert sprite layer added on first use; missing texture when it fails to load
|
||||
AtlasManager.prepareSharedState skip:java future-based stitch plumbing
|
||||
AtlasManager.reload skip:java async reload; sprites load synchronously in RenderAssets::load
|
||||
AtlasManager.updateSpriteMaps port crates/horizon-client/src/sprites.rs::Sprites::finish lays out every layer at one size; gap: minor: no duplicate-sprite warning
|
||||
AtlasManager.AtlasEntry.scheduleLoad port crates/horizon-client/src/sprites.rs::Sprites::insert gap: minor: sprites load on demand, not per atlas source
|
||||
AtlasManager.updateSpriteMaps port crates/horizon-client-render/src/sprites.rs::Sprites::finish lays out every layer at one size; gap: minor: no duplicate-sprite warning
|
||||
AtlasManager.AtlasEntry.scheduleLoad port crates/horizon-client-render/src/sprites.rs::Sprites::insert gap: minor: sprites load on demand, not per atlas source
|
||||
AtlasManager.PendingStitch.joinAndUpload skip:java future join
|
||||
AtlasManager.PendingStitchResults.joinAndUpload skip:java future join
|
||||
MaterialBaker.get port crates/horizon-assets/src/item_bake.rs::bake_item_model sprite that is not known falls back to missingno
|
||||
@@ -110,12 +110,12 @@ TextureSlots.Data.Builder.addReference skip:java builder
|
||||
TextureSlots.Data.Builder.addTexture skip:java builder
|
||||
TextureSlots.Data.Builder.build skip:java builder
|
||||
TextureSlots.Resolver.resolve port crates/horizon-assets/src/resolve.rs::ModelLibrary::resolve alias chain resolution; the batch hint (horizon-protocol resolver.rs) is a DNS resolver and is not this
|
||||
resources/palette/Palette.listAndLoad port crates/horizon-client/src/trims.rs::TrimTexture::generate gap: minor: palettes load on demand, none listed up front
|
||||
resources/palette/Palette.tryLoad port crates/horizon-client/src/trims.rs::TrimTexture::generate gap: minor: a broken palette skips the recolour instead of an empty palette
|
||||
resources/palette/Palette.load port crates/horizon-client/src/trims.rs::TrimTexture::generate palette PNG read as ARGB
|
||||
resources/palette/Palette.listAndLoad port crates/horizon-client-render/src/trims.rs::TrimTexture::generate gap: minor: palettes load on demand, none listed up front
|
||||
resources/palette/Palette.tryLoad port crates/horizon-client-render/src/trims.rs::TrimTexture::generate gap: minor: a broken palette skips the recolour instead of an empty palette
|
||||
resources/palette/Palette.load port crates/horizon-client-render/src/trims.rs::TrimTexture::generate palette PNG read as ARGB
|
||||
PaletteMapping.apply port crates/horizon-assets/src/palette.rs::apply_palette colour swap; alpha scaled by the target's alpha
|
||||
PalettedTextureManager.loadBaseTexture port crates/horizon-client/src/trims.rs::TrimTexture::generate base texture and its base_palette from the mcmeta
|
||||
PalettedTextureManager.allocateSlot port crates/horizon-client/src/sprites.rs::Sprites::insert gap: minor: one array layer per texture, no dynamic atlas tree
|
||||
PalettedTextureManager.loadBaseTexture port crates/horizon-client-render/src/trims.rs::TrimTexture::generate base texture and its base_palette from the mcmeta
|
||||
PalettedTextureManager.allocateSlot port crates/horizon-client-render/src/sprites.rs::Sprites::insert gap: minor: one array layer per texture, no dynamic atlas tree
|
||||
PalettedTextureManager.close skip:java GPU release
|
||||
PalettedTextureManager.reload port crates/horizon-client/src/game/armor_stands.rs::ArmorStandAssets::reload_trims trim data reloaded; models kept
|
||||
PalettedTextureManager.AtlasTexture.dumpContents skip:debug debug dump
|
||||
@@ -135,15 +135,15 @@ ServerPackManager.allowServerPacks port crates/horizon-client/src/server_packs.r
|
||||
ServerPackManager.rejectServerPacks port crates/horizon-client/src/server_packs.rs::ServerPacks::reject prompt declined; removes the unaccepted packs
|
||||
ServerPackManager.cleanupRemovedPacks inline crates/horizon-client/src/server_packs.rs::ServerPacks::tick forgets removed packs and reports why
|
||||
ServerPackManager.updateDownloads port crates/horizon-client/src/server_packs.rs::ServerPacks::tick gap: minor: returns the download requests; the download itself starts in game/server_packs.rs::Game::start_pack_downloads
|
||||
AbstractSoundInstance.getOrResolve port crates/horizon-client/src/sound_engine.rs::SoundEngine::resolve event lookup, then a random sound from it
|
||||
BeeSoundInstance.tick port crates/horizon-client/src/sound_engine.rs::SoundInstance::tick Ticking::Bee follows the bee; pitch and volume from speed
|
||||
AbstractSoundInstance.getOrResolve port crates/horizon-client-render/src/sound_engine.rs::SoundEngine::resolve event lookup, then a random sound from it
|
||||
BeeSoundInstance.tick port crates/horizon-client-render/src/sound_engine.rs::SoundInstance::tick Ticking::Bee follows the bee; pitch and volume from speed
|
||||
DirectionalSoundInstance.setPosition port crates/horizon-client/src/game/end.rs::Game::tick_end gap: minor: the end flash plays without a position, so the 10-block offset toward the view is not kept
|
||||
ElytraOnPlayerSoundInstance.tick missing player: no elytra wind loop (entity.elytra.flying) while gliding; gliding is silent
|
||||
EntityBoundSoundInstance.tick missing minor: a sound tied to an entity does not follow it; it stays at its start point
|
||||
RidingEntitySoundInstance.tick missing player: no riding loop for a happy ghast or nautilus (HAPPY_GHAST_RIDING, NAUTILUS_RIDING)
|
||||
RidingMinecartSoundInstance.shoudlPlaySound port crates/horizon-client/src/sound_engine.rs::SoundInstance::tick RidingMinecart arm; the predicate is always true for OldMinecartBehavior, Horizon's only minecart
|
||||
RidingMinecartSoundInstance.shoudlPlaySound port crates/horizon-client-render/src/sound_engine.rs::SoundInstance::tick RidingMinecart arm; the predicate is always true for OldMinecartBehavior, Horizon's only minecart
|
||||
SnifferSoundInstance.tick port crates/horizon-client/src/game/sniffers.rs::Game::tick_sniffer_effects gap: player: the digging sound plays once when digging starts; vanilla loops it while digging
|
||||
Sound.Type.getByName port crates/horizon-client/src/sound_events.rs::parse_sound type file or event; other values reject the sound
|
||||
SoundEventRegistrationSerializer.deserialize port crates/horizon-client/src/sound_events.rs::parse_file replace and subtitle per event
|
||||
SoundEventRegistrationSerializer.getSounds inline crates/horizon-client/src/sound_events.rs::parse_file sounds array inside parse_file
|
||||
SoundEventRegistrationSerializer.getType inline crates/horizon-client/src/sound_events.rs::parse_sound type field inside parse_sound
|
||||
Sound.Type.getByName port crates/horizon-client-render/src/sound_events.rs::parse_sound type file or event; other values reject the sound
|
||||
SoundEventRegistrationSerializer.deserialize port crates/horizon-client-render/src/sound_events.rs::parse_file replace and subtitle per event
|
||||
SoundEventRegistrationSerializer.getSounds inline crates/horizon-client-render/src/sound_events.rs::parse_file sounds array inside parse_file
|
||||
SoundEventRegistrationSerializer.getType inline crates/horizon-client-render/src/sound_events.rs::parse_sound type field inside parse_sound
|
||||
|
@@ -1,29 +1,29 @@
|
||||
ActiveTextCollector.accept inline crates/horizon-client/src/screen_draw.rs::text default overload: left-aligned text at x,y with default parameters
|
||||
ActiveTextCollector.accept inline crates/horizon-client-render/src/screen_draw.rs::text default overload: left-aligned text at x,y with default parameters
|
||||
ActiveTextCollector.acceptScrolling missing minor: forwards to the scrolling helper, which Horizon lacks; long text is clipped
|
||||
ActiveTextCollector.acceptScrollingWithDefaultCenter port crates/horizon-client/src/ui.rs::centered_text gap: minor: centres the message; wider text is clipped instead of scrolled
|
||||
ActiveTextCollector.acceptScrollingWithDefaultCenter port crates/horizon-client-render/src/ui.rs::centered_text gap: minor: centres the message; wider text is clipped instead of scrolled
|
||||
ActiveTextCollector.defaultScrollingHelper missing minor: wider text does not slide back and forth; ui.rs centered_text clips it
|
||||
ActiveTextCollector.findElementUnderCursor port crates/horizon-client/src/chat.rs::Chat::style_at gap: minor: hit test covers chat lines only; book links use game/books.rs book_link_at
|
||||
ActiveTextCollector.isPointInRectangle port crates/horizon-client/src/ui.rs::hovered half-open rectangle test with the same bounds
|
||||
ActiveTextCollector.ClickableStyleFinder.accept port crates/horizon-client/src/chat.rs::Chat::style_at gap: minor: only chat lines are searched for click and hover styles
|
||||
ActiveTextCollector.findElementUnderCursor port crates/horizon-client-render/src/chat.rs::Chat::style_at gap: minor: hit test covers chat lines only; book links use game/books.rs book_link_at
|
||||
ActiveTextCollector.isPointInRectangle port crates/horizon-client-render/src/ui.rs::hovered half-open rectangle test with the same bounds
|
||||
ActiveTextCollector.ClickableStyleFinder.accept port crates/horizon-client-render/src/chat.rs::Chat::style_at gap: minor: only chat lines are searched for click and hover styles
|
||||
ActiveTextCollector.ClickableStyleFinder.acceptScrolling missing minor: no scrolling text (see defaultScrollingHelper)
|
||||
ActiveTextCollector.Parameters.withScale skip:java record copy-with helper; Horizon has no pose matrix per text
|
||||
ActiveTextCollector.Parameters.withOpacity skip:java record copy-with helper; opacity is the colour's alpha byte in Horizon
|
||||
ActiveTextCollector.Parameters.withScissor missing minor: a nested scissor is not intersected with the enclosing rectangle; Draw::Scissor replaces it
|
||||
ComponentPath.path skip:other focus path builder for nested containers; Horizon focus is a flat per-screen state
|
||||
ComponentPath.Path.applyFocus skip:other focus path propagation; Horizon focus is a flat per-screen state
|
||||
Font.bidirectionalShaping port crates/horizon-client/src/bidi.rs::visual_str Arabic shaping and bidi reorder
|
||||
Font.bidirectionalShaping port crates/horizon-client-render/src/bidi.rs::visual_str Arabic shaping and bidi reorder
|
||||
Font.prepare8xTextOutline skip:other no vanilla 26.3 caller passes a non-zero outline colour; text displays, name tags and map labels pass 0 or a background
|
||||
Font.prepareBackground port crates/horizon-client/src/hud_overlays.rs::text_with_backdrop background box behind text; callers pass their own bounds
|
||||
Font.getGlyph port crates/horizon-client/src/font.rs::Font::glyph_styled obfuscated glyph is picked in HudBatch::text_styled via random_glyph
|
||||
Font.prepareBackground port crates/horizon-client-render/src/hud_overlays.rs::text_with_backdrop background box behind text; callers pass their own bounds
|
||||
Font.getGlyph port crates/horizon-client-render/src/font.rs::Font::glyph_styled obfuscated glyph is picked in HudBatch::text_styled via random_glyph
|
||||
Font.PreparedTextBuilder.markSize skip:other bounds bookkeeping of prepared text; Horizon has no prepared-text object and hit-tests by advance
|
||||
Font.PreparedTextBuilder.markBackground inline crates/horizon-client/src/hud_overlays.rs::text_with_backdrop gap: minor: the background box is 2 px around the text; vanilla grows it by 1 px around each glyph
|
||||
Font.PreparedTextBuilder.addGlyph inline crates/horizon-client/src/hud.rs::HudBatch::text_styled glyph quad pushed directly into the batch
|
||||
Font.PreparedTextBuilder.addEffect inline crates/horizon-client/src/hud.rs::HudBatch::text_styled underline and strikethrough rectangles drawn inline
|
||||
Font.PreparedTextBuilder.markBackground inline crates/horizon-client-render/src/hud_overlays.rs::text_with_backdrop gap: minor: the background box is 2 px around the text; vanilla grows it by 1 px around each glyph
|
||||
Font.PreparedTextBuilder.addGlyph inline crates/horizon-client-render/src/hud.rs::HudBatch::text_styled glyph quad pushed directly into the batch
|
||||
Font.PreparedTextBuilder.addEffect inline crates/horizon-client-render/src/hud.rs::HudBatch::text_styled underline and strikethrough rectangles drawn inline
|
||||
Font.PreparedTextBuilder.addEmptyGlyph skip:other hit areas for glyphs with no image; Chat::style_at hit-tests by advance width instead
|
||||
Font.PreparedTextBuilder.accept port crates/horizon-client/src/hud.rs::HudBatch::text_styled per-character draw with bold, italic, shadow, underline and strikethrough
|
||||
Font.PreparedTextBuilder.accept port crates/horizon-client-render/src/hud.rs::HudBatch::text_styled per-character draw with bold, italic, shadow, underline and strikethrough
|
||||
Font.PreparedTextBuilder.visit skip:other visitor over prepared glyphs for draw order and hit tests; Horizon draws straight into the batch
|
||||
Font.PreparedTextBuilder.getTextColor inline crates/horizon-client/src/hud.rs::HudBatch::text_styled style colour with the base colour's alpha
|
||||
Font.PreparedTextBuilder.getShadowColor inline crates/horizon-client/src/hud.rs::HudBatch::text_styled gap: minor: style shadow colour is ignored; shadow is always 0.25 of the text colour
|
||||
Font.PreparedTextBuilder.getTextColor inline crates/horizon-client-render/src/hud.rs::HudBatch::text_styled style colour with the base colour's alpha
|
||||
Font.PreparedTextBuilder.getShadowColor inline crates/horizon-client-render/src/hud.rs::HudBatch::text_styled gap: minor: style shadow colour is ignored; shadow is always 0.25 of the text colour
|
||||
Font.PreparedTextBuilder.bounds skip:other bounds of prepared text for vanilla hit tests; Horizon hit-tests by advance
|
||||
Gui.update skip:other narrator-only update (handleDelayedNarration); Horizon has no narrator
|
||||
Gui.isPausing port crates/horizon-client/src/game.rs::Game::pauses_game gap: minor: checks the open screen only, not the overlay part
|
||||
@@ -35,115 +35,115 @@ Gui.buildInitialScreens missing player: first launch skips vanilla's accessibil
|
||||
Gui.addInitialScreens missing player: same as buildInitialScreens: no onboarding screen or ban notice is queued
|
||||
Gui.canInterruptScreen missing minor: unsure: no check that stops a server-opened screen from replacing an open one was found
|
||||
Gui.renderActiveTextDebug skip:debug debug overlay of text bounds
|
||||
GuiGraphicsExtractor.enableScissor port crates/horizon-client/src/screen_draw.rs::paint gap: minor: callers push a rectangle; there is no stack, so enclosing rectangles are not intersected
|
||||
GuiGraphicsExtractor.enableScissor port crates/horizon-client-render/src/screen_draw.rs::paint gap: minor: callers push a rectangle; there is no stack, so enclosing rectangles are not intersected
|
||||
GuiGraphicsExtractor.outline missing minor: unsure: no one-pixel outline draw was found in Horizon's UI
|
||||
GuiGraphicsExtractor.innerFill port crates/horizon-client/src/hud.rs::HudBatch::fill gap: minor: one colour only; a second gradient colour (color2) is not drawn
|
||||
GuiGraphicsExtractor.textWithWordWrap port crates/horizon-client/src/screen_draw.rs::Measure::split gap: minor: only the wrap; the caller draws each line at y+9
|
||||
GuiGraphicsExtractor.blitSprite port crates/horizon-client/src/hud.rs::HudBatch::sprite_scaled gap: minor: tile and nine-slice scaling is in ui.rs (tiled, draw_nine_slice)
|
||||
GuiGraphicsExtractor.blitNineSliceInnerSegment port crates/horizon-client/src/ui.rs::draw_nine_slice inner segments of a nine-slice sprite
|
||||
GuiGraphicsExtractor.blitTiledSprite port crates/horizon-client/src/ui.rs::tiled
|
||||
GuiGraphicsExtractor.innerBlit port crates/horizon-client/src/screen_draw.rs::blit texture rectangle by UV; the atlas lookup is in HudBatch::rect_uv
|
||||
GuiGraphicsExtractor.innerTiledBlit port crates/horizon-client/src/ui.rs::tile one tiled quad
|
||||
GuiGraphicsExtractor.innerFill port crates/horizon-client-render/src/hud.rs::HudBatch::fill gap: minor: one colour only; a second gradient colour (color2) is not drawn
|
||||
GuiGraphicsExtractor.textWithWordWrap port crates/horizon-client-render/src/screen_draw.rs::Measure::split gap: minor: only the wrap; the caller draws each line at y+9
|
||||
GuiGraphicsExtractor.blitSprite port crates/horizon-client-render/src/hud.rs::HudBatch::sprite_scaled gap: minor: tile and nine-slice scaling is in ui.rs (tiled, draw_nine_slice)
|
||||
GuiGraphicsExtractor.blitNineSliceInnerSegment port crates/horizon-client-render/src/ui.rs::draw_nine_slice inner segments of a nine-slice sprite
|
||||
GuiGraphicsExtractor.blitTiledSprite port crates/horizon-client-render/src/ui.rs::tiled
|
||||
GuiGraphicsExtractor.innerBlit port crates/horizon-client-render/src/screen_draw.rs::blit texture rectangle by UV; the atlas lookup is in HudBatch::rect_uv
|
||||
GuiGraphicsExtractor.innerTiledBlit port crates/horizon-client-render/src/ui.rs::tile one tiled quad
|
||||
GuiGraphicsExtractor.item port crates/horizon-client/src/game.rs::Game::draw_item gap: minor: no seed or owner is passed to the item model
|
||||
GuiGraphicsExtractor.fakeItem inline crates/horizon-client/src/game.rs::Game::draw_item same as item with no owner
|
||||
GuiGraphicsExtractor.itemBar inline crates/horizon-client/src/hud_overlays.rs::item_decorations damage bar drawn in item_decorations
|
||||
GuiGraphicsExtractor.itemCount inline crates/horizon-client/src/hud_overlays.rs::item_decorations stack count drawn in item_decorations
|
||||
GuiGraphicsExtractor.itemBar inline crates/horizon-client-render/src/hud_overlays.rs::item_decorations damage bar drawn in item_decorations
|
||||
GuiGraphicsExtractor.itemCount inline crates/horizon-client-render/src/hud_overlays.rs::item_decorations stack count drawn in item_decorations
|
||||
GuiGraphicsExtractor.map port crates/horizon-client/src/game/cartography.rs::Game::draw_cartography gap: player: the map image is drawn only in the cartography preview; map markers (decorations) are not drawn there
|
||||
GuiGraphicsExtractor.entity port crates/horizon-client/src/game/perspective.rs::Game::draw_player_preview gap: player: only the local player is previewed; the smithing armour-stand preview has no port
|
||||
GuiGraphicsExtractor.skin missing minor: no skin customisation preview was found; the inventory preview is draw_player_preview
|
||||
GuiGraphicsExtractor.book missing player: the enchanting table screen does not draw its open book; Horizon's book drawing is for written books only
|
||||
GuiGraphicsExtractor.profilerChart skip:debug F3 profiler pie chart; Horizon's debug_chart.rs covers its own charts
|
||||
GuiGraphicsExtractor.setTooltipForNextFrame inline crates/horizon-client/src/screen_draw.rs::tooltip the overloads route to the tooltip draw
|
||||
GuiGraphicsExtractor.setComponentTooltipForNextFrame port crates/horizon-client/src/screen_draw.rs::tooltip gap: minor: tooltip images (TooltipComponent) are not drawn
|
||||
GuiGraphicsExtractor.setTooltipForNextFrameInternal port crates/horizon-client/src/ui.rs::tooltip frame and background sprites beside the cursor
|
||||
GuiGraphicsExtractor.extractDeferredElements port crates/horizon-client/src/screen_draw.rs::paint gap: minor: deferred hover effects come from chat only (see componentHoverEffect)
|
||||
GuiGraphicsExtractor.setTooltipForNextFrame inline crates/horizon-client-render/src/screen_draw.rs::tooltip the overloads route to the tooltip draw
|
||||
GuiGraphicsExtractor.setComponentTooltipForNextFrame port crates/horizon-client-render/src/screen_draw.rs::tooltip gap: minor: tooltip images (TooltipComponent) are not drawn
|
||||
GuiGraphicsExtractor.setTooltipForNextFrameInternal port crates/horizon-client-render/src/ui.rs::tooltip frame and background sprites beside the cursor
|
||||
GuiGraphicsExtractor.extractDeferredElements port crates/horizon-client-render/src/screen_draw.rs::paint gap: minor: deferred hover effects come from chat only (see componentHoverEffect)
|
||||
GuiGraphicsExtractor.componentHoverEffect port crates/horizon-client/src/game/chat.rs::Game::draw_chat_screen gap: player: hover tooltips exist only for text in chat; item and entity hover events show nothing
|
||||
GuiGraphicsExtractor.textRendererForWidget skip:ui builds a text collector for a widget; Horizon draws widget text directly
|
||||
GuiGraphicsExtractor.textRenderer skip:ui builds a text collector; Horizon draws text directly
|
||||
GuiGraphicsExtractor.createDefaultTextParameters skip:ui default pose, opacity and scissor for text; Horizon passes colour and rectangle directly
|
||||
GuiGraphicsExtractor.RenderingTextCollector.accept port crates/horizon-client/src/screen_draw.rs::text gap: minor: no hover or click registration here (see Chat::style_at)
|
||||
GuiGraphicsExtractor.RenderingTextCollector.accept port crates/horizon-client-render/src/screen_draw.rs::text gap: minor: no hover or click registration here (see Chat::style_at)
|
||||
GuiGraphicsExtractor.RenderingTextCollector.acceptScrolling missing minor: no scrolling text (see defaultScrollingHelper)
|
||||
GuiGraphicsExtractor.ScissorStack.push inline crates/horizon-client/src/screen_draw.rs::paint gap: minor: the pushed rectangle replaces the current one; it is not intersected
|
||||
GuiGraphicsExtractor.ScissorStack.pop inline crates/horizon-client/src/screen_draw.rs::paint gap: minor: Draw::NoScissor clears the scissor instead of restoring the enclosing one
|
||||
GuiGraphicsExtractor.ScissorStack.push inline crates/horizon-client-render/src/screen_draw.rs::paint gap: minor: the pushed rectangle replaces the current one; it is not intersected
|
||||
GuiGraphicsExtractor.ScissorStack.pop inline crates/horizon-client-render/src/screen_draw.rs::paint gap: minor: Draw::NoScissor clears the scissor instead of restoring the enclosing one
|
||||
GuiGraphicsExtractor.ScissorStack.containsPoint missing minor: hover ignores the scissor, so a widget clipped out of view can still highlight
|
||||
Hud.extractHotbarAndDecorations port crates/horizon-client/src/hud_layout.rs::hotbar items and decorations are drawn by Game::draw_item
|
||||
Hud.extractHotbarAndDecorations port crates/horizon-client-render/src/hud_layout.rs::hotbar items and decorations are drawn by Game::draw_item
|
||||
Hud.<init> skip:java constructor state setup
|
||||
Hud.extractSubtitleOverlay port crates/horizon-client/src/hud_overlays.rs::SubtitleOverlay::extract
|
||||
Hud.extractChat port crates/horizon-client/src/chat.rs::Chat::layout
|
||||
Hud.extractSubtitleOverlay port crates/horizon-client-render/src/hud_overlays.rs::SubtitleOverlay::extract
|
||||
Hud.extractChat port crates/horizon-client-render/src/chat.rs::Chat::layout
|
||||
Hud.getMobEffectSprite port crates/horizon-client/src/game/potions.rs::Game::draw_effect_icons sprite lookup is inside the effect icon draw
|
||||
Hud.extractDemoOverlay skip:other Horizon has no demo mode, so there is no demo timer overlay
|
||||
Hud.getCameraPlayer missing minor: unsure: no camera-entity lookup found; the HUD always uses the local player
|
||||
Hud.getPlayerVehicleWithHealth port crates/horizon-client/src/hud_layout.rs::vehicle_health gap: minor: the vehicle is chosen by the caller
|
||||
Hud.extractArmor inline crates/horizon-client/src/hud_layout.rs::HealthHud::layout armour row inside the health layout
|
||||
Hud.extractHearts inline crates/horizon-client/src/hud_layout.rs::HealthHud::layout heart row inside the health layout
|
||||
Hud.extractHeart port crates/horizon-client/src/hud_layout.rs::heart heart sprite choice for one heart
|
||||
Hud.getAirBubbleYLine inline crates/horizon-client/src/hud_layout.rs::HealthHud::layout air bubble row position
|
||||
Hud.getCurrentAirSupplyBubble inline crates/horizon-client/src/hud_layout.rs::HealthHud::layout air bubble count
|
||||
Hud.extractFood inline crates/horizon-client/src/hud_layout.rs::HealthHud::layout food row inside the health layout
|
||||
Hud.extractTextureOverlay port crates/horizon-client/src/hud_overlays.rs::camera_overlays gap: player: the powder-snow freeze outline is not drawn; only the equippable camera overlay (pumpkin) is
|
||||
Hud.getPlayerVehicleWithHealth port crates/horizon-client-render/src/hud_layout.rs::vehicle_health gap: minor: the vehicle is chosen by the caller
|
||||
Hud.extractArmor inline crates/horizon-client-render/src/hud_layout.rs::HealthHud::layout armour row inside the health layout
|
||||
Hud.extractHearts inline crates/horizon-client-render/src/hud_layout.rs::HealthHud::layout heart row inside the health layout
|
||||
Hud.extractHeart port crates/horizon-client-render/src/hud_layout.rs::heart heart sprite choice for one heart
|
||||
Hud.getAirBubbleYLine inline crates/horizon-client-render/src/hud_layout.rs::HealthHud::layout air bubble row position
|
||||
Hud.getCurrentAirSupplyBubble inline crates/horizon-client-render/src/hud_layout.rs::HealthHud::layout air bubble count
|
||||
Hud.extractFood inline crates/horizon-client-render/src/hud_layout.rs::HealthHud::layout food row inside the health layout
|
||||
Hud.extractTextureOverlay port crates/horizon-client-render/src/hud_overlays.rs::camera_overlays gap: player: the powder-snow freeze outline is not drawn; only the equippable camera overlay (pumpkin) is
|
||||
Hud.extractSlot port crates/horizon-client/src/game.rs::Game::draw_slot_item
|
||||
Hud.setNowPlaying port crates/horizon-client/src/hud_overlays.rs::Titles::set_overlay the animate flag gives the rainbow colour
|
||||
Hud.setNowPlaying port crates/horizon-client-render/src/hud_overlays.rs::Titles::set_overlay the animate flag gives the rainbow colour
|
||||
Hud.onDisconnected missing minor: unsure: no HUD reset on disconnect found; titles clear only on the ClearTitles packet
|
||||
Hud.willPrioritizeJumpInfo port crates/horizon-client/src/locator_bar.rs::ContextualBar::next the jumping input comes from Game::mount_jumping
|
||||
Hud.HeartType.getSprite port crates/horizon-client/src/hud_layout.rs::heart
|
||||
Hud.HeartType.forPlayer inline crates/horizon-client/src/hud_layout.rs::HealthHud::layout heart kind comes from the health inputs
|
||||
AbstractButton.extractDefaultSprite port crates/horizon-client/src/screen_draw.rs::button disabled, highlighted and normal sprites
|
||||
Hud.HeartType.getSprite port crates/horizon-client-render/src/hud_layout.rs::heart
|
||||
Hud.HeartType.forPlayer inline crates/horizon-client-render/src/hud_layout.rs::HealthHud::layout heart kind comes from the health inputs
|
||||
AbstractButton.extractDefaultSprite port crates/horizon-client-render/src/screen_draw.rs::button disabled, highlighted and normal sprites
|
||||
AbstractButton.keyPressed missing minor: Enter or Space does not press a focused button; Enter only confirms a title text edit
|
||||
AbstractContainerWidget.setFocused port crates/horizon-client/src/pack_screen.rs::PackScreen::set_focus gap: minor: screen-level focus only; no child-container focus
|
||||
AbstractContainerWidget.mouseClicked port crates/horizon-client/src/ui.rs::Scroll::press gap: minor: child click dispatch is per screen; only the scrollbar press is here
|
||||
AbstractScrollArea.isOverScrollbar inline crates/horizon-client/src/ui.rs::Scroll::press scrollbar column test folded into the press
|
||||
AbstractScrollArea.scrollBarY port crates/horizon-client/src/ui.rs::scrollbar_thumb thumb top
|
||||
AbstractScrollArea.extractScrollbar port crates/horizon-client/src/ui.rs::scrollbar gap: minor: no disabled-scroller sprite and no cursor request
|
||||
AbstractContainerWidget.setFocused port crates/horizon-client-render/src/pack_screen.rs::PackScreen::set_focus gap: minor: screen-level focus only; no child-container focus
|
||||
AbstractContainerWidget.mouseClicked port crates/horizon-client-render/src/ui.rs::Scroll::press gap: minor: child click dispatch is per screen; only the scrollbar press is here
|
||||
AbstractScrollArea.isOverScrollbar inline crates/horizon-client-render/src/ui.rs::Scroll::press scrollbar column test folded into the press
|
||||
AbstractScrollArea.scrollBarY port crates/horizon-client-render/src/ui.rs::scrollbar_thumb thumb top
|
||||
AbstractScrollArea.extractScrollbar port crates/horizon-client-render/src/ui.rs::scrollbar gap: minor: no disabled-scroller sprite and no cursor request
|
||||
AbstractScrollArea.defaultSettings skip:java settings record builder; the sizes are constants in ui.rs
|
||||
AbstractSelectionList.swap port crates/horizon-client/src/multiplayer.rs::Screen::click gap: minor: mouse move up or down works; keyboard Shift+arrow reorder is not ported
|
||||
AbstractSelectionList.clearEntriesExcept skip:other only the stats screen calls it; Horizon's stats screen draws its rows directly
|
||||
AbstractSelectionList.replaceEntries port crates/horizon-client/src/pack_screen.rs::PackScreen::update_list
|
||||
AbstractSelectionList.getNextY port crates/horizon-client/src/pack_screen.rs::List::next_y
|
||||
AbstractSelectionList.addEntry port crates/horizon-client/src/pack_screen.rs::List::add
|
||||
AbstractSelectionList.replaceEntries port crates/horizon-client-render/src/pack_screen.rs::PackScreen::update_list
|
||||
AbstractSelectionList.getNextY port crates/horizon-client-render/src/pack_screen.rs::List::next_y
|
||||
AbstractSelectionList.addEntry port crates/horizon-client-render/src/pack_screen.rs::List::add
|
||||
AbstractSelectionList.addEntryToTop skip:service only the chat report selection screen calls it; Horizon has no chat reporting
|
||||
AbstractSelectionList.repositionEntries port crates/horizon-client/src/pack_screen.rs::List::reposition
|
||||
AbstractSelectionList.repositionEntries port crates/horizon-client-render/src/pack_screen.rs::List::reposition
|
||||
AbstractSelectionList.removeEntryFromTop skip:service only the chat report selection screen calls it; Horizon has no chat reporting
|
||||
AbstractSelectionList.updateSizeAndPosition port crates/horizon-client/src/pack_screen.rs::List::update_size
|
||||
AbstractSelectionList.extractListSeparators port crates/horizon-client/src/pack_screen.rs::PackScreen::draw_list header and footer separators, with the in-world variant
|
||||
AbstractSelectionList.extractListBackground port crates/horizon-client/src/pack_screen.rs::PackScreen::draw_list menu list background sprite
|
||||
AbstractSelectionList.enableScissor port crates/horizon-client/src/pack_screen.rs::PackScreen::draw_list scissor clamped to the screen
|
||||
AbstractSelectionList.scrollToEntry port crates/horizon-client/src/pack_screen.rs::List::scroll_to
|
||||
AbstractSelectionList.setFocused port crates/horizon-client/src/pack_screen.rs::PackScreen::set_focus gap: minor: focusing a row does not select it; vanilla selects the focused entry
|
||||
AbstractSelectionList.updateSizeAndPosition port crates/horizon-client-render/src/pack_screen.rs::List::update_size
|
||||
AbstractSelectionList.extractListSeparators port crates/horizon-client-render/src/pack_screen.rs::PackScreen::draw_list header and footer separators, with the in-world variant
|
||||
AbstractSelectionList.extractListBackground port crates/horizon-client-render/src/pack_screen.rs::PackScreen::draw_list menu list background sprite
|
||||
AbstractSelectionList.enableScissor port crates/horizon-client-render/src/pack_screen.rs::PackScreen::draw_list scissor clamped to the screen
|
||||
AbstractSelectionList.scrollToEntry port crates/horizon-client-render/src/pack_screen.rs::List::scroll_to
|
||||
AbstractSelectionList.setFocused port crates/horizon-client-render/src/pack_screen.rs::PackScreen::set_focus gap: minor: focusing a row does not select it; vanilla selects the focused entry
|
||||
AbstractSelectionList.nextEntry missing minor: arrow keys do not move the selection between rows in the pack or server lists
|
||||
AbstractSelectionList.extractListItems port crates/horizon-client/src/pack_screen.rs::PackScreen::draw_list rows culled to the visible band
|
||||
AbstractSelectionList.extractItem port crates/horizon-client/src/pack_screen.rs::PackScreen::draw_list selection outline and row content
|
||||
AbstractSelectionList.extractSelection port crates/horizon-client/src/pack_screen.rs::PackScreen::draw_list two fills: outline colour and black inner
|
||||
AbstractSelectionList.extractListItems port crates/horizon-client-render/src/pack_screen.rs::PackScreen::draw_list rows culled to the visible band
|
||||
AbstractSelectionList.extractItem port crates/horizon-client-render/src/pack_screen.rs::PackScreen::draw_list selection outline and row content
|
||||
AbstractSelectionList.extractSelection port crates/horizon-client-render/src/pack_screen.rs::PackScreen::draw_list two fills: outline colour and black inner
|
||||
AbstractSelectionList.getRowBottom skip:other only the LAN-found narration uses it; Horizon has no narrator
|
||||
AbstractSelectionList.narrationPriority skip:other screen-reader priority; Horizon has no narrator
|
||||
AbstractSelectionList.removeEntry skip:other rows are rebuilt from the model when they change (update_list, server list), so there is no single-row removal
|
||||
AbstractSelectionList.narrateListElementPosition skip:other narration only
|
||||
AbstractSelectionList.TrackedList.set skip:java delegate list wrapper
|
||||
AbstractSliderButton.getSprite inline crates/horizon-client/src/ui.rs::slider gap: minor: track highlight follows hover only; keyboard focus does not highlight
|
||||
AbstractSliderButton.getHandleSprite inline crates/horizon-client/src/ui.rs::slider gap: minor: handle highlight follows hover only
|
||||
AbstractSliderButton.getSprite inline crates/horizon-client-render/src/ui.rs::slider gap: minor: track highlight follows hover only; keyboard focus does not highlight
|
||||
AbstractSliderButton.getHandleSprite inline crates/horizon-client-render/src/ui.rs::slider gap: minor: handle highlight follows hover only
|
||||
AbstractSliderButton.updateWidgetNarration skip:other narration only
|
||||
AbstractSliderButton.extractWidgetRenderState port crates/horizon-client/src/ui.rs::slider gap: minor: the label is clipped rather than scrolled
|
||||
AbstractSliderButton.handleCursor port crates/horizon-client/src/widgets.rs::widget_cursor gap: minor: no resize cursor while dragging
|
||||
AbstractSliderButton.extractWidgetRenderState port crates/horizon-client-render/src/ui.rs::slider gap: minor: the label is clipped rather than scrolled
|
||||
AbstractSliderButton.handleCursor port crates/horizon-client-render/src/widgets.rs::widget_cursor gap: minor: no resize cursor while dragging
|
||||
AbstractSliderButton.setFocused missing minor: keyboard focus does not enable value changes; there is no change-value state
|
||||
AbstractSliderButton.keyPressed missing minor: Left and Right do not move a focused slider; Enter does not toggle it
|
||||
AbstractSliderButton.setValue port crates/horizon-client/src/options_ui.rs::set_slider gap: minor: the 0..1 clamp is done by the caller (ui.rs slider_value)
|
||||
AbstractStringWidget.extractWidgetRenderState port crates/horizon-client/src/screen_draw.rs::string_widget gap: minor: hover tooltip and click cursor for styled text are not drawn
|
||||
AbstractSliderButton.setValue port crates/horizon-client-render/src/options_ui.rs::set_slider gap: minor: the 0..1 clamp is done by the caller (ui.rs slider_value)
|
||||
AbstractStringWidget.extractWidgetRenderState port crates/horizon-client-render/src/screen_draw.rs::string_widget gap: minor: hover tooltip and click cursor for styled text are not drawn
|
||||
AbstractStringWidget.onClick missing minor: clicking a styled label with a click event does nothing here; book links and chat links do work
|
||||
AbstractStringWidget.setMessage skip:ui width is measured at draw time
|
||||
AbstractStringWidget.handleStyleClick missing minor: same as onClick: no click handling for labels
|
||||
AbstractTextAreaWidget.mouseClicked port crates/horizon-client/src/game/books.rs::Game::book_page_click gap: minor: a scrollbar press inside the area is not checked
|
||||
AbstractTextAreaWidget.keyPressed port crates/horizon-client/src/game/books.rs::Game::book_page_key gap: minor: up and down scroll the area in vanilla; not checked here
|
||||
AbstractTextAreaWidget.extractWidgetRenderState port crates/horizon-client/src/game/books.rs::Game::draw_book gap: minor: scroll offset inside the page not verified
|
||||
AbstractTextAreaWidget.isMouseOver port crates/horizon-client/src/screen_draw.rs::inside gap: minor: the scrollbar width is not added to the hit area
|
||||
AbstractTextAreaWidget.isMouseOver port crates/horizon-client-render/src/screen_draw.rs::inside gap: minor: the scrollbar width is not added to the hit area
|
||||
AbstractTextAreaWidget.extractBackground skip:ui frame drawn by the book screen; no behaviour
|
||||
AbstractTextAreaWidget.extractBorder skip:ui frame sprite; no behaviour
|
||||
AbstractTextAreaWidget.withinContentAreaTopBottom skip:ui visibility cull while drawing lines
|
||||
AbstractWidget.extractRenderState port crates/horizon-client/src/ui.rs::hovered gap: minor: the scissor test is not part of the hover check
|
||||
AbstractWidget.extractTooltipForNextRenderPass port crates/horizon-client/src/ui.rs::tooltip gap: minor: unsure: widget hover delay not checked
|
||||
AbstractWidget.extractScrollingStringOverContents port crates/horizon-client/src/ui.rs::centered_text gap: minor: text is clipped, not scrolled
|
||||
AbstractWidget.extractRenderState port crates/horizon-client-render/src/ui.rs::hovered gap: minor: the scissor test is not part of the hover check
|
||||
AbstractWidget.extractTooltipForNextRenderPass port crates/horizon-client-render/src/ui.rs::tooltip gap: minor: unsure: widget hover delay not checked
|
||||
AbstractWidget.extractScrollingStringOverContents port crates/horizon-client-render/src/ui.rs::centered_text gap: minor: text is clipped, not scrolled
|
||||
AbstractWidget.mouseClicked port crates/horizon-client/src/title.rs::Title::click gap: minor: click dispatch is per screen; the press sound is audio.rs Audio::play_button_click
|
||||
AbstractWidget.mouseReleased port crates/horizon-client/src/ui.rs::Scroll::release gap: minor: only the scroll release; widget onRelease is per screen
|
||||
AbstractWidget.mouseDragged port crates/horizon-client/src/ui.rs::Scroll::drag gap: minor: only scroll drag; widget onDrag is per screen
|
||||
AbstractWidget.mouseReleased port crates/horizon-client-render/src/ui.rs::Scroll::release gap: minor: only the scroll release; widget onRelease is per screen
|
||||
AbstractWidget.mouseDragged port crates/horizon-client-render/src/ui.rs::Scroll::drag gap: minor: only scroll drag; widget onDrag is per screen
|
||||
AbstractWidget.nextFocusPath missing minor: unsure: no per-widget focus path; Horizon focus is per screen
|
||||
AbstractWidget.isMouseOver port crates/horizon-client/src/screen_draw.rs::inside gap: minor: isActive is checked by the caller
|
||||
AbstractWidget.isMouseOver port crates/horizon-client-render/src/screen_draw.rs::inside gap: minor: isActive is checked by the caller
|
||||
AbstractWidget.narrationPriority skip:other narration only
|
||||
AbstractWidget.defaultButtonNarrationText skip:other narration only
|
||||
|
@@ -1,17 +1,17 @@
|
||||
BossHealthOverlay.extractBar port crates/horizon-client/src/hud_overlays.rs::boss_overlay background bar, then progress at the lerped width; the notch overlay only when not PROGRESS
|
||||
BossHealthOverlay.extractBar port crates/horizon-client-render/src/hud_overlays.rs::boss_overlay background bar, then progress at the lerped width; the notch overlay only when not PROGRESS
|
||||
BossHealthOverlay.shouldDarkenScreen port crates/horizon-client/src/boss_overlay.rs::BossOverlay::should_darken any bar asks to darken
|
||||
Button.Builder.build skip:java builder: makes a Plain button and sets its tooltip
|
||||
ChatComponent.forEachLine port crates/horizon-client/src/chat.rs::Chat::layout visible lines bottom-up with the alpha filter, inlined in layout
|
||||
ChatComponent.addMessageToDisplayQueue port crates/horizon-client/src/chat.rs::Chat::add_lines wrap at width/scale, keep the scroll when chatting, trim to 100 lines
|
||||
ChatComponent.addMessageToQueue port crates/horizon-client/src/chat.rs::Chat::add_message newest first, trimmed to 100 messages
|
||||
ChatComponent.forEachLine port crates/horizon-client-render/src/chat.rs::Chat::layout visible lines bottom-up with the alpha filter, inlined in layout
|
||||
ChatComponent.addMessageToDisplayQueue port crates/horizon-client-render/src/chat.rs::Chat::add_lines wrap at width/scale, keep the scroll when chatting, trim to 100 lines
|
||||
ChatComponent.addMessageToQueue port crates/horizon-client-render/src/chat.rs::Chat::add_message newest first, trimmed to 100 messages
|
||||
ChatComponent.processMessageDeletionQueue missing player: a message the server deletes stays in chat; the delete-chat packet is only named in horizon-protocol/src/ids.rs and never decoded
|
||||
ChatComponent.deleteMessage missing player: a server message delete does nothing in Horizon; the removed line stays visible
|
||||
ChatComponent.deleteMessageOrDelay missing player: no 60-tick delayed removal and no deleted marker; see deleteMessage
|
||||
ChatComponent.refreshTrimmedMessages port crates/horizon-client/src/chat.rs::Chat::rewrap gap: minor: rewrap also resets the scroll to 0; vanilla keeps the position
|
||||
ChatComponent.scrollChat port crates/horizon-client/src/chat.rs::Chat::scroll_by clamp to the page and clear new-message marker at the bottom
|
||||
ChatComponent.getHeight port crates/horizon-client/src/chat.rs::ChatOptions::height_px focused or unfocused chat height in px
|
||||
ChatComponent.defaultUnfocusedPct inline crates/horizon-client/src/chat.rs::ChatOptions::default the default 70/160 is stored as a constant, not computed from the height formula
|
||||
ChatComponent.getLineHeight port crates/horizon-client/src/chat.rs::ChatOptions::line_height 9 * (line spacing + 1)
|
||||
ChatComponent.refreshTrimmedMessages port crates/horizon-client-render/src/chat.rs::Chat::rewrap gap: minor: rewrap also resets the scroll to 0; vanilla keeps the position
|
||||
ChatComponent.scrollChat port crates/horizon-client-render/src/chat.rs::Chat::scroll_by clamp to the page and clear new-message marker at the bottom
|
||||
ChatComponent.getHeight port crates/horizon-client-render/src/chat.rs::ChatOptions::height_px focused or unfocused chat height in px
|
||||
ChatComponent.defaultUnfocusedPct inline crates/horizon-client-render/src/chat.rs::ChatOptions::default the default 70/160 is stored as a constant, not computed from the height formula
|
||||
ChatComponent.getLineHeight port crates/horizon-client-render/src/chat.rs::ChatOptions::line_height 9 * (line spacing + 1)
|
||||
ChatComponent.saveAsDraft missing player: Save Chat Drafts does nothing (options_ui.rs notes no drafts yet); text left in the box is not kept for the next open
|
||||
ChatComponent.createScreen port crates/horizon-client/src/game/chat.rs::Game::open_chat gap: player: no draft is restored; the chat opens with the initial text its caller passes
|
||||
ChatComponent.preserveCurrentChatScreen missing minor: unsure: Horizon keeps no chat screen across a dimension change; game/nether_portal.rs:577 only leaves Chat alone during a portal effect, the change path was not checked
|
||||
@@ -19,12 +19,12 @@ ChatComponent.storeState inline crates/horizon-client/src/game/remote.rs::Game::
|
||||
ChatComponent.restoreState inline crates/horizon-client/src/game/remote.rs::Game::poll_remote no restore: the chat is never cleared on Reconfigured, so there is nothing to put back
|
||||
ChatComponent.ClickableTextOnlyGraphicsAccess.updatePose skip:ui pose bookkeeping of the text collector; chat draws with its own scale and translate
|
||||
ChatComponent.DrawingBackgroundGraphicsAccess.updatePose skip:ui pose bookkeeping of the text collector; chat draws with its own scale and translate
|
||||
ChatComponent.DrawingBackgroundGraphicsAccess.handleMessage port crates/horizon-client/src/chat.rs::Chat::layout line text drawn at its bottom with the text opacity
|
||||
ChatComponent.DrawingBackgroundGraphicsAccess.handleTag port crates/horizon-client/src/chat.rs::Chat::layout indicator bar filled from x -4 to -2 with the line alpha
|
||||
ChatComponent.DrawingFocusedGraphicsAccess.updateLocalMousePos inline crates/horizon-client/src/chat.rs::Chat::line_at the mouse mapped into line space inside line_at
|
||||
ChatComponent.DrawingBackgroundGraphicsAccess.handleMessage port crates/horizon-client-render/src/chat.rs::Chat::layout line text drawn at its bottom with the text opacity
|
||||
ChatComponent.DrawingBackgroundGraphicsAccess.handleTag port crates/horizon-client-render/src/chat.rs::Chat::layout indicator bar filled from x -4 to -2 with the line alpha
|
||||
ChatComponent.DrawingFocusedGraphicsAccess.updateLocalMousePos inline crates/horizon-client-render/src/chat.rs::Chat::line_at the mouse mapped into line space inside line_at
|
||||
ChatComponent.DrawingFocusedGraphicsAccess.updatePose skip:ui pose bookkeeping; the mouse mapping is done in Chat::line_at
|
||||
ChatComponent.DrawingFocusedGraphicsAccess.handleMessage port crates/horizon-client/src/chat.rs::Chat::style_at gap: player: no pointing-hand cursor over clickable chat text; only widgets set the cursor (widgets.rs:28)
|
||||
ChatComponent.DrawingFocusedGraphicsAccess.isMouseOver inline crates/horizon-client/src/chat.rs::Chat::indicator_at the rectangle test of the indicator bar; tag icons are not drawn
|
||||
ChatComponent.DrawingFocusedGraphicsAccess.handleMessage port crates/horizon-client-render/src/chat.rs::Chat::style_at gap: player: no pointing-hand cursor over clickable chat text; only widgets set the cursor (widgets.rs:28)
|
||||
ChatComponent.DrawingFocusedGraphicsAccess.isMouseOver inline crates/horizon-client-render/src/chat.rs::Chat::indicator_at the rectangle test of the indicator bar; tag icons are not drawn
|
||||
ChatComponent.DrawingFocusedGraphicsAccess.handleTagIcon missing minor: no tag icons; Horizon never tags a message as modified, so the Modified icon never shows
|
||||
ChatComponent.DrawingFocusedGraphicsAccess.showTooltip port crates/horizon-client/src/game/chat.rs::Game::draw_chat_screen gap: minor: every indicator shows the systemSinglePlayer text as its tooltip
|
||||
Checkbox.adjustWidth missing minor: the checkbox width is never capped or its label wrapped; dialog_screen.rs:137 sets the width to the full label
|
||||
@@ -34,26 +34,26 @@ Checkbox.updateWidgetNarration skip:other narration text; Horizon has no narrat
|
||||
Checkbox.extractContents port crates/horizon-client/src/game/dialogs.rs::Game::draw_dialog box sprite for checked and focused states, then the label (Part::Check)
|
||||
Checkbox.Builder.selected skip:java builder setter
|
||||
Checkbox.Builder.build skip:java builder: makes the checkbox and its tooltip
|
||||
CommandSuggestions.mouseScrolled port crates/horizon-client/src/chat.rs::ChatScreen::scroll wheel over the suggestion list, clamped to the list
|
||||
CommandSuggestions.mouseClicked port crates/horizon-client/src/chat.rs::ChatScreen::click click on a suggestion row uses it, else the input box
|
||||
CommandSuggestions.mouseScrolled port crates/horizon-client-render/src/chat.rs::ChatScreen::scroll wheel over the suggestion list, clamped to the list
|
||||
CommandSuggestions.mouseClicked port crates/horizon-client-render/src/chat.rs::ChatScreen::click click on a suggestion row uses it, else the input box
|
||||
CommandSuggestions.getUsageNarration skip:other narration text; no narrator in Horizon
|
||||
CommandSuggestions.sortSuggestions port crates/horizon-client/src/chat.rs::ChatScreen::show_suggestions suggestions starting with the typed word first, including the minecraft: prefix
|
||||
CommandSuggestions.sortSuggestions port crates/horizon-client-render/src/chat.rs::ChatScreen::show_suggestions suggestions starting with the typed word first, including the minecraft: prefix
|
||||
CommandSuggestions.hasMessageArguments missing minor: a /say or /msg style command with a message argument counts as a command here; Horizon's parse keeps no argument kinds (see update_usage_info comment)
|
||||
CommandSuggestions.getExceptionMessage port crates/horizon-client/src/chat.rs::ChatScreen::update_usage_info parse errors shown with their context (e.with_context)
|
||||
CommandSuggestions.fillNodeUsage port crates/horizon-client/src/chat.rs::ChatScreen::update_usage_info smart usage lines, without the literal nodes
|
||||
CommandSuggestions.recomputeUsageBoxWidth port crates/horizon-client/src/chat.rs::ChatScreen::update_usage_info usage width is the widest line
|
||||
CommandSuggestions.formatChat port crates/horizon-client/src/chat.rs::ChatScreen::format argument colours over the input
|
||||
CommandSuggestions.calculateSuggestionSuffix port crates/horizon-client/src/chat.rs::ChatScreen::select the grey suffix for the highlighted suggestion (strip the typed prefix)
|
||||
CommandSuggestions.extractSuggestions port crates/horizon-client/src/chat.rs::ChatScreen::draw the suggestion list drawn when it is open
|
||||
CommandSuggestions.extractUsage port crates/horizon-client/src/chat.rs::ChatScreen::draw usage lines drawn upward from the bottom, as vanilla's bottom anchor
|
||||
CommandSuggestions.getExceptionMessage port crates/horizon-client-render/src/chat.rs::ChatScreen::update_usage_info parse errors shown with their context (e.with_context)
|
||||
CommandSuggestions.fillNodeUsage port crates/horizon-client-render/src/chat.rs::ChatScreen::update_usage_info smart usage lines, without the literal nodes
|
||||
CommandSuggestions.recomputeUsageBoxWidth port crates/horizon-client-render/src/chat.rs::ChatScreen::update_usage_info usage width is the widest line
|
||||
CommandSuggestions.formatChat port crates/horizon-client-render/src/chat.rs::ChatScreen::format argument colours over the input
|
||||
CommandSuggestions.calculateSuggestionSuffix port crates/horizon-client-render/src/chat.rs::ChatScreen::select the grey suffix for the highlighted suggestion (strip the typed prefix)
|
||||
CommandSuggestions.extractSuggestions port crates/horizon-client-render/src/chat.rs::ChatScreen::draw the suggestion list drawn when it is open
|
||||
CommandSuggestions.extractUsage port crates/horizon-client-render/src/chat.rs::ChatScreen::draw usage lines drawn upward from the bottom, as vanilla's bottom anchor
|
||||
CommandSuggestions.getNarrationMessage skip:other narration text; no narrator in Horizon
|
||||
CommandSuggestions.hasAllowedInput port crates/horizon-client/src/chat.rs::ChatAbilities::allows gap: minor: a /say style command is judged as a command, not a message (see hasMessageArguments)
|
||||
CommandSuggestions.SuggestionsList.extractRenderState port crates/horizon-client/src/chat.rs::ChatScreen::draw_list rows, scroll marks, highlight and tooltip; gap: minor: no pointing-hand cursor over the rows
|
||||
CommandSuggestions.SuggestionsList.mouseClicked port crates/horizon-client/src/chat.rs::ChatScreen::click row click selects and uses the suggestion
|
||||
CommandSuggestions.SuggestionsList.keyPressed port crates/horizon-client/src/chat.rs::ChatScreen::key up, down and tab cycle and use the suggestion
|
||||
CommandSuggestions.SuggestionsList.cycle port crates/horizon-client/src/chat.rs::ChatScreen::cycle moves the selection and the scroll offset
|
||||
CommandSuggestions.hasAllowedInput port crates/horizon-client-render/src/chat.rs::ChatAbilities::allows gap: minor: a /say style command is judged as a command, not a message (see hasMessageArguments)
|
||||
CommandSuggestions.SuggestionsList.extractRenderState port crates/horizon-client-render/src/chat.rs::ChatScreen::draw_list rows, scroll marks, highlight and tooltip; gap: minor: no pointing-hand cursor over the rows
|
||||
CommandSuggestions.SuggestionsList.mouseClicked port crates/horizon-client-render/src/chat.rs::ChatScreen::click row click selects and uses the suggestion
|
||||
CommandSuggestions.SuggestionsList.keyPressed port crates/horizon-client-render/src/chat.rs::ChatScreen::key up, down and tab cycle and use the suggestion
|
||||
CommandSuggestions.SuggestionsList.cycle port crates/horizon-client-render/src/chat.rs::ChatScreen::cycle moves the selection and the scroll offset
|
||||
CommandSuggestions.SuggestionsList.getNarrationMessage skip:other narration text; no narrator in Horizon
|
||||
ComponentRenderUtils.stripColor port crates/horizon-client/src/chat.rs::Chat::displayed with Chat Colors off the section codes are stripped before wrapping
|
||||
ComponentRenderUtils.stripColor port crates/horizon-client-render/src/chat.rs::Chat::displayed with Chat Colors off the section codes are stripped before wrapping
|
||||
ContainerObjectSelectionList.nextFocusPath port crates/horizon-client-input/src/gamepad.rs::focus_step gap: minor: picks the nearest target by geometry, not row by row with a wrap to the next entry
|
||||
ContainerObjectSelectionList.setFocused missing minor: unsure: no list focus model beyond gamepad focus_step; losing focus does not clear a selection here
|
||||
ContainerObjectSelectionList.updateWidgetNarration skip:other narration text; no narrator in Horizon
|
||||
@@ -61,12 +61,12 @@ ContainerObjectSelectionList.Entry.setFocused missing minor: unsure: focus insi
|
||||
ContainerObjectSelectionList.Entry.focusPathAtIndex missing minor: unsure: no per-row focus path; see focus_step
|
||||
ContainerObjectSelectionList.Entry.nextFocusPath port crates/horizon-client-input/src/gamepad.rs::focus_step gap: minor: moves by nearest target rectangle, not by child index within the row
|
||||
ContainerObjectSelectionList.Entry.updateNarration skip:other narration text; no narrator in Horizon
|
||||
CycleButton.extractContents port crates/horizon-client/src/options_ui.rs::OptionsUi::draw option button sprite and its label
|
||||
CycleButton.onPress port crates/horizon-client/src/options_ui.rs::Opt::cycle click cycles forward; the back flag comes from the caller
|
||||
CycleButton.extractContents port crates/horizon-client-render/src/options_ui.rs::OptionsUi::draw option button sprite and its label
|
||||
CycleButton.onPress port crates/horizon-client-render/src/options_ui.rs::Opt::cycle click cycles forward; the back flag comes from the caller
|
||||
CycleButton.getCycledValue skip:other only feeds the narration of the next value
|
||||
CycleButton.mouseScrolled missing player: the mouse wheel over an option button does not change its value; OptionsUi::scroll_at only scrolls the list
|
||||
CycleButton.updateValue inline crates/horizon-client/src/options_ui.rs::Opt::cycle labels are read live from Settings (Opt::label), so no cached message needs updating
|
||||
CycleButton.createLabelForValue port crates/horizon-client/src/options_ui.rs::Opt::label value label or full name, from the setting
|
||||
CycleButton.updateValue inline crates/horizon-client-render/src/options_ui.rs::Opt::cycle labels are read live from Settings (Opt::label), so no cached message needs updating
|
||||
CycleButton.createLabelForValue port crates/horizon-client-render/src/options_ui.rs::Opt::label value label or full name, from the setting
|
||||
CycleButton.updateWidgetNarration skip:other narration text; no narrator in Horizon
|
||||
CycleButton.createDefaultNarrationMessage skip:other narration text; no narrator in Horizon
|
||||
CycleButton.builder skip:java builder
|
||||
@@ -93,18 +93,18 @@ DebugScreenOverlay.toggleLightmapTexture port crates/horizon-client/src/debug_ch
|
||||
DebugScreenOverlay.toggleProfilerChart missing minor: no profiler chart (see showProfilerChart)
|
||||
DebugScreenOverlay.logRemoteSample missing minor: unsure: the debug_sample packet is only named in horizon-protocol/src/ids.rs, so a server's samples are not logged
|
||||
EditBox.createNarrationMessage skip:other narration text; no narrator in Horizon
|
||||
EditBox.getHighlighted port crates/horizon-client/src/ui.rs::EditBox::selected_text the text between cursor and highlight
|
||||
EditBox.moveCursorTo port crates/horizon-client/src/ui.rs::EditBox::set_cursor sets the cursor and keeps the highlight unless extending
|
||||
EditBox.setCursorPosition port crates/horizon-client/src/ui.rs::EditBox::set_cursor clamps and scrolls to the cursor
|
||||
EditBox.getHighlighted port crates/horizon-client-render/src/ui.rs::EditBox::selected_text the text between cursor and highlight
|
||||
EditBox.moveCursorTo port crates/horizon-client-render/src/ui.rs::EditBox::set_cursor sets the cursor and keeps the highlight unless extending
|
||||
EditBox.setCursorPosition port crates/horizon-client-render/src/ui.rs::EditBox::set_cursor clamps and scrolls to the cursor
|
||||
EditBox.canConsumeInput missing minor: unsure: no editable flag on the box; whether a box takes typed input is the caller's focus routing
|
||||
EditBox.preeditUpdated missing minor: no IME composition; Horizon has no preedit input
|
||||
EditBox.selectWord port crates/horizon-client/src/ui.rs::EditBox::click a double click selects the word (the double flag)
|
||||
EditBox.applyFormat port crates/horizon-client/src/ui.rs::EditBox::draw the format text passed to draw
|
||||
EditBox.updateTextPosition port crates/horizon-client/src/ui.rs::EditBox::text_x text_x of the shown part; text_y is its sibling
|
||||
EditBox.setMaxLength port crates/horizon-client/src/ui.rs::Caret::sync gap: minor: sets the limit only; a longer value is not cut to it
|
||||
EditBox.setFocused port crates/horizon-client/src/ui.rs::Caret::draw focus restarts the blink clock here; gap: minor: no IME or narration side effects
|
||||
EditBox.selectWord port crates/horizon-client-render/src/ui.rs::EditBox::click a double click selects the word (the double flag)
|
||||
EditBox.applyFormat port crates/horizon-client-render/src/ui.rs::EditBox::draw the format text passed to draw
|
||||
EditBox.updateTextPosition port crates/horizon-client-render/src/ui.rs::EditBox::text_x text_x of the shown part; text_y is its sibling
|
||||
EditBox.setMaxLength port crates/horizon-client-render/src/ui.rs::Caret::sync gap: minor: sets the limit only; a longer value is not cut to it
|
||||
EditBox.setFocused port crates/horizon-client-render/src/ui.rs::Caret::draw focus restarts the blink clock here; gap: minor: no IME or narration side effects
|
||||
EditBox.setEditable missing minor: unsure: no read-only flag on the box in ui.rs; a box vanilla locks would accept typing
|
||||
EditBox.setHighlightPos port crates/horizon-client/src/ui.rs::EditBox::set_cursor sets the highlight and scrolls
|
||||
EditBox.setHighlightPos port crates/horizon-client-render/src/ui.rs::EditBox::set_cursor sets the highlight and scrolls
|
||||
EditBox.setHint inline crates/horizon-client/src/title/create_world.rs::Title::create_field the hint is passed when the box is made and drawn while it is empty and unfocused
|
||||
FittingMultiLineTextWidget.setWidth skip:ui layout: wrap width
|
||||
FittingMultiLineTextWidget.minimizeHeight skip:ui layout: height to content
|
||||
@@ -130,15 +130,15 @@ LockIconButton.createNarrationMessage skip:other narration text; no narrator in
|
||||
LockIconButton.extractContents port crates/horizon-client/src/game/lan_screen.rs::Game::draw_options_view lock or unlock sprite by state
|
||||
LogoRenderer.extractRenderState port crates/horizon-client/src/title.rs::Title::draw logo top 44 rows and edition banner; gap: minor: no easter-egg logo variant
|
||||
MultiLineEditBox.updateWidgetNarration skip:other narration text; no narrator in Horizon
|
||||
MultiLineEditBox.onClick port crates/horizon-client/src/screen_draw.rs::TextField::click click places the cursor; gap: minor: no double-click word select
|
||||
MultiLineEditBox.onClick port crates/horizon-client-render/src/screen_draw.rs::TextField::click click places the cursor; gap: minor: no double-click word select
|
||||
MultiLineEditBox.onDrag missing minor: unsure: TextField has no drag selection in screen_draw.rs
|
||||
MultiLineEditBox.charTyped port crates/horizon-client/src/screen_draw.rs::TextField::insert_text typed text replaces the selection or goes at the cursor
|
||||
MultiLineEditBox.charTyped port crates/horizon-client-render/src/screen_draw.rs::TextField::insert_text typed text replaces the selection or goes at the cursor
|
||||
MultiLineEditBox.preeditUpdated missing minor: no IME composition; Horizon has no preedit input
|
||||
MultiLineEditBox.extractContents port crates/horizon-client/src/screen_draw.rs::TextField::draw placeholder, lines, caret and selection highlight
|
||||
MultiLineEditBox.extractContents port crates/horizon-client-render/src/screen_draw.rs::TextField::draw placeholder, lines, caret and selection highlight
|
||||
MultiLineEditBox.extractDecorations missing minor: no character count under a limited box; TextField has no limit
|
||||
MultiLineEditBox.scrollToCursor port crates/horizon-client/src/screen_draw.rs::TextField::scroll_to keeps the cursor line in view
|
||||
MultiLineEditBox.seekCursorScreen port crates/horizon-client/src/screen_draw.rs::TextField::click maps the pointer to a cursor position
|
||||
MultiLineEditBox.setFocused inline crates/horizon-client/src/screen_draw.rs::TextField::draw gap: minor: focus does not restart the caret blink here; the caller passes the blink clock
|
||||
MultiLineEditBox.scrollToCursor port crates/horizon-client-render/src/screen_draw.rs::TextField::scroll_to keeps the cursor line in view
|
||||
MultiLineEditBox.seekCursorScreen port crates/horizon-client-render/src/screen_draw.rs::TextField::click maps the pointer to a cursor position
|
||||
MultiLineEditBox.setFocused inline crates/horizon-client-render/src/screen_draw.rs::TextField::draw gap: minor: focus does not restart the caret blink here; the caller passes the blink clock
|
||||
MultiLineEditBox.Builder.build skip:java builder
|
||||
MultiLineTextWidget.getHeight skip:ui layout: wrapped line count times 9; Measure::split does the wrap when drawing
|
||||
MultiLineTextWidget.getFreshCacheKey skip:java cache key for the wrapped lines
|
||||
|
@@ -1,43 +1,43 @@
|
||||
MultilineTextField.setCharacterLimit port crates/horizon-client/src/game/books.rs::book_page_box gap: minor: only the book page sets a character limit (PAGE_CHARS); other MultiLineEditBox users are not ported
|
||||
MultilineTextField.setLineLimit inline crates/horizon-client/src/game/books.rs::Game::page_keep the 14-line limit (LINES) is checked in page_keep; there is no limit field
|
||||
MultilineTextField.setValue port crates/horizon-client/src/ui.rs::EditBox::set_value gap: minor: no line-limit check when the text is set (allowOverflowLineLimit path)
|
||||
MultilineTextField.insertText port crates/horizon-client/src/ui.rs::EditBox::insert_text the line limit is checked by Game::page_keep, not here
|
||||
MultilineTextField.deleteText port crates/horizon-client/src/ui.rs::EditBox::delete_chars
|
||||
MultilineTextField.getSelected port crates/horizon-client/src/ui.rs::EditBox::selected
|
||||
MultilineTextField.setValue port crates/horizon-client-render/src/ui.rs::EditBox::set_value gap: minor: no line-limit check when the text is set (allowOverflowLineLimit path)
|
||||
MultilineTextField.insertText port crates/horizon-client-render/src/ui.rs::EditBox::insert_text the line limit is checked by Game::page_keep, not here
|
||||
MultilineTextField.deleteText port crates/horizon-client-render/src/ui.rs::EditBox::delete_chars
|
||||
MultilineTextField.getSelected port crates/horizon-client-render/src/ui.rs::EditBox::selected
|
||||
MultilineTextField.getLineAtCursor port crates/horizon-client/src/game/books.rs::Game::caret_on the visual row holding the caret, with its x
|
||||
MultilineTextField.getLineView port crates/horizon-client/src/game/books.rs::Game::page_rows visual rows as character ranges
|
||||
MultilineTextField.seekCursor port crates/horizon-client/src/ui.rs::EditBox::set_cursor
|
||||
MultilineTextField.seekCursor port crates/horizon-client-render/src/ui.rs::EditBox::set_cursor
|
||||
MultilineTextField.seekCursorLine port crates/horizon-client/src/game/books.rs::Game::book_move_line gap: minor: column kept without vanilla's 2 px bias (LINE_SEEK_PIXEL_BIAS)
|
||||
MultilineTextField.seekCursorToPoint port crates/horizon-client/src/game/books.rs::Game::page_index_at
|
||||
MultilineTextField.selectWordAtCursor missing player: double-clicking a word on a book page does not select it; Game::book_page_click only sets the caret
|
||||
MultilineTextField.keyPressed port crates/horizon-client/src/game/books.rs::Game::book_input gap: player: Home and End go to the start and end of the whole page, not of the visual line (EditBox::key); Enter, arrows and Ctrl keys are handled
|
||||
MultilineTextField.getSelectedText port crates/horizon-client/src/ui.rs::EditBox::selected_text
|
||||
MultilineTextField.getSelectedText port crates/horizon-client-render/src/ui.rs::EditBox::selected_text
|
||||
MultilineTextField.getCursorLineView inline crates/horizon-client/src/game/books.rs::Game::book_move_line gap: player: Home and End do not use the caret's line range (see keyPressed)
|
||||
MultilineTextField.getNextWord port crates/horizon-client/src/ui.rs::multiline_word_position start of the next word; Ctrl+Right and Ctrl+Delete reach it through EditBox::word_position
|
||||
MultilineTextField.getNextWord port crates/horizon-client-render/src/ui.rs::multiline_word_position start of the next word; Ctrl+Right and Ctrl+Delete reach it through EditBox::word_position
|
||||
MultilineTextField.getWordEndPosition missing minor: the word end only feeds selectWordAtCursor, which is missing for book pages
|
||||
MultilineTextField.onValueChange inline crates/horizon-client/src/game/books.rs::Game::page_keep the page text is stored there after the limit check; visual rows are computed on demand by page_rows
|
||||
MultilineTextField.reflowDisplayLines port crates/horizon-client/src/game/books.rs::Game::page_rows same wrap breaks as font.split
|
||||
MultilineTextField.truncateFullText port crates/horizon-client/src/ui.rs::EditBox::set_value truncates to max_length characters
|
||||
MultilineTextField.truncateInsertionText inline crates/horizon-client/src/ui.rs::EditBox::insert_text room left under max_length
|
||||
MultilineTextField.truncateFullText port crates/horizon-client-render/src/ui.rs::EditBox::set_value truncates to max_length characters
|
||||
MultilineTextField.truncateInsertionText inline crates/horizon-client-render/src/ui.rs::EditBox::insert_text room left under max_length
|
||||
MultilineTextField.overflowsLineLimit port crates/horizon-client/src/game/books.rs::Game::page_keep counts page_rows, which adds the row after a trailing newline as vanilla does
|
||||
ObjectSelectionList.nextFocusPath missing minor: no arrow-key focus or selection in the title, server or pack lists
|
||||
ObjectSelectionList.updateWidgetNarration skip:other screen-reader narration; Horizon has no narration output
|
||||
OptionsList.addBig port crates/horizon-client/src/options_ui.rs::OptionsUi::layout Row::Big: 310 wide at width/2 - 155
|
||||
OptionsList.addHeader port crates/horizon-client/src/options_ui.rs::OptionsUi::layout gap: minor: the header is centred text, not a focusable widget
|
||||
OptionsList.addBig port crates/horizon-client-render/src/options_ui.rs::OptionsUi::layout Row::Big: 310 wide at width/2 - 155
|
||||
OptionsList.addHeader port crates/horizon-client-render/src/options_ui.rs::OptionsUi::layout gap: minor: the header is centred text, not a focusable widget
|
||||
OptionsList.findOption skip:ui widget lookup only; rows are rebuilt from Settings in OptionsUi::layout each frame
|
||||
OptionsList.applyUnsavedChanges inline crates/horizon-client/src/options_ui.rs::set_slider gap: minor: delayed sliders (render, simulation, biome blend distance, wheel sensitivity) apply on every step, not 600 ms after the last move or on close
|
||||
OptionsList.resetOption inline crates/horizon-client/src/options_ui.rs::OptionsUi::slider_shown gap: minor: only sliders show the stored value at once; other options redraw from Settings each frame
|
||||
OptionsList.Entry.big port crates/horizon-client/src/options_ui.rs::option_widget 310 wide; the row's place is OptionsUi::layout (Row::Big)
|
||||
OptionsList.Entry.small port crates/horizon-client/src/options_ui.rs::option_widget pair placement at width/2 - 155 and + 5 in OptionsUi::layout (Row::Pair)
|
||||
OptionsList.Entry.extractContent inline crates/horizon-client/src/options_ui.rs::OptionsUi::layout positions the row's widgets; OptionsUi::draw paints them
|
||||
OptionsList.applyUnsavedChanges inline crates/horizon-client-render/src/options_ui.rs::set_slider gap: minor: delayed sliders (render, simulation, biome blend distance, wheel sensitivity) apply on every step, not 600 ms after the last move or on close
|
||||
OptionsList.resetOption inline crates/horizon-client-render/src/options_ui.rs::OptionsUi::slider_shown gap: minor: only sliders show the stored value at once; other options redraw from Settings each frame
|
||||
OptionsList.Entry.big port crates/horizon-client-render/src/options_ui.rs::option_widget 310 wide; the row's place is OptionsUi::layout (Row::Big)
|
||||
OptionsList.Entry.small port crates/horizon-client-render/src/options_ui.rs::option_widget pair placement at width/2 - 155 and + 5 in OptionsUi::layout (Row::Pair)
|
||||
OptionsList.Entry.extractContent inline crates/horizon-client-render/src/options_ui.rs::OptionsUi::layout positions the row's widgets; OptionsUi::draw paints them
|
||||
OptionsList.Entry.findOption skip:ui widget lookup only; rows are rebuilt from Settings each frame
|
||||
OptionsList.HeaderEntry.extractContent inline crates/horizon-client/src/options_ui.rs::OptionsUi::layout header centred on the row (Row::Header)
|
||||
OptionsList.HeaderEntry.extractContent inline crates/horizon-client-render/src/options_ui.rs::OptionsUi::layout header centred on the row (Row::Header)
|
||||
PlainTextButton.extractContents missing player: the title's bottom copyright text that opens Credits is not a button; Horizon draws "Not affiliated with Mojang or Microsoft" there instead
|
||||
PlayerFaceExtractor.extractHat port crates/horizon-client/src/tab_list.rs::face hat layer: UV 40, flipped for Dinnerbone and Grumm
|
||||
PlayerFaceExtractor.extractHat port crates/horizon-client-render/src/tab_list.rs::face hat layer: UV 40, flipped for Dinnerbone and Grumm
|
||||
PlayerFaceWidget.extractWidgetRenderState skip:service head in the friends list (AbstractFriendsEntryContainerWidget)
|
||||
PlayerSkinWidget.extractWidgetRenderState skip:service skin preview on the chat report screen (SkinReportScreen)
|
||||
PlayerSkinWidget.onDrag skip:service drag-to-rotate of the chat report skin preview
|
||||
PlayerTabOverlay.extractTablistScore inline crates/horizon-client/src/tab_list.rs::TabList::layout the score column is drawn in layout; hearts go through TabList::hearts
|
||||
PlayerTabOverlay.extractTablistScore inline crates/horizon-client-render/src/tab_list.rs::TabList::layout the score column is drawn in layout; hearts go through TabList::hearts
|
||||
PopupScreen.init port crates/horizon-client/src/game/lan_screen.rs::Popup::new gap: minor: only the lock question (title, message, Yes and No); images and extra buttons are not ported
|
||||
PopupScreen.buildButtonRow inline crates/horizon-client/src/game/lan_screen.rs::Popup::new two buttons min((214 - 6) / 2, 150) wide, 6 apart
|
||||
PopupScreen.repositionElements inline crates/horizon-client/src/game/lan_screen.rs::Popup::new centres the popup on the screen
|
||||
@@ -58,11 +58,11 @@ ScrollableLayout.Container.setFocused missing minor: focusing a row does not sc
|
||||
ScrollableLayout.Container.setX skip:ui positions set by each screen's layout
|
||||
ScrollableLayout.Container.setY skip:ui positions set by each screen's layout
|
||||
ScrollableLayout.Container.setScrollAmount inline crates/horizon-client/src/game/server_links.rs::Game::server_links_scroll gap: minor: clamps the server links list only; other scroll areas clamp in their own code
|
||||
SelectableEntry.mouseOverIcon port crates/horizon-client/src/pack_screen.rs::PackScreen::entry_click 32 px icon of a pack row; the world row's icon uses the same test in Title::click_action
|
||||
SelectableEntry.mouseOverLeftHalf port crates/horizon-client/src/pack_screen.rs::PackScreen::entry_click left 16 px of the pack icon (unselect)
|
||||
SelectableEntry.mouseOverIcon port crates/horizon-client-render/src/pack_screen.rs::PackScreen::entry_click 32 px icon of a pack row; the world row's icon uses the same test in Title::click_action
|
||||
SelectableEntry.mouseOverLeftHalf port crates/horizon-client-render/src/pack_screen.rs::PackScreen::entry_click left 16 px of the pack icon (unselect)
|
||||
SelectableEntry.mouseOverRightHalf port crates/horizon-client/src/multiplayer.rs::over_right_half join half of a server row
|
||||
SelectableEntry.mouseOverTopRightQuarter port crates/horizon-client/src/pack_screen.rs::PackScreen::entry_click move-up arrow of a pack row
|
||||
SelectableEntry.mouseOverBottomRightQuarter port crates/horizon-client/src/pack_screen.rs::PackScreen::entry_click move-down arrow of a pack row
|
||||
SelectableEntry.mouseOverTopRightQuarter port crates/horizon-client-render/src/pack_screen.rs::PackScreen::entry_click move-up arrow of a pack row
|
||||
SelectableEntry.mouseOverBottomRightQuarter port crates/horizon-client-render/src/pack_screen.rs::PackScreen::entry_click move-down arrow of a pack row
|
||||
SelectableEntry.mouseOverTopLeftQuarter port crates/horizon-client/src/multiplayer.rs::over_top_left move-up arrow of a server row
|
||||
SelectableEntry.mouseOverBottomLeftQuarter port crates/horizon-client/src/multiplayer.rs::over_bottom_left move-down arrow of a server row
|
||||
SplashRenderer.extractRenderState missing player: the random splash line on the title screen is not drawn; Title::draw has no splash
|
||||
@@ -73,15 +73,15 @@ SpriteIconButton.Builder.sprite skip:java builder setter; Horizon draws its ico
|
||||
SpriteIconButton.Builder.build skip:java builder; no object is needed
|
||||
SpriteIconButton.CenteredIcon.extractContents skip:service FriendsButton (social)
|
||||
SpriteIconButton.TextAndIcon.extractContents skip:other no caller in 26.3
|
||||
StringWidget.setMaxWidth port crates/horizon-client/src/screen_draw.rs::string_widget clips at max_width (CLAMPED)
|
||||
StringWidget.getWidth port crates/horizon-client/src/screen_draw.rs::label_width the smaller of max width and text width
|
||||
StringWidget.visitLines port crates/horizon-client/src/screen_draw.rs::string_widget gap: minor: SCROLLING overflow (scrolling text) is not ported; CLAMPED draws "..."
|
||||
StringWidget.setMaxWidth port crates/horizon-client-render/src/screen_draw.rs::string_widget clips at max_width (CLAMPED)
|
||||
StringWidget.getWidth port crates/horizon-client-render/src/screen_draw.rs::label_width the smaller of max width and text width
|
||||
StringWidget.visitLines port crates/horizon-client-render/src/screen_draw.rs::string_widget gap: minor: SCROLLING overflow (scrolling text) is not ported; CLAMPED draws "..."
|
||||
TabButton.updateWidgetNarration skip:other screen-reader text; Horizon has no narration output
|
||||
TextCursorUtils.extractInsertCursor port crates/horizon-client/src/ui.rs::EditBox::draw insert caret is a 1 px bar; blinks every 300 ms
|
||||
TextCursorUtils.extractInsertCursor port crates/horizon-client-render/src/ui.rs::EditBox::draw insert caret is a 1 px bar; blinks every 300 ms
|
||||
Tooltip.create skip:java factory; Horizon passes the lines to ui::tooltip
|
||||
Tooltip.toCharSequence port crates/horizon-client/src/screen_draw.rs::tooltip wraps at 170 px as splitTooltip does; no per-language cache
|
||||
WidgetSprites.get port crates/horizon-client/src/ui.rs::button disabled and hovered pick the sprite; gap: minor: keyboard focus does not pick the highlighted sprite
|
||||
WidgetTooltipHolder.refreshTooltipForNextRenderPass port crates/horizon-client/src/ui.rs::tooltip gap: minor: tooltips show only under the pointer, not for keyboard focus
|
||||
Tooltip.toCharSequence port crates/horizon-client-render/src/screen_draw.rs::tooltip wraps at 170 px as splitTooltip does; no per-language cache
|
||||
WidgetSprites.get port crates/horizon-client-render/src/ui.rs::button disabled and hovered pick the sprite; gap: minor: keyboard focus does not pick the highlighted sprite
|
||||
WidgetTooltipHolder.refreshTooltipForNextRenderPass port crates/horizon-client-render/src/ui.rs::tooltip gap: minor: tooltips show only under the pointer, not for keyboard focus
|
||||
WidgetTooltipHolder.createTooltipPositioner missing minor: a keyboard-focused widget gets no tooltip placed below or above it
|
||||
DebugEntryBiome.display missing minor: F3 biome line not drawn
|
||||
DebugEntryBiome.printBiome missing minor: biome id text for the F3 biome line
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 35 and column 134.
|
@@ -1,10 +1,10 @@
|
||||
ContainerEventHandler.getChildAt port crates/horizon-client/src/pack_screen.rs::PackScreen::button_at gap: minor: says whether a button is under the pointer; list rows are hit-tested in PackScreen::click
|
||||
ContainerEventHandler.mouseReleased port crates/horizon-client/src/pack_screen.rs::PackScreen::release gap: minor: only clears list scrollbar holds; no focused child gets the release
|
||||
ContainerEventHandler.mouseDragged port crates/horizon-client/src/pack_screen.rs::PackScreen::drag drags a held list scrollbar
|
||||
ContainerEventHandler.mouseScrolled port crates/horizon-client/src/pack_screen.rs::PackScreen::scroll list under the pointer scrolls 18 px per notch
|
||||
ContainerEventHandler.keyPressed port crates/horizon-client/src/pack_screen.rs::PackScreen::shortcut gap: minor: only Ctrl+A/C/V/X here; Backspace is PackScreen::erase
|
||||
ContainerEventHandler.getChildAt port crates/horizon-client-render/src/pack_screen.rs::PackScreen::button_at gap: minor: says whether a button is under the pointer; list rows are hit-tested in PackScreen::click
|
||||
ContainerEventHandler.mouseReleased port crates/horizon-client-render/src/pack_screen.rs::PackScreen::release gap: minor: only clears list scrollbar holds; no focused child gets the release
|
||||
ContainerEventHandler.mouseDragged port crates/horizon-client-render/src/pack_screen.rs::PackScreen::drag drags a held list scrollbar
|
||||
ContainerEventHandler.mouseScrolled port crates/horizon-client-render/src/pack_screen.rs::PackScreen::scroll list under the pointer scrolls 18 px per notch
|
||||
ContainerEventHandler.keyPressed port crates/horizon-client-render/src/pack_screen.rs::PackScreen::shortcut gap: minor: only Ctrl+A/C/V/X here; Backspace is PackScreen::erase
|
||||
ContainerEventHandler.keyReleased skip:other no widget in the pack screen acts on key release
|
||||
ContainerEventHandler.charTyped port crates/horizon-client/src/pack_screen.rs::PackScreen::type_text typed text goes to the search box when it has focus
|
||||
ContainerEventHandler.charTyped port crates/horizon-client-render/src/pack_screen.rs::PackScreen::type_text typed text goes to the search box when it has focus
|
||||
ContainerEventHandler.preeditUpdated missing minor: no IME composition (preedit) anywhere in Horizon
|
||||
ContainerEventHandler.getBorderForArrowNavigation missing minor: arrow keys do not move focus; pack screen focus is set by clicks (PackScreen::set_focus)
|
||||
ContainerEventHandler.getCurrentFocusPath missing minor: no focus path; PackScreen::focus is read only inside the screen
|
||||
@@ -81,70 +81,70 @@ CodepointMap.computeIfAbsent skip:java codepoint lookup table; Horizon uses Has
|
||||
CodepointMap.remove skip:java codepoint lookup table; Horizon uses HashMap and BTreeSet
|
||||
CodepointMap.forEach skip:java codepoint lookup table; Horizon uses HashMap and BTreeSet
|
||||
CodepointMap.keySet skip:java codepoint lookup table; Horizon uses HashMap and BTreeSet
|
||||
FontManager.createFontSet port crates/horizon-client/src/font.rs::Font::load_set a font's providers into a set
|
||||
FontManager.reload port crates/horizon-client/src/hud.rs::HudAtlas::pack fonts are rebuilt with the atlas (Font::load at hud.rs:582); no async reload
|
||||
FontManager.safeLoad inline crates/horizon-client/src/font.rs::Font::load_set a TrueType provider that fails to load is dropped; a bitmap sheet that fails warns
|
||||
FontManager.resolveProviders port crates/horizon-client/src/font.rs::providers reference expansion in order, depth 8; no dependency sort
|
||||
FontManager.createFontSet port crates/horizon-client-render/src/font.rs::Font::load_set a font's providers into a set
|
||||
FontManager.reload port crates/horizon-client-render/src/hud.rs::HudAtlas::pack fonts are rebuilt with the atlas (Font::load at hud.rs:582); no async reload
|
||||
FontManager.safeLoad inline crates/horizon-client-render/src/font.rs::Font::load_set a TrueType provider that fails to load is dropped; a bitmap sheet that fails warns
|
||||
FontManager.resolveProviders port crates/horizon-client-render/src/font.rs::providers reference expansion in order, depth 8; no dependency sort
|
||||
FontManager.finalizeProviderLoading skip:java prewarm of glyph lookups; the fallback is the missing box that Font::resolve gives
|
||||
FontManager.getFontOptions port crates/horizon-client/src/game/options.rs::Game::apply_ui_settings force unicode and japanese flags read from settings
|
||||
FontManager.apply port crates/horizon-client/src/hud.rs::HudAtlas::pack new font sets replace the old ones with the atlas
|
||||
FontManager.apply port crates/horizon-client-render/src/hud.rs::HudAtlas::pack new font sets replace the old ones with the atlas
|
||||
FontManager.updateOptions port crates/horizon-client/src/game/options.rs::Game::apply_ui_settings sets the Font uniform and japanese flags when they change (options.rs:299-301)
|
||||
FontManager.loadResourceStack inline crates/horizon-client/src/font.rs::providers gap: minor: one fonts.json per font id from the resource root, not a stack of packs
|
||||
FontManager.loadResourceStack inline crates/horizon-client-render/src/font.rs::providers gap: minor: one fonts.json per font id from the resource root, not a stack of packs
|
||||
FontManager.getSpriteFont missing minor: atlas sprite fonts (sprite objects in text) are not parsed; they draw the missing box
|
||||
FontManager.close skip:java Rust drop frees the atlas, font sets and providers
|
||||
FontManager.BuilderResult.resolve port crates/horizon-client/src/font.rs::providers reference builders expanded in place
|
||||
FontManager.BuilderResult.mergeFilters inline crates/horizon-client/src/font.rs::providers the reference's filter merged onto each expanded provider
|
||||
FontManager.CachedFontProvider.getGlyphSource port crates/horizon-client/src/font.rs::Font::glyph_in gap: minor: atlas sprite and player descriptions fall back to the missing glyph
|
||||
FontManager.BuilderResult.resolve port crates/horizon-client-render/src/font.rs::providers reference builders expanded in place
|
||||
FontManager.BuilderResult.mergeFilters inline crates/horizon-client-render/src/font.rs::providers the reference's filter merged onto each expanded provider
|
||||
FontManager.CachedFontProvider.getGlyphSource port crates/horizon-client-render/src/font.rs::Font::glyph_in gap: minor: atlas sprite and player descriptions fall back to the missing glyph
|
||||
FontManager.CachedFontProvider.glyphs skip:java one-entry lookup cache
|
||||
FontManager.CachedFontProvider.effect port crates/horizon-client/src/font.rs::Font::white the white glyph under underline and strikethrough
|
||||
FontManager.CachedFontProvider.effect port crates/horizon-client-render/src/font.rs::Font::white the white glyph under underline and strikethrough
|
||||
FontManager.UnresolvedBuilderBundle.add skip:java collects builders; providers() reads them directly
|
||||
FontManager.UnresolvedBuilderBundle.listBuilders skip:java collects builders
|
||||
FontManager.UnresolvedBuilderBundle.resolve port crates/horizon-client/src/font.rs::providers reference expansion for the bundle
|
||||
FontOption.Filter.apply port crates/horizon-client/src/font.rs::Filter::passes option match per provider
|
||||
FontOption.Filter.merge inline crates/horizon-client/src/font.rs::providers filter merge while expanding references
|
||||
FontSet.reload port crates/horizon-client/src/font.rs::Font::load_set providers into the font set
|
||||
FontSet.resetTextures port crates/horizon-client/src/font.rs::Font::reset_cache glyph cache and atlas cursor cleared
|
||||
FontSet.selectProviders port crates/horizon-client/src/font.rs::Font::glyphs_by_width gap: minor: width buckets hold only Space and atlas bitmap glyphs, so TrueType and unihex never appear as obfuscated characters
|
||||
FontManager.UnresolvedBuilderBundle.resolve port crates/horizon-client-render/src/font.rs::providers reference expansion for the bundle
|
||||
FontOption.Filter.apply port crates/horizon-client-render/src/font.rs::Filter::passes option match per provider
|
||||
FontOption.Filter.merge inline crates/horizon-client-render/src/font.rs::providers filter merge while expanding references
|
||||
FontSet.reload port crates/horizon-client-render/src/font.rs::Font::load_set providers into the font set
|
||||
FontSet.resetTextures port crates/horizon-client-render/src/font.rs::Font::reset_cache glyph cache and atlas cursor cleared
|
||||
FontSet.selectProviders port crates/horizon-client-render/src/font.rs::Font::glyphs_by_width gap: minor: width buckets hold only Space and atlas bitmap glyphs, so TrueType and unihex never appear as obfuscated characters
|
||||
FontSet.hasFishyAdvance missing minor: no check for advances under 0 or over 32
|
||||
FontSet.computeGlyphInfo port crates/horizon-client/src/font.rs::Font::resolve gap: minor: no skip of a glyph with a fishy advance in favour of a later provider
|
||||
FontSet.computeGlyphInfo port crates/horizon-client-render/src/font.rs::Font::resolve gap: minor: no skip of a glyph with a fishy advance in favour of a later provider
|
||||
FontSet.DelayedBake.get skip:java lazy bake into the atlas; Font::resolve bakes at once
|
||||
FontTexture.dumpContents skip:debug writes the atlas to a PNG for debugging
|
||||
GlyphRenderTypes.createForGrayscaleTexture skip:ui one textured quad path for all glyphs (hud.rs text_styled)
|
||||
GlyphRenderTypes.createForColorTexture skip:ui one textured quad path for all glyphs (hud.rs text_styled)
|
||||
GlyphRenderTypes.select port crates/horizon-client/src/game/names.rs::Game::name_tag_geometry gap: minor: normal and see-through only; polygon-offset text has no draw path
|
||||
GlyphStitcher.reset port crates/horizon-client/src/font.rs::Font::reset_cache atlas cursor rewound
|
||||
GlyphStitcher.stitch port crates/horizon-client/src/font.rs::Region::place rectangle placed in the atlas region
|
||||
PlainTextRenderable.render port crates/horizon-client/src/hud.rs::HudBatch::text_styled shadow pass first, then the front text
|
||||
GlyphStitcher.reset port crates/horizon-client-render/src/font.rs::Font::reset_cache atlas cursor rewound
|
||||
GlyphStitcher.stitch port crates/horizon-client-render/src/font.rs::Region::place rectangle placed in the atlas region
|
||||
PlainTextRenderable.render port crates/horizon-client-render/src/hud.rs::HudBatch::text_styled shadow pass first, then the front text
|
||||
PlayerGlyphProvider.Instance.renderSprite missing minor: player-head text objects are not parsed
|
||||
PlayerGlyphProvider.Instance.renderQuad missing minor: no player-head glyphs (same gap)
|
||||
TextFieldHelper.charTyped inline crates/horizon-client/src/game/sign_edit.rs::Game::sign_input typed-text branch; EditBox::insert_text filters the text
|
||||
TextFieldHelper.moveBy inline crates/horizon-client/src/ui.rs::EditBox::key arrow keys; Ctrl moves by word
|
||||
TextFieldHelper.moveByChars inline crates/horizon-client/src/ui.rs::EditBox::key arrow keys, Home and End
|
||||
TextFieldHelper.moveByWords inline crates/horizon-client/src/ui.rs::EditBox::key Ctrl+arrow word moves
|
||||
TextFieldHelper.removeFromCursor inline crates/horizon-client/src/ui.rs::EditBox::key Backspace and Delete
|
||||
TextFieldHelper.removeWordsFromCursor inline crates/horizon-client/src/ui.rs::EditBox::key Ctrl+Backspace and Ctrl+Delete
|
||||
TextFieldHelper.removeCharsFromCursor port crates/horizon-client/src/ui.rs::EditBox::delete_chars deletes the selection or n characters
|
||||
TextFieldHelper.moveBy inline crates/horizon-client-render/src/ui.rs::EditBox::key arrow keys; Ctrl moves by word
|
||||
TextFieldHelper.moveByChars inline crates/horizon-client-render/src/ui.rs::EditBox::key arrow keys, Home and End
|
||||
TextFieldHelper.moveByWords inline crates/horizon-client-render/src/ui.rs::EditBox::key Ctrl+arrow word moves
|
||||
TextFieldHelper.removeFromCursor inline crates/horizon-client-render/src/ui.rs::EditBox::key Backspace and Delete
|
||||
TextFieldHelper.removeWordsFromCursor inline crates/horizon-client-render/src/ui.rs::EditBox::key Ctrl+Backspace and Ctrl+Delete
|
||||
TextFieldHelper.removeCharsFromCursor port crates/horizon-client-render/src/ui.rs::EditBox::delete_chars deletes the selection or n characters
|
||||
TextFieldHelper.cut port crates/horizon-client-input/src/clipboard.rs::apply Cut arm: copy the selection, then clear it
|
||||
TextFieldHelper.paste port crates/horizon-client-input/src/clipboard.rs::apply Paste arm: insert the clipboard text
|
||||
TextFieldHelper.getSelected port crates/horizon-client/src/ui.rs::EditBox::selected_text
|
||||
TextFieldHelper.deleteSelection inline crates/horizon-client/src/ui.rs::EditBox::delete_chars selection branch
|
||||
TextFieldHelper.setCursorToEnd port crates/horizon-client/src/ui.rs::EditBox::move_cursor_to_end
|
||||
TextFieldHelper.setCursorPos port crates/horizon-client/src/ui.rs::EditBox::set_cursor no vanilla caller (callers 0)
|
||||
TextFieldHelper.getSelected port crates/horizon-client-render/src/ui.rs::EditBox::selected_text
|
||||
TextFieldHelper.deleteSelection inline crates/horizon-client-render/src/ui.rs::EditBox::delete_chars selection branch
|
||||
TextFieldHelper.setCursorToEnd port crates/horizon-client-render/src/ui.rs::EditBox::move_cursor_to_end
|
||||
TextFieldHelper.setCursorPos port crates/horizon-client-render/src/ui.rs::EditBox::set_cursor no vanilla caller (callers 0)
|
||||
TextFieldHelper.setSelectionRange skip:other no vanilla caller; Horizon sets cursor and highlight separately (EditBox::set_cursor)
|
||||
BakedSheetGlyph.left inline crates/horizon-client/src/hud.rs::HudBatch::text_styled glyph edge with bold thickness and italic shear
|
||||
BakedSheetGlyph.top inline crates/horizon-client/src/hud.rs::HudBatch::text_styled glyph edge with bold thickness and italic shear
|
||||
BakedSheetGlyph.right inline crates/horizon-client/src/hud.rs::HudBatch::text_styled glyph edge with bold thickness and italic shear
|
||||
BakedSheetGlyph.bottom inline crates/horizon-client/src/hud.rs::HudBatch::text_styled glyph edge with bold thickness and italic shear
|
||||
BakedSheetGlyph.renderChar inline crates/horizon-client/src/hud.rs::HudBatch::text_styled shadow pass, bold double draw, italic shear
|
||||
BakedSheetGlyph.render inline crates/horizon-client/src/hud.rs::HudBatch::text_styled glyph quad with shear and bold
|
||||
BakedSheetGlyph.renderEffect inline crates/horizon-client/src/hud.rs::HudBatch::text_styled underline and strikethrough rectangles
|
||||
BakedSheetGlyph.buildEffect inline crates/horizon-client/src/hud.rs::HudBatch::text_styled rectangle quad of an effect
|
||||
BakedSheetGlyph.left inline crates/horizon-client-render/src/hud.rs::HudBatch::text_styled glyph edge with bold thickness and italic shear
|
||||
BakedSheetGlyph.top inline crates/horizon-client-render/src/hud.rs::HudBatch::text_styled glyph edge with bold thickness and italic shear
|
||||
BakedSheetGlyph.right inline crates/horizon-client-render/src/hud.rs::HudBatch::text_styled glyph edge with bold thickness and italic shear
|
||||
BakedSheetGlyph.bottom inline crates/horizon-client-render/src/hud.rs::HudBatch::text_styled glyph edge with bold thickness and italic shear
|
||||
BakedSheetGlyph.renderChar inline crates/horizon-client-render/src/hud.rs::HudBatch::text_styled shadow pass, bold double draw, italic shear
|
||||
BakedSheetGlyph.render inline crates/horizon-client-render/src/hud.rs::HudBatch::text_styled glyph quad with shear and bold
|
||||
BakedSheetGlyph.renderEffect inline crates/horizon-client-render/src/hud.rs::HudBatch::text_styled underline and strikethrough rectangles
|
||||
BakedSheetGlyph.buildEffect inline crates/horizon-client-render/src/hud.rs::HudBatch::text_styled rectangle quad of an effect
|
||||
BakedSheetGlyph.createGlyph skip:java factory for a glyph instance
|
||||
BakedSheetGlyph.createEffect skip:java factory for an effect instance
|
||||
BakedSheetGlyph.EffectInstance.right inline crates/horizon-client/src/hud.rs::HudBatch::text_styled underline end: pen x plus advance
|
||||
BakedSheetGlyph.EffectInstance.bottom inline crates/horizon-client/src/hud.rs::HudBatch::text_styled underline row, 1 px at pen y plus 8
|
||||
BakedSheetGlyph.GlyphInstance.activeRight inline crates/horizon-client/src/hud.rs::HudBatch::text_styled advance including the bold offset
|
||||
SpecialGlyphs.generate port crates/horizon-client/src/font.rs::Font::white gap: minor: white texel only; the missing-box outline is drawn in HudBatch::text_styled, not generated
|
||||
BakedSheetGlyph.EffectInstance.right inline crates/horizon-client-render/src/hud.rs::HudBatch::text_styled underline end: pen x plus advance
|
||||
BakedSheetGlyph.EffectInstance.bottom inline crates/horizon-client-render/src/hud.rs::HudBatch::text_styled underline row, 1 px at pen y plus 8
|
||||
BakedSheetGlyph.GlyphInstance.activeRight inline crates/horizon-client-render/src/hud.rs::HudBatch::text_styled advance including the bold offset
|
||||
SpecialGlyphs.generate port crates/horizon-client-render/src/font.rs::Font::white gap: minor: white texel only; the missing-box outline is drawn in HudBatch::text_styled, not generated
|
||||
BitmapProvider.Definition.validateDimensions missing minor: no check of the bitmap grid; a ragged grid loads
|
||||
BitmapProvider.Definition.validate missing minor: no check that ascent is at most height
|
||||
BitmapProvider.Definition.getActualGlyphWidth port crates/horizon-client/src/font.rs::used_width rightmost column with any visible pixel
|
||||
BitmapProvider.Definition.getActualGlyphWidth port crates/horizon-client-render/src/font.rs::used_width rightmost column with any visible pixel
|
||||
|
Loaded 100 of 145 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user