Compare commits

..
Author SHA1 Message Date
Horizon agentandClaude Opus 5.5 23293b86b4 Keep the mod screen over other screens out of the big files
The new mod-screen code moves from native_hooks.rs to native_mod_screen.rs
(helpers retargeted in its fragment), its state rides the existing
native_mod_screen field, and draw_screen lives in screens.rs, so game.rs,
native_hooks.rs and remote.rs no longer grow.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-09 18:41:59 +01:00
Horizon agent 4fa9c5b082 Merge remote-tracking branch 'origin/agent/hooks-outside-native-hooks' into fix/12272 2026-11-09 18:31:09 +01:00
Horizon agent a790d406c7 Merge remote-tracking branch 'origin/main' into fix/12272
# Conflicts:
#	crates/horizon-client/src/game/pointer_capture.rs
2026-11-09 18:31:06 +01:00
Horizon agentandClaude Opus 5.5 1d12ab67f5 mod-api-gen: committed render hooks ignore rustfmt's trailing commas
xtask mod-api writes the renderer's hooks through rustfmt, which adds a
trailing comma wherever it wraps a list. The build's staleness check compares
tokens with the generator's one-line code, so any render hook with a signature
long enough to wrap (#12277's client_sound) failed the build as "out of date"
right after being regenerated. The check now drops trailing commas before ),
] and } on both sides.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-09 18:16:58 +01:00
Horizon agentandClaude Opus 5.5 98c8498c8b Native mods: a hook point may live in any module under game/
native_hooks.rs is over the 3000-line limit and may not grow, so every new
hook point was refused. The generator now accepts a hook whose target names
another game/ module, as helpers already do, provided the function there makes
its dispatch_<hook> call itself (xtask mod-api only fills native_hooks.rs) and
holds no macro or cfg, which horizon-mixins cannot patch.
catalog_matches_hook_points checks those hooks against the file their target
names.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-09 17:38:48 +01:00
Horizon agentandClaude Opus 5.5 507e48073a Merge main into #12272
main moved native_hooks.rs's tests to native_hooks_tests.rs: the new test moves there.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-09 16:28:45 +01:00
Cheezer1656andClaude Code 85514bb934 Mod API: a mod's own screen over the options page, pause menu or container
native_mod_screen_open_over(mod_id, keep_drawing_previous) opens a mod screen
over Options, Paused or Inventory (a container) as well as the world. Escape or
native_mod_screen_close gives the replaced screen back (vanilla's lastScreen).
With keep_drawing_previous the pause title or container panel still draws under
it, pointer-less, and native_mod_screen_under_rect reports its panel. A server
ContainerClose closes the mod screen with the container; single-player stays
paused under a pausing screen. Ships as a hook fragment (mod_screen_over).

Co-Authored-By: Claude Code <[email protected]>
Claude-Session: https://claude.ai/code/session_011R7zRUK5udBTJmDgHhoXY2
2026-11-08 13:14:05 -07:00
339 changed files with 5698 additions and 10982 deletions

No files matched your search

-8
View File
@@ -13,14 +13,6 @@ else (videos, Discord and other community material) lives elsewhere.
suggest something. A pull request for a roadmap item carries its `roadmap:<id>` label; ideas go to the suggestion
queue (the hub, or `/suggest` on Discord) rather than to issues, which are for bugs.
**Mod-related pull requests go to [arismoko/horizon](https://github.com/arismoko/horizon)**, the fork that carries the
mod API and reviews and merges that work. That covers the mod API, native hooks and events for mods, native mods,
content mods (`.hmod`), mixins, wasm mods, `cargo horizon-mod`, `horizon-mods`, mod builds and the registry, and
their docs ([mods](docs/mods.md), [native mods](docs/native-mods.md)). Open the pull request there from your usual
branch. Mod-related pull requests opened here are closed with a pointer to it. Engine extension points that Horizon's
own crates register (the [layer split](docs/layers.md)), resource and shader pack formats, and gameplay fixes stay
here.
## Build it
- **Prerequisites:** Rust (stable), Python 3.10+, JDK 25, and on Linux the ALSA and udev headers
Generated
+10 -10
View File
@@ -1401,9 +1401,7 @@ dependencies = [
name = "horizon-bench"
version = "0.1.0"
dependencies = [
"horizon-platform",
"horizon-telemetry",
"log",
"serde",
"serde_json",
]
@@ -1420,7 +1418,6 @@ dependencies = [
name = "horizon-chat-filter"
version = "0.1.0"
dependencies = [
"horizon-server-files",
"serde",
"serde_json",
"toml",
@@ -1441,6 +1438,7 @@ dependencies = [
"glam",
"horizon-anticheat",
"horizon-assets",
"horizon-bench",
"horizon-chat-filter",
"horizon-client-core",
"horizon-client-input",
@@ -1462,7 +1460,6 @@ dependencies = [
"horizon-server-files",
"horizon-shader-api",
"horizon-shaders",
"horizon-system",
"horizon-telemetry",
"horizon-theme",
"horizon-threads",
@@ -1524,12 +1521,14 @@ dependencies = [
"glam",
"horizon-anticheat",
"horizon-assets",
"horizon-bench",
"horizon-chat-filter",
"horizon-client-core",
"horizon-client-input",
"horizon-client-render",
"horizon-command",
"horizon-core",
"horizon-friends",
"horizon-hdr",
"horizon-hooks",
"horizon-items",
@@ -1593,6 +1592,7 @@ dependencies = [
"horizon-optifine",
"horizon-polytone",
"horizon-protocol",
"horizon-render-pipeline",
"horizon-shader-api",
"horizon-shaders",
"horizon-system",
@@ -1668,7 +1668,6 @@ dependencies = [
"getrandom 0.3.4",
"hkdf",
"horizon-addresses",
"horizon-platform",
"if-addrs",
"serde",
"serde_json",
@@ -1794,12 +1793,9 @@ dependencies = [
name = "horizon-official"
version = "0.1.0"
dependencies = [
"horizon-bench",
"horizon-friends",
"horizon-game",
"horizon-heartbeat",
"horizon-platform",
"horizon-telemetry",
"log",
"winresource",
]
@@ -1808,8 +1804,8 @@ dependencies = [
name = "horizon-optifine"
version = "0.1.0"
dependencies = [
"horizon-bench",
"horizon-core",
"horizon-system",
"log",
"regex",
]
@@ -1895,6 +1891,10 @@ dependencies = [
"ring",
]
[[package]]
name = "horizon-render-pipeline"
version = "0.1.0"
[[package]]
name = "horizon-server"
version = "0.1.0"
@@ -1957,6 +1957,7 @@ version = "0.1.0"
dependencies = [
"half",
"horizon-core",
"horizon-render-pipeline",
"naga",
"png",
"zip",
@@ -1973,7 +1974,6 @@ dependencies = [
"getrandom 0.3.4",
"horizon-addresses",
"horizon-platform",
"log",
"ruzstd",
"serde",
"serde_json",
+1
View File
@@ -43,6 +43,7 @@ members = [
"crates/cargo-horizon-mod",
"crates/horizon-port-pack",
"crates/horizon-shaders",
"crates/horizon-render-pipeline",
"crates/horizon-optifine",
"crates/horizon-polytone",
"crates/horizon-shader-api",
+2 -21
View File
@@ -26,23 +26,18 @@ pub struct AnimationMetadata {
pub enum ClipMode {
/// `day_time`: frame 0 at time 0, the last at the cycle's end, then again.
DayTime { cycle: i64 },
/// `screen_time`: the ticks the open screen has been open, over the cycle, held on the last
/// frame once it has run (it doesn't loop, measured on the real mod).
ScreenTime { cycle: i64 },
/// `weather`: frame 0 in clear weather, 1 in rain, 2 in a storm (the rain and thunder levels
/// added up), at most the last.
Weather,
/// A Polytone expression: its value's fraction is how far through the animation it is.
Expression(String),
/// `game_time`, `season`: not driven; the animation runs on ticks.
/// `game_time`, `screen_time`, `season`: not driven yet; the animation runs on ticks.
Unsupported(String),
}
/// The values [`ClipMode`]s read this frame.
pub struct ModeInputs<'a> {
pub day_time: i64,
/// The ticks the open screen has been open (0 without one).
pub screen_time: i64,
/// The rain level plus the thunder level, 0 to 2.
pub weather: f32,
/// An expression's value this frame, by its source.
@@ -121,8 +116,7 @@ impl AnimationMetadata {
match mode.as_str() {
"day_time" => ClipMode::DayTime { cycle },
"weather" => ClipMode::Weather,
"screen_time" => ClipMode::ScreenTime { cycle },
"game_time" | "season" => ClipMode::Unsupported(mode),
"game_time" | "screen_time" | "season" => ClipMode::Unsupported(mode),
_ => ClipMode::Expression(mode),
}
}),
@@ -245,9 +239,6 @@ impl AnimationClip {
pub fn sample_mode(&self, inputs: &ModeInputs) -> Option<AnimationSample> {
let fraction = match self.mode.as_ref()? {
ClipMode::DayTime { cycle } => day_fraction(inputs.day_time, *cycle),
ClipMode::ScreenTime { cycle } => {
(inputs.screen_time.max(0) as f64 / *cycle as f64).min(1.0 - f64::EPSILON)
}
ClipMode::Weather => {
let last = self.sequence.len() - 1;
let index = (inputs.weather.max(0.0) as usize).min(last);
@@ -297,7 +288,6 @@ mod mode_tests {
let expression = move |_: &str| Some(value);
clip.sample_mode(&ModeInputs {
day_time,
screen_time: day_time,
weather,
expression: &expression,
})
@@ -312,13 +302,6 @@ mod mode_tests {
.map(|t| frame(&day, t, 0.0, 0.0))
.into();
assert_eq!(frames, [0, 1, 2, 3, 1]);
// 26.3-6.8.6, four frames over 200 ticks: 40 ticks open show the first, and from ten
// seconds on the last, even after 27 (it doesn't loop).
let screen = clip(r#"{"frametime": 1, "mode": "screen_time", "time_cycle_duration": 200}"#);
let frames: Vec<u32> = [0, 40, 200, 360, 540]
.map(|t| frame(&screen, t, 0.0, 0.0))
.into();
assert_eq!(frames, [0, 0, 3, 3, 3]);
let value = clip(r#"{"frametime": 1, "mode": "0.6"}"#);
assert_eq!(frame(&value, 0, 0.0, 0.6), 2);
assert_eq!(frame(&value, 0, 0.0, 2.0), 0);
@@ -338,7 +321,6 @@ mod mode_tests {
clip(r#"{"frametime": 1}"#)
.sample_mode(&ModeInputs {
day_time: 0,
screen_time: 0,
weather: 0.0,
expression: &|_| None
})
@@ -348,7 +330,6 @@ mod mode_tests {
clip(r#"{"frametime": 1, "mode": "season"}"#)
.sample_mode(&ModeInputs {
day_time: 0,
screen_time: 0,
weather: 0.0,
expression: &|_| None
})
+1 -9
View File
@@ -1,7 +1,7 @@
[package]
name = "horizon-bench"
version = "0.1.0"
edition.workspace = true
edition = "2021"
rust-version.workspace = true
license = "MIT"
description = "The benchmark report format (docs/benchmark-report.md), shared by the game, the launcher and the upload"
@@ -11,11 +11,3 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
# The hardware probes shared with telemetry (cores, clocks, drive, VRAM, power, form).
horizon-telemetry = { path = "../horizon-telemetry", default-features = false }
# The game's part (`game`): the hooks it plugs into and the probes, from the platform's API.
horizon-platform = { path = "../horizon-platform", optional = true }
log = { version = "0.4", optional = true }
[features]
# The game's `--benchmark` driver, started through the platform's hooks. The official executable
# turns it on.
game = ["dep:horizon-platform", "dep:log"]
+117 -3
View File
@@ -1,7 +1,6 @@
//! The machine a report was measured on. CPU, RAM and OS are detected here; the GPU and the display
//! come from the program that rendered (the game knows its adapter, the launcher its window).
#[cfg(any(target_os = "macos", target_os = "linux"))]
use horizon_telemetry::system::command_output as output;
use serde::{Deserialize, Serialize};
@@ -69,12 +68,12 @@ impl Hardware {
os: std::env::consts::OS.to_owned(),
os_version: os_version(),
arch: std::env::consts::ARCH.to_owned(),
cpu: horizon_telemetry::system::cpu(),
cpu: cpu(),
cpu_threads: std::thread::available_parallelism().ok().map(|n| n.get()),
cpu_cores: probes.cores,
cpu_base_mhz: probes.base,
cpu_boost_mhz: probes.boost,
ram_mb: horizon_telemetry::system::ram_bytes().map(|b| b >> 20),
ram_mb: ram_bytes().map(|b| b >> 20),
storage: None,
form: probes.form,
gpu: None,
@@ -110,16 +109,84 @@ fn probes() -> Probes {
}
}
/// The one-minute load average: above about the number of threads the machine was busy.
pub fn load_average() -> Option<f64> {
if cfg!(target_os = "macos") {
// "{ 1.23 1.10 1.00 }"
output("sysctl", &["-n", "vm.loadavg"])?
.trim_start_matches('{')
.split_whitespace()
.next()?
.parse()
.ok()
} else {
std::fs::read_to_string("/proc/loadavg")
.ok()?
.split_whitespace()
.next()?
.parse()
.ok()
}
}
/// `ac` or `battery`; `None` where it can't be read (and on machines without a battery on Linux).
pub fn power_source() -> Option<&'static str> {
horizon_telemetry::system::power()
}
/// Resident memory of a process, in bytes (macOS, Linux).
pub fn rss_bytes(pid: u32) -> Option<u64> {
if cfg!(target_os = "macos") {
return output("ps", &["-o", "rss=", "-p", &pid.to_string()])?
.parse::<u64>()
.ok()
.map(|kb| kb * 1024);
}
let status = std::fs::read_to_string(format!("/proc/{pid}/status")).ok()?;
let line = status.lines().find(|l| l.starts_with("VmRSS:"))?;
line.split_whitespace()
.nth(1)?
.parse::<u64>()
.ok()
.map(|kb| kb * 1024)
}
/// CPU time a process has used, user and system, in seconds (macOS, Linux).
pub fn cpu_seconds(pid: u32) -> Option<f64> {
if cfg!(target_os = "macos") {
// "[[dd-]hh:]mm:ss.cc"
let time = output("ps", &["-o", "time=", "-p", &pid.to_string()])?;
let (rest, days) = match time.split_once('-') {
Some((d, rest)) => (rest.to_owned(), d.parse::<f64>().ok()?),
None => (time, 0.0),
};
let seconds = rest.split(':').try_fold(0.0, |acc, part| {
part.parse::<f64>().ok().map(|v| acc * 60.0 + v)
})?;
return Some(days * 86_400.0 + seconds);
}
// utime and stime, fields 14 and 15, in clock ticks (100 per second on Linux).
let stat = std::fs::read_to_string(format!("/proc/{pid}/stat")).ok()?;
let fields: Vec<&str> = stat.rsplit_once(')')?.1.split_whitespace().collect();
let ticks: f64 = fields.get(11)?.parse::<f64>().ok()? + fields.get(12)?.parse::<f64>().ok()?;
Some(ticks / 100.0)
}
#[cfg(target_os = "macos")]
fn os_version() -> Option<String> {
output("sw_vers", &["-productVersion"])
}
#[cfg(target_os = "macos")]
fn cpu() -> Option<String> {
output("sysctl", &["-n", "machdep.cpu.brand_string"])
}
#[cfg(target_os = "macos")]
fn ram_bytes() -> Option<u64> {
output("sysctl", &["-n", "hw.memsize"])?.parse().ok()
}
#[cfg(target_os = "linux")]
fn os_version() -> Option<String> {
let release = std::fs::read_to_string("/etc/os-release").ok()?;
@@ -132,6 +199,22 @@ fn os_version() -> Option<String> {
})
}
#[cfg(target_os = "linux")]
fn cpu() -> Option<String> {
let info = std::fs::read_to_string("/proc/cpuinfo").ok()?;
// x86 names the CPU per core; arm64 only gives the core's implementer and part.
horizon_telemetry::system::linux_cpu_name(&info)
}
#[cfg(target_os = "linux")]
fn ram_bytes() -> Option<u64> {
let info = std::fs::read_to_string("/proc/meminfo").ok()?;
let kb = key_value(&info, "MemTotal", ':')?
.trim_end_matches("kB")
.trim();
kb.parse::<u64>().ok().map(|kb| kb * 1024)
}
#[cfg(target_os = "linux")]
fn key_value<'a>(text: &'a str, key: &str, sep: char) -> Option<&'a str> {
text.lines().find_map(|line| {
@@ -145,11 +228,39 @@ fn os_version() -> Option<String> {
horizon_telemetry::system::windows_version()
}
#[cfg(target_os = "windows")]
fn cpu() -> Option<String> {
horizon_telemetry::system::windows_cpu()
}
#[cfg(target_os = "windows")]
fn ram_bytes() -> Option<u64> {
let out = output(
"powershell",
&[
"-NoProfile",
"-Command",
"(Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory",
],
)?;
out.parse().ok()
}
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
fn os_version() -> Option<String> {
None
}
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
fn cpu() -> Option<String> {
None
}
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
fn ram_bytes() -> Option<u64> {
None
}
#[cfg(test)]
mod tests {
use super::*;
@@ -163,6 +274,9 @@ mod tests {
assert!(hw.cpu.is_some(), "{hw:?}");
assert!(hw.ram_mb.unwrap() > 256, "{hw:?}");
assert!(hw.os_version.is_some(), "{hw:?}");
assert!(load_average().is_some());
assert!(rss_bytes(std::process::id()).unwrap() > 1 << 20);
assert!(cpu_seconds(std::process::id()).unwrap() >= 0.0);
}
}
+1 -5
View File
@@ -11,13 +11,9 @@ use std::time::{SystemTime, UNIX_EPOCH};
use serde::{Deserialize, Serialize};
#[cfg(feature = "game")]
pub mod game;
mod hardware;
pub use hardware::{Display, Gpu, Hardware, power_source};
/// The process probes, from the platform (`horizon-system`).
pub use horizon_telemetry::system::{cpu_seconds, load_average, rss_bytes};
pub use hardware::{cpu_seconds, load_average, power_source, rss_bytes, Display, Gpu, Hardware};
pub const FORMAT: &str = "horizon-benchmark-report";
pub const VERSION: u32 = 1;
-2
View File
@@ -7,8 +7,6 @@ rust-version.workspace = true
description = "The dedicated server's chat filter: its [chat_filter] settings, the slur list's matcher and the mutes, with no game state (docs/chat-filter.md)"
[dependencies]
# The moderation durations, shared with `/tempban`.
horizon-server-files = { path = "../horizon-server-files" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
toml = "1.1"
+55 -3
View File
@@ -10,9 +10,6 @@ use std::path::{Path, PathBuf};
mod normalise;
pub use horizon_server_files::duration::{
DurationUnit, duration_parts, duration_text, now_millis, parse_duration,
};
pub use normalise::{Run, Text};
/// The list built into the game, used when `words_file` is empty.
@@ -477,6 +474,61 @@ pub fn append_log(path: &Path, line: &str) -> std::io::Result<()> {
writeln!(f, "{line}")
}
/// `/tempmute`'s duration in milliseconds: a positive whole number and a unit, `s`, `m`, `h`, `d` or
/// `w` (`10m`, `2h`, `1d`).
pub fn parse_duration(text: &str) -> Option<i64> {
let unit = match text.chars().last()? {
's' => 1_000,
'm' => MINUTE,
'h' => 60 * MINUTE,
'd' => 24 * 60 * MINUTE,
'w' => 7 * 24 * 60 * MINUTE,
_ => return None,
};
let count: i64 = text[..text.len() - 1].parse().ok()?;
(count > 0).then(|| count.checked_mul(unit)).flatten()
}
/// The unit used when presenting a moderation duration to a player.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DurationUnit {
Minute,
Hour,
Day,
}
/// A duration's rounded count and unit, shared by logs and translated replies.
pub fn duration_parts(millis: i64) -> (i64, DurationUnit) {
let (unit, kind) = if millis <= 60 * MINUTE {
(MINUTE, DurationUnit::Minute)
} else if millis <= 48 * 60 * MINUTE {
(60 * MINUTE, DurationUnit::Hour)
} else {
(24 * 60 * MINUTE, DurationUnit::Day)
};
(
millis.div_euclid(unit) + i64::from(millis.rem_euclid(unit) > 0),
kind,
)
}
/// `millis` for logs, rounded up: minutes up to an hour, hours up to two days, then days.
pub fn duration_text(millis: i64) -> String {
let (count, unit) = duration_parts(millis);
let unit = match unit {
DurationUnit::Minute => "minute",
DurationUnit::Hour => "hour",
DurationUnit::Day => "day",
};
format!("{count} {unit}{}", if count == 1 { "" } else { "s" })
}
pub fn now_millis() -> i64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.as_millis() as i64)
}
/// `YYYY-MM-DDTHH:MM:SSZ` for epoch milliseconds.
pub fn utc(millis: i64) -> String {
let secs = millis.div_euclid(1000);
-33
View File
@@ -670,39 +670,6 @@ pub fn with_horizon_icon(
attributes
}
/// The screen a window is on: its size in physical pixels, its refresh rate where the OS gives
/// it, and its scale. For `horizon-ctl status` and the performance statistics' `system.display`.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Display {
pub width: u32,
pub height: u32,
pub refresh_hz: Option<u32>,
pub scale: f64,
}
/// The screen `window` is on, else the primary one, else the only one: right after the window
/// is made, Wayland doesn't say yet which screen it is on.
pub fn monitor(window: &winit::window::Window) -> Option<winit::monitor::MonitorHandle> {
let only = || {
let mut all = window.available_monitors();
all.next().filter(|_| all.next().is_none())
};
window
.current_monitor()
.or_else(|| window.primary_monitor())
.or_else(only)
}
/// [`monitor`]'s size, refresh rate and scale.
pub fn display(window: &winit::window::Window) -> Option<Display> {
monitor(window).map(|m| Display {
width: m.size().width,
height: m.size().height,
refresh_hz: m.refresh_rate_millihertz().map(|r| r.div_ceil(1000)),
scale: m.scale_factor(),
})
}
#[cfg(test)]
mod tests {
use super::*;
+7 -5
View File
@@ -42,6 +42,8 @@ horizon-chat-filter = { path = "../horizon-chat-filter" }
horizon-shaders = { path = "../horizon-shaders" }
# Horizon's own shader packs: the format, the stages and the check (docs/shaders.md).
horizon-shader-api = { path = "../horizon-shader-api" }
# Play with Friends: invite codes and their tunnel; the `friends` feature.
horizon-friends = { path = "../horizon-friends", optional = true }
# The narrator (GameNarrator): text-to-speech of chat, system messages and screens.
horizon-narrator = { path = "../horizon-narrator" }
# Horizon mods: sandboxed WebAssembly components, off unless installed and turned on (docs/mods.md).
@@ -71,8 +73,9 @@ mio = { version = "1.2.4", features = ["os-poll", "net"] }
# Shared chunk frames are found by one keyed 128-bit hash of their body (lan/conn.rs SharedFrames).
xxhash-rust = { version = "0.8.19", features = ["xxh3"] }
horizon-theme = { path = "../horizon-theme" }
# The official server's address (multiplayer.rs) until it moves to the Horizon layer.
# Opt-in data collection: the game keeps, the launcher sends (docs/telemetry.md).
horizon-telemetry = { path = "../horizon-telemetry", default-features = false }
horizon-bench = { path = "../horizon-bench" }
crc32fast = "1"
zip = { version = "9.1.3", default-features = false, features = ["deflate"] }
# Controllers (evdev + libudev on Linux, IOKit on macOS, Windows.Gaming.Input); the `controllers` feature.
@@ -108,17 +111,16 @@ objc2-foundation = { version = "0.3", default-features = false, features = ["std
wgpu = { version = "30.0.1", default-features = false, features = ["gles"] }
[features]
default = ["audio", "controllers", "horizon-extras"]
default = ["audio", "controllers", "discord", "friends"]
# 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"]
# Controllers: gilrs, and the DualSense's own output. Without it only `horizon-ctl pad`'s virtual pad.
controllers = ["dep:gilrs", "horizon-client-input/controllers"]
# Horizon's own parts (docs/forking.md, docs/layers.md): the features below, and the official
# server in the list (`official_servers`).
horizon-extras = ["discord"]
# "Playing Horizon" on the player's Discord profile (`discord_presence`).
discord = []
# Play with Friends, joining side (`net.rs`): without it no invite code joins.
friends = ["dep:horizon-friends"]
# Records the call sites of mob random draws (`mob_ai_probe` traces).
rng-trace = ["horizon-core/rng-trace"]
# Compatibility with earlier experiment commands: main already uses mimalloc.
+2 -2
View File
@@ -7,8 +7,7 @@ rust-version.workspace = true
description = "The client's world data, rendering and screens: chunks, meshing, light, streaming, the renderer, the HUD, menus and settings; no gameplay"
[dependencies]
# The crates above the engine: their settings stores (docs/layers.md, "Platform registries"),
# and the hooks that hear each crash (`crash.rs`).
# The settings stores of the crates above the engine (docs/layers.md, "Platform registries").
horizon-hooks = { path = "../horizon-hooks" }
horizon-cosmetics = { path = "../horizon-cosmetics" }
# The GUI atlas's pixels, kept compressed once uploaded (hud.rs `pack_pixels`).
@@ -27,6 +26,7 @@ horizon-narrator = { path = "../horizon-narrator" }
horizon-optifine = { path = "../horizon-optifine" }
horizon-polytone = { path = "../horizon-polytone" }
horizon-protocol = { path = "../horizon-protocol" }
horizon-render-pipeline = { path = "../horizon-render-pipeline" }
horizon-shader-api = { path = "../horizon-shader-api" }
horizon-shaders = { path = "../horizon-shaders" }
horizon-system = { path = "../horizon-system" }
@@ -121,8 +121,8 @@ impl CommandBlock {
condition_met: flag("conditionMet", false),
}
}
/// `CommandBlockEntity.saveAdditional`. The last output is the component
/// itself (`ComponentSerialization.CODEC`), styles and click events kept.
/// `CommandBlockEntity.saveAdditional`. The last output is written as a
/// plain string component.
pub fn save(&self) -> Vec<(&'static str, Tag)> {
let mut out = vec![
("Command", Tag::String(self.command.as_str().into())),
@@ -135,7 +135,7 @@ impl CommandBlock {
if self.track_output
&& let Some(last) = &self.last_output
{
out.push(("LastOutput", scoreboard::component_tag(&last.component())));
out.push(("LastOutput", Tag::String(last.plain().as_str().into())));
}
out.push((
"UpdateLastExecution",
+20 -32
View File
@@ -204,23 +204,6 @@ impl Report {
}
}
/// The renderer's adapter ([`crate::gfx::adapter`]), for the hooks above the engine.
pub fn gpu() -> Option<horizon_hooks::Gpu> {
crate::gfx::adapter().map(|a| horizon_hooks::Gpu {
name: a.name,
vendor: a.vendor,
kind: a.kind,
driver: a.driver,
backend: a.backend,
})
}
/// The build, for what leaves the machine: the release (`v0.2.3`), or `dev <crate version>` for a
/// build from source.
pub fn build() -> String {
release_tag().unwrap_or_else(|| format!("dev {}", env!("CARGO_PKG_VERSION")))
}
/// The release this binary is (`v0.2.3`); a nightly's label (`Nightly 2026-10-28 · 0c02067`); for a
/// release from before 0.2, its tag and the client's crate version; for a build from source, that
/// version alone.
@@ -400,20 +383,19 @@ fn thread_name(side: &str) -> String {
/// promise to launchers (docs/api/launch.md), so it never changes within a line of that API.
pub const SAVED_LINE: &str = "Horizon: crash report saved to ";
/// The hooks above the engine hear the report before it is saved (`Hooks::crashed`; Horizon's
/// opt-in telemetry keeps it). They run inside the crash handler: they must not panic or block.
fn tell_hooks(home: &Path, report: &Report) {
let version = build();
let gpu = gpu();
horizon_hooks::engine::crashed(&horizon_hooks::Crash {
home,
version: &version,
error: &report.error,
stack: &report.stack,
thread: &report.thread,
details: &report.details,
gpu: gpu.as_ref(),
});
/// What runs on each report before it is saved ([`on_report`]).
pub type ReportHook = fn(&Path, &Report);
static REPORT_HOOKS: std::sync::RwLock<Vec<ReportHook>> = std::sync::RwLock::new(Vec::new());
/// Runs `hook` with every crash report, before it is saved: the game folder and the report. An
/// extension point for what lives above the engine (Horizon's opt-in telemetry keeps the
/// report). A hook runs inside the crash handler: it must not panic or block.
pub fn on_report(hook: ReportHook) {
REPORT_HOOKS
.write()
.unwrap_or_else(|e| e.into_inner())
.push(hook);
}
/// Writes the report and tells where it is; the log gets the error too.
@@ -436,7 +418,13 @@ fn deliver(home: &Path, report: Report) -> Option<PathBuf> {
),
);
crate::logging::flush();
tell_hooks(home, &report);
for hook in REPORT_HOOKS
.read()
.unwrap_or_else(|e| e.into_inner())
.iter()
{
hook(home, &report);
}
// A player of a shared game folder (`--player-dir`) finds the report beside its own log.
match report.save(crate::logging::player_dir_override().unwrap_or(home)) {
Ok(path) => {
+204 -76
View File
@@ -1,34 +1,27 @@
//! Custom sky layers: skyboxes a pack draws over the vanilla sky, in a format vanilla doesn't read
//! (OptiFine's), which the platform's pack formats fill ([`crate::pack_hooks`]). The layers'
//! textures are read once with the pack; each frame the game asks for the layers to draw, their
//! brightness and rotation ([`SkyLayers::draws`]), and `gfx::custom_sky` draws them over the
//! vanilla sky. A pack without layers keeps the vanilla sky.
//! OptiFine custom skies (`horizon_optifine::sky`) as the game draws them: a pack's layers with
//! their decoded textures, read once with the pack, and each frame's choice of layers, their
//! brightness and rotation ([`CustomSky::draws`]), which `gfx::custom_sky` draws over the
//! vanilla sky. A pack without `optifine/sky` gives an empty [`CustomSky`] and the sky stays
//! vanilla.
use std::path::Path;
use std::sync::Arc;
use glam::Mat4;
use glam::{Mat4, Vec3};
use horizon_optifine::sky::{Blend, Conditions, Skies, SkyLayer, Transition};
/// A layer's texture, RGBA8: the skybox's six faces in a 3x2 grid, bottom, top and south in the
/// top row, west, north and east below (`gfx::custom_sky`'s faces).
/// A layer's texture, RGBA8.
pub struct Image {
pub width: u32,
pub height: u32,
pub rgba: Vec<u8>,
}
/// How a layer mixes with what is drawn below it.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Blend {
Add,
Subtract,
Multiply,
Dodge,
Burn,
Screen,
Replace,
Overlay,
Alpha,
/// The pack's layers, by world, with one texture per layer (`images[i]` for the `i`th layer
/// over all worlds, in `worlds` order).
#[derive(Default)]
pub struct CustomSky {
worlds: Vec<(i32, Vec<SkyLayer>)>,
pub images: Vec<Image>,
}
/// One layer of a frame: which texture, with which blend, how bright and turned how.
@@ -41,44 +34,62 @@ pub struct Draw {
pub rotation: Mat4,
}
/// The weather of a frame: thunder is a storm's rain, so it is only `Thunder`.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Weather {
Clear,
Rain,
Thunder,
}
/// What a frame's layers may depend on.
#[derive(Clone, Copy, Debug)]
pub struct Conditions<'a> {
/// Ticks of the day, counting the days.
pub day_time: i64,
pub weather: Weather,
/// The biome at the camera, namespaced.
pub biome: &'a str,
/// The camera's block height.
pub y: i32,
}
/// The eased brightness of each layer, kept by the game between frames.
#[derive(Default)]
pub struct State {
/// The world the layers were last drawn for.
pub world: Option<i32>,
/// Each layer's eased visibility, 0 to 1.
pub eased: Vec<f32>,
world: Option<i32>,
eased: Vec<Transition>,
}
/// A pack's custom sky layers, by world (`0` the Overworld, `1` the End).
pub trait SkyLayers: Send + Sync {
/// Every layer's texture: [`Draw::image`] indexes it.
fn images(&self) -> &[Image];
/// Whether `world` has layers.
fn has(&self, world: i32) -> bool;
/// This frame's layers of `world` into `out` (cleared first), in drawing order, eased by
/// `seconds` since the last frame. `sun_turns` is the sun's angle over 360° (`0` at noon).
fn draws(
/// Textures past this many texels in all are left out (a skybox is a few megapixels at most).
const TEXEL_BUDGET: u64 = 64 << 20;
impl CustomSky {
/// The custom skies of the overlay at `root`. A layer whose texture does not decode is
/// dropped with a warning.
pub fn load(root: &Path) -> Self {
let skies = Skies::scan(root);
let mut out = Self::default();
let mut texels = 0u64;
for (world, sky) in skies.worlds {
let mut layers = Vec::new();
for layer in sky.layers {
match crate::hud::decode(&root.join(&layer.source)) {
Ok((width, height, rgba))
if texels + u64::from(width) * u64::from(height) <= TEXEL_BUDGET =>
{
texels += u64::from(width) * u64::from(height);
out.images.push(Image {
width,
height,
rgba,
});
layers.push(layer);
}
Ok(_) => log::warn!("{}: past the custom sky's texture budget", layer.source),
Err(e) => log::warn!("{}: {e:#}", layer.source),
}
}
if !layers.is_empty() {
out.worlds.push((world, layers));
}
}
out
}
pub fn is_empty(&self) -> bool {
self.worlds.is_empty()
}
/// Whether `world` has layers (`0` the Overworld, `1` the End).
pub fn has(&self, world: i32) -> bool {
self.worlds.iter().any(|(w, _)| *w == world)
}
/// This frame's layers of `world` into `out` (cleared first), in drawing order: those whose
/// conditions hold, eased over their `transition` by `seconds` since the last frame, at the
/// brightness of their fade times. `sun_turns` is the sun's angle over 360° (`0` at noon).
pub fn draws(
&self,
world: i32,
conditions: &Conditions,
@@ -86,30 +97,147 @@ pub trait SkyLayers: Send + Sync {
seconds: f32,
state: &mut State,
out: &mut Vec<Draw>,
);
}
/// No layers: the vanilla sky.
struct NoLayers;
impl SkyLayers for NoLayers {
fn images(&self) -> &[Image] {
&[]
}
fn has(&self, _: i32) -> bool {
false
}
fn draws(&self, _: i32, _: &Conditions, _: f32, _: f32, s: &mut State, out: &mut Vec<Draw>) {
) {
out.clear();
s.world = None;
let mut first = 0;
let mut found = None;
for (w, layers) in &self.worlds {
if *w == world {
found = Some(layers);
break;
}
first += layers.len();
}
let Some(layers) = found else {
state.world = None;
return;
};
// A new world (or pack) starts each layer where its conditions put it: no fade in.
let snap = state.world != Some(world) || state.eased.len() != layers.len();
if snap {
state.world = Some(world);
state.eased.clear();
state.eased.resize(layers.len(), Transition::default());
}
for (i, (layer, eased)) in layers.iter().zip(&mut state.eased).enumerate() {
let target = if layer.shows(conditions) { 1.0 } else { 0.0 };
let shown = if snap {
eased.value = target;
target
} else {
eased.step(target, seconds, layer.transition)
};
let brightness = shown * layer.brightness(conditions.day_time);
if brightness <= 0.0 {
continue;
}
let turns = layer.turns(conditions.day_time, sun_turns);
let rotation = if turns == 0.0 {
Mat4::IDENTITY
} else {
Mat4::from_axis_angle(Vec3::from_array(layer.axis), turns * std::f32::consts::TAU)
};
out.push(Draw {
image: (first + i) as u16,
blend: layer.blend,
brightness,
rotation,
});
}
}
}
/// The custom sky layers of the overlay at `root`, from the installed pack formats.
pub fn load(root: &Path) -> Arc<dyn SkyLayers> {
crate::pack_hooks::formats()
.custom_sky(root)
.unwrap_or_else(|| Arc::new(NoLayers))
#[cfg(test)]
mod tests {
use super::*;
use horizon_optifine::sky::Now;
fn pack(files: &[(&str, &str)]) -> (std::path::PathBuf, CustomSky) {
let root = std::env::temp_dir().join(format!(
"horizon-custom-sky-{}-{}",
std::process::id(),
files.len()
));
let _ = std::fs::remove_dir_all(&root);
let dir = root.join("assets/minecraft/optifine/sky/world0");
std::fs::create_dir_all(&dir).unwrap();
let png = {
let mut bytes = Vec::new();
let mut encoder = png::Encoder::new(&mut bytes, 6, 4);
encoder.set_color(png::ColorType::Rgba);
encoder.set_depth(png::BitDepth::Eight);
let mut writer = encoder.write_header().unwrap();
writer
.write_image_data(&[255, 0, 0, 255].repeat(24))
.unwrap();
writer.finish().unwrap();
bytes
};
for (name, text) in files {
std::fs::write(dir.join(format!("{name}.properties")), text).unwrap();
std::fs::write(dir.join(format!("{name}.png")), &png).unwrap();
}
let sky = CustomSky::load(&root);
(root, sky)
}
fn noon(biome: &str) -> Conditions<'_> {
Conditions {
day_time: 6000,
weather: Now::Clear,
biome,
y: 64,
}
}
#[test]
fn layers_ease_in_at_a_biome_border_but_not_on_arrival() {
let (root, sky) = pack(&[
("sky1", "biomes=desert\ntransition=2"),
("sky2", "blend=alpha\nrotate=false"),
]);
let _ = std::fs::remove_dir_all(root);
assert!(sky.has(0) && !sky.has(1));
assert_eq!(sky.images[0].width, 6);
let (mut state, mut out) = (State::default(), Vec::new());
sky.draws(
0,
&noon("minecraft:desert"),
0.0,
0.05,
&mut state,
&mut out,
);
assert_eq!(
out.iter().map(|d| d.brightness).collect::<Vec<_>>(),
[1.0, 1.0]
);
// Out of the desert: the first layer fades over its two seconds.
sky.draws(0, &noon("minecraft:plains"), 0.0, 0.5, &mut state, &mut out);
assert_eq!(out[0].brightness, 0.75);
assert_eq!(out[1].blend, Blend::Alpha);
sky.draws(0, &noon("minecraft:plains"), 0.0, 2.0, &mut state, &mut out);
assert_eq!(out.len(), 1, "a dark layer is not drawn");
assert_eq!(out[0].image, 1);
// Into the End, which has no layers, and back: no fade.
sky.draws(1, &noon("minecraft:desert"), 0.0, 0.0, &mut state, &mut out);
assert!(out.is_empty());
sky.draws(0, &noon("minecraft:desert"), 0.0, 0.0, &mut state, &mut out);
assert_eq!(out.len(), 2);
}
#[test]
fn a_layer_turns_west_like_the_sun() {
let (root, sky) = pack(&[("sky1", "")]);
let _ = std::fs::remove_dir_all(root);
let (mut state, mut out) = (State::default(), Vec::new());
// A quarter turn after noon (sunset): the skybox's zenith has gone down to the west.
let c = Conditions {
day_time: 12000,
..noon("minecraft:plains")
};
sky.draws(0, &c, 0.25, 0.0, &mut state, &mut out);
let up = out[0].rotation.transform_vector3(Vec3::Y);
assert!((up - Vec3::NEG_X).length() < 1e-5, "{up}");
}
}
+8 -288
View File
@@ -1,6 +1,5 @@
//! The Horizon Extras page, on the settings shell (`settings_shell`): which rows it has and what
//! pressing them does. Each one's value and what it does stay in `extras::Item`; the sections crates
//! add (`horizon_hooks::pages::Place::Extras`) keep theirs.
//! pressing them does. Each one's value and what it does stay in `extras::Item`.
use crate::{
extras::{self, Item},
lang,
@@ -8,7 +7,6 @@ use crate::{
settings::Settings,
settings_shell::{self as shell, Binding, Group, Output, Row, Screen, Sheet, Value},
};
use horizon_hooks::pages::{self as added, Act, Label};
const TITLE: &str = "horizon.pages.extras";
const ABOUT: &str = "horizon.pages.extras.tooltip";
@@ -33,121 +31,6 @@ pub(crate) enum ExtraRow {
/// It isn't installed: where to get it. A note, not a button: Options > Mods already has the
/// one way into the Mods page (`every_setting_and_page_is_reached_from_one_place`).
ZoomInstall,
/// Row `.1` of the section registered page `.0` adds (`Place::Extras`).
Added(usize, usize),
}
#[cfg(test)]
thread_local! {
/// A test's own pages, instead of the game's registered ones: each test runs on its thread.
static TEST_PAGES: std::cell::Cell<Option<&'static [&'static dyn added::Page]>> =
const { std::cell::Cell::new(None) };
}
/// The pages crates registered, all places.
fn pages() -> &'static [&'static dyn added::Page] {
#[cfg(test)]
if let Some(pages) = TEST_PAGES.get() {
return pages;
}
added::engine::all()
}
/// The sections of this page, by their index among the registered pages.
fn sections() -> impl Iterator<Item = (usize, &'static dyn added::Page)> {
let pages = pages().iter().copied().enumerate();
pages.filter(|(_, page)| page.place() == added::Place::Extras)
}
fn label_text(label: Label) -> String {
match label {
Label::Key(key) => lang::get(key),
Label::Value(text) => text,
}
}
/// The page's group named `name`, made at the end if it has none.
fn group_named<'a>(
groups: &'a mut Vec<Group<ExtraRow>>,
name: &'static str,
) -> &'a mut Group<ExtraRow> {
let at = groups.iter().position(|group| group.name == name);
let at = at.unwrap_or_else(|| {
groups.push(Group {
name,
rows: Vec::new(),
});
groups.len() - 1
});
&mut groups[at]
}
/// The sections' rows, in the groups their titles and headings name.
fn add_sections(groups: &mut Vec<Group<ExtraRow>>) {
for (page, section) in sections() {
let mut rows = Vec::new();
section.rows(&mut rows);
let mut name = section.title();
for (r, row) in rows.into_iter().enumerate() {
let id = ExtraRow::Added(page, r);
let row = match row {
added::Row::Heading(key) => {
name = key;
continue;
}
added::Row::Note(text) => Row::new(id, text, Value::Note).live(false),
added::Row::Toggle { label, on } => {
Row::new(id, lang::get(label), Value::Switch(on))
}
added::Row::Cycle { label, value } => {
Row::new(id, lang::get(label), Value::Choice(label_text(value)))
}
added::Row::Slider { label, value, text } => {
let position = value.clamp(0.0, 1.0);
let text = label_text(text);
let value = Value::Slider {
position,
steps: None,
text,
};
Row::new(id, lang::get(label), value)
}
added::Row::Button { label } => Row::new(id, lang::get(label), Value::Press),
added::Row::Key { label, key } => {
let key = crate::keybinds::display_name(&key).plain();
Row::new(id, lang::get(label), Value::Read(key))
}
// A row kind newer than this engine.
_ => continue,
};
let row = match section.tooltip(r) {
Some(about) => row.about(label_text(about)),
None => row,
};
group_named(groups, name).rows.push(row);
}
}
}
/// Row `r` of registered page `page` was acted on: the options screen saves when it closes.
fn act(page: usize, r: usize, act: Act) -> Vec<Output> {
let Some(section) = pages().get(page) else {
return Vec::new();
};
section.act(r, act);
vec![Output::Event(Event::Changed)]
}
/// Whether row `r` of registered page `page` steps (a switch or a cycle).
fn steps(page: usize, r: usize) -> bool {
let mut rows = Vec::new();
if let Some(section) = pages().get(page) {
section.rows(&mut rows);
}
matches!(
rows.get(r),
Some(added::Row::Toggle { .. } | added::Row::Cycle { .. })
)
}
fn item_row(item: Item, s: &Settings) -> Row<ExtraRow> {
@@ -202,14 +85,9 @@ impl Screen for ExtrasScreen {
fn note_lines(&self) -> usize {
NOTE_LINES
}
/// The rows read the extras, the mods, whether cosmetics are offered and the sections' rows.
/// The rows read the extras, the mods and whether cosmetics are offered.
fn stamp(&self, s: &Settings) -> u64 {
let mut added = Vec::new();
for (_, section) in sections() {
section.rows(&mut added);
}
let cosmetics = horizon_cosmetics::enabled();
shell::stamp_of(&(&s.extras, &s.mods, cosmetics, added))
shell::stamp_of(&(&s.extras, &s.mods, horizon_cosmetics::enabled()))
}
fn sheets(&self, s: &Settings) -> Vec<Sheet<(), ExtraRow>> {
let mut groups = vec![Group {
@@ -229,7 +107,6 @@ impl Screen for ExtrasScreen {
}
}
}
add_sections(&mut groups);
vec![Sheet::new((), lang::get(TITLE), lang::get(ABOUT), groups)]
}
fn press(&mut self, s: &mut Settings, row: ExtraRow, back: bool) -> Vec<Output> {
@@ -248,29 +125,16 @@ impl Screen for ExtrasScreen {
Output::Event(Event::NativeSettings(i)),
],
ExtraRow::ZoomInstall => Vec::new(),
ExtraRow::Added(page, r) => {
act(page, r, if back { Act::PressBack } else { Act::Press })
}
}
}
fn slide(&mut self, _s: &mut Settings, row: ExtraRow, position: f32) {
if let ExtraRow::Added(page, r) = row {
act(page, r, Act::Slide(position));
}
}
/// With the wheel extra on, the wheel over an item changes it, without a click; the mod's rows
/// let the list scroll.
fn wheel_step(&mut self, s: &mut Settings, row: ExtraRow, back: bool) -> Option<Vec<Output>> {
match row {
ExtraRow::Item(item) => {
item.cycle(&mut s.extras, back);
Some(vec![Output::Event(Event::Changed)])
}
ExtraRow::Added(page, r) if steps(page, r) => {
Some(act(page, r, if back { Act::PressBack } else { Act::Press }))
}
_ => None,
}
let ExtraRow::Item(item) = row else {
return None;
};
item.cycle(&mut s.extras, back);
Some(vec![Output::Event(Event::Changed)])
}
}
@@ -428,148 +292,4 @@ mod tests {
shell.key(&mut s, K::ArrowDown, None, false, size);
assert_eq!(shell.focused_row(), Some(first));
}
/// A section with a switch in the page's Multiplayer group, then a heading of its own with a
/// number that steps either way, a note and a volume.
struct Section {
on: std::sync::Mutex<bool>,
step: std::sync::Mutex<i32>,
volume: std::sync::Mutex<f32>,
}
impl added::Page for Section {
fn id(&self) -> &'static str {
"test.section"
}
fn title(&self) -> &'static str {
"horizon.extras.header.multiplayer"
}
fn place(&self) -> added::Place {
added::Place::Extras
}
fn rows(&self, rows: &mut Vec<added::Row>) {
rows.push(added::Row::Toggle {
label: "test.section.switch",
on: *self.on.lock().unwrap(),
});
rows.push(added::Row::Heading("test.section.heading"));
rows.push(added::Row::Cycle {
label: "test.section.step",
value: Label::Value(self.step.lock().unwrap().to_string()),
});
rows.push(added::Row::Note("a note".into()));
rows.push(added::Row::Slider {
label: "test.section.volume",
value: *self.volume.lock().unwrap(),
text: Label::Value(String::new()),
});
}
fn act(&self, row: usize, act: Act) {
match (row, act) {
(0, Act::Press | Act::PressBack) => {
let mut on = self.on.lock().unwrap();
*on = !*on;
}
(2, Act::Press) => *self.step.lock().unwrap() += 1,
(2, Act::PressBack) => *self.step.lock().unwrap() -= 1,
(4, Act::Slide(v)) => *self.volume.lock().unwrap() = v,
_ => {}
}
}
}
/// A page for Horizon Settings: never on Extras.
struct Elsewhere;
impl added::Page for Elsewhere {
fn id(&self) -> &'static str {
"test.elsewhere"
}
fn title(&self) -> &'static str {
"test.elsewhere"
}
fn place(&self) -> added::Place {
added::Place::HorizonSettings
}
fn rows(&self, rows: &mut Vec<added::Row>) {
rows.push(added::Row::Button {
label: "test.elsewhere.button",
});
}
fn act(&self, _: usize, _: Act) {}
}
fn with_section() -> &'static Section {
let section: &'static Section = Box::leak(Box::new(Section {
on: false.into(),
step: 0.into(),
volume: 0.5.into(),
}));
let pages: &'static [&'static dyn added::Page] = Box::leak(Box::new([
&Elsewhere as &dyn added::Page,
section as &dyn added::Page,
]));
TEST_PAGES.set(Some(pages));
section
}
/// The section's first row joins the page's own Multiplayer group, after its rows; its heading
/// starts a group after the page's; a page for elsewhere adds nothing.
#[test]
fn a_section_joins_the_group_its_title_names_and_heads_its_own() {
with_section();
let s = Settings::default();
let sheets = ExtrasScreen.sheets(&s);
let groups = &sheets[0].groups;
let ids = |name: &str| -> Vec<ExtraRow> {
let group = groups.iter().find(|g| g.name == name).unwrap();
group.rows.iter().map(|r| r.id).collect()
};
let multiplayer = ids("horizon.extras.header.multiplayer");
assert_eq!(multiplayer.last(), Some(&ExtraRow::Added(1, 0)));
assert_eq!(groups.last().unwrap().name, "test.section.heading");
let own = [
ExtraRow::Added(1, 2),
ExtraRow::Added(1, 3),
ExtraRow::Added(1, 4),
];
assert_eq!(ids("test.section.heading"), own);
let all = rows_of(&s);
assert!(!all.iter().any(|r| matches!(r.id, ExtraRow::Added(0, _))));
}
/// A click flips the switch, a Shift click steps the number back, the wheel steps it, and the
/// note takes no click; the rows show what the section holds after each.
#[test]
fn a_sections_rows_act_on_it() {
let section = with_section();
let mut s = Settings::default();
let mut shell = Shell::<ExtrasScreen>::default();
let switch = ExtraRow::Added(1, 0);
let at = shell.point_at(&s, SIZE, switch);
assert!(
!shell
.click(&mut s, at, SIZE, false, false, false)
.is_empty()
);
assert!(*section.on.lock().unwrap());
let value = |s: &Settings, id| rows_of(s).into_iter().find(|r| r.id == id).unwrap().value;
assert_eq!(value(&s, switch), Value::Switch(true));
let step = ExtraRow::Added(1, 2);
let mut screen = ExtrasScreen;
screen.press(&mut s, step, true);
assert_eq!(value(&s, step), Value::Choice("-1".into()));
assert!(screen.wheel_step(&mut s, step, false).is_some());
assert_eq!(*section.step.lock().unwrap(), 0);
assert_eq!(
screen.wheel_step(&mut s, ExtraRow::Added(1, 3), false),
None
);
let note = shell.point_at(&s, SIZE, ExtraRow::Added(1, 3));
assert!(
shell
.click(&mut s, note, SIZE, false, false, false)
.is_empty()
);
screen.slide(&mut s, ExtraRow::Added(1, 4), 0.25);
assert_eq!(*section.volume.lock().unwrap(), 0.25);
}
}
+4 -4
View File
@@ -231,8 +231,8 @@ pub struct FogInputs<'a> {
/// `GameRenderer.nightVisionScale` while Night Vision is on without
/// Darkness, else 0 (`FogRenderer.computeFogColor`'s brightenFactor).
pub night_vision: f32,
/// The packs' `environment` fog settings (Polytone's).
pub environment: crate::pack_palette::Environment,
/// The packs' Polytone `environment` fog settings.
pub environment: horizon_polytone::colors::Environment,
}
/// `FogRenderer.setupFog` + `computeFogColor`.
@@ -581,7 +581,7 @@ mod tests {
let tables = FogTables {
water_fog_end: vec![1.0],
};
let environment = crate::pack_palette::Environment {
let environment = horizon_polytone::colors::Environment {
powder_snow_fog: Some(0xFF0000),
blindness_fog_distance: 40.0,
water_fog_brightening: 0.0,
@@ -613,7 +613,7 @@ mod tests {
}
fn inputs_with<'a>(
input: &FogInputs<'a>,
environment: crate::pack_palette::Environment,
environment: horizon_polytone::colors::Environment,
) -> FogInputs<'a> {
FogInputs {
environment,
+87 -39
View File
@@ -179,7 +179,7 @@ fn packed_wanted() -> bool {
std::env::var("HORIZON_PACKED_QUADS").as_deref() != Ok("0")
}
pub(crate) mod animate;
mod animate;
mod arena;
pub mod capture;
pub mod compositor;
@@ -239,7 +239,6 @@ mod reuse;
mod stars;
mod surface_setup;
use surface_setup::{AdapterChoice, AdapterFailure, open_adapter, pick_adapter, set_up_surface};
mod off_page;
pub mod submit;
mod sunrise_fan;
mod timing;
@@ -251,7 +250,6 @@ mod wgsl_prefix;
mod world_post;
pub mod world_primitives;
mod world_warp;
use off_page::move_off_page;
/// A section's instance: its camera-relative origin and, in `w`, the step of
/// its fixed-point UVs (`terrain.wgsl`'s `terrain_uv`).
@@ -728,6 +726,62 @@ impl Part {
}
}
}
/// `Renderer::compact_arena`'s and `shrink_translucent`'s moves: what the meshes
/// hold on `page` of `arena` (`part`) goes to its other pages in video memory,
/// until the next range would pass `budget` bytes (it keeps what is left; the
/// first range moves whatever its size, so none keeps the page closed), the
/// copies into `encoder`, the ranges' flat draw ids (`flat`) and, in the
/// section arena, GPU culling's records (`records`) along. False when the other
/// pages have no room left.
fn move_off_page<'a>(
arena: &mut arena::Arena,
encoder: &mut wgpu::CommandEncoder,
mut flat: Option<(&mut flat::Flat, &wgpu::Device, &mut upload::Uploads)>,
mut records: Option<&mut gpu_culling::Records>,
meshes: impl Iterator<Item = &'a mut SectionGpu>,
page: u32,
part: Part,
budget: &mut u64,
) -> bool {
debug_assert!(
records.is_none() || matches!(part, Part::Section { .. }),
"records follow the section arena's ranges"
);
let mut moved = false;
for s in meshes {
// Nothing of a mesh here: split, one without solid quads (its (0, 0) is no
// range of page 0); in the other arenas, one without translucent quads.
let Some((_, start, vertices)) = part.of(s).filter(|r| r.0 == page) else {
continue;
};
let bytes = vertices * arena.stride();
if moved && bytes > *budget {
break;
}
let Some(to) = arena.alloc_moved(vertices) else {
return false;
};
arena.copy(encoder, (page, start), to, vertices);
arena.release(page, start, vertices);
if let (Some((flat, device, uploads)), Some(id)) = (&mut flat, s.flat_id) {
let key = match part {
Part::Translucent => to.0 + flat::TRANSLUCENT_KEYS,
_ => to.0,
};
flat.place(device, encoder, uploads, id, key, to.1, vertices);
}
part.set(s, to);
// Its solid quads start the range in either layout.
if let (Part::Section { .. }, Some(records), Some(id)) =
(part, records.as_deref_mut(), s.gpu_id)
{
records.relocate(id, to.0, u64::from(s.solid_base()));
}
*budget = budget.saturating_sub(bytes);
moved = true;
}
true
}
/// Starts moving a page of a large arena far larger than its ranges in use
/// (`Arena::oversized`) into a new page of the size they get (`Arena::open_page`),
/// for `Renderer::shrink_translucent`: the page closes and `move_off_page` empties
@@ -1275,7 +1329,7 @@ pub struct Renderer {
celestial_buffer: wgpu::Buffer,
end_sky: end_sky::EndSky,
stars: stars::Stars,
/// Custom sky layers (`crate::custom_sky`), and the frame's from the game.
/// OptiFine custom sky layers (`crate::custom_sky`), and the frame's from the game.
custom_sky: custom_sky::CustomSky,
custom_sky_draws: Vec<crate::custom_sky::Draw>,
/// The glowing entities' outline (`LevelRenderer.entityOutlineTarget`).
@@ -4195,13 +4249,16 @@ impl Renderer {
CRUMBLING_BLEND,
false,
);
// RenderPipelines.ENTITY_SHADOW: translucent, back-face culled, depth test without writes.
// The quads lie exactly on the block tops like vanilla's, facing +Y so looking up from
// below culls them, but terrain and shadows are separate draw paths, so their fp32 depth
// interpolations disagree by a few dozen ulps of the fragment depth and the shadow's rim
// z-fights with the block top (speckle that re-rolls as the camera drifts a subpixel). A
// constant bias of 128 ulps covers that spread at any resolution while staying far below
// the 0.001 a model's feet sit higher, so shadows never bleed onto the feet.
// RenderPipelines.ENTITY_SHADOW: translucent, back-face culled, depth
// test without writes. The quads lie exactly on the block tops like
// vanilla's, facing +Y so looking up from below culls them, but
// terrain and shadows are separate draw paths, so their fp32 depth
// interpolations disagree by a few dozen ulps of the fragment depth
// and the shadow's rim z-fights with the block top (speckle that
// re-rolls as the camera drifts a subpixel). A constant bias of 128
// ulps covers that spread at any resolution while staying far below
// the 0.001 a model's feet sit higher, so shadows never bleed onto
// the feet.
let shadow_blob_pipeline = entity_pipeline_with(
"Entity shadows",
Some(wgpu::Face::Back),
@@ -5463,11 +5520,11 @@ impl Renderer {
))
});
}
/// The pack's custom sky layer textures (`crate::custom_sky::load`).
pub fn set_custom_sky(&mut self, pack: &dyn crate::custom_sky::SkyLayers) {
/// The pack's OptiFine custom sky layer textures (`crate::custom_sky::CustomSky::load`).
pub fn set_custom_sky(&mut self, pack: &crate::custom_sky::CustomSky) {
self.custom_sky.set_pack(&self.device, &self.queue, pack);
}
/// The custom sky layers the next frame draws (`SkyLayers::draws`): the game refills it each
/// The custom sky layers the next frame draws (`CustomSky::draws`): the game refills it each
/// frame, empty when the world's sky has none.
pub fn custom_sky_draws(&mut self) -> &mut Vec<crate::custom_sky::Draw> {
&mut self.custom_sky_draws
@@ -8295,12 +8352,8 @@ impl Renderer {
t.begin(&self.device, &self.queue);
}
if self.animated_textures {
self.hud.animate(
&mut self.uploads,
&mut encoder,
frame.ticks,
&self.animations,
);
self.hud
.animate_sprites(&mut self.uploads, &mut encoder, frame.ticks);
}
self.update_lightmap(
&mut encoder,
@@ -9238,11 +9291,13 @@ impl Renderer {
}
// Near water and distant water share one back-to-front draw order.
if self.translucent_draws.len() > section_translucent {
// Distant water goes after the near chunks', and replaced tiles' after all: each is a
// hair behind the one before, so depth leaves it only where that one has none. Back to
// front together, a far tile's water under a near chunk's (one fading in, or brought
// into view by a longer render distance) blended twice and looked opaque. The cost: far
// water just behind near water shows no surface of its own there.
// Distant water goes after the near chunks', and replaced tiles'
// after all: each is a hair behind the one before, so depth leaves
// it only where that one has none. Back to front together, a far
// tile's water under a near chunk's (one fading in, or brought
// into view by a longer render distance) blended twice and looked
// opaque. The cost: far water just behind near water shows no
// surface of its own there.
let tier = |instance: usize| match instance.checked_sub(visible.len()) {
None => 0,
Some(tile) if visible_lods[tile].old => 2,
@@ -10062,11 +10117,7 @@ impl Renderer {
let pack_reads_world = pack_post || native_post;
laps.lap("r_uploads");
// Phase 2 of occlusion culling draws over the depth, and a shader pack or TAA reads it.
// Post effects (a resource pack's chains, `use_depth_buffer`) read the world's depth
// after the world pass, as `GameRenderer.render` hands them the main target's.
let post_reads_depth = !frame.title && !frame.post_effects.effects.is_empty();
let keep_depth = pack_reads_world
|| post_reads_depth
|| (taa && self.world_post.taa)
|| oit_on
|| native_render::render_needs().world_depth
@@ -13664,7 +13715,6 @@ mod tests {
Some(Reported {
paper_white: 240,
peak: 998,
peak_guessed: true,
})
);
assert_eq!(
@@ -13672,7 +13722,6 @@ mod tests {
Some(Reported {
paper_white: 480,
peak: 480,
peak_guessed: true,
})
);
// White above the calibrated peak (a Samsung QD-OLED's 380, the slider at 456).
@@ -13681,7 +13730,6 @@ mod tests {
Some(Reported {
paper_white: 456,
peak: 456,
peak_guessed: true,
})
);
assert_eq!(os_reported(&info(Some(1000.0), None)), None);
@@ -15372,9 +15420,11 @@ fn log_os_reported(reported: Option<crate::graphics::Reported>) {
None => "Horizon renderer: the system doesn't report the display's luminance".into(),
});
}
/// What the OS reports in absolute nits, HDR output's scale (Windows: DXGI's SDR white level,
/// the brightness slider's, as Paper White, and its `MaxLuminance`, mostly the display's EDID
/// guess, as Peak Brightness), kept within the settings' ranges. The player's values win.
/// What the OS reports in absolute nits (Windows: DXGI's `MaxLuminance` and the SDR
/// white level the brightness slider sets), which is HDR output's scale there: SDR
/// white is Paper White, the display's peak is Peak Brightness. Both are kept within
/// the settings' ranges. The OS's figures often come from the display's EDID and may
/// run optimistic; the player's own value always wins.
fn os_reported(info: &wgpu::DisplayHdrInfo) -> Option<crate::graphics::Reported> {
let luminance = info.luminance?;
let usable = |n: f32| n.is_finite() && n > 0.0;
@@ -15386,7 +15436,6 @@ fn os_reported(info: &wgpu::DisplayHdrInfo) -> Option<crate::graphics::Reported>
// Windows' SDR brightness slider can set white above the reported peak (480 against
// a calibrated 380): the highlights then stay at white instead of dropping below it.
peak: (peak.round() as u32).clamp(200, 10_000).max(paper_white),
peak_guessed: true,
})
}
@@ -15422,7 +15471,6 @@ fn reported(l: horizon_hdr::wayland::Luminances) -> crate::graphics::Reported {
crate::graphics::Reported {
paper_white: 203,
peak: peak.clamp(200, 10_000),
peak_guessed: false,
}
}
@@ -15671,11 +15719,11 @@ mod shadow_caster_tests {
#[test]
fn the_shadow_pass_draws_terrain_outside_the_pack_s_planes() {
let kept = |near: Option<f32>, far: Option<f32>, y: f64, min: [f32; 3], depth: bool| {
let shadow = horizon_shaders::Shadow {
let shadow = horizon_render_pipeline::Shadow {
distance: 128.0,
near,
far,
..horizon_shaders::Shadow::default()
..horizon_render_pipeline::Shadow::default()
};
let camera = DVec3::new(13.5, y + 1.62, -13.5);
let view = pack_post::ShadowView {
@@ -64,7 +64,7 @@ enum Writer {
},
}
pub(crate) struct SpriteAnimator {
pub(super) struct SpriteAnimator {
sprites: Vec<Sprite>,
groups: Vec<Group>,
writer: Writer,
@@ -86,9 +86,6 @@ struct AnimationModes {
day_time: i64,
weather: f32,
values: std::collections::HashMap<String, f64>,
/// The open screen (an identity the game picks) and the ticks it has been open.
screen: Option<u64>,
screen_time: i64,
}
impl AnimationModes {
@@ -119,15 +116,8 @@ impl super::Renderer {
day_time: i64,
[rain, thunder]: [f32; 2],
frame: Option<&crate::environment::Frame>,
screen: Option<u64>,
) {
let modes = &mut self.animations.modes;
// `screen_time`: a tick more while the same screen stays open, from 0 on a new one.
modes.screen_time = match (screen, modes.screen) {
(Some(open), Some(was)) if open == was => modes.screen_time + 1,
_ => 0,
};
modes.screen = screen;
modes.day_time = day_time;
modes.weather = rain + thunder;
modes.values.clear();
@@ -143,16 +133,6 @@ impl super::Renderer {
}
impl SpriteAnimator {
/// What the modes read this frame, for the GUI atlas's sprites (their expressions aren't
/// compiled: those run on ticks).
pub(crate) fn mode_inputs(&self) -> horizon_assets::animation::ModeInputs<'static> {
horizon_assets::animation::ModeInputs {
day_time: self.modes.day_time,
screen_time: self.modes.screen_time,
weather: self.modes.weather,
expression: &|_| None,
}
}
/// `textures` are the sprite arrays, `split` layers each, `size` pixels
/// wide; `animations` hold their frames at that size. `compute`: whether
/// the device says it runs compute shaders writing storage textures.
@@ -381,7 +361,6 @@ impl SpriteAnimator {
let expression = |source: &str| values.get(source).copied();
let inputs = horizon_assets::animation::ModeInputs {
day_time: self.modes.day_time,
screen_time: self.modes.screen_time,
weather: self.modes.weather,
expression: &expression,
};
@@ -1,11 +1,12 @@
//! Custom sky layers (`crate::custom_sky`) on the GPU: one texture per layer, kept with the renderer (its pack),
//! OptiFine custom sky layers on the GPU: one texture per layer, kept with the renderer (its pack),
//! a static skybox mesh of the 3x2 grid's six faces, and one pipeline per blend mode. A frame
//! uploads one small uniform per drawn layer ([`crate::custom_sky::Draw`]) and draws 36 vertices
//! each, after the vanilla sky's disc and sunrise and before the sun, moon and stars.
use glam::Mat4;
use horizon_optifine::sky::{Blend, Face};
use wgpu::util::DeviceExt;
use crate::custom_sky::{Blend, Draw, SkyLayers};
use crate::custom_sky::{CustomSky as Pack, Draw};
/// The blend modes in the shader's `mode` order (custom_sky.wgsl).
const MODES: [Blend; 9] = [
@@ -84,76 +85,6 @@ fn blend(mode: Blend) -> wgpu::BlendComponent {
}
}
/// A face of the skybox, as the 3x2 grid of its texture holds them: `Bottom`, `Top`, `South` in
/// the top row, `West`, `North`, `East` below.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Face {
Bottom,
Top,
South,
West,
North,
East,
}
impl Face {
const ALL: [Self; 6] = [
Self::Bottom,
Self::Top,
Self::South,
Self::West,
Self::North,
Self::East,
];
/// The face's cell of the texture, `[u0, v0, u1, v1]` in 0..1.
fn cell(self) -> [f32; 4] {
let (col, row) = match self {
Self::Bottom => (0, 0),
Self::Top => (1, 0),
Self::South => (2, 0),
Self::West => (0, 1),
Self::North => (1, 1),
Self::East => (2, 1),
};
let (u, v) = (col as f32 / 3.0, row as f32 / 2.0);
[u, v, u + 1.0 / 3.0, v + 0.5]
}
/// The face's corners (on the unit cube around the camera; x east, y up, z south) in the
/// order of its cell's `(u0, v0)`, `(u1, v0)`, `(u1, v1)`, `(u0, v1)`. The side faces are
/// seen from inside with up at the top of their cell; the top face has north at the bottom of
/// its cell, the bottom face north at the top (the template's `N` marks).
fn corners(self) -> [[f32; 3]; 4] {
match self {
// Seen looking north: west on the left.
Self::North => [
[-1., 1., -1.],
[1., 1., -1.],
[1., -1., -1.],
[-1., -1., -1.],
],
Self::South => [[1., 1., 1.], [-1., 1., 1.], [-1., -1., 1.], [1., -1., 1.]],
Self::East => [[1., 1., -1.], [1., 1., 1.], [1., -1., 1.], [1., -1., -1.]],
Self::West => [
[-1., 1., 1.],
[-1., 1., -1.],
[-1., -1., -1.],
[-1., -1., 1.],
],
// Looking up with north toward the cell's bottom edge: east on the right.
Self::Top => [[-1., 1., 1.], [1., 1., 1.], [1., 1., -1.], [-1., 1., -1.]],
// Looking down with north toward the cell's top edge: east on the right.
Self::Bottom => [
[-1., -1., -1.],
[1., -1., -1.],
[1., -1., 1.],
[-1., -1., 1.],
],
}
}
}
/// The skybox's 36 vertices: two triangles per face, each corner with its cell's UV.
fn mesh() -> Vec<Vertex> {
let mut out = Vec::with_capacity(36);
@@ -325,15 +256,10 @@ impl CustomSky {
}
/// The pack's layer textures, replacing any before.
pub(super) fn set_pack(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
pack: &dyn SkyLayers,
) {
pub(super) fn set_pack(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, pack: &Pack) {
let limit = device.limits().max_texture_dimension_2d;
self.groups = pack
.images()
.images
.iter()
.map(|image| {
// Too large for this GPU: a 1x1 transparent texel, so the layer draws nothing.
@@ -487,38 +413,6 @@ impl CustomSky {
mod tests {
use super::*;
#[test]
fn the_grid_holds_each_face_once_and_faces_point_out() {
let mut cells: Vec<_> = Face::ALL
.iter()
.map(|f| f.cell().map(|x| (x * 6.0) as i32))
.collect();
cells.dedup();
assert_eq!(cells.len(), 6);
assert_eq!(Face::East.cell(), [2.0 / 3.0, 0.5, 1.0, 1.0]);
for face in Face::ALL {
let c = face.corners();
let sub = |a: [f32; 3], b: [f32; 3]| [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
let (a, b) = (sub(c[1], c[0]), sub(c[3], c[0]));
let n = [
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
];
let centre: [f32; 3] =
std::array::from_fn(|i| c.iter().map(|p| p[i]).sum::<f32>() / 4.0);
// Seen from inside, u runs right and v down, so u x v points away from the camera.
let dot: f32 = (0..3).map(|i| n[i] * centre[i]).sum();
assert!(dot > 0.0, "{face:?} is mirrored");
}
// The top cell's bottom edge (v1) is north: its corners 2 and 3 have z = -1.
assert_eq!(Face::Top.corners()[2][2], -1.0);
assert_eq!(Face::Top.corners()[3][2], -1.0);
// The bottom cell's top edge (v0) is north.
assert_eq!(Face::Bottom.corners()[0][2], -1.0);
assert_eq!(Face::Bottom.corners()[1][2], -1.0);
}
/// What a blend leaves a sky texel of `dst` with when the shader outputs `src`
/// (custom_sky.wgsl) for a layer texel `t` at brightness `b`, per channel.
fn mix(mode: Blend, dst: f32, t: f32, a: f32, b: f32) -> f32 {
@@ -6,7 +6,7 @@ struct Layer {
// Projection * view (camera-relative) * the layer's rotation.
clip_from_sky: mat4x4<f32>,
brightness: f32,
// crate::custom_sky::Blend, in gfx/custom_sky.rs's MODES order.
// horizon_optifine::sky::Blend, in gfx/custom_sky.rs's MODES order.
mode: u32,
pad: vec2<u32>,
hdr_settings: vec4<f32>,
+3 -49
View File
@@ -185,7 +185,7 @@ pub struct Hdr {
/// The display's black level, in nits: shadows are lifted onto it (`hdr.wgsl`).
pub black: f32,
pub tiers: [Tier; 4],
/// The HUD and menus' brightness, as a share of paper white: above 1 brighter than it.
/// The HUD and menus' brightness, as a share of paper white (0.25..=1).
pub ui: f32,
/// Which pixels of a light source shine (`Masks`).
pub masks: Masks,
@@ -956,7 +956,7 @@ impl Hdr {
f32::from(u8::from(pq)),
]);
params[5] = self.black.clamp(0.0, paper);
params[6] = self.ui;
params[6] = self.ui.clamp(0.25, 1.0);
params[7] = if self.lighting {
scene_gain(self.peak, self.paper_white)
} else {
@@ -1906,11 +1906,9 @@ struct ReferenceParams { peak: f32, scene: f32, world: f32, debug: f32, share: v
// Covered: the HUD as drawn, at UI Brightness.
(2.0, 0.3, 1.0, 1.0, 0.3),
(2.0, 0.3, 1.0, 0.5, 0.3 * 0.5f32.powf(1.0 / 2.2)),
// UI Brightness above paper white: brighter than drawn.
(2.0, 0.3, 1.0, 2.0, 0.3 * 2f32.powf(1.0 / 2.2)),
// Added over the world (the text highlight), which it doesn't hide.
(0.6, 0.8, 0.0, 1.0, 0.8),
// At or below white, at paper white: the HUD's picture as drawn.
// At or below white, at 100%: the HUD's picture as drawn.
(0.7, 0.45, 0.5, 1.0, 0.45),
] {
hdr.ui = ui;
@@ -1930,50 +1928,6 @@ struct ReferenceParams { peak: f32, scene: f32, world: f32, debug: f32, share: v
}
}
/// On the frame without the HUD's layer, the HUD at UI Brightness reaches its nits, and
/// above the peak stops there, as on the layered frame.
#[test]
fn the_hud_at_ui_brightness_stops_at_the_peak() {
let Some((device, queue)) = gpu() else {
return;
};
let mut hdr = Hdr::with_debug(&device, &queue, F::Rgba8Unorm, Output::ScRgb, false, None);
(hdr.paper_white, hdr.peak) = (200.0, 300.0);
let target = device.create_texture(&wgpu::TextureDescriptor {
label: None,
size: wgpu::Extent3d {
width: 1,
height: 1,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: Output::ScRgb.format(),
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
// UI Brightness (a share of paper white) and the white HUD's nits.
for (ui, want) in [(0.5, 100.0), (1.25, 250.0), (2.0, 300.0)] {
hdr.ui = ui;
let frame = hdr.frame(&device, [1, 1]);
let mut encoder = device.create_command_encoder(&Default::default());
fill(&mut encoder, &frame, [0.2, 0.2, 0.2, 1.0]);
hdr.keep_world(&mut encoder, None);
// Opaque white HUD over the world: alpha 1 is full coverage.
fill(&mut encoder, &frame, [1.0; 4]);
hdr.begin(&queue, true);
hdr.present(&queue, &mut encoder, &target, true, None);
let got = texel(&device, &queue, encoder, &target);
for c in &got[..3] {
assert!(
(c * 80.0 - want).abs() < 0.5,
"UI {ui}: {got:?}, not {want} nits"
);
}
}
}
#[test]
fn the_menu_blur_keeps_the_light_it_blurs() {
let Some((device, queue)) = gpu() else {
@@ -338,7 +338,7 @@ pub struct NativeFrame {
reflection_sampler: wgpu::Sampler,
/// The shadow map the stages read, and the camera that draws it when the pack asks for one.
pub shadow_map: super::native_shadow::ShadowMap,
shadow_camera: Option<horizon_shaders::Shadow>,
shadow_camera: Option<horizon_render_pipeline::Shadow>,
/// Whether the pack's material reads `reflection` (`NativePack::new`): else no mirror
/// is drawn and `has_reflection` stays 0.
pub reflects: bool,
@@ -1497,7 +1497,7 @@ pub fn frame_inputs(
size: [u32; 2],
seconds: f32,
index: u32,
shadow: Option<&horizon_shaders::Shadow>,
shadow: Option<&horizon_render_pipeline::Shadow>,
) -> FrameInputs {
let aspect = size[0] as f32 / size[1].max(1) as f32;
let view = crate::camera::view_matrix(frame.yaw, frame.pitch);
@@ -6,8 +6,8 @@
//! draw lists (`ShadowView`) serve both; the map's texels are distorted toward the camera as the
//! prelude's `shadow_position` undoes.
use horizon_render_pipeline::Shadow;
use horizon_shader_api::{Api, Frame as FrameInputs, manifest::Shadow as Settings, stages::GROUP};
use horizon_shaders::Shadow;
use super::native_material::TerrainShape;
use super::wgsl_prefix::{TERRAIN, prefixed, section};
@@ -1,59 +0,0 @@
//! `move_off_page`, moved out of `gfx.rs` unchanged.
use super::*;
/// `Renderer::compact_arena`'s and `shrink_translucent`'s moves: what the meshes
/// hold on `page` of `arena` (`part`) goes to its other pages in video memory,
/// until the next range would pass `budget` bytes (it keeps what is left; the
/// first range moves whatever its size, so none keeps the page closed), the
/// copies into `encoder`, the ranges' flat draw ids (`flat`) and, in the
/// section arena, GPU culling's records (`records`) along. False when the other
/// pages have no room left.
pub(super) fn move_off_page<'a>(
arena: &mut arena::Arena,
encoder: &mut wgpu::CommandEncoder,
mut flat: Option<(&mut flat::Flat, &wgpu::Device, &mut upload::Uploads)>,
mut records: Option<&mut gpu_culling::Records>,
meshes: impl Iterator<Item = &'a mut SectionGpu>,
page: u32,
part: Part,
budget: &mut u64,
) -> bool {
debug_assert!(
records.is_none() || matches!(part, Part::Section { .. }),
"records follow the section arena's ranges"
);
let mut moved = false;
for s in meshes {
// Nothing of a mesh here: split, one without solid quads (its (0, 0) is no
// range of page 0); in the other arenas, one without translucent quads.
let Some((_, start, vertices)) = part.of(s).filter(|r| r.0 == page) else {
continue;
};
let bytes = vertices * arena.stride();
if moved && bytes > *budget {
break;
}
let Some(to) = arena.alloc_moved(vertices) else {
return false;
};
arena.copy(encoder, (page, start), to, vertices);
arena.release(page, start, vertices);
if let (Some((flat, device, uploads)), Some(id)) = (&mut flat, s.flat_id) {
let key = match part {
Part::Translucent => to.0 + flat::TRANSLUCENT_KEYS,
_ => to.0,
};
flat.place(device, encoder, uploads, id, key, to.1, vertices);
}
part.set(s, to);
// Its solid quads start the range in either layout.
if let (Part::Section { .. }, Some(records), Some(id)) =
(part, records.as_deref_mut(), s.gpu_id)
{
records.relocate(id, to.0, u64::from(s.solid_base()));
}
*budget = budget.saturating_sub(bytes);
moved = true;
}
true
}
+2 -38
View File
@@ -142,7 +142,7 @@ pub struct EffectiveOutput {
/// Paper white, peak and black level in nits, as HDR output's last pass and its
/// metadata take them, while it is on.
pub(crate) nits: [f32; 3],
/// The HUD and menus' brightness, as a share of paper white: their nits over its.
/// The HUD and menus' brightness, as a share of paper white.
pub(crate) ui_brightness: f32,
}
impl Default for EffectiveOutput {
@@ -271,8 +271,7 @@ pub(crate) fn resolve_output(
settings.peak_nits(reading.luminance) as f32,
settings.black_level_nits(),
],
ui_brightness: settings.ui_nits(reading.luminance) as f32
/ settings.paper_white_nits(reading.luminance) as f32,
ui_brightness: settings.hdr_ui_brightness as f32 / 100.0,
}
}
@@ -300,7 +299,6 @@ pub(crate) mod tests {
luminance: Some(Reported {
paper_white: 400,
peak: 1000,
peak_guessed: false,
}),
hdr: system,
},
@@ -568,39 +566,6 @@ pub(crate) mod tests {
);
}
/// UI Brightness reaches HDR output as its nits over paper white's: 1 on Auto, whatever
/// paper white is; a value set by hand holds its nits, Paper White on Auto or set by hand.
#[test]
fn ui_brightness_is_its_nits_over_paper_whites() {
let (caps, reading) = window(Some(Output::Pq), Some(true));
let share = |paper_white, ui_nits, reading| {
let settings = Graphics {
hdr: HdrMode::On,
hdr_paper_white: paper_white,
hdr_ui_nits: ui_nits,
..Graphics::default()
};
let frame = resolve_frame(
&settings,
FrameRequest::default(),
caps,
reading,
Bgra8Unorm,
);
resolve_output(&settings, caps, reading, frame, true).ui_brightness
};
// Auto: the report's 400 nits, 203 without one, or the player's 250.
let unreported = DisplayReading::default();
for (paper_white, reading) in [(0, reading), (0, unreported), (250, reading)] {
assert_eq!(share(paper_white, 0, reading), 1.0);
}
assert_eq!(share(0, 100, reading), 0.25);
assert_eq!(share(0, 480, reading), 1.2);
assert_eq!(share(0, 406, unreported), 2.0);
assert_eq!(share(250, 100, reading), 0.4);
assert_eq!(share(250, 480, reading), 1.92);
}
/// Two renderers' states, a window's and an offscreen one's, made and changed in turn:
/// neither's offer or report shows in the other's snapshot, and the offscreen one
/// stopping its watch (HDR Off) leaves the window's report alone.
@@ -651,7 +616,6 @@ pub(crate) mod tests {
window_reading.luminance = Some(Reported {
paper_white: 300,
peak: 800,
peak_guessed: false,
});
assert_eq!(window_output(window_reading).nits[..2], [300.0, 800.0]);
assert_eq!(
@@ -549,10 +549,8 @@ mod tests {
reading.luminance = Some(Reported {
paper_white: 250,
peak: 600,
peak_guessed: false,
});
// 150 nits under the reported paper white of 250: 0.6 of it.
settings.hdr_ui_nits = 150;
settings.hdr_ui_brightness = 60;
let mut swapchain = Swapchain::default();
let next = candidate(&settings, caps, reading, &applied);
let published = commit(&mut swapchain, &applied, next, caps, &mut None);
@@ -585,7 +583,6 @@ mod tests {
reading.luminance = Some(Reported {
paper_white,
peak: 1000,
peak_guessed: false,
});
let next = candidate(&settings, caps, reading, &published);
let again = commit(&mut swapchain, &published, next, caps, &mut rejected);
@@ -731,7 +728,6 @@ mod tests {
luminance: (hdr == Some(true)).then_some(Reported {
paper_white: 300,
peak: 800,
peak_guessed: false,
}),
hdr,
};
@@ -4,7 +4,8 @@
use std::collections::BTreeMap;
use horizon_shaders::{BufferObject, CustomImage, Format, Kind};
use horizon_render_pipeline::{Format, Kind};
use horizon_shaders::{BufferObject, CustomImage};
pub(super) struct PackImage {
pub(super) def: CustomImage,
@@ -18,7 +18,8 @@
use std::collections::HashMap;
use glam::{DMat4, DVec3};
use horizon_shaders::{BUFFERS, Format, Group, Kind, Post, SHADOW_COLORS};
use horizon_render_pipeline::{BUFFERS, Format, Kind, SHADOW_COLORS};
use horizon_shaders::{Group, Post};
use smallvec::SmallVec;
use super::pack_draw::DrawUniforms;
@@ -892,7 +893,7 @@ fn depth_linear(interface: &horizon_shaders::Interface, linear: u8) -> [f32; 4]
}
fn source(name: &str, shadow_space: bool, shadow_pass: bool, watershadow: bool) -> Source {
let shadow_color = horizon_shaders::shadow_color(name);
let shadow_color = horizon_render_pipeline::shadow_color(name);
let shadow_depth = match name {
"shadowtex0" | "watershadow" | "shadowtex0HW" => Some(0),
"shadowtex1" | "shadowtex1HW" => Some(1),
@@ -911,7 +912,7 @@ fn source(name: &str, shadow_space: bool, shadow_pass: bool, watershadow: bool)
};
}
}
if shadow_space && horizon_shaders::buffer_index(name).is_some() {
if shadow_space && horizon_render_pipeline::buffer_index(name).is_some() {
// colortex buffers are not this pass's.
return Source::Dummy;
}
@@ -925,7 +926,7 @@ fn source(name: &str, shadow_space: bool, shadow_pass: bool, watershadow: bool)
"normals" => Source::Normals,
"specular" => Source::Specular,
horizon_shaders::WATER_MASK => Source::WaterMask,
n => horizon_shaders::buffer_index(n).map_or(Source::Dummy, Source::Color),
n => horizon_render_pipeline::buffer_index(n).map_or(Source::Dummy, Source::Color),
}
}
@@ -1582,7 +1583,7 @@ fn bindings(
!unbound
&& (t.stage != pack_textures::stage(group)
|| released.0 != t.stage
|| horizon_shaders::buffer_index(&t.name)
|| horizon_render_pipeline::buffer_index(&t.name)
.is_none_or(|b| released.1 & 1 << b == 0))
});
let image = pack
@@ -2432,7 +2433,7 @@ impl PackPost {
let shadow_config = post.config.shadow.clone();
let noise_size = post.config.noise_resolution.unwrap_or(256);
let half_lives = post.config.half_lives;
for (name, ticks) in horizon_shaders::HALF_LIVES.iter().zip(half_lives) {
for (name, ticks) in horizon_render_pipeline::HALF_LIVES.iter().zip(half_lives) {
if let Some(t) = ticks {
notes.push(format!("`{name}` is {t} ticks"));
}
@@ -10,7 +10,7 @@
//! a fit to one driver's values, not a rule Iris states. `RGBA4` and `RGBA2` read back the `RGBA8`
//! bytes in the same probe, as Horizon's 8-bit buffers already do.
use horizon_shaders::{Format, Kind};
use horizon_render_pipeline::{Format, Kind};
use naga::{BinaryOperator, Expression, Literal, MathFunction, Statement, VectorSize};
/// The bits each channel keeps in a buffer of format `f`, for the formats that keep fewer than
@@ -3,7 +3,7 @@
//! with two sides that `shadowcomp` programs flip as composite programs flip colortex (6.1.6).
//! Square, `shadowMapResolution` pixels.
use horizon_shaders::SHADOW_COLORS;
use horizon_render_pipeline::SHADOW_COLORS;
type Ops<T> = wgpu::Operations<T>;
type Color = wgpu::Color;
@@ -97,7 +97,11 @@ pub(super) fn stand_in(device: &wgpu::Device) -> wgpu::TextureView {
}
impl ShadowTargets {
pub fn reset_history(&mut self, device: &wgpu::Device, shadow: &horizon_shaders::Shadow) {
pub fn reset_history(
&mut self,
device: &wgpu::Device,
shadow: &horizon_render_pipeline::Shadow,
) {
let mut colors = 0;
let mut doubled = 0;
let formats = std::array::from_fn(|n| match &self.color[n] {
@@ -125,7 +129,7 @@ impl ShadowTargets {
/// resolution is kept within the device's limit.
pub fn new(
device: &wgpu::Device,
shadow: &horizon_shaders::Shadow,
shadow: &horizon_render_pipeline::Shadow,
formats: [wgpu::TextureFormat; SHADOW_COLORS],
colors: u16,
doubled: u16,
@@ -1,7 +1,8 @@
//! A shader pack's custom textures (`texture.<stage>.<name>`, `texture.noise`) on the GPU, and
//! which of them a program's sampler reads.
use horizon_shaders::{CustomTexture, Group, Kind, Texels, Texture, TextureDim, TextureSource};
use horizon_render_pipeline::Kind;
use horizon_shaders::{CustomTexture, Group, Texels, Texture, TextureDim, TextureSource};
/// What a custom texture is once uploaded: an image of ours, or one the game already has.
pub(super) enum Image {
@@ -118,12 +119,12 @@ pub(super) fn find(textures: &[PackTexture], group: Group, ty: &str, name: &str)
} else {
wgpu::TextureViewDimension::D2
};
let buffer = horizon_shaders::buffer_index(name);
let buffer = horizon_render_pipeline::buffer_index(name);
let staged = textures.iter().position(|t| {
t.stage == stage(group)
&& t.dimension == dimension
&& (t.name == name
|| buffer.is_some() && horizon_shaders::buffer_index(&t.name) == buffer)
|| buffer.is_some() && horizon_render_pipeline::buffer_index(&t.name) == buffer)
});
staged.or_else(|| {
textures.iter().position(|t| {
@@ -28,7 +28,7 @@
//! [-w, w], and the depth buffers packs read hold GL window depth.
use glam::{DMat4, DVec3, Mat4, Vec3, Vec4};
use horizon_shaders::Shadow;
use horizon_render_pipeline::Shadow;
use super::Frame;
@@ -440,7 +440,7 @@ pub struct FrameUniforms {
center_depth: Option<f32>,
/// The pack's shadow settings (7.1).
pub shadow: Shadow,
/// The pack's smoothing half-lives in ticks (`horizon_shaders::HALF_LIVES`).
/// The pack's smoothing half-lives in ticks (`horizon_render_pipeline::HALF_LIVES`).
pub half_lives: [Option<f32>; 4],
}
+32 -151
View File
@@ -30,22 +30,6 @@ fn marker_driver(text: &str) -> &str {
pub struct Reported {
pub paper_white: u32,
pub peak: u32,
/// The peak is the display's own claim (DXGI's `MaxLuminance`, mostly its EDID figure),
/// which often runs high: Auto still uses it, but the HDR page asks to calibrate.
pub peak_guessed: bool,
}
/// Whether HDR is set up for the display, and whether the player kept the system's setup.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HdrSetup {
/// The system says nothing: the player calibrates.
Unknown,
/// The system says, but only guesses the peak: the player calibrates.
Guessed,
/// Peak Brightness and Paper White follow the system.
Automatic,
/// The system said, and the player changed them anyway.
Changed,
}
/// The top of Peak Brightness's scale: `peak_nits_at(PEAK_STEPS)` is PQ's 10 000 nits.
@@ -68,23 +52,6 @@ pub fn peak_step_of(nits: f32) -> f32 {
};
step.clamp(0.0, PEAK_STEPS as f32)
}
/// The steps of Paper White's and UI Brightness's scale: Auto (0), then 10 to 480 nits by 10.
pub const NITS_STEPS: u32 = 48;
/// Paper White's or UI Brightness's setting at slider position `v`, 0 to 1 ([`NITS_STEPS`]).
pub fn nits_at(v: f32) -> u32 {
(v * NITS_STEPS as f32).round() as u32 * 10
}
/// Where a Paper White or UI Brightness setting sits on its slider, 0 to 1.
pub fn nits_position(nits: u32) -> f32 {
nits as f32 / (NITS_STEPS * 10) as f32
}
/// A saved Paper White or UI Brightness: Auto (0), or 10 to 480 nits.
fn nits_setting(nits: u32) -> u32 {
match nits {
0 => 0,
n => n.clamp(10, 480),
}
}
/// Simple-level presets. `Custom` leaves every option as the player set it.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
@@ -537,6 +504,8 @@ pub fn reduced_far_detail_suggested() -> bool {
REDUCED_SUGGESTED.load(std::sync::atomic::Ordering::Relaxed)
}
/// The name the options screen waits on to bind `Graphics::hdr_key`.
pub const HDR_KEY: &str = "horizon.hdrSettings";
/// The Screenshot Quality choices, times the game's resolution.
pub const SCREENSHOT_QUALITIES: [u32; 4] = [1, 2, 4, 8];
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
@@ -846,12 +815,14 @@ pub struct Graphics {
/// HDR black level: the display's darkest black, in hundredths of a nit; 0, the default,
/// lifts nothing. Shadows are lifted onto it, and the HDR metadata says it.
pub hdr_black_level: u32,
/// HDR brightness of the HUD and menus, in nits; 0 is Auto: at paper white
/// ([`Graphics::ui_nits`]). The first-person hand stays at the world's.
pub hdr_ui_nits: u32,
/// HDR brightness of the HUD and menus, in percent of paper white (25..=100); the
/// first-person hand stays at the world's.
pub hdr_ui_brightness: u32,
/// HDR Lighting: Horizon's own HDR look, light sources, the sun and sunlight above paper
/// white. Off, HDR output shows the same picture as SDR, for shader packs or the vanilla look.
pub hdr_lighting: bool,
/// A vanilla key name (`HDR_KEY`): opens HDR's settings from the game. Unbound by default.
pub hdr_key: String,
/// The graphics driver whose last start ended before HDR output showed a frame (the
/// marker found, [`HDR_MARKER`]): a window on it stays SDR whatever the mode, until the
/// mode is changed or the window starts on another driver ([`Graphics::hdr_held`]).
@@ -929,8 +900,9 @@ impl Default for Graphics {
hdr_paper_white: 0,
hdr_peak: 0,
hdr_black_level: 0,
hdr_ui_nits: 0,
hdr_ui_brightness: 100,
hdr_lighting: true,
hdr_key: "key.keyboard.unknown".into(),
hdr_failed_start: None,
battery: false,
shadow_cache: false,
@@ -1003,8 +975,6 @@ impl Graphics {
}
pub fn parse(text: &str) -> Self {
let mut g = Self::default();
// UI Brightness: `hdrUiNits`, else an older file's share of Paper White.
let (mut ui_nits, mut ui_share) = (None, None);
for line in text.lines() {
let Some((key, value)) = line.split_once(':') else {
continue;
@@ -1094,9 +1064,12 @@ impl Graphics {
"frameGeneration" => g.frame_generation = FrameGeneration::parse(value),
"hdr" => g.hdr = HdrMode::parse(value, g.hdr),
"hdrLighting" => g.hdr_lighting = flag(g.hdr_lighting),
"hdrPaperWhite" => g.hdr_paper_white = nits_setting(number(g.hdr_paper_white)),
"hdrUiNits" => ui_nits = value.parse().ok().map(nits_setting),
"hdrUiBrightness" => ui_share = value.parse::<u32>().ok(),
"hdrPaperWhite" => {
g.hdr_paper_white = match number(g.hdr_paper_white) {
0 => 0,
n => n.clamp(10, 480),
}
}
"hdrPeak" => {
g.hdr_peak = match number(g.hdr_peak) {
0 => 0,
@@ -1104,6 +1077,10 @@ impl Graphics {
}
}
"hdrBlackLevel" => g.hdr_black_level = number(g.hdr_black_level).min(100),
"hdrUiBrightness" => {
g.hdr_ui_brightness = number(g.hdr_ui_brightness).clamp(25, 100)
}
"hdrKey" if value.starts_with("key.") => g.hdr_key = value.to_owned(),
"hdrFailedStart" => g.hdr_failed_start = Some(value.to_owned()),
"batteryMode" => g.battery = flag(g.battery),
"shadowCache" => g.shadow_cache = flag(g.shadow_cache),
@@ -1121,20 +1098,11 @@ impl Graphics {
_ => {}
}
}
// The share was 25 to 100% of Paper White. 100% is Auto, and so is any share of an
// Auto Paper White, whose nits the file doesn't hold; another share of a Paper White set
// by hand is its nits, to the nearest step.
g.hdr_ui_nits = ui_nits.unwrap_or(match (ui_share, g.hdr_paper_white) {
(Some(share), paper) if share < 100 && paper != 0 => {
((paper * share.max(25) + 500) / 1000).max(1) * 10
}
_ => 0,
});
g
}
pub fn serialize(&self) -> String {
let mut text = format!(
"preset:{}\nlodDistance:{}\nfarDetail:{}\ncaveCulling:{}\nentityCulling:{}\nentityCullingPrecheck:{}\nparticleCulling:{}\nleafCulling:{}\nopaqueIce:{}\nblockEntityCacheShulkerBoxes:{}\nblockEntityCacheSigns:{}\nblockEntityCacheDecoratedPots:{}\nblockEntityCacheBells:{}\nblockEntityCacheChests:{}\nblockEntityCacheBanners:{}\nblockEntityCacheCopperGolemStatues:{}\nchunkUpdates:{}\nuploadBudgetKiB:{}\ntextureBudgetMiB:{}\nsky:{}\nfog:{}\nweather:{}\nanimatedTextures:{}\nditherCutoutMips:{}\npresentMode:{}\nframePacing:{}\nworkers:{}\noverlay:{}\nresolutionScale:{}\nfxaa:{}\ntaa:{}\nupscaling:{}\nupscalingSharpness:{}\nscreenshotQuality:{}\nframeGeneration:{}\nhdr:{}\nhdrPaperWhite:{}\nhdrPeak:{}\nhdrBlackLevel:{}\nhdrUiNits:{}\nhdrLighting:{}\nbatteryMode:{}\nshadowCache:{}\ngraphicsApi:{}\npickedShaderPack:{}\nbatchTranslucent:{}\nhdrSmoothGradients:{}\ngpuCulling:{}\n",
"preset:{}\nlodDistance:{}\nfarDetail:{}\ncaveCulling:{}\nentityCulling:{}\nentityCullingPrecheck:{}\nparticleCulling:{}\nleafCulling:{}\nopaqueIce:{}\nblockEntityCacheShulkerBoxes:{}\nblockEntityCacheSigns:{}\nblockEntityCacheDecoratedPots:{}\nblockEntityCacheBells:{}\nblockEntityCacheChests:{}\nblockEntityCacheBanners:{}\nblockEntityCacheCopperGolemStatues:{}\nchunkUpdates:{}\nuploadBudgetKiB:{}\ntextureBudgetMiB:{}\nsky:{}\nfog:{}\nweather:{}\nanimatedTextures:{}\nditherCutoutMips:{}\npresentMode:{}\nframePacing:{}\nworkers:{}\noverlay:{}\nresolutionScale:{}\nfxaa:{}\ntaa:{}\nupscaling:{}\nupscalingSharpness:{}\nscreenshotQuality:{}\nframeGeneration:{}\nhdr:{}\nhdrPaperWhite:{}\nhdrPeak:{}\nhdrBlackLevel:{}\nhdrUiBrightness:{}\nhdrLighting:{}\nhdrKey:{}\nbatteryMode:{}\nshadowCache:{}\ngraphicsApi:{}\npickedShaderPack:{}\nbatchTranslucent:{}\nhdrSmoothGradients:{}\ngpuCulling:{}\n",
self.preset.name(),
self.lod_distance,
self.far_detail.name(),
@@ -1181,8 +1149,9 @@ impl Graphics {
self.hdr_paper_white,
self.hdr_peak,
self.hdr_black_level,
self.hdr_ui_nits,
self.hdr_ui_brightness,
bool_text(self.hdr_lighting),
self.hdr_key,
bool_text(self.battery),
bool_text(self.shadow_cache),
self.api.name(),
@@ -1197,6 +1166,14 @@ impl Graphics {
text += &self.blue_map.serialize();
text
}
/// Binds Horizon's graphics key `name` (`HDR_KEY`); false for another name.
pub fn set_key(&mut self, name: &str, value: String) -> bool {
match name {
HDR_KEY => self.hdr_key = value,
_ => return false,
}
true
}
pub fn save(&self, dir: &Path) {
let _ = fs::write(dir.join(FILE), self.serialize());
}
@@ -1208,13 +1185,6 @@ impl Graphics {
n => n,
}
}
/// The HUD and menus' brightness, in nits: the player's, or on Auto paper white's.
pub fn ui_nits(&self, reported: Option<Reported>) -> u32 {
match self.hdr_ui_nits {
0 => self.paper_white_nits(reported),
n => n,
}
}
/// The peak the world's highlights reach, in nits: the player's, or on Auto
/// what the system `reported`, else 1000.
pub fn peak_nits(&self, reported: Option<Reported>) -> u32 {
@@ -1223,15 +1193,6 @@ impl Graphics {
n => n,
}
}
/// How HDR is set up for the display the system `reported`.
pub fn hdr_setup(&self, reported: Option<Reported>) -> HdrSetup {
match reported {
None => HdrSetup::Unknown,
Some(_) if self.hdr_peak != 0 || self.hdr_paper_white != 0 => HdrSetup::Changed,
Some(r) if r.peak_guessed => HdrSetup::Guessed,
Some(_) => HdrSetup::Automatic,
}
}
/// The black level, in nits. Not the system's: files from before saved 0 for that
/// (Auto), and a reported black would lift every display's shadows by default.
pub fn black_level_nits(&self) -> f32 {
@@ -1304,83 +1265,6 @@ pub fn apply_preset(preset: Preset, s: &mut crate::settings::Settings) {
mod tests {
use super::*;
/// UI Brightness is saved in nits, 0 for Auto. An older file's share of Paper White reads
/// as Auto at 100% or with Paper White on Auto, else as that share of it to the nearest step.
#[test]
fn ui_brightness_round_trips_in_nits_and_reads_older_shares() {
for (file, nits) in [
("", 0),
("hdrUiNits:0\n", 0),
("hdrUiNits:160\n", 160),
("hdrUiNits:5\n", 10),
("hdrUiNits:9000\n", 480),
("hdrUiNits:x\n", 0),
// Older files: 100% is Auto,
("hdrPaperWhite:200\nhdrUiBrightness:100\n", 0),
// any share of Paper White's Auto too,
("hdrUiBrightness:60\n", 0),
("hdrPaperWhite:0\nhdrUiBrightness:25\n", 0),
// and another share of a Paper White set by hand is its nits.
("hdrPaperWhite:200\nhdrUiBrightness:60\n", 120),
("hdrUiBrightness:50\nhdrPaperWhite:203\n", 100),
("hdrPaperWhite:250\nhdrUiBrightness:75\n", 190),
("hdrPaperWhite:10\nhdrUiBrightness:25\n", 10),
("hdrPaperWhite:480\nhdrUiBrightness:10\n", 120),
// The new key wins over the old one.
(
"hdrPaperWhite:200\nhdrUiBrightness:60\nhdrUiNits:300\n",
300,
),
("hdrUiNits:0\nhdrPaperWhite:200\nhdrUiBrightness:60\n", 0),
] {
let g = Graphics::parse(file);
assert_eq!(g.hdr_ui_nits, nits, "{file}");
let saved = g.serialize();
assert!(!saved.contains("hdrUiBrightness"), "{file}");
assert!(saved.contains(&format!("\nhdrUiNits:{nits}\n")), "{file}");
assert_eq!(Graphics::parse(&saved), g, "{file}");
}
let mut g = Graphics::default();
assert_eq!(g.ui_nits(None), 203);
let reported = Some(Reported {
paper_white: 300,
peak: 1000,
peak_guessed: false,
});
assert_eq!(g.ui_nits(reported), 300);
g.hdr_paper_white = 120;
assert_eq!(g.ui_nits(reported), 120);
g.hdr_ui_nits = 400;
assert_eq!((g.ui_nits(None), g.ui_nits(reported)), (400, 400));
}
#[test]
fn hdr_setup_asks_to_calibrate_where_the_peak_is_guessed() {
let wayland = Reported {
paper_white: 203,
peak: 1015,
peak_guessed: false,
};
let windows = Reported {
paper_white: 400,
peak: 1000,
peak_guessed: true,
};
let mut g = Graphics::default();
assert_eq!(g.hdr_setup(None), HdrSetup::Unknown);
assert_eq!(g.hdr_setup(Some(wayland)), HdrSetup::Automatic);
assert_eq!(g.hdr_setup(Some(windows)), HdrSetup::Guessed);
// Auto still takes the guess, over the 1000-nit default.
assert_eq!(g.peak_nits(Some(windows)), 1000);
assert_eq!(g.paper_white_nits(Some(windows)), 400);
for (peak, paper_white) in [(800, 0), (0, 240)] {
(g.hdr_peak, g.hdr_paper_white) = (peak, paper_white);
assert_eq!(g.hdr_setup(Some(windows)), HdrSetup::Changed);
assert_eq!(g.hdr_setup(Some(wayland)), HdrSetup::Changed);
assert_eq!(g.hdr_setup(None), HdrSetup::Unknown);
}
}
#[test]
fn hdr_modes_keep_boolean_files_and_round_trip_auto() {
assert_eq!(Graphics::parse("").hdr, HdrMode::Off);
@@ -1546,8 +1430,9 @@ mod tests {
hdr_paper_white: 240,
hdr_peak: 1400,
hdr_black_level: 12,
hdr_ui_nits: 160,
hdr_ui_brightness: 60,
hdr_lighting: false,
hdr_key: "key.keyboard.h".into(),
hdr_failed_start: Some(
"Vulkan 10de:2684 NVIDIA GeForce RTX 4090; NVIDIA; 581.57".into(),
),
@@ -1689,10 +1574,6 @@ mod tests {
Graphics::parse("shaderPack:Pack.zip\n"),
Graphics::default()
);
// The HDR page's old shortcut key loads as nothing, and the next save drops it.
let old = Graphics::parse("hdr:true\nhdrKey:key.keyboard.h\nhdrPeak:1000\n");
assert_eq!((old.hdr, old.hdr_peak), (HdrMode::On, 1000));
assert!(!old.serialize().contains("hdrKey"));
assert_eq!(
Graphics::parse(
"workers:abc\nsky:maybe\ncullLeaves:true\nentityCulling:maybe\nleafCulling:maybe\n"
+82 -39
View File
@@ -8,14 +8,13 @@ use crate::{
gfx::EffectiveOutput,
graphics::{
EntityCullingMode, FarDetail, FrameGeneration, GpuCullingMode, GraphicsApi, HdrMode,
HdrSetup as Setup, LeafCullingMode, Overlay, Pacing, ParticleCullingMode, PresentMode,
Preset, Upscaling,
LeafCullingMode, Overlay, Pacing, ParticleCullingMode, PresentMode, Preset, Upscaling,
},
graphics_style::Cost,
lang,
options_ui::{Event, Page as OptionsPage},
settings::{Opt, Settings},
settings_shell::{self as shell, Group, Output, Row, Screen, Sheet, Value},
settings_shell::{self as shell, Binding, Group, Output, Row, Screen, Sheet, Value},
text::Text,
};
@@ -107,6 +106,8 @@ pub enum H {
FrameGeneration,
Hdr,
HdrBlackLevel,
/// Binds `Graphics::hdr_key`.
HdrKey,
/// Starts HDR calibration (`crate::hdr_calibration`).
HdrCalibrate,
HdrPeak,
@@ -613,7 +614,7 @@ pub fn rows() -> Vec<(Page, Vec<(&'static str, Vec<Setting>)>)> {
h(
H::HdrUiBrightness,
None,
"How bright the HUD and menus are. Auto: at paper white.",
"How bright the HUD and menus are, against paper white.",
),
h(
H::HdrLighting,
@@ -622,6 +623,14 @@ pub fn rows() -> Vec<(Page, Vec<(&'static str, Vec<Setting>)>)> {
),
],
),
(
"Shortcut",
vec![h(
H::HdrKey,
None,
"A key that opens this screen from the game. Not bound at first.",
)],
),
],
),
];
@@ -676,9 +685,28 @@ const LOD_STEPS: [u32; 24] = [
crate::lod::MAX_CHUNKS,
];
/// Whether the system set HDR up for the display, and whether the player kept it.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Setup {
/// The system says nothing: the player calibrates.
Unknown,
/// Peak Brightness and Paper White follow the system.
Automatic,
/// The system said, and the player changed them anyway.
Changed,
}
/// `out`: the output of the renderer the settings are shown for, with what its display reports.
fn setup(s: &Settings, out: &EffectiveOutput) -> Setup {
s.horizon.hdr_setup(out.reported)
setup_of(&s.horizon, out.reported.is_some())
}
fn setup_of(g: &crate::graphics::Graphics, reported: bool) -> Setup {
if !reported {
Setup::Unknown
} else if g.hdr_peak == 0 && g.hdr_paper_white == 0 {
Setup::Automatic
} else {
Setup::Changed
}
}
/// What the HDR page says while HDR output is held back on this window's graphics driver
/// (`EffectiveOutput::held`): picking a mode tries again (`H::cycle`).
@@ -696,19 +724,13 @@ fn description(
(Item::Horizon(H::HdrCalibrate), Setup::Unknown) => {
"Your display didn't say how bright it gets: calibrate to set it up."
}
(Item::Horizon(H::HdrCalibrate), Setup::Guessed) => {
"Your display's reported peak is often too high: calibrate to set it up."
}
(Item::Horizon(H::HdrCalibrate), Setup::Automatic) => {
"Already set up for your display. Only calibrate if HDR looks wrong."
}
(Item::Horizon(H::HdrCalibrate), Setup::Changed) => {
"Puts Peak Brightness and Paper White back to your display's setup."
}
// A guessed peak's Auto is what calibration replaced; paper white is the player's own.
(Item::Horizon(h @ (H::HdrPeak | H::HdrPaperWhite)), Setup::Automatic | Setup::Changed)
if h == H::HdrPaperWhite || out.reported.is_some_and(|r| !r.peak_guessed) =>
{
(Item::Horizon(H::HdrPeak | H::HdrPaperWhite), Setup::Automatic | Setup::Changed) => {
"Set up for your display. Leave on Auto unless it looks wrong."
}
(Item::Horizon(H::GpuCulling), _) if s.horizon.gpu_culling == GpuCullingMode::Auto => {
@@ -797,14 +819,14 @@ impl H {
H::ResolutionScale => Some((g.resolution_scale as f32 - 50.0) / 150.0),
H::UpscalingSharpness => Some(g.upscaling_sharpness as f32 / 100.0),
// Auto, then 10..480 by 10.
H::HdrPaperWhite => Some(crate::graphics::nits_position(g.hdr_paper_white)),
H::HdrUiBrightness => Some(crate::graphics::nits_position(g.hdr_ui_nits)),
H::HdrPaperWhite => Some(g.hdr_paper_white as f32 / 480.0),
// Auto, then the peak's scale.
H::HdrPeak => Some(match g.hdr_peak {
0 => 0.0,
n => (1.0 + crate::graphics::peak_step_of(n as f32)) / PEAK_POSITIONS as f32,
}),
H::HdrBlackLevel => Some(g.hdr_black_level as f32 / 100.0),
H::HdrUiBrightness => Some((g.hdr_ui_brightness as f32 - 25.0) / 75.0),
_ => None,
}
}
@@ -816,26 +838,13 @@ impl H {
H::Workers => Some(max_workers()),
H::ResolutionScale => Some(30),
H::UpscalingSharpness => Some(20),
H::HdrPaperWhite | H::HdrUiBrightness => Some(crate::graphics::NITS_STEPS),
H::HdrPaperWhite => Some(48),
H::HdrPeak => Some(PEAK_POSITIONS),
H::HdrBlackLevel => Some(100),
H::HdrUiBrightness => Some(15),
_ => None,
}
}
/// "Paper White: Auto (203)" or "Paper White: 160": a setting in nits, 0 for Auto, which
/// shows the nits Auto gives.
fn nits_label(self, s: &Settings, out: &EffectiveOutput) -> Text {
let (g, r) = (&s.horizon, out.reported);
let (caption, set, auto) = match self {
H::HdrPaperWhite => ("Paper White", g.hdr_paper_white, g.paper_white_nits(r)),
H::HdrPeak => ("Peak Brightness", g.hdr_peak, g.peak_nits(r)),
_ => ("UI Brightness", g.hdr_ui_nits, g.ui_nits(r)),
};
match set {
0 => labelled(caption, lang::tr("Auto (%s)", &[&auto])),
n => labelled(caption, n),
}
}
fn label(self, s: &Settings, out: &EffectiveOutput) -> Text {
let g = &s.horizon;
match self {
@@ -932,13 +941,30 @@ impl H {
H::FrameGeneration => labelled("Frame Generation", g.frame_generation.label()),
H::Hdr => labelled("HDR", g.hdr.label()),
H::HdrCalibrate => Text::literal(lang::get(match setup(s, out) {
Setup::Unknown | Setup::Guessed => "Calibrate...",
Setup::Unknown => "Calibrate...",
Setup::Automatic => "Calibrate Anyway...",
Setup::Changed => "Use Automatic Setup",
})),
H::HdrPaperWhite | H::HdrPeak | H::HdrUiBrightness => self.nits_label(s, out),
H::HdrPaperWhite => match g.hdr_paper_white {
0 => labelled(
"Paper White",
lang::tr("Auto (%s)", &[&g.paper_white_nits(out.reported)]),
),
n => labelled("Paper White", n.to_string()),
},
H::HdrPeak => match g.hdr_peak {
0 => labelled(
"Peak Brightness",
lang::tr("Auto (%s)", &[&g.peak_nits(out.reported)]),
),
n => labelled("Peak Brightness", n.to_string()),
},
H::HdrBlackLevel => labelled("Black Level", format!("{:.2}", g.black_level_nits())),
H::HdrUiBrightness => labelled_percent("UI Brightness", g.hdr_ui_brightness),
H::HdrLighting => labelled("HDR Lighting", on_off(g.hdr_lighting)),
H::HdrKey => {
Text::translate("Shortcut: %s", &[crate::keybinds::display_name(&g.hdr_key)])
}
H::BatteryMode => labelled("Battery Mode", on_off(g.battery)),
H::ShadowCache => labelled("Shadow Cache", on_off(g.shadow_cache)),
// A press across the row, as Calibrate...: the list it opens shows the pick.
@@ -979,8 +1005,7 @@ impl H {
H::Workers => g.workers = (v * max_workers() as f32).round() as u32,
H::ResolutionScale => g.resolution_scale = (50.0 + (v * 30.0).round() * 5.0) as u32,
H::UpscalingSharpness => g.upscaling_sharpness = (v * 20.0).round() as u32 * 5,
H::HdrPaperWhite => g.hdr_paper_white = crate::graphics::nits_at(v),
H::HdrUiBrightness => g.hdr_ui_nits = crate::graphics::nits_at(v),
H::HdrPaperWhite => g.hdr_paper_white = (v * 48.0).round() as u32 * 10,
H::HdrPeak => {
g.hdr_peak = match (v * PEAK_POSITIONS as f32).round() as u32 {
0 => 0,
@@ -988,6 +1013,7 @@ impl H {
}
}
H::HdrBlackLevel => g.hdr_black_level = (v * 100.0).round() as u32,
H::HdrUiBrightness => g.hdr_ui_brightness = (25.0 + (v * 15.0).round() * 5.0) as u32,
_ => {}
}
}
@@ -1163,7 +1189,10 @@ impl Item {
fn steps(self, s: &Settings, out: &EffectiveOutput) -> bool {
self.switch(s, out).is_none()
&& self.slider(s).is_none()
&& !matches!(self, Item::Horizon(H::HdrCalibrate | H::ShaderPacks))
&& !matches!(
self,
Item::Horizon(H::HdrKey | H::HdrCalibrate | H::ShaderPacks)
)
}
/// On/off settings and their value.
pub fn switch(self, s: &Settings, out: &EffectiveOutput) -> Option<bool> {
@@ -1318,6 +1347,12 @@ fn shown(item: Item, s: &Settings) -> bool {
}
/// A control's label as its name and its value ("Render Distance" and "12 chunks").
fn parts(item: Item, s: &Settings, out: &EffectiveOutput) -> (String, Option<String>) {
// The shortcut's label is one translated "Shortcut: %s"; the row shows its key apart, as
// KeyBindsScreen does ("> key <" while it waits, `settings_shell::Value::Binding`).
if item == Item::Horizon(H::HdrKey) {
let key = crate::keybinds::display_name(&s.horizon.hdr_key).plain();
return (lang::get("Shortcut"), Some(key));
}
shell::name_and_value(&item.label(s, out).plain())
}
@@ -1326,9 +1361,13 @@ fn parts(item: Item, s: &Settings, out: &EffectiveOutput) -> (String, Option<Str
fn row(setting: Setting, s: &Settings, out: &EffectiveOutput) -> Row<Item> {
let item = setting.item;
let (name, value) = parts(item, s, out);
let value = match (item.switch(s, out), item.slider(s)) {
(Some(on), _) => Value::Switch(on),
(None, Some(position)) => Value::Slider {
let value = match (item, item.switch(s, out), item.slider(s)) {
(Item::Horizon(H::HdrKey), ..) => Value::Binding(Binding::key(
crate::graphics::HDR_KEY,
value.unwrap_or_default(),
)),
(_, Some(on), _) => Value::Switch(on),
(_, None, Some(position)) => Value::Slider {
position,
steps: item.slider_steps(),
text: value.unwrap_or_default(),
@@ -1544,7 +1583,7 @@ mod tests {
use super::*;
use crate::{
graphics_style as style,
settings_shell::{Columns, Hosted, Shell, Target},
settings_shell::{Capture, Columns, Hosted, Shell, Target},
};
use winit::keyboard::KeyCode as K;
@@ -2010,6 +2049,10 @@ mod tests {
precheck.why.as_deref(),
Some("Only applies to asynchronous entity culling.")
);
let key = find(&s, Item::Horizon(H::HdrKey));
assert_eq!(key.name, "Shortcut");
let waits = Capture::Key(crate::graphics::HDR_KEY);
assert!(matches!(key.value, Value::Binding(ref b) if b.capture == waits));
let calibrate = find(&s, Item::Horizon(H::HdrCalibrate));
assert_eq!(calibrate.value, Value::Press);
}
@@ -2694,7 +2737,7 @@ mod tests {
H::HdrPaperWhite => s.horizon.hdr_paper_white,
H::HdrPeak => s.horizon.hdr_peak,
H::HdrBlackLevel => s.horizon.hdr_black_level,
H::HdrUiBrightness => s.horizon.hdr_ui_nits,
H::HdrUiBrightness => s.horizon.hdr_ui_brightness,
_ => unreachable!(),
};
let fresh = || {
@@ -2,7 +2,7 @@
//! reports.
use super::tests::{CHANGED, CLICK, DRAGGED, FB, NONE, SIZE, Ui, offering, press, row, ui};
use super::*;
use crate::settings_shell::{Columns, Hosted};
use crate::settings_shell::{Capture, Columns, Hosted};
use winit::keyboard::KeyCode as K;
#[test]
@@ -64,12 +64,17 @@ fn hdr_auto_can_be_selected_before_querying_and_brightness_waits_for_output() {
let peak = row(&mut ui, &s, Item::Horizon(H::HdrPeak));
assert_eq!(press(&mut ui, &mut s, (tx + 2.0, peak[1] + 4.0)), [DRAGGED]);
ui.release();
// Calibrate starts the calibration.
// Calibrate starts the calibration; the shortcut row waits for a key.
let calibrate = row(&mut ui, &s, Item::Horizon(H::HdrCalibrate));
assert_eq!(
press(&mut ui, &mut s, at(calibrate)),
[CLICK, Output::Push(OptionsPage::HdrCalibration)]
);
let key = row(&mut ui, &s, Item::Horizon(H::HdrKey));
assert_eq!(
press(&mut ui, &mut s, at(key)),
[CLICK, Output::Bind(Capture::Key(crate::graphics::HDR_KEY))]
);
assert!(ui.changed());
}
@@ -120,125 +125,14 @@ fn auto_levels_and_setup_read_their_own_renderers_report() {
assert_eq!(label(H::HdrCalibrate, &silent), "Calibrate...");
}
/// The setup rows' label and description for each kind of report: none, a compositor's
/// (Wayland), and Windows', whose peak is the display's own guess.
#[test]
fn the_setup_rows_ask_to_calibrate_unless_the_system_set_the_display_up() {
let trusted = offering(true, Some(true));
let mut guessed = trusted;
guessed.reported = guessed.reported.map(|r| crate::graphics::Reported {
peak_guessed: true,
..r
});
let rows = |s: &Settings, out: &EffectiveOutput| {
[H::HdrCalibrate, H::HdrPeak, H::HdrPaperWhite].map(|h| {
let item = Item::Horizon(h);
(item.label(s, out).plain(), description(item, s, out, "-"))
})
};
let calibrate = "Calibrate...";
let set_up = "Set up for your display. Leave on Auto unless it looks wrong.";
let mut s = Settings::default();
s.horizon.hdr = HdrMode::On;
let none = (
calibrate.into(),
"Your display didn't say how bright it gets: calibrate to set it up.",
);
assert_eq!(
rows(&s, &NONE),
[
none.clone(),
("Peak Brightness: Auto (1000)".into(), "-"),
("Paper White: Auto (203)".into(), "-"),
]
);
assert_eq!(
rows(&s, &guessed),
[
(
calibrate.into(),
"Your display's reported peak is often too high: calibrate to set it up."
),
("Peak Brightness: Auto (1000)".into(), "-"),
("Paper White: Auto (400)".into(), "-"),
]
);
assert_eq!(
rows(&s, &trusted),
[
(
"Calibrate Anyway...".into(),
"Already set up for your display. Only calibrate if HDR looks wrong."
),
("Peak Brightness: Auto (1000)".into(), set_up),
("Paper White: Auto (400)".into(), set_up),
]
);
// Calibrated or set by hand, either report offers its automatic setup back.
s.horizon.hdr_peak = 800;
// Peak's Auto stays a guess on Windows: only Paper White is said to be set up there.
let changed = |peak| {
[
(
"Use Automatic Setup".into(),
"Puts Peak Brightness and Paper White back to your display's setup.",
),
("Peak Brightness: 800".into(), peak),
("Paper White: Auto (400)".into(), set_up),
]
};
assert_eq!(rows(&s, &guessed), changed("-"));
assert_eq!(rows(&s, &trusted), changed(set_up));
assert_eq!(rows(&s, &NONE)[0], none);
let mut ui = ui();
ui.screen.output = guessed;
let at = |[x, y, ..]: [f32; 4]| (x + 8.0, y + 4.0);
let calibrate = row(&mut ui, &s, Item::Horizon(H::HdrCalibrate));
assert_eq!(press(&mut ui, &mut s, at(calibrate)), [CLICK, CHANGED]);
assert_eq!((s.horizon.hdr_peak, s.horizon.hdr_paper_white), (0, 0));
assert_eq!(
press(&mut ui, &mut s, at(calibrate)),
[CLICK, Output::Push(OptionsPage::HdrCalibration)]
);
}
/// UI Brightness reads as Paper White does, in nits apart from it: Auto shows the paper white
/// in use, and a value set by hand its own nits, whatever Paper White is. Its slider has Paper
/// White's steps: Auto, then 10 to 480 nits by 10.
#[test]
fn ui_brightness_reads_in_nits_on_paper_whites_steps() {
let mut s = Settings::default();
s.horizon.hdr = HdrMode::On;
let reporting = offering(true, Some(true));
let label = |s: &Settings, out| Item::Horizon(H::HdrUiBrightness).label(s, out).plain();
assert_eq!(label(&s, &reporting), "UI Brightness: Auto (400)");
assert_eq!(label(&s, &NONE), "UI Brightness: Auto (203)");
s.horizon.hdr_paper_white = 250;
assert_eq!(label(&s, &reporting), "UI Brightness: Auto (250)");
s.horizon.hdr_ui_nits = 160;
for paper_white in [0, 80, 250] {
s.horizon.hdr_paper_white = paper_white;
assert_eq!(label(&s, &reporting), "UI Brightness: 160");
}
let item = Item::Horizon(H::HdrUiBrightness);
assert_eq!(
H::HdrUiBrightness.slider_steps(),
H::HdrPaperWhite.slider_steps()
);
H::HdrUiBrightness.set_slider(&mut s, 0.0);
let mut reached = vec![s.horizon.hdr_ui_nits];
for _ in 0..H::HdrUiBrightness.slider_steps().unwrap() {
item.step_slider(&mut s, true, 200.0, &reporting);
reached.push(s.horizon.hdr_ui_nits);
}
let paper: Vec<u32> = std::iter::once(0).chain((10..=480).step_by(10)).collect();
assert_eq!(reached, paper);
// Each value sits where Paper White's same value does.
for nits in paper {
s.horizon.hdr_ui_nits = nits;
s.horizon.hdr_paper_white = nits;
assert_eq!(item.slider(&s), Item::Horizon(H::HdrPaperWhite).slider(&s));
}
fn hdr_setup_follows_what_the_system_reported() {
let mut g = crate::graphics::Graphics::default();
assert_eq!(setup_of(&g, false), Setup::Unknown);
assert_eq!(setup_of(&g, true), Setup::Automatic);
g.hdr_paper_white = 80;
assert_eq!(setup_of(&g, true), Setup::Changed);
assert_eq!(setup_of(&g, false), Setup::Unknown);
}
/// While HDR output is held back on this window's driver, the HDR row says so and keeps
+4 -5
View File
@@ -206,9 +206,8 @@ fn display(light: vec3<f32>) -> vec4<f32> {
let shows = params.world > 0.5 && params.peak > 1.0
&& (hand || (!covered && shows_world(f.rgb, w.rgb)));
var light = lit(f.rgb, w.a, shows, true).light;
// The HUD and menus at their own brightness, by how much they cover, no brighter than the
// peak (as `fs_hdr_layered`): UI Brightness can pass paper white.
light = min(light * mix(1.0, params.ui, ui), vec3(params.peak));
// The HUD and menus at their own brightness, by how much they cover.
light *= mix(1.0, params.ui, ui);
return display(light);
}
@@ -276,8 +275,8 @@ fn backdrop(w: vec3<f32>) -> vec3<f32> {
// The HUD over the world in gamma, the space the HUD blends in: its own part, what its backdrop
// B doesn't explain (H = P - (1 - c) B, exact for the layering blends), at UI Brightness, over
// (1 - c) of the world, light above white included. Where the world is at or below white, with
// UI Brightness at paper white, that is the HUD's picture as drawn.
// (1 - c) of the world, light above white included. Where the world is at or below white, at
// 100%, that is the HUD's picture as drawn.
fn layered(at: vec2<i32>) -> vec3<f32> {
let w = max(textureLoad(frame, at, 0).rgb, vec3(0.0));
let p = textureLoad(hud, at, 0).rgb;
+53 -1
View File
@@ -12,7 +12,6 @@ use std::{
};
use wgpu::util::DeviceExt;
mod animate;
pub mod cit_glint;
mod pack_text;
pub mod pictures;
@@ -2877,6 +2876,59 @@ impl HudRenderer {
pack_text::PackText::new(device, self.format, &self.atlas_view, shader, self.coverage)
});
}
pub fn animate_sprites(
&mut self,
uploads: &mut crate::gfx::upload::Uploads,
encoder: &mut wgpu::CommandEncoder,
tick: u64,
) {
let Some(texture) = &self.atlas_texture else {
return;
};
for (rect, animation, previous) in &mut self.sprite_animations {
// `SpriteContents.AnimationState`: the frame from the mcmeta's
// list, blended into the next one by the sub-frame's thousandths
// when the sprite interpolates.
let blend = animation.blend(tick);
let state = (blend.frame, blend.next, blend.millis);
if state == *previous {
continue;
}
let (w, h) = rect.texels((texture.width(), texture.height()));
let pixels = if blend.millis == 0 {
animation.resized_pixels(blend.frame, w, h)
} else {
let current = animation.resized_pixels(blend.frame, w, h);
let next = animation.resized_pixels(blend.next, w, h);
let millis = blend.millis;
current
.iter()
.zip(next)
.map(|(&a, b)| {
((u32::from(a) * (1000 - millis) + u32::from(b) * millis + 500) / 1000)
as u8
})
.collect()
};
uploads.texture(
encoder,
wgpu::TexelCopyTextureInfo {
texture,
mip_level: 0,
origin: wgpu::Origin3d {
x: (rect.u0 * texture.width() as f32).round() as u32,
y: (rect.v0 * texture.height() as f32).round() as u32,
z: 0,
},
aspect: wgpu::TextureAspect::All,
},
w,
h,
&pixels,
);
*previous = state;
}
}
/// Stage `patches` into the shared atlas texture.
pub fn upload_patches(
&self,
@@ -1,61 +0,0 @@
//! The GUI atlas's animated sprites, frame by frame (vanilla's or a Polytone `mode`'s).
impl super::HudRenderer {
/// Shows each animated sprite's frame: its mcmeta's, or what its Polytone `mode` picks.
pub(crate) fn animate(
&mut self,
uploads: &mut crate::gfx::upload::Uploads,
encoder: &mut wgpu::CommandEncoder,
tick: u64,
animator: &crate::gfx::animate::SpriteAnimator,
) {
let inputs = animator.mode_inputs();
let inputs = Some(&inputs);
let Some(texture) = &self.atlas_texture else {
return;
};
for (rect, animation, previous) in &mut self.sprite_animations {
// `SpriteContents.AnimationState`: the frame from the mcmeta's
// list, blended into the next one by the sub-frame's thousandths
// when the sprite interpolates; or what a Polytone `mode` picks.
let blend = animation.blend_with(tick, inputs);
let state = (blend.frame, blend.next, blend.millis);
if state == *previous {
continue;
}
let (w, h) = rect.texels((texture.width(), texture.height()));
let pixels = if blend.millis == 0 {
animation.resized_pixels(blend.frame, w, h)
} else {
let current = animation.resized_pixels(blend.frame, w, h);
let next = animation.resized_pixels(blend.next, w, h);
let millis = blend.millis;
current
.iter()
.zip(next)
.map(|(&a, b)| {
((u32::from(a) * (1000 - millis) + u32::from(b) * millis + 500) / 1000)
as u8
})
.collect()
};
uploads.texture(
encoder,
wgpu::TexelCopyTextureInfo {
texture,
mip_level: 0,
origin: wgpu::Origin3d {
x: (rect.u0 * texture.width() as f32).round() as u32,
y: (rect.v0 * texture.height() as f32).round() as u32,
z: 0,
},
aspect: wgpu::TextureAspect::All,
},
w,
h,
&pixels,
);
*previous = state;
}
}
}
+1 -2
View File
@@ -67,6 +67,7 @@ mod jei_sprites;
pub mod jigsaw_block;
pub mod keypad;
pub mod terrain_model;
pub use horizon_polytone::colors as polytone_colors;
pub use horizon_polytone::creative_tabs as polytone_creative_tabs;
pub use horizon_world::json_folder;
pub mod level_loading;
@@ -88,8 +89,6 @@ pub mod options_fixes;
pub mod options_ui;
pub mod overlay;
pub mod pack_browser;
pub mod pack_formats;
pub mod pack_hooks;
pub mod pack_options_ui;
pub mod pack_palette;
pub mod pack_screen;
+2 -2
View File
@@ -2593,7 +2593,8 @@ impl Lod {
self.returned_at = std::time::Instant::now();
let nodes = &self.plan.nodes;
self.mesh_samples.retain(|key| nodes.contains(key));
// Queued tiles the view left are not sampled; a running one is dropped (`finish_sample`).
// Queued tiles the view left are not sampled (a running one is
// dropped when it finishes, `finish_sample`).
drop_queued_samples(&self.shared, &mut self.pending, |key| {
self.plan.nodes.contains(key)
});
@@ -2679,7 +2680,6 @@ impl Lod {
}
if let Some((columns, mask)) = self.kept.take(&key) {
self.ready.insert(key, columns);
self.sync_sample(key);
if let Some(mask) = mask {
self.detail_masks.insert(key, mask);
}
+68 -25
View File
@@ -339,7 +339,6 @@ impl Opt {
Opt::SharePresence => "options.sharePresence",
Opt::DirectionalAudio => "options.directionalAudio",
Opt::MusicToast => "options.musicToast",
Opt::Page(page, row) => pages::slider_caption(page, row),
}
}
pub(crate) fn kind(self, s: &Settings) -> Kind {
@@ -403,7 +402,6 @@ impl Opt {
Opt::PadLookDeadzone => unit(s.controller.look_deadzone, 0.0, 0.5),
Opt::PadTriggerThreshold => unit(s.controller.trigger_threshold, 0.1, 0.9),
Opt::SprintWindow => unit(s.sprint_window as f32, 0.0, 10.0),
Opt::Page(page, row) => pages::slider_kind(page, row),
_ => Kind::Cycle,
}
}
@@ -880,7 +878,6 @@ impl Opt {
gamepad::TextKeyboard::Horizon => "horizon.options.keyboard.horizon",
gamepad::TextKeyboard::Steam => "horizon.options.keyboard.steam",
}),
Opt::Page(page, row) => pages::slider_text(page, row),
_ => Text::empty(),
}
}
@@ -955,7 +952,6 @@ impl Opt {
Opt::PadLookDeadzone => s.controller.look_deadzone = step(0.0, 50.0) / 100.0,
Opt::PadTriggerThreshold => s.controller.trigger_threshold = step(10.0, 90.0) / 100.0,
Opt::SprintWindow => s.sprint_window = step(0.0, 10.0) as i32,
Opt::Page(page, row) => pages::slide(page, row, v),
_ => {}
}
if self.affects_preset() {
@@ -1335,8 +1331,6 @@ pub enum Page {
ModProject,
/// A native mod's settings page: its index in `mods.installed`.
NativeMod(usize),
/// A page a crate registered (`horizon_hooks::pages`): its index in `OptionsUi::pages`.
Added(usize),
/// Horizon's shader packs, either kind (`shader_pack_list`).
ShaderPacks,
/// The picked pack's options: the shader screen (`shader_ui`) at the pack's main screen.
@@ -1396,8 +1390,6 @@ enum Act {
/// A native mod's key (mod, key) waits for a key; a button of its page.
NativeKey(usize, usize),
NativeButton(usize, usize),
/// A registered page's row: (page, row).
PageRow(usize, usize),
OpenModFolder,
/// Browse mods: the page of listing `.0` of `mod_browser`.
ModProject(usize),
@@ -1625,8 +1617,7 @@ pub struct OptionsUi {
/// Key Binds: the mapping waiting for a key ([`NATIVE_KEY`]: a native
/// mod's, `selected_native_key`).
pub selected_key: Option<&'static str>,
/// The native mod's key waiting: (mod, key) in `mods.installed`, or a registered page's
/// (page, row) ([`pages::PAGE_KEY`]).
/// The native mod's key waiting: (mod, key) in `mods.installed`.
selected_native_key: Option<(usize, usize)>,
/// Bindings: the action waiting for a pad button.
pub selected_pad_action: Option<&'static str>,
@@ -1870,6 +1861,25 @@ fn wide_extras(s: &Settings, width: impl Fn(&str) -> f32) -> Vec<crate::extras::
})
.collect()
}
/// What a button opening one of Horizon's pages says on hover.
fn horizon_page_tooltip(page: Page) -> Option<&'static str> {
Some(match page {
Page::Horizon => "horizon.pages.settings.tooltip",
Page::Studio => "horizon.pages.studio.tooltip",
Page::Graphics => "horizon.pages.graphics.tooltip",
Page::GraphicsAdvanced => "horizon.pages.graphics_advanced.tooltip",
Page::Controller => "horizon.pages.controller.tooltip",
Page::Extras => "horizon.pages.extras.tooltip",
Page::Pvp => "horizon.pages.pvp.tooltip",
Page::Agent => "horizon.pages.agent.tooltip",
Page::SplitProfiles => "horizon.pages.split_profiles.tooltip",
Page::ShaderPacks => "horizon.pages.shader_packs.tooltip",
Page::Mods => "horizon.pages.mods.tooltip",
Page::SplitSetup => "horizon.pages.split_setup.tooltip",
Page::Music => "horizon.pages.music.tooltip",
_ => return None,
})
}
/// Rows of an OptionsList: (big?, options) or a header.
enum Row {
Big(Opt),
@@ -1889,17 +1899,14 @@ fn tooltip_text(s: &Settings, wd: &Widget) -> Option<Text> {
return None;
};
let key = opt.tooltip_key(s);
let Opt::Page(page, row) = opt else {
return lang::has(&key).then(|| tr(&key));
};
return pages::tooltip(*page, *row);
return lang::has(&key).then(|| tr(&key));
};
let mut tip = None;
if let Act::Extra(item) = act {
tip = Some(Text::literal(item.description_text()));
}
if let Act::Push(page) = act
&& let Some(text) = pages::horizon_page_tooltip(*page)
&& let Some(text) = horizon_page_tooltip(*page)
{
tip = Some(tr(text));
}
@@ -1919,7 +1926,6 @@ fn tooltip_text(s: &Settings, wd: &Widget) -> Option<Text> {
}
match act {
Act::Cycle(opt) if !opt.available() => Some(tr("horizon.options.unavailable")),
Act::PageRow(page, row) => pages::tooltip(*page, *row),
Act::ToggleMod(i) => match s.mods.installed.get(*i) {
Some(installed) => {
let description = installed
@@ -2361,7 +2367,6 @@ impl OptionsUi {
Page::ModBrowser => "horizon.mods.browse.title",
Page::ModProject => "horizon.mods.browse.project",
Page::NativeMod(_) => "Mod Settings",
Page::Added(i) => pages::title(i),
Page::ShaderPacks => "horizon.pages.shader_packs",
Page::ShaderOptions => "horizon.pages.shader_options",
Page::PackOptions => "horizon.pack.options.title",
@@ -2642,7 +2647,47 @@ impl OptionsUi {
flush(&mut rows, &mut section);
rows
}
Page::Horizon => self.horizon_rows(s),
Page::Horizon => {
// Two to a row, as vanilla's OptionsList.addSmall lays out
// buttons; each opens one of Horizon's settings pages. Each page has
// one way in: graphics and shader packs are Video Settings' (the graphics
// screen), the controller is Controls', mods are the Options screen's.
let open =
|key: &str, page: Page| button(0.0, 0.0, 150.0, tr(key), Act::Push(page));
vec![
Row::Buttons(
open("horizon.pages.open.extras", Page::Extras),
Some(open("horizon.pages.open.pvp", Page::Pvp)),
),
Row::Buttons(
open(
"horizon.pages.open.split_setup",
if self.split_guest {
Page::SplitProfiles
} else {
Page::SplitSetup
},
),
Some(open("horizon.pages.open.studio", Page::Studio)),
),
// No AI hides the AI Agent page and everything AI.
Row::Buttons(
self.extra_button(s, crate::extras::Item::DiscordPresence),
Some(if s.extras.no_ai {
self.extra_button(s, crate::extras::Item::NoAi)
} else {
open("horizon.pages.open.agent", Page::Agent)
}),
),
]
.into_iter()
.chain(
(!s.extras.no_ai).then(|| {
Row::Buttons(self.extra_button(s, crate::extras::Item::NoAi), None)
}),
)
.collect()
}
Page::Agent => {
let mut rows = vec![Row::Buttons(
self.extra_button(s, crate::extras::Item::AgentControl),
@@ -2925,7 +2970,6 @@ impl OptionsUi {
Page::ModBrowser => self.mod_browser_rows(),
Page::ModProject => self.mod_project_rows(s),
Page::NativeMod(i) => self.native_rows(s, i),
Page::Added(i) => self.added_rows(i),
_ => {
let _ = s;
Vec::new()
@@ -4194,7 +4238,6 @@ impl OptionsUi {
}
Act::NativeKey(i, k) => self.wait_native_key(i, k),
Act::NativeButton(i, b) => events.push(Event::NativeButton(i, b)),
Act::PageRow(p, r) => events.extend(self.page_act(p, r, self.shift)),
Act::OpenModFolder => events.push(Event::OpenModFolder),
Act::ModProject(i) => {
if let Some(listing) = self.mod_browser.listings.get(i) {
@@ -4698,10 +4741,10 @@ impl OptionsUi {
let Some(name) = self.selected_key.take() else {
return Vec::new();
};
if name == NATIVE_KEY || name == pages::PAGE_KEY {
return self.bind_native_key(s, name, value);
if name == NATIVE_KEY {
return self.bind_native_key(s, value);
}
if !s.extras.set_key(name, value.clone()) {
if !s.extras.set_key(name, value.clone()) && !s.horizon.set_key(name, value.clone()) {
s.keys.set(name, &value);
}
vec![Event::Changed]
@@ -5083,7 +5126,6 @@ impl OptionsUi {
}
Act::NativeKey(i, k) => self.wait_native_key(i, k),
Act::NativeButton(i, b) => events.push(Event::NativeButton(i, b)),
Act::PageRow(p, r) => events.extend(self.page_act(p, r, shift)),
Act::OpenModFolder => events.push(Event::OpenModFolder),
Act::ModProject(i) => {
if let Some(listing) = self.mod_browser.listings.get(i) {
@@ -7254,7 +7296,7 @@ mod tests {
MouseButton::Back,
MouseButton::Forward,
] {
for name in [SORT_KEY, "key.jump"] {
for name in [SORT_KEY, crate::graphics::HDR_KEY, "key.jump"] {
let mut ui = OptionsUi::default();
let mut s = Settings::default();
ui.selected_key = Some(name);
@@ -7263,6 +7305,7 @@ mod tests {
let value = keybinds::mouse_name(button);
match name {
SORT_KEY => assert_eq!(s.extras.sort_key, value),
crate::graphics::HDR_KEY => assert_eq!(s.horizon.hdr_key, value),
_ => assert_eq!(s.keys.get(name), value),
}
assert_eq!(Extras::parse(&s.extras.serialize()), s.extras);
@@ -1,230 +1,8 @@
//! The pages crates add to the options screens: a native mod's settings page (its keys and
//! buttons), and the pages crates above the engine register (`horizon_hooks::pages`, docs/layers.md
//! "Platform registries") with their entries on Horizon Settings. Kept out of options_ui.rs, which
//! may not grow (tools/check_file_size.py).
//! buttons). Kept out of options_ui.rs, which may not grow (tools/check_file_size.py).
use super::*;
use horizon_hooks::pages as added;
/// `selected_key` while a registered page's key waits (`selected_native_key`: page, row).
pub(super) const PAGE_KEY: &str = "horizon.pageKey";
#[cfg(test)]
thread_local! {
/// A test's own pages, instead of the game's registered ones: each test runs on its thread.
static TEST_PAGES: std::cell::Cell<Option<&'static [&'static dyn added::Page]>> =
const { std::cell::Cell::new(None) };
}
/// The pages crates registered.
fn registered() -> &'static [&'static dyn added::Page] {
#[cfg(test)]
if let Some(pages) = TEST_PAGES.get() {
return pages;
}
added::engine::all()
}
/// A registered page's title (a translation key).
pub(super) fn title(page: usize) -> &'static str {
registered().get(page).map_or("", |p| p.title())
}
/// Row `r` of a registered page, as it is now.
fn row(page: usize, r: usize) -> Option<added::Row> {
let mut rows = Vec::new();
registered().get(page)?.rows(&mut rows);
rows.into_iter().nth(r)
}
/// A registered page's slider ([`Opt::Page`]): its caption (a translation key).
pub(super) fn slider_caption(page: usize, r: usize) -> &'static str {
match row(page, r) {
Some(added::Row::Slider { label, .. }) => label,
_ => "",
}
}
/// A registered page's slider: its position.
pub(super) fn slider_kind(page: usize, r: usize) -> Kind {
match row(page, r) {
Some(added::Row::Slider { value, .. }) => Kind::Slider(value.clamp(0.0, 1.0)),
_ => Kind::Cycle,
}
}
/// A registered page's slider: the value its message shows after the caption.
pub(super) fn slider_text(page: usize, r: usize) -> Text {
match row(page, r) {
Some(added::Row::Slider { text, .. }) => label_text(text),
_ => Text::empty(),
}
}
/// A registered page's slider moved.
pub(super) fn slide(page: usize, r: usize, value: f32) {
if let Some(registered) = registered().get(page) {
registered.act(r, added::Act::Slide(value));
}
}
/// What a registered page's row says on hover.
pub(super) fn tooltip(page: usize, r: usize) -> Option<Text> {
registered().get(page)?.tooltip(r).map(label_text)
}
/// What a button opening one of Horizon's pages says on hover.
pub(super) fn horizon_page_tooltip(page: Page) -> Option<&'static str> {
Some(match page {
Page::Horizon => "horizon.pages.settings.tooltip",
Page::Studio => "horizon.pages.studio.tooltip",
Page::Graphics => "horizon.pages.graphics.tooltip",
Page::GraphicsAdvanced => "horizon.pages.graphics_advanced.tooltip",
Page::Controller => "horizon.pages.controller.tooltip",
Page::Extras => "horizon.pages.extras.tooltip",
Page::Pvp => "horizon.pages.pvp.tooltip",
Page::Agent => "horizon.pages.agent.tooltip",
Page::SplitProfiles => "horizon.pages.split_profiles.tooltip",
Page::ShaderPacks => "horizon.pages.shader_packs.tooltip",
Page::Mods => "horizon.pages.mods.tooltip",
Page::SplitSetup => "horizon.pages.split_setup.tooltip",
Page::Music => "horizon.pages.music.tooltip",
_ => return None,
})
}
fn label_text(label: added::Label) -> Text {
match label {
added::Label::Key(key) => tr(key),
added::Label::Value(value) => Text::literal(value),
}
}
impl OptionsUi {
/// Horizon Settings: its pages, then the pages crates registered for it.
pub(super) fn horizon_rows(&self, s: &Settings) -> Vec<Row> {
// Two to a row, as vanilla's OptionsList.addSmall lays out
// buttons; each opens one of Horizon's settings pages. Each page has
// one way in: graphics and shader packs are Video Settings' (the graphics
// screen), the controller is Controls', mods are the Options screen's.
let open = |key: &str, page: Page| button(0.0, 0.0, 150.0, tr(key), Act::Push(page));
let mut rows: Vec<Row> = vec![
Row::Buttons(
open("horizon.pages.open.extras", Page::Extras),
Some(open("horizon.pages.open.pvp", Page::Pvp)),
),
Row::Buttons(
open(
"horizon.pages.open.split_setup",
if self.split_guest {
Page::SplitProfiles
} else {
Page::SplitSetup
},
),
Some(open("horizon.pages.open.studio", Page::Studio)),
),
// No AI hides the AI Agent page and everything AI.
Row::Buttons(
self.extra_button(s, crate::extras::Item::DiscordPresence),
Some(if s.extras.no_ai {
self.extra_button(s, crate::extras::Item::NoAi)
} else {
open("horizon.pages.open.agent", Page::Agent)
}),
),
]
.into_iter()
.chain(
(!s.extras.no_ai)
.then(|| Row::Buttons(self.extra_button(s, crate::extras::Item::NoAi), None)),
)
.collect();
self.added_entries(&mut rows);
rows
}
/// The entries of the pages registered for Horizon Settings, two to a row like the engine's.
fn added_entries(&self, rows: &mut Vec<Row>) {
let mut entries = registered()
.iter()
.enumerate()
.filter(|(_, page)| page.place() == added::Place::HorizonSettings)
.map(|(i, page)| button(0.0, 0.0, 150.0, tr(page.title()), Act::Push(Page::Added(i))));
while let Some(first) = entries.next() {
rows.push(Row::Buttons(first, entries.next()));
}
}
/// A registered page's rows, asked of it each time the page is drawn.
pub(super) fn added_rows(&self, page: usize) -> Vec<Row> {
let Some(registered) = registered().get(page) else {
return Vec::new();
};
let mut rows = Vec::new();
registered.rows(&mut rows);
rows.into_iter()
.enumerate()
.map(|(r, row)| self.added_row(page, r, row))
.collect()
}
fn added_row(&self, page: usize, r: usize, row: added::Row) -> Row {
let act = Act::PageRow(page, r);
let wide = |label: Text| Row::Buttons(button(0.0, 0.0, 310.0, label, act.clone()), None);
match row {
added::Row::Heading(key) => Row::Heading(key.to_owned()),
added::Row::Note(text) => Row::Note(text),
added::Row::Toggle { label, on } => wide(generic(label, on_off(on))),
added::Row::Cycle { label, value } => wide(generic(label, label_text(value))),
added::Row::Slider { .. } => Row::Big(Opt::Page(page, r)),
added::Row::Button { label } => wide(tr(label)),
added::Row::Key { label, key } => {
let mut message = keybinds::display_name(&key);
if self.selected_key == Some(PAGE_KEY)
&& self.selected_native_key == Some((page, r))
{
message = Text::literal("> ")
.append(message.with_color(text::WHITE))
.append(" <")
.with_color(text::YELLOW);
}
let caption = button(0.0, 0.0, 150.0, tr(label), Act::None);
let change = button(0.0, 0.0, 150.0, message, act);
Row::Buttons(inactive(caption), Some(change))
}
// A row kind newer than this engine: shown empty.
_ => Row::Note(String::new()),
}
}
/// A registered page's row was pressed: a key row waits for a key, any other row hears it.
pub(super) fn page_act(&mut self, page: usize, r: usize, shift: bool) -> Vec<Event> {
let Some(registered) = registered().get(page) else {
return Vec::new();
};
let mut rows = Vec::new();
registered.rows(&mut rows);
match rows.get(r) {
Some(added::Row::Key { .. }) => {
self.selected_key = Some(PAGE_KEY);
self.selected_native_key = Some((page, r));
Vec::new()
}
Some(_) => {
registered.act(
r,
if shift {
added::Act::PressBack
} else {
added::Act::Press
},
);
vec![Event::Changed]
}
None => Vec::new(),
}
}
/// A native mod's page: its name, its keys as Key Binds rows (the
/// player's choice, else the default), then its buttons.
pub(super) fn native_rows(&self, s: &Settings, i: usize) -> Vec<Row> {
@@ -268,21 +46,9 @@ impl OptionsUi {
self.selected_native_key = Some((i, k));
}
/// The key pressed for the native mod's key, or the registered page's ([`PAGE_KEY`]), that
/// waited for one.
pub(super) fn bind_native_key(
&mut self,
s: &mut Settings,
name: &str,
value: String,
) -> Vec<Event> {
/// The key pressed for the native mod's key that waited for one.
pub(super) fn bind_native_key(&mut self, s: &mut Settings, value: String) -> Vec<Event> {
let (i, k) = self.selected_native_key.take().unwrap_or_default();
if name == PAGE_KEY {
if let Some(page) = registered().get(i) {
page.act(k, added::Act::Bind(value));
}
return vec![Event::Changed];
}
let Some(m) = s.mods.installed.get(i) else {
return Vec::new();
};
@@ -294,171 +60,3 @@ impl OptionsUi {
vec![Event::Changed]
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
/// A page with a heading, a switch, a number that steps either way, a key and a volume.
struct Sample {
on: Mutex<bool>,
step: Mutex<i32>,
key: Mutex<String>,
volume: Mutex<f32>,
}
impl added::Page for Sample {
fn id(&self) -> &'static str {
"test.sample"
}
fn title(&self) -> &'static str {
"test.sample.title"
}
fn place(&self) -> added::Place {
added::Place::HorizonSettings
}
fn rows(&self, rows: &mut Vec<added::Row>) {
rows.push(added::Row::Heading("test.sample.heading"));
rows.push(added::Row::Toggle {
label: "test.sample.switch",
on: *self.on.lock().unwrap(),
});
rows.push(added::Row::Cycle {
label: "test.sample.step",
value: added::Label::Value(self.step.lock().unwrap().to_string()),
});
rows.push(added::Row::Key {
label: "test.sample.key",
key: self.key.lock().unwrap().clone(),
});
let volume = *self.volume.lock().unwrap();
rows.push(added::Row::Slider {
label: "test.sample.volume",
value: volume,
text: added::Label::Value(format!("{}%", (volume * 100.0).round())),
});
}
fn act(&self, row: usize, act: added::Act) {
match (row, act) {
(1, added::Act::Press) => {
let mut on = self.on.lock().unwrap();
*on = !*on;
}
(2, added::Act::Press) => *self.step.lock().unwrap() += 1,
(2, added::Act::PressBack) => *self.step.lock().unwrap() -= 1,
(3, added::Act::Bind(key)) => *self.key.lock().unwrap() = key,
(4, added::Act::Slide(v)) => *self.volume.lock().unwrap() = v,
other => panic!("row and act the page doesn't expect: {other:?}"),
}
}
fn tooltip(&self, row: usize) -> Option<added::Label> {
(row == 1).then_some(added::Label::Key("test.sample.switch.tooltip"))
}
}
static SAMPLE: Sample = Sample {
on: Mutex::new(false),
step: Mutex::new(0),
key: Mutex::new(String::new()),
volume: Mutex::new(0.0),
};
static PAGES: [&dyn added::Page; 1] = [&SAMPLE];
/// Clicks the active button that does `act`, with Shift held or not.
fn press(ui: &mut OptionsUi, s: &mut Settings, act: Act, shift: bool) -> Vec<Event> {
let size = (480.0, 240.0);
let (widgets, ..) = ui.layout(s, size.0, size.1);
let (x, y) = widgets
.iter()
.find_map(|w| match &w.kind {
WKind::Button {
act: a,
active: true,
..
} if *a == act => Some((w.x + 1.0, w.y + 1.0)),
_ => None,
})
.unwrap_or_else(|| panic!("no button does {act:?}"));
ui.shift = shift;
let atlas = super::super::tests::atlas();
ui.click(s, &atlas, x, y, size, [480, 240], false, false)
}
#[test]
fn a_registered_page_opens_from_horizon_settings_and_hears_its_rows() {
let mut s = Settings::default();
TEST_PAGES.set(Some(&PAGES));
let mut ui = OptionsUi::default();
ui.show(Page::Horizon);
press(&mut ui, &mut s, Act::Push(Page::Added(0)), false);
assert_eq!(ui.page(), Page::Added(0));
// Rows: the heading (0), the switch (1), the step (2), the key (3).
let events = press(&mut ui, &mut s, Act::PageRow(0, 1), false);
assert_eq!(
events.last(),
Some(&Event::Changed),
"the settings get saved"
);
assert!(*SAMPLE.on.lock().unwrap());
press(&mut ui, &mut s, Act::PageRow(0, 2), false);
press(&mut ui, &mut s, Act::PageRow(0, 2), true);
press(&mut ui, &mut s, Act::PageRow(0, 2), true);
assert_eq!(*SAMPLE.step.lock().unwrap(), -1, "Shift steps back");
// A key row waits for the next key, as on Key Binds, and the page gets its name.
press(&mut ui, &mut s, Act::PageRow(0, 3), false);
let atlas = super::super::tests::atlas();
let events = ui.key(&mut s, &atlas, winit::keyboard::KeyCode::KeyZ, None);
assert_eq!(events, [Event::Changed]);
assert_eq!(*SAMPLE.key.lock().unwrap(), "key.keyboard.z");
assert_eq!(ui.selected_key, None);
// The volume is one of the screen's own sliders: dragged to its middle, then let go.
let size = (480.0, 240.0);
let (widgets, ..) = ui.layout(&s, size.0, size.1);
let slider = widgets
.iter()
.find(|w| {
matches!(
w.kind,
WKind::Slider {
opt: Opt::Page(0, 4),
..
}
)
})
.expect("the volume's slider");
let (x, y) = (slider.x + slider.w / 2.0, slider.y + 1.0);
ui.click(&mut s, &atlas, x, y, size, [480, 240], false, false);
let volume = *SAMPLE.volume.lock().unwrap();
assert!((0.45..0.55).contains(&volume), "{volume}");
assert!(ui.release().contains(&Event::Changed));
// The slider shows the page's value once let go.
let (widgets, ..) = ui.layout(&s, size.0, size.1);
let shown = widgets.iter().find_map(|w| match w.kind {
WKind::Slider {
opt: Opt::Page(0, 4),
value,
..
} => Some(value),
_ => None,
});
assert_eq!(shown, Some(volume));
// The page's tooltip for its switch.
let switch = widgets
.iter()
.find(|w| {
matches!(
&w.kind,
WKind::Button {
act: Act::PageRow(0, 1),
..
}
)
})
.expect("the switch");
assert_eq!(
tooltip_text(&s, switch).map(|t| t.plain()),
Some(tr("test.sample.switch.tooltip").plain())
);
}
}
@@ -1,67 +0,0 @@
//! Polytone's custom colours (`horizon_polytone::colors`, `colors.json` or `color.properties`)
//! as the engine's pack colours ([`crate::pack_palette::PackColors`]).
use std::path::Path;
use horizon_polytone::colors::PackColors;
use crate::block_entity_render::DYES;
use crate::pack_palette::{self, Environment};
/// The colours of every namespace's file under the pack overlay at `root`.
pub fn read(root: &Path) -> pack_palette::PackColors {
let colors = PackColors::read(root);
let by_dye = |f: &dyn Fn(&str) -> Option<u32>| std::array::from_fn(|i| f(DYES[i].0));
let env = colors.env;
pack_palette::PackColors {
dye_diffuse: by_dye(&|name| colors.dye(name, "diffuse")),
dye_text: by_dye(&|name| colors.dye(name, "text")),
sheep: by_dye(&|name| colors.sheep(name)),
redstone: std::array::from_fn(|i| colors.redstone(i as u8)),
effects: colors.effects(),
entity_shadows: colors.entity_shadows(),
xp_bar: colors.xp_bar(),
env: Environment {
powder_snow_fog: env.powder_snow_fog,
blindness_fog_distance: env.blindness_fog_distance,
darkness_fog_distance: env.darkness_fog_distance,
boss_fog_distance: env.boss_fog_distance,
water_fog_brightening: env.water_fog_brightening,
water_fog_brightening_time: env.water_fog_brightening_time,
horizon_height: env.horizon_height,
},
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pack_palette::{diffuse, redstone, sheep_wool, text};
#[test]
fn a_pack_recolours_only_what_it_names() {
// Dyes no other test draws: the table is process-wide.
let dir = std::env::temp_dir().join(format!("pack-dyes-{}", std::process::id()));
let polytone = dir.join("assets/obs/polytone");
std::fs::create_dir_all(&polytone).unwrap();
std::fs::write(
polytone.join("colors.json"),
r#"{"dye": {"cyan": {"diffuse": "00ff00", "text": "0000ff"}}, "sheep": {"pink": "2040ff"}, "redstone": {"7": "0x123456"}}"#,
)
.unwrap();
crate::pack_palette::set(&read(&dir));
let (cyan, pink, purple) = (9, 6, 10);
assert_eq!(diffuse(cyan), 0x00ff00);
assert_eq!(text(cyan), 0x0000ff);
assert_eq!(sheep_wool(pink as u8), 0x2040ff);
// Wool reads vanilla's dye, not the pack's diffuse: 75% of 0x169C9C.
assert_eq!(sheep_wool(cyan as u8), 0x107575);
assert_eq!(diffuse(purple), 0x8932B8);
assert_eq!(text(purple), 0xA020F0);
assert_eq!((redstone(7), redstone(15)), (Some(0x123456), None));
crate::pack_palette::set(&Default::default());
assert_eq!(redstone(7), None);
assert_eq!(diffuse(cyan), 0x169C9C);
assert_eq!(sheep_wool(pink as u8), 0xB6687F);
std::fs::remove_dir_all(&dir).ok();
}
}
@@ -1,252 +0,0 @@
//! OptiFine custom skies (`horizon_optifine::sky`) on the engine's sky layers
//! ([`crate::custom_sky`]): a pack's layers with their decoded textures, read once with the pack,
//! and each frame's choice of layers, their brightness and rotation. A pack without
//! `optifine/sky` has none, and the sky stays vanilla.
use std::path::Path;
use glam::{Mat4, Vec3};
use horizon_optifine::sky::{Blend, Conditions, Now, Skies, SkyLayer, Transition};
use crate::custom_sky::{self as engine, Draw, Image, SkyLayers, State};
/// The pack's layers, by world, with one texture per layer (`images[i]` for the `i`th layer
/// over all worlds, in `worlds` order).
#[derive(Default)]
pub struct CustomSky {
worlds: Vec<(i32, Vec<SkyLayer>)>,
pub images: Vec<Image>,
}
/// Textures past this many texels in all are left out (a skybox is a few megapixels at most).
const TEXEL_BUDGET: u64 = 64 << 20;
impl CustomSky {
/// The custom skies of the overlay at `root`. A layer whose texture does not decode is
/// dropped with a warning.
pub fn load(root: &Path) -> Self {
let skies = Skies::scan(root);
let mut out = Self::default();
let mut texels = 0u64;
for (world, sky) in skies.worlds {
let mut layers = Vec::new();
for layer in sky.layers {
match crate::hud::decode(&root.join(&layer.source)) {
Ok((width, height, rgba))
if texels + u64::from(width) * u64::from(height) <= TEXEL_BUDGET =>
{
texels += u64::from(width) * u64::from(height);
out.images.push(Image {
width,
height,
rgba,
});
layers.push(layer);
}
Ok(_) => log::warn!("{}: past the custom sky's texture budget", layer.source),
Err(e) => log::warn!("{}: {e:#}", layer.source),
}
}
if !layers.is_empty() {
out.worlds.push((world, layers));
}
}
out
}
pub fn is_empty(&self) -> bool {
self.worlds.is_empty()
}
}
/// The engine's sky blend for OptiFine's.
fn blend(blend: Blend) -> engine::Blend {
match blend {
Blend::Add => engine::Blend::Add,
Blend::Subtract => engine::Blend::Subtract,
Blend::Multiply => engine::Blend::Multiply,
Blend::Dodge => engine::Blend::Dodge,
Blend::Burn => engine::Blend::Burn,
Blend::Screen => engine::Blend::Screen,
Blend::Replace => engine::Blend::Replace,
Blend::Overlay => engine::Blend::Overlay,
Blend::Alpha => engine::Blend::Alpha,
}
}
impl SkyLayers for CustomSky {
fn images(&self) -> &[Image] {
&self.images
}
/// Whether `world` has layers (`0` the Overworld, `1` the End).
fn has(&self, world: i32) -> bool {
self.worlds.iter().any(|(w, _)| *w == world)
}
/// The layers whose conditions hold, eased over their `transition`, at the brightness of
/// their fade times.
fn draws(
&self,
world: i32,
conditions: &engine::Conditions,
sun_turns: f32,
seconds: f32,
state: &mut State,
out: &mut Vec<Draw>,
) {
let conditions = &Conditions {
day_time: conditions.day_time,
weather: match conditions.weather {
engine::Weather::Clear => Now::Clear,
engine::Weather::Rain => Now::Rain,
engine::Weather::Thunder => Now::Thunder,
},
biome: conditions.biome,
y: conditions.y,
};
out.clear();
let mut first = 0;
let mut found = None;
for (w, layers) in &self.worlds {
if *w == world {
found = Some(layers);
break;
}
first += layers.len();
}
let Some(layers) = found else {
state.world = None;
return;
};
// A new world (or pack) starts each layer where its conditions put it: no fade in.
let snap = state.world != Some(world) || state.eased.len() != layers.len();
if snap {
state.world = Some(world);
state.eased.clear();
state.eased.resize(layers.len(), 0.0);
}
for (i, (layer, eased)) in layers.iter().zip(&mut state.eased).enumerate() {
let target = if layer.shows(conditions) { 1.0 } else { 0.0 };
let shown = if snap {
*eased = target;
target
} else {
let mut t = Transition { value: *eased };
*eased = t.step(target, seconds, layer.transition);
*eased
};
let brightness = shown * layer.brightness(conditions.day_time);
if brightness <= 0.0 {
continue;
}
let turns = layer.turns(conditions.day_time, sun_turns);
let rotation = if turns == 0.0 {
Mat4::IDENTITY
} else {
Mat4::from_axis_angle(Vec3::from_array(layer.axis), turns * std::f32::consts::TAU)
};
out.push(Draw {
image: (first + i) as u16,
blend: blend(layer.blend),
brightness,
rotation,
});
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::custom_sky::{Conditions, Weather};
fn pack(files: &[(&str, &str)]) -> (std::path::PathBuf, CustomSky) {
let root = std::env::temp_dir().join(format!(
"horizon-custom-sky-{}-{}",
std::process::id(),
files.len()
));
let _ = std::fs::remove_dir_all(&root);
let dir = root.join("assets/minecraft/optifine/sky/world0");
std::fs::create_dir_all(&dir).unwrap();
let png = {
let mut bytes = Vec::new();
let mut encoder = png::Encoder::new(&mut bytes, 6, 4);
encoder.set_color(png::ColorType::Rgba);
encoder.set_depth(png::BitDepth::Eight);
let mut writer = encoder.write_header().unwrap();
writer
.write_image_data(&[255, 0, 0, 255].repeat(24))
.unwrap();
writer.finish().unwrap();
bytes
};
for (name, text) in files {
std::fs::write(dir.join(format!("{name}.properties")), text).unwrap();
std::fs::write(dir.join(format!("{name}.png")), &png).unwrap();
}
let sky = CustomSky::load(&root);
(root, sky)
}
fn noon(biome: &str) -> Conditions<'_> {
Conditions {
day_time: 6000,
weather: Weather::Clear,
biome,
y: 64,
}
}
#[test]
fn layers_ease_in_at_a_biome_border_but_not_on_arrival() {
let (root, sky) = pack(&[
("sky1", "biomes=desert\ntransition=2"),
("sky2", "blend=alpha\nrotate=false"),
]);
let _ = std::fs::remove_dir_all(root);
assert!(sky.has(0) && !sky.has(1));
assert_eq!(sky.images[0].width, 6);
let (mut state, mut out) = (State::default(), Vec::new());
sky.draws(
0,
&noon("minecraft:desert"),
0.0,
0.05,
&mut state,
&mut out,
);
assert_eq!(
out.iter().map(|d| d.brightness).collect::<Vec<_>>(),
[1.0, 1.0]
);
// Out of the desert: the first layer fades over its two seconds.
sky.draws(0, &noon("minecraft:plains"), 0.0, 0.5, &mut state, &mut out);
assert_eq!(out[0].brightness, 0.75);
assert_eq!(out[1].blend, engine::Blend::Alpha);
sky.draws(0, &noon("minecraft:plains"), 0.0, 2.0, &mut state, &mut out);
assert_eq!(out.len(), 1, "a dark layer is not drawn");
assert_eq!(out[0].image, 1);
// Into the End, which has no layers, and back: no fade.
sky.draws(1, &noon("minecraft:desert"), 0.0, 0.0, &mut state, &mut out);
assert!(out.is_empty());
sky.draws(0, &noon("minecraft:desert"), 0.0, 0.0, &mut state, &mut out);
assert_eq!(out.len(), 2);
}
#[test]
fn a_layer_turns_west_like_the_sun() {
let (root, sky) = pack(&[("sky1", "")]);
let _ = std::fs::remove_dir_all(root);
let (mut state, mut out) = (State::default(), Vec::new());
// A quarter turn after noon (sunset): the skybox's zenith has gone down to the west.
let c = Conditions {
day_time: 12000,
..noon("minecraft:plains")
};
sky.draws(0, &c, 0.25, 0.0, &mut state, &mut out);
let up = out[0].rotation.transform_vector3(Vec3::Y);
assert!((up - Vec3::NEG_X).length() < 1e-5, "{up}");
}
}
@@ -1,146 +0,0 @@
//! Polytone's item modifiers' durability bar colours, on the engine's
//! [`crate::pack_palette::ItemBars`].
use std::path::Path;
use std::sync::Arc;
/// An item modifier's `bar_color` of the `damage` type: a row of colours picked by the stack's
/// damage, the first pixel undamaged, the last at full damage (measured on the real mod).
struct DamageBar {
targets: horizon_polytone::Targets,
row: Vec<u32>,
}
/// The packs' damage bars, the last read first.
struct Bars(Vec<DamageBar>);
impl crate::pack_palette::ItemBars for Bars {
fn color(&self, item: &str, damage: f32) -> Option<u32> {
let bar = self
.0
.iter()
.rev()
.find(|b| b.targets.matches(item, |_| false))?;
let last = (bar.row.len() - 1) as f32;
Some(bar.row[(damage.clamp(0.0, 1.0) * last).round() as usize])
}
}
/// Reads the packs' item modifiers' durability bar colours under a pack overlay root: an
/// `<item>_bar.png` alone, or a json whose `bar_color` is `damage` with its `<name>_bar.png` (or
/// `<name>.png`) beside it. Colormap ids and inline colormaps aren't read. `None` without any.
pub fn read(root: &Path) -> Option<Arc<dyn crate::pack_palette::ItemBars>> {
let mut bars = Vec::new();
for namespace in std::fs::read_dir(root.join("assets"))
.into_iter()
.flatten()
.flatten()
{
let base = namespace.path().join("polytone/item_modifiers");
let mut stack = vec![base.clone()];
while let Some(dir) = stack.pop() {
for entry in std::fs::read_dir(&dir).into_iter().flatten().flatten() {
let path = entry.path();
if path.is_dir() {
stack.push(path);
} else if let Some(bar) = item_bar(root, &base, &path) {
bars.push(bar);
}
}
}
}
(!bars.is_empty()).then(|| Arc::new(Bars(bars)) as _)
}
fn item_bar(root: &Path, base: &Path, path: &Path) -> Option<DamageBar> {
let name = path.file_name()?.to_str()?;
let row = |png: &Path| {
let (width, _, rgba) = crate::sprites::read_rgba(png).ok()?;
let row: Vec<u32> = rgba[..width as usize * 4]
.as_chunks::<4>()
.0
.iter()
.map(|p| u32::from(p[0]) << 16 | u32::from(p[1]) << 8 | u32::from(p[2]))
.collect();
(!row.is_empty()).then_some(row)
};
if let Some(stem) = name.strip_suffix("_bar.png") {
// A json of the same name owns its png.
if path.with_file_name(format!("{stem}.json")).exists() {
return None;
}
let relative = path.parent()?.strip_prefix(base).ok()?.join(stem);
let namespace = base.parent()?.parent()?.file_name()?.to_str()?;
let id = format!("{namespace}:{}", relative.to_str()?.replace('\\', "/"));
let targets = horizon_polytone::Targets::parse(None, Some(&id)).ok()?;
return Some(DamageBar {
targets,
row: row(path)?,
});
}
let stem = name.strip_suffix(".json")?;
let json: serde_json::Value = horizon_world::json_file::read(path).ok()?;
if json.get("bar_color")?.as_str()? != "damage" {
return None;
}
let png = [format!("{stem}_bar.png"), format!("{stem}.png")]
.into_iter()
.map(|png| path.with_file_name(png))
.find(|png| png.exists())?;
let relative = path.strip_prefix(root).unwrap_or(path).to_string_lossy();
let implicit = horizon_polytone::implicit_id(&relative, "item_modifiers");
let targets =
horizon_polytone::Targets::parse(json.get("targets"), implicit.as_deref()).ok()?;
Some(DamageBar {
targets,
row: row(&png)?,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn png_row(path: &Path, pixels: &[[u8; 3]]) {
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
let file = std::fs::File::create(path).unwrap();
let mut encoder = png::Encoder::new(file, pixels.len() as u32, 1);
encoder.set_color(png::ColorType::Rgba);
let data: Vec<u8> = pixels
.iter()
.flat_map(|p| [p[0], p[1], p[2], 255])
.collect();
encoder
.write_header()
.unwrap()
.write_image_data(&data)
.unwrap();
}
#[test]
fn a_damage_bar_picks_its_pixel_by_the_stacks_damage() {
let dir = std::env::temp_dir().join(format!("pack-bars-{}", std::process::id()));
let modifiers = dir.join("assets/minecraft/polytone/item_modifiers");
let rgby = [[255, 0, 0], [0, 255, 0], [0, 0, 255], [255, 255, 0]];
png_row(&modifiers.join("diamond_pickaxe_bar.png"), &rgby);
std::fs::write(
modifiers.join("tools.json"),
r#"{"targets": ["minecraft:iron_axe"], "bar_color": "damage"}"#,
)
.unwrap();
png_row(&modifiers.join("tools_bar.png"), &[[1, 2, 3], [4, 5, 6]]);
let bars = read(&dir).unwrap();
// The real mod: damage 200 of 1561 shows the first pixel, 1400 the last.
assert_eq!(
bars.color("minecraft:diamond_pickaxe", 200.0 / 1561.0),
Some(0xFF0000)
);
assert_eq!(
bars.color("minecraft:diamond_pickaxe", 1400.0 / 1561.0),
Some(0xFFFF00)
);
assert_eq!(bars.color("minecraft:iron_axe", 0.9), Some(0x040506));
assert_eq!(bars.color("minecraft:diamond_shovel", 0.5), None);
assert!(read(&dir.join("none")).is_none());
std::fs::remove_dir_all(&dir).ok();
}
}
@@ -1,36 +0,0 @@
//! The resource pack formats vanilla doesn't read (OptiFine's, Polytone's) on the engine's
//! extension points ([`crate::pack_hooks`]). This is platform code (docs/layers.md) kept in this
//! crate until the platform's executable installs it: it uses only the crate's public API, so it
//! moves to a platform crate unchanged, and nothing else in the crate names it.
mod colors;
mod custom_sky;
mod item_bars;
use std::path::Path;
use std::sync::Arc;
use crate::pack_hooks::PackFormats;
/// OptiFine's and Polytone's formats.
pub struct Formats;
impl PackFormats for Formats {
fn colors(&self, root: &Path) -> Option<crate::pack_palette::PackColors> {
Some(colors::read(root))
}
fn custom_sky(&self, root: &Path) -> Option<Arc<dyn crate::custom_sky::SkyLayers>> {
let sky = custom_sky::CustomSky::load(root);
(!sky.is_empty()).then(|| Arc::new(sky) as _)
}
fn item_bars(&self, root: &Path) -> Option<Arc<dyn crate::pack_palette::ItemBars>> {
item_bars::read(root)
}
}
/// Installs these formats ([`crate::pack_hooks::install`]); the game does at startup.
pub fn install() {
crate::pack_hooks::install(&Formats);
}
@@ -1,45 +0,0 @@
//! The engine's extension points for resource pack formats vanilla doesn't read (OptiFine's,
//! Polytone's: platform code, docs/layers.md). The engine asks [`formats`] once per pack load and
//! gets engine types back (tables, or an object it calls at most once a frame), never a format's
//! own types. The platform installs its formats at startup ([`install`]); with none installed,
//! packs load as vanilla reads them.
use std::path::Path;
use std::sync::{Arc, OnceLock};
/// What the pack formats beyond vanilla's add to a pack. Each method reads one overlay of a pack
/// (its folder at `root`); the default is "nothing in this format".
pub trait PackFormats: Send + Sync {
/// The colours the overlay replaces where vanilla hardcodes them.
fn colors(&self, _root: &Path) -> Option<crate::pack_palette::PackColors> {
None
}
/// The sky layers the overlay draws over the vanilla sky, if it has any.
fn custom_sky(&self, _root: &Path) -> Option<Arc<dyn crate::custom_sky::SkyLayers>> {
None
}
/// The durability bar colours the overlay sets by item, if any.
fn item_bars(&self, _root: &Path) -> Option<Arc<dyn crate::pack_palette::ItemBars>> {
None
}
}
/// No format beyond vanilla's.
struct Vanilla;
impl PackFormats for Vanilla {}
static FORMATS: OnceLock<&'static dyn PackFormats> = OnceLock::new();
/// Installs the pack formats, before the first pack loads. The first call wins: later ones are
/// ignored, so each test or probe that needs the formats may install them.
pub fn install(formats: &'static dyn PackFormats) {
let _ = FORMATS.set(formats);
}
/// The installed pack formats, or vanilla's alone.
pub fn formats() -> &'static dyn PackFormats {
FORMATS.get().copied().unwrap_or(&Vanilla)
}
+184 -113
View File
@@ -1,87 +1,14 @@
//! The colours a pack replaces where vanilla hardcodes them (Polytone's `colors.json`, read by the
//! platform's pack formats, [`crate::pack_hooks`]): dye, sheep and redstone colours, for drawing
//! only (the pack recolours collars, tropical fish, banners, wool, sign text and redstone wire and
//! dust, while gameplay keeps vanilla's `DyeColor`), mob effect colours, the experience number's,
//! entity shadows, the effect fogs' settings and durability bar colours. Lock-free: the draw paths
//! and the mesher's workers read the dye, sheep and redstone tables per face.
//! Polytone's dye, sheep and redstone colours (`dye.<name>.diffuse|text`, `sheep.<name>`,
//! `redstone.<power>`), for drawing only: the pack recolours collars, tropical fish, banners, wool,
//! sign text and redstone wire and dust, while gameplay (crafting, item data, the beacon) keeps
//! vanilla's `DyeColor`. Lock-free: the draw paths and the mesher's workers read it per face.
use std::path::Path;
use std::sync::RwLock;
use std::sync::atomic::{AtomicU64, Ordering::Relaxed};
use std::sync::{Arc, RwLock};
use crate::block_entity_render::DYES;
use crate::signs::DYE_TEXT;
/// The fog settings vanilla hardcodes, as the packs set them (vanilla's by default).
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Environment {
/// The fog colour inside powder snow (RGB).
pub powder_snow_fog: Option<u32>,
/// The fog ends of Blindness (5), Darkness (15) and bosses that add world fog (96).
pub blindness_fog_distance: f32,
pub darkness_fog_distance: f32,
pub boss_fog_distance: f32,
/// How much the underwater fog colour washes out (1 vanilla, 0 none).
pub water_fog_brightening: f32,
/// The ticks under water it takes (vanilla's 600).
pub water_fog_brightening_time: f32,
/// Below it the sky draws its dark disc (vanilla's 63; flat worlds keep their floor).
pub horizon_height: f32,
}
impl Default for Environment {
fn default() -> Self {
Self {
powder_snow_fog: None,
blindness_fog_distance: 5.0,
darkness_fog_distance: 15.0,
boss_fog_distance: 96.0,
water_fog_brightening: 1.0,
water_fog_brightening_time: 600.0,
horizon_height: 63.0,
}
}
}
/// The packs' colours; `None` (and the defaults) where they keep vanilla's.
#[derive(Clone, Debug, PartialEq)]
pub struct PackColors {
/// By dye id (`DyeColor` order): `getTextureDiffuseColor`, `getTextColor`, the sheep's wool.
pub dye_diffuse: [Option<u32>; 16],
pub dye_text: [Option<u32>; 16],
pub sheep: [Option<u32>; 16],
/// `RedStoneWireBlock.COLORS` by power.
pub redstone: [Option<u32>; 16],
/// Mob effects' colours, by effect id without vanilla's namespace.
pub effects: Vec<(String, u32)>,
/// False hides every entity's round shadow, whatever the video option says.
pub entity_shadows: bool,
/// The experience level number's colour and outline.
pub xp_bar: [Option<u32>; 2],
/// The effect fogs' settings: the fog reads them every frame.
pub env: Environment,
}
impl Default for PackColors {
fn default() -> Self {
Self {
dye_diffuse: [None; 16],
dye_text: [None; 16],
sheep: [None; 16],
redstone: [None; 16],
effects: Vec::new(),
entity_shadows: true,
xp_bar: [None; 2],
env: Environment::default(),
}
}
}
impl PackColors {
/// Whether entities draw their round shadow, as far as the packs say.
pub fn entity_shadows(&self) -> bool {
self.entity_shadows
}
}
use horizon_polytone::colors::PackColors;
/// 0 is unset, else `1 << 32 | rgb`.
static DIFFUSE: [AtomicU64; 16] = [const { AtomicU64::new(0) }; 16];
@@ -89,30 +16,6 @@ static TEXT: [AtomicU64; 16] = [const { AtomicU64::new(0) }; 16];
static SHEEP: [AtomicU64; 16] = [const { AtomicU64::new(0) }; 16];
static REDSTONE: [AtomicU64; 16] = [const { AtomicU64::new(0) }; 16];
/// The colours of the pack overlay at `root`, from the installed pack formats; their dye, sheep
/// and redstone colours go to the draw paths' tables ([`set`]).
pub fn read(root: &Path) -> PackColors {
let formats = crate::pack_hooks::formats();
let colors = formats.colors(root).unwrap_or_default();
set(&colors);
*ITEM_BARS.write().unwrap_or_else(|e| e.into_inner()) = formats.item_bars(root);
colors
}
/// The packs' durability bar colours by item (Polytone's item modifiers' `bar_color`).
pub trait ItemBars: Send + Sync {
/// The bar colour (RGB) of `item` at this damage (0 to 1), when a pack sets one.
fn color(&self, item: &str, damage: f32) -> Option<u32>;
}
static ITEM_BARS: RwLock<Option<Arc<dyn ItemBars>>> = RwLock::new(None);
/// A pack's durability bar colour (RGB) for an item at this damage (0 to 1).
pub fn item_bar_color(item: &str, damage: f32) -> Option<u32> {
let bars = ITEM_BARS.read().unwrap_or_else(|e| e.into_inner());
bars.as_ref()?.color(item, damage)
}
/// Takes the colours of the packs just loaded (vanilla's where they set none).
pub fn set(colors: &PackColors) {
let store = |table: &[AtomicU64; 16], i: usize, color: Option<u32>| {
@@ -121,39 +24,129 @@ pub fn set(colors: &PackColors) {
Relaxed,
);
};
for i in 0..16 {
store(&DIFFUSE, i, colors.dye_diffuse[i]);
store(&TEXT, i, colors.dye_text[i]);
store(&SHEEP, i, colors.sheep[i]);
store(&REDSTONE, i, colors.redstone[i]);
for (i, (name, _)) in DYES.iter().enumerate() {
store(&DIFFUSE, i, colors.dye(name, "diffuse"));
store(&TEXT, i, colors.dye(name, "text"));
store(&SHEEP, i, colors.sheep(name));
store(&REDSTONE, i, colors.redstone(i as u8));
}
}
fn get(table: &[AtomicU64; 16], i: usize) -> Option<u32> {
/// An item modifier's `bar_color` of the `damage` type: a row of colours picked by the stack's
/// damage, the first pixel undamaged, the last at full damage (measured on the real mod).
struct DamageBar {
targets: horizon_polytone::Targets,
row: Vec<u32>,
}
static ITEM_BARS: RwLock<Vec<DamageBar>> = RwLock::new(Vec::new());
/// Reads the packs' item modifiers' durability bar colours under a pack overlay root: an
/// `<item>_bar.png` alone, or a json whose `bar_color` is `damage` with its `<name>_bar.png` (or
/// `<name>.png`) beside it. Colormap ids and inline colormaps aren't read.
pub fn set_item_bars(root: &Path) {
let mut bars = Vec::new();
for namespace in std::fs::read_dir(root.join("assets"))
.into_iter()
.flatten()
.flatten()
{
let base = namespace.path().join("polytone/item_modifiers");
let mut stack = vec![base.clone()];
while let Some(dir) = stack.pop() {
for entry in std::fs::read_dir(&dir).into_iter().flatten().flatten() {
let path = entry.path();
if path.is_dir() {
stack.push(path);
} else if let Some(bar) = item_bar(root, &base, &path) {
bars.push(bar);
}
}
}
}
*ITEM_BARS.write().unwrap_or_else(|e| e.into_inner()) = bars;
}
fn item_bar(root: &Path, base: &Path, path: &Path) -> Option<DamageBar> {
let name = path.file_name()?.to_str()?;
let row = |png: &Path| {
let (width, _, rgba) = crate::sprites::read_rgba(png).ok()?;
let row: Vec<u32> = rgba[..width as usize * 4]
.as_chunks::<4>()
.0
.iter()
.map(|p| u32::from(p[0]) << 16 | u32::from(p[1]) << 8 | u32::from(p[2]))
.collect();
(!row.is_empty()).then_some(row)
};
if let Some(stem) = name.strip_suffix("_bar.png") {
// A json of the same name owns its png.
if path.with_file_name(format!("{stem}.json")).exists() {
return None;
}
let relative = path.parent()?.strip_prefix(base).ok()?.join(stem);
let namespace = base.parent()?.parent()?.file_name()?.to_str()?;
let id = format!("{namespace}:{}", relative.to_str()?.replace('\\', "/"));
let targets = horizon_polytone::Targets::parse(None, Some(&id)).ok()?;
return Some(DamageBar {
targets,
row: row(path)?,
});
}
let stem = name.strip_suffix(".json")?;
let json: serde_json::Value = horizon_world::json_file::read(path).ok()?;
if json.get("bar_color")?.as_str()? != "damage" {
return None;
}
let png = [format!("{stem}_bar.png"), format!("{stem}.png")]
.into_iter()
.map(|png| path.with_file_name(png))
.find(|png| png.exists())?;
let relative = path.strip_prefix(root).unwrap_or(path).to_string_lossy();
let implicit = horizon_polytone::implicit_id(&relative, "item_modifiers");
let targets =
horizon_polytone::Targets::parse(json.get("targets"), implicit.as_deref()).ok()?;
Some(DamageBar {
targets,
row: row(&png)?,
})
}
/// A pack's durability bar colour (RGB) for an item at this damage (0 to 1).
pub fn item_bar_color(item: &str, damage: f32) -> Option<u32> {
let bars = ITEM_BARS.read().unwrap_or_else(|e| e.into_inner());
let bar = bars
.iter()
.rev()
.find(|b| b.targets.matches(item, |_| false))?;
let last = (bar.row.len() - 1) as f32;
Some(bar.row[(damage.clamp(0.0, 1.0) * last).round() as usize])
}
fn read(table: &[AtomicU64; 16], i: usize) -> Option<u32> {
let v = table[i & 15].load(Relaxed);
(v != 0).then_some(v as u32)
}
/// `DyeColor.getTextureDiffuseColor` (RGB) as the packs draw it.
pub fn diffuse(dye: usize) -> u32 {
get(&DIFFUSE, dye).unwrap_or(DYES[dye & 15].1)
read(&DIFFUSE, dye).unwrap_or(DYES[dye & 15].1)
}
/// `DyeColor.getTextColor` (RGB) as the packs draw it.
pub fn text(dye: usize) -> u32 {
get(&TEXT, dye).unwrap_or(DYE_TEXT[dye & 15].1)
read(&TEXT, dye).unwrap_or(DYE_TEXT[dye & 15].1)
}
/// A pack's `RedStoneWireBlock.COLORS` entry for this power (RGB), when it sets one.
pub fn redstone(power: u8) -> Option<u32> {
get(&REDSTONE, usize::from(power))
read(&REDSTONE, usize::from(power))
}
/// `ColorLerper.Type.SHEEP` wool colour: a pack's `sheep.<name>`, else white 0xE6E6E6 and the
/// others 75% of vanilla's dye (a pack's `dye.*.diffuse` doesn't reach wool).
pub fn sheep_wool(dye: u8) -> u32 {
let i = usize::from(dye & 15);
if let Some(color) = get(&SHEEP, i) {
if let Some(color) = read(&SHEEP, i) {
return color;
}
if i == 0 {
@@ -163,3 +156,81 @@ pub fn sheep_wool(dye: u8) -> u32 {
let f = |c: u32| ((c as f32 * 0.75).floor() as u32).min(255);
f(src >> 16 & 255) << 16 | f(src >> 8 & 255) << 8 | f(src & 255)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_pack_recolours_only_what_it_names() {
// Dyes no other test draws: the table is process-wide.
let dir = std::env::temp_dir().join(format!("pack-dyes-{}", std::process::id()));
let polytone = dir.join("assets/obs/polytone");
std::fs::create_dir_all(&polytone).unwrap();
std::fs::write(
polytone.join("colors.json"),
r#"{"dye": {"cyan": {"diffuse": "00ff00", "text": "0000ff"}}, "sheep": {"pink": "2040ff"}, "redstone": {"7": "0x123456"}}"#,
)
.unwrap();
set(&PackColors::read(&dir));
let (cyan, pink, purple) = (9, 6, 10);
assert_eq!(diffuse(cyan), 0x00ff00);
assert_eq!(text(cyan), 0x0000ff);
assert_eq!(sheep_wool(pink as u8), 0x2040ff);
// Wool reads vanilla's dye, not the pack's diffuse: 75% of 0x169C9C.
assert_eq!(sheep_wool(cyan as u8), 0x107575);
assert_eq!(diffuse(purple), 0x8932B8);
assert_eq!(text(purple), 0xA020F0);
assert_eq!((redstone(7), redstone(15)), (Some(0x123456), None));
set(&PackColors::default());
assert_eq!(redstone(7), None);
assert_eq!(diffuse(cyan), 0x169C9C);
assert_eq!(sheep_wool(pink as u8), 0xB6687F);
std::fs::remove_dir_all(&dir).ok();
}
fn png_row(path: &Path, pixels: &[[u8; 3]]) {
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
let file = std::fs::File::create(path).unwrap();
let mut encoder = png::Encoder::new(file, pixels.len() as u32, 1);
encoder.set_color(png::ColorType::Rgba);
let data: Vec<u8> = pixels
.iter()
.flat_map(|p| [p[0], p[1], p[2], 255])
.collect();
encoder
.write_header()
.unwrap()
.write_image_data(&data)
.unwrap();
}
#[test]
fn a_damage_bar_picks_its_pixel_by_the_stacks_damage() {
let dir = std::env::temp_dir().join(format!("pack-bars-{}", std::process::id()));
let modifiers = dir.join("assets/minecraft/polytone/item_modifiers");
let rgby = [[255, 0, 0], [0, 255, 0], [0, 0, 255], [255, 255, 0]];
png_row(&modifiers.join("diamond_pickaxe_bar.png"), &rgby);
std::fs::write(
modifiers.join("tools.json"),
r#"{"targets": ["minecraft:iron_axe"], "bar_color": "damage"}"#,
)
.unwrap();
png_row(&modifiers.join("tools_bar.png"), &[[1, 2, 3], [4, 5, 6]]);
set_item_bars(&dir);
// The real mod: damage 200 of 1561 shows the first pixel, 1400 the last.
assert_eq!(
item_bar_color("minecraft:diamond_pickaxe", 200.0 / 1561.0),
Some(0xFF0000)
);
assert_eq!(
item_bar_color("minecraft:diamond_pickaxe", 1400.0 / 1561.0),
Some(0xFFFF00)
);
assert_eq!(item_bar_color("minecraft:iron_axe", 0.9), Some(0x040506));
assert_eq!(item_bar_color("minecraft:diamond_shovel", 0.5), None);
set_item_bars(&dir.join("none"));
assert_eq!(item_bar_color("minecraft:diamond_pickaxe", 0.5), None);
std::fs::remove_dir_all(&dir).ok();
}
}
+110 -44
View File
@@ -31,7 +31,6 @@ mod native;
pub mod polytone;
mod polytone_gpu;
mod polytone_model;
mod spawn;
pub use native::{ParticleController, ParticleUpdate};
/// `ParticleOptions` with the parameters of the option classes used here.
@@ -1223,9 +1222,8 @@ pub struct ParticleLevel<'a> {
pub collision: &'a BlockCollisionRegistry<'a>,
/// Local player feet position and vertical velocity (`PlayerCloudParticle`).
pub player: Option<([f64; 3], f64)>,
pub cached_chunk: std::cell::Cell<Option<((i32, i32), Option<&'a crate::chunk::Chunk>)>>,
}
impl<'a> ParticleLevel<'a> {
impl ParticleLevel<'_> {
fn collide(&self, bb: Aabb, d: [f64; 3]) -> [f64; 3] {
let mut scene = SceneCollisions {
world: self.world,
@@ -1233,6 +1231,9 @@ impl<'a> ParticleLevel<'a> {
subject: None,
};
let movement = Vec3::new(d[0], d[1], d[2]);
// `Entity.collideBoundingBox(null, ...)`: no entity shapes, no border. Where no
// block of the swept box's cells can collide, it finds no shape and the
// movement stands, as the resolution below would return it.
if scene.cells_without_collision(bb.expand_towards(movement)) {
return d;
}
@@ -1248,46 +1249,38 @@ impl<'a> ParticleLevel<'a> {
p[2].floor() as i32,
]
}
#[inline]
pub fn block(&self, x: i32, y: i32, z: i32) -> horizon_world::registry::BlockStateId {
let (cx, cz) = (x >> 4, z >> 4);
let chunk = match self.cached_chunk.get() {
Some(((ccx, ccz), c)) if ccx == cx && ccz == cz => c,
_ => {
let c = self.world.chunks.get(&(cx, cz)).map(|c| c.as_ref());
self.cached_chunk.set(Some(((cx, cz), c)));
c
}
};
chunk.map_or(self.world.air, |c| c.block(x & 15, y, z & 15))
}
/// The fluid at a block and `FluidState.getHeight`.
fn fluid(&self, b: [i32; 3]) -> (Fluid, f64) {
let s = self.facts.get(self.block(b[0], b[1], b[2]));
let s = self.facts.get(self.world.block(b[0], b[1], b[2]));
if s.fluid == Fluid::None {
return (Fluid::None, 0.0);
}
let above = self.facts.get(self.block(b[0], b[1] + 1, b[2]));
let h = if above.fluid == s.fluid {
let above = self.facts.get(self.world.block(b[0], b[1] + 1, b[2]));
let height = if above.fluid == s.fluid {
1.0
} else {
f64::from(s.fluid_level) / 9.0
};
(s.fluid, h)
(s.fluid, height)
}
/// The fluid at a block and whether it is a source (`FluidState.isSource`).
pub(crate) fn fluid_source(&self, b: [i32; 3]) -> (Fluid, bool) {
let s = self.facts.get(self.block(b[0], b[1], b[2]));
let s = self.facts.get(self.world.block(b[0], b[1], b[2]));
(s.fluid, s.fluid_level == 8 && !s.fluid_falling)
}
/// `getCollisionShape(...).max(Y, x, z)` for cached shapes.
fn collision_top(&self, b: [i32; 3], fx: f64, fz: f64) -> f64 {
let state = self.block(b[0], b[1], b[2]);
let state = self.world.block(b[0], b[1], b[2]);
match self.collision.cached_shapes().cached_shape(state) {
Ok(Some(shape)) if !shape.is_empty() => shape.max_at(Axis::Y, fx, fz),
Ok(Some(_)) => 0.0,
_ if self.facts.get(state).full_collision => 1.0,
_ => 0.0,
_ => {
if self.facts.get(state).full_collision {
1.0
} else {
0.0
}
}
}
}
}
@@ -2027,6 +2020,73 @@ impl ParticleEngine {
(eye[0] - pos[0]).powi(2) + (eye[1] - pos[1]).powi(2) + (eye[2] - pos[2]).powi(2) <= 9.0
})
}
/// `ClientPacketListener.handleParticleEvent` for `ServerLevel.sendParticles`.
pub fn send_particles(
&mut self,
options: &ParticleOptions,
at: [f64; 3],
spread: [f64; 3],
speed: f64,
count: u32,
) {
if count == 0 {
let v = [speed * spread[0], speed * spread[1], speed * spread[2]];
self.add(options, at, v);
return;
}
for _ in 0..count {
let r = &mut self.random;
let p = [
at[0] + r.next_gaussian() * spread[0],
at[1] + r.next_gaussian() * spread[1],
at[2] + r.next_gaussian() * spread[2],
];
let v = [
r.next_gaussian() * speed,
r.next_gaussian() * speed,
r.next_gaussian() * speed,
];
self.add(options, p, v);
}
}
/// `ParticleEngine.createParticle`: always spawned, but `ParticleEngine.add`
/// drops a spore blossom air particle once `ParticleLimit.SPORE_BLOSSOM` is full.
pub fn create(&mut self, options: &ParticleOptions, p: [f64; 3], v: [f64; 3]) {
if let Some(requests) = &mut self.created_requests {
requests.push((options.clone(), p, v));
return;
}
let seed = self.seeds.next_long();
if let Some(mut particle) = self.make(options, p, v, seed) {
*self.made.entry(options.ty.name()).or_default() += 1;
// Resource packs' Polytone particle modifiers (#11486), once as it spawns.
if let Some(registry) = &self.polytone
&& registry.modifies_particles()
{
let id = match &options.mod_particle {
Some(name) => name.clone(),
None => format!("minecraft:{}", options.ty.name()),
};
polytone::modify(registry, &id, options.source.as_deref(), &mut particle);
}
// The provider has drawn, so rejecting here keeps vanilla's RNG order.
// shortcut: counts live plus queued particles by scanning instead of
// vanilla's counted map; use a counter if spore spawning shows in profiles.
if particle.ty == ParticleType::SporeBlossomAir
&& self.particles.len() + self.pending.len() >= SPORE_BLOSSOM_LIMIT
&& self
.particles
.iter()
.chain(self.pending.iter())
.filter(|q| q.ty == ParticleType::SporeBlossomAir)
.count()
>= SPORE_BLOSSOM_LIMIT
{
return;
}
self.pending.push(particle);
}
}
/// How many particles of each type were made so far.
pub fn made(&self) -> &std::collections::BTreeMap<&'static str, u64> {
&self.made
@@ -3992,12 +4052,15 @@ impl ParticleEngine {
}
Behaviour::Lava => {
p.base_tick(level);
if !p.removed && p.random.next_float() > p.age as f32 / p.lifetime as f32 {
self.add(
&ParticleOptions::simple(ParticleType::Smoke),
p.position,
p.velocity,
);
if !p.removed {
let odds = p.age as f32 / p.lifetime as f32;
if p.random.next_float() > odds {
self.add(
&ParticleOptions::simple(ParticleType::Smoke),
p.position,
p.velocity,
);
}
}
}
Behaviour::Crit => {
@@ -4081,7 +4144,9 @@ impl ParticleEngine {
*c *= f64::from(0.98f32);
}
if p.on_ground {
p.removed |= p.random.next_float() < 0.5;
if p.random.next_float() < 0.5 {
p.removed = true;
}
p.velocity[0] *= f64::from(0.7f32);
p.velocity[2] *= f64::from(0.7f32);
}
@@ -4121,10 +4186,13 @@ impl ParticleEngine {
} => {
p.base_tick(level);
let (fluid, source) = level.fluid_source(ParticleLevel::block_at(p.position));
if !(fluid == Fluid::Water && source)
|| p.position[1] >= y_end
|| p.position[1] <= y_prev
{
if !(p.removed || (fluid == Fluid::Water && source)) {
p.removed = true;
}
if !p.removed && p.position[1] >= y_end {
p.removed = true;
}
if !p.removed && p.position[1] <= y_prev {
p.removed = true;
}
// randomHorizontalWiggling, then a second, horizontal move.
@@ -4148,8 +4216,9 @@ impl ParticleEngine {
p.base_tick(level);
self.sprite_from_age(p);
if p.age as f32 > fade_start {
let frames_since_fade_out_start = p.age as f32 - fade_start;
p.rgba[3] =
(p.lifetime as f32 - (p.age as f32 - fade_start)) / p.lifetime as f32;
(p.lifetime as f32 - frames_since_fade_out_start) / p.lifetime as f32;
}
}
Behaviour::NoxiousGasCloud => {
@@ -4613,7 +4682,7 @@ impl ParticleEngine {
self.sprite_from_age(p);
half(p);
let b = ParticleLevel::block_at(p.position);
if level.facts.is_air(level.block(b[0], b[1], b[2])) {
if level.facts.is_air(level.world.block(b[0], b[1], b[2])) {
p.velocity[1] -= f64::from(0.0074f32);
}
}
@@ -4621,7 +4690,7 @@ impl ParticleEngine {
Behaviour::Firefly => {
p.base_tick(level);
let b = ParticleLevel::block_at(p.position);
if !level.facts.is_air(level.block(b[0], b[1], b[2])) {
if !level.facts.is_air(level.world.block(b[0], b[1], b[2])) {
p.removed = true;
} else {
p.rgba[3] = fade_amount(p.lifetime_progress(p.age as f32), 0.3, 0.5);
@@ -5085,12 +5154,11 @@ fn hdr_tier(ty: ParticleType) -> u8 {
}
/// `Particle.LifetimeAlpha.currentAlphaForAge`.
fn lifetime_alpha(a: [f32; 4], age: i32, lifetime: i32, partial: f32) -> f32 {
let t = ((age as f32 + partial) / lifetime as f32 - a[2]) / (a[3] - a[2]);
if (a[0] - a[1]).abs() < 1.0e-5 {
a[0]
} else {
a[0] + (a[1] - a[0]) * t.clamp(0.0, 1.0)
return a[0];
}
let t = ((age as f32 + partial) / lifetime as f32 - a[2]) / (a[3] - a[2]);
a[0] + (a[1] - a[0]) * t.clamp(0.0, 1.0)
}
struct Starter {
@@ -5521,7 +5589,6 @@ mod tests {
facts: &facts,
collision: &collision,
player: None,
cached_chunk: std::cell::Cell::new(None),
};
let mut engine = ParticleEngine::new(Path::new(""), &HashMap::new(), &HashSet::new());
engine.set_sprites.push(vec![10, 11, 12]);
@@ -5613,7 +5680,6 @@ mod tests {
facts: &facts,
collision: &collision,
player: None,
cached_chunk: std::cell::Cell::new(None),
};
let mut engine = ParticleEngine::new(Path::new(""), &HashMap::new(), &HashSet::new());
let mut particle = Particle::blank(ParticleType::Smoke, [0.0; 3]);
@@ -124,7 +124,6 @@ mod tests {
facts: &facts,
collision: &collision,
player: None,
cached_chunk: std::cell::Cell::new(None),
};
let mut e = engine();
let mut p = e
@@ -490,14 +490,14 @@ enum Subject<'a> {
}
/// What one expression runs against: its subject, the frame and the level.
struct Ctx<'a, 'l> {
struct Ctx<'a> {
subject: Subject<'a>,
random: &'a mut LegacyRandom,
custom: &'a mut f64,
customs: &'a mut BTreeMap<String, f64>,
frame: &'a Frame,
registry: &'a Registry,
level: Option<&'a ParticleLevel<'l>>,
level: Option<&'a ParticleLevel<'a>>,
blocks: Vec<[i32; 3]>,
}
@@ -515,7 +515,7 @@ fn axis(name: &str) -> usize {
usize::from(name.as_bytes()[0] - b'x')
}
impl Ctx<'_, '_> {
impl Ctx<'_> {
fn position(&self) -> [f64; 3] {
match &self.subject {
Subject::Particle(p) => p.position,
@@ -683,7 +683,7 @@ impl Ctx<'_, '_> {
}
}
impl Host for Ctx<'_, '_> {
impl Host for Ctx<'_> {
fn root(&mut self, root: Root) -> V {
match root {
Root::Object => V::Object(SELF),
@@ -1662,7 +1662,6 @@ mod tests {
facts: &scene.facts,
collision: &scene.collision,
player: None,
cached_chunk: std::cell::Cell::new(None),
};
let mut engine = engine(Registry::read(&root, Default::default(), blocks));
// Air has no emitters; the torch fires its first emitter only.
@@ -1702,7 +1701,6 @@ mod tests {
facts: &scene.facts,
collision: &scene.collision,
player: None,
cached_chunk: std::cell::Cell::new(None),
};
let mut engine = engine(Registry::read(&root, Default::default(), blocks));
engine.polytone_animate(&level, [1, 64, 1]);
@@ -1724,7 +1722,6 @@ mod tests {
facts: &scene.facts,
collision: &scene.collision,
player: None,
cached_chunk: std::cell::Cell::new(None),
};
let mut engine = engine(Registry::read(&root, Default::default(), blocks));
engine.polytone_animate(&level, [1, 64, 1]);
@@ -1770,7 +1767,6 @@ mod tests {
facts: &scene.facts,
collision: &scene.collision,
player: None,
cached_chunk: std::cell::Cell::new(None),
};
let registry = Registry::read(&root, Default::default(), blocks);
let frame = registry.frame.clone();
@@ -1824,7 +1820,6 @@ mod tests {
facts: &scene.facts,
collision: &scene.collision,
player: None,
cached_chunk: std::cell::Cell::new(None),
};
let registry = Registry::read(&root, Default::default(), blocks);
let mut engine = engine(registry);
@@ -1858,7 +1853,6 @@ mod tests {
facts: &scene.facts,
collision: &scene.collision,
player: None,
cached_chunk: std::cell::Cell::new(None),
};
let mut engine = engine(Registry::read(&root, Default::default(), blocks));
let rect = crate::hud::Rect {
@@ -1911,7 +1905,6 @@ mod tests {
facts: &scene.facts,
collision: &scene.collision,
player: None,
cached_chunk: std::cell::Cell::new(None),
};
let mut engine = engine(Registry::read(&root, Default::default(), blocks));
let rect = crate::hud::Rect {
@@ -1973,7 +1966,6 @@ mod tests {
facts: &scene.facts,
collision: &scene.collision,
player: None,
cached_chunk: std::cell::Cell::new(None),
};
let mut engine = engine(Registry::read(&root, Default::default(), blocks));
engine.polytone_entity(&level, "minecraft:player", [1.0, 70.0, 1.0]);
@@ -2022,7 +2014,6 @@ mod tests {
facts: &scene.facts,
collision: &scene.collision,
player: None,
cached_chunk: std::cell::Cell::new(None),
};
let mut engine = engine(Registry::read(&root, Default::default(), blocks));
let rect = crate::hud::Rect {
@@ -2089,7 +2080,6 @@ mod tests {
facts: &scene.facts,
collision: &scene.collision,
player: None,
cached_chunk: std::cell::Cell::new(None),
};
let registry = Arc::new(Registry::read(&root, Default::default(), blocks));
// The chunk's biome is id 0: plains, whose foliage is #336699.
@@ -2208,7 +2198,6 @@ mod tests {
facts: &scene.facts,
collision: &scene.collision,
player: None,
cached_chunk: std::cell::Cell::new(None),
};
let mut engine = engine(Registry::read(&root, Default::default(), blocks));
engine.polytone_entity(&level, "minecraft:player", [1.5, 70.0, 1.5]);
@@ -2287,7 +2276,6 @@ mod tests {
facts: &scene.facts,
collision: &scene.collision,
player: None,
cached_chunk: std::cell::Cell::new(None),
};
let mut engine = engine(Registry::read(&root, Default::default(), blocks));
// The test chunk's biome is the table's first: a cold one first, then a warm one.
@@ -1,73 +0,0 @@
//! Where particles come into the engine: a server's particle packet and a single particle made
//! where the game asks for one.
use super::*;
impl ParticleEngine {
/// `ClientPacketListener.handleParticleEvent` for `ServerLevel.sendParticles`.
pub fn send_particles(
&mut self,
options: &ParticleOptions,
at: [f64; 3],
spread: [f64; 3],
speed: f64,
count: u32,
) {
if count == 0 {
let v = [speed * spread[0], speed * spread[1], speed * spread[2]];
self.add(options, at, v);
return;
}
for _ in 0..count {
let r = &mut self.random;
let p = [
at[0] + r.next_gaussian() * spread[0],
at[1] + r.next_gaussian() * spread[1],
at[2] + r.next_gaussian() * spread[2],
];
let v = [
r.next_gaussian() * speed,
r.next_gaussian() * speed,
r.next_gaussian() * speed,
];
self.add(options, p, v);
}
}
/// `ParticleEngine.createParticle`: always spawned, but `ParticleEngine.add`
/// drops a spore blossom air particle once `ParticleLimit.SPORE_BLOSSOM` is full.
pub fn create(&mut self, options: &ParticleOptions, p: [f64; 3], v: [f64; 3]) {
if let Some(requests) = &mut self.created_requests {
requests.push((options.clone(), p, v));
return;
}
let seed = self.seeds.next_long();
if let Some(mut particle) = self.make(options, p, v, seed) {
*self.made.entry(options.ty.name()).or_default() += 1;
// Resource packs' Polytone particle modifiers (#11486), once as it spawns.
if let Some(registry) = &self.polytone
&& registry.modifies_particles()
{
let id = match &options.mod_particle {
Some(name) => name.clone(),
None => format!("minecraft:{}", options.ty.name()),
};
polytone::modify(registry, &id, options.source.as_deref(), &mut particle);
}
// The provider has drawn, so rejecting here keeps vanilla's RNG order.
// shortcut: counts live plus queued particles by scanning instead of
// vanilla's counted map; use a counter if spore spawning shows in profiles.
if particle.ty == ParticleType::SporeBlossomAir
&& self.particles.len() + self.pending.len() >= SPORE_BLOSSOM_LIMIT
&& self
.particles
.iter()
.chain(self.pending.iter())
.filter(|q| q.ty == ParticleType::SporeBlossomAir)
.count()
>= SPORE_BLOSSOM_LIMIT
{
return;
}
self.pending.push(particle);
}
}
}
@@ -93,7 +93,6 @@ fn particle_tick_cost() {
facts: &facts,
collision: &collision,
player: None,
cached_chunk: std::cell::Cell::new(None),
};
let ty = |name: &str| ParticleType::from_name(name).unwrap();
let (fx, fy, fz) = (0.5, f64::from(FLOOR_Y + 5), 20.5);
@@ -864,8 +864,6 @@ pub enum Opt {
PadGameplayGuide,
/// `options.modelPart.*`: one skin layer (index into `options_file::MODEL_PARTS`).
ModelPart(usize),
/// A slider of a page a crate registered (`horizon_hooks::pages`): (page, row).
Page(usize, usize),
/// 3D skin layers: the player's outer layer as extruded slabs.
SkinLayers3d,
MainHand,
+1 -27
View File
@@ -192,12 +192,7 @@ impl SignData {
let side = |t: Option<&Tag>| -> SignText {
let mut out = SignText::default();
let Some(t) = t else { return out };
if let Some(messages) = t.get("messages") {
// `SignText.CODEC` reads a side all or nothing: a messages
// field that is not a list leaves the side empty.
let Tag::List(messages) = messages else {
return SignText::default();
};
if let Some(Tag::List(messages)) = t.get("messages") {
for (i, m) in messages.iter().take(4).enumerate() {
out.lines[i] = component_text(m);
out.click[i] = click_event(m);
@@ -312,27 +307,6 @@ pub fn editor_sprite(folder: &str, wood: &str) -> String {
#[cfg(test)]
mod tests {
use super::*;
/// `SignText.CODEC` reads messages as a list: a string field fails the
/// whole side, taking its colour and glow with it.
#[test]
fn messages_that_are_not_a_list_load_an_empty_side() {
let tag = Tag::compound([(
"front_text",
Tag::compound([
("messages", Tag::String("not a list".into())),
("color", Tag::String("red".into())),
("has_glowing_text", Tag::Byte(1)),
]),
)]);
let mut decoded = SignData::default();
decoded.decode(&tag);
assert_eq!(
decoded.front,
SignText::default(),
"a failed message list takes the side's colour and glow with it"
);
}
#[test]
fn waxed_and_glowing_text_take_any_number() {
let tag = Tag::compound([
+2 -7
View File
@@ -51,7 +51,7 @@ pub fn enabled() -> bool {
}
/// Record this thread's spans even with spans off, their wall time only (no CPU clock read):
/// what the render thread's slow frames spent their time in, for the opt-in performance
/// statistics (`play_session::Session::frame`). The thread takes them every frame ([`take`]).
/// statistics (`telemetry::Session::frame`). The thread takes them every frame ([`take`]).
pub fn record_here(on: bool) {
HERE.with(|h| h.set(on));
}
@@ -466,7 +466,7 @@ pub mod startup {
Mutex, OnceLock,
atomic::{AtomicBool, AtomicU32, Ordering},
},
time::{Duration, Instant},
time::Instant,
};
static ON: AtomicBool = AtomicBool::new(false);
@@ -489,11 +489,6 @@ pub mod startup {
ON.store(sink.is_some(), Ordering::Relaxed);
}
/// The time since the process started ([`init`]'s origin; zero before it).
pub fn since_start() -> Duration {
ORIGIN.get().map_or(Duration::ZERO, Instant::elapsed)
}
/// Whether a trace is being written.
#[inline]
pub fn on() -> bool {
+7 -74
View File
@@ -810,14 +810,8 @@ fn slider_sprites(hover: bool, active: bool, dragging: bool) -> (&'static str, &
slider_sprites_ext(hover, active, false, false, dragging)
}
/// `AbstractSliderButton.getSprite` and `getHandleSprite`: the track outline
/// lights while the slider is focused and its value cannot change
/// (`isActive() && isFocused() && !canChangeValue`), the handle while it is
/// hovered or its value can change (`isActive() && (isHovered || canChangeValue)`).
/// `canChangeValue` is set when the slider is focused with the mouse or Tab and
/// flipped by Enter (`setFocused`, `keyPressed`): Horizon's `can_change_value`
/// for the keyboard, and a drag for the mouse, which focuses it with the value
/// free to change. So a dragged slider keeps its plain track.
/// Track and handle sprites: the track outline shows while dragged or focused with
/// keyboard navigation (!can_change_value), hovering or editing highlights the handle.
fn slider_sprites_ext(
hover: bool,
active: bool,
@@ -825,13 +819,12 @@ fn slider_sprites_ext(
can_change_value: bool,
dragging: bool,
) -> (&'static str, &'static str) {
let can_change_value = can_change_value || dragging;
let track = if active && focused && !can_change_value {
let track = if dragging || (active && focused && !can_change_value) {
"gui/sprites/widget/slider_highlighted"
} else {
"gui/sprites/widget/slider"
};
let handle = if active && (hover || can_change_value) {
let handle = if active && (hover || can_change_value || dragging) {
"gui/sprites/widget/slider_handle_highlighted"
} else {
"gui/sprites/widget/slider_handle"
@@ -1238,9 +1231,6 @@ pub struct EditBox {
/// Grey completion shown after the text (EditBox.setSuggestion).
pub suggestion: Option<String>,
pub text_color: u32,
/// `EditBox.setCentered`: the text starts in the middle of the box's
/// width, not at its left edge (`BookSignScreen`'s title).
pub centered: bool,
/// `EditBox.textColorUneditable`: the colour of a box `setEditable(false)`
/// made read-only, which still shows its value and caret.
pub text_color_uneditable: u32,
@@ -1539,7 +1529,6 @@ impl EditBox {
focused_at: Instant::now(),
suggestion: None,
text_color: rgba(224, 224, 224, 255),
centered: false,
text_color_uneditable: rgba(112, 112, 112, 255),
editable: true,
message: String::new(),
@@ -1563,16 +1552,6 @@ impl EditBox {
self.y
}
}
/// `EditBox.updateTextPosition`: where the text starts, `(width - text
/// width) / 2` in for a centred box (vanilla's `textX`), else at
/// `text_x` (4 in from a border).
pub fn text_start_x(&self, text_width: f32) -> f32 {
if self.centered {
self.x + ((self.width - text_width) / 2.0).floor()
} else {
self.text_x()
}
}
pub fn reset_blink(&mut self) {
self.focused_at = Instant::now();
}
@@ -1655,13 +1634,8 @@ impl EditBox {
/// EditBox.findClickedPositionInText: the character boundary under GUI `x`.
pub fn position_at(&mut self, atlas: &HudAtlas, x: f32) -> usize {
self.layout(atlas);
let room = (x.floor() - self.text_x()).min(self.inner_width());
let rest: String = self.value.chars().skip(self.display).collect();
let shown: String = rest
.chars()
.take(atlas.font.fit(&rest, self.inner_width()))
.collect();
let start = self.text_start_x(atlas.font.width_text(&Text::literal(shown.as_str())));
let room = (x.floor() - start).min(self.inner_width());
self.display + atlas.font.fit(&rest, room)
}
/// EditBox.onClick: the cursor to the pointer, extending the selection
@@ -1868,12 +1842,7 @@ impl EditBox {
),
}
};
let start = if self.centered {
self.text_start_x(atlas.font.width_text(&Text::literal(displayed.as_str())))
} else {
self.text_x()
};
let (tx, ty) = (start, self.text_y());
let (tx, ty) = (self.text_x(), self.text_y());
let mut draw_x = tx;
if !displayed.is_empty() {
let half = if on_screen {
@@ -2206,38 +2175,17 @@ mod tests {
}
}
/// `AbstractSliderButton.getSprite`/`getHandleSprite`: a focused slider
/// whose value can't change lights its track, a hovered one or one whose
/// value can change (Tab or Enter, or the mouse's drag) lights its handle.
#[test]
fn slider_sprites_follow_focus_and_the_drag() {
// Hovered, at rest: plain track, lit handle.
fn slider_track_is_outlined_only_while_dragged() {
let (track, handle) = slider_sprites(true, true, false);
assert_eq!(track, "gui/sprites/widget/slider");
assert_eq!(handle, "gui/sprites/widget/slider_handle_highlighted");
// Dragged: still a plain track, lit handle.
let (track, handle) = slider_sprites(true, true, true);
assert_eq!(track, "gui/sprites/widget/slider");
assert_eq!(handle, "gui/sprites/widget/slider_handle_highlighted");
// Focused with the arrow keys: the value can't change, the track lights.
let (track, handle) = slider_sprites_ext(false, true, true, false, false);
assert_eq!(track, "gui/sprites/widget/slider_highlighted");
assert_eq!(handle, "gui/sprites/widget/slider_handle");
// Focused with Tab, or Enter pressed on it: the value can change, the handle lights.
let (track, handle) = slider_sprites_ext(false, true, true, true, false);
assert_eq!(track, "gui/sprites/widget/slider");
assert_eq!(handle, "gui/sprites/widget/slider_handle_highlighted");
// Focused while the mouse drags: the track stays plain.
let (track, _) = slider_sprites_ext(true, true, true, false, true);
assert_eq!(track, "gui/sprites/widget/slider");
// Not hovered, not dragged: nothing is lit.
let (track, handle) = slider_sprites(false, true, false);
assert_eq!(track, "gui/sprites/widget/slider");
assert_eq!(handle, "gui/sprites/widget/slider_handle");
// An inactive slider highlights neither, even when focused.
let (track, handle) = slider_sprites_ext(true, false, true, true, true);
assert_eq!(track, "gui/sprites/widget/slider");
assert_eq!(handle, "gui/sprites/widget/slider_handle");
}
#[test]
fn separator_tiles_sample_both_rows_and_clip_the_last_column() {
@@ -2674,19 +2622,4 @@ mod tests {
assert!(edit.key(winit::keyboard::KeyCode::Backspace, false, false));
assert_eq!(edit.value, "name");
}
/// `EditBox.setCentered` (`BookSignScreen`'s title box): with the flag the
/// text starts `(width - text width) / 2` in, which is also where a click
/// maps; without it the text starts at the box's left edge.
#[test]
fn a_centred_box_starts_its_text_in_the_middle() {
let mut edit = EditBox::new(10.0, 0.0, 114.0, 9.0);
edit.bordered = false;
assert_eq!(edit.text_start_x(0.0), 10.0);
edit.centered = true;
assert_eq!(edit.text_start_x(0.0), 67.0);
assert_eq!(edit.text_start_x(18.0), 58.0);
// (114 - 5) / 2 = 54.5, rounded down.
assert_eq!(edit.text_start_x(5.0), 64.0);
}
}
+6 -8
View File
@@ -68,11 +68,10 @@ log = "0.4"
chrono = { version = "0.4.42", default-features = false, features = ["clock", "std"] }
flate2 = { version = "1.1.10", default-features = false, features = ["zlib-rs", "runtime_detection"] }
horizon-theme = { path = "../horizon-theme" }
# The official server's address (multiplayer.rs) until it moves to the Horizon layer. Telemetry,
# the "I'm playing" ping and the benchmark are Horizon parts the official executable registers.
# Opt-in data collection: the game keeps, the launcher sends (docs/telemetry.md). The
# anonymous "I'm playing" ping is the Horizon layer's (`horizon-heartbeat`, in the official executable).
horizon-telemetry = { path = "../horizon-telemetry", default-features = false }
# The machine and process probes (the dedicated server's performance report).
horizon-system = { path = "../horizon-system" }
horizon-bench = { path = "../horizon-bench" }
crc32fast = "1"
zip = { version = "9.1.3", default-features = false, features = ["deflate"] }
# Controllers (evdev + libudev on Linux, IOKit on macOS, Windows.Gaming.Input); the `controllers` feature.
@@ -123,13 +122,12 @@ controllers = ["horizon-client-lib/controllers", "dep:gilrs", "dep:horizon-duals
# the server list, the Horizon Settings and Mods pages, Discord and Play with Friends. 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 = ["horizon-client-lib/horizon-extras", "discord", "friends"]
horizon-extras = ["discord", "friends"]
# 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"]
# Play with Friends: sharing a world behind an invite code (game/friends.rs). Joining one is
# horizon-friends' `game` side, which horizon-official registers (`horizon_hooks::tunnel`).
friends = ["dep:horizon-friends"]
# Play with Friends: sharing a world behind an invite code (game/friends.rs) and joining one.
friends = ["horizon-client-lib/friends", "dep:horizon-friends"]
# Records the call sites of mob random draws (`mob_ai_probe` traces).
rng-trace = ["horizon-core/rng-trace", "horizon-client-lib/rng-trace"]
# Rust's allocations go to the system heap instead of mimalloc, where heap profilers that replace
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Lig skaduwees na jou skerm se donkerste swart. 0: suiwer swart, geen ligting.",
"How bright highlights get. Auto: the peak your system reports.": "Hoe helder hoogtepunte word. Outo: die hoogtepunt wat jou stelsel rapporteer.",
"How bright white looks in the world. Auto: your desktop's white.": "Hoe helder wit in die wêreld lyk. Outo: jou lessenaar se wit.",
"How bright the HUD and menus are, against paper white.": "Hoe helder die HUD en spyskaarte is, teenoor papierwit.",
"How far mobs, crops and machines keep working. CPU time.": "Hoe ver skare, gewasse en masjiene aanhou werk. SVE tyd.",
"How far away mobs and items are drawn, as a share of normal.": "Hoe ver weg skares en items getrek word, as 'n deel van normaal.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Slaan terrein oor wat agter geslote gedeeltes versteek is. Af kos baie.",
@@ -366,6 +367,7 @@
"Advanced": "Gevorderd",
"Undo": "Ontdoen",
"Search options": "Soek opsies",
"Shortcut": "Kortpad",
"Distance": "Afstand",
"Display": "Vertoon",
"Interface": "Koppelvlak",
@@ -436,9 +438,11 @@
"Calibrate...": "Kalibreer...",
"Calibrate Anyway...": "Kalibreer in elk geval...",
"Use Automatic Setup": "Gebruik outomatiese opstelling",
"Shortcut: %s": "Kortpad: %s",
"Next": "Volgende",
"HDR Calibration %s/%s: %s": "HDR-kalibrasie %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Stel die drie hieronder volskerm, een toetsprent op 'n slag.",
"A key that opens this screen from the game. Not bound at first.": "'n Sleutel wat hierdie skerm vanaf die speletjie oopmaak. Eers nie gebonde nie.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Jou skerm het nie gesê hoe helder dit word nie: kalibreer om dit op te stel.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Reeds opgestel vir jou vertoning. Kalibreer slegs as HDR verkeerd lyk.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Plaas Peak Brightness en Paper White terug na jou skerm se opstelling.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "يرفع الظلال إلى أغمق أسود في شاشتك. 0: أسود حقيقي، دون رفع.",
"How bright highlights get. Auto: the peak your system reports.": "كيف تحصل على النقاط البارزة الساطعة. تلقائي: ذروة تقارير النظام الخاص بك.",
"How bright white looks in the world. Auto: your desktop's white.": "مدى سطوع الأبيض في العالم. تلقائي: أبيض سطح المكتب.",
"How bright the HUD and menus are, against paper white.": "مدى سطوع شاشة HUD والقوائم مقابل الورق الأبيض.",
"How far mobs, crops and machines keep working. CPU time.": "إلى أي مدى يستمر الغوغاء والمحاصيل والآلات في العمل. وقت وحدة المعالجة المركزية.",
"How far away mobs and items are drawn, as a share of normal.": "إلى أي مدى يتم رسم الغوغاء والعناصر، كنسبة من المعدل الطبيعي.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "يتخطى التضاريس المخفية خلف الأقسام المغلقة. قبالة تكاليف الكثير.",
@@ -366,6 +367,7 @@
"Advanced": "متقدم",
"Undo": "تراجع",
"Search options": "خيارات البحث",
"Shortcut": "الاختصار",
"Distance": "المسافة",
"Display": "عرض",
"Interface": "واجهة",
@@ -436,9 +438,11 @@
"Calibrate...": "معايرة...",
"Calibrate Anyway...": "معايرة على أية حال...",
"Use Automatic Setup": "استخدم الإعداد التلقائي",
"Shortcut: %s": "الاختصار: %s",
"Next": "التالي",
"HDR Calibration %s/%s: %s": "معايرة HDR %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "لتعيين الصور الثلاثة أدناه بملء الشاشة، صورة اختبارية واحدة في كل مرة.",
"A key that opens this screen from the game. Not bound at first.": "المفتاح الذي يفتح هذه الشاشة من اللعبة. غير ملزمة في البداية.",
"Your display didn't say how bright it gets: calibrate to set it up.": "لم توضح شاشتك مدى سطوعها: قم بالمعايرة لإعدادها.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "تم إعداده بالفعل للعرض الخاص بك. قم بالمعايرة فقط إذا كان تقرير HDR يبدو خاطئًا.",
"Puts Peak Brightness and Paper White back to your display's setup.": "يعيد ذروة السطوع والورق الأبيض إلى إعداد شاشتك.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Kölgələri ekranınızın ən tünd qarasına qaldırır. 0: həqiqi qara, qaldırma yoxdur.",
"How bright highlights get. Auto: the peak your system reports.": "Parlaq məqamlar necə əldə edilir. Avtomatik: sisteminizin ən yüksək hesabatı.",
"How bright white looks in the world. Auto: your desktop's white.": "Dünyada ağ rəngin nə qədər parlaq göründüyü. Avtomatik: iş masanızın ağı.",
"How bright the HUD and menus are, against paper white.": "HUD və menyular ağ kağıza qarşı nə qədər parlaqdır.",
"How far mobs, crops and machines keep working. CPU time.": "Kütlələr, məhsullar və maşınlar nə qədər işləməyə davam edir. CPU vaxtı.",
"How far away mobs and items are drawn, as a share of normal.": "Nə qədər uzağa mob və əşyalar çəkilir, normal bir pay olaraq.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Qapalı hissələrin arxasında gizlənmiş ərazini ötür. Sıxılma çox baha başa gəlir.",
@@ -366,6 +367,7 @@
"Advanced": "Qabaqcıl",
"Undo": "Geri alın",
"Search options": "Axtarış seçimləri",
"Shortcut": "Qısayol",
"Distance": "Məsafə",
"Display": "Ekran",
"Interface": "İnterfeys",
@@ -436,9 +438,11 @@
"Calibrate...": "Kalibrləmə...",
"Calibrate Anyway...": "İstənilən halda kalibrləyin...",
"Use Automatic Setup": "Avtomatik Quraşdırmadan istifadə edin",
"Shortcut: %s": "Qısayol: %s",
"Next": "Sonrakı",
"HDR Calibration %s/%s: %s": "HDR Kalibrləmə %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Aşağıdakı üç tam ekranı, hər dəfə bir sınaq şəklini təyin edir.",
"A key that opens this screen from the game. Not bound at first.": "Oyundan bu ekranı açan açar. Əvvəlcə bağlı deyil.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Ekranınız nə qədər parlaq olduğunu deməyib: onu qurmaq üçün kalibrləyin.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Ekranınız üçün artıq qurulub. Yalnız HDR səhv görünürsə kalibrləyin.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Pik Parlaqlığı və Kağız Ağı ekranınızın quraşdırılmasına qaytarır.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Күләгәләрҙе дисплейығыҙҙың иң ҡара ҡараһына күтәрә. 0: ысын ҡара, күтәреү юҡ.",
"How bright highlights get. Auto: the peak your system reports.": "Нисек сағыу өҫтөнлөктәр ала. Авто: пик һеҙҙең система отчеттар.",
"How bright white looks in the world. Auto: your desktop's white.": "Донъяла аҡ ни тиклем сағыу күренә. Авто: эш өҫтәлегеҙҙең ағы.",
"How bright the HUD and menus are, against paper white.": "Нисек сағыу HUD һәм менюлар, ҡағыҙ аҡ ҡаршы.",
"How far mobs, crops and machines keep working. CPU time.": "Күпме алыҫҡа мобтар, культуралар һәм машиналар эшләп тора. Процессор ваҡыты.",
"How far away mobs and items are drawn, as a share of normal.": "Нисек алыҫҡа мобтар һәм әйберҙәр тартыла, өлөшө булараҡ нормаль.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Ябыҡ участкалар артында йәшеренгән ерҙәрҙе һикерә. Офф күп сығымдар талап итә.",
@@ -366,6 +367,7 @@
"Advanced": "Алдынғы",
"Undo": "Кире ҡайтарыу",
"Search options": "Эҙләү варианттары",
"Shortcut": "Ярлыҡ",
"Distance": "Арауыҡ",
"Display": "Дисплей",
"Interface": "Интерфейс",
@@ -436,9 +438,11 @@
"Calibrate...": "Калибрлау...",
"Calibrate Anyway...": "Калибрлау барыбер...",
"Use Automatic Setup": "Автоматик ҡуйыуҙы ҡулланыу",
"Shortcut: %s": "Ҡыҫҡа юл: %s",
"Next": "Артабан",
"HDR Calibration %s/%s: %s": "HDR калибровкаһы %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Өс аҫтында тулы экран ҡуя, бер һынау һүрәте бер юлы.",
"A key that opens this screen from the game. Not bound at first.": "Уйындан был экранды асҡан асҡыс. Тәүҙә бәйләнмәгән.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Һеҙҙең дисплей нисек яҡты ала, тип әйтмәне: калибровка, уны ҡуйырға.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Һеҙҙең дисплей өсөн инде ҡуйылған. Тик калибровка, әгәр HDR дөрөҫ түгел күренә.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Ҡуя Пик Яркость һәм Ҡағыҙ Аҡ кире һеҙҙең дисплей&#8217;ы ҡороу.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Падымае цені да самага цёмнага чорнага вашага дысплея. 0: сапраўдны чорны, без пад'ёму.",
"How bright highlights get. Auto: the peak your system reports.": "Якія яркія моманты становяцца. Аўта: пік, пра які паведамляе ваша сістэма.",
"How bright white looks in the world. Auto: your desktop's white.": "Наколькі ярка выглядае белы ў свеце. Аўто: белы колер вашага працоўнага стала.",
"How bright the HUD and menus are, against paper white.": "Наколькі яркія HUD і меню на фоне белай паперы.",
"How far mobs, crops and machines keep working. CPU time.": "Як далёка працягваюць працаваць мобы, ураджай і машыны. працэсарны час.",
"How far away mobs and items are drawn, as a share of normal.": "Наколькі далёка намаляваны мобы і прадметы ў долі звычайнага.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Прапускае мясцовасць, схаваную за закрытымі ўчасткамі. Off каштуе шмат.",
@@ -366,6 +367,7 @@
"Advanced": "Пашыраны",
"Undo": "Адмяніць",
"Search options": "Параметры пошуку",
"Shortcut": "Ярлык",
"Distance": "Дыстанцыя",
"Display": "Дысплей",
"Interface": "Інтэрфейс",
@@ -436,9 +438,11 @@
"Calibrate...": "Адкалібраваць...",
"Calibrate Anyway...": "Усё роўна адкалібраваць...",
"Use Automatic Setup": "Выкарыстоўвайце аўтаматычную наладу",
"Shortcut: %s": "Ярлык: %s",
"Next": "Далей",
"HDR Calibration %s/%s: %s": "Каліброўка HDR %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Устанаўлівае тры ніжэй на ўвесь экран, па адным тэставым малюнку.",
"A key that opens this screen from the game. Not bound at first.": "Ключ, які адкрывае гэты экран з гульні. Спачатку не звязаны.",
"Your display didn't say how bright it gets: calibrate to set it up.": "На вашым дысплеі не пазначана, наколькі ён яркім: адкалібруйце, каб наладзіць яго.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Ужо наладжана для вашага дысплея. Калібруйце, толькі калі HDR выглядае няправільна.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Вяртае пікавую яркасць і папяровы белы ў налады вашага дысплея.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Повдига сенките до най-тъмното черно на вашия дисплей. 0: истинско черно, без повдигане.",
"How bright highlights get. Auto: the peak your system reports.": "Колко ярки стават акцентите. Автоматично: пикът, който вашата система отчита.",
"How bright white looks in the world. Auto: your desktop's white.": "Колко ярко изглежда бялото в света. Авто: бялото на вашия работен плот.",
"How bright the HUD and menus are, against paper white.": "Колко ярки са HUD и менютата на бяла хартия.",
"How far mobs, crops and machines keep working. CPU time.": "Докъде продължават да работят тълпите, културите и машините. процесорно време.",
"How far away mobs and items are drawn, as a share of normal.": "Колко далеч са изтеглени тълпите и предметите, като дял от нормалното.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Пропуска терен, скрит зад затворени участъци. Off струва много.",
@@ -366,6 +367,7 @@
"Advanced": "Разширено",
"Undo": "Отмяна",
"Search options": "Опции за търсене",
"Shortcut": "Пряк път",
"Distance": "Разстояние",
"Display": "Дисплей",
"Interface": "Интерфейс",
@@ -436,9 +438,11 @@
"Calibrate...": "Калибриране...",
"Calibrate Anyway...": "Калибриране все пак...",
"Use Automatic Setup": "Използвайте автоматична настройка",
"Shortcut: %s": "Пряк път: %s",
"Next": "Следваща",
"HDR Calibration %s/%s: %s": "HDR калибриране %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Задава трите под цял екран, една тестова картина наведнъж.",
"A key that opens this screen from the game. Not bound at first.": "Ключ, който отваря този екран от играта. В началото не е обвързан.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Вашият дисплей не каза колко ярък става: калибрирайте, за да го настроите.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Вече е настроен за вашия дисплей. Калибрирайте само ако HDR изглежда грешно.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Връща Peak Brightness и Paper White обратно към настройката на вашия дисплей.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Sevel a ra ar skeudoù betek du teñvalañ ho skramm. 0 : du gwir, sevel ebet.",
"How bright highlights get. Auto: the peak your system reports.": "Pegen skedus e teu ar sklêrijennoù. Emgefre : ar pik a vez embannet gant ho reizhiad.",
"How bright white looks in the world. Auto: your desktop's white.": "Pegen skedus e seblant ar gwenn er bed. Emgefreek : gwenn ho kefridi.",
"How bright the HUD and menus are, against paper white.": "Pegen skedus eo an HUD hag ar mennozhioù, a-enep gwenn ar paper.",
"How far mobs, crops and machines keep working. CPU time.": "Pegen pell e kendalc'h ar mobs, ar c'hoadoù hag ar mekanikoù da labourat. Amzer ar CPU.",
"How far away mobs and items are drawn, as a share of normal.": "Pegen pell e vez tennet ar mobs hag an traoù, evel ul lodenn eus ar pezh a vez graet normal.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Tremen a ra an douaroù kuzhet a-dreñv rannoù serr. Koustañ a ra kalz.",
@@ -366,6 +367,7 @@
"Advanced": "Araokaet",
"Undo": "Dizober",
"Search options": "Dibarzhioù klask",
"Shortcut": "Berradennoù",
"Distance": "Pellder",
"Display": "Diskouez",
"Interface": "Etrefas",
@@ -436,9 +438,11 @@
"Calibrate...": "Kalibrañ...",
"Calibrate Anyway...": "Kalibrañ koulskoude...",
"Use Automatic Setup": "Implijout arventennoù emgefre",
"Shortcut: %s": "Berradennoù : %s",
"Next": "Da heul",
"HDR Calibration %s/%s: %s": "Kalibradur HDR %s/%s : %s",
"Sets the three below full screen, one test picture at a time.": "Lakaat a ra an tri skramm leun a-is, ur skeudenn amprouiñ bep tro.",
"A key that opens this screen from the game. Not bound at first.": "Un alc'hwez a zigor ar skramm-mañ eus ar c'hoari. N'eo ket stag da gentañ.",
"Your display didn't say how bright it gets: calibrate to set it up.": "N'eo ket bet embannet gant ho skramm pegen sklaer e teu da vezañ : kalibret evit e lakaat war-sav.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Kefluniet dija evit ho skramm. Kalibret nemet ma seblant HDR bezañ fazius.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Lakaat a ra ar sked uhelañ hag ar gwenn paper en-dro e kefluniadur ho skramm.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Podiže sjene do najcrnje crne vašeg ekrana. 0: prava crna, bez podizanja.",
"How bright highlights get. Auto: the peak your system reports.": "Kako svetli naglasci postaju. Automatski: vrhunac o kojem vaš sistem prijavljuje.",
"How bright white looks in the world. Auto: your desktop's white.": "Koliko svijetlo izgleda bijelo u svijetu. Automatski: bijela vaše radne površine.",
"How bright the HUD and menus are, against paper white.": "Koliko su svijetli HUD i meniji, naspram bijelog papira.",
"How far mobs, crops and machines keep working. CPU time.": "Koliko mobovi, usjevi i mašine rade. CPU vrijeme.",
"How far away mobs and items are drawn, as a share of normal.": "Koliko su udaljeni mobovi i predmeti, kao udio normalnog.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Preskače teren skriven iza zatvorenih dijelova. Isključenje košta puno.",
@@ -366,6 +367,7 @@
"Advanced": "Napredno",
"Undo": "Poništi",
"Search options": "Opcije pretraživanja",
"Shortcut": "Prečica",
"Distance": "Udaljenost",
"Display": "Ekran",
"Interface": "Interfejs",
@@ -436,9 +438,11 @@
"Calibrate...": "Kalibriraj...",
"Calibrate Anyway...": "Kalibriraj svejedno...",
"Use Automatic Setup": "Koristite automatsko podešavanje",
"Shortcut: %s": "Prečica: %s",
"Next": "Sledeći",
"HDR Calibration %s/%s: %s": "HDR kalibracija %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Postavlja tri ispod preko celog ekrana, jednu po jednu probnu sliku.",
"A key that opens this screen from the game. Not bound at first.": "Taster koji otvara ovaj ekran iz igre. U početku nije vezan.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Vaš displej nije rekao koliko svetli: kalibrirajte da biste ga postavili.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Već postavljeno za vaš ekran. Kalibrirajte samo ako HDR izgleda pogrešno.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Vraća Peak Brightness i Paper White u postavke vašeg ekrana.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Puja les ombres fins al negre més fosc de la teva pantalla. 0: negre pur, sense pujar.",
"How bright highlights get. Auto: the peak your system reports.": "Com es fan els reflexos brillants. Automàtic: el pic que informa el vostre sistema.",
"How bright white looks in the world. Auto: your desktop's white.": "Com de brillant es veu el blanc al món. Automàtic: el blanc de l'escriptori.",
"How bright the HUD and menus are, against paper white.": "Què brillants són l'HUD i els menús, contra el paper blanc.",
"How far mobs, crops and machines keep working. CPU time.": "Fins on segueixen treballant les turbes, els conreus i les màquines. Temps de CPU.",
"How far away mobs and items are drawn, as a share of normal.": "A quina distància es dibuixen les turbes i els articles, com a part de la normalitat.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Salta el terreny amagat darrere de seccions tancades. Off costa molt.",
@@ -366,6 +367,7 @@
"Advanced": "Avançat",
"Undo": "Desfer",
"Search options": "Opcions de cerca",
"Shortcut": "Drecera",
"Distance": "Distància",
"Display": "Mostra",
"Interface": "Interfície",
@@ -436,9 +438,11 @@
"Calibrate...": "Calibrar...",
"Calibrate Anyway...": "Calibra de totes maneres...",
"Use Automatic Setup": "Utilitzeu la configuració automàtica",
"Shortcut: %s": "Drecera: %s",
"Next": "A continuació",
"HDR Calibration %s/%s: %s": "Calibració HDR %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Estableix les tres imatges de sota de pantalla completa, una imatge de prova a la vegada.",
"A key that opens this screen from the game. Not bound at first.": "Una clau que obre aquesta pantalla des del joc. No lligat al principi.",
"Your display didn't say how bright it gets: calibrate to set it up.": "La pantalla no va dir la brillantor que té: calibra per configurar-la.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Ja està configurat per a la vostra pantalla. Calibreu només si l'HDR sembla incorrecte.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Torna a posar la brillantor màxima i el blanc del paper a la configuració de la pantalla.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Zvedne stíny na nejtemnější černou vašeho displeje. 0: skutečná černá, bez zvednutí.",
"How bright highlights get. Auto: the peak your system reports.": "Jak se získá jasný melír. Auto: vrchol, který váš systém hlásí.",
"How bright white looks in the world. Auto: your desktop's white.": "Jak jasně bílá ve světě vypadá. Auto: bílá vaší plochy.",
"How bright the HUD and menus are, against paper white.": "Jak jasný je HUD a nabídky oproti bílé na papíře.",
"How far mobs, crops and machines keep working. CPU time.": "Jak dalece davy, plodiny a stroje fungují. CPU čas.",
"How far away mobs and items are drawn, as a share of normal.": "Jak daleko jsou mobové a předměty nakresleny jako podíl normálu.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Přeskočí terén skrytý za uzavřenými úseky. Vypnutí stojí hodně.",
@@ -366,6 +367,7 @@
"Advanced": "Pokročilé",
"Undo": "Vrátit zpět",
"Search options": "Možnosti vyhledávání",
"Shortcut": "Zkratka",
"Distance": "Vzdálenost",
"Display": "Displej",
"Interface": "Rozhraní",
@@ -436,9 +438,11 @@
"Calibrate...": "Kalibrovat...",
"Calibrate Anyway...": "Přesto kalibrovat...",
"Use Automatic Setup": "Použijte Automatické nastavení",
"Shortcut: %s": "Zkratka: %s",
"Next": "Další",
"HDR Calibration %s/%s: %s": "Kalibrace HDR %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Nastaví tři pod celou obrazovkou, vždy jeden testovací obrázek.",
"A key that opens this screen from the game. Not bound at first.": "Klíč, který otevře tuto obrazovku ze hry. Zpočátku bez vazby.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Váš displej neříkal, jak jasný je: zkalibrujte jej, abyste jej nastavili.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Již nastaveno pro váš displej. Kalibrujte pouze v případě, že HDR vypadá špatně.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Vrátí maximální jas a papírovou bílou zpět do nastavení vašeho displeje.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Мӗлкесене дисплейӑн чи тӗттӗм хури ҫине ҫӗклет. 0: чӑн хура, ҫӗклевсӗр.",
"How bright highlights get. Auto: the peak your system reports.": "Мӗнле ҫутӑ ҫутӑсем пулаҫҫӗ. Авто: сирӗн системӑн пӗлтерекен пикӗ.",
"How bright white looks in the world. Auto: your desktop's white.": "Тӗнчере шурӑ мӗнле ҫутӑ курӑнать. Авто: эсир рабочий столӑн шурри.",
"How bright the HUD and menus are, against paper white.": "HUD тата менюсем мӗнле ҫутӑ, хут ҫинчи шурӑ тӗспе танлаштарсан.",
"How far mobs, crops and machines keep working. CPU time.": "Мобсем, культурӑсем тата машинӑсем мӗн таран ӗҫлеҫҫӗ. Процессор вӑхӑчӗ.",
"How far away mobs and items are drawn, as a share of normal.": "Мобсемпе япаласем мӗн чухлӗ аяккалла туртӑнаҫҫӗ, йӗркеллӗ пай пек.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Хупӑ участоксем хыҫӗнче пытаннӑ ҫӗре сиктерсе хӑварать. Офф нумай тӑкакланать.",
@@ -366,6 +367,7 @@
"Advanced": "Малалла",
"Undo": "Каялла тавӑр",
"Search options": "Шырав опцийӗсем",
"Shortcut": "Кӗске ҫул",
"Distance": "Инҫет",
"Display": "Дисплей",
"Interface": "Интерфейс",
@@ -436,9 +438,11 @@
"Calibrate...": "Калибровка...",
"Calibrate Anyway...": "Ҫапах та калибровка...",
"Use Automatic Setup": "Автоматла настройкӑпа усӑ курӑр",
"Shortcut: %s": "Кӗске ҫул: %s",
"Next": "Малалла",
"HDR Calibration %s/%s: %s": "HDR калибровка %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Аялта виҫҫӗшне тулли экран ҫине лартать, пӗрер тӗрӗслев ӳкерчӗкӗ.",
"A key that opens this screen from the game. Not bound at first.": "Вӑйӑран ҫак экрана уҫакан уҫҫи. Малтанах ҫыхӑнман.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Сирӗн дисплей ҫинче вӑл мӗнле ҫутӑ пулнине каламан: ӑна лартма калибровка тумалла.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Сирӗн дисплей валли хатӗрленӗ ӗнтӗ. HDR тӗрӗс мар курӑнсан ҫеҫ калибровка тӑвӑр.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Чи пысӑк ҫутӑлӑха тата хут шуррине сирӗн дисплей настройкине каялла тавӑрать.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Yn codi cysgodion i ddu tywyllaf eich arddangosfa. 0: du go iawn, dim codi.",
"How bright highlights get. Auto: the peak your system reports.": "Sut mae uchafbwyntiau disglair yn ei gael. Auto: yr uchafbwynt y mae eich system yn ei adrodd.",
"How bright white looks in the world. Auto: your desktop's white.": "Pa mor llachar y mae gwyn yn edrych yn y byd. Awtomatig: gwyn eich bwrdd gwaith.",
"How bright the HUD and menus are, against paper white.": "Pa mor llachar yw'r HUD a'r bwydlenni, yn erbyn papur gwyn.",
"How far mobs, crops and machines keep working. CPU time.": "Pa mor bell y mae mobs, cnydau a pheiriannau yn dal i weithio. Amser CPU.",
"How far away mobs and items are drawn, as a share of normal.": "Pa mor bell i ffwrdd mae mobs ac eitemau yn cael eu tynnu, fel cyfran o'r arferol.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Sgipiau tir wedi'i guddio y tu ôl i adrannau caeedig. Mae i ffwrdd yn costio llawer.",
@@ -366,6 +367,7 @@
"Advanced": "Uwch",
"Undo": "Dadwneud",
"Search options": "Dewisiadau chwilio",
"Shortcut": "Llwybr byr",
"Distance": "Pellter",
"Display": "Arddangos",
"Interface": "Rhyngwyneb",
@@ -436,9 +438,11 @@
"Calibrate...": "Graddnodi...",
"Calibrate Anyway...": "Calibro beth bynnag...",
"Use Automatic Setup": "Defnyddiwch Gosodiad Awtomatig",
"Shortcut: %s": "Llwybr byr: %s",
"Next": "Nesaf",
"HDR Calibration %s/%s: %s": "Graddnodi HDR %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Yn gosod y tri isod sgrin lawn, un llun prawf ar y tro.",
"A key that opens this screen from the game. Not bound at first.": "Allwedd sy'n agor y sgrin hon o'r gêm. Heb ei rwymo ar y dechrau.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Ni ddywedodd eich arddangosfa pa mor llachar y mae'n ei gael: graddnodwch i'w osod.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Eisoes wedi'i osod ar gyfer eich arddangosfa. Calibrowch dim ond os yw HDR yn edrych yn anghywir.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Yn rhoi Disgleirdeb Peak a Phapur Gwyn yn ôl i drefniant eich arddangosfa.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Løfter skygger op på skærmens mørkeste sort. 0: ægte sort, ingen løft.",
"How bright highlights get. Auto: the peak your system reports.": "Hvor lyse højdepunkter bliver. Auto: det højeste, dit system rapporterer.",
"How bright white looks in the world. Auto: your desktop's white.": "Hvor lyst hvid ser ud i verden. Auto: din skrivebords hvide.",
"How bright the HUD and menus are, against paper white.": "Hvor lys HUD og menuer er i forhold til hvidt papir.",
"How far mobs, crops and machines keep working. CPU time.": "Hvor langt mobs, afgrøder og maskiner bliver ved med at arbejde. CPU tid.",
"How far away mobs and items are drawn, as a share of normal.": "Hvor langt væk mobs og genstande trækkes, som en andel af normalen.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Springer over terræn skjult bag lukkede sektioner. Off koster meget.",
@@ -366,6 +367,7 @@
"Advanced": "Avanceret",
"Undo": "Fortryd",
"Search options": "Søgemuligheder",
"Shortcut": "Genvej",
"Distance": "Afstand",
"Display": "Skærm",
"Interface": "Grænseflade",
@@ -436,9 +438,11 @@
"Calibrate...": "Kalibrer...",
"Calibrate Anyway...": "Kalibrer alligevel...",
"Use Automatic Setup": "Brug automatisk opsætning",
"Shortcut: %s": "Genvej: %s",
"Next": "Næste",
"HDR Calibration %s/%s: %s": "HDR-kalibrering %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Indstiller de tre nedenfor i fuld skærm, et testbillede ad gangen.",
"A key that opens this screen from the game. Not bound at first.": "En tast, der åbner denne skærm fra spillet. Ikke bundet i starten.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Din skærm sagde ikke, hvor lys den bliver: Kalibrer for at konfigurere den.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Allerede konfigureret til din skærm. Kalibrer kun, hvis HDR ser forkert ud.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Sætter Peak Brightness og Paper White tilbage til din skærms opsætning.",
@@ -290,6 +290,7 @@
"Advanced": "Erweitert",
"Undo": "Rückgängig",
"Search options": "Einstellungen durchsuchen",
"Shortcut": "Tastenkürzel",
"Distance": "Entfernung",
"Display": "Anzeige",
"Interface": "Oberfläche",
@@ -439,6 +440,7 @@
"Sets the options below together. Changing one makes it Custom.": "Stellt Optionen darunter ein. Änderung = „Benutzerdefiniert“.",
"Lava, fire and sunlight shine brighter than white. Needs an HDR display.": "Lava, Feuer, Sonne heller als Weiß. Braucht HDR-Bildschirm.",
"How bright highlights get. Auto: the peak your system reports.": "Wie hell Lichter werden. Auto: der Spitzenwert, den dein System meldet.",
"How bright the HUD and menus are, against paper white.": "Wie hell HUD und Menüs im Vergleich zu Papierweiß sind.",
"How far mobs, crops and machines keep working. CPU time.": "Wie weit Kreaturen, Pflanzen und Maschinen weiterarbeiten. Kostet CPU-Zeit.",
"How far away mobs and items are drawn, as a share of normal.": "Zeichenweite für Kreaturen und Gegenstände, anteilig zur Norm.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Überspringt Gelände hinter geschlossenen Abschnitten. Aus kostet viel.",
@@ -517,9 +519,11 @@
"Calibrate...": "Kalibrieren …",
"Calibrate Anyway...": "Trotzdem kalibrieren …",
"Use Automatic Setup": "Automatische Einrichtung verwenden",
"Shortcut: %s": "Tastenkürzel: %s",
"Next": "Weiter",
"HDR Calibration %s/%s: %s": "HDR-Kalibrierung %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Stellt die drei Werte darunter im Vollbild ein, jeweils mit einem Testbild.",
"A key that opens this screen from the game. Not bound at first.": "Eine Taste, die diesen Bildschirm im Spiel öffnet. Zunächst nicht belegt.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Bildschirm meldete keine Helligkeit: zum Einrichten kalibrieren.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Bereits eingerichtet. Nur kalibrieren, wenn HDR falsch aussieht.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Stellt Spitzenhelligkeit und Papierweiß des Bildschirms wieder her.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Ανεβάζει τις σκιές στο πιο σκοτεινό μαύρο της οθόνης σας. 0: αληθινό μαύρο, χωρίς ανύψωση.",
"How bright highlights get. Auto: the peak your system reports.": "Πόσο φωτεινά σημεία αποκτούν. Auto: η κορύφωση που αναφέρει το σύστημά σας.",
"How bright white looks in the world. Auto: your desktop's white.": "Πόσο φωτεινό φαίνεται το λευκό στον κόσμο. Αυτόματο: το λευκό της επιφάνειας εργασίας σας.",
"How bright the HUD and menus are, against paper white.": "Πόσο φωτεινά είναι τα HUD και τα μενού, σε σχέση με το λευκό χαρτί.",
"How far mobs, crops and machines keep working. CPU time.": "Πόσο μακριά συνεχίζουν να λειτουργούν όχλοι, καλλιέργειες και μηχανές. Χρόνος CPU.",
"How far away mobs and items are drawn, as a share of normal.": "Πόσο μακριά κληρώνονται όχλοι και αντικείμενα, ως ποσοστό του κανονικού.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Παρακάμπτει το έδαφος που κρύβεται πίσω από κλειστά τμήματα. Το off κοστίζει πολύ.",
@@ -366,6 +367,7 @@
"Advanced": "Προχωρημένο",
"Undo": "Αναίρεση",
"Search options": "Επιλογές αναζήτησης",
"Shortcut": "Συντόμευση",
"Distance": "Απόσταση",
"Display": "Εμφάνιση",
"Interface": "Διασύνδεση",
@@ -436,9 +438,11 @@
"Calibrate...": "Βαθμονόμηση...",
"Calibrate Anyway...": "Βαθμονόμηση πάντως...",
"Use Automatic Setup": "Χρησιμοποιήστε την Αυτόματη Ρύθμιση",
"Shortcut: %s": "Συντόμευση: %s",
"Next": "Επόμενο",
"HDR Calibration %s/%s: %s": "Βαθμονόμηση HDR %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Ορίζει τις τρεις παρακάτω πλήρη οθόνη, μία δοκιμαστική εικόνα τη φορά.",
"A key that opens this screen from the game. Not bound at first.": "Ένα κλειδί που ανοίγει αυτήν την οθόνη από το παιχνίδι. Δεν δεσμεύεται στην αρχή.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Η οθόνη σας δεν έλεγε πόσο φωτεινή γίνεται: βαθμονομήστε για να τη ρυθμίσετε.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Έχει ήδη ρυθμιστεί για την οθόνη σας. Βαθμονόμηση μόνο εάν το HDR φαίνεται λάθος.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Επαναφέρει το Peak Brightness και το Paper White στη ρύθμιση της οθόνης σας.",
@@ -310,7 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Lifts shadows onto your display's darkest black. 0: true black, no lift.",
"How bright highlights get. Auto: the peak your system reports.": "How bright highlights get. Auto: the peak your system reports.",
"How bright white looks in the world. Auto: your desktop's white.": "How bright white looks in the world. Auto: your desktop's white.",
"How bright the HUD and menus are. Auto: at paper white.": "How bright the HUD and menus are. Auto: at paper white.",
"How bright the HUD and menus are, against paper white.": "How bright the HUD and menus are, against paper white.",
"How far mobs, crops and machines keep working. CPU time.": "How far mobs, crops and machines keep working. CPU time.",
"How far away mobs and items are drawn, as a share of normal.": "How far away mobs and items are drawn, as a share of normal.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Skips terrain hidden behind closed sections. Off costs a lot.",
@@ -367,6 +367,7 @@
"Advanced": "Advanced",
"Undo": "Undo",
"Search options": "Search options",
"Shortcut": "Shortcut",
"Distance": "Distance",
"Display": "Display",
"Interface": "Interface",
@@ -437,11 +438,12 @@
"Calibrate...": "Calibrate...",
"Calibrate Anyway...": "Calibrate Anyway...",
"Use Automatic Setup": "Use Automatic Setup",
"Shortcut: %s": "Shortcut: %s",
"Next": "Next",
"HDR Calibration %s/%s: %s": "HDR Calibration %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Sets the three below full screen, one test picture at a time.",
"A key that opens this screen from the game. Not bound at first.": "A key that opens this screen from the game. Not bound at first.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Your display didn't say how bright it gets: calibrate to set it up.",
"Your display's reported peak is often too high: calibrate to set it up.": "Your display's reported peak is often too high: calibrate to set it up.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Already set up for your display. Only calibrate if HDR looks wrong.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Puts Peak Brightness and Paper White back to your display's setup.",
"Set up for your display. Leave on Auto unless it looks wrong.": "Set up for your display. Leave on Auto unless it looks wrong.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Levas ombrojn al la plej malhela nigro de via ekrano. 0: vera nigro, sen levo.",
"How bright highlights get. Auto: the peak your system reports.": "Kiel brilaj brilaĵoj ricevas. Aŭtomata: la pinto kiun via sistemo raportas.",
"How bright white looks in the world. Auto: your desktop's white.": "Kiom hela blanko aspektas en la mondo. Aŭtomate: la blanko de via labortablo.",
"How bright the HUD and menus are, against paper white.": "Kiel brilaj la HUD kaj menuoj estas, kontraŭ papera blanka.",
"How far mobs, crops and machines keep working. CPU time.": "Kiom malproksime amasoj, kultivaĵoj kaj maŝinoj daŭre funkcias. CPU-tempo.",
"How far away mobs and items are drawn, as a share of normal.": "Kiom malproksime amasoj kaj eroj estas tiritaj, kiel parto de normalo.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Saltas terenon kaŝitan malantaŭ fermitaj sekcioj. For kostas multe.",
@@ -366,6 +367,7 @@
"Advanced": "Altnivela",
"Undo": "Malfari",
"Search options": "Serĉaj opcioj",
"Shortcut": "Ŝparvojo",
"Distance": "Distanco",
"Display": "Montru",
"Interface": "Interfaco",
@@ -436,9 +438,11 @@
"Calibrate...": "Kalibri...",
"Calibrate Anyway...": "Kalibru ĉiuokaze...",
"Use Automatic Setup": "Uzu Aŭtomatan Agordon",
"Shortcut: %s": "Ŝparvojo: %s",
"Next": "Poste",
"HDR Calibration %s/%s: %s": "HDR-Kalibrado %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Agordas la tri sub plenan ekranon, unu testan bildon samtempe.",
"A key that opens this screen from the game. Not bound at first.": "Ŝlosilo, kiu malfermas ĉi tiun ekranon de la ludo. Komence ne ligita.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Via ekrano ne diris kiom brilas ĝi fariĝas: kalibru por agordi ĝin.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Jam agordita por via ekrano. Nur kalibru se HDR aspektas malĝusta.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Remetas Pintan Brilon kaj Paperan Blankan al la agordo de via ekrano.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Eleva las sombras hasta el negro más oscuro de tu pantalla. 0: negro puro, sin elevación.",
"How bright highlights get. Auto: the peak your system reports.": "Cuánto brillan los reflejos. Automático: el pico que informa tu sistema.",
"How bright white looks in the world. Auto: your desktop's white.": "Qué tan brillante se ve el blanco en el mundo. Automático: el blanco de tu escritorio.",
"How bright the HUD and menus are, against paper white.": "Qué tan brillantes son el HUD y los menús respecto al blanco de papel.",
"How far mobs, crops and machines keep working. CPU time.": "Hasta dónde siguen funcionando mobs, cultivos y máquinas. Tiempo de CPU.",
"How far away mobs and items are drawn, as a share of normal.": "A qué distancia se dibujan mobs y objetos, como proporción de lo normal.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Omite el terreno oculto tras secciones cerradas. Desactivarlo cuesta mucho.",
@@ -366,6 +367,7 @@
"Advanced": "Avanzado",
"Undo": "Deshacer",
"Search options": "Buscar opciones",
"Shortcut": "Atajo",
"Distance": "Distancia",
"Display": "Pantalla",
"Interface": "Interfaz",
@@ -436,9 +438,11 @@
"Calibrate...": "Calibrar...",
"Calibrate Anyway...": "Calibrar de todos modos...",
"Use Automatic Setup": "Usar la configuración automática",
"Shortcut: %s": "Atajo: %s",
"Next": "Siguiente",
"HDR Calibration %s/%s: %s": "Calibración HDR %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Configura las tres opciones siguientes a pantalla completa, con una imagen de prueba cada vez.",
"A key that opens this screen from the game. Not bound at first.": "Una tecla que abre esta pantalla desde el juego. Sin asignar al principio.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Tu pantalla no indicó cuánto brilla: calíbrala para configurarla.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Ya está configurado para tu pantalla. Calibra solo si el HDR se ve mal.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Devuelve Brillo máximo y Blanco de papel a la configuración de tu pantalla.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Tõstab varjud ekraani tumedaima mustani. 0: tõeline must, ilma tõstmiseta.",
"How bright highlights get. Auto: the peak your system reports.": "Kui eredad esiletõstud saavad. Automaatne: tipp, millest süsteem teatab.",
"How bright white looks in the world. Auto: your desktop's white.": "Kui hele valge maailmas paistab. Automaatne: sinu töölaua valge.",
"How bright the HUD and menus are, against paper white.": "Kui eredad on HUD ja menüüd pabervalge taustal.",
"How far mobs, crops and machines keep working. CPU time.": "Kui kaugele mobid, põllukultuurid ja masinad töötavad. CPU aeg.",
"How far away mobs and items are drawn, as a share of normal.": "Kui kaugele mobid ja esemed tõmmatakse, kui osa normaalsest.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Jätab vahele suletud lõikude taha peidetud maastiku. Off maksab palju.",
@@ -366,6 +367,7 @@
"Advanced": "Täpsemalt",
"Undo": "Võta tagasi",
"Search options": "Otsinguvalikud",
"Shortcut": "Otsetee",
"Distance": "Kaugus",
"Display": "Ekraan",
"Interface": "Liides",
@@ -436,9 +438,11 @@
"Calibrate...": "Kalibreeri...",
"Calibrate Anyway...": "Kalibreerige ikkagi...",
"Use Automatic Setup": "Kasutage automaatset seadistust",
"Shortcut: %s": "Otsetee: %s",
"Next": "Edasi",
"HDR Calibration %s/%s: %s": "HDR-kalibreerimine %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Määrab kolm allolevat täisekraani, üks testpilt korraga.",
"A key that opens this screen from the game. Not bound at first.": "Klahv, mis avab mängust selle ekraani. Alguses ei köideta.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Teie ekraan ei öelnud, kui hele see muutub: seadistamiseks kalibreerige.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Teie kuva jaoks on juba seadistatud. Kalibreerige ainult siis, kui HDR tundub vale.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Tagab maksimaalse ereduse ja paberivalgeduse teie ekraani seadistustesse.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Itzalak zure pantailaren beltzik ilunenera igotzen ditu. 0: benetako beltza, igoerarik gabe.",
"How bright highlights get. Auto: the peak your system reports.": "Nola distiratsuak diren argiak. Auto: zure sistemak ematen duen gailurra.",
"How bright white looks in the world. Auto: your desktop's white.": "Zenbat distiratzen duen zuriak munduan. Autoa: zure mahaigaineko zuria.",
"How bright the HUD and menus are, against paper white.": "Zein distiratsuak diren HUD eta menuak, paper zuriaren aurka.",
"How far mobs, crops and machines keep working. CPU time.": "Noraino jarraitzen duten mafiak, laboreak eta makinak lanean. CPU denbora.",
"How far away mobs and items are drawn, as a share of normal.": "Noraino ateratzen diren mobs eta elementuak, normalaren zati gisa.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Sekzio itxien atzean ezkutatuta dagoen lurra saltatzen du. Off asko kostatzen da.",
@@ -366,6 +367,7 @@
"Advanced": "Aurreratua",
"Undo": "Desegin",
"Search options": "Bilaketa aukerak",
"Shortcut": "Lasterbidea",
"Distance": "Distantzia",
"Display": "Bistaratzea",
"Interface": "Interfazea",
@@ -436,9 +438,11 @@
"Calibrate...": "Kalibratu...",
"Calibrate Anyway...": "Kalibratu Dena den...",
"Use Automatic Setup": "Erabili konfigurazio automatikoa",
"Shortcut: %s": "Lasterbidea: %s",
"Next": "Hurrengoa",
"HDR Calibration %s/%s: %s": "HDR Kalibrazioa %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Pantaila osoko azpian dauden hiru ezartzen ditu, probako argazki bat aldi berean.",
"A key that opens this screen from the game. Not bound at first.": "Pantaila hau jokotik irekitzen duen giltza. Hasieran lotu gabe.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Pantailak ez du esan zenbat argi dagoen: kalibratu konfiguratzeko.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Dagoeneko konfiguratuta zaude zure pantailarako. Kalibratu HDR okerra bada bakarrik.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Distira gailurra eta Paper Zuria berriro jartzen ditu pantailaren konfiguraziora.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "سایه‌ها را به تاریک‌ترین سیاه نمایشگر شما می‌رساند. 0: سیاه واقعی، بدون بالا بردن.",
"How bright highlights get. Auto: the peak your system reports.": "هایلایت ها چقدر روشن می شوند. خودکار: اوج گزارش سیستم شما.",
"How bright white looks in the world. Auto: your desktop's white.": "سفیدی در دنیا چقدر روشن دیده می‌شود. خودکار: سفیدی میزکار شما.",
"How bright the HUD and menus are, against paper white.": "HUD و منوها در برابر سفید کاغذ چقدر روشن هستند.",
"How far mobs, crops and machines keep working. CPU time.": "اوباش، محصولات زراعی و ماشین‌آلات تا کجا به کار خود ادامه می‌دهند. زمان CPU",
"How far away mobs and items are drawn, as a share of normal.": "اوباش و اقلام چقدر دور کشیده شده اند، به عنوان یک سهم عادی.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "از زمین های پنهان شده در پشت بخش های بسته می گذرد. هزینه های خاموش بسیار زیاد است.",
@@ -366,6 +367,7 @@
"Advanced": "پیشرفته",
"Undo": "واگرد",
"Search options": "گزینه های جستجو",
"Shortcut": "میانبر",
"Distance": "فاصله",
"Display": "نمایش",
"Interface": "رابط",
@@ -436,9 +438,11 @@
"Calibrate...": "کالیبره کردن...",
"Calibrate Anyway...": "به هر حال کالیبره کردن...",
"Use Automatic Setup": "از تنظیم خودکار استفاده کنید",
"Shortcut: %s": "میانبر: %s",
"Next": "بعدی",
"HDR Calibration %s/%s: %s": "کالیبراسیون HDR %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "سه عکس زیر تمام صفحه را در یک زمان یک عکس آزمایشی تنظیم می کند.",
"A key that opens this screen from the game. Not bound at first.": "کلیدی که این صفحه را از بازی باز می کند. ابتدا مقید نیست.",
"Your display didn't say how bright it gets: calibrate to set it up.": "صفحه نمایش شما میزان روشنایی آن را بیان نکرده است: برای تنظیم آن کالیبره کنید.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "قبلاً برای نمایشگر شما تنظیم شده است. فقط در صورتی کالیبره کنید که HDR اشتباه به نظر برسد.",
"Puts Peak Brightness and Paper White back to your display's setup.": "حداکثر روشنایی و کاغذ سفید را به تنظیمات صفحه نمایش شما باز می گرداند.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Nostaa varjot näyttösi tummimpaan mustaan. 0: aito musta, ei nostoa.",
"How bright highlights get. Auto: the peak your system reports.": "Kuinka kirkkaat kohokohdat saavat. Auto: huippu, jonka järjestelmäsi raportoi.",
"How bright white looks in the world. Auto: your desktop's white.": "Kuinka kirkkaalta valkoinen näyttää maailmassa. Automaattinen: työpöytäsi valkoinen.",
"How bright the HUD and menus are, against paper white.": "Kuinka kirkkaita HUD ja valikot ovat paperinvalkoisia vastaan.",
"How far mobs, crops and machines keep working. CPU time.": "Kuinka pitkälle väkijoukot, viljat ja koneet toimivat. CPU-aika.",
"How far away mobs and items are drawn, as a share of normal.": "Kuinka kauas väkijoukot ja esineet piirretään, osuus normaalista.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Ohittaa suljettujen osien taakse piilotetun maaston. Poisto maksaa paljon.",
@@ -366,6 +367,7 @@
"Advanced": "Edistynyt",
"Undo": "Kumoa",
"Search options": "Hakuvaihtoehdot",
"Shortcut": "Pikakuvake",
"Distance": "Etäisyys",
"Display": "Näyttö",
"Interface": "Käyttöliittymä",
@@ -436,9 +438,11 @@
"Calibrate...": "Kalibroi...",
"Calibrate Anyway...": "Kalibroi joka tapauksessa...",
"Use Automatic Setup": "Käytä automaattista asetusta",
"Shortcut: %s": "Pikanäppäin: %s",
"Next": "Seuraavaksi",
"HDR Calibration %s/%s: %s": "HDR-kalibrointi %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Asettaa kolme koko näytön alapuolelle yhden testikuvan kerrallaan.",
"A key that opens this screen from the game. Not bound at first.": "Avain, joka avaa tämän näytön pelistä. Ei aluksi sidottu.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Näyttösi ei ilmoittanut, kuinka kirkkaaksi se tulee: kalibroi se määrittääksesi sen.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Näyttöäsi varten on jo määritetty. Kalibroi vain, jos HDR näyttää väärältä.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Palauttaa huippukirkkauden ja paperin valkoisen näytön asetuksiin.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Iniaangat ang mga anino sa pinakamadilim na itim ng iyong display. 0: totoong itim, walang angat.",
"How bright highlights get. Auto: the peak your system reports.": "Kung gaano kaliwanag ang mga highlight. Auto: ang pinakamataas na ulat ng iyong system.",
"How bright white looks in the world. Auto: your desktop's white.": "Gaano kaliwanag ang puti sa mundo. Auto: ang puti ng iyong desktop.",
"How bright the HUD and menus are, against paper white.": "Gaano kaliwanag ang HUD at mga menu, laban sa puting papel.",
"How far mobs, crops and machines keep working. CPU time.": "Hanggang saan patuloy na gumagana ang mga mandurumog, pananim at makina. oras ng CPU.",
"How far away mobs and items are drawn, as a share of normal.": "Kung gaano kalayo ang mga mandurumog at mga item ay iginuhit, bilang bahagi ng normal.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Nilalaktawan ang lupain na nakatago sa likod ng mga saradong seksyon. Malaki ang gastos sa off.",
@@ -366,6 +367,7 @@
"Advanced": "Advanced",
"Undo": "I-undo",
"Search options": "Mga opsyon sa paghahanap",
"Shortcut": "Shortcut",
"Distance": "Distansya",
"Display": "Pagpapakita",
"Interface": "Interface",
@@ -436,9 +438,11 @@
"Calibrate...": "I-calibrate...",
"Calibrate Anyway...": "Mag-calibrate pa rin...",
"Use Automatic Setup": "Gamitin ang Awtomatikong Pag-setup",
"Shortcut: %s": "Shortcut: %s",
"Next": "Susunod",
"HDR Calibration %s/%s: %s": "Pag-calibrate ng HDR %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Itinatakda ang tatlo sa ibaba ng full screen, isang pagsubok na larawan sa isang pagkakataon.",
"A key that opens this screen from the game. Not bound at first.": "Isang susi na nagbubukas ng screen na ito mula sa laro. Hindi nakatali sa una.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Hindi sinabi ng iyong display kung gaano ito kaliwanag: i-calibrate para i-set up ito.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Naka-set up na para sa iyong display. I-calibrate lang kung mukhang mali ang HDR.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Ibinabalik ang Peak Brightness at Paper White sa setup ng iyong display.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Lyftir skuggum upp á myrkasta svart á tínum skjá. 0: veruligt svart, ongin lyfting.",
"How bright highlights get. Auto: the peak your system reports.": "Hvussu bjart hæddarpunkt fáa. Sjálvvirkandi: toppurin, sum tín skipan greiðir frá.",
"How bright white looks in the world. Auto: your desktop's white.": "Hvussu bjart hvítt sær út í heiminum. Sjálvvirkandi: hvítt á tínum skriviborði.",
"How bright the HUD and menus are, against paper white.": "Hvussu ljós HUD og valmyndir eru, móti pappírshvítum.",
"How far mobs, crops and machines keep working. CPU time.": "Hvussu langt mobbar, grøðir og maskinur halda fram at virka. CPU tíð.",
"How far away mobs and items are drawn, as a share of normal.": "Hvussu langt burtur mobbar og lutir eru drignir, sum partur av vanligum.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Sleppur lendi goymt aftan fyri stongdar partar. Av kostar nógv.",
@@ -366,6 +367,7 @@
"Advanced": "Framkomin",
"Undo": "Angra",
"Search options": "Leitimøguleikar",
"Shortcut": "Snarvegur",
"Distance": "Fjarstøða",
"Display": "Sýn",
"Interface": "Grunnflata",
@@ -436,9 +438,11 @@
"Calibrate...": "Kalibrera...",
"Calibrate Anyway...": "Kalibrera allíkavæl...",
"Use Automatic Setup": "Brúka sjálvvirkandi uppseting",
"Shortcut: %s": "Snarvegur: %s",
"Next": "Næsta",
"HDR Calibration %s/%s: %s": "HDR-kalibrering %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Setur tey trý niðanfyri fullan skerm, eina royndarmynd í senn.",
"A key that opens this screen from the game. Not bound at first.": "Ein lykil, sum letur upp hendan skermin frá spælinum. Ikki bundin í fyrstuni.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Títt skjálvti segði ikki, hvussu ljóst tað blívur: kalibrera fyri at seta tað upp.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Longu sett upp til tín skjálvta. Kalibrera bert um HDR sær skeivt út.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Setur Toppljósstyrki og Pappírshvítt aftur til uppsetingina á skjánum.",
@@ -332,6 +332,7 @@
"Sets the options below together. Changing one makes it Custom.": "Règle les options ci-dessous. Modifier une : « Personnalisé ».",
"Lava, fire and sunlight shine brighter than white. Needs an HDR display.": "Lave, feu et soleil plus vifs que le blanc. Écran HDR requis.",
"How bright highlights get. Auto: the peak your system reports.": "La luminosité des hautes lumières. Auto : le pic indiqué par votre système.",
"How bright the HUD and menus are, against paper white.": "La luminosité du HUD et des menus, par rapport au blanc papier.",
"How far mobs, crops and machines keep working. CPU time.": "Portée d'activité des créatures, cultures, machines. Coût : CPU.",
"How far away mobs and items are drawn, as a share of normal.": "Distance des créatures et objets, en part de la normale.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Ignore le terrain caché. Désactiver coûte cher.",
@@ -406,9 +407,11 @@
"Calibrate...": "Calibrer...",
"Calibrate Anyway...": "Calibrer quand même...",
"Use Automatic Setup": "Utiliser la configuration automatique",
"Shortcut: %s": "Raccourci : %s",
"Next": "Suivant",
"HDR Calibration %s/%s: %s": "Calibrage HDR %s/%s : %s",
"Sets the three below full screen, one test picture at a time.": "Règle les trois valeurs ci-dessous en plein écran, une image test à la fois.",
"A key that opens this screen from the game. Not bound at first.": "Une touche qui ouvre cet écran depuis le jeu. Non assignée au départ.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Votre écran n'a pas indiqué sa luminosité : calibrez-le.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Déjà configuré. Calibrez seulement si le HDR semble incorrect.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Rétablit Luminosité max. et Point blanc de votre écran.",
@@ -672,6 +675,7 @@
"Advanced": "Avancé",
"Undo": "Annuler",
"Search options": "Rechercher des options",
"Shortcut": "Raccourci",
"Sky and weather": "Ciel et météo",
"Lighting and entities": "Éclairage et entités",
"Chunk loading": "Chargement des tronçons",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Al alce lis ombris sul neri plui scur dal to visôr. 0: neri vêr, cence alçâ.",
"How bright highlights get. Auto: the peak your system reports.": "Cuant che i ponts clârs a deventin luminosis. Auto: il massim dai segnalazions dal sisteme.",
"How bright white looks in the world. Auto: your desktop's white.": "Cuant che al somee lûs il blanc tal mont. Automatic: il blanc dal to scritori.",
"How bright the HUD and menus are, against paper white.": "Cuant che a son luminosis il HUD e i menus, cuintri dal blanc di cjarte.",
"How far mobs, crops and machines keep working. CPU time.": "Fin dulà che lis mobis, lis coltivazions e lis machinis a continuin a lavorâ. Timp dal CPU.",
"How far away mobs and items are drawn, as a share of normal.": "Cuant lontan che a son tirâts fûr lis mobis e i ogjets, come part dal normâl.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Salte il teren scuindût daûr di sezions sieradis. Off al coste une vore.",
@@ -366,6 +367,7 @@
"Advanced": "Avanzade",
"Undo": "Annulâ",
"Search options": "Opzions di ricercje",
"Shortcut": "Scurtis",
"Distance": "Distance",
"Display": "Visualizazion",
"Interface": "Interface",
@@ -436,9 +438,11 @@
"Calibrate...": "Calibrâ...",
"Calibrate Anyway...": "Calibrâ in ogni câs...",
"Use Automatic Setup": "Utilizâ la configurazion automatiche",
"Shortcut: %s": "Scurtis: %s",
"Next": "Sucessîf",
"HDR Calibration %s/%s: %s": "Taradure HDR %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Imposta i trê sot dal schermi intîr, une imagjin di prove par volte.",
"A key that opens this screen from the game. Not bound at first.": "Une clâf che e vierç chest schermi dal zûc. No leât tal imprin.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Il display nol diseve cetant che al devente luminôs: calibrâlu par configurâlu.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Za configurât pal to display. Calibrâ dome se il HDR al somee sbaglât.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Al torne a meti la luminositât di massime e il blanc di cjarte inte configurazion dal display.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Tilt skaad op nei it tsjûkste swart fan jo display. 0: echt swart, gjin oplichting.",
"How bright highlights get. Auto: the peak your system reports.": "Hoe ljochte hichtepunten krije. Auto: de pyk dy't jo systeem rapportearret.",
"How bright white looks in the world. Auto: your desktop's white.": "Hoe helder wyt yn 'e wrâld liket. Auto: it wyt fan jo buroblêd.",
"How bright the HUD and menus are, against paper white.": "Hoe helder de HUD en menu's binne, tsjin papierwyt.",
"How far mobs, crops and machines keep working. CPU time.": "Hoe fier mobs, gewaaksen en masines bliuwe wurkjen. CPU tiid.",
"How far away mobs and items are drawn, as a share of normal.": "Hoe fier fuort mobs en items wurde lutsen, as in oandiel fan normale.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Skips terrein ferburgen efter sletten seksjes. Off kostet in soad.",
@@ -366,6 +367,7 @@
"Advanced": "Avansearre",
"Undo": "Ungedien meitsje",
"Search options": "Sykje opsjes",
"Shortcut": "Fluchtoets",
"Distance": "Ofstân",
"Display": "Byldskerm",
"Interface": "Ynterface",
@@ -436,9 +438,11 @@
"Calibrate...": "Kalibrearje...",
"Calibrate Anyway...": "Kalibrearje dochs...",
"Use Automatic Setup": "Brûk Automatyske opset",
"Shortcut: %s": "Fluchtoets: %s",
"Next": "Folgjende",
"HDR Calibration %s/%s: %s": "HDR-kalibraasje %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Stelt de trije hjirûnder folslein skerm, ien testfoto tagelyk.",
"A key that opens this screen from the game. Not bound at first.": "In kaai dy't iepenet dit skerm út it spul. Earst net bûn.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Jo display sei net hoe helder it wurdt: kalibrearje om it yn te stellen.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Al ynsteld foar jo display. Kalibrearje allinich as HDR ferkeard sjocht.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Set Peak Brightness en Paper White werom nei de opset fan jo display.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Ardaíonn scáthanna go dtí an dubh is dorcha ar do thaispeántas. 0: fíordhubh, gan ardú.",
"How bright highlights get. Auto: the peak your system reports.": "Conas a fhaigheann buaicphointí geala. Auto: an bhuaic tuairiscí do chórais.",
"How bright white looks in the world. Auto: your desktop's white.": "Cé chomh geal is a fheictear bán sa domhan. Uathoibríoch: bán do dheisce.",
"How bright the HUD and menus are, against paper white.": "Cé chomh geal is atá an HUD agus na biachláir, i gcoinne bán páipéir.",
"How far mobs, crops and machines keep working. CPU time.": "Cé chomh fada agus a leanann mobs, barraí agus meaisíní ag obair. Am LAP.",
"How far away mobs and items are drawn, as a share of normal.": "Cé chomh fada uaidh a tharraingítear mobs agus míreanna, mar sciar den ghnáth.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Scipeanna tír-raon i bhfolach taobh thiar codanna dúnta. Lasmuigh costais go leor.",
@@ -366,6 +367,7 @@
"Advanced": "Casta",
"Undo": "Cealaigh",
"Search options": "Roghanna cuardaigh",
"Shortcut": "aicearra",
"Distance": "Fad",
"Display": "Taispeáint",
"Interface": "Comhéadan",
@@ -436,9 +438,11 @@
"Calibrate...": "Calabraigh...",
"Calibrate Anyway...": "Calabraigh Ar aon nós...",
"Use Automatic Setup": "Úsáid Socrú Uathoibríoch",
"Shortcut: %s": "Aicearra: %s",
"Next": "Ar aghaidh",
"HDR Calibration %s/%s: %s": "Calabrú HDR %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Socraíonn sé na trí thíos scáileán iomlán, pictiúr tástála amháin ag an am.",
"A key that opens this screen from the game. Not bound at first.": "Eochair a osclaíonn an scáileán seo ón gcluiche. Gan cheangal ar dtús.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Níor dúirt do thaispeántas cé chomh geal agus atá sé: calabraigh chun é a shocrú.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Socraithe cheana féin le haghaidh do thaispeántais. Ná calabraigh ach amháin má tá cuma mícheart ar HDR.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Cuireann Buaic Ghile agus Páipéar Bán ar ais go dtí socrú do thaispeántais.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "A' togail fhaileasan gu dubh as dorcha an taisbeanaidh agad. 0: fìor dhubh, gun togail.",
"How bright highlights get. Auto: the peak your system reports.": "Mar a gheibh seallaidhean soilleir. Auto: an ìre as àirde a tha an siostam agad ag aithris.",
"How bright white looks in the world. Auto: your desktop's white.": "Dè cho soilleir 's a tha geal a' coimhead san t-saoghal. Auto: geal an deasga agad.",
"How bright the HUD and menus are, against paper white.": "Dè cho soilleir sa tha an HUD agus na clàran-bìdh, an aghaidh pàipear geal.",
"How far mobs, crops and machines keep working. CPU time.": "Dè cho fada ‘s a chumas mobs, bàrr agus innealan ag obair. Uair CPU.",
"How far away mobs and items are drawn, as a share of normal.": "Dè cho fada air falbh tha mobs agus nithean air an tarraing, mar chuibhreann den àbhaist.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Sgiobaidhean talmhainn falaichte air cùl earrannan dùinte. Cosgaidh dheth tòrr.",
@@ -366,6 +367,7 @@
"Advanced": "Adhartach",
"Undo": "Neo-dhèanamh",
"Search options": "Rannsaich roghainnean",
"Shortcut": "Ath-ghoirid",
"Distance": "Astar",
"Display": "Taisbeanadh",
"Interface": "Eadar-aghaidh",
@@ -436,9 +438,11 @@
"Calibrate...": "Calibraich...",
"Calibrate Anyway...": "Calibrate co-dhiù...",
"Use Automatic Setup": "Cleachd suidheachadh fèin-ghluasadach",
"Shortcut: %s": "Ath-ghoirid: %s",
"Next": "Air adhart",
"HDR Calibration %s/%s: %s": "Calibration HDR %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Suidhich na trì gu h-ìosal làn-sgrìn, aon dealbh deuchainn aig aon àm.",
"A key that opens this screen from the game. Not bound at first.": "Iuchair a dh'fhosglas an sgrìn seo bhon gheama. Gun a bhith ceangailte an toiseach.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Cha d’ thuirt an taisbeanadh agad cho soilleir ‘s a gheibh e: calibrate gus a rèiteachadh.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Air a chuir air dòigh mu thràth airson do thaisbeanadh. Na dèan calibration ach ma tha HDR a’ coimhead ceàrr.",
"Puts Peak Brightness and Paper White back to your display's setup.": "A’ cur Peak Brightness agus Paper White air ais gu suidheachadh an taisbeanaidh agad.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Eleva as sombras ata o negro máis escuro da túa pantalla. 0: negro puro, sen elevación.",
"How bright highlights get. Auto: the peak your system reports.": "Como se fan os reflejos brillantes. Automático: o pico que informa o seu sistema.",
"How bright white looks in the world. Auto: your desktop's white.": "Que brillante se ve o branco no mundo. Automático: o branco do teu escritorio.",
"How bright the HUD and menus are, against paper white.": "Que brillantes son o HUD e os menús, contra o branco do papel.",
"How far mobs, crops and machines keep working. CPU time.": "Ata onde seguen traballando as turbas, os cultivos e as máquinas. Tempo de CPU.",
"How far away mobs and items are drawn, as a share of normal.": "A que distancia se debuxan as turbas e os elementos, como parte do normal.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Salta o terreo escondido detrás de seccións pechadas. Off custa moito.",
@@ -366,6 +367,7 @@
"Advanced": "Avanzado",
"Undo": "Desfacer",
"Search options": "Opcións de busca",
"Shortcut": "Atallo",
"Distance": "Distancia",
"Display": "Mostrar",
"Interface": "Interface",
@@ -436,9 +438,11 @@
"Calibrate...": "Calibrar...",
"Calibrate Anyway...": "Calibrar de todos os xeitos...",
"Use Automatic Setup": "Use a configuración automática",
"Shortcut: %s": "Atallo: %s",
"Next": "A continuación",
"HDR Calibration %s/%s: %s": "Calibración HDR %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Establece os tres debaixo da pantalla completa, unha imaxe de proba á vez.",
"A key that opens this screen from the game. Not bound at first.": "Unha tecla que abre esta pantalla desde o xogo. Non atado ao principio.",
"Your display didn't say how bright it gets: calibrate to set it up.": "A túa pantalla non indica o brillo que ten: calíbraa para configurala.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Xa estás configurado para a túa pantalla. Calibra só se o HDR parece incorrecto.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Devolve o brillo máximo e o branco de papel á configuración da pantalla.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Hāpai i nā aka i ka ʻeleʻele loa o kāu hōʻike. 0: ʻeleʻele maoli, ʻaʻohe hāpai.",
"How bright highlights get. Auto: the peak your system reports.": "Pehea e loaʻa ai nā hiʻohiʻona nani. Auto: ke kiʻekiʻe o kāu hōʻike pūnaewele.",
"How bright white looks in the world. Auto: your desktop's white.": "Pehea ke keʻokeʻo o ka honua. Akomi: ke keʻokeʻo o kāu papa hana.",
"How bright the HUD and menus are, against paper white.": "Pehea ka mālamalama o ka HUD a me nā menus, e kūʻē i ka pepa keʻokeʻo.",
"How far mobs, crops and machines keep working. CPU time.": "Pehea ka lōʻihi o ka hana ʻana o nā kānaka, nā mea kanu a me nā mīkini. manawa CPU.",
"How far away mobs and items are drawn, as a share of normal.": "Pehea ka lōʻihi o ka huki ʻia ʻana o nā poʻe pōʻino a me nā mea, ma ke ʻano he mea maʻamau.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Holo i ka ʻāina huna ma hope o nā ʻāpana pani. He nui ke kumu kūʻai.",
@@ -366,6 +367,7 @@
"Advanced": "Kiʻekiʻe",
"Undo": "Wehe",
"Search options": "Nā koho huli",
"Shortcut": "Pōkole",
"Distance": "mamao",
"Display": "Hōʻike",
"Interface": "Ikepili",
@@ -436,9 +438,11 @@
"Calibrate...": "Hoʻoponopono ʻia...",
"Calibrate Anyway...": "E hoʻokaʻawale i kēlā me kēia...",
"Use Automatic Setup": "E hoʻohana i ka hoʻonohonoho ʻakomi",
"Shortcut: %s": "Pōkole: %s",
"Next": "Aʻe",
"HDR Calibration %s/%s: %s": "Hoʻoponopono HDR %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Hoʻonohonoho i nā ʻekolu ma lalo o ka pale piha, hoʻokahi kiʻi hoʻāʻo i ka manawa.",
"A key that opens this screen from the game. Not bound at first.": "He kī e wehe ana i kēia pale mai ka pāʻani. ʻAʻole paʻa i ka wā mua.",
"Your display didn't say how bright it gets: calibrate to set it up.": "ʻAʻole i haʻi mai kāu hōʻike i ke ʻano o kona ʻālohilohi: calibrate e hoʻonohonoho iā ia.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Ua hoʻonohonoho ʻia no kāu hōʻike. Hoʻopili wale inā ʻike hewa ka HDR.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Hoʻihoʻi i ka Peak Brightness a me ka Pepa Keʻokeʻo i ka hoʻonohonoho o kāu hōʻike.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "מרים צללים אל השחור הכהה ביותר של הצג שלך. 0: שחור אמיתי, ללא הרמה.",
"How bright highlights get. Auto: the peak your system reports.": "כמה מדגישים בהירים מקבלים. אוטומטי: השיא שהמערכת שלך מדווחת עליו.",
"How bright white looks in the world. Auto: your desktop's white.": "כמה בהיר נראה לבן בעולם. אוטומטי: הלבן של שולחן העבודה שלך.",
"How bright the HUD and menus are, against paper white.": "כמה בהירים ה-HUD והתפריטים, מול נייר לבן.",
"How far mobs, crops and machines keep working. CPU time.": "עד כמה המון, יבולים ומכונות ממשיכים לעבוד. זמן מעבד.",
"How far away mobs and items are drawn, as a share of normal.": "כמה רחוק נמשכים אספסוף ופריטים, כחלק מהרגיל.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "מדלג על שטח מוסתר מאחורי קטעים סגורים. חופש עולה הרבה.",
@@ -366,6 +367,7 @@
"Advanced": "מתקדם",
"Undo": "בטל",
"Search options": "אפשרויות חיפוש",
"Shortcut": "קיצור דרך",
"Distance": "מרחק",
"Display": "תצוגה",
"Interface": "ממשק",
@@ -436,9 +438,11 @@
"Calibrate...": "כיול...",
"Calibrate Anyway...": "כייל בכל מקרה...",
"Use Automatic Setup": "השתמש בהגדרה אוטומטית",
"Shortcut: %s": "קיצור דרך: %s",
"Next": "הבא",
"HDR Calibration %s/%s: %s": "כיול HDR %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "מגדיר את שלושת המסך המלא מתחת, תמונת בדיקה אחת בכל פעם.",
"A key that opens this screen from the game. Not bound at first.": "מפתח שפותח את המסך הזה מהמשחק. לא קשור בהתחלה.",
"Your display didn't say how bright it gets: calibrate to set it up.": "הצג שלך לא אמר כמה הוא בהיר: כייל כדי להגדיר אותו.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "כבר מוגדר לתצוגה שלך. כייל רק אם HDR נראה שגוי.",
"Puts Peak Brightness and Paper White back to your display's setup.": "מחזיר את Peak Brightness ואת נייר לבן להגדרות התצוגה.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "परछाइयों को आपके डिस्प्ले के सबसे गहरे काले तक उठाता है। 0: शुद्ध काला, कोई उठाव नहीं।",
"How bright highlights get. Auto: the peak your system reports.": "हाइटलाइट्स कितने चमकीले होते हैं। ऑटो: आपका सिस्टम जो शिखर रिपोर्ट करता है।",
"How bright white looks in the world. Auto: your desktop's white.": "दुनिया में सफेद कितना चमकीला दिखता है। ऑटो: आपके डेस्कटॉप का सफेद।",
"How bright the HUD and menus are, against paper white.": "पेपर व्हाइट की तुलना में HUD और मेनू कितने चमकीले हैं।",
"How far mobs, crops and machines keep working. CPU time.": "मॉब्स, फसलें और मशीनें कितनी दूर तक काम करती रहती हैं। CPU समय।",
"How far away mobs and items are drawn, as a share of normal.": "सामान्य के हिस्से के रूप में, मॉब्स और आइटम कितनी दूर खींचे जाते हैं।",
"Skips terrain hidden behind closed sections. Off costs a lot.": "बंद खंडों के पीछे छिपा भू-भाग छोड़ देता है। बंद करने पर अधिक लागत आती है।",
@@ -366,6 +367,7 @@
"Advanced": "उन्नत",
"Undo": "पूर्ववत करें",
"Search options": "विकल्प खोजें",
"Shortcut": "शॉर्टकट",
"Distance": "दूरी",
"Display": "डिस्प्ले",
"Interface": "इंटरफ़ेस",
@@ -436,9 +438,11 @@
"Calibrate...": "कैलिब्रेट करें...",
"Calibrate Anyway...": "वैसे भी कैलिब्रेट करें...",
"Use Automatic Setup": "स्वचालित सेटअप का उपयोग करें",
"Shortcut: %s": "शॉर्टकट: %s",
"Next": "अगला",
"HDR Calibration %s/%s: %s": "HDR कैलिब्रेशन %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "एक समय में एक परीक्षण चित्र के साथ, नीचे दिए गए तीनों को पूर्ण स्क्रीन पर सेट करता है।",
"A key that opens this screen from the game. Not bound at first.": "एक कुंजी जो गेम से इस स्क्रीन को खोलती है। पहले से बाउंड नहीं है।",
"Your display didn't say how bright it gets: calibrate to set it up.": "आपके डिस्प्ले ने यह नहीं बताया कि यह कितना चमकीला होता है: इसे सेट अप करने के लिए कैलिब्रेट करें।",
"Already set up for your display. Only calibrate if HDR looks wrong.": "आपके डिस्प्ले के लिए पहले से सेट अप है। केवल तभी कैलिब्रेट करें जब HDR गलत लगे।",
"Puts Peak Brightness and Paper White back to your display's setup.": "पीक ब्राइटनेस और पेपर व्हाइट को आपके डिस्प्ले के सेटअप पर वापस रखता है।",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Podiže sjene do najtamnije crne vašeg zaslona. 0: prava crna, bez podizanja.",
"How bright highlights get. Auto: the peak your system reports.": "Koliko svijetli postaju svijetli dijelovi. Automatski: vršna vrijednost koju prijavljuje vaš sustav.",
"How bright white looks in the world. Auto: your desktop's white.": "Koliko bijela izgleda svijetlo u svijetu. Automatski: bijela vaše radne površine.",
"How bright the HUD and menus are, against paper white.": "Koliko su svijetli HUD i izbornici u odnosu na papirnato bijelu.",
"How far mobs, crops and machines keep working. CPU time.": "Koliko daleko čudovišta, usjevi i strojevi nastavljaju raditi. Vrijeme CPU-a.",
"How far away mobs and items are drawn, as a share of normal.": "Koliko se daleko crtaju čudovišta i predmeti, kao udio normalnog.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Preskače teren skriven iza zatvorenih dijelova. Isključeno opterećuje sustav.",
@@ -366,6 +367,7 @@
"Advanced": "Napredno",
"Undo": "Poništi",
"Search options": "Opcije pretraživanja",
"Shortcut": "Prečac",
"Distance": "Udaljenost",
"Display": "Zaslon",
"Interface": "Sučelje",
@@ -436,9 +438,11 @@
"Calibrate...": "Kalibriraj...",
"Calibrate Anyway...": "Svejedno kalibriraj...",
"Use Automatic Setup": "Koristi automatsku postavu",
"Shortcut: %s": "Prečac: %s",
"Next": "Sljedeće",
"HDR Calibration %s/%s: %s": "HDR kalibracija %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Postavlja tri donja na cijeli zaslon, jednu testnu sliku po jednu.",
"A key that opens this screen from the game. Not bound at first.": "Tipka koja otvara ovaj zaslon iz igre. U početku nije povezana.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Vaš zaslon nije javio koliko svijetli: kalibrirajte ga za postavljanje.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Već postavljeno za vaš zaslon. Kalibrirajte samo ako HDR izgleda pogrešno.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Vraća najveću svjetlinu i papirnato bijelu na postavke vašeg zaslona.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Az árnyékokat a kijelző legmélyebb feketéjére emeli. 0: valódi fekete, emelés nélkül.",
"How bright highlights get. Auto: the peak your system reports.": "Milyen fényesek a csúcsfények. Automatikus: a rendszer által jelentett csúcs.",
"How bright white looks in the world. Auto: your desktop's white.": "Milyen fényesnek tűnik a fehér a világban. Automatikus: az asztal fehérje.",
"How bright the HUD and menus are, against paper white.": "Milyen fényesek a HUD és a menük papírfehérrel szemben.",
"How far mobs, crops and machines keep working. CPU time.": "Meddig dolgoznak a csőcselék, a termények és a gépek. CPU idő.",
"How far away mobs and items are drawn, as a share of normal.": "Milyen messzire húzódnak a mobok és tárgyak, a normál arányaként.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Kihagyja a zárt szakaszok mögé rejtett terepet. Az off sokba kerül.",
@@ -366,6 +367,7 @@
"Advanced": "Haladó",
"Undo": "Visszavonás",
"Search options": "Keresési lehetőségek",
"Shortcut": "Parancsikon",
"Distance": "Távolság",
"Display": "Kijelző",
"Interface": "Interfész",
@@ -436,9 +438,11 @@
"Calibrate...": "Kalibrálás...",
"Calibrate Anyway...": "Mindenesetre kalibráld...",
"Use Automatic Setup": "Használja az Automatikus beállítást",
"Shortcut: %s": "Parancsikon: %s",
"Next": "Következő",
"HDR Calibration %s/%s: %s": "HDR kalibrálás %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "A három alatti teljes képernyőt állítja be, egyszerre egy tesztképet.",
"A key that opens this screen from the game. Not bound at first.": "Kulcs, amely megnyitja ezt a képernyőt a játékból. Először nem kötve.",
"Your display didn't say how bright it gets: calibrate to set it up.": "A kijelzője nem jelezte, hogy mennyire lesz fényes: kalibrálja a beállításhoz.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Már be van állítva a kijelzőjéhez. Csak akkor kalibráljon, ha a HDR rossznak tűnik.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Visszaállítja a csúcsfényerőt és a papírfehéret a kijelző beállításához.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Ստվերները բարձրացնում է ձեր էկրանի ամենամուգ սևին։ 0՝ իրական սև, առանց բարձրացման։",
"How bright highlights get. Auto: the peak your system reports.": "Որքան վառ ընդգծումներ են ստանում: Ավտոմատ. ձեր համակարգի գագաթնակետը հաղորդում է:",
"How bright white looks in the world. Auto: your desktop's white.": "Թե որքան պայծառ է սպիտակը երևում աշխարհում։ Ավտոմատ՝ ձեր աշխատասեղանի սպիտակը։",
"How bright the HUD and menus are, against paper white.": "Որքան վառ են HUD-ն ու մենյուները՝ թղթի սպիտակի դեմ:",
"How far mobs, crops and machines keep working. CPU time.": "Որքան հեռու են աշխատում ամբոխները, մշակաբույսերը և մեքենաները: Պրոցեսորի ժամանակ.",
"How far away mobs and items are drawn, as a share of normal.": "Որքա՜ն հեռու են քաշված ամբոխներն ու իրերը՝ որպես նորմալի մասնաբաժին։",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Բաց է թողնում փակ հատվածների հետևում թաքնված տեղանքը: Off ծախսերը շատ.",
@@ -366,6 +367,7 @@
"Advanced": "Ընդլայնված",
"Undo": "Հետարկել",
"Search options": "Որոնման ընտրանքներ",
"Shortcut": "Դյուրանցում",
"Distance": "Հեռավորությունը",
"Display": "Ցուցադրել",
"Interface": "Ինտերֆեյս",
@@ -436,9 +438,11 @@
"Calibrate...": "Կալիբրացիա...",
"Calibrate Anyway...": "Կալիբրացիա, այնուամենայնիվ...",
"Use Automatic Setup": "Օգտագործեք ավտոմատ կարգավորում",
"Shortcut: %s": "Դյուրանցում՝ %s",
"Next": "Հաջորդը",
"HDR Calibration %s/%s: %s": "HDR չափորոշում %s/%s՝ %s",
"Sets the three below full screen, one test picture at a time.": "Սահմանում է ներքևում գտնվող երեքը լիաէկրան, միաժամանակ մեկ փորձնական նկար:",
"A key that opens this screen from the game. Not bound at first.": "Բանալին, որը բացում է այս էկրանը խաղից: Սկզբում կապված չէ:",
"Your display didn't say how bright it gets: calibrate to set it up.": "Ձեր էկրանը չի ասում, թե որքան պայծառ է այն դառնում. չափաբերեք այն կարգավորելու համար:",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Արդեն ստեղծվել է ձեր ցուցադրման համար: Չափորոշեք միայն այն դեպքում, եթե HDR-ն սխալ տեսք ունի:",
"Puts Peak Brightness and Paper White back to your display's setup.": "Վերադարձնում է առավելագույն պայծառությունն ու թղթի սպիտակը ձեր էկրանի կարգավորումներին:",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Mengangkat bayangan ke hitam tergelap layar Anda. 0: hitam sejati, tanpa pengangkatan.",
"How bright highlights get. Auto: the peak your system reports.": "Seberapa terang sorotan cahaya. Otomatis: puncak yang dilaporkan oleh sistem Anda.",
"How bright white looks in the world. Auto: your desktop's white.": "Seberapa terang warna putih di dunia. Otomatis: putih desktop Anda.",
"How bright the HUD and menus are, against paper white.": "Seberapa terang HUD dan menu dibandingkan dengan putih kertas.",
"How far mobs, crops and machines keep working. CPU time.": "Seberapa jauh mob, tanaman, dan mesin tetap bekerja. Waktu CPU.",
"How far away mobs and items are drawn, as a share of normal.": "Seberapa jauh mob dan item digambar, sebagai persentase dari normal.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Lewati medan yang tersembunyi di balik bagian tertutup. Nonaktif memakan banyak sumber daya.",
@@ -366,6 +367,7 @@
"Advanced": "Lanjutan",
"Undo": "Urungkan",
"Search options": "Cari opsi",
"Shortcut": "Pintasan",
"Distance": "Jarak",
"Display": "Tampilan",
"Interface": "Antarmuka",
@@ -436,9 +438,11 @@
"Calibrate...": "Kalibrasi...",
"Calibrate Anyway...": "Tetap Kalibrasi...",
"Use Automatic Setup": "Gunakan Pengaturan Otomatis",
"Shortcut: %s": "Pintasan: %s",
"Next": "Berikutnya",
"HDR Calibration %s/%s: %s": "Kalibrasi HDR %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Mengatur ketiga hal di bawah ini ke mode layar penuh, satu gambar uji dalam satu waktu.",
"A key that opens this screen from the game. Not bound at first.": "Tombol untuk membuka layar ini dari dalam game. Belum diatur sebelumnya.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Layar Anda tidak melaporkan tingkat kecerahannya: lakukan kalibrasi untuk mengaturnya.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Sudah diatur untuk layar Anda. Kalibrasi ulang hanya jika tampilan HDR terlihat keliru.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Mengembalikan Kecerahan Puncak dan Putih Kertas ke pengaturan awal layar Anda.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Na-ebuli onyinyo ruo n'ojii kacha njọ nke ihuenyo gị. 0: ojii zuru oke, enweghị ebuli.",
"How bright highlights get. Auto: the peak your system reports.": "Kedu ka isi ihe na-egbuke egbuke si enweta. Akpaaka: ọnụ ọgụgụ kasị elu nke sistemụ gị na-akọ.",
"How bright white looks in the world. Auto: your desktop's white.": "Otú ọcha si acha na ụwa. Akpaaka: ọcha nke desktọp gị.",
"How bright the HUD and menus are, against paper white.": "Lee ka HUD na menus si na-enwu, megide ọcha akwụkwọ.",
"How far mobs, crops and machines keep working. CPU time.": "Ogologo oge ole na-eme mkpọtụ, ihe ọkụkụ na igwe na-arụ ọrụ. CPU oge.",
"How far away mobs and items are drawn, as a share of normal.": "Kedu ebe a na-adọta mobs na ihe dị anya, dịka òkè nke nkịtị.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Na-amafe ala zoro ezo n'azụ ngalaba mechiri emechi. Gbanyụọ na-efu nnukwu ego.",
@@ -366,6 +367,7 @@
"Advanced": "Nke dị elu",
"Undo": "Megharịa",
"Search options": "Chọọ nhọrọ",
"Shortcut": "Ụzọ mkpirisi",
"Distance": "Anya",
"Display": "Ngosipụta",
"Interface": "Nhazi ihu",
@@ -436,9 +438,11 @@
"Calibrate...": "Hazie...",
"Calibrate Anyway...": "Hazie agbanyeghị...",
"Use Automatic Setup": "Jiri Ntọala akpaaka",
"Shortcut: %s": "Ụzọ mkpirisi: %s",
"Next": "Na-esote",
"HDR Calibration %s/%s: %s": "HDR calibration %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Tọọ atọ n'okpuru ihuenyo zuru oke, otu foto nnwale n'otu oge.",
"A key that opens this screen from the game. Not bound at first.": "Igodo na-emepe ihuenyo a site na egwuregwu. Ekebeghị ya na mbụ.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Ngosipụta gị ekwughị ka ọ na-egbuke egbuke: hazie ka ịtọlite ya.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Ahaziri maka ngosi gị. Naanị hazie ma ọ bụrụ na HDR adịghị mma.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Na-eweghachi Peak Brightness na White Akwụkwọ na ntọlite ihe ngosi gị.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Lyftir skuggum upp að dökkasta svarti skjásins. 0: ekta svart, engin lyfting.",
"How bright highlights get. Auto: the peak your system reports.": "Hversu bjartir hápunktar verða. Sjálfvirkt: hámarkið sem kerfið þitt tilkynnir.",
"How bright white looks in the world. Auto: your desktop's white.": "Hversu bjart hvítt lítur út í heiminum. Sjálfvirk: hvítt skjáborðsins þíns.",
"How bright the HUD and menus are, against paper white.": "Hversu björt HUD og valmyndir eru, á móti pappírshvítum.",
"How far mobs, crops and machines keep working. CPU time.": "Hversu langt múgur, uppskera og vélar halda áfram að vinna. CPU tími.",
"How far away mobs and items are drawn, as a share of normal.": "Hversu langt í burtu múgur og hlutir eru dregnir, sem hlutfall af venjulegum.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Sleppir landslagi sem er falið á bak við lokaða kafla. Off kostar mikið.",
@@ -366,6 +367,7 @@
"Advanced": "Ítarlegri",
"Undo": "Afturkalla",
"Search options": "Leitarmöguleikar",
"Shortcut": "Flýtileið",
"Distance": "Fjarlægð",
"Display": "Skjár",
"Interface": "Viðmót",
@@ -436,9 +438,11 @@
"Calibrate...": "Kvarða...",
"Calibrate Anyway...": "Kvörðuðu samt...",
"Use Automatic Setup": "Notaðu sjálfvirka uppsetningu",
"Shortcut: %s": "Flýtileið: %s",
"Next": "Næst",
"HDR Calibration %s/%s: %s": "HDR kvörðun %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Stillir þrjár fyrir neðan allan skjáinn, eina prufumynd í einu.",
"A key that opens this screen from the game. Not bound at first.": "Lykill sem opnar þennan skjá úr leiknum. Ekki bundið í fyrstu.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Skjárinn þinn sagði ekki hversu bjartur hann verður: kvarðaðu til að setja hann upp.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Nú þegar sett upp fyrir skjáinn þinn. Kvörðaðu aðeins ef HDR lítur rangt út.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Setur Peak Brightness og Paper White aftur í uppsetningu skjásins þíns.",
@@ -310,6 +310,7 @@
"Lifts shadows onto your display's darkest black. 0: true black, no lift.": "Solleva le ombre fino al nero più cupo del tuo schermo. 0: nero reale, nessun sollevamento.",
"How bright highlights get. Auto: the peak your system reports.": "Come diventano brillanti le luci. Auto: il picco segnalato dal sistema.",
"How bright white looks in the world. Auto: your desktop's white.": "Quanto luminoso appare il bianco nel mondo. Auto: il bianco del tuo desktop.",
"How bright the HUD and menus are, against paper white.": "Quanto sono luminosi l'HUD e i menu, rispetto al bianco della carta.",
"How far mobs, crops and machines keep working. CPU time.": "Fino a che punto mob, raccolti e macchine continuano a funzionare. Tempo della CPU.",
"How far away mobs and items are drawn, as a share of normal.": "Quanto lontano vengono disegnati mob e oggetti, come percentuale del normale.",
"Skips terrain hidden behind closed sections. Off costs a lot.": "Salta il terreno nascosto dietro le sezioni chiuse. Spendere costa molto.",
@@ -366,6 +367,7 @@
"Advanced": "Avanzato",
"Undo": "Annulla",
"Search options": "Opzioni di ricerca",
"Shortcut": "Scorciatoia",
"Distance": "Distanza",
"Display": "Visualizzazione",
"Interface": "Interfaccia",
@@ -436,9 +438,11 @@
"Calibrate...": "Calibra...",
"Calibrate Anyway...": "Calibra comunque...",
"Use Automatic Setup": "Utilizza l'impostazione automatica",
"Shortcut: %s": "Scorciatoia: %s",
"Next": "Avanti",
"HDR Calibration %s/%s: %s": "Calibrazione HDR %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "Imposta le tre immagini sottostanti a schermo intero, un'immagine di prova alla volta.",
"A key that opens this screen from the game. Not bound at first.": "Una chiave che apre questa schermata del gioco. All'inizio non legato.",
"Your display didn't say how bright it gets: calibrate to set it up.": "Il display non ha indicato la luminosità: calibralo per configurarlo.",
"Already set up for your display. Only calibrate if HDR looks wrong.": "Già predisposto per il tuo display. Calibrare solo se l'HDR sembra sbagliato.",
"Puts Peak Brightness and Paper White back to your display's setup.": "Riporta la luminosità di picco e il bianco della carta alla configurazione del display.",
@@ -315,6 +315,7 @@
"Sets the options below together. Changing one makes it Custom.": "以下のオプションをまとめて設定します。どれかを変更するとカスタムになります。",
"Lava, fire and sunlight shine brighter than white. Needs an HDR display.": "溶岩、火、太陽光は白よりも明るく輝きます。 HDRディスプレイが必要です。",
"How bright highlights get. Auto: the peak your system reports.": "ハイライトがどれだけ明るくなるか。自動: システムが報告するピーク。",
"How bright the HUD and menus are, against paper white.": "紙の白に対する HUD とメニューの明るさ。",
"How far mobs, crops and machines keep working. CPU time.": "暴徒、作物、機械がどこまで機能し続けるか。 CPU時間。",
"How far away mobs and items are drawn, as a share of normal.": "通常の割合として、Mob とアイテムが描画される距離。",
"Skips terrain hidden behind closed sections. Off costs a lot.": "閉じたセクションの後ろに隠れている地形をスキップします。オフには多額の費用がかかります。",
@@ -367,6 +368,7 @@
"Advanced": "上級者向け",
"Undo": "元に戻す",
"Search options": "検索オプション",
"Shortcut": "ショートカット",
"Distance": "距離",
"Display": "ディスプレイ",
"Interface": "インターフェース",
@@ -436,9 +438,11 @@
"Calibrate...": "校正...",
"Calibrate Anyway...": "とにかく校正してください...",
"Use Automatic Setup": "自動セットアップを使用する",
"Shortcut: %s": "ショートカット: %s",
"Next": "次へ",
"HDR Calibration %s/%s: %s": "HDR キャリブレーション %s/%s: %s",
"Sets the three below full screen, one test picture at a time.": "フルスクリーンの下の 3 つを、一度に 1 つのテスト画像を設定します。",
"A key that opens this screen from the game. Not bound at first.": "ゲームからこの画面を開くキー。最初は縛られてない。",
"Your display didn't say how bright it gets: calibrate to set it up.": "ディスプレイにはどのくらい明るくなるかは記載されていません。調整して設定してください。",
"Already set up for your display. Only calibrate if HDR looks wrong.": "ディスプレイ用にすでにセットアップされています。 HDR が間違っていると思われる場合にのみ調整してください。",
"Puts Peak Brightness and Paper White back to your display's setup.": "ピークの明るさとペーパー ホワイトをディスプレイの設定に戻します。",
Loaded 100 of 339 files, more files were not shown because too many files have changed in this diff. Show more