mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca17c0c5ba | ||
|
|
abaa2a9f68 |
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;
|
||||
|
||||
@@ -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": "高级",
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user