Compare commits

..
Author SHA1 Message Date
Horizon agent ca17c0c5ba Merge remote-tracking branch 'origin/main' into vanilla-split/extras-feature
# Conflicts:
#	crates/horizon-client/src/game.rs
2026-11-07 21:55:27 +01:00
Horizon agentandClaude Opus 5.5 abaa2a9f68 Put Horizon's extras behind a horizon-extras feature for forks
One Cargo feature, on by default, holds the entry points of Horizon's own
extras so a fork can build the game without them
(`--no-default-features --features audio,controllers`) or delete them:
cosmetics, emotes and the "Horizon player" presence (one gate,
game::cosmetics::enabled), the official and partner servers added to
servers.dat, the Options screen's Horizon Settings and Mods buttons, and
the existing discord, friends and heartbeat features it now turns on.

The default build resolves the same features as before and every gate is
cfg!(), so both sides compile in every build. A build without the extras
names the official build on Credits & Attribution. docs/forking.md lists
the extras, their files and their gates.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 20:18:11 +01:00
32 changed files with 167 additions and 37 deletions

No files matched your search

@@ -20,6 +20,14 @@ use crate::{
fn tr(key: &str) -> Text {
Text::translate(key, &[])
}
/// False only in a game built without the `horizon-extras` feature (docs/forking.md), which says
/// so once at startup: the Options screen then has vanilla's Telemetry Data slot instead of
/// Horizon Settings and Mods, and Credits & Attribution names the official build.
pub static HORIZON_EXTRAS: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
fn horizon_extras() -> bool {
HORIZON_EXTRAS.load(std::sync::atomic::Ordering::Relaxed)
}
fn generic(caption: &str, value: Text) -> Text {
Text::translate("options.generic_value", &[tr(caption), value])
}
@@ -3113,6 +3121,22 @@ impl OptionsUi {
("horizon.pages.open.mods", Some(Page::Mods)),
("options.credits_and_attribution", Some(Page::Credits)),
];
// horizon-extras: without it, vanilla's last row instead of Horizon Settings and
// Mods: Telemetry Data (greyed: nothing is sent) and Credits & Attribution.
let plain;
let entries: &[_] = if horizon_extras() {
&entries
} else {
plain = [
&entries[..entries.len() - 3],
&[
("options.telemetry", None),
("options.credits_and_attribution", Some(Page::Credits)),
],
]
.concat();
&plain
};
let rows = entries.len().div_ceil(2) as f32;
let gy = (61.0 + 30.0f32).min(h - 33.0 - rows * 24.0);
for (i, (key, page)) in entries.iter().enumerate() {
@@ -3162,6 +3186,22 @@ impl OptionsUi {
let y = y + 28.0 * (i + 1) as f32;
out.push(inactive(button(x, y, 210.0, tr(key), Act::None)));
}
// horizon-extras: a build without them says where the official one is.
if !horizon_extras() {
out.push(Widget {
x: 0.0,
y: (y + 92.0).min(h - 42.0),
w,
h: 9.0,
kind: WKind::Text {
text: tr("horizon.plain.official"),
color: 0xFFA0A0A0,
centered: true,
},
list: false,
row: 0,
});
}
out.push(done(h - 27.0));
top = 0.0;
bottom = 0.0;
+8 -1
View File
@@ -109,12 +109,19 @@ objc2-foundation = { version = "0.3", default-features = false, features = ["std
wgpu = { version = "30.0.1", default-features = false, features = ["gles"] }
[features]
default = ["audio", "controllers", "discord", "friends", "heartbeat"]
default = ["audio", "controllers", "horizon-extras"]
# Sound output. Without it (and `controllers`) the program links neither libasound nor libudev on
# Linux: the server build (`--no-default-features`, released as horizon-engine; docs/server.md).
audio = ["horizon-client-render/audio", "horizon-client-lib/audio"]
# Controllers: gilrs, and the DualSense's own output. Without it only `horizon-ctl pad`'s virtual pad.
controllers = ["horizon-client-lib/controllers", "dep:gilrs", "dep:horizon-dualsense", "horizon-client-input/controllers"]
# Horizon's own extras over the vanilla game, for a fork that wants them out (docs/forking.md):
# cosmetics and emotes, the "Horizon player" presence, the official and partner servers added to
# the server list, the Horizon Settings and Mods pages, Discord, Play with Friends and the
# heartbeat. Players always get them; there is no switch. `grep -rn horizon-extras crates/` lists
# every place it gates. Without it (`--no-default-features --features audio,controllers`) the
# client is best effort: CI builds only the default.
horizon-extras = ["discord", "friends", "heartbeat"]
# Online extras a server or a port can leave out; the server build has none of them. "Playing Horizon"
# on Discord (game/discord.rs).
discord = ["horizon-client-lib/discord"]
@@ -226,6 +226,7 @@
"Mesh data sent to the GPU per frame. Lower is smoother while loading.": "Pro Bild an die GPU gesendete Mesh-Daten. Weniger ist flüssiger beim Laden.",
"Frame rate and frame times over the game, with a graph.": "Bildrate und Bildzeiten über dem Spiel, mit Diagramm.",
"horizon.multiplayer.finding_friend": "Die Welt deines Freundes wird gesucht …",
"horizon.plain.official": "Offizielles Horizon mit Launcher, Mods und Updates: playhorizon.dev",
"horizon.title.not_affiliated": "Nicht mit Mojang oder Microsoft verbunden",
"horizon.extras.header.lighting": "Beleuchtung",
"horizon.extras.header.third_person": "Dritte Person",
@@ -366,6 +366,7 @@
"Brighter-than-white output on an HDR display.": "Brighter-than-white output on an HDR display.",
"Results for \"%s\"": "Results for \"%s\"",
"No settings match.": "No settings match.",
"horizon.plain.official": "Official Horizon with launcher, mods and updates: playhorizon.dev",
"horizon.title.not_affiliated": "Not affiliated with Mojang or Microsoft",
"horizon.extras.header.lighting": "Lighting",
"horizon.extras.header.third_person": "Third person",
@@ -295,6 +295,7 @@
"Brighter-than-white output on an HDR display.": "Salida más brillante que el blanco en una pantalla HDR.",
"Results for \"%s\"": "Resultados de \"%s\"",
"No settings match.": "Ningún ajuste coincide.",
"horizon.plain.official": "Horizon oficial con launcher, mods y actualizaciones: playhorizon.dev",
"horizon.title.not_affiliated": "No está afiliado a Mojang ni a Microsoft",
"horizon.extras.header.lighting": "Iluminación",
"horizon.extras.header.third_person": "Tercera persona",
@@ -188,6 +188,7 @@
"Mesh data sent to the GPU per frame. Lower is smoother while loading.": "Données de maillage vers le GPU par image. Moins : plus fluide.",
"Frame rate and frame times over the game, with a graph.": "Fréquence et temps d'image par-dessus le jeu, avec un graphique.",
"horizon.multiplayer.finding_friend": "Recherche du monde de votre ami...",
"horizon.plain.official": "Horizon officiel avec launcher, mods et mises à jour : playhorizon.dev",
"horizon.title.not_affiliated": "Non affilié à Mojang ou à Microsoft",
"horizon.extras.header.lighting": "Éclairage",
"horizon.extras.header.third_person": "Troisième personne",
@@ -231,6 +231,7 @@
"Brighter-than-white output on an HDR display.": "Uscita più luminosa del bianco su un display HDR.",
"Results for \"%s\"": "Risultati per \"%s\"",
"No settings match.": "Nessuna impostazione corrisponde.",
"horizon.plain.official": "Horizon ufficiale con launcher, mod e aggiornamenti: playhorizon.dev",
"horizon.title.not_affiliated": "Non affiliato con Mojang o Microsoft",
"horizon.extras.header.lighting": "Illuminazione",
"horizon.extras.header.third_person": "Terza persona",
@@ -29,6 +29,7 @@
"horizon.pages.extras": "Horizon エクストラ",
"horizon.pages.settings": "Horizon 設定",
"horizon.pages.agent": "AIエージェント",
"horizon.plain.official": "ランチャー、Mod、アップデート付きの公式 Horizon: playhorizon.dev",
"horizon.split.leave": "休暇を取る",
"horizon.pages.split_settings": "分割画面設定",
"horizon.pages.split_profiles": "誰が遊んでるの?",
@@ -26,6 +26,7 @@
"horizon.pages.extras": "Horizon 추가 기능",
"horizon.pages.settings": "Horizon 설정",
"horizon.pages.agent": "AI 에이전트",
"horizon.plain.official": "런처, 모드, 업데이트가 포함된 공식 Horizon: playhorizon.dev",
"horizon.split.leave": "나가기",
"horizon.pages.split_settings": "화면 분할 설정",
"horizon.pages.split_profiles": "플레이어 선택",
@@ -223,6 +223,7 @@
"Brighter-than-white output on an HDR display.": "Helderder dan witte uitvoer op een HDR-scherm.",
"Results for \"%s\"": "Resultaten voor \"%s\"",
"No settings match.": "Er komen geen instellingen overeen.",
"horizon.plain.official": "Officiële Horizon met launcher, mods en updates: playhorizon.dev",
"horizon.title.not_affiliated": "Niet aangesloten bij Mojang of Microsoft",
"horizon.extras.header.lighting": "Verlichting",
"horizon.extras.header.third_person": "Derde persoon",
@@ -222,6 +222,7 @@
"Brighter-than-white output on an HDR display.": "Obraz jaśniejszy niż biel na ekranie HDR.",
"Results for \"%s\"": "Wyniki dla \"%s\"",
"No settings match.": "Żadne ustawienie nie pasuje.",
"horizon.plain.official": "Oficjalny Horizon z launcherem, modami i aktualizacjami: playhorizon.dev",
"horizon.title.not_affiliated": "Niepowiązane z Mojang ani Microsoft",
"horizon.extras.header.lighting": "Oświetlenie",
"horizon.extras.header.third_person": "Widok z trzeciej osoby",
@@ -233,6 +233,7 @@
"Brighter-than-white output on an HDR display.": "Saída mais brilhante que o branco em uma tela HDR.",
"Results for \"%s\"": "Resultados para \"%s\"",
"No settings match.": "Nenhuma configuração corresponde.",
"horizon.plain.official": "Horizon oficial com launcher, mods e atualizações: playhorizon.dev",
"horizon.title.not_affiliated": "Não afiliado à Mojang ou Microsoft",
"horizon.extras.header.lighting": "Iluminação",
"horizon.extras.header.third_person": "Terceira pessoa",
@@ -185,6 +185,7 @@
"Frame rate and frame times over the game, with a graph.": "Частота кадров и их время поверх игры, с графиком.",
"horizon.multiplayer.finding_friend": "Ищем мир друга…",
"horizon.multiplayer.not_signed_in": "Этот сервер работает в онлайн-режиме. Войдите с помощью аккаунта Microsoft в лаунчере Horizon, чтобы присоединиться.",
"horizon.plain.official": "Официальный Horizon с лаунчером, модами и обновлениями: playhorizon.dev",
"horizon.realms.not_signed_in": "Войдите в лаунчере Horizon с учётной записью Microsoft, чтобы играть в Realms.",
"horizon.multiplayer.invalid_address": "Недействительный адрес сервера: %s",
"horizon.multiplayer.connected_friend_direct": "Установлено прямое соединение с другом…",
@@ -187,6 +187,7 @@
"horizon.pages.shader_options": "Gölgelendirici Seçenekleri",
"horizon.pages.split_profiles": "Kim oynuyor?",
"horizon.pages.split_profiles.tooltip": "Kim oynuyor: bu oyun klasöründe tutulan bir profil.",
"horizon.plain.official": "Başlatıcı, modlar ve güncellemelerle resmi Horizon: playhorizon.dev",
"horizon.shader_options.profile": "Profil",
"Particle Culling": "Particle Culling",
"Draws every particle.": "Draws every particle.",
@@ -57,6 +57,7 @@
"horizon.mods.dev": " (розробка)",
"horizon.mods.settings": "Налаштування",
"horizon.mods.open_folder": "Відкрити теку з модами",
"horizon.plain.official": "Офіційний Horizon із лаунчером, модами та оновленнями: playhorizon.dev",
"horizon.shader_options.profile": "Профіль",
"horizon.agent.copy_details": "Копіювати дані з’єднання",
"horizon.multiplayer.join": "Приєднатися",
@@ -195,6 +195,7 @@
"horizon.pages.shader_options": "光影选项",
"horizon.pages.split_profiles": "谁在玩?",
"horizon.pages.split_profiles.tooltip": "谁在玩:保存在此游戏文件夹中的一份档案。",
"horizon.plain.official": "带启动器、模组和更新的官方 Horizon:playhorizon.dev",
"horizon.shader_options.profile": "档案",
"General": "常规",
"Advanced": "高级",
+5
View File
@@ -13,6 +13,11 @@ pub fn main_with(mods: crate::mod_api::Mods) {
log::warn!("Trait mods already installed; ignoring the second list");
}
horizon_threads::configure_allocator();
// horizon-extras (docs/forking.md): without the feature, no Horizon Settings or Mods page.
if !cfg!(feature = "horizon-extras") {
horizon_client::options_ui::HORIZON_EXTRAS
.store(false, std::sync::atomic::Ordering::Relaxed);
}
horizon_client::telemetry::mark_start();
horizon_client::spans::startup::init(std::time::Instant::now());
horizon_client::spans::trace::start_from_env();
+7 -2
View File
@@ -17100,10 +17100,15 @@ pub fn run_with() -> Result<()> {
#[cfg(feature = "heartbeat")]
horizon_client::heartbeat::start();
let player_dir = horizon_client::logging::player_dir();
if let Err(e) = horizon_client::multiplayer::add_official_server(&player_dir) {
// horizon-extras: the official and partner servers are Horizon's (docs/forking.md).
if cfg!(feature = "horizon-extras")
&& let Err(e) = horizon_client::multiplayer::add_official_server(&player_dir)
{
log::warn!("could not add the official server to servers.dat: {e}");
}
if let Err(e) = horizon_client::multiplayer::add_partner_servers(&player_dir) {
if cfg!(feature = "horizon-extras")
&& let Err(e) = horizon_client::multiplayer::add_partner_servers(&player_dir)
{
log::warn!("could not add the partner servers to servers.dat: {e}");
}
}
+10 -3
View File
@@ -1,5 +1,5 @@
//! Cosmetics on players ([docs/cosmetics.md](../../../../docs/cosmetics.md)): only Horizon
//! clients draw them, off unless `horizon_cosmetics::enabled()`. What another player wears comes
//! clients draw them, off unless [`enabled`]. What another player wears comes
//! from the cosmetics service by UUID (`horizon_cosmetics::Cosmetics`, its own thread); this file
//! puts the items on the avatar's bones, on the GPU skin path when the renderer takes it.
//!
@@ -433,11 +433,18 @@ fn body_top(
.reduce(f32::max)
}
/// Whether cosmetics, emotes and the "Horizon player" presence run at all: the `horizon-extras`
/// feature's gate for all three (docs/forking.md), then `horizon_cosmetics::enabled()`. The game
/// asks this, never `horizon_cosmetics::enabled()` itself.
pub(super) fn enabled() -> bool {
cfg!(feature = "horizon-extras") && horizon_cosmetics::enabled()
}
impl Game<'_> {
/// Once a frame, before drawing: starts the service's client on first use and puts the
/// textures the last frame wanted into the atlas.
pub(super) fn cosmetics_frame(&mut self) {
if !horizon_cosmetics::enabled() {
if !enabled() {
return;
}
let c = &mut self.cosmetics;
@@ -644,7 +651,7 @@ impl Game<'_> {
const AGAIN: Duration = Duration::from_secs(600);
let e = &mut self.emotes;
if self.cosmetics.local
|| !horizon_cosmetics::enabled()
|| !enabled()
|| horizon_client::auth::current().is_none()
|| e.loading.is_some()
|| e.loaded.is_some_and(|at| at.elapsed() < AGAIN)
@@ -255,7 +255,7 @@ impl Game<'_> {
alpha: f64,
out: &mut Vec<Vertex>,
) {
if !horizon_cosmetics::enabled() {
if !super::enabled() {
return;
}
if !self.own_body_in_view()
+1 -1
View File
@@ -7666,7 +7666,7 @@ impl Game<'_> {
// Cosmetics (game/cosmetics.rs), posed on the GPU with the mob's own jobs: culled
// with it.
if !layers_only
&& horizon_cosmetics::enabled()
&& super::cosmetics::enabled()
&& (is_player || &*mob.kind.id == "minecraft:mannequin")
{
let mark = self.skin_frame.borrow().mark();
+1 -1
View File
@@ -87,7 +87,7 @@ impl Game<'_> {
let Some(p) = self.player.as_ref() else {
return false;
};
horizon_cosmetics::enabled()
super::cosmetics::enabled()
&& self.player_model.is_some()
&& !p.health.dead_or_dying()
&& !p.fall_flying
@@ -18,7 +18,7 @@ impl Game<'_> {
/// or the server stops, not saved: keep what you hold (a level
/// attachment) and hold it again in `native_level_load`. True when the
/// set of owners changed; false for an owner without a namespace, a chunk
/// past the world's edge (as `/forceload` refuses it) and off a server. API 412.
/// past the world's edge (as `/forceload` refuses it) and off a server. API @API:chunk_tickets@.
pub(super) fn native_force_chunk(&mut self, owner: &str, chunk: [i32; 2], hold: bool) -> bool {
self.native_chunk_ticket(self.dim, owner, chunk, hold)
}
@@ -28,7 +28,7 @@ impl Game<'_> {
/// level held this way opens at the next server tick and ticks from then
/// on, so a mod can generate or run the Nether and the End from any
/// server hook. False also for a dimension other than the Overworld, the
/// Nether and the End. API 413.
/// Nether and the End. API @API:chunk_tickets_in_level@.
pub(super) fn native_force_chunk_in(
&mut self,
owner: &str,
@@ -103,7 +103,7 @@ impl Game<'_> {
}
/// The chunks of the level in place `owner` holds (`native_force_chunk`),
/// sorted. API 412.
/// sorted. API @API:chunk_tickets@.
pub(super) fn native_forced_chunks(&self, owner: &str) -> Vec<[i32; 2]> {
let mut held: Vec<[i32; 2]> = self
.mod_chunk_tickets
@@ -6,10 +6,6 @@
//! Horizon's own HUD extras do, and keep a mod's settings where the
//! WebAssembly mod of the same id kept them.
//!
//! API 413 adds chunk tickets in a level nobody is in (`native_force_chunk_in`).
//!
//! API 412 adds chunks a mod keeps loaded and ticking (`native_force_chunk`, `native_forced_chunks`).
//!
//! API 411 adds client Host services, GUI painting, model-head snapshots and client lifecycle notifications.
//!
//! API 410 adds a final foreground GUI overlay (`native_gui_render_post`) and the current frame's camera during GUI rendering.
@@ -1171,7 +1167,7 @@ pub(super) use horizon_world::level::flags;
/// The hook points' API version. The launcher reads this line from the
/// source it builds; keep its form.
pub(crate) const NATIVE_HOOKS_API: u32 = 413;
pub(crate) const NATIVE_HOOKS_API: u32 = 411;
impl GameLevel<'_> {
/// A hopper at `pos` is about to pick up `count` of `item`. Set `cancel` to leave the dropped stack in the world. Server-side. 232.
@@ -790,7 +790,7 @@ impl Game<'_> {
packed,
out,
);
if horizon_cosmetics::enabled() {
if super::cosmetics::enabled() {
let base = horizon_client::mobs::render::base_transform(m, alpha as f32, camera);
self.cosmetics_geometry(
m,
@@ -916,7 +916,7 @@ impl Game<'_> {
240 | 240 << 8,
&mut verts,
);
if horizon_cosmetics::enabled() {
if super::cosmetics::enabled() {
let base = horizon_client::mobs::render::base_transform(&m, 1.0, camera);
self.cosmetics_geometry(&m, true, base, None, 1.0, 240 | 240 << 8, false, &mut verts);
}
+1 -1
View File
@@ -147,7 +147,7 @@ impl Game<'_> {
}
KeyCode::KeyL if self.screen == Screen::Advancements => self.close_advancements(),
// key.emote (Horizon's): the emote wheel, a tap again closes it.
KeyCode::KeyB if self.screen == Screen::Playing && horizon_cosmetics::enabled() => {
KeyCode::KeyB if self.screen == Screen::Playing && super::cosmetics::enabled() => {
self.open_emote_wheel();
}
KeyCode::KeyB if self.screen == Screen::EmoteWheel => self.close_emote_wheel(None),
+1
View File
@@ -29,6 +29,7 @@ To play, use [the launcher](../launcher/README.md); the project site is [playhor
- [Extras](extras.md): optional zoom, minimap, FPS and keystroke overlays and auto-reconnect, all off by default.
- [PvP & HUD](pvp.md): optional client settings for PvP players (custom crosshair, block outline, hit color, HUD widgets), fair-play boundaries, and why each is off by default.
- [Cosmetics](cosmetics.md): wearable items drawn only by Horizon clients (hats, back items, shoulder pets, name tag styles), with no gameplay effect, client-side visibility toggles and the JSON item format.
- [Forking](forking.md): the `horizon-extras` feature that holds Horizon's own extras (cosmetics, Friends, its settings pages...), and building the game without them.
- [Narrator](narrator.md): vanilla's text-to-speech (Ctrl+B): the voices per platform, how screens record what they narrate, `horizon-ctl narration`, and where it still differs from vanilla.
- [Shader packs](shaders.md): Horizon's own WGSL shader packs, how to make and check one (`horizon-shader`).
- [Mods](mods.md): content mods (data) and native mods (Rust), how to make, install and publish them. [Native mods](native-mods.md): how the launcher builds them. [Native hook points modelled on NeoForge](native-hooks-neoforge.md): the proposal for new hooks.
+48
View File
@@ -0,0 +1,48 @@
# Forking: Horizon's extras and the vanilla game
Horizon is the vanilla game plus a few things of its own: cosmetics, Play with Friends, the
official server in the list, its settings pages. In the code they sit behind one Cargo feature,
`horizon-extras`, on by default, so a fork can build without them or delete them. Players always
get the full Horizon: there is no setting, flag or variable that turns the extras off.
Optimizations are not extras; they are part of the game in every build.
## Building without Horizon's extras
```sh
cargo build --release -p horizon-client --no-default-features --features audio,controllers
```
Best effort: CI builds and tests the default only. The server build (`--no-default-features`,
docs/server.md) compiles the same gates, so the code keeps compiling, but nobody plays it as a
client before a release. [horizon-rust/horizon-plain](https://github.com/horizon-rust/horizon-plain)
builds it from each official release tag, unsigned.
What changes in that build:
| Extra | Where it lives | The gate |
|---|---|---|
| Cosmetics, emotes and the emote wheel, "Show me as a Horizon player" | `game/cosmetics.rs` (+ `cosmetics/fx.rs`), `game/emotes.rs`, `game/emote_wheel.rs`, crate `horizon-cosmetics` | `game::cosmetics::enabled()`: the service is never asked, nothing is drawn, B opens no wheel |
| The official and partner servers added to the server list | `multiplayer.rs` (`add_official_server`, `add_partner_servers`) | their call at startup (`game.rs`, `fn run`) |
| Horizon Settings (Extras, PvP & HUD, Splitscreen, Studio, AI Agent, Discord) and Mods (with the registry browser) | `options_ui.rs` (`Page::Horizon` and below), `extras_ui.rs`, `extras.rs`, `pvp.rs`, `game/extras.rs`, `game/tweaks.rs`, `extras_hud.rs` | the Options screen shows vanilla's greyed Telemetry Data instead (`options_ui::HORIZON_EXTRAS`, set in `entry.rs`) |
| Discord Rich Presence | `discord_presence.rs`, `game/discord.rs` | the `discord` feature, which `horizon-extras` turns on |
| Play with Friends | `game/friends.rs`, `friends_screen.rs`, crate `horizon-friends` | the `friends` feature (`game/friends_off.rs` stands in) |
| The anonymous "I'm playing" ping | `heartbeat.rs` | the `heartbeat` feature |
Credits & Attribution then says where the official build is (`horizon.plain.official`).
`grep -rn horizon-extras crates/` lists every gate. Apart from the three features above, the gates
are `cfg!(feature = "horizon-extras")`: both sides compile in every build, so no stub has to follow
the code it replaces. To remove an extra for good, delete its files from the table and the lines its
gate guards.
## Not behind the feature
- The launcher (`launcher/`) holds the Wardrobe, the Shop, the featured servers page, its themes,
the telemetry consent and updates. A build of the game alone has none of them; without the
launcher's consent the game keeps no crash or performance report (docs/telemetry.md).
- The graphics screen's fullbright, dynamic lights and hiding block-break particles: renderer
options, off by default (docs/extras.md). A `horizon-extras.txt` left by an official install is
still read.
- The mod runtime: nothing installs a mod without the launcher, so it stays idle.
- The `horizon` brand reply to a Horizon server, for its protocol extensions (`net.rs`).
- Agent control (`--control`, `horizon-ctl`), controllers, HDR, shader packs.
+1 -5
View File
@@ -1787,10 +1787,6 @@ API 410 adds `native_gui_render_post(batch, gui_hidden, focused)` draws after th
API 411 adds Client-only Host snapshots of the window, connection, entities, interpolated model heads and profile skins; visual rays and block occlusion facts; atlas GUI painting, nine-slice widgets and tooltips; persisted mod key changes and a local action bar. client_start runs before connecting (a window need not exist yet), client_disconnecting before a real connection is discarded (not dimension travel), client_stopping once before final save/exit. menu_background can change a normal menu blur request (None disables it; finite 1..64 only). Bundles need "hooks": 411.
API 412 adds `native_force_chunk(owner, chunk, hold)` holds a chunk of the level in place loaded and ticking for `owner`, as NeoForge's `TicketController.forceChunk`; `native_forced_chunks(owner)` lists what it holds. Tickets aren't saved. Bundles need "hooks": 412.
API 413 adds `native_force_chunk_in(owner, dimension, chunk, hold)`, `native_force_chunk` for a named level, which opens and ticks a level not open yet, so a mod can pre-generate the Nether and the End with nobody there. Bundles need "hooks": 413.
## What the launcher does
**Developing a mod?** Run `horizon-launcher settings native-profile dev` once, or pick "Dev build" in the Mods page's
@@ -2653,7 +2649,7 @@ server only.
level nobody is in, or one not open yet, opens at the next server tick and ticks while a ticket holds it, so a
pre-generator walks the Nether and the End from any server hook (a console command, `native_level_tick_pre` of the
Overworld) with nobody there. False for a dimension other than the Overworld, the Nether and the End. API
413.
@API:chunk_tickets_in_level@.
- **`native_world_border(dimension)`** (`native_world_border.rs`): the level's world border as the server enforces it,
`[min_x, min_z, max_x, max_z]` in blocks (the previous size while it moves), for a web map's border line or a
pre-generator's `worldborder` selection. Vanilla 26.3 keeps a border per level (`ServerLevel.getWorldBorder`, each
-12
View File
@@ -733,18 +733,6 @@
{
"name": "player",
"target": "horizon_client::game::native_hooks::Game<'_>::native_player"
},
{
"name": "force_chunk",
"target": "horizon_client::game::native_chunk_tickets::Game<'_>::native_force_chunk"
},
{
"name": "forced_chunks",
"target": "horizon_client::game::native_chunk_tickets::Game<'_>::native_forced_chunks"
},
{
"name": "force_chunk_in",
"target": "horizon_client::game::native_chunk_tickets::Game<'_>::native_force_chunk_in"
}
],
"hooks": [
@@ -0,0 +1,12 @@
{
"hooks": [],
"helpers": [
{"name": "force_chunk", "target": "horizon_client::game::native_chunk_tickets::Game<'_>::native_force_chunk"},
{"name": "forced_chunks", "target": "horizon_client::game::native_chunk_tickets::Game<'_>::native_forced_chunks"}
],
"rust_summary": "chunks a mod keeps loaded and ticking (`native_force_chunk`, `native_forced_chunks`).",
"doc_summary": "`native_force_chunk(owner, chunk, hold)` holds a chunk of the level in place loaded and ticking for `owner`, as NeoForge's `TicketController.forceChunk`; `native_forced_chunks(owner)` lists what it holds. Tickets aren't saved. Bundles need \"hooks\": @API:chunk_tickets@.",
"comments": [
"crates/horizon-client/src/game/native_chunk_tickets.rs"
]
}
@@ -0,0 +1,11 @@
{
"hooks": [],
"helpers": [
{"name": "force_chunk_in", "target": "horizon_client::game::native_chunk_tickets::Game<'_>::native_force_chunk_in"}
],
"rust_summary": "chunk tickets in a level nobody is in (`native_force_chunk_in`).",
"doc_summary": "`native_force_chunk_in(owner, dimension, chunk, hold)`, `native_force_chunk` for a named level, which opens and ticks a level not open yet, so a mod can pre-generate the Nether and the End with nobody there. Bundles need \"hooks\": @API:chunk_tickets_in_level@.",
"comments": [
"crates/horizon-client/src/game/native_chunk_tickets.rs"
]
}