mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 17:21:49 -10:00
Compare commits
271
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2993ef0b20 | ||
|
|
12d7eaccdb | ||
|
|
342824b506 | ||
|
|
195515000a | ||
|
|
1ff952a4f0 | ||
|
|
e5875b0b19 | ||
|
|
1ab39a15ec | ||
|
|
764df32826 | ||
|
|
75e54413d7 | ||
|
|
e0bd7cd69a | ||
|
|
be04fbb531 | ||
|
|
745956e29b | ||
|
|
7b36dd82a3 | ||
|
|
f43950f5fb | ||
|
|
b0aacebaab | ||
|
|
42e6d9cc49 | ||
|
|
369e466faa | ||
|
|
34ddea78e1 | ||
|
|
4020e25379 | ||
|
|
2b74332fbe | ||
|
|
f563b7aafe | ||
|
|
f8c3edd81e | ||
|
|
5ba18cae86 | ||
|
|
f06b417ac4 | ||
|
|
346a0a8d81 | ||
|
|
fc8781c2ce | ||
|
|
6d36365aeb | ||
|
|
3400a0316b | ||
|
|
4d4c3ff32f | ||
|
|
932050ffab | ||
|
|
04c3b710cf | ||
|
|
a6f2414081 | ||
|
|
62db23c23b | ||
|
|
eabc4160a8 | ||
|
|
cccc296d9a | ||
|
|
270e435b3f | ||
|
|
5f625c748e | ||
|
|
2266c4de14 | ||
|
|
76c0362eb8 | ||
|
|
12c5d55a95 | ||
|
|
765da7031e | ||
|
|
89e625981b | ||
|
|
1f09d99dad | ||
|
|
fe49561c78 | ||
|
|
152eaed3bd | ||
|
|
efbd46aaad | ||
|
|
6d99e14ff0 | ||
|
|
6421a384f5 | ||
|
|
39e99fdbd6 | ||
|
|
ad1a6a3101 | ||
|
|
e692430ffe | ||
|
|
861d3168cb | ||
|
|
3431d0e18f | ||
|
|
c4e4800d44 | ||
|
|
f33f49aa06 | ||
|
|
c4c815689b | ||
|
|
2aaaa76e9b | ||
|
|
460dbb0e5e | ||
|
|
b756cf01e6 | ||
|
|
d6bac327fa | ||
|
|
a35ea8b72a | ||
|
|
ec21e06600 | ||
|
|
8d09d8b3f2 | ||
|
|
25d55ff457 | ||
|
|
4f4a931b21 | ||
|
|
cabfc96e14 | ||
|
|
2044fc756c | ||
|
|
043705f347 | ||
|
|
0f5dc8d6d5 | ||
|
|
83da211d38 | ||
|
|
b7df353fdf | ||
|
|
d55c2840a7 | ||
|
|
3c7bb44aa1 | ||
|
|
51e5d01c83 | ||
|
|
b0a738abc3 | ||
|
|
c8eee9842f | ||
|
|
731544d1c5 | ||
|
|
a27b7f20fe | ||
|
|
2ed3ede17f | ||
|
|
11e7d633d6 | ||
|
|
8dcf45e95e | ||
|
|
489a1334a6 | ||
|
|
166addb003 | ||
|
|
f66e017f5c | ||
|
|
2d483f7ffc | ||
|
|
52b0be4adb | ||
|
|
38db573502 | ||
|
|
d3a00abb07 | ||
|
|
4d4872cedd | ||
|
|
603584ef2e | ||
|
|
e9eed40a30 | ||
|
|
20dc224154 | ||
|
|
7a08446cfe | ||
|
|
0a2d899426 | ||
|
|
9e317340d9 | ||
|
|
2228fd7be1 | ||
|
|
188ad5b2d1 | ||
|
|
9e239c1c0c | ||
|
|
78c6d024dd | ||
|
|
2cce72df97 | ||
|
|
54fd1a76d8 | ||
|
|
5849fa33b3 | ||
|
|
f23fd18f41 | ||
|
|
725efc0eca | ||
|
|
f17a44249f | ||
|
|
7df444ab89 | ||
|
|
2a76fd1a00 | ||
|
|
9dae50aefd | ||
|
|
86a9d9e669 | ||
|
|
23e710f75d | ||
|
|
27c2e6457f | ||
|
|
eb55d13be3 | ||
|
|
3d33b0299d | ||
|
|
50d9f3d7f7 | ||
|
|
f15ca8d82a | ||
|
|
46d83389ac | ||
|
|
422c36b236 | ||
|
|
2d0c715866 | ||
|
|
f00c4c6392 | ||
|
|
6e3ecedc2a | ||
|
|
532421c5f0 | ||
|
|
cfccbb8f40 | ||
|
|
e78ed4a7a1 | ||
|
|
db65974cd8 | ||
|
|
c58f498288 | ||
|
|
1fdf875085 | ||
|
|
bd340bf472 | ||
|
|
6e98dae760 | ||
|
|
bff2562ec0 | ||
|
|
e3ad1ce3ee | ||
|
|
e6d6ce8df2 | ||
|
|
1f15474046 | ||
|
|
114dbd0e21 | ||
|
|
d8781cb9df | ||
|
|
abf29eb414 | ||
|
|
5b4823c198 | ||
|
|
c6ad84b481 | ||
|
|
4a5450c6f8 | ||
|
|
0508903777 | ||
|
|
d216a5477d | ||
|
|
216a6218af | ||
|
|
5f69e3a789 | ||
|
|
be42c5d25e | ||
|
|
ef4463a476 | ||
|
|
ee7a6dd9c8 | ||
|
|
e7c5cb7331 | ||
|
|
5583a6bd8b | ||
|
|
e15d46d371 | ||
|
|
ab9929b5ab | ||
|
|
b5ed12bc33 | ||
|
|
e5105f07eb | ||
|
|
f2f95dad8a | ||
|
|
f513a784f0 | ||
|
|
56349b9713 | ||
|
|
4f0af1f5f2 | ||
|
|
75adf421e0 | ||
|
|
ec78951af1 | ||
|
|
85056bcaaf | ||
|
|
ff4b4b17bd | ||
|
|
120fe4d7e3 | ||
|
|
bebaecd65a | ||
|
|
674de01447 | ||
|
|
d1dc1cb937 | ||
|
|
b0061bc2d3 | ||
|
|
b162cbaa46 | ||
|
|
0ad6666970 | ||
|
|
1011abe707 | ||
|
|
6f88fac9a9 | ||
|
|
baa9153c82 | ||
|
|
a14ca6acf4 | ||
|
|
cc7200cd7a | ||
|
|
936594e71b | ||
|
|
1e5bf4c58f | ||
|
|
35fe345f50 | ||
|
|
a553d191a8 | ||
|
|
8c3f6e7d7b | ||
|
|
e26dae0527 | ||
|
|
186a478e21 | ||
|
|
1c712e434a | ||
|
|
0068b79fc4 | ||
|
|
0744579340 | ||
|
|
ce9d8ae6cf | ||
|
|
4a59bae751 | ||
|
|
95244a792a | ||
|
|
f2f3363a0a | ||
|
|
ff8997168c | ||
|
|
2fed27fce9 | ||
|
|
44c14af45a | ||
|
|
a6730f376e | ||
|
|
aa6fc60c4a | ||
|
|
315d1e424d | ||
|
|
b1e41d46ec | ||
|
|
071feb3308 | ||
|
|
7a882e8442 | ||
|
|
1fcc43f60f | ||
|
|
b4644568dd | ||
|
|
44c13ed7ac | ||
|
|
6039812a76 | ||
|
|
93fc52b7ef | ||
|
|
875f62b6b5 | ||
|
|
eb7cc8305c | ||
|
|
9e7b50b560 | ||
|
|
635fa28928 | ||
|
|
7ac3021dfb | ||
|
|
ad73e4a692 | ||
|
|
fac063268c | ||
|
|
56c896f51c | ||
|
|
aa95310c72 | ||
|
|
621d47ab6d | ||
|
|
49e8906c56 | ||
|
|
1a7a3678bb | ||
|
|
07c08509df | ||
|
|
bdfe6e4dd1 | ||
|
|
b90cebc01b | ||
|
|
d9e35999b6 | ||
|
|
40eeef6f80 | ||
|
|
249aa0d4c1 | ||
|
|
8aba8be7a9 | ||
|
|
3fc5f274be | ||
|
|
3f164ac06a | ||
|
|
a729b3cfe6 | ||
|
|
ece613e260 | ||
|
|
5b52010b12 | ||
|
|
7b9daf7bbf | ||
|
|
afc6c3c43a | ||
|
|
69874aef48 | ||
|
|
9f46032653 | ||
|
|
463c970b13 | ||
|
|
58263dc4a0 | ||
|
|
150423300c | ||
|
|
c8a86851b9 | ||
|
|
408044e9a9 | ||
|
|
c0323c7707 | ||
|
|
e95658709a | ||
|
|
3c154d3bb6 | ||
|
|
df263fef74 | ||
|
|
dec18717de | ||
|
|
429defdece | ||
|
|
f8d0840cd7 | ||
|
|
e51fd1dff4 | ||
|
|
c135993a63 | ||
|
|
c942e133da | ||
|
|
8df11ffe4c | ||
|
|
3ef40bc92f | ||
|
|
875b657878 | ||
|
|
2ec1ce0480 | ||
|
|
4993bc83ac | ||
|
|
eb0193cb34 | ||
|
|
2816f17962 | ||
|
|
afad8241b6 | ||
|
|
677e00c72f | ||
|
|
9d11ae8abb | ||
|
|
25cb339d57 | ||
|
|
3555aa81d3 | ||
|
|
af128082f4 | ||
|
|
61a74fdd0a | ||
|
|
e58ee9c714 | ||
|
|
6492b16a7b | ||
|
|
2cb008e118 | ||
|
|
0c897e3e33 | ||
|
|
770e4461f8 | ||
|
|
a85fb7c819 | ||
|
|
ab4f48abfe | ||
|
|
db3dcf2dcc | ||
|
|
bc2363c93b | ||
|
|
f5aef42f71 | ||
|
|
a66f0fc57d | ||
|
|
4a5c6ab5d1 | ||
|
|
6fae1fdc44 | ||
|
|
764795ae20 | ||
|
|
62646f32ab |
No files matched your search
@@ -35,6 +35,7 @@ Feature → files → harness for the most-changed areas: [docs/architecture.md]
|
||||
- **No hardcoded user-facing strings.** Keep UI translation-ready from the start. In the launcher (`launcher/`), wrap visible text with `tr("...")` or `trf("... {arg} ...", &[("arg", &val)])` from `crate::i18n` instead of raw string literals or `format!`. In the game, use translation keys (`Text::tr`, `lang::get`), never hardcoded English strings.
|
||||
- **Claim before you start**: comment `/claim` on the issue (no issue yet: open one, then claim it). An assigned
|
||||
issue is taken: pick another. `/unclaim` when you stop; a claim with no open PR linking it is released after 5 days.
|
||||
- **Controllers reach what registers.** Draw controls with a registering helper (`ui::button`, `settings_shell::foot_button`); `graphics_style::button` and `screen_draw::button` only paint, and registering one part of a screen ends its crosshair, so register all of it.
|
||||
|
||||
## Crates
|
||||
|
||||
|
||||
Generated
+42
@@ -329,6 +329,12 @@ dependencies = [
|
||||
"syn 3.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-lite"
|
||||
version = "0.1.0"
|
||||
@@ -1364,6 +1370,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"toml",
|
||||
"unicode-bidi",
|
||||
"ureq",
|
||||
"wgpu",
|
||||
@@ -1413,6 +1420,7 @@ dependencies = [
|
||||
"horizon-client-render",
|
||||
"horizon-command",
|
||||
"horizon-core",
|
||||
"horizon-cosmetics",
|
||||
"horizon-dualsense",
|
||||
"horizon-friends",
|
||||
"horizon-hdr",
|
||||
@@ -3714,6 +3722,17 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socks"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0c3dbbd9ae980613c6dd8e28a9407b50509d3803b57624d5dfe8315218cd58b"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spake2"
|
||||
version = "0.4.0"
|
||||
@@ -4167,6 +4186,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"socks",
|
||||
"url",
|
||||
"webpki-roots 0.26.11",
|
||||
]
|
||||
@@ -4629,6 +4649,22 @@ version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
@@ -4638,6 +4674,12 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.54.0"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# `keys.contains(&KeyCode::ShiftLeft)` reads a mapping by its default key: ask by `Action`, so the
|
||||
# mapping is named and every device that can hold it counts.
|
||||
disallowed-methods = [
|
||||
{ path = "horizon_client_input::held::HeldKeys::contains", reason = "use `held(Action::..)`, or `raw` for a key that is no mapping's default", allow-invalid = true },
|
||||
]
|
||||
@@ -0,0 +1,143 @@
|
||||
//! Every input the game names: vanilla's key mappings (`Options.keyMappings`) and Horizon's
|
||||
//! pad-only actions. Held state is asked by [`Action`] ([`crate::held::HeldKeys::held`]), not by a
|
||||
//! key: code that wants a mapping says which one, and every device that can hold it counts.
|
||||
use winit::keyboard::KeyCode;
|
||||
|
||||
macro_rules! actions {
|
||||
($($variant:ident, $name:literal, $key:expr;)*) => {
|
||||
/// A key mapping, or a pad-only action.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Action {
|
||||
$($variant,)*
|
||||
}
|
||||
impl Action {
|
||||
/// In `Options.keyMappings` order, the pad-only actions last.
|
||||
pub const ALL: &[Action] = &[$(Action::$variant,)*];
|
||||
/// The saved name: vanilla's for a mapping, `pad.*` for Horizon's own.
|
||||
pub fn name(self) -> &'static str {
|
||||
match self {
|
||||
$(Action::$variant => $name,)*
|
||||
}
|
||||
}
|
||||
/// The default key the game's held state keeps this action under. `None` for what
|
||||
/// isn't held as a key there: a mouse mapping, one with no default, a pad-only action,
|
||||
/// and the debug keys, which act on the physical key (`debug_keys.rs`) and whose
|
||||
/// defaults are other mappings' keys.
|
||||
pub fn key(self) -> Option<KeyCode> {
|
||||
match self {
|
||||
$(Action::$variant => $key,)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
actions! {
|
||||
Attack, "key.attack", None;
|
||||
Use, "key.use", None;
|
||||
Forward, "key.forward", Some(KeyCode::KeyW);
|
||||
Left, "key.left", Some(KeyCode::KeyA);
|
||||
Back, "key.back", Some(KeyCode::KeyS);
|
||||
Right, "key.right", Some(KeyCode::KeyD);
|
||||
Jump, "key.jump", Some(KeyCode::Space);
|
||||
Sneak, "key.sneak", Some(KeyCode::ShiftLeft);
|
||||
Sprint, "key.sprint", Some(KeyCode::ControlLeft);
|
||||
Drop, "key.drop", Some(KeyCode::KeyQ);
|
||||
Inventory, "key.inventory", Some(KeyCode::KeyE);
|
||||
Chat, "key.chat", Some(KeyCode::KeyT);
|
||||
Playerlist, "key.playerlist", Some(KeyCode::Tab);
|
||||
PickItem, "key.pickItem", None;
|
||||
Command, "key.command", Some(KeyCode::Slash);
|
||||
Friends, "key.friends", Some(KeyCode::KeyO);
|
||||
Emote, "key.emote", Some(KeyCode::KeyB);
|
||||
SocialInteractions, "key.socialInteractions", Some(KeyCode::KeyP);
|
||||
ToggleGui, "key.toggleGui", Some(KeyCode::F1);
|
||||
ToggleSpectatorShaderEffects, "key.toggleSpectatorShaderEffects", Some(KeyCode::F4);
|
||||
Screenshot, "key.screenshot", Some(KeyCode::F2);
|
||||
TogglePerspective, "key.togglePerspective", Some(KeyCode::F5);
|
||||
SmoothCamera, "key.smoothCamera", None;
|
||||
Fullscreen, "key.fullscreen", Some(KeyCode::F11);
|
||||
SpectatorOutlines, "key.spectatorOutlines", None;
|
||||
SpectatorHotbar, "key.spectatorHotbar", None;
|
||||
SwapOffhand, "key.swapOffhand", Some(KeyCode::KeyF);
|
||||
SaveToolbarActivator, "key.saveToolbarActivator", Some(KeyCode::KeyC);
|
||||
LoadToolbarActivator, "key.loadToolbarActivator", Some(KeyCode::KeyX);
|
||||
Advancements, "key.advancements", Some(KeyCode::KeyL);
|
||||
QuickActions, "key.quickActions", Some(KeyCode::KeyG);
|
||||
DebugOverlay, "key.debug.overlay", None;
|
||||
DebugModifier, "key.debug.modifier", None;
|
||||
Hotbar1, "key.hotbar.1", Some(KeyCode::Digit1);
|
||||
Hotbar2, "key.hotbar.2", Some(KeyCode::Digit2);
|
||||
Hotbar3, "key.hotbar.3", Some(KeyCode::Digit3);
|
||||
Hotbar4, "key.hotbar.4", Some(KeyCode::Digit4);
|
||||
Hotbar5, "key.hotbar.5", Some(KeyCode::Digit5);
|
||||
Hotbar6, "key.hotbar.6", Some(KeyCode::Digit6);
|
||||
Hotbar7, "key.hotbar.7", Some(KeyCode::Digit7);
|
||||
Hotbar8, "key.hotbar.8", Some(KeyCode::Digit8);
|
||||
Hotbar9, "key.hotbar.9", Some(KeyCode::Digit9);
|
||||
DebugReloadChunk, "key.debug.reloadChunk", None;
|
||||
DebugShowHitboxes, "key.debug.showHitboxes", None;
|
||||
DebugClearChat, "key.debug.clearChat", None;
|
||||
DebugCrash, "key.debug.crash", None;
|
||||
DebugShowChunkBorders, "key.debug.showChunkBorders", None;
|
||||
DebugShowAdvancedTooltips, "key.debug.showAdvancedTooltips", None;
|
||||
DebugCopyRecreateCommand, "key.debug.copyRecreateCommand", None;
|
||||
DebugSpectate, "key.debug.spectate", None;
|
||||
DebugSwitchGameMode, "key.debug.switchGameMode", None;
|
||||
DebugDebugOptions, "key.debug.debugOptions", None;
|
||||
DebugFocusPause, "key.debug.focusPause", None;
|
||||
DebugDumpDynamicTextures, "key.debug.dumpDynamicTextures", None;
|
||||
DebugReloadResourcePacks, "key.debug.reloadResourcePacks", None;
|
||||
DebugProfiling, "key.debug.profiling", None;
|
||||
DebugCopyLocation, "key.debug.copyLocation", None;
|
||||
DebugDumpVersion, "key.debug.dumpVersion", None;
|
||||
DebugProfilingChart, "key.debug.profilingChart", None;
|
||||
DebugFpsCharts, "key.debug.fpsCharts", None;
|
||||
DebugNetworkCharts, "key.debug.networkCharts", None;
|
||||
DebugLightmapTexture, "key.debug.lightmapTexture", None;
|
||||
DebugImprovedTransparency, "key.debug.improvedTransparency", None;
|
||||
HotbarPrevious, "pad.hotbarPrevious", None;
|
||||
HotbarNext, "pad.hotbarNext", None;
|
||||
}
|
||||
|
||||
impl Action {
|
||||
pub fn parse(name: &str) -> Option<Self> {
|
||||
Self::ALL.iter().copied().find(|a| a.name() == name)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::keybinds::{self, KeyBinds, MAPPINGS};
|
||||
|
||||
#[test]
|
||||
fn every_mapping_is_an_action_in_order() {
|
||||
let names: Vec<&str> = Action::ALL.iter().map(|a| a.name()).collect();
|
||||
let mappings: Vec<&str> = MAPPINGS.iter().map(|m| m.0).collect();
|
||||
assert_eq!(names[..mappings.len()], mappings[..]);
|
||||
for action in &names[mappings.len()..] {
|
||||
assert!(action.starts_with("pad."), "{action}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_actions_key_is_its_mappings_default_key() {
|
||||
for (name, _, category) in MAPPINGS {
|
||||
let action = Action::parse(name).unwrap();
|
||||
let want = if *category == "debug" {
|
||||
None
|
||||
} else {
|
||||
keybinds::key_code(KeyBinds::default_of(name))
|
||||
};
|
||||
assert_eq!(action.key(), want, "{name}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_bindable_action_is_an_action() {
|
||||
for (name, _) in crate::gamepad::ACTIONS {
|
||||
assert!(Action::parse(name).is_some(), "{name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,17 @@
|
||||
//! Controllers: Horizon's own feature (vanilla 26.3 has no gamepad input).
|
||||
//! The default layout is Bedrock Edition's; see docs/controllers.md.
|
||||
//! Its default buttons are Horizon's own; analog walking and toggle sneak follow Bedrock Edition on
|
||||
//! consoles. See docs/controllers.md.
|
||||
//!
|
||||
//! This module is the backend-free part: positional buttons, the action
|
||||
//! bindings, deadzones and the look curve, button edges from successive pad
|
||||
//! states, and `horizon-controller.txt`, kept next to `options.txt` so that
|
||||
//! file stays exactly vanilla's. The game reads the pads with gilrs.
|
||||
use std::{collections::BTreeMap, fs, path::Path};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fs,
|
||||
path::Path,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
pub const FILE: &str = "horizon-controller.txt";
|
||||
|
||||
@@ -296,7 +302,7 @@ pub const HOTBAR_NEXT: &str = "pad.hotbarNext";
|
||||
/// always pause / back and isn't listed.
|
||||
pub const ACTIONS: &[(&str, Option<Button>)] = &[
|
||||
("key.jump", Some(Button::South)),
|
||||
("key.drop", Some(Button::East)),
|
||||
("key.drop", Some(Button::DPadDown)),
|
||||
("key.swapOffhand", Some(Button::West)),
|
||||
("key.inventory", Some(Button::North)),
|
||||
(HOTBAR_PREVIOUS, Some(Button::LeftBumper)),
|
||||
@@ -305,15 +311,31 @@ pub const ACTIONS: &[(&str, Option<Button>)] = &[
|
||||
("key.attack", Some(Button::RightTrigger)),
|
||||
("key.sprint", Some(Button::LeftStick)),
|
||||
("key.sneak", Some(Button::RightStick)),
|
||||
("key.togglePerspective", Some(Button::DPadUp)),
|
||||
("key.togglePerspective", Some(Button::Select)),
|
||||
("key.pickItem", Some(Button::DPadLeft)),
|
||||
("key.chat", Some(Button::DPadRight)),
|
||||
("key.advancements", Some(Button::DPadDown)),
|
||||
("key.playerlist", Some(Button::Select)),
|
||||
("key.chat", Some(Button::DPadUp)),
|
||||
("key.advancements", None),
|
||||
("key.playerlist", None),
|
||||
("key.command", None),
|
||||
("key.screenshot", None),
|
||||
("key.toggleGui", None),
|
||||
("key.smoothCamera", None),
|
||||
// Horizon's emote wheel: no button is free by default; the stick's cursor picks.
|
||||
("key.emote", None),
|
||||
("key.fullscreen", None),
|
||||
("key.socialInteractions", None),
|
||||
("key.quickActions", None),
|
||||
("key.saveToolbarActivator", None),
|
||||
("key.loadToolbarActivator", None),
|
||||
("key.hotbar.1", None),
|
||||
("key.hotbar.2", None),
|
||||
("key.hotbar.3", None),
|
||||
("key.hotbar.4", None),
|
||||
("key.hotbar.5", None),
|
||||
("key.hotbar.6", None),
|
||||
("key.hotbar.7", None),
|
||||
("key.hotbar.8", None),
|
||||
("key.hotbar.9", None),
|
||||
];
|
||||
|
||||
/// How a bindable action reaches the game: one of its own handlers, shared
|
||||
@@ -328,6 +350,10 @@ pub enum Route {
|
||||
Sneak,
|
||||
Screenshot,
|
||||
SmoothCamera,
|
||||
Fullscreen,
|
||||
/// Does nothing by itself: what reads it asks whether the button is held
|
||||
/// (`Minecraft.handleKeybinds`' toolbar keys, held with a hotbar key).
|
||||
Held,
|
||||
Key(winit::keyboard::KeyCode),
|
||||
Mouse(winit::event::MouseButton),
|
||||
}
|
||||
@@ -340,6 +366,8 @@ pub fn route(action: &str) -> Option<Route> {
|
||||
"key.sneak" => Some(Route::Sneak),
|
||||
"key.screenshot" => Some(Route::Screenshot),
|
||||
"key.smoothCamera" => Some(Route::SmoothCamera),
|
||||
"key.fullscreen" => Some(Route::Fullscreen),
|
||||
"key.saveToolbarActivator" | "key.loadToolbarActivator" => Some(Route::Held),
|
||||
_ => {
|
||||
let default = crate::keybinds::KeyBinds::default_of(action);
|
||||
crate::keybinds::mouse_button(default)
|
||||
@@ -359,6 +387,46 @@ pub fn action_caption(action: &str) -> &str {
|
||||
}
|
||||
}
|
||||
|
||||
/// How long a held drop button throws single items before it throws the rest of the stack.
|
||||
pub const DROP_STACK_HOLD: Duration = Duration::from_secs(1);
|
||||
|
||||
/// What a held drop button does now, past its press.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum DropStep {
|
||||
/// Held key.drop (a repeat reaches `KeyMapping.click` as a press).
|
||||
One,
|
||||
/// Control + key.drop: a pad has no Control to hold, so holding on is how it asks.
|
||||
Stack,
|
||||
}
|
||||
|
||||
/// A held drop button's repeats: a tap throws one item, as Q does; held, it repeats at the pad's
|
||||
/// repeat delay and interval; and held for [`DROP_STACK_HOLD`] it throws what's left.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct HeldDrop {
|
||||
since: Instant,
|
||||
next: Instant,
|
||||
}
|
||||
impl HeldDrop {
|
||||
pub fn new(now: Instant, delay: Duration) -> Self {
|
||||
Self {
|
||||
since: now,
|
||||
next: now + delay,
|
||||
}
|
||||
}
|
||||
/// The step due at `now`, if any; at most one per call.
|
||||
pub fn due(&mut self, now: Instant, interval: Duration) -> Option<DropStep> {
|
||||
if now < self.next {
|
||||
return None;
|
||||
}
|
||||
self.next = now + interval;
|
||||
Some(if now.duration_since(self.since) >= DROP_STACK_HOLD {
|
||||
DropStep::Stack
|
||||
} else {
|
||||
DropStep::One
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Which button pictures prompts use.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
|
||||
pub enum GlyphStyle {
|
||||
@@ -402,13 +470,21 @@ impl GlyphStyle {
|
||||
GlyphStyle::PlayStation,
|
||||
GlyphStyle::Nintendo,
|
||||
];
|
||||
/// The style for a pad by its USB vendor (Sony, Nintendo; Microsoft,
|
||||
/// Valve's Steam Deck and Steam Input's virtual pad, and everything else
|
||||
/// look like Xbox).
|
||||
pub fn of_vendor(vendor: Option<u16>) -> Self {
|
||||
match vendor {
|
||||
Some(0x054c) => GlyphStyle::PlayStation,
|
||||
Some(0x057e) => GlyphStyle::Nintendo,
|
||||
/// The style for a pad by its USB ids: Sony's and Nintendo's own, and a few officially licensed
|
||||
/// third-party pads made for their consoles. Microsoft, Valve's Steam Deck and Steam Input's
|
||||
/// virtual pad, and everything else look like Xbox.
|
||||
pub fn of_device(vendor: Option<u16>, product: Option<u16>) -> Self {
|
||||
match (vendor, product) {
|
||||
(Some(0x054c), _) => GlyphStyle::PlayStation,
|
||||
(Some(0x057e), _) => GlyphStyle::Nintendo,
|
||||
// HORIPAD 4 FPS and FPS Plus; Razer Raiju; Nacon Revolution Pro.
|
||||
(Some(0x0f0d), Some(0x0055 | 0x0066))
|
||||
| (Some(0x1532), Some(0x1000))
|
||||
| (Some(0x146b), Some(0x0d01)) => GlyphStyle::PlayStation,
|
||||
// HORIPAD for Nintendo Switch; PowerA Wired Plus; PDP Faceoff Wired Pro.
|
||||
(Some(0x0f0d), Some(0x00c1))
|
||||
| (Some(0x20d6), Some(0xa711))
|
||||
| (Some(0x0e6f), Some(0x0180)) => GlyphStyle::Nintendo,
|
||||
_ => GlyphStyle::Xbox,
|
||||
}
|
||||
}
|
||||
@@ -591,6 +667,24 @@ impl PadSettings {
|
||||
}
|
||||
}
|
||||
}
|
||||
// A file from before these defaults: its player's buttons stay where they were.
|
||||
if !text.is_empty() && !text.lines().any(|line| line.starts_with("bindLayout:")) {
|
||||
for (action, button) in [
|
||||
("key.drop", Button::East),
|
||||
("key.togglePerspective", Button::DPadUp),
|
||||
("key.chat", Button::DPadRight),
|
||||
("key.playerlist", Button::Select),
|
||||
("key.advancements", Button::DPadDown),
|
||||
] {
|
||||
// A button saved by hand stays, even one that's now this action's default.
|
||||
if !text
|
||||
.lines()
|
||||
.any(|line| line.starts_with(&format!("bind_{action}:")))
|
||||
{
|
||||
s.bindings.entry(action).or_insert(Some(button));
|
||||
}
|
||||
}
|
||||
}
|
||||
s
|
||||
}
|
||||
pub fn serialize(&self) -> String {
|
||||
@@ -621,6 +715,7 @@ impl PadSettings {
|
||||
button.map_or("none", Button::name)
|
||||
));
|
||||
}
|
||||
text.push_str("bindLayout:1\n");
|
||||
text
|
||||
}
|
||||
pub fn default_of(action: &str) -> Option<Button> {
|
||||
@@ -697,6 +792,29 @@ pub fn stick_axes(
|
||||
}
|
||||
}
|
||||
|
||||
/// The tilt from which a stick holds a key in [`eight_direction_keys`]: holding one is full speed,
|
||||
/// so the small tilts past the deadzone stay still.
|
||||
const KEY_TILT: f32 = 0.3;
|
||||
/// tan(22.5 degrees): where one of the 8 sectors meets the next.
|
||||
const SECTOR_EDGE: f32 = 0.414_213_57;
|
||||
|
||||
/// The movement keys (forward, backward, left, right) a move vector (`[left, forward]`) holds: the
|
||||
/// one of the 8 headings it is nearest, so it is at most 22.5 degrees off. An edge goes to the
|
||||
/// straight heading.
|
||||
pub fn eight_direction_keys([left, forward]: [f32; 2]) -> [bool; 4] {
|
||||
if length([left, forward]) < KEY_TILT {
|
||||
return [false; 4];
|
||||
}
|
||||
let along = forward.abs() > SECTOR_EDGE * left.abs();
|
||||
let across = left.abs() > SECTOR_EDGE * forward.abs();
|
||||
[
|
||||
along && forward > 0.0,
|
||||
along && forward < 0.0,
|
||||
across && left > 0.0,
|
||||
across && left < 0.0,
|
||||
]
|
||||
}
|
||||
|
||||
/// A radial deadzone: inside it the stick is centred; beyond it the rest of
|
||||
/// the travel is rescaled to 0..1 in the same direction, so diagonals aren't
|
||||
/// clipped and the first movement past the deadzone is small.
|
||||
@@ -745,15 +863,6 @@ pub fn look_delta(
|
||||
)
|
||||
}
|
||||
|
||||
/// The hotbar slot after a bumper: one step with wrap-around.
|
||||
pub fn hotbar_step(selected: usize, forward: bool) -> usize {
|
||||
if forward {
|
||||
(selected + 1) % 9
|
||||
} else {
|
||||
(selected + 8) % 9
|
||||
}
|
||||
}
|
||||
|
||||
/// Menu cursor speed in GUI pixels per second at full tilt.
|
||||
pub const CURSOR_SPEED: f32 = 300.0;
|
||||
|
||||
@@ -1460,6 +1569,52 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_held_drop_repeats_then_throws_the_stack() {
|
||||
let t = Instant::now();
|
||||
let at = |ms: u64| t + Duration::from_millis(ms);
|
||||
let (delay, interval) = (Duration::from_millis(350), Duration::from_millis(90));
|
||||
let mut held = HeldDrop::new(t, delay);
|
||||
assert_eq!(held.due(at(100), interval), None, "a tap is one item");
|
||||
assert_eq!(held.due(at(349), interval), None);
|
||||
assert_eq!(held.due(at(350), interval), Some(DropStep::One));
|
||||
assert_eq!(held.due(at(400), interval), None, "not before the interval");
|
||||
assert_eq!(held.due(at(440), interval), Some(DropStep::One));
|
||||
assert_eq!(held.due(at(999), interval), Some(DropStep::One));
|
||||
assert_eq!(held.due(at(1000), interval), None);
|
||||
assert_eq!(held.due(at(1100), interval), Some(DropStep::Stack));
|
||||
assert_eq!(held.due(at(1200), interval), Some(DropStep::Stack));
|
||||
assert_eq!(held.due(at(1201), interval), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_later_actions_have_no_default_button_and_leave_saved_bindings() {
|
||||
let defaults = PadSettings::default();
|
||||
for action in [
|
||||
"key.fullscreen",
|
||||
"key.socialInteractions",
|
||||
"key.quickActions",
|
||||
"key.saveToolbarActivator",
|
||||
"key.loadToolbarActivator",
|
||||
"key.hotbar.1",
|
||||
"key.hotbar.9",
|
||||
] {
|
||||
assert_eq!(defaults.binding(action), None, "{action}");
|
||||
}
|
||||
let read = PadSettings::parse(
|
||||
"bindLayout:1
|
||||
bind_key.jump:east
|
||||
bind_key.hotbar.2:north
|
||||
",
|
||||
);
|
||||
assert_eq!(read.binding("key.jump"), Some(Button::East));
|
||||
assert_eq!(read.binding("key.inventory"), Some(Button::North));
|
||||
assert_eq!(
|
||||
read.actions_of(Button::North),
|
||||
["key.inventory", "key.hotbar.2"]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn navigation_preferences_migrate_and_stay_with_each_player() {
|
||||
let dir = std::env::temp_dir().join(format!(
|
||||
@@ -1579,7 +1734,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn defaults_follow_bedrock() {
|
||||
fn every_action_has_its_default() {
|
||||
let s = PadSettings::default();
|
||||
assert_eq!(s.actions_of(Button::South), ["key.jump"]);
|
||||
assert_eq!(s.actions_of(Button::RightTrigger), ["key.attack"]);
|
||||
@@ -1597,6 +1752,36 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_file_from_before_these_defaults_keeps_its_buttons() {
|
||||
// Saved settings that never bound D-pad down: it stays on the advancements.
|
||||
let s = PadSettings::parse("vibration:false\nbind_key.chat:up\n");
|
||||
assert_eq!(s.binding("key.advancements"), Some(Button::DPadDown));
|
||||
assert_eq!(s.binding("key.chat"), Some(Button::DPadUp));
|
||||
assert_eq!(s.binding("key.drop"), Some(Button::East));
|
||||
assert_eq!(s.binding("key.togglePerspective"), Some(Button::DPadUp));
|
||||
assert_eq!(s.binding("key.playerlist"), Some(Button::Select));
|
||||
assert!(!s.conflicts("key.advancements"));
|
||||
// Advancements moved by hand stays moved, and the file stays this way from then on.
|
||||
let s = PadSettings::parse("bind_key.advancements:left\nbind_key.drop:down\n");
|
||||
assert_eq!(s.binding("key.advancements"), Some(Button::DPadLeft));
|
||||
assert_eq!(s.binding("key.drop"), Some(Button::DPadDown));
|
||||
assert_eq!(PadSettings::parse(&s.serialize()), s);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_new_player_drops_on_d_pad_down_and_chats_on_d_pad_up() {
|
||||
for s in [PadSettings::default(), PadSettings::parse("")] {
|
||||
assert_eq!(s.binding("key.drop"), Some(Button::DPadDown));
|
||||
assert_eq!(s.binding("key.chat"), Some(Button::DPadUp));
|
||||
assert_eq!(s.binding("key.togglePerspective"), Some(Button::Select));
|
||||
assert_eq!(s.binding("key.playerlist"), None);
|
||||
assert_eq!(s.binding("key.advancements"), None);
|
||||
}
|
||||
let s = PadSettings::parse(&PadSettings::default().serialize());
|
||||
assert_eq!(s, PadSettings::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settings_round_trip_and_store_only_changes() {
|
||||
let mut s = PadSettings::default();
|
||||
@@ -1618,8 +1803,9 @@ mod tests {
|
||||
assert_eq!(back.binding("key.drop"), None);
|
||||
assert_eq!(back.actions_of(Button::East), ["key.jump"]);
|
||||
// Garbage keeps the defaults.
|
||||
let odd =
|
||||
PadSettings::parse("lookSensitivity:NaN\nbind_key.nope:south\nbind_key.jump:x9\n");
|
||||
let odd = PadSettings::parse(
|
||||
"lookSensitivity:NaN\nbind_key.nope:south\nbind_key.jump:x9\nbindLayout:1\n",
|
||||
);
|
||||
assert_eq!(odd, PadSettings::default());
|
||||
}
|
||||
|
||||
@@ -1676,6 +1862,63 @@ mod tests {
|
||||
assert_eq!(Button::North.by_letter().label(GlyphStyle::Nintendo), "Y");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_stick_holds_the_keys_of_its_nearest_heading() {
|
||||
// Forward, backward, left, right, by the heading counted from forward toward left.
|
||||
let headings = [
|
||||
[true, false, false, false],
|
||||
[true, false, true, false],
|
||||
[false, false, true, false],
|
||||
[false, true, true, false],
|
||||
[false, true, false, false],
|
||||
[false, true, false, true],
|
||||
[false, false, false, true],
|
||||
[true, false, false, true],
|
||||
];
|
||||
for tenth in 0..3600 {
|
||||
let degrees = tenth as f32 / 10.0;
|
||||
if degrees % 45.0 == 22.5 {
|
||||
continue;
|
||||
}
|
||||
for tilt in [0.31, 0.5, 1.0] {
|
||||
let (s, c) = degrees.to_radians().sin_cos();
|
||||
let keys = eight_direction_keys([tilt * s, tilt * c]);
|
||||
let sector = ((degrees / 45.0).round() as usize) % 8;
|
||||
assert_eq!(keys, headings[sector], "{degrees} at {tilt}");
|
||||
let [left, forward] =
|
||||
crate::input::keyboard_direction(keys[0], keys[1], keys[2], keys[3]);
|
||||
let off = (left.atan2(forward).to_degrees() - degrees).rem_euclid(360.0);
|
||||
assert!(off.min(360.0 - off) <= 22.5 + 1e-3, "{degrees}: {off}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_sector_edge_goes_to_the_straight_heading() {
|
||||
assert_eq!(
|
||||
eight_direction_keys([0.414_213_57, 1.0]),
|
||||
[true, false, false, false]
|
||||
);
|
||||
assert_eq!(
|
||||
eight_direction_keys([0.4143, 1.0]),
|
||||
[true, false, true, false]
|
||||
);
|
||||
assert_eq!(
|
||||
eight_direction_keys([1.0, 0.414_213_57]),
|
||||
[false, false, true, false]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_small_tilt_holds_no_key() {
|
||||
assert_eq!(eight_direction_keys([0.0, 0.0]), [false; 4]);
|
||||
assert_eq!(eight_direction_keys([0.2, 0.2]), [false; 4]);
|
||||
assert_eq!(
|
||||
eight_direction_keys([0.0, 0.31]),
|
||||
[true, false, false, false]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn radial_deadzone_rescales() {
|
||||
assert_eq!(radial([0.1, 0.05], 0.15), [0.0, 0.0]);
|
||||
@@ -2206,12 +2449,27 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hotbar_wraps_and_glyphs_follow_the_vendor() {
|
||||
assert_eq!(hotbar_step(8, true), 0);
|
||||
assert_eq!(hotbar_step(0, false), 8);
|
||||
assert_eq!(hotbar_step(3, true), 4);
|
||||
assert_eq!(GlyphStyle::of_vendor(Some(0x054c)), GlyphStyle::PlayStation);
|
||||
assert_eq!(GlyphStyle::of_vendor(Some(0x28de)), GlyphStyle::Xbox);
|
||||
assert_eq!(GlyphStyle::of_vendor(None), GlyphStyle::Xbox);
|
||||
fn glyphs_follow_the_vendor() {
|
||||
let of = |vendor, product| GlyphStyle::of_device(Some(vendor), Some(product));
|
||||
// Any product of the consoles' makers.
|
||||
assert_eq!(of(0x054c, 0x0ce6), GlyphStyle::PlayStation);
|
||||
assert_eq!(of(0x054c, 0x0001), GlyphStyle::PlayStation);
|
||||
assert_eq!(of(0x057e, 0x2009), GlyphStyle::Nintendo);
|
||||
// A pad made for a console by another maker, and that maker's others.
|
||||
assert_eq!(of(0x0f0d, 0x0066), GlyphStyle::PlayStation);
|
||||
assert_eq!(of(0x0f0d, 0x00c1), GlyphStyle::Nintendo);
|
||||
assert_eq!(of(0x0f0d, 0x0067), GlyphStyle::Xbox);
|
||||
assert_eq!(of(0x20d6, 0xa711), GlyphStyle::Nintendo);
|
||||
// Valve's virtual pad, Microsoft's, nothing known, nothing reported.
|
||||
assert_eq!(of(0x28de, 0x11ff), GlyphStyle::Xbox);
|
||||
assert_eq!(of(0x045e, 0x0b12), GlyphStyle::Xbox);
|
||||
assert_eq!(of(0x1234, 0x5678), GlyphStyle::Xbox);
|
||||
assert_eq!(GlyphStyle::of_device(None, None), GlyphStyle::Xbox);
|
||||
// A vendor alone is enough only for the consoles' makers.
|
||||
assert_eq!(
|
||||
GlyphStyle::of_device(Some(0x054c), None),
|
||||
GlyphStyle::PlayStation
|
||||
);
|
||||
assert_eq!(GlyphStyle::of_device(Some(0x0f0d), None), GlyphStyle::Xbox);
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,18 @@
|
||||
//! device letting go (splitscreen moving the keyboard to another player, say)
|
||||
//! can't release what another still presses.
|
||||
|
||||
use crate::actions::Action;
|
||||
use std::collections::HashMap;
|
||||
use winit::keyboard::KeyCode;
|
||||
|
||||
/// Where a press came from.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Source {
|
||||
/// The keyboard and mouse, and the scripting socket acting as them.
|
||||
/// The keyboard and mouse.
|
||||
Keyboard,
|
||||
Pad,
|
||||
/// `horizon-ctl`'s `key` and `mouse` and the agent commands that walk and click.
|
||||
Agent,
|
||||
}
|
||||
|
||||
impl Source {
|
||||
@@ -19,6 +22,7 @@ impl Source {
|
||||
match self {
|
||||
Source::Keyboard => 1,
|
||||
Source::Pad => 2,
|
||||
Source::Agent => 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,9 +33,20 @@ pub struct HeldKeys {
|
||||
}
|
||||
|
||||
impl HeldKeys {
|
||||
pub fn contains(&self, key: &KeyCode) -> bool {
|
||||
/// Whether anything holds this mapping: every device, whatever the mapping is bound to.
|
||||
pub fn held(&self, action: Action) -> bool {
|
||||
action.key().is_some_and(|key| self.raw(&key))
|
||||
}
|
||||
/// Whether a key that is no mapping's default is held (Right Shift, the arrows, Alt, the system
|
||||
/// key), or the key a caller already holds as a key: most code wants [`HeldKeys::held`].
|
||||
pub fn raw(&self, key: &KeyCode) -> bool {
|
||||
self.held.contains_key(key)
|
||||
}
|
||||
/// The old way of asking, by a mapping's default key; clippy refuses it (`clippy.toml`). It
|
||||
/// stays so that a change written against it says what to use instead.
|
||||
pub fn contains(&self, key: &KeyCode) -> bool {
|
||||
self.raw(key)
|
||||
}
|
||||
/// `source` holds `key`. True when nothing held it before.
|
||||
pub fn press(&mut self, key: KeyCode, source: Source) -> bool {
|
||||
let sources = self.held.entry(key).or_insert(0);
|
||||
@@ -72,7 +87,7 @@ impl HeldKeys {
|
||||
/// Held, by the keyboard unless a device already holds it: for callers
|
||||
/// that don't say where the press came from.
|
||||
pub fn insert(&mut self, key: KeyCode) -> bool {
|
||||
if self.contains(&key) {
|
||||
if self.raw(&key) {
|
||||
return false;
|
||||
}
|
||||
self.press(key, Source::Keyboard)
|
||||
@@ -132,13 +147,25 @@ mod tests {
|
||||
assert!(!keys.press(KeyCode::Space, Source::Keyboard));
|
||||
assert!(keys.press(KeyCode::KeyW, Source::Keyboard));
|
||||
assert_eq!(keys.release_source(Source::Keyboard), vec![KeyCode::KeyW]);
|
||||
assert!(keys.contains(&KeyCode::Space) && !keys.contains(&KeyCode::KeyW));
|
||||
assert!(keys.held(Action::Jump) && !keys.held(Action::Forward));
|
||||
assert!(keys.held_by(&KeyCode::Space, Source::Pad));
|
||||
assert!(!keys.held_by(&KeyCode::Space, Source::Keyboard));
|
||||
assert!(keys.release(&KeyCode::Space, Source::Pad));
|
||||
assert!(keys.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_script_holds_a_key_by_itself() {
|
||||
let mut keys = HeldKeys::default();
|
||||
keys.press(KeyCode::KeyW, Source::Agent);
|
||||
keys.press(KeyCode::KeyW, Source::Keyboard);
|
||||
assert!(!keys.release(&KeyCode::KeyW, Source::Keyboard));
|
||||
assert!(keys.held(Action::Forward));
|
||||
assert!(keys.release_source(Source::Pad).is_empty());
|
||||
assert_eq!(keys.release_source(Source::Agent), vec![KeyCode::KeyW]);
|
||||
assert!(!keys.held(Action::Forward));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn untagged_inserts_and_removes_act_as_before() {
|
||||
let mut keys = HeldKeys::default();
|
||||
@@ -149,7 +176,7 @@ mod tests {
|
||||
keys.remove(&KeyCode::ShiftLeft),
|
||||
"remove lets go for everyone"
|
||||
);
|
||||
assert!(!keys.contains(&KeyCode::ShiftLeft));
|
||||
assert!(!keys.held(Action::Sneak));
|
||||
assert!(keys.release(&KeyCode::KeyA, Source::Keyboard), "never held");
|
||||
}
|
||||
}
|
||||
@@ -51,11 +51,6 @@ impl InteractionInput {
|
||||
pub fn press(&mut self, button: Button) {
|
||||
self.clicks[button as usize] += 1;
|
||||
}
|
||||
/// Whether a click waits for the next tick.
|
||||
pub fn has_clicks(&self) -> bool {
|
||||
self.clicks.iter().any(|&c| c > 0)
|
||||
}
|
||||
|
||||
pub fn tick(&mut self, world: &mut impl InteractionWorld, attack_down: bool, use_down: bool) {
|
||||
let [attack, use_item, pick] = std::mem::take(&mut self.clicks);
|
||||
world.pick_hit_result();
|
||||
|
||||
@@ -23,6 +23,8 @@ pub const MAPPINGS: &[(&str, &str, &str)] = &[
|
||||
("key.pickItem", "key.mouse.middle", "gameplay"),
|
||||
("key.command", "key.keyboard.slash", "multiplayer"),
|
||||
("key.friends", "key.keyboard.o", "multiplayer"),
|
||||
// Horizon's emote wheel (docs/cosmetics.md), on a key vanilla leaves free.
|
||||
("key.emote", "key.keyboard.b", "multiplayer"),
|
||||
("key.socialInteractions", "key.keyboard.p", "multiplayer"),
|
||||
("key.toggleGui", "key.keyboard.f1", "misc"),
|
||||
(
|
||||
@@ -440,6 +442,18 @@ impl KeyBinds {
|
||||
}
|
||||
vec![Ok(key)]
|
||||
}
|
||||
/// `Minecraft.handleKeybinds`: key.pickItem and key.spectatorHotbar share the middle button by
|
||||
/// default and are separate mappings, so a press of what `input` is bound to acts only as the
|
||||
/// one this player can use: the spectator hotbar for a spectator, pick block otherwise. (Pick
|
||||
/// block does nothing a spectator would see.)
|
||||
pub fn middle_acts(&self, input: &str, spectator: bool) -> bool {
|
||||
let mapping = if spectator {
|
||||
"key.spectatorHotbar"
|
||||
} else {
|
||||
"key.pickItem"
|
||||
};
|
||||
self.get(mapping) == input
|
||||
}
|
||||
/// Translate a mouse button the same way (keys bound to mouse buttons).
|
||||
pub fn translate_mouse(&self, button: MouseButton) -> Option<Result<KeyCode, MouseButton>> {
|
||||
self.translate_mouse_all(button).into_iter().next()
|
||||
@@ -503,6 +517,27 @@ impl KeyBinds {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn mappings_sharing_the_middle_button_act_as_themselves() {
|
||||
let middle = "key.mouse.middle";
|
||||
let z = "key.keyboard.z";
|
||||
let mut keys = KeyBinds::default();
|
||||
assert!(keys.middle_acts(middle, false) && keys.middle_acts(middle, true));
|
||||
// The spectator hotbar on Z: the middle button only picks, and Z never picks.
|
||||
keys.set("key.spectatorHotbar", z);
|
||||
assert!(keys.middle_acts(middle, false));
|
||||
assert!(!keys.middle_acts(middle, true));
|
||||
assert!(keys.middle_acts(z, true));
|
||||
assert!(!keys.middle_acts(z, false));
|
||||
// Pick block on Z instead: the other way round.
|
||||
keys.reset_all();
|
||||
keys.set("key.pickItem", z);
|
||||
assert!(keys.middle_acts(z, false));
|
||||
assert!(!keys.middle_acts(z, true));
|
||||
assert!(keys.middle_acts(middle, true));
|
||||
assert!(!keys.middle_acts(middle, false));
|
||||
}
|
||||
#[test]
|
||||
fn container_mouse_hotkeys_follow_slot_bindings_and_pick_item_priority() {
|
||||
for button in [MouseButton::Back, MouseButton::Forward, MouseButton::Middle] {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
//! options. horizon-client re-exports each module under its old path
|
||||
//! (`horizon_client::keybinds`, ...).
|
||||
#![forbid(unsafe_code)]
|
||||
pub mod actions;
|
||||
pub mod clipboard;
|
||||
pub mod gamepad;
|
||||
pub mod held;
|
||||
|
||||
@@ -18,6 +18,8 @@ horizon-client-core = { path = "../horizon-client-core" }
|
||||
horizon-client-input = { path = "../horizon-client-input" }
|
||||
horizon-client-render = { path = "../horizon-client-render" }
|
||||
horizon-command = { path = "../horizon-command" }
|
||||
# Emotes pose the player model (`Mob::emote`, `mobs::anim::setup`).
|
||||
horizon-cosmetics = { path = "../horizon-cosmetics" }
|
||||
horizon-items = { path = "../horizon-items" }
|
||||
horizon-core = { path = "../horizon-core" }
|
||||
# `horizon-ctl mcp`: the Model Context Protocol, shared with the launcher and the server.
|
||||
|
||||
@@ -225,6 +225,8 @@ pub const BIOME_STRATEGY: Strategy = Strategy::biomes(8);
|
||||
/// (what vanilla servers and clients send), more when content mods add states
|
||||
/// past 65,536 (docs/modpacks.md). Set once, before the first chunk.
|
||||
static BLOCK_BITS: std::sync::atomic::AtomicU8 = std::sync::atomic::AtomicU8::new(16);
|
||||
/// States in the registry, set with [`BLOCK_BITS`].
|
||||
static BLOCK_STATES: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(u32::MAX);
|
||||
|
||||
/// Block states are `PalettedContainer`s over the registry: vanilla's 16-bit
|
||||
/// global palette, wider with enough content mods.
|
||||
@@ -232,6 +234,35 @@ pub fn block_strategy() -> Strategy {
|
||||
Strategy::blocks(BLOCK_BITS.load(std::sync::atomic::Ordering::Relaxed))
|
||||
}
|
||||
|
||||
/// Whether the registry has this state. Every id is known until the registry is loaded.
|
||||
pub fn known_block_state(id: u32) -> bool {
|
||||
id < BLOCK_STATES.load(std::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// A server's block container in our palette width. Ids past our registry (a
|
||||
/// server or host with mods we lack) read as air: every per-state table is
|
||||
/// indexed by them, and a palette's unused entries are scanned too.
|
||||
pub fn known_blocks_only(blocks: PalettedContainer) -> PalettedContainer {
|
||||
let ours = block_strategy();
|
||||
let unknown = |id: u32| !known_block_state(id) || u64::from(id) >> ours.global_bits != 0;
|
||||
let any_unknown = if blocks.is_global() {
|
||||
blocks.has(unknown)
|
||||
} else {
|
||||
blocks.maybe_has(unknown)
|
||||
};
|
||||
if !any_unknown && blocks.strategy().global_bits == ours.global_bits {
|
||||
return blocks;
|
||||
}
|
||||
let mut values = [0u32; 4096];
|
||||
blocks.unpack(&mut values);
|
||||
for value in &mut values {
|
||||
if unknown(*value) {
|
||||
*value = 0;
|
||||
}
|
||||
}
|
||||
PalettedContainer::from_values(ours, &values)
|
||||
}
|
||||
|
||||
/// Frees `columns` (`count` of them) on a short-lived thread of its own
|
||||
/// when there are many: a teleport or a dimension change drops a few
|
||||
/// thousand columns at once, and freeing their sections took the frame
|
||||
@@ -250,6 +281,10 @@ pub fn free_columns<T: Send + 'static>(columns: T, count: usize) {
|
||||
pub fn set_block_states(states: usize) {
|
||||
let bits = horizon_core::paletted::ceil_log2(states).max(16);
|
||||
BLOCK_BITS.store(bits, std::sync::atomic::Ordering::Relaxed);
|
||||
BLOCK_STATES.store(
|
||||
u32::try_from(states).unwrap_or(u32::MAX),
|
||||
std::sync::atomic::Ordering::Relaxed,
|
||||
);
|
||||
forget_shared();
|
||||
}
|
||||
|
||||
|
||||
@@ -133,6 +133,13 @@ pub fn rows() -> Vec<(Page, Vec<(&'static str, Vec<Setting>)>)> {
|
||||
),
|
||||
],
|
||||
),
|
||||
(
|
||||
"Play",
|
||||
vec![option(
|
||||
Opt::PadTriggerThreshold,
|
||||
"How far a trigger is pulled before it counts as pressed.",
|
||||
)],
|
||||
),
|
||||
(
|
||||
"Menus",
|
||||
vec![
|
||||
@@ -1266,6 +1273,7 @@ mod tests {
|
||||
Opt::PadInvertY,
|
||||
Opt::PadMoveDeadzone,
|
||||
Opt::PadLookDeadzone,
|
||||
Opt::PadTriggerThreshold,
|
||||
Opt::PadGlyphs,
|
||||
Opt::PadKeyboard,
|
||||
Opt::PadPointerSpeed,
|
||||
|
||||
@@ -164,10 +164,10 @@ pub struct Extras {
|
||||
/// Cosmetics (`horizon_cosmetics`, off unless enabled), the lines `horizon_cosmetics::prefs`
|
||||
/// shares with the launcher's Wardrobe: Show my cosmetics (the account's, sent to the service;
|
||||
/// on by default), how much of other players' to show (no line until chosen:
|
||||
/// `effective_others`), and their auras and trails (off by default).
|
||||
/// `effective_others`), and whether their trails and auras are hidden (off by default).
|
||||
pub cosmetics_mine: bool,
|
||||
pub cosmetics_others: Option<horizon_cosmetics::prefs::Others>,
|
||||
pub cosmetics_auras: bool,
|
||||
pub cosmetics_hide_fx: bool,
|
||||
/// My cosmetics in single player too (`cosmeticsSolo`, off by default): the window's own server
|
||||
/// names the player by their offline profile, which the service doesn't know, so their own
|
||||
/// cosmetics and name tag style show only with this on.
|
||||
@@ -230,12 +230,12 @@ impl Default for Extras {
|
||||
splitscreen_two: 0,
|
||||
splitscreen_three: 0,
|
||||
splitscreen_names: true,
|
||||
wheel_cycles_settings: true,
|
||||
wheel_cycles_settings: false,
|
||||
music_off: Vec::new(),
|
||||
music_page: false,
|
||||
cosmetics_mine: true,
|
||||
cosmetics_others: None,
|
||||
cosmetics_auras: false,
|
||||
cosmetics_hide_fx: false,
|
||||
cosmetics_solo: false,
|
||||
cosmetics_owner: false,
|
||||
horizon_marks: true,
|
||||
@@ -334,7 +334,11 @@ impl Extras {
|
||||
"splitscreenTwo" => e.splitscreen_two = value.parse().unwrap_or(0).min(2),
|
||||
"splitscreenThree" => e.splitscreen_three = value.parse().unwrap_or(0).min(2),
|
||||
"splitscreenNames" => e.splitscreen_names = flag(e.splitscreen_names),
|
||||
"wheelCyclesSettings" => e.wheel_cycles_settings = flag(e.wheel_cycles_settings),
|
||||
// A new name: `wheelCyclesSettings` was written as true by every
|
||||
// install while it was the default, so it says nothing of a choice.
|
||||
"wheelCyclesListSettings" => {
|
||||
e.wheel_cycles_settings = flag(e.wheel_cycles_settings)
|
||||
}
|
||||
"musicOff" => {
|
||||
e.music_off = value
|
||||
.split(',')
|
||||
@@ -349,7 +353,7 @@ impl Extras {
|
||||
e.cosmetics_others =
|
||||
horizon_cosmetics::prefs::Others::parse(value).or(e.cosmetics_others)
|
||||
}
|
||||
"cosmeticsAuras" => e.cosmetics_auras = flag(e.cosmetics_auras),
|
||||
"cosmeticsHideFx" => e.cosmetics_hide_fx = flag(e.cosmetics_hide_fx),
|
||||
"cosmeticsSolo" => e.cosmetics_solo = flag(e.cosmetics_solo),
|
||||
horizon_cosmetics::prefs::MARKS => e.horizon_marks = flag(e.horizon_marks),
|
||||
horizon_cosmetics::prefs::LISTED => e.horizon_listed = flag(e.horizon_listed),
|
||||
@@ -363,7 +367,7 @@ impl Extras {
|
||||
}
|
||||
pub fn serialize(&self) -> String {
|
||||
let mut text = format!(
|
||||
"packBrowser:{}\npregen:{}\nfullbright:{}\ndynamicLights:{}\nzoom:{}\nzoomKey:{}\nzoomLevel:{}\nzoomSmooth:{}\nzoomSensitivity:{}\nlodZoomDetail:{}\nfps:{}\nfpsCorner:{}\nfpsLows:{}\nkeystrokes:{}\nkeystrokesCorner:{}\nkeystrokesCps:{}\nmovedToMods:{}\nminimap:{}\nminimapCorner:{}\nminimapSize:{}\nminimapShape:{}\nminimapZoom:{}\nminimapRotate:{}\nautoReconnect:{}\nuiSoundReserve:{}\nownNameTag:{}\nhideBlockParticles:{}\nmouseTweaks:{}\nmouseTweaksWheel:{}\nsort:{}\nsortKey:{}\nchatAnimation:{}\nchatIndicator:{}\n{}:{}\nagentControl:{}\nreproKeys:{}\ndiscordPresence:{}\nsplitscreen:{}\nsplitscreenTwo:{}\nsplitscreenThree:{}\nsplitscreenNames:{}\nwheelCyclesSettings:{}\nmusicOff:{}\nmusicPage:{}\ncosmeticsMine:{}\n{}cosmeticsAuras:{}\ncosmeticsSolo:{}\n{}:{}\n{}:{}\n{}:{}\n",
|
||||
"packBrowser:{}\npregen:{}\nfullbright:{}\ndynamicLights:{}\nzoom:{}\nzoomKey:{}\nzoomLevel:{}\nzoomSmooth:{}\nzoomSensitivity:{}\nlodZoomDetail:{}\nfps:{}\nfpsCorner:{}\nfpsLows:{}\nkeystrokes:{}\nkeystrokesCorner:{}\nkeystrokesCps:{}\nmovedToMods:{}\nminimap:{}\nminimapCorner:{}\nminimapSize:{}\nminimapShape:{}\nminimapZoom:{}\nminimapRotate:{}\nautoReconnect:{}\nuiSoundReserve:{}\nownNameTag:{}\nhideBlockParticles:{}\nmouseTweaks:{}\nmouseTweaksWheel:{}\nsort:{}\nsortKey:{}\nchatAnimation:{}\nchatIndicator:{}\n{}:{}\nagentControl:{}\nreproKeys:{}\ndiscordPresence:{}\nsplitscreen:{}\nsplitscreenTwo:{}\nsplitscreenThree:{}\nsplitscreenNames:{}\nwheelCyclesListSettings:{}\nmusicOff:{}\nmusicPage:{}\ncosmeticsMine:{}\n{}cosmeticsHideFx:{}\ncosmeticsSolo:{}\n{}:{}\n{}:{}\n{}:{}\n",
|
||||
bool_text(self.pack_browser),
|
||||
bool_text(self.pregen),
|
||||
bool_text(self.fullbright),
|
||||
@@ -417,7 +421,7 @@ impl Extras {
|
||||
// Written once the player chose.
|
||||
self.cosmetics_others
|
||||
.map_or(String::new(), |o| format!("cosmeticsOthers:{}\n", o.name())),
|
||||
bool_text(self.cosmetics_auras),
|
||||
bool_text(self.cosmetics_hide_fx),
|
||||
bool_text(self.cosmetics_solo),
|
||||
horizon_cosmetics::prefs::MARKS,
|
||||
bool_text(self.horizon_marks),
|
||||
@@ -452,6 +456,7 @@ impl Extras {
|
||||
pub fn set_key(&mut self, name: &str, value: String) -> bool {
|
||||
match name {
|
||||
SORT_KEY => self.sort_key = value,
|
||||
crate::pvp::HUD_EDITOR_KEY => self.pvp.editor_key = value,
|
||||
_ => return false,
|
||||
}
|
||||
true
|
||||
@@ -598,7 +603,7 @@ pub enum Item {
|
||||
/// Shown only while cosmetics are enabled (`horizon_cosmetics::enabled`).
|
||||
CosmeticsMine,
|
||||
CosmeticsOthers,
|
||||
CosmeticsAuras,
|
||||
CosmeticsHideFx,
|
||||
CosmeticsSolo,
|
||||
HorizonMarks,
|
||||
HorizonMe,
|
||||
@@ -638,7 +643,7 @@ pub fn rows() -> Vec<Result<Item, &'static str>> {
|
||||
Ok(CosmeticsMine),
|
||||
Ok(CosmeticsSolo),
|
||||
Ok(CosmeticsOthers),
|
||||
Ok(CosmeticsAuras),
|
||||
Ok(CosmeticsHideFx),
|
||||
Ok(HorizonMarks),
|
||||
Ok(HorizonMe),
|
||||
]);
|
||||
@@ -672,7 +677,7 @@ impl Item {
|
||||
Item::Pregen => "horizon.extras.pregen",
|
||||
Item::CosmeticsMine => "horizon.extras.cosmetics_mine",
|
||||
Item::CosmeticsOthers => "horizon.extras.cosmetics_others",
|
||||
Item::CosmeticsAuras => "horizon.extras.cosmetics_auras",
|
||||
Item::CosmeticsHideFx => "horizon.extras.cosmetics_hide_fx",
|
||||
Item::CosmeticsSolo => "horizon.extras.cosmetics_solo",
|
||||
Item::HorizonMarks => "horizon.extras.horizon_marks",
|
||||
Item::HorizonMe => "horizon.extras.horizon_me",
|
||||
@@ -700,12 +705,12 @@ impl Item {
|
||||
Item::Splitscreen => "horizon.extras.splitscreen.tooltip",
|
||||
Item::ChatAnimation => "horizon.extras.chat_animation.tooltip",
|
||||
Item::ChatIndicator => "horizon.extras.chat_indicator.tooltip",
|
||||
Item::WheelCyclesSettings => "horizon.extras.wheel_cycles_settings.tooltip",
|
||||
Item::WheelCyclesSettings => "horizon.extras.wheel_cycles_list_settings.tooltip",
|
||||
Item::PackBrowser => "horizon.extras.pack_browser.tooltip",
|
||||
Item::Pregen => "horizon.extras.pregen.tooltip",
|
||||
Item::CosmeticsMine => "horizon.extras.cosmetics_mine.tooltip",
|
||||
Item::CosmeticsOthers => "horizon.extras.cosmetics_others.tooltip",
|
||||
Item::CosmeticsAuras => "horizon.extras.cosmetics_auras.tooltip",
|
||||
Item::CosmeticsHideFx => "horizon.extras.cosmetics_hide_fx.tooltip",
|
||||
Item::CosmeticsSolo => "horizon.extras.cosmetics_solo.tooltip",
|
||||
Item::HorizonMarks => "horizon.extras.horizon_marks.tooltip",
|
||||
Item::HorizonMe => "horizon.extras.horizon_me.tooltip",
|
||||
@@ -747,7 +752,7 @@ impl Item {
|
||||
Item::Pregen => "Chunk pregeneration",
|
||||
Item::CosmeticsMine => "My cosmetics",
|
||||
Item::CosmeticsOthers => "Other players' cosmetics",
|
||||
Item::CosmeticsAuras => "Others' auras and trails",
|
||||
Item::CosmeticsHideFx => "Hide other players' trails and auras",
|
||||
Item::CosmeticsSolo => "In single player",
|
||||
Item::HorizonMarks => "Horizon logos",
|
||||
Item::HorizonMe => "Show me as Horizon",
|
||||
@@ -766,7 +771,7 @@ impl Item {
|
||||
Fullbright | DynamicLights | OwnNameTag | HideBlockParticles | MouseTweaks | Sort
|
||||
| AutoReconnect | NoAi | AgentControl | ReproKeys | DiscordPresence | Splitscreen
|
||||
| ChatAnimation | WheelCyclesSettings | MusicPage | PackBrowser | CosmeticsMine
|
||||
| CosmeticsOthers | CosmeticsAuras | CosmeticsSolo | HorizonMarks | HorizonMe
|
||||
| CosmeticsOthers | CosmeticsHideFx | CosmeticsSolo | HorizonMarks | HorizonMe
|
||||
| Pregen => {
|
||||
return None;
|
||||
}
|
||||
@@ -834,7 +839,9 @@ impl Item {
|
||||
Item::CosmeticsOthers => {
|
||||
"What other Horizon players wear: everything, only their name tag styles, or nothing. Hidden unless you own cosmetics, until you choose. Hidden, the game never asks what they wear."
|
||||
}
|
||||
Item::CosmeticsAuras => "Auras and trails other players wear. Off by default.",
|
||||
Item::CosmeticsHideFx => {
|
||||
"Other players' trails and auras stay hidden; their other cosmetics still show. Off by default."
|
||||
}
|
||||
Item::CosmeticsSolo => {
|
||||
"Show your cosmetics and your name tag's badge in your single-player worlds too (your name tag with Own name tag). Off: only on servers."
|
||||
}
|
||||
@@ -872,7 +879,7 @@ impl Item {
|
||||
| Item::PackBrowser => Some(Cost::None),
|
||||
Item::CosmeticsMine
|
||||
| Item::CosmeticsOthers
|
||||
| Item::CosmeticsAuras
|
||||
| Item::CosmeticsHideFx
|
||||
| Item::CosmeticsSolo
|
||||
| Item::HorizonMarks
|
||||
| Item::HorizonMe => Some(Cost::Low),
|
||||
@@ -902,7 +909,7 @@ impl Item {
|
||||
Item::WheelCyclesSettings => e.wheel_cycles_settings,
|
||||
Item::PackBrowser => e.pack_browser,
|
||||
Item::Pregen => e.pregen,
|
||||
Item::CosmeticsAuras => e.cosmetics_auras,
|
||||
Item::CosmeticsHideFx => e.cosmetics_hide_fx,
|
||||
Item::CosmeticsSolo => e.cosmetics_solo,
|
||||
Item::HorizonMarks => e.horizon_marks,
|
||||
Item::HorizonMe => e.horizon_listed,
|
||||
@@ -967,7 +974,7 @@ impl Item {
|
||||
let o = e.effective_others();
|
||||
e.cosmetics_others = Some(if back { o.next().next() } else { o.next() });
|
||||
}
|
||||
Item::CosmeticsAuras => e.cosmetics_auras = !e.cosmetics_auras,
|
||||
Item::CosmeticsHideFx => e.cosmetics_hide_fx = !e.cosmetics_hide_fx,
|
||||
Item::CosmeticsSolo => e.cosmetics_solo = !e.cosmetics_solo,
|
||||
Item::HorizonMarks => e.horizon_marks = !e.horizon_marks,
|
||||
Item::HorizonMe => e.horizon_listed = !e.horizon_listed,
|
||||
@@ -979,6 +986,7 @@ impl Item {
|
||||
pub fn key_name(self) -> Option<&'static str> {
|
||||
match self {
|
||||
Item::SortKey => Some(SORT_KEY),
|
||||
Item::Pvp(crate::pvp::Opt::HudEditorKey) => Some(crate::pvp::HUD_EDITOR_KEY),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -996,7 +1004,7 @@ mod tests {
|
||||
..Extras::default()
|
||||
};
|
||||
assert_eq!(
|
||||
(e.effective_others(), e.cosmetics_auras, e.cosmetics_mine),
|
||||
(e.effective_others(), e.cosmetics_hide_fx, e.cosmetics_mine),
|
||||
(Others::Shown, false, true)
|
||||
);
|
||||
Item::CosmeticsOthers.cycle(&mut e, false);
|
||||
@@ -1055,6 +1063,16 @@ mod tests {
|
||||
assert_eq!(hidden.effective_others(), Others::Hidden);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_saved_wheel_setting_from_before_the_default_changed_is_dropped() {
|
||||
assert!(!Extras::parse("wheelCyclesSettings:true\n").wheel_cycles_settings);
|
||||
let on = Extras {
|
||||
wheel_cycles_settings: true,
|
||||
..Extras::default()
|
||||
};
|
||||
assert!(Extras::parse(&on.serialize()).wheel_cycles_settings);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn everything_is_off_by_default_and_round_trips() {
|
||||
let d = Extras::default();
|
||||
@@ -1063,7 +1081,7 @@ mod tests {
|
||||
assert!(!d.mouse_tweaks && !d.sort && !d.own_name_tag && !d.ui_sound_reserve);
|
||||
assert!(!d.hide_block_particles && !d.pregen);
|
||||
assert!(!d.music_page && d.music_off.is_empty());
|
||||
assert!(d.discord_presence && d.wheel_cycles_settings);
|
||||
assert!(d.discord_presence && !d.wheel_cycles_settings);
|
||||
assert_eq!(Extras::parse(""), d);
|
||||
assert_eq!(Extras::parse(&d.serialize()), d);
|
||||
let mut e = d.clone();
|
||||
@@ -1107,7 +1125,7 @@ mod tests {
|
||||
Item::Splitscreen.cycle(&mut e, false);
|
||||
assert_eq!(Item::Splitscreen.switch(&e), Some(true));
|
||||
Item::WheelCyclesSettings.cycle(&mut e, false);
|
||||
assert_eq!(Item::WheelCyclesSettings.switch(&e), Some(false));
|
||||
assert_eq!(Item::WheelCyclesSettings.switch(&e), Some(true));
|
||||
e.music_off = vec![
|
||||
"minecraft:music/game/calm1".into(),
|
||||
"minecraft:music.menu".into(),
|
||||
|
||||
@@ -12227,7 +12227,8 @@ mod tests {
|
||||
}
|
||||
}
|
||||
/// Every WGSL shader file translates to HLSL as wgpu's DirectX 12 backend
|
||||
/// writes it for FXC (shader model 5.1), entry point by entry point.
|
||||
/// writes it for FXC (shader model 5.1), entry point by entry point, and
|
||||
/// every shader the OpenGL backend runs also translates to GLSL.
|
||||
#[test]
|
||||
fn shaders_translate_to_hlsl_for_directx_12_and_glsl_for_opengl() {
|
||||
fn wgsl_files(dir: &std::path::Path, out: &mut Vec<std::path::PathBuf>) {
|
||||
@@ -12277,6 +12278,14 @@ mod tests {
|
||||
}
|
||||
}
|
||||
let mut glsl_checked = 0;
|
||||
// Shaders the OpenGL backend runs must translate: skipping the ones
|
||||
// that fail hides exactly the bug where a shader GL needs cannot
|
||||
// compile (#9448).
|
||||
let mut glsl_failed = Vec::new();
|
||||
let mut translated = std::collections::HashSet::new();
|
||||
// naga's GLSL writer gives an image one sampler, and the HUD's glint
|
||||
// samples one image through two; one failed entry does not hide the rest.
|
||||
let known_unsupported = [("hud.wgsl", "fs_cit_glint")];
|
||||
for (path, text) in sources {
|
||||
// entity.wgsl comes after the overlay's constants (entity_shader).
|
||||
let module = naga::front::wgsl::parse_str(&text)
|
||||
@@ -12377,11 +12386,18 @@ mod tests {
|
||||
Default::default(),
|
||||
)
|
||||
.and_then(|mut w| w.write());
|
||||
// Some shaders use features GLSL cannot express (depth textureLoad): GL does
|
||||
// not run them. The ones it does run must translate, checked below.
|
||||
if glsl_result.is_err() {
|
||||
let name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
|
||||
if let Err(e) = glsl_result {
|
||||
if !known_unsupported.contains(&(name, entry.name.as_str())) {
|
||||
glsl_failed.push(format!(
|
||||
"{} {} (GLSL {version}): {e}",
|
||||
path.display(),
|
||||
entry.name
|
||||
));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
translated.insert((name.to_owned(), entry.name.clone()));
|
||||
glsl_checked += 1;
|
||||
assert!(
|
||||
!glsl.contains("ARB_arrays_of_arrays"),
|
||||
@@ -12392,6 +12408,19 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
glsl_failed.is_empty(),
|
||||
"shaders the OpenGL backend runs must translate:\n{}",
|
||||
glsl_failed.join("\n")
|
||||
);
|
||||
// These read the world's depth through `textureLoad`; before #9448 their
|
||||
// depth binding could not translate, so TAA and the blit never came up.
|
||||
for (file, entry) in [("world_post.wgsl", "fs_taa"), ("pack_post.wgsl", "fs_blit")] {
|
||||
assert!(
|
||||
translated.contains(&(file.to_owned(), entry.to_owned())),
|
||||
"{file} {entry} must translate to GLSL"
|
||||
);
|
||||
}
|
||||
assert!(glsl_checked >= 10, "{glsl_checked} GLSL translations");
|
||||
}
|
||||
#[test]
|
||||
|
||||
@@ -287,7 +287,7 @@ impl Cull {
|
||||
0,
|
||||
fragment,
|
||||
wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Depth,
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
|
||||
@@ -114,8 +114,10 @@ fn see(t: u32) {
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 1's depth, where the test runs in the shader (gfx/cull.rs).
|
||||
@group(1) @binding(0) var depth: texture_depth_2d;
|
||||
// Phase 1's depth, where the test runs in the shader (gfx/cull.rs). A depth
|
||||
// view bound as an unfilterable float: naga's GLSL backend cannot `textureLoad`
|
||||
// a depth texture, and the texel is the same value.
|
||||
@group(1) @binding(0) var depth: texture_2d<f32>;
|
||||
|
||||
// The world pass's reversed depth test, greater or equal passing, made here:
|
||||
// where it cannot be forced before a shader that writes storage, a fragment
|
||||
@@ -123,7 +125,7 @@ fn see(t: u32) {
|
||||
// runs only for the fragments that pass it.
|
||||
@fragment
|
||||
fn fs_box(in: Box) {
|
||||
if in.position.z >= textureLoad(depth, vec2<u32>(in.position.xy), 0) {
|
||||
if in.position.z >= textureLoad(depth, vec2<u32>(in.position.xy), 0).r {
|
||||
see(in.test);
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,9 @@ struct Params {
|
||||
|
||||
@group(0) @binding(0) var<uniform> params: Params;
|
||||
@group(0) @binding(1) var linear_clamp: sampler;
|
||||
@group(0) @binding(2) var input_depth: texture_depth_2d;
|
||||
// A depth view bound as unfilterable float: naga's GLSL backend cannot
|
||||
// `textureLoad` a depth texture, and the texel is the same value.
|
||||
@group(0) @binding(2) var input_depth: texture_2d<f32>;
|
||||
@group(0) @binding(3) var current_color: texture_2d<f32>;
|
||||
@group(0) @binding(4) var previous_color: texture_2d<f32>;
|
||||
@group(0) @binding(5) var dilated_depth_out: texture_storage_2d<r32float, write>;
|
||||
@@ -333,7 +335,7 @@ fn load_input_depth(p: vec2<i32>) -> f32 {
|
||||
if !is_on_screen(p, params.render_size) {
|
||||
return 0.0;
|
||||
}
|
||||
return textureLoad(input_depth, p, 0);
|
||||
return textureLoad(input_depth, p, 0).r;
|
||||
}
|
||||
|
||||
@compute @workgroup_size(8, 8)
|
||||
|
||||
@@ -12,7 +12,9 @@ struct Params {
|
||||
};
|
||||
|
||||
@group(0) @binding(0) var<uniform> params: Params;
|
||||
@group(0) @binding(1) var depth: texture_depth_2d;
|
||||
// A depth view bound as unfilterable float: naga's GLSL backend cannot
|
||||
// `textureLoad` a depth texture, and the texel is the same value.
|
||||
@group(0) @binding(1) var depth: texture_2d<f32>;
|
||||
@group(0) @binding(2) var atlas: texture_2d<f32>;
|
||||
@group(0) @binding(3) var atlas_sampler: sampler;
|
||||
|
||||
@@ -38,7 +40,7 @@ struct Out {
|
||||
|
||||
// xy: the motion vector in UV (to where the pixel was last frame, FSR's convention), z: 1.
|
||||
@fragment fn fs_motion(in: Out) -> @location(0) vec4<f32> {
|
||||
let stored = textureLoad(depth, vec2<i32>(in.clip.xy), 0);
|
||||
let stored = textureLoad(depth, vec2<i32>(in.clip.xy), 0).r;
|
||||
let tolerance = stored * 1e-3 + 1e-7;
|
||||
// Not what the world shows here: a transparent texel, or behind something.
|
||||
if in.translucent != 0u {
|
||||
|
||||
@@ -16,14 +16,16 @@ const MAPPED: u8 = 3;
|
||||
const ROW: u32 = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
|
||||
|
||||
const SHADER: &str = "
|
||||
@group(0) @binding(0) var depth: texture_depth_2d;
|
||||
// A depth view bound as unfilterable float: naga's GLSL backend cannot
|
||||
// `textureLoad` a depth texture, and the texel is the same value.
|
||||
@group(0) @binding(0) var depth: texture_2d<f32>;
|
||||
@vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> {
|
||||
let p = vec2<f32>(f32((i << 1u) & 2u), f32(i & 2u));
|
||||
return vec4<f32>(p * 2.0 - 1.0, 0.0, 1.0);
|
||||
}
|
||||
@fragment fn fs() -> @location(0) vec4<f32> {
|
||||
let centre = vec2<i32>(textureDimensions(depth) / 2u);
|
||||
return vec4<f32>(textureLoad(depth, centre, 0), 0.0, 0.0, 1.0);
|
||||
return vec4<f32>(textureLoad(depth, centre, 0).r, 0.0, 0.0, 1.0);
|
||||
}
|
||||
";
|
||||
|
||||
@@ -64,7 +66,7 @@ impl CenterDepth {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Depth,
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
|
||||
@@ -433,6 +433,14 @@ struct Mips {
|
||||
levels: Vec<(wgpu::TextureView, wgpu::BindGroup)>,
|
||||
}
|
||||
|
||||
/// The picture a pack's `final` draws at the world's size while the window is another: it is
|
||||
/// copied onto the window once, after the pack's passes, as the game's own post pass does
|
||||
/// without a pack.
|
||||
struct FinalTarget {
|
||||
size: [u32; 2],
|
||||
view: wgpu::TextureView,
|
||||
}
|
||||
|
||||
struct Targets {
|
||||
size: [u32; 2],
|
||||
/// A and B of each buffer used.
|
||||
@@ -664,6 +672,12 @@ pub struct PackPost {
|
||||
blit_group: Option<([wgpu::TextureView; 2], wgpu::BindGroup)>,
|
||||
/// Shows colortex0 when the pack has no `final` (OQ-5).
|
||||
copy: (wgpu::RenderPipeline, wgpu::BindGroupLayout),
|
||||
/// Copies the picture `final` drew at the world's size onto the window: no flip, it is
|
||||
/// already in the screen's rows.
|
||||
upscale: (wgpu::RenderPipeline, wgpu::BindGroupLayout),
|
||||
/// The picture `final` draws while the world's size is not the window's.
|
||||
final_target: Option<FinalTarget>,
|
||||
screen: wgpu::TextureFormat,
|
||||
uniforms: FrameUniforms,
|
||||
}
|
||||
|
||||
@@ -2724,7 +2738,7 @@ impl PackPost {
|
||||
},
|
||||
&[
|
||||
texture_entry(0, float),
|
||||
texture_entry(1, wgpu::TextureSampleType::Depth),
|
||||
texture_entry(1, wgpu::TextureSampleType::Float { filterable: false }),
|
||||
],
|
||||
&blit_targets,
|
||||
// GL's window depth, written for depthtex0 and the terrain's depth test.
|
||||
@@ -2751,6 +2765,20 @@ impl PackPost {
|
||||
&[target(screen)],
|
||||
None,
|
||||
);
|
||||
let upscale = helper(
|
||||
device,
|
||||
"vs_screen",
|
||||
"fs_copy",
|
||||
&[
|
||||
texture_entry(0, float),
|
||||
entry(
|
||||
2,
|
||||
wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
),
|
||||
],
|
||||
&[target(screen)],
|
||||
None,
|
||||
);
|
||||
let filterable = |b: usize| {
|
||||
formats[b].sample_type(None, Some(device.features()))
|
||||
== Some(wgpu::TextureSampleType::Float { filterable: true })
|
||||
@@ -2952,6 +2980,9 @@ impl PackPost {
|
||||
}),
|
||||
blit,
|
||||
copy,
|
||||
upscale,
|
||||
final_target: None,
|
||||
screen,
|
||||
uniforms: {
|
||||
let mut u = FrameUniforms::default();
|
||||
u.atlas = world.atlas.map_or([0; 2], |a| a.1);
|
||||
@@ -4484,6 +4515,35 @@ impl PackPost {
|
||||
terrain: &mut TerrainDraw<'_>,
|
||||
) {
|
||||
self.resize(device, size);
|
||||
// A resolution scale leaves the picture smaller (or larger) than the window: the pack's
|
||||
// `final` draws it at the picture's size, and one copy takes it to the window (#9291).
|
||||
let final_view = if size != window && self.passes.iter().any(|p| p.group == Group::Final) {
|
||||
if self.final_target.as_ref().is_none_or(|t| t.size != size) {
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("Shader pack final"),
|
||||
size: wgpu::Extent3d {
|
||||
width: size[0],
|
||||
height: size[1],
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: self.screen,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
|
||||
| wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
});
|
||||
self.final_target = Some(FinalTarget {
|
||||
size,
|
||||
view: texture.create_view(&Default::default()),
|
||||
});
|
||||
}
|
||||
self.final_target.as_ref().map(|t| t.view.clone())
|
||||
} else {
|
||||
self.final_target = None;
|
||||
None
|
||||
};
|
||||
if let Some((layout, ids, rects)) = &self.block_ids {
|
||||
// A page that grew has a new buffer.
|
||||
self.page_groups
|
||||
@@ -4788,8 +4848,8 @@ impl PackPost {
|
||||
self.mips_valid |= stale;
|
||||
let final_pass = pass.group == Group::Final;
|
||||
// A pass writing buffers of a size of their own draws at that size (they share it).
|
||||
// `final` draws the picture at the world's size; a copy takes it to the window.
|
||||
let out = match pass.writes.first() {
|
||||
_ if final_pass => window,
|
||||
Some(&(_, b)) => self.targets.as_ref().map_or(size, |t| t.sizes[b]),
|
||||
None => size,
|
||||
};
|
||||
@@ -4812,7 +4872,7 @@ impl PackPost {
|
||||
overwrite
|
||||
};
|
||||
if final_pass {
|
||||
attachments.push(attach(screen, overwrite));
|
||||
attachments.push(attach(final_view.as_ref().unwrap_or(screen), overwrite));
|
||||
shown = true;
|
||||
} else if pass.sink {
|
||||
attachments.push(attach(&targets.sink, ops));
|
||||
@@ -4895,6 +4955,31 @@ impl PackPost {
|
||||
rp.set_pipeline(&self.copy.0);
|
||||
rp.set_bind_group(0, &group, &[]);
|
||||
rp.draw(0..3, 0..1);
|
||||
} else if let Some(picture) = &final_view {
|
||||
// The picture at the world's size onto the window: one upscale, smoothly.
|
||||
let group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("Shader pack final"),
|
||||
layout: &self.upscale.1,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: wgpu::BindingResource::TextureView(picture),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: wgpu::BindingResource::Sampler(&self.linear),
|
||||
},
|
||||
],
|
||||
});
|
||||
let mut rp = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("Shader pack final"),
|
||||
color_attachments: &[attach(screen, overwrite)],
|
||||
timestamp_writes: self.timing.as_mut().and_then(|t| t.writes("final copy")),
|
||||
..Default::default()
|
||||
});
|
||||
rp.set_pipeline(&self.upscale.0);
|
||||
rp.set_bind_group(0, &group, &[]);
|
||||
rp.draw(0..3, 0..1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ fn vs_full(@builtin(vertex_index) i: u32) -> @builtin(position) vec4<f32> {
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var source: texture_2d<f32>;
|
||||
@group(0) @binding(1) var world_depth: texture_depth_2d;
|
||||
// A depth view read as an unfilterable float: naga's GLSL backend cannot
|
||||
// `textureLoad` a depth texture, and the texel is the same either way.
|
||||
@group(0) @binding(1) var world_depth: texture_2d<f32>;
|
||||
|
||||
struct World {
|
||||
@location(0) color: vec4<f32>,
|
||||
@@ -24,7 +26,7 @@ fn fs_blit(@builtin(position) p: vec4<f32>) -> World {
|
||||
let at = vec2<i32>(i32(p.x), size.y - 1 - i32(p.y));
|
||||
var out: World;
|
||||
out.color = textureLoad(source, at, 0);
|
||||
out.depth = 1.0 - textureLoad(world_depth, at, 0);
|
||||
out.depth = 1.0 - textureLoad(world_depth, at, 0).r;
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -49,6 +51,13 @@ fn vs_uv(@builtin(vertex_index) i: u32) -> Full {
|
||||
return Full(vec4<f32>(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, 0.0, 1.0), vec2<f32>(uv.x, 1.0 - uv.y));
|
||||
}
|
||||
|
||||
// The screen's own rows: a picture a shader pack's `final` drew (not a GL-rows buffer).
|
||||
@vertex
|
||||
fn vs_screen(@builtin(vertex_index) i: u32) -> Full {
|
||||
let uv = vec2<f32>(f32((i << 1u) & 2u), f32(i & 2u));
|
||||
return Full(vec4<f32>(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, 0.0, 1.0), uv);
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_copy(v: Full) -> @location(0) vec4<f32> {
|
||||
return textureSample(source, linear, v.uv);
|
||||
|
||||
@@ -43,6 +43,64 @@ fn a_pack_without_final_reads_the_replaced_world_colour() {
|
||||
replacement(Change::Colour, false);
|
||||
}
|
||||
|
||||
/// A Resolution Scale leaves the pack's picture smaller than the window. Its `final` draws at
|
||||
/// the picture's size and one copy upsizes it to the window (#9291), so a final that fetches
|
||||
/// its pixel by `gl_FragCoord` (as Photon's does) covers every pixel, not a quarter of them.
|
||||
#[test]
|
||||
fn a_scaled_picture_fills_the_whole_window() {
|
||||
let instance = crate::gfx::test_gpu::instance();
|
||||
let Ok(adapter) = pollster::block_on(instance.request_adapter(&Default::default())) else {
|
||||
return;
|
||||
};
|
||||
let Ok((device, queue)) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
|
||||
required_limits: adapter.limits(),
|
||||
..Default::default()
|
||||
})) else {
|
||||
return;
|
||||
};
|
||||
eprintln!("scaled picture adapter: {:?}", adapter.get_info());
|
||||
let picture = [4, 2];
|
||||
let window = [8, 4];
|
||||
let colour = texture(&device, picture, T::Rgba8Unorm);
|
||||
let depth = texture(&device, picture, T::Depth32Float);
|
||||
clear(&device, &queue, &colour, &depth, [32, 64, 16, 255], 0.25);
|
||||
let mut post = pack_with(
|
||||
&device,
|
||||
&queue,
|
||||
&adapter,
|
||||
&colour,
|
||||
&[
|
||||
(
|
||||
"final.vsh",
|
||||
"#version 120\nvoid main(){gl_Position=ftransform();}",
|
||||
),
|
||||
(
|
||||
"final.fsh",
|
||||
"#version 330 compatibility\nuniform sampler2D colortex0;\nvoid main(){gl_FragColor=texelFetch(colortex0,ivec2(gl_FragCoord.xy),0);}",
|
||||
),
|
||||
],
|
||||
);
|
||||
let hud = HudBatch::new(window);
|
||||
let pixels = render(
|
||||
&device,
|
||||
&queue,
|
||||
&mut post,
|
||||
&colour,
|
||||
&depth,
|
||||
&frame(&hud),
|
||||
picture,
|
||||
window,
|
||||
);
|
||||
// Independently specified: the world's colour at every window pixel.
|
||||
let wrong = pixels.iter().filter(|p| **p != [32, 64, 16, 255]).count();
|
||||
assert_eq!(
|
||||
wrong,
|
||||
0,
|
||||
"{wrong} of {} pixels are not the picture",
|
||||
pixels.len()
|
||||
);
|
||||
}
|
||||
|
||||
fn replacement(change: Change, final_shader: bool) {
|
||||
let instance = crate::gfx::test_gpu::instance();
|
||||
let Ok(adapter) = pollster::block_on(instance.request_adapter(&Default::default())) else {
|
||||
@@ -62,7 +120,9 @@ fn replacement(change: Change, final_shader: bool) {
|
||||
let mut post = pack(&device, &queue, &adapter, &colour, final_shader);
|
||||
let hud = HudBatch::new(size);
|
||||
let frame = frame(&hud);
|
||||
let initial = render(&device, &queue, &mut post, &colour, &depth, &frame, size);
|
||||
let initial = render(
|
||||
&device, &queue, &mut post, &colour, &depth, &frame, size, size,
|
||||
);
|
||||
let expected = if final_shader {
|
||||
[32, 64, 191, 255]
|
||||
} else {
|
||||
@@ -93,9 +153,13 @@ fn replacement(change: Change, final_shader: bool) {
|
||||
0.75
|
||||
};
|
||||
clear(&device, &queue, &colour, &depth, pixel, reverse_depth);
|
||||
let actual = render(&device, &queue, &mut post, &colour, &depth, &frame, size);
|
||||
let actual = render(
|
||||
&device, &queue, &mut post, &colour, &depth, &frame, size, size,
|
||||
);
|
||||
let mut fresh = pack(&device, &queue, &adapter, &colour, final_shader);
|
||||
let reference = render(&device, &queue, &mut fresh, &colour, &depth, &frame, size);
|
||||
let reference = render(
|
||||
&device, &queue, &mut fresh, &colour, &depth, &frame, size, size,
|
||||
);
|
||||
// Independently specified RG channels and GL depth (1 - reverse-Z), in UNORM8.
|
||||
let expected = if !final_shader {
|
||||
[160, 96, 48, 255]
|
||||
@@ -178,13 +242,8 @@ fn pack(
|
||||
lightmap: &wgpu::TextureView,
|
||||
final_shader: bool,
|
||||
) -> PackPost {
|
||||
let mut zip = zip::ZipWriter::new(Cursor::new(Vec::new()));
|
||||
let options = zip::write::SimpleFileOptions::default();
|
||||
zip.start_file("shaders/shaders.properties", options)
|
||||
.unwrap();
|
||||
zip.write_all(b"oldLighting=false\n").unwrap();
|
||||
if final_shader {
|
||||
for (name, source) in [
|
||||
let programs: &[(&str, &str)] = if final_shader {
|
||||
&[
|
||||
(
|
||||
"final.vsh",
|
||||
"#version 120\nvarying vec2 uv;\nvoid main(){gl_Position=ftransform();uv=gl_MultiTexCoord0.xy;}",
|
||||
@@ -193,10 +252,29 @@ fn pack(
|
||||
"final.fsh",
|
||||
"#version 120\nvarying vec2 uv;uniform sampler2D colortex0;uniform sampler2D depthtex0;\nvoid main(){gl_FragColor=vec4(texture2D(colortex0,uv).rg,texture2D(depthtex0,uv).r,1.0);}",
|
||||
),
|
||||
] {
|
||||
zip.start_file(format!("shaders/{name}"), options).unwrap();
|
||||
zip.write_all(source.as_bytes()).unwrap();
|
||||
}
|
||||
]
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
pack_with(device, queue, adapter, lightmap, programs)
|
||||
}
|
||||
|
||||
/// A pack of `programs` (`name`, GLSL source) over `lightmap`.
|
||||
fn pack_with(
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
adapter: &wgpu::Adapter,
|
||||
lightmap: &wgpu::TextureView,
|
||||
programs: &[(&str, &str)],
|
||||
) -> PackPost {
|
||||
let mut zip = zip::ZipWriter::new(Cursor::new(Vec::new()));
|
||||
let options = zip::write::SimpleFileOptions::default();
|
||||
zip.start_file("shaders/shaders.properties", options)
|
||||
.unwrap();
|
||||
zip.write_all(b"oldLighting=false\n").unwrap();
|
||||
for (name, source) in programs {
|
||||
zip.start_file(format!("shaders/{name}"), options).unwrap();
|
||||
zip.write_all(source.as_bytes()).unwrap();
|
||||
}
|
||||
let pack = horizon_shaders::Pack::from_zip_bytes(
|
||||
"original-world-inputs",
|
||||
@@ -241,12 +319,13 @@ fn render(
|
||||
depth: &wgpu::TextureView,
|
||||
frame: &Frame,
|
||||
size: [u32; 2],
|
||||
window: [u32; 2],
|
||||
) -> Vec<[u8; 4]> {
|
||||
let screen = texture(device, size, T::Rgba8Unorm);
|
||||
let screen = texture(device, window, T::Rgba8Unorm);
|
||||
let stride = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
|
||||
let read = device.create_buffer(&wgpu::BufferDescriptor {
|
||||
label: None,
|
||||
size: u64::from(stride * size[1]),
|
||||
size: u64::from(stride * window[1]),
|
||||
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
@@ -261,7 +340,7 @@ fn render(
|
||||
frame,
|
||||
&Default::default(),
|
||||
size,
|
||||
size,
|
||||
window,
|
||||
&[],
|
||||
&mut |_, _, _, _| {},
|
||||
);
|
||||
@@ -285,7 +364,7 @@ fn render(
|
||||
.unwrap()
|
||||
.chunks_exact(stride as usize)
|
||||
.flat_map(|row| {
|
||||
row[..size[0] as usize * 4]
|
||||
row[..window[0] as usize * 4]
|
||||
.as_chunks::<4>()
|
||||
.0
|
||||
.iter()
|
||||
|
||||
@@ -43,9 +43,7 @@ struct TaaUniform {
|
||||
jitter: [f32; 4],
|
||||
}
|
||||
|
||||
/// TAA's GPU resources, absent when the device cannot compile the shader
|
||||
/// (the OpenGL backend's naga GLSL emitter does not support `textureLoad`
|
||||
/// from depth textures).
|
||||
/// TAA's GPU resources, absent when the device cannot compile the shader.
|
||||
struct TaaResources {
|
||||
layout: wgpu::BindGroupLayout,
|
||||
pipeline: wgpu::RenderPipeline,
|
||||
@@ -146,9 +144,6 @@ impl Post {
|
||||
cache: None,
|
||||
})
|
||||
};
|
||||
// The OpenGL backend's GLSL translator does not support textureLoad
|
||||
// from depth textures: on devices running through OpenGL (such as older
|
||||
// Intel GPUs on Linux), TAA cannot compile its pipeline.
|
||||
let taa_resources = {
|
||||
let taa_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("Horizon TAA bindings"),
|
||||
@@ -161,7 +156,9 @@ impl Post {
|
||||
count: None,
|
||||
},
|
||||
texture(2, float),
|
||||
texture(3, wgpu::TextureSampleType::Depth),
|
||||
// A depth view through an unfilterable float binding: the
|
||||
// texel is the same, and the GLSL backend can read it.
|
||||
texture(3, wgpu::TextureSampleType::Float { filterable: false }),
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 4,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
|
||||
@@ -1840,9 +1840,10 @@ impl GraphicsUi {
|
||||
self.scroll.release();
|
||||
self.dragging.take().is_some()
|
||||
}
|
||||
/// The mouse wheel at the cursor. It scrolls the list, except that a hovered value
|
||||
/// stepped by `<` `>` takes the step (`CycleButton.mouseScrolled`: up goes back)
|
||||
/// while the "Scroll wheel changes settings" extra is on. A slider, a tick box and a
|
||||
/// The mouse wheel at the cursor. It scrolls the list, as vanilla's does
|
||||
/// (`AbstractScrollArea.mouseScrolled`), except that a hovered value stepped by
|
||||
/// `<` `>` takes the step (up goes back) while the "Scroll wheel changes settings"
|
||||
/// extra is on. A slider, a tick box and a
|
||||
/// focused row don't take it, wherever the focus came from: the keyboard and a
|
||||
/// controller step them with Left and Right.
|
||||
pub fn wheel(
|
||||
@@ -2526,6 +2527,25 @@ mod tests {
|
||||
assert_eq!(ui.scroll.amount, 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_hovered_value_takes_the_wheel_only_with_the_extra_on() {
|
||||
let mut s = Settings::default();
|
||||
let mut ui = GraphicsUi::default();
|
||||
let item = Item::Vanilla(Opt::AttackIndicator);
|
||||
let r = row(&mut ui, &s, item);
|
||||
let before = format!("{s:?}");
|
||||
let actions = ui.wheel(&mut s, SIZE, (r[0] + 4.0, r[1] + 4.0), FB, -1.0);
|
||||
assert_eq!(format!("{s:?}"), before, "the hovered value stays put");
|
||||
assert!(actions.is_empty());
|
||||
|
||||
s.extras.wheel_cycles_settings = true;
|
||||
let r = row(&mut ui, &s, item);
|
||||
let before = format!("{s:?}");
|
||||
let actions = ui.wheel(&mut s, SIZE, (r[0] + 4.0, r[1] + 4.0), FB, -1.0);
|
||||
assert_ne!(format!("{s:?}"), before, "the hovered value steps");
|
||||
assert!(!actions.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_arrows_move_between_rows_in_view_and_over_to_the_page_list() {
|
||||
let mut s = Settings::default();
|
||||
|
||||
@@ -1232,7 +1232,8 @@ impl HudAtlas {
|
||||
.ok();
|
||||
let animation = animation_of(mcmeta.as_deref());
|
||||
// An animated strip keeps its first frame.
|
||||
let frames = name.starts_with("block/") || name.starts_with("item/");
|
||||
let frames =
|
||||
animation.is_some() || name.starts_with("block/") || name.starts_with("item/");
|
||||
let (w, h, pixels) = if frames {
|
||||
first_frame(image, animation.as_ref())
|
||||
} else {
|
||||
@@ -1313,12 +1314,13 @@ impl HudAtlas {
|
||||
edition.3 = pixels;
|
||||
}
|
||||
images.push(("horizon/white".into(), 4, 4, vec![255; 64]));
|
||||
images.push((
|
||||
crate::pad_cursor::SPRITE.into(),
|
||||
crate::pad_cursor::SIZE,
|
||||
crate::pad_cursor::SIZE,
|
||||
crate::pad_cursor::pixels(),
|
||||
));
|
||||
// The controller's art, unless a pack brought its own (walked above, as a mod's GUI
|
||||
// textures are).
|
||||
for image in crate::pad_glyph::images() {
|
||||
if !images.iter().any(|i| i.0 == image.0) {
|
||||
images.push(image);
|
||||
}
|
||||
}
|
||||
if full {
|
||||
Self::slots(&mut images, crate::trims::slot_size(root), mod_image);
|
||||
// MissingTextureAtlasSprite.generateMissingImage: 16x16, magenta
|
||||
@@ -3495,6 +3497,49 @@ mod tests {
|
||||
let banner = atlas.sprites["custom/banner"];
|
||||
assert_eq!(banner.texels((atlas.width, atlas.height)), (16, 32));
|
||||
}
|
||||
#[test]
|
||||
fn an_animated_gui_sprite_packs_its_first_frame_and_animates() {
|
||||
let root =
|
||||
std::env::temp_dir().join(format!("horizon-atlas-gui-anim-{}", std::process::id()));
|
||||
let sprites = root.join("assets/minecraft/textures/gui/sprites/icon");
|
||||
fs::create_dir_all(&sprites).unwrap();
|
||||
// A GUI sprite like icon/music_notes: 4 frames stacked vertically (16x64),
|
||||
// with animation metadata, and a still 16x32 texture beside it.
|
||||
write_strip(
|
||||
&sprites.join("music_notes.png"),
|
||||
16,
|
||||
64,
|
||||
&[
|
||||
[10, 20, 30, 255],
|
||||
[40, 50, 60, 255],
|
||||
[70, 80, 90, 255],
|
||||
[100, 110, 120, 255],
|
||||
],
|
||||
Some(r#"{"animation": {"frametime": 2}}"#),
|
||||
);
|
||||
write_strip(
|
||||
&sprites.join("static_badge.png"),
|
||||
16,
|
||||
32,
|
||||
&[[9, 9, 9, 255]],
|
||||
None,
|
||||
);
|
||||
let atlas = HudAtlas::build(&root).unwrap();
|
||||
let _ = fs::remove_dir_all(&root);
|
||||
let rect = atlas.sprites["gui/sprites/icon/music_notes"];
|
||||
// One frame, not the whole 16x64 strip squeezed into a slot.
|
||||
assert_eq!(rect.texels((atlas.width, atlas.height)), (16, 16));
|
||||
assert_eq!(texel_at(&atlas, rect, 0, 15), [10, 20, 30, 255]);
|
||||
let (_, animation) = atlas
|
||||
.sprite_animations
|
||||
.iter()
|
||||
.find(|(r, _)| r.u0 == rect.u0 && r.v0 == rect.v0)
|
||||
.expect("the gui sprite animates");
|
||||
assert_eq!(animation.frame_count(), 4);
|
||||
// A still texture there stays whole.
|
||||
let static_badge = atlas.sprites["gui/sprites/icon/static_badge"];
|
||||
assert_eq!(static_badge.texels((atlas.width, atlas.height)), (16, 32));
|
||||
}
|
||||
/// A resource root with one plain item texture and these CIT files (relative path, contents).
|
||||
fn cit_root(name: &str, files: &[(&str, &str)]) -> PathBuf {
|
||||
let root =
|
||||
|
||||
@@ -63,6 +63,7 @@ pub mod menu_blur;
|
||||
pub mod mesher;
|
||||
pub mod minimap;
|
||||
pub mod mipmap;
|
||||
pub mod mod_browser;
|
||||
pub mod mod_list;
|
||||
pub mod model_library;
|
||||
pub mod models;
|
||||
@@ -76,6 +77,7 @@ pub mod pack_browser;
|
||||
pub mod pack_screen;
|
||||
pub mod packs;
|
||||
pub mod pad_cursor;
|
||||
pub mod pad_glyph;
|
||||
pub mod par;
|
||||
pub mod particles;
|
||||
pub mod poi_sections;
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
//! What Options > Mods > Browse mods shows (docs/design/mod-hotload.md): the
|
||||
//! registry's content mods this game installs, a search over them, and the one
|
||||
//! whose page is open. The game fills it from a worker
|
||||
//! (`horizon_client::mod_registry`, `game/mod_browser.rs`); the options screen
|
||||
//! only reads it and asks for installs through its events.
|
||||
|
||||
/// One project of the registry, as the list and its page show it.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct Listing {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub summary: String,
|
||||
/// Markdown, shown as plain text.
|
||||
pub description: String,
|
||||
pub authors: Vec<String>,
|
||||
pub license: String,
|
||||
pub categories: Vec<String>,
|
||||
pub tags: Vec<String>,
|
||||
/// The version this game would install, its date and changelog.
|
||||
pub version: String,
|
||||
pub published: String,
|
||||
pub changelog: String,
|
||||
pub downloads: u64,
|
||||
/// The project's page on the website.
|
||||
pub page: String,
|
||||
/// The version installed in this game folder, if any.
|
||||
pub installed: Option<String>,
|
||||
}
|
||||
|
||||
impl Listing {
|
||||
/// Whether a search for `query` (already lower case) finds it: its name,
|
||||
/// id, summary, authors, categories or tags.
|
||||
pub fn matches(&self, query: &str) -> bool {
|
||||
let query = query.trim();
|
||||
query.is_empty()
|
||||
|| [&self.name, &self.id, &self.summary]
|
||||
.into_iter()
|
||||
.chain(&self.authors)
|
||||
.chain(&self.categories)
|
||||
.chain(&self.tags)
|
||||
.any(|field| field.to_lowercase().contains(query))
|
||||
}
|
||||
|
||||
/// Installed, but not the version the registry offers now.
|
||||
pub fn has_update(&self) -> bool {
|
||||
self.installed
|
||||
.as_ref()
|
||||
.is_some_and(|installed| *installed != self.version)
|
||||
}
|
||||
}
|
||||
|
||||
/// Where the browser is.
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub enum Status {
|
||||
/// Not asked yet.
|
||||
#[default]
|
||||
Idle,
|
||||
/// The index is being read.
|
||||
Loading,
|
||||
/// The projects are listed.
|
||||
Ready,
|
||||
/// The index couldn't be read: why, in English (shown inside a translated line).
|
||||
Failed(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ModBrowser {
|
||||
pub status: Status,
|
||||
pub listings: Vec<Listing>,
|
||||
/// The project an install runs for.
|
||||
pub installing: Option<String>,
|
||||
/// The last install's outcome, already translated, for the page it ran on.
|
||||
pub message: Option<(String, bool)>,
|
||||
}
|
||||
|
||||
impl ModBrowser {
|
||||
/// The listings a search for `query` finds, by index into `listings`.
|
||||
pub fn found(&self, query: &str) -> Vec<usize> {
|
||||
let query = query.to_lowercase();
|
||||
(0..self.listings.len())
|
||||
.filter(|&i| self.listings[i].matches(&query))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn listing(&self, id: &str) -> Option<&Listing> {
|
||||
self.listings.iter().find(|l| l.id == id)
|
||||
}
|
||||
}
|
||||
|
||||
/// A project's markdown description as lines of plain text for the page:
|
||||
/// heading marks, emphasis and list bullets dropped, links shown as their text,
|
||||
/// blank lines and fences left out.
|
||||
pub fn plain_lines(markdown: &str) -> Vec<String> {
|
||||
let mut out = Vec::new();
|
||||
for line in markdown.lines() {
|
||||
let line = line.trim();
|
||||
if line.is_empty() || line.starts_with("```") || line.starts_with("<!--") {
|
||||
continue;
|
||||
}
|
||||
let line = line.trim_start_matches('#').trim_start();
|
||||
let line = match line.strip_prefix("- ").or_else(|| line.strip_prefix("* ")) {
|
||||
Some(rest) => format!("• {rest}"),
|
||||
None => line.to_owned(),
|
||||
};
|
||||
let line = links_as_text(&line).replace("**", "").replace('`', "");
|
||||
if !line.is_empty() {
|
||||
out.push(line);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// `[text](url)` → `text`, `` → nothing.
|
||||
fn links_as_text(line: &str) -> String {
|
||||
let mut out = String::with_capacity(line.len());
|
||||
let mut rest = line;
|
||||
while let Some(open) = rest.find('[') {
|
||||
let Some(close) = rest[open..].find("](").map(|c| open + c) else {
|
||||
break;
|
||||
};
|
||||
let Some(end) = rest[close..].find(')').map(|e| close + e) else {
|
||||
break;
|
||||
};
|
||||
let image = open > 0 && rest[..open].ends_with('!');
|
||||
out.push_str(&rest[..if image { open - 1 } else { open }]);
|
||||
if !image {
|
||||
out.push_str(&rest[open + 1..close]);
|
||||
}
|
||||
rest = &rest[end + 1..];
|
||||
}
|
||||
out.push_str(rest);
|
||||
out
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn listing(id: &str, tags: &[&str]) -> Listing {
|
||||
Listing {
|
||||
id: id.into(),
|
||||
name: id.to_uppercase(),
|
||||
summary: format!("All about {id}"),
|
||||
tags: tags.iter().map(|t| (*t).to_owned()).collect(),
|
||||
version: "1.0".into(),
|
||||
..Listing::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_search_finds_names_tags_and_categories_in_any_case() {
|
||||
let mut pipes = listing("pipes", &["tech"]);
|
||||
pipes.categories = vec!["technology".into()];
|
||||
let browser = ModBrowser {
|
||||
listings: vec![listing("ruby", &["gems"]), pipes],
|
||||
..ModBrowser::default()
|
||||
};
|
||||
assert_eq!(browser.found(""), [0, 1]);
|
||||
assert_eq!(browser.found("RUBY"), [0]);
|
||||
assert_eq!(browser.found("gems"), [0]);
|
||||
assert_eq!(browser.found("techno"), [1]);
|
||||
assert!(browser.found("nothing like it").is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_update_is_another_version_than_the_installed_one() {
|
||||
let mut l = listing("ruby", &[]);
|
||||
assert!(!l.has_update());
|
||||
l.installed = Some("1.0".into());
|
||||
assert!(!l.has_update());
|
||||
l.installed = Some("0.9".into());
|
||||
assert!(l.has_update());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn markdown_reads_as_plain_lines() {
|
||||
let text = "# Ruby\n\nAdds **rubies**. See [the wiki](https://x.y).\n\n\n- one\n* two\n```\ncode\n```\n";
|
||||
assert_eq!(
|
||||
plain_lines(text),
|
||||
[
|
||||
"Ruby",
|
||||
"Adds rubies. See the wiki.",
|
||||
"• one",
|
||||
"• two",
|
||||
"code"
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,30 @@ fn percent(caption: &str, value: f32) -> Text {
|
||||
],
|
||||
)
|
||||
}
|
||||
/// `text` cut into lines of at most `width` characters, at spaces where it has them.
|
||||
fn wrap(text: &str, width: usize) -> Vec<String> {
|
||||
let mut lines = Vec::new();
|
||||
let mut line = String::new();
|
||||
for word in text.split_whitespace() {
|
||||
let len = line.chars().count();
|
||||
if len > 0 && len + 1 + word.chars().count() > width {
|
||||
lines.push(std::mem::take(&mut line));
|
||||
}
|
||||
if !line.is_empty() {
|
||||
line.push(' ');
|
||||
}
|
||||
line.push_str(word);
|
||||
while line.chars().count() > width {
|
||||
let rest: String = line.chars().skip(width).collect();
|
||||
line = line.chars().take(width).collect();
|
||||
lines.push(std::mem::replace(&mut line, rest));
|
||||
}
|
||||
}
|
||||
if !line.is_empty() {
|
||||
lines.push(line);
|
||||
}
|
||||
lines
|
||||
}
|
||||
fn on_off(v: bool) -> Text {
|
||||
tr(if v { "options.on" } else { "options.off" })
|
||||
}
|
||||
@@ -281,6 +305,7 @@ impl Opt {
|
||||
Opt::PadToggleSneak => "horizon.options.sneak_button",
|
||||
Opt::PadMoveDeadzone => "horizon.options.move_deadzone",
|
||||
Opt::PadLookDeadzone => "horizon.options.look_deadzone",
|
||||
Opt::PadTriggerThreshold => "horizon.options.trigger_threshold",
|
||||
Opt::PadGlyphs => "horizon.options.button_glyphs",
|
||||
Opt::PadKeyboard => "horizon.options.on_screen_keyboard",
|
||||
Opt::ModelPart(i) => [
|
||||
@@ -361,6 +386,7 @@ impl Opt {
|
||||
Opt::PadRepeatInterval => unit(s.controller.repeat_interval, 0.03, 0.3),
|
||||
Opt::PadMoveDeadzone => unit(s.controller.move_deadzone, 0.0, 0.5),
|
||||
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),
|
||||
_ => Kind::Cycle,
|
||||
}
|
||||
@@ -425,6 +451,7 @@ impl Opt {
|
||||
Opt::MenuBlur => Some(10),
|
||||
Opt::NotificationDisplayTime => Some(95),
|
||||
Opt::PadMoveDeadzone | Opt::PadLookDeadzone => Some(50),
|
||||
Opt::PadTriggerThreshold => Some(80),
|
||||
Opt::SprintWindow => Some(10),
|
||||
_ => None,
|
||||
}
|
||||
@@ -634,11 +661,11 @@ impl Opt {
|
||||
)],
|
||||
),
|
||||
),
|
||||
Opt::PadMoveDeadzone | Opt::PadLookDeadzone => {
|
||||
let v = if self == Opt::PadMoveDeadzone {
|
||||
s.controller.move_deadzone
|
||||
} else {
|
||||
s.controller.look_deadzone
|
||||
Opt::PadMoveDeadzone | Opt::PadLookDeadzone | Opt::PadTriggerThreshold => {
|
||||
let v = match self {
|
||||
Opt::PadMoveDeadzone => s.controller.move_deadzone,
|
||||
Opt::PadLookDeadzone => s.controller.look_deadzone,
|
||||
_ => s.controller.trigger_threshold,
|
||||
};
|
||||
Text::translate(
|
||||
"options.percent_value",
|
||||
@@ -897,6 +924,7 @@ impl Opt {
|
||||
Opt::PadRepeatInterval => s.controller.repeat_interval = step(30.0, 300.0) / 1000.0,
|
||||
Opt::PadMoveDeadzone => s.controller.move_deadzone = step(0.0, 50.0) / 100.0,
|
||||
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,
|
||||
_ => {}
|
||||
}
|
||||
@@ -1254,6 +1282,10 @@ pub enum Page {
|
||||
Font,
|
||||
Packs,
|
||||
Mods,
|
||||
/// Browse mods: the registry's content mods, searched (`mod_browser`).
|
||||
ModBrowser,
|
||||
/// One registry project's page: `OptionsUi::mod_project`.
|
||||
ModProject,
|
||||
/// A native mod's settings page: its index in `mods.installed`.
|
||||
NativeMod(usize),
|
||||
/// Horizon's shader packs, either kind (`shader_pack_list`).
|
||||
@@ -1309,6 +1341,8 @@ enum Act {
|
||||
ToggleMod(usize),
|
||||
/// PvP & HUD's PvP preset (`pvp::apply_preset`).
|
||||
PvpPreset,
|
||||
/// PvP & HUD's Edit HUD button.
|
||||
HudEditor,
|
||||
/// Pick `s.shader_packs[i]`, or turn it off when it is the pick.
|
||||
PickShaderPack(usize),
|
||||
OpenShaderPackFolder,
|
||||
@@ -1332,6 +1366,12 @@ enum Act {
|
||||
NativeKey(usize, usize),
|
||||
NativeButton(usize, usize),
|
||||
OpenModFolder,
|
||||
/// Browse mods: the page of listing `.0` of `mod_browser`.
|
||||
ModProject(usize),
|
||||
/// The open project page's Install or Update.
|
||||
InstallMod,
|
||||
/// The open project page's page on the website.
|
||||
OpenModPage,
|
||||
/// Accessibility's "Accessibility Help" link
|
||||
/// (`AccessibilityOptionsScreen.addFooter`'s openLinkButton).
|
||||
AccessibilityHelp,
|
||||
@@ -1468,6 +1508,12 @@ pub enum Event {
|
||||
/// Data Packs' Open Pack Folder: create the folder if needed and open it.
|
||||
OpenDataPackFolder(std::path::PathBuf),
|
||||
OpenModFolder,
|
||||
/// Browse mods opened: read the registry's index into `mod_browser`.
|
||||
BrowseMods,
|
||||
/// Install (or update) the registry's content mod with this id.
|
||||
InstallMod(String),
|
||||
/// Open a registry project's page on the website.
|
||||
OpenModPage(String),
|
||||
/// Accessibility's Help link: confirm and open
|
||||
/// `CommonLinks.ACCESSIBILITY_HELP` over the options screen.
|
||||
AccessibilityHelp,
|
||||
@@ -1491,6 +1537,8 @@ pub enum Event {
|
||||
NativeButton(usize, usize),
|
||||
/// Roll the credits (`CreditsAndAttributionScreen.openCreditsScreen`).
|
||||
Credits,
|
||||
/// PvP & HUD's Edit HUD: open the HUD editor over the game.
|
||||
HudEditor,
|
||||
/// Copy how an agent connects (the AI Agent page).
|
||||
CopyAgentDetails,
|
||||
/// The AI Agent page's recording button: start or stop.
|
||||
@@ -1564,7 +1612,7 @@ pub struct OptionsUi {
|
||||
pub selected_key: Option<&'static str>,
|
||||
/// The native mod's key waiting: (mod, key) in `mods.installed`.
|
||||
selected_native_key: Option<(usize, usize)>,
|
||||
/// Controller Binds: the action waiting for a pad button.
|
||||
/// Bindings: the action waiting for a pad button.
|
||||
pub selected_pad_action: Option<&'static str>,
|
||||
/// The glyphs the binds list shows (the setting, else the pad's family).
|
||||
pub pad_style: GlyphStyle,
|
||||
@@ -1578,6 +1626,15 @@ pub struct OptionsUi {
|
||||
language_choice: Option<String>,
|
||||
language_search: ui::EditBox,
|
||||
pub(crate) settings_search: ui::EditBox,
|
||||
/// Browse mods: the registry's projects, filled by the game.
|
||||
pub mod_browser: crate::mod_browser::ModBrowser,
|
||||
/// The project whose page is open (`Page::ModProject`).
|
||||
pub mod_project: String,
|
||||
/// The Mods page's line about switches that wait for a restart.
|
||||
pub mods_note: Option<String>,
|
||||
/// The content mods that switch with a world open: only assets and data,
|
||||
/// in a world this game serves (docs/design/mod-hotload.md).
|
||||
pub mods_live: std::collections::BTreeSet<String>,
|
||||
/// Horizon's `packBrowser` extra: the pack screen's Add button.
|
||||
pub pack_browser: bool,
|
||||
/// Packs: the packs found and the selection the screen opens with.
|
||||
@@ -1666,6 +1723,10 @@ impl Default for OptionsUi {
|
||||
language_choice: None,
|
||||
language_search: search,
|
||||
settings_search,
|
||||
mod_browser: Default::default(),
|
||||
mod_project: String::new(),
|
||||
mods_note: None,
|
||||
mods_live: Default::default(),
|
||||
pack_browser: false,
|
||||
pack_infos: Vec::new(),
|
||||
pack_selection: Vec::new(),
|
||||
@@ -1993,6 +2054,7 @@ fn tooltip_text(s: &Settings, wd: &Widget) -> Option<Text> {
|
||||
Act::Push(Page::Language) => Some("options.language.tooltip".into()),
|
||||
Act::Push(Page::Accessibility) => Some("options.accessibility.tooltip".into()),
|
||||
Act::PvpPreset => Some("horizon.pvp.preset.tooltip".into()),
|
||||
Act::HudEditor => Some("horizon.pvp.editor.tooltip".into()),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(key) = key.filter(|key| lang::has(key)) {
|
||||
@@ -2156,7 +2218,10 @@ impl OptionsUi {
|
||||
}
|
||||
fn settings_search_active(&self) -> bool {
|
||||
self.settings_search.focused
|
||||
&& matches!(self.page(), Page::Video | Page::Controls | Page::KeyBinds)
|
||||
&& matches!(
|
||||
self.page(),
|
||||
Page::Video | Page::Controls | Page::KeyBinds | Page::ModBrowser
|
||||
)
|
||||
}
|
||||
/// Whether the resource pack screen is open (not Create World's data packs on the same
|
||||
/// page).
|
||||
@@ -2297,7 +2362,10 @@ impl OptionsUi {
|
||||
self.focus = None;
|
||||
self.focus_from_keyboard = false;
|
||||
self.can_change_value = false;
|
||||
if matches!(page, Page::Video | Page::Controls | Page::KeyBinds) {
|
||||
if matches!(
|
||||
page,
|
||||
Page::Video | Page::Controls | Page::KeyBinds | Page::ModBrowser
|
||||
) {
|
||||
self.settings_search.set_value("");
|
||||
self.settings_search.focused = false;
|
||||
}
|
||||
@@ -2423,6 +2491,8 @@ impl OptionsUi {
|
||||
Page::Font => "options.font.title",
|
||||
Page::Packs => "resourcePack.title",
|
||||
Page::Mods => "horizon.pages.mods",
|
||||
Page::ModBrowser => "horizon.mods.browse.title",
|
||||
Page::ModProject => "horizon.mods.browse.project",
|
||||
Page::NativeMod(_) => "Mod Settings",
|
||||
Page::ShaderPacks => "horizon.pages.shader_packs",
|
||||
Page::ShaderOptions | Page::NativePackOptions(..) => "horizon.pages.shader_options",
|
||||
@@ -2489,6 +2559,156 @@ impl OptionsUi {
|
||||
filtered
|
||||
}
|
||||
|
||||
/// Browse mods: what the registry offers this game, by the search box.
|
||||
fn mod_browser_rows(&self) -> Vec<Row> {
|
||||
use crate::mod_browser::Status;
|
||||
let b = &self.mod_browser;
|
||||
match &b.status {
|
||||
Status::Idle | Status::Loading => {
|
||||
return vec![Row::Note(lang::get("horizon.mods.browse.loading"))];
|
||||
}
|
||||
Status::Failed(why) => {
|
||||
return vec![Row::Note(lang::tr("horizon.mods.browse.failed", &[why]))];
|
||||
}
|
||||
Status::Ready => {}
|
||||
}
|
||||
let found = b.found(&self.settings_search.value);
|
||||
if found.is_empty() {
|
||||
return vec![Row::Note(lang::get("horizon.mods.browse.none"))];
|
||||
}
|
||||
let mut rows = Vec::new();
|
||||
for i in found {
|
||||
let l = &b.listings[i];
|
||||
let mut label = Text::literal(format!("{} {}", l.name, l.version));
|
||||
if l.has_update() {
|
||||
label = label.append(tr("horizon.mods.browse.has_update"));
|
||||
} else if l.installed.is_some() {
|
||||
label = label.append(tr("horizon.mods.browse.is_installed"));
|
||||
}
|
||||
rows.push(Row::Buttons(
|
||||
button(0.0, 0.0, 310.0, label, Act::ModProject(i)),
|
||||
None,
|
||||
));
|
||||
rows.extend(wrap(&l.summary, 64).into_iter().take(1).map(Row::Note));
|
||||
}
|
||||
rows
|
||||
}
|
||||
/// A registry project's page: what it is, Install or Update, its switch
|
||||
/// once installed, its description and changelog.
|
||||
/// A content mod's set feeds the block-state tables the open world's
|
||||
/// chunks were decoded with: changing it mid-world remaps their ids
|
||||
/// (Forge/Fabric freeze the registry before a world opens). One with only
|
||||
/// assets and data reloads live, and a native mod toggles live through
|
||||
/// `native_on`, so they stay available.
|
||||
fn mod_frozen(&self, installed: &crate::mod_list::Installed) -> bool {
|
||||
self.in_world && installed.native.is_none() && !self.mods_live.contains(&installed.id)
|
||||
}
|
||||
fn mod_project_rows(&self, s: &Settings) -> Vec<Row> {
|
||||
let b = &self.mod_browser;
|
||||
let Some(l) = b.listing(&self.mod_project) else {
|
||||
return vec![Row::Note(lang::get("horizon.mods.browse.none"))];
|
||||
};
|
||||
let mut rows = vec![Row::Note(format!("{} {}", l.name, l.version))];
|
||||
let by = [
|
||||
lang::tr("horizon.mods.browse.by", &[&l.authors.join(", ")]),
|
||||
l.license.clone(),
|
||||
l.published.clone(),
|
||||
];
|
||||
let by: Vec<&str> = by
|
||||
.iter()
|
||||
.map(String::as_str)
|
||||
.filter(|t| !t.is_empty())
|
||||
.collect();
|
||||
rows.push(Row::Note(by.join(" · ")));
|
||||
let labels: Vec<&str> = l
|
||||
.categories
|
||||
.iter()
|
||||
.chain(&l.tags)
|
||||
.map(String::as_str)
|
||||
.collect();
|
||||
if !labels.is_empty() {
|
||||
rows.push(Row::Note(labels.join(", ")));
|
||||
}
|
||||
let installing = b.installing.as_deref() == Some(l.id.as_str());
|
||||
let install = match (&l.installed, installing) {
|
||||
(_, true) => inactive(button(
|
||||
0.0,
|
||||
0.0,
|
||||
150.0,
|
||||
tr("horizon.mods.browse.installing"),
|
||||
Act::None,
|
||||
)),
|
||||
(None, false) => button(
|
||||
0.0,
|
||||
0.0,
|
||||
150.0,
|
||||
tr("horizon.mods.browse.install"),
|
||||
Act::InstallMod,
|
||||
),
|
||||
(Some(_), false) if l.has_update() => button(
|
||||
0.0,
|
||||
0.0,
|
||||
150.0,
|
||||
tr("horizon.mods.browse.update"),
|
||||
Act::InstallMod,
|
||||
),
|
||||
(Some(_), false) => inactive(button(
|
||||
0.0,
|
||||
0.0,
|
||||
150.0,
|
||||
tr("horizon.mods.browse.installed"),
|
||||
Act::None,
|
||||
)),
|
||||
};
|
||||
let page = button(
|
||||
0.0,
|
||||
0.0,
|
||||
150.0,
|
||||
tr("horizon.mods.browse.open_page"),
|
||||
Act::OpenModPage,
|
||||
);
|
||||
let row = s.mods.installed.iter().position(|m| m.id == l.id && !m.dev);
|
||||
match row.filter(|_| l.installed.is_some()) {
|
||||
Some(i) => {
|
||||
let label =
|
||||
Text::literal(format!("{}: ", lang::get("horizon.mods.browse.enabled")))
|
||||
.append(on_off(s.mods.is_on(i)));
|
||||
let toggle = button(0.0, 0.0, 150.0, label, Act::ToggleMod(i));
|
||||
let toggle = match self.mod_frozen(&s.mods.installed[i]) {
|
||||
true => inactive(toggle),
|
||||
false => toggle,
|
||||
};
|
||||
rows.push(Row::Buttons(install, Some(toggle)));
|
||||
rows.push(Row::Buttons(page, None));
|
||||
}
|
||||
None => rows.push(Row::Buttons(install, Some(page))),
|
||||
}
|
||||
if let Some((message, _)) = &b.message {
|
||||
rows.extend(wrap(message, 64).into_iter().map(Row::Note));
|
||||
}
|
||||
let description = crate::mod_browser::plain_lines(if l.description.is_empty() {
|
||||
&l.summary
|
||||
} else {
|
||||
&l.description
|
||||
});
|
||||
rows.extend(
|
||||
description
|
||||
.iter()
|
||||
.flat_map(|line| wrap(line, 64))
|
||||
.map(Row::Note),
|
||||
);
|
||||
let changelog = crate::mod_browser::plain_lines(&l.changelog);
|
||||
if !changelog.is_empty() {
|
||||
rows.push(Row::Heading("horizon.mods.browse.changelog".into()));
|
||||
rows.extend(
|
||||
changelog
|
||||
.iter()
|
||||
.flat_map(|line| wrap(line, 64))
|
||||
.map(Row::Note),
|
||||
);
|
||||
}
|
||||
rows
|
||||
}
|
||||
/// A native mod's page: its name, its keys as Key Binds rows (the
|
||||
/// player's choice, else the default), then its buttons.
|
||||
fn native_rows(&self, s: &Settings, i: usize) -> Vec<Row> {
|
||||
@@ -2562,7 +2782,10 @@ impl OptionsUi {
|
||||
let preset = button(0.0, 0.0, 150.0, tr("horizon.pvp.preset"), Act::PvpPreset);
|
||||
let mut rows = Vec::new();
|
||||
if self.page() == Page::Pvp {
|
||||
rows.push(Row::Buttons(preset, None));
|
||||
// The editor moves what the game shows: only from a world.
|
||||
let edit = button(0.0, 0.0, 150.0, tr("horizon.pvp.editor"), Act::HudEditor);
|
||||
let edit = if self.in_world { edit } else { inactive(edit) };
|
||||
rows.push(Row::Buttons(preset, Some(edit)));
|
||||
}
|
||||
let mut section = Vec::new();
|
||||
let flush = |rows: &mut Vec<Row>, section: &mut Vec<crate::extras::Item>| {
|
||||
@@ -2857,61 +3080,62 @@ impl OptionsUi {
|
||||
}
|
||||
Page::Font => pairs(&[Opt::ForceUnicode, Opt::JapaneseGlyphs]),
|
||||
Page::Mods => {
|
||||
if s.mods.installed.is_empty() {
|
||||
return vec![Row::Header("horizon.mods.empty")];
|
||||
let browse = button(
|
||||
0.0,
|
||||
0.0,
|
||||
310.0,
|
||||
tr("horizon.mods.browse"),
|
||||
Act::Push(Page::ModBrowser),
|
||||
);
|
||||
let mut rows = vec![Row::Buttons(browse, None)];
|
||||
if let Some(note) = &self.mods_note {
|
||||
rows.extend(wrap(note, 64).into_iter().map(Row::Note));
|
||||
}
|
||||
s.mods
|
||||
.installed
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, installed)| {
|
||||
let mut label = Text::option_name_value(
|
||||
Text::literal(installed.title.clone()),
|
||||
on_off(s.mods.is_on(i)),
|
||||
if s.mods.installed.is_empty() {
|
||||
rows.push(Row::Header("horizon.mods.empty"));
|
||||
return rows;
|
||||
}
|
||||
rows.extend(s.mods.installed.iter().enumerate().map(|(i, installed)| {
|
||||
let mut label = Text::option_name_value(
|
||||
Text::literal(installed.title.clone()),
|
||||
on_off(s.mods.is_on(i)),
|
||||
);
|
||||
if installed.problem.as_deref() == Some(crate::mod_list::NOT_BUILT) {
|
||||
label = label.append(tr("horizon.mods.not_built"));
|
||||
} else if installed.problem.is_some() {
|
||||
label = label.append(tr("horizon.mods.stopped"));
|
||||
} else if installed.dev {
|
||||
label = label.append(tr("horizon.mods.dev"));
|
||||
}
|
||||
let frozen = self.mod_frozen(installed);
|
||||
if frozen {
|
||||
label = label.append(tr("horizon.mods.in_world"));
|
||||
}
|
||||
let dead = installed.dev || installed.id.is_empty() || frozen;
|
||||
// Only native mods with actual settings need a second button.
|
||||
if installed
|
||||
.native
|
||||
.as_ref()
|
||||
.is_some_and(|page| !page.keys.is_empty() || !page.buttons.is_empty())
|
||||
{
|
||||
let toggle = button(0.0, 0.0, 150.0, label, Act::ToggleMod(i));
|
||||
let settings = button(
|
||||
0.0,
|
||||
0.0,
|
||||
150.0,
|
||||
tr("horizon.mods.settings"),
|
||||
Act::ModSettings(i),
|
||||
);
|
||||
if installed.problem.as_deref() == Some(crate::mod_list::NOT_BUILT) {
|
||||
label = label.append(tr("horizon.mods.not_built"));
|
||||
} else if installed.problem.is_some() {
|
||||
label = label.append(tr("horizon.mods.stopped"));
|
||||
} else if installed.dev {
|
||||
label = label.append(tr("horizon.mods.dev"));
|
||||
}
|
||||
// A content mod's set feeds the block-state tables the
|
||||
// open world's chunks were decoded with: changing it
|
||||
// mid-world remaps their ids (Forge/Fabric freeze the
|
||||
// registry before a world opens). A native mod toggles
|
||||
// live through `native_on`, so it stays available.
|
||||
let content = installed.native.is_none();
|
||||
if self.in_world && content {
|
||||
label = label.append(tr("horizon.mods.in_world"));
|
||||
}
|
||||
let dead =
|
||||
installed.dev || installed.id.is_empty() || self.in_world && content;
|
||||
// Only native mods with actual settings need a second button.
|
||||
if installed
|
||||
.native
|
||||
.as_ref()
|
||||
.is_some_and(|page| !page.keys.is_empty() || !page.buttons.is_empty())
|
||||
{
|
||||
let toggle = button(0.0, 0.0, 150.0, label, Act::ToggleMod(i));
|
||||
let settings = button(
|
||||
0.0,
|
||||
0.0,
|
||||
150.0,
|
||||
tr("horizon.mods.settings"),
|
||||
Act::ModSettings(i),
|
||||
);
|
||||
Row::Buttons(
|
||||
if dead { inactive(toggle) } else { toggle },
|
||||
Some(settings),
|
||||
)
|
||||
} else {
|
||||
let widget = button(0.0, 0.0, 310.0, label, Act::ToggleMod(i));
|
||||
Row::Buttons(if dead { inactive(widget) } else { widget }, None)
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
Row::Buttons(if dead { inactive(toggle) } else { toggle }, Some(settings))
|
||||
} else {
|
||||
let widget = button(0.0, 0.0, 310.0, label, Act::ToggleMod(i));
|
||||
Row::Buttons(if dead { inactive(widget) } else { widget }, None)
|
||||
}
|
||||
}));
|
||||
rows
|
||||
}
|
||||
Page::ModBrowser => self.mod_browser_rows(),
|
||||
Page::ModProject => self.mod_project_rows(s),
|
||||
Page::NativeMod(i) => self.native_rows(s, i),
|
||||
Page::ShaderPacks => {
|
||||
if s.shader_packs.is_empty() {
|
||||
@@ -2989,7 +3213,10 @@ impl OptionsUi {
|
||||
let mut rows = ListRows::default();
|
||||
let page = self.page();
|
||||
let cx = (w / 2.0).floor();
|
||||
if matches!(page, Page::Video | Page::Controls | Page::KeyBinds) {
|
||||
if matches!(
|
||||
page,
|
||||
Page::Video | Page::Controls | Page::KeyBinds | Page::ModBrowser
|
||||
) {
|
||||
self.settings_search.x = cx - 100.0;
|
||||
self.settings_search.y = 17.0;
|
||||
}
|
||||
@@ -3331,12 +3558,12 @@ impl OptionsUi {
|
||||
// Drawn by `pack_screen`.
|
||||
Page::Packs => {}
|
||||
_ => {
|
||||
if matches!(page, Page::Video | Page::Controls) {
|
||||
if matches!(page, Page::Video | Page::Controls | Page::ModBrowser) {
|
||||
top = 38.0;
|
||||
}
|
||||
out.push(Widget {
|
||||
x: 0.0,
|
||||
y: if matches!(page, Page::Video | Page::Controls) {
|
||||
y: if matches!(page, Page::Video | Page::Controls | Page::ModBrowser) {
|
||||
4.0
|
||||
} else {
|
||||
12.0
|
||||
@@ -3915,23 +4142,30 @@ impl OptionsUi {
|
||||
}
|
||||
self.language_search = search;
|
||||
}
|
||||
if matches!(self.page(), Page::Video | Page::Controls | Page::KeyBinds) {
|
||||
if matches!(
|
||||
self.page(),
|
||||
Page::Video | Page::Controls | Page::KeyBinds | Page::ModBrowser
|
||||
) {
|
||||
let mut search = self.settings_search.clone();
|
||||
if self.focus == Some(FocusId::Search) {
|
||||
search.focused = true;
|
||||
}
|
||||
search.draw(batch, atlas, None);
|
||||
let hint = match self.page() {
|
||||
Page::ModBrowser => "horizon.mods.browse.search",
|
||||
_ => "horizon.options.search",
|
||||
};
|
||||
// Horizon's search box sits in the header, before the list.
|
||||
crate::widgets::last_narratable(|n| {
|
||||
n.order = -1;
|
||||
if let crate::widgets::What::EditBox { message, .. } = &mut n.what {
|
||||
*message = tr("horizon.options.search").plain();
|
||||
*message = tr(hint).plain();
|
||||
}
|
||||
});
|
||||
if search.value.is_empty() && !search.focused {
|
||||
batch.text_styled(
|
||||
atlas,
|
||||
&tr("horizon.options.search").with_color(text::GRAY).italic(),
|
||||
&tr(hint).with_color(text::GRAY).italic(),
|
||||
search.text_x(),
|
||||
search.text_y(),
|
||||
u32::MAX,
|
||||
@@ -4091,7 +4325,12 @@ impl OptionsUi {
|
||||
self.size = size;
|
||||
self.framebuffer = framebuffer;
|
||||
let mut events = Vec::new();
|
||||
if !drag && matches!(self.page(), Page::Video | Page::Controls | Page::KeyBinds) {
|
||||
if !drag
|
||||
&& matches!(
|
||||
self.page(),
|
||||
Page::Video | Page::Controls | Page::KeyBinds | Page::ModBrowser
|
||||
)
|
||||
{
|
||||
let search = &mut self.settings_search;
|
||||
search.focused = ui::hovered((x, y), search.x, search.y, search.width, search.height);
|
||||
if search.focused {
|
||||
@@ -4247,6 +4486,9 @@ impl OptionsUi {
|
||||
if p == Page::Music {
|
||||
events.push(Event::ListMusicTracks);
|
||||
}
|
||||
if p == Page::ModBrowser {
|
||||
events.push(Event::BrowseMods);
|
||||
}
|
||||
self.push(p)
|
||||
}
|
||||
Act::Done => events.extend(self.back(s)),
|
||||
@@ -4297,10 +4539,26 @@ impl OptionsUi {
|
||||
}
|
||||
Act::NativeButton(i, b) => events.push(Event::NativeButton(i, b)),
|
||||
Act::OpenModFolder => events.push(Event::OpenModFolder),
|
||||
Act::ModProject(i) => {
|
||||
if let Some(listing) = self.mod_browser.listings.get(i) {
|
||||
self.mod_project = listing.id.clone();
|
||||
self.push(Page::ModProject);
|
||||
}
|
||||
}
|
||||
Act::InstallMod => {
|
||||
self.mod_browser.message = None;
|
||||
events.push(Event::InstallMod(self.mod_project.clone()));
|
||||
}
|
||||
Act::OpenModPage => {
|
||||
if let Some(l) = self.mod_browser.listing(&self.mod_project) {
|
||||
events.push(Event::OpenModPage(l.page.clone()));
|
||||
}
|
||||
}
|
||||
Act::PvpPreset => {
|
||||
crate::pvp::apply_preset(s);
|
||||
events.push(Event::Changed);
|
||||
}
|
||||
Act::HudEditor => events.push(Event::HudEditor),
|
||||
Act::AccessibilityHelp => events.push(Event::AccessibilityHelp),
|
||||
Act::PickShaderPack(i) => {
|
||||
if let Some(pack) = s.shader_packs.get(i) {
|
||||
@@ -4489,19 +4747,19 @@ impl OptionsUi {
|
||||
}
|
||||
events
|
||||
}
|
||||
/// The mouse wheel at the cursor. Vanilla scrolls the list
|
||||
/// (`AbstractScrollArea.mouseScrolled`), except that a hovered cycle button
|
||||
/// takes the step and keeps the wheel (`CycleButton.mouseScrolled`: up goes
|
||||
/// back); `AbstractSliderButton` does not override it, so a slider under the
|
||||
/// cursor lets the list scroll.
|
||||
/// The mouse wheel at the cursor. A vanilla list keeps the wheel for itself:
|
||||
/// `AbstractContainerWidget` inherits `AbstractScrollArea.mouseScrolled`, which
|
||||
/// scrolls and never reaches the list's rows. A cycle button outside a list is
|
||||
/// the screen's own child, so it takes the step (`CycleButton.mouseScrolled`: up
|
||||
/// goes back). `AbstractSliderButton` does not override it, so a slider outside
|
||||
/// a list does nothing.
|
||||
///
|
||||
/// **Horizon improvement (diverges from Vanilla 26.3):** a slider or cycle
|
||||
/// button focused by the keyboard (Tab, the arrows, a pad) steps or cycles on
|
||||
/// the wheel wherever the cursor is. Vanilla 26.3 does not intercept the wheel
|
||||
/// for sliders at all; this makes keyboard navigation far more usable. Focus
|
||||
/// left by a click does not count (`focus_from_keyboard`): vanilla's wheel
|
||||
/// ignores it, and honouring it toggled the clicked option on every later wheel
|
||||
/// event, from anywhere on the screen, while the list never scrolled.
|
||||
/// **Horizon extra (diverges from Vanilla 26.3, off by default):** "Scroll wheel
|
||||
/// changes settings" lets a hovered list row take the step too, and a slider or
|
||||
/// cycle button focused by the keyboard (Tab, the arrows, a pad) step on the
|
||||
/// wheel wherever the cursor is. Focus left by a click does not count
|
||||
/// (`focus_from_keyboard`): honouring it toggled the clicked option on every
|
||||
/// later wheel event, from anywhere on the screen, while the list never scrolled.
|
||||
/// The graphics screen has its own wheel (`GraphicsUi::wheel`).
|
||||
pub fn wheel(
|
||||
&mut self,
|
||||
@@ -4527,17 +4785,17 @@ impl OptionsUi {
|
||||
}
|
||||
let plain = self.resource_error.is_none()
|
||||
&& !(self.pack_screen.is_some() && self.stack.last() == Some(&Page::Packs));
|
||||
// The "Scroll wheel changes settings" extra off: the wheel only scrolls.
|
||||
if !plain || !s.extras.wheel_cycles_settings {
|
||||
if !plain {
|
||||
self.scroll_at(s, size, cursor, amount);
|
||||
return Vec::new();
|
||||
}
|
||||
// Horizon improvement: a keyboard-focused slider or cycle button
|
||||
// intercepts the wheel before hover detection (see doc-comment above).
|
||||
// A click focuses its widget for the highlight too, and vanilla's wheel
|
||||
// must not act on that: it would toggle the clicked option from anywhere
|
||||
// on the screen and the list would never scroll.
|
||||
if amount != 0.0 && self.focus_from_keyboard {
|
||||
let extra = s.extras.wheel_cycles_settings;
|
||||
// The extra: a keyboard-focused slider or cycle button intercepts the
|
||||
// wheel before hover detection (see doc-comment above). A click focuses
|
||||
// its widget for the highlight too, and the wheel must not act on that:
|
||||
// it would toggle the clicked option from anywhere on the screen and the
|
||||
// list would never scroll.
|
||||
if extra && amount != 0.0 && self.focus_from_keyboard {
|
||||
let (widgets, _, _, _) = self.layout(s, size.0, size.1);
|
||||
match &self.focus {
|
||||
Some(FocusId::Slider(opt)) => {
|
||||
@@ -4566,25 +4824,22 @@ impl OptionsUi {
|
||||
Extra(crate::extras::Item),
|
||||
}
|
||||
let (widgets, top, bottom, _) = self.layout(s, size.0, size.1);
|
||||
// A list's row takes the wheel only with the extra on.
|
||||
let takes_wheel = |wd: &Widget| {
|
||||
ui::hovered(cursor, wd.x, wd.y, wd.w, wd.h)
|
||||
&& (!wd.list || (extra && cursor.1 >= top && cursor.1 < bottom))
|
||||
};
|
||||
let hovered = widgets.iter().find_map(|wd| match &wd.kind {
|
||||
WKind::Button {
|
||||
active: true,
|
||||
act: Act::Cycle(opt),
|
||||
..
|
||||
} if ui::hovered(cursor, wd.x, wd.y, wd.w, wd.h)
|
||||
&& !(wd.list && (cursor.1 < top || cursor.1 >= bottom)) =>
|
||||
{
|
||||
Some(HoveredControl::Cycle(*opt))
|
||||
}
|
||||
} if takes_wheel(wd) => Some(HoveredControl::Cycle(*opt)),
|
||||
WKind::Button {
|
||||
active: true,
|
||||
act: Act::Extra(item),
|
||||
..
|
||||
} if ui::hovered(cursor, wd.x, wd.y, wd.w, wd.h)
|
||||
&& !(wd.list && (cursor.1 < top || cursor.1 >= bottom)) =>
|
||||
{
|
||||
Some(HoveredControl::Extra(*item))
|
||||
}
|
||||
} if takes_wheel(wd) => Some(HoveredControl::Extra(*item)),
|
||||
_ => None,
|
||||
});
|
||||
match hovered {
|
||||
@@ -5202,6 +5457,9 @@ impl OptionsUi {
|
||||
if p == Page::Music {
|
||||
events.push(Event::ListMusicTracks);
|
||||
}
|
||||
if p == Page::ModBrowser {
|
||||
events.push(Event::BrowseMods);
|
||||
}
|
||||
self.push(p);
|
||||
}
|
||||
Act::Done => {
|
||||
@@ -5259,10 +5517,26 @@ impl OptionsUi {
|
||||
}
|
||||
Act::NativeButton(i, b) => events.push(Event::NativeButton(i, b)),
|
||||
Act::OpenModFolder => events.push(Event::OpenModFolder),
|
||||
Act::ModProject(i) => {
|
||||
if let Some(listing) = self.mod_browser.listings.get(i) {
|
||||
self.mod_project = listing.id.clone();
|
||||
self.push(Page::ModProject);
|
||||
}
|
||||
}
|
||||
Act::InstallMod => {
|
||||
self.mod_browser.message = None;
|
||||
events.push(Event::InstallMod(self.mod_project.clone()));
|
||||
}
|
||||
Act::OpenModPage => {
|
||||
if let Some(l) = self.mod_browser.listing(&self.mod_project) {
|
||||
events.push(Event::OpenModPage(l.page.clone()));
|
||||
}
|
||||
}
|
||||
Act::PvpPreset => {
|
||||
crate::pvp::apply_preset(s);
|
||||
events.push(Event::Changed);
|
||||
}
|
||||
Act::HudEditor => events.push(Event::HudEditor),
|
||||
Act::AccessibilityHelp => events.push(Event::AccessibilityHelp),
|
||||
Act::PickShaderPack(i) => {
|
||||
if let Some(pack) = s.shader_packs.get(i) {
|
||||
@@ -7549,9 +7823,10 @@ mod tests {
|
||||
|
||||
/// A content mod's set builds the block-state tables an open world's
|
||||
/// chunks were decoded with, so its toggle freezes in-world; a native
|
||||
/// mod gates live through `native_on` and stays available.
|
||||
/// mod gates live through `native_on` and one of assets and data reloads
|
||||
/// live, so they stay available.
|
||||
#[test]
|
||||
fn in_a_world_only_native_mod_toggles_stay_active() {
|
||||
fn in_a_world_only_live_mod_toggles_stay_active() {
|
||||
let mut s = Settings::default();
|
||||
let installed =
|
||||
|id: &str, native: Option<crate::mod_list::NativePage>| crate::mod_list::Installed {
|
||||
@@ -7585,6 +7860,11 @@ mod tests {
|
||||
assert_eq!(toggles(&mut ui, &s), [(0, true), (1, true)]);
|
||||
ui.open_at(true, Page::Mods);
|
||||
assert_eq!(toggles(&mut ui, &s), [(0, false), (1, true)]);
|
||||
// A content mod of assets and data only reloads live in a world this
|
||||
// game serves (`mods_live`, filled by the game).
|
||||
s.mods.installed.push(installed("data_only", None));
|
||||
ui.mods_live.insert("data_only".into());
|
||||
assert_eq!(toggles(&mut ui, &s), [(0, false), (1, true), (2, true)]);
|
||||
}
|
||||
|
||||
/// An Extras or PvP & HUD label wider than half a row takes the whole row, so every label
|
||||
@@ -8044,7 +8324,8 @@ mod tests {
|
||||
let mut s = Settings::default();
|
||||
let mut ui = OptionsUi::default();
|
||||
|
||||
// 1. Extras page: scrolling wheel on Sort switch cycles it
|
||||
// 1. Extras page: with the wheel extra on, the wheel on the Sort switch cycles it
|
||||
s.extras.wheel_cycles_settings = true;
|
||||
ui.show(Page::Extras);
|
||||
let size = (480.0, 600.0);
|
||||
let (widgets, _, _, _) = ui.layout(&s, size.0, size.1);
|
||||
@@ -8108,9 +8389,9 @@ mod tests {
|
||||
assert_eq!(s.horizon.chunk_updates, 11);
|
||||
}
|
||||
|
||||
/// Vanilla's wheel scrolls the list (`AbstractScrollArea.mouseScrolled`); only a
|
||||
/// widget focused by the keyboard takes the step from anywhere on the screen.
|
||||
/// A click focuses its widget for the highlight, and honouring that focus made a
|
||||
/// Vanilla's wheel scrolls the list (`AbstractScrollArea.mouseScrolled`); with the
|
||||
/// extra on, a widget focused by the keyboard takes the step from anywhere on the
|
||||
/// screen. A click focuses its widget for the highlight, and honouring that focus made a
|
||||
/// clicked option toggle on every later wheel event while the list never scrolled.
|
||||
#[test]
|
||||
fn a_clicked_option_lets_the_wheel_scroll_the_list() {
|
||||
@@ -8172,8 +8453,9 @@ mod tests {
|
||||
assert!(!events.contains(&Event::Changed));
|
||||
assert!(ui.scroll > start, "the list scrolls down instead");
|
||||
|
||||
// Keyboard focus keeps the documented divergence: the wheel steps it
|
||||
// wherever the cursor is, even off the widget.
|
||||
// With the extra on, keyboard focus keeps the documented divergence: the
|
||||
// wheel steps it wherever the cursor is, even off the widget.
|
||||
s.extras.wheel_cycles_settings = true;
|
||||
ui.focus = Some(FocusId::Cycle(Opt::ViewBobbing));
|
||||
ui.focus_from_keyboard = true;
|
||||
let events = ui.wheel(&mut s, size, cursor, framebuffer, -1.0);
|
||||
@@ -8183,7 +8465,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_wheel_only_scrolls_with_the_extra_off() {
|
||||
fn the_wheel_scrolls_a_list_past_its_cycle_buttons_with_the_extra_off() {
|
||||
let mut s = Settings::default();
|
||||
let mut ui = OptionsUi::default();
|
||||
ui.show(Page::Video);
|
||||
@@ -8194,20 +8476,22 @@ mod tests {
|
||||
content > bottom - top,
|
||||
"the video page scrolls at this size"
|
||||
);
|
||||
let (opt, over) = widgets
|
||||
let (opt, over, list) = widgets
|
||||
.iter()
|
||||
.find_map(|wd| match wd.kind {
|
||||
WKind::Button {
|
||||
act: Act::Cycle(opt),
|
||||
..
|
||||
} if !wd.list || (wd.y >= top && wd.y + wd.h <= bottom) => {
|
||||
Some((opt, (wd.x + 5.0, wd.y + 5.0)))
|
||||
} if wd.y >= top && wd.y + wd.h <= bottom => {
|
||||
Some((opt, (wd.x + 5.0, wd.y + 5.0), wd.list))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.expect("a visible cycle button");
|
||||
|
||||
s.extras.wheel_cycles_settings = false;
|
||||
assert!(list, "{opt:?} is a row of the video list");
|
||||
// The default, as in vanilla (#9633).
|
||||
assert!(!s.extras.wheel_cycles_settings);
|
||||
let before = format!("{s:?}");
|
||||
let start = ui.scroll;
|
||||
let events = ui.wheel(&mut s, size, over, framebuffer, -1.0);
|
||||
@@ -8223,7 +8507,7 @@ mod tests {
|
||||
ui.wheel(&mut s, size, over, framebuffer, -1.0);
|
||||
assert_eq!(format!("{s:?}"), before);
|
||||
|
||||
// On (the default), vanilla's CycleButton.mouseScrolled again.
|
||||
// On, the row under the cursor takes the step.
|
||||
s.extras.wheel_cycles_settings = true;
|
||||
let before = format!("{s:?}");
|
||||
let events = ui.wheel(&mut s, size, over, framebuffer, -1.0);
|
||||
@@ -8249,6 +8533,7 @@ mod tests {
|
||||
Opt::NotificationDisplayTime,
|
||||
Opt::PadMoveDeadzone,
|
||||
Opt::PadLookDeadzone,
|
||||
Opt::PadTriggerThreshold,
|
||||
Opt::SprintWindow,
|
||||
];
|
||||
for opt in stepped_opts {
|
||||
|
||||
@@ -1,43 +1,13 @@
|
||||
//! The controller's menu cursor in inventories: a crosshair whose centre is
|
||||
//! the point it clicks. Horizon's own art (pad_cursor.png).
|
||||
//! the point it clicks. Horizon's own art, packed with the button glyphs (`pad_glyph`).
|
||||
|
||||
/// The atlas sprite.
|
||||
pub const SPRITE: &str = "horizon/pad_cursor";
|
||||
/// The texture's size in texels, and the size it's drawn at in GUI pixels
|
||||
/// (an item's).
|
||||
pub const SIZE: u32 = 32;
|
||||
pub const SPRITE: &str = "horizon:gui/controller/crosshair_inv";
|
||||
/// The size it's drawn at in GUI pixels (an item's); its texture is twice that.
|
||||
pub const DRAWN: f32 = 16.0;
|
||||
|
||||
const PNG: &[u8] = include_bytes!("pad_cursor.png");
|
||||
|
||||
/// The texture as `SIZE` square RGBA texels.
|
||||
pub fn pixels() -> Vec<u8> {
|
||||
decode(PNG).expect("pad_cursor.png is a 32x32 PNG")
|
||||
}
|
||||
|
||||
fn decode(bytes: &[u8]) -> Option<Vec<u8>> {
|
||||
let mut decoder = png::Decoder::new(std::io::Cursor::new(bytes));
|
||||
decoder.set_transformations(png::Transformations::EXPAND | png::Transformations::STRIP_16);
|
||||
let mut reader = decoder.read_info().ok()?;
|
||||
let info = reader.info();
|
||||
if (info.width, info.height) != (SIZE, SIZE) {
|
||||
return None;
|
||||
}
|
||||
let mut buf = vec![0; reader.output_buffer_size()?];
|
||||
let frame = reader.next_frame(&mut buf).ok()?;
|
||||
let channels = frame.color_type.samples();
|
||||
Some(
|
||||
buf.chunks(channels)
|
||||
.take((SIZE * SIZE) as usize)
|
||||
.flat_map(|p| match channels {
|
||||
4 => [p[0], p[1], p[2], p[3]],
|
||||
3 => [p[0], p[1], p[2], 255],
|
||||
2 => [p[0], p[0], p[0], p[1]],
|
||||
_ => [p[0], p[0], p[0], 255],
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
pub(crate) const PNG: &[u8] =
|
||||
include_bytes!("../../horizon-client/assets/horizon/textures/gui/controller/crosshair_inv.png");
|
||||
|
||||
/// Where to draw the cursor (top-left, GUI pixels) so its centre lands on
|
||||
/// the GUI point `at`.
|
||||
@@ -51,10 +21,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn the_cursor_is_a_crosshair_centred_on_its_point() {
|
||||
let px = pixels();
|
||||
assert_eq!(px.len(), (SIZE * SIZE * 4) as usize);
|
||||
let alpha = |x: u32, y: u32| px[((y * SIZE + x) * 4 + 3) as usize];
|
||||
let middle = (SIZE / 2 - 1)..=(SIZE / 2);
|
||||
let (_, w, h, px) = crate::pad_glyph::images()
|
||||
.find(|i| i.0 == SPRITE)
|
||||
.expect("the cursor is packed");
|
||||
assert_eq!((w as f32, h as f32), (DRAWN * 2.0, DRAWN * 2.0));
|
||||
let alpha = |x: u32, y: u32| px[((y * w + x) * 4 + 3) as usize];
|
||||
let middle = (w / 2 - 1)..=(w / 2);
|
||||
assert!(
|
||||
middle
|
||||
.clone()
|
||||
|
||||
@@ -0,0 +1,386 @@
|
||||
//! A controller button as the player sees it: Horizon's own art where the pad's family has one
|
||||
//! (`assets/horizon/textures/gui/controller/`, which a resource pack can replace), else the label
|
||||
//! printed on the pad, in its colour, on a dark key. Whatever shows a button draws and measures it
|
||||
//! here; `Button::label` stays for plain text and narration.
|
||||
use crate::gamepad::{Button, GlyphStyle};
|
||||
use crate::hud::{HudAtlas, HudBatch, rgba};
|
||||
use crate::pad_cursor;
|
||||
|
||||
/// The art's height as drawn, in GUI pixels. Its texture is twice that, so it maps pixel for pixel
|
||||
/// at GUI scale 2 and to whole pixels at 4.
|
||||
const DRAWN_HEIGHT: f32 = 16.0;
|
||||
/// The clear edge the art has all round.
|
||||
const MARGIN: f32 = 2.0;
|
||||
/// How tall a glyph shows, art or key: the text line and 2 above and below it.
|
||||
pub const HEIGHT: f32 = DRAWN_HEIGHT - 2.0 * MARGIN;
|
||||
/// How wide a face button's art shows; a declared button's label leaves it room.
|
||||
pub const FACE_WIDTH: f32 = 12.0;
|
||||
|
||||
/// A picture of a button in a family (Auto reads as Xbox, as `Button::label` has it). Every family
|
||||
/// has its own file for every button, some of them a copy of another's until theirs is drawn.
|
||||
struct Art {
|
||||
family: GlyphStyle,
|
||||
button: Button,
|
||||
/// The atlas sprite: where a pack's own file lands.
|
||||
sprite: &'static str,
|
||||
png: &'static [u8],
|
||||
}
|
||||
|
||||
macro_rules! art {
|
||||
($($family:ident $button:ident $file:literal,)*) => {
|
||||
&[$(Art {
|
||||
family: GlyphStyle::$family,
|
||||
button: Button::$button,
|
||||
sprite: concat!("horizon:gui/controller/", $file),
|
||||
png: include_bytes!(concat!(
|
||||
"../../horizon-client/assets/horizon/textures/gui/controller/",
|
||||
$file,
|
||||
".png"
|
||||
)),
|
||||
},)*]
|
||||
};
|
||||
}
|
||||
|
||||
/// All the art. A new picture is one line here and its file: a button not listed is text, and a
|
||||
/// picture is measured from its texture, so it can be as wide as it needs.
|
||||
static ART: &[Art] = art![
|
||||
Xbox South "xb_a",
|
||||
Xbox East "xb_b",
|
||||
Xbox West "xb_x",
|
||||
Xbox North "xb_y",
|
||||
Xbox LeftBumper "xb_lb",
|
||||
Xbox RightBumper "xb_rb",
|
||||
Xbox LeftTrigger "xb_lt",
|
||||
Xbox RightTrigger "xb_rt",
|
||||
Xbox Select "xb_view",
|
||||
Xbox Start "xb_menu",
|
||||
Xbox LeftStick "xb_l3",
|
||||
Xbox RightStick "xb_r3",
|
||||
Xbox DPadUp "xb_up",
|
||||
Xbox DPadDown "xb_down",
|
||||
Xbox DPadLeft "xb_left",
|
||||
Xbox DPadRight "xb_right",
|
||||
PlayStation South "psx_cross",
|
||||
PlayStation East "psx_circle",
|
||||
PlayStation West "psx_square",
|
||||
PlayStation North "psx_triangle",
|
||||
PlayStation LeftBumper "psx_l1",
|
||||
PlayStation RightBumper "psx_r1",
|
||||
PlayStation LeftTrigger "psx_l2",
|
||||
PlayStation RightTrigger "psx_r2",
|
||||
PlayStation Select "psx_create",
|
||||
PlayStation Start "psx_options",
|
||||
PlayStation LeftStick "psx_l3",
|
||||
PlayStation RightStick "psx_r3",
|
||||
PlayStation DPadUp "psx_up",
|
||||
PlayStation DPadDown "psx_down",
|
||||
PlayStation DPadLeft "psx_left",
|
||||
PlayStation DPadRight "psx_right",
|
||||
Nintendo South "nx_b",
|
||||
Nintendo East "nx_a",
|
||||
Nintendo West "nx_y",
|
||||
Nintendo North "nx_x",
|
||||
Nintendo LeftBumper "nx_l",
|
||||
Nintendo RightBumper "nx_r",
|
||||
Nintendo LeftTrigger "nx_zl",
|
||||
Nintendo RightTrigger "nx_zr",
|
||||
Nintendo Select "nx_minus",
|
||||
Nintendo Start "nx_plus",
|
||||
Nintendo LeftStick "nx_l3",
|
||||
Nintendo RightStick "nx_r3",
|
||||
Nintendo DPadUp "nx_up",
|
||||
Nintendo DPadDown "nx_down",
|
||||
Nintendo DPadLeft "nx_left",
|
||||
Nintendo DPadRight "nx_right",
|
||||
];
|
||||
|
||||
fn find(button: Button, style: GlyphStyle) -> Option<&'static Art> {
|
||||
let family = match style {
|
||||
GlyphStyle::Auto => GlyphStyle::Xbox,
|
||||
style => style,
|
||||
};
|
||||
ART.iter()
|
||||
.find(|art| art.family == family && art.button == button)
|
||||
}
|
||||
|
||||
/// The sprite of a button in a family, if it has one.
|
||||
pub fn sprite(button: Button, style: GlyphStyle) -> Option<&'static str> {
|
||||
find(button, style).map(|art| art.sprite)
|
||||
}
|
||||
|
||||
/// The sprite this atlas can draw for the button; none (the boot screen's atlas) means text.
|
||||
pub fn art(atlas: &HudAtlas, button: Button, style: GlyphStyle) -> Option<&'static str> {
|
||||
sprite(button, style).filter(|name| atlas.sprites.contains_key(*name))
|
||||
}
|
||||
|
||||
/// How wide the art shows: half its texture (IHDR's width, in a PNG's first bytes) less its edge.
|
||||
/// A pack's own texture of another size is drawn in the same place.
|
||||
fn art_width(art: &Art) -> f32 {
|
||||
let texels = u32::from_be_bytes([art.png[16], art.png[17], art.png[18], art.png[19]]);
|
||||
texels as f32 / 2.0 - 2.0 * MARGIN
|
||||
}
|
||||
|
||||
/// How wide the glyph shows.
|
||||
pub fn width(atlas: &HudAtlas, button: Button, style: GlyphStyle) -> f32 {
|
||||
match find(button, style).filter(|a| atlas.sprites.contains_key(a.sprite)) {
|
||||
Some(art) => art_width(art),
|
||||
None => HudBatch::text_width(atlas, button.label(style)).ceil() + 4.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// What the glyph covers with its label's text at `(x, y)`: `[x, y, w, h]`.
|
||||
pub fn rect(atlas: &HudAtlas, button: Button, style: GlyphStyle, x: f32, y: f32) -> [f32; 4] {
|
||||
[x - 1.0, y - 2.0, width(atlas, button, style), HEIGHT]
|
||||
}
|
||||
|
||||
/// Draw the glyph over the 8 pixel text line at `(x, y)`, filling [`rect`]. A dark key backs text
|
||||
/// only: the art brings its own.
|
||||
pub fn draw(
|
||||
batch: &mut HudBatch,
|
||||
atlas: &HudAtlas,
|
||||
button: Button,
|
||||
style: GlyphStyle,
|
||||
x: f32,
|
||||
y: f32,
|
||||
) {
|
||||
let [left, top, wide, tall] = rect(atlas, button, style, x, y);
|
||||
if let Some(name) = art(atlas, button, style) {
|
||||
batch.sprite_scaled(
|
||||
atlas,
|
||||
name,
|
||||
left - MARGIN,
|
||||
top - MARGIN,
|
||||
wide + 2.0 * MARGIN,
|
||||
tall + 2.0 * MARGIN,
|
||||
u32::MAX,
|
||||
);
|
||||
} else {
|
||||
let [r, g, b] = button.color(style);
|
||||
batch.fill(atlas, left, top, wide, tall, rgba(40, 40, 40, 255));
|
||||
batch.text(
|
||||
atlas,
|
||||
button.label(style),
|
||||
x + 1.0,
|
||||
y,
|
||||
rgba(r, g, b, 255),
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Horizon's art for the atlas: each `(sprite, width, height, RGBA texels)`.
|
||||
pub fn images() -> impl Iterator<Item = (String, u32, u32, Vec<u8>)> {
|
||||
ART.iter()
|
||||
.map(|art| (art.sprite, art.png))
|
||||
.chain([(pad_cursor::SPRITE, pad_cursor::PNG)])
|
||||
.map(|(name, png)| {
|
||||
let (w, h, pixels) = decode(png).expect("Horizon's controller art is PNG");
|
||||
(name.to_owned(), w, h, pixels)
|
||||
})
|
||||
}
|
||||
|
||||
fn decode(bytes: &[u8]) -> Option<(u32, u32, Vec<u8>)> {
|
||||
let mut decoder = png::Decoder::new(std::io::Cursor::new(bytes));
|
||||
decoder.set_transformations(png::Transformations::EXPAND | png::Transformations::STRIP_16);
|
||||
let mut reader = decoder.read_info().ok()?;
|
||||
let (w, h) = (reader.info().width, reader.info().height);
|
||||
let mut buf = vec![0; reader.output_buffer_size()?];
|
||||
let frame = reader.next_frame(&mut buf).ok()?;
|
||||
let channels = frame.color_type.samples();
|
||||
let pixels = buf
|
||||
.chunks(channels)
|
||||
.take((w * h) as usize)
|
||||
.flat_map(|p| match channels {
|
||||
4 => [p[0], p[1], p[2], p[3]],
|
||||
3 => [p[0], p[1], p[2], 255],
|
||||
2 => [p[0], p[0], p[0], p[1]],
|
||||
_ => [p[0], p[0], p[0], 255],
|
||||
})
|
||||
.collect();
|
||||
Some((w, h, pixels))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn atlas() -> HudAtlas {
|
||||
HudAtlas::build(std::path::Path::new("/nonexistent/horizon-test-assets")).unwrap()
|
||||
}
|
||||
|
||||
fn file(button: Button, style: GlyphStyle) -> Option<&'static str> {
|
||||
sprite(button, style).and_then(|name| name.strip_prefix("horizon:gui/controller/"))
|
||||
}
|
||||
|
||||
/// The batch's quads as `[left, top, right, bottom]`.
|
||||
fn quads(batch: &HudBatch) -> Vec<[f32; 4]> {
|
||||
batch
|
||||
.vertices
|
||||
.as_chunks::<6>()
|
||||
.0
|
||||
.iter()
|
||||
.map(|quad| {
|
||||
let xs = quad.iter().map(|v| v.pos[0]);
|
||||
let ys = quad.iter().map(|v| v.pos[1]);
|
||||
[
|
||||
xs.clone().fold(f32::MAX, f32::min),
|
||||
ys.clone().fold(f32::MAX, f32::min),
|
||||
xs.fold(f32::MIN, f32::max),
|
||||
ys.fold(f32::MIN, f32::max),
|
||||
]
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xbox_and_auto_show_the_letters_and_shoulders() {
|
||||
for style in [GlyphStyle::Xbox, GlyphStyle::Auto] {
|
||||
for (button, expected) in [
|
||||
(Button::South, "xb_a"),
|
||||
(Button::East, "xb_b"),
|
||||
(Button::West, "xb_x"),
|
||||
(Button::North, "xb_y"),
|
||||
(Button::LeftBumper, "xb_lb"),
|
||||
(Button::RightBumper, "xb_rb"),
|
||||
(Button::LeftTrigger, "xb_lt"),
|
||||
(Button::RightTrigger, "xb_rt"),
|
||||
(Button::Select, "xb_view"),
|
||||
(Button::Start, "xb_menu"),
|
||||
] {
|
||||
assert_eq!(file(button, style), Some(expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn playstation_shows_its_symbols_by_position_and_its_own_shoulders() {
|
||||
let style = GlyphStyle::PlayStation;
|
||||
assert_eq!(file(Button::South, style), Some("psx_cross"));
|
||||
assert_eq!(file(Button::East, style), Some("psx_circle"));
|
||||
assert_eq!(file(Button::West, style), Some("psx_square"));
|
||||
assert_eq!(file(Button::North, style), Some("psx_triangle"));
|
||||
assert_eq!(file(Button::LeftBumper, style), Some("psx_l1"));
|
||||
assert_eq!(file(Button::RightTrigger, style), Some("psx_r2"));
|
||||
assert_eq!(file(Button::Select, style), Some("psx_create"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nintendo_shows_what_its_pad_prints_at_each_position() {
|
||||
let style = GlyphStyle::Nintendo;
|
||||
assert_eq!(file(Button::South, style), Some("nx_b"));
|
||||
assert_eq!(file(Button::East, style), Some("nx_a"));
|
||||
assert_eq!(file(Button::West, style), Some("nx_y"));
|
||||
assert_eq!(file(Button::North, style), Some("nx_x"));
|
||||
assert_eq!(file(Button::LeftTrigger, style), Some("nx_zl"));
|
||||
assert_eq!(file(Button::Start, style), Some("nx_plus"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_family_has_its_own_file_for_every_button() {
|
||||
for (style, prefix) in [
|
||||
(GlyphStyle::Xbox, "xb_"),
|
||||
(GlyphStyle::PlayStation, "psx_"),
|
||||
(GlyphStyle::Nintendo, "nx_"),
|
||||
] {
|
||||
for button in Button::ALL {
|
||||
let name = file(button, style).unwrap();
|
||||
assert!(name.starts_with(prefix), "{button:?} {name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_sprite_has_its_texture_at_twice_gui_size() {
|
||||
let images: Vec<_> = images().collect();
|
||||
assert_eq!(images.len(), ART.len() + 1, "the art and the cursor");
|
||||
for (name, w, h, pixels) in &images {
|
||||
assert_eq!(pixels.len(), (w * h * 4) as usize, "{name}");
|
||||
assert_eq!(*h as f32, DRAWN_HEIGHT * 2.0, "{name}");
|
||||
}
|
||||
for art in ART {
|
||||
let (_, w, ..) = images.iter().find(|i| i.0 == art.sprite).unwrap();
|
||||
assert_eq!(art_width(art), *w as f32 / 2.0 - 4.0, "{}", art.sprite);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_button_has_two_pictures_in_a_family() {
|
||||
for (i, art) in ART.iter().enumerate() {
|
||||
assert!(
|
||||
!ART[..i]
|
||||
.iter()
|
||||
.any(|a| a.family == art.family && a.button == art.button),
|
||||
"{}",
|
||||
art.sprite
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn art_measures_from_its_shape_and_text_from_its_label() {
|
||||
let atlas = atlas();
|
||||
assert_eq!(width(&atlas, Button::South, GlyphStyle::Xbox), 12.0);
|
||||
assert_eq!(width(&atlas, Button::LeftBumper, GlyphStyle::Xbox), 20.0);
|
||||
assert_eq!(
|
||||
width(&atlas, Button::LeftBumper, GlyphStyle::PlayStation),
|
||||
20.0
|
||||
);
|
||||
assert_eq!(width(&atlas, Button::DPadUp, GlyphStyle::Nintendo), 12.0);
|
||||
let mut no_art = atlas;
|
||||
no_art
|
||||
.sprites
|
||||
.remove(sprite(Button::LeftTrigger, GlyphStyle::Nintendo).unwrap());
|
||||
let label = HudBatch::text_width(&no_art, "ZL").ceil() + 4.0;
|
||||
assert_eq!(
|
||||
width(&no_art, Button::LeftTrigger, GlyphStyle::Nintendo),
|
||||
label
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_atlas_without_the_art_falls_back_to_text() {
|
||||
let mut atlas = atlas();
|
||||
atlas
|
||||
.sprites
|
||||
.remove(sprite(Button::South, GlyphStyle::Xbox).unwrap());
|
||||
assert_eq!(art(&atlas, Button::South, GlyphStyle::Xbox), None);
|
||||
let label = HudBatch::text_width(&atlas, "A").ceil() + 4.0;
|
||||
assert_eq!(width(&atlas, Button::South, GlyphStyle::Xbox), label);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn art_is_one_quad_centred_on_the_text_line_with_no_key_behind_it() {
|
||||
let atlas = atlas();
|
||||
let mut batch = HudBatch::new([640, 480]);
|
||||
draw(
|
||||
&mut batch,
|
||||
&atlas,
|
||||
Button::South,
|
||||
GlyphStyle::Xbox,
|
||||
10.0,
|
||||
20.0,
|
||||
);
|
||||
assert_eq!(quads(&batch), vec![[7.0, 16.0, 23.0, 32.0]]);
|
||||
let mut batch = HudBatch::new([640, 480]);
|
||||
draw(
|
||||
&mut batch,
|
||||
&atlas,
|
||||
Button::LeftBumper,
|
||||
GlyphStyle::Xbox,
|
||||
10.0,
|
||||
20.0,
|
||||
);
|
||||
assert_eq!(quads(&batch), vec![[7.0, 16.0, 31.0, 32.0]]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_sits_on_a_key_covering_what_rect_says() {
|
||||
let mut atlas = atlas();
|
||||
let style = GlyphStyle::Nintendo;
|
||||
atlas.sprites.remove(sprite(Button::South, style).unwrap());
|
||||
let mut batch = HudBatch::new([640, 480]);
|
||||
draw(&mut batch, &atlas, Button::South, style, 10.0, 20.0);
|
||||
let [x, y, w, h] = rect(&atlas, Button::South, style, 10.0, 20.0);
|
||||
assert_eq!(quads(&batch)[0], [x, y, x + w, y + h]);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ use horizon_core::{
|
||||
geometry::{Aabb, Axis, Vec3, trig},
|
||||
random::{LegacyRandom, RandomSource},
|
||||
};
|
||||
use horizon_protocol::entity_data::PositionSource;
|
||||
use horizon_world::block_collision::BlockCollisionRegistry;
|
||||
use std::{
|
||||
array,
|
||||
@@ -50,9 +51,10 @@ pub struct ParticleOptions {
|
||||
/// `GeyserParticleOptions.waterBlocks` and `GeyserBaseParticleOptions`'
|
||||
/// `waterBlocks` and `burstImpulseBase` (0 where the type has none).
|
||||
pub geyser: Option<(i32, f32)>,
|
||||
/// `VibrationParticleOption`: the listener the signal flies to and its
|
||||
/// arrival time in ticks (`ParticleEngine::vibration` is the same particle).
|
||||
pub vibration: Option<([f64; 3], i32)>,
|
||||
/// `VibrationParticleOption`: the listener's `PositionSource`, the point
|
||||
/// the signal flies to and its arrival time in ticks
|
||||
/// (`ParticleEngine::vibration` is the same particle).
|
||||
pub vibration: Option<(PositionSource, [f64; 3], i32)>,
|
||||
/// A native mod's own particle type, by its namespaced name (`mymod:spark`):
|
||||
/// `SimpleParticleType` plus the provider `particles/<name>.json` names.
|
||||
pub mod_particle: Option<String>,
|
||||
@@ -94,10 +96,11 @@ impl ParticleOptions {
|
||||
..Self::simple(ty)
|
||||
}
|
||||
}
|
||||
/// `VibrationParticleOption(destination, arrivalInTicks)`.
|
||||
pub fn vibration(dest: [f64; 3], ticks: i32) -> Self {
|
||||
/// `VibrationParticleOption(destination, arrivalInTicks)`: the listener's
|
||||
/// position source and the point this engine flies the signal to.
|
||||
pub fn vibration(source: PositionSource, dest: [f64; 3], ticks: i32) -> Self {
|
||||
Self {
|
||||
vibration: Some((dest, ticks)),
|
||||
vibration: Some((source, dest, ticks)),
|
||||
..Self::simple(ParticleType::Vibration)
|
||||
}
|
||||
}
|
||||
@@ -2988,8 +2991,8 @@ impl ParticleEngine {
|
||||
// the listener the signal flies to and its arrival time; the sprite
|
||||
// is the set's own draw from the engine's random.
|
||||
T::Vibration => {
|
||||
let (dest, ticks) = o.vibration?;
|
||||
let mut q = Self::vibration_particle(p, dest, ticks);
|
||||
let (_, dest, ticks) = o.vibration.as_ref()?;
|
||||
let mut q = Self::vibration_particle(p, *dest, *ticks);
|
||||
q.set = set;
|
||||
q.sprite = self.sprite_from_set(ty);
|
||||
q
|
||||
@@ -5644,7 +5647,8 @@ mod tests {
|
||||
#[test]
|
||||
fn a_vibration_option_makes_the_signal_its_provider_draws() {
|
||||
let mut engine = ParticleEngine::new(Path::new(""), &HashMap::new(), &HashSet::new());
|
||||
let options = ParticleOptions::vibration([10.5, 70.5, -3.5], 20);
|
||||
let options =
|
||||
ParticleOptions::vibration(PositionSource::Block([10, 70, -4]), [10.5, 70.5, -3.5], 20);
|
||||
// Within the camera's 32 blocks, so only the provider decides.
|
||||
engine.add_with(&options, [0.5, 1.5, 0.5], [0.0; 3], false, false);
|
||||
assert_eq!(engine.made().get("vibration"), Some(&1));
|
||||
|
||||
@@ -575,8 +575,53 @@ pub struct Hud {
|
||||
/// Hits in a row the server confirmed, until this player is hurt or 3 seconds pass.
|
||||
pub combo: bool,
|
||||
pub combo_corner: Corner,
|
||||
/// Frames drawn in the last second.
|
||||
pub fps: bool,
|
||||
pub fps_corner: Corner,
|
||||
/// Left and right clicks in the last second.
|
||||
pub cps: bool,
|
||||
pub cps_corner: Corner,
|
||||
/// The movement keys, mouse buttons and jump, lit while held, with clicks per second.
|
||||
pub keys: bool,
|
||||
pub keys_corner: Corner,
|
||||
/// The computer's local time.
|
||||
pub clock: bool,
|
||||
pub clock_corner: Corner,
|
||||
/// How far the last hit landed from the eyes, for 3 seconds; it only measures.
|
||||
pub reach: bool,
|
||||
pub reach_corner: Corner,
|
||||
/// Horizontal speed in blocks per second.
|
||||
pub speed: bool,
|
||||
pub speed_corner: Corner,
|
||||
/// A dark box behind each widget.
|
||||
pub background: bool,
|
||||
/// The boxes' opacity, in percent.
|
||||
pub opacity: u8,
|
||||
/// The widgets' text colour (durability keeps its own).
|
||||
pub text: Colour,
|
||||
/// A shadow under the widgets' text, as vanilla's HUD text has.
|
||||
pub shadow: bool,
|
||||
}
|
||||
impl Hud {
|
||||
/// Whether any widget is on: with none, the HUD draws nothing and reads nothing.
|
||||
pub fn any(&self) -> bool {
|
||||
[
|
||||
self.armour,
|
||||
self.effects,
|
||||
self.held,
|
||||
self.ping,
|
||||
self.coords,
|
||||
self.server,
|
||||
self.combo,
|
||||
self.fps,
|
||||
self.cps,
|
||||
self.keys,
|
||||
self.clock,
|
||||
self.reach,
|
||||
self.speed,
|
||||
]
|
||||
.contains(&true)
|
||||
}
|
||||
}
|
||||
impl Default for Hud {
|
||||
fn default() -> Self {
|
||||
@@ -596,11 +641,35 @@ impl Default for Hud {
|
||||
server_corner: Corner::TopLeft,
|
||||
combo: false,
|
||||
combo_corner: Corner::TopLeft,
|
||||
fps: false,
|
||||
fps_corner: Corner::TopLeft,
|
||||
cps: false,
|
||||
cps_corner: Corner::TopLeft,
|
||||
keys: false,
|
||||
keys_corner: Corner::BottomLeft,
|
||||
clock: false,
|
||||
clock_corner: Corner::TopRight,
|
||||
reach: false,
|
||||
reach_corner: Corner::TopLeft,
|
||||
speed: false,
|
||||
speed_corner: Corner::TopLeft,
|
||||
background: true,
|
||||
opacity: HUD_OPACITY,
|
||||
text: Colour::White,
|
||||
shadow: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub use crate::extras::Corner;
|
||||
/// The widgets' boxes at first: the extras' 0x78 alpha, 47%.
|
||||
pub const HUD_OPACITY: u8 = 47;
|
||||
pub const HUD_OPACITIES: (u8, u8) = (0, 100);
|
||||
impl Hud {
|
||||
/// The boxes' colour, ARGB.
|
||||
pub fn background_argb(&self) -> u32 {
|
||||
alpha(self.opacity) << 24
|
||||
}
|
||||
}
|
||||
/// The value of the view options while they follow the world.
|
||||
fn world_label() -> String {
|
||||
crate::lang::get_or("horizon.pvp.view.world", "World's")
|
||||
@@ -682,7 +751,38 @@ impl ChatOptions {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
/// Combat feel (`docs/pvp.md`): how fights look. Legacy draws them as 1.8.9 did; what the
|
||||
/// game sends to a server never changes, so a vanilla server and its anticheat see 26.3.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct Combat {
|
||||
/// Legacy (1.8.9) instead of Modern.
|
||||
pub legacy: bool,
|
||||
/// Keep the attack indicator under Legacy (1.8.9 had none).
|
||||
pub indicator: bool,
|
||||
/// Under Legacy, silence the attack sounds 1.9 added (1.8.9 played none on a hit).
|
||||
pub sounds: bool,
|
||||
/// The worlds this game hosts (single player and LAN) fight by 1.8.9's rules: no attack
|
||||
/// cooldown, no sweep, 1.8 weapon damage, effects, enchantments and critical hits
|
||||
/// (`horizon_client::legacy_combat`). Never used as a client of a server.
|
||||
pub rules: bool,
|
||||
}
|
||||
impl Combat {
|
||||
/// Whether this client leaves out sound `name` a server sent: under Legacy with its
|
||||
/// sounds, the `entity.player.attack.*` family (strong, weak, crit, sweep, knockback,
|
||||
/// nodamage), which 1.9 brought with the attack cooldown.
|
||||
pub fn mutes_sound(&self, name: &str) -> bool {
|
||||
let name = name.strip_prefix("minecraft:").unwrap_or(name);
|
||||
self.legacy && self.sounds && name.starts_with("entity.player.attack.")
|
||||
}
|
||||
/// Whether this client leaves out particles of `ty` a server sent: under Legacy, the
|
||||
/// sweep and the damage hearts, which 1.9 brought (1.8.9's hits show crits only).
|
||||
pub fn hides_particle(&self, ty: crate::particles::ParticleType) -> bool {
|
||||
use crate::particles::ParticleType as T;
|
||||
self.legacy && matches!(ty, T::SweepAttack | T::DamageIndicator)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Pvp {
|
||||
pub crosshair: Crosshair,
|
||||
pub outline: Outline,
|
||||
@@ -690,6 +790,28 @@ pub struct Pvp {
|
||||
pub hud: Hud,
|
||||
pub tweaks: Tweaks,
|
||||
pub chat: ChatOptions,
|
||||
/// Where each HUD widget sits and its size.
|
||||
pub layout: crate::pvp_hud::Layout,
|
||||
/// The key that opens the HUD editor from the game (unbound at first).
|
||||
pub editor_key: String,
|
||||
pub combat: Combat,
|
||||
}
|
||||
/// The HUD editor key's name for the controls (`Extras::set_key`).
|
||||
pub const HUD_EDITOR_KEY: &str = "horizon.hud_editor";
|
||||
impl Default for Pvp {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
crosshair: Crosshair::default(),
|
||||
outline: Outline::default(),
|
||||
hit: Hit::default(),
|
||||
hud: Hud::default(),
|
||||
tweaks: Tweaks::default(),
|
||||
chat: ChatOptions::default(),
|
||||
layout: crate::pvp_hud::Layout::default(),
|
||||
editor_key: "key.keyboard.unknown".into(),
|
||||
combat: Combat::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
fn flag(value: &str, d: bool) -> bool {
|
||||
match value {
|
||||
@@ -743,6 +865,15 @@ impl Pvp {
|
||||
"hudHeld" => u.held = flag(value, u.held),
|
||||
"hudHeldCorner" => u.held_corner = Corner::parse(value, u.held_corner),
|
||||
"hudBackground" => u.background = flag(value, u.background),
|
||||
"hudOpacity" => u.opacity = num(value, HUD_OPACITIES, u.opacity),
|
||||
"hudTextColour" => {
|
||||
u.text = Colour::parse(value)
|
||||
.filter(|c| *c != Colour::Inverted)
|
||||
.unwrap_or(u.text)
|
||||
}
|
||||
"hudShadow" => u.shadow = flag(value, u.shadow),
|
||||
"hudPlace" => self.layout.parse(value),
|
||||
"hudEditorKey" if value.starts_with("key.") => self.editor_key = value.to_owned(),
|
||||
"hudPing" => u.ping = flag(value, u.ping),
|
||||
"hudPingCorner" => u.ping_corner = Corner::parse(value, u.ping_corner),
|
||||
"hudCoords" => u.coords = flag(value, u.coords),
|
||||
@@ -751,6 +882,18 @@ impl Pvp {
|
||||
"hudServerCorner" => u.server_corner = Corner::parse(value, u.server_corner),
|
||||
"hudCombo" => u.combo = flag(value, u.combo),
|
||||
"hudComboCorner" => u.combo_corner = Corner::parse(value, u.combo_corner),
|
||||
"hudFps" => u.fps = flag(value, u.fps),
|
||||
"hudFpsCorner" => u.fps_corner = Corner::parse(value, u.fps_corner),
|
||||
"hudCps" => u.cps = flag(value, u.cps),
|
||||
"hudCpsCorner" => u.cps_corner = Corner::parse(value, u.cps_corner),
|
||||
"hudKeys" => u.keys = flag(value, u.keys),
|
||||
"hudKeysCorner" => u.keys_corner = Corner::parse(value, u.keys_corner),
|
||||
"hudClock" => u.clock = flag(value, u.clock),
|
||||
"hudClockCorner" => u.clock_corner = Corner::parse(value, u.clock_corner),
|
||||
"hudReach" => u.reach = flag(value, u.reach),
|
||||
"hudReachCorner" => u.reach_corner = Corner::parse(value, u.reach_corner),
|
||||
"hudSpeed" => u.speed = flag(value, u.speed),
|
||||
"hudSpeedCorner" => u.speed_corner = Corner::parse(value, u.speed_corner),
|
||||
"numericEnchants" => t.numeric_enchants = flag(value, t.numeric_enchants),
|
||||
"lowFire" => t.fire_lower = num(value, FIRE_LOWER, t.fire_lower),
|
||||
"shieldSize" => t.shield_size = num(value, SHIELD_SIZES, t.shield_size),
|
||||
@@ -767,6 +910,14 @@ impl Pvp {
|
||||
"chatCompact" => self.chat.compact = flag(value, self.chat.compact),
|
||||
"chatCopy" => self.chat.copy = flag(value, self.chat.copy),
|
||||
"chatSearch" => self.chat.search = flag(value, self.chat.search),
|
||||
"combatFeel" => match value {
|
||||
"legacy" => self.combat.legacy = true,
|
||||
"modern" => self.combat.legacy = false,
|
||||
_ => {}
|
||||
},
|
||||
"combatIndicator" => self.combat.indicator = flag(value, self.combat.indicator),
|
||||
"combatSounds" => self.combat.sounds = flag(value, self.combat.sounds),
|
||||
"combatRules" => self.combat.rules = flag(value, self.combat.rules),
|
||||
"viewTime" => {
|
||||
t.time = match value {
|
||||
"off" => None,
|
||||
@@ -838,6 +989,35 @@ impl Pvp {
|
||||
u.combo,
|
||||
u.combo_corner.name(),
|
||||
);
|
||||
let _ = write!(
|
||||
out,
|
||||
"hudFps:{}\nhudFpsCorner:{}\nhudCps:{}\nhudCpsCorner:{}\nhudKeys:{}\nhudKeysCorner:{}\n",
|
||||
u.fps,
|
||||
u.fps_corner.name(),
|
||||
u.cps,
|
||||
u.cps_corner.name(),
|
||||
u.keys,
|
||||
u.keys_corner.name(),
|
||||
);
|
||||
let _ = write!(
|
||||
out,
|
||||
"hudClock:{}\nhudClockCorner:{}\nhudReach:{}\nhudReachCorner:{}\nhudSpeed:{}\nhudSpeedCorner:{}\n",
|
||||
u.clock,
|
||||
u.clock_corner.name(),
|
||||
u.reach,
|
||||
u.reach_corner.name(),
|
||||
u.speed,
|
||||
u.speed_corner.name(),
|
||||
);
|
||||
let _ = write!(
|
||||
out,
|
||||
"hudOpacity:{}\nhudTextColour:{}\nhudShadow:{}\n",
|
||||
u.opacity,
|
||||
u.text.name(),
|
||||
u.shadow,
|
||||
);
|
||||
self.layout.serialize(out);
|
||||
let _ = writeln!(out, "hudEditorKey:{}", self.editor_key);
|
||||
let t = &self.tweaks;
|
||||
let _ = write!(
|
||||
out,
|
||||
@@ -862,6 +1042,13 @@ impl Pvp {
|
||||
"chatTimestamps:{}\nchatCompact:{}\nchatCopy:{}\nchatSearch:{}\n",
|
||||
c.timestamps, c.compact, c.copy, c.search,
|
||||
);
|
||||
let c = &self.combat;
|
||||
let feel = if c.legacy { "legacy" } else { "modern" };
|
||||
let _ = write!(
|
||||
out,
|
||||
"combatFeel:{feel}\ncombatIndicator:{}\ncombatSounds:{}\ncombatRules:{}\n",
|
||||
c.indicator, c.sounds, c.rules
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -955,7 +1142,27 @@ opts! {
|
||||
HudCombo, "hud_combo", "Combo counter",
|
||||
"Hits in a row the server confirmed, until you are hurt or 3 seconds pass without one.";
|
||||
HudComboCorner, "hud_combo_corner", "Combo corner", "Where the combo shows.";
|
||||
HudFps, "hud_fps", "FPS", "Frames drawn in the last second.";
|
||||
HudFpsCorner, "hud_fps_corner", "FPS corner", "Where the FPS shows.";
|
||||
HudCps, "hud_cps", "Clicks per second", "Your left and right clicks in the last second.";
|
||||
HudCpsCorner, "hud_cps_corner", "CPS corner", "Where the clicks per second show.";
|
||||
HudKeys, "hud_keys", "Keystrokes",
|
||||
"Your movement keys, mouse buttons and jump, lit while you hold them, with clicks per second.";
|
||||
HudKeysCorner, "hud_keys_corner", "Keystrokes corner", "Where the keystrokes show.";
|
||||
HudClock, "hud_clock", "Clock", "Your computer's time.";
|
||||
HudClockCorner, "hud_clock_corner", "Clock corner", "Where the clock shows.";
|
||||
HudReach, "hud_reach", "Reach",
|
||||
"How far from your eyes your last hit landed, for 3 seconds. It only measures: your reach stays vanilla's.";
|
||||
HudReachCorner, "hud_reach_corner", "Reach corner", "Where the reach shows.";
|
||||
HudSpeed, "hud_speed", "Speed", "How fast you move across the ground, in blocks per second.";
|
||||
HudSpeedCorner, "hud_speed_corner", "Speed corner", "Where the speed shows.";
|
||||
HudBackground, "hud_background", "Widget backgrounds", "A dark box behind each widget.";
|
||||
HudOpacity, "hud_opacity", "Background opacity", "How dark the boxes behind the widgets are.";
|
||||
HudTextColour, "hud_text_colour", "Text colour",
|
||||
"The widgets' text colour. Durability keeps its green to red.";
|
||||
HudShadow, "hud_shadow", "Text shadow", "A shadow under the widgets' text, as vanilla's HUD text has.";
|
||||
HudEditorKey, "hud_editor_key", "HUD editor key",
|
||||
"Opens the HUD editor from the game. Click, then press a key; Escape unbinds it.";
|
||||
NumericEnchants, "numeric_enchants", "Enchantment levels as numbers",
|
||||
"Tooltips and the enchanting table show Sharpness 5 instead of Sharpness V.";
|
||||
FireLower, "fire_lower", "Lower fire",
|
||||
@@ -980,6 +1187,14 @@ opts! {
|
||||
"Clicking a message in the open chat copies its text, unless the click does something else.";
|
||||
ChatSearch, "chat_search", "Chat search",
|
||||
"Ctrl+F in the open chat: what you type shows only the messages holding it. Enter or Ctrl+F again stops.";
|
||||
CombatFeel, "combat_feel", "Combat feel",
|
||||
"Legacy draws fights as 1.8.9 did: the item in your hand, its swing, eating, the bow and a sword held up while you hold use, without the dip after each hit or the attack indicator. Only the look: what you send a server stays the same.";
|
||||
CombatIndicator, "combat_indicator", "Attack indicator in Legacy",
|
||||
"Keeps the attack indicator under Legacy, which 1.8.9 didn't have.";
|
||||
CombatSounds, "combat_sounds", "1.8 hit sounds",
|
||||
"Under Legacy, leaves out the attack sounds 1.9 added (strong, weak, sweep, critical...). Hurt sounds stay.";
|
||||
CombatRules, "combat_rules", "1.8 combat in worlds you host",
|
||||
"Single player and the worlds you open to LAN fight by 1.8.9's rules: no attack cooldown or sweep, 1.8 weapon damage, Strength, Sharpness and critical hits. Servers you join keep their own rules.";
|
||||
}
|
||||
/// The PvP & HUD page: headers (language keys) and options.
|
||||
pub fn rows() -> Vec<Result<Opt, &'static str>> {
|
||||
@@ -1030,7 +1245,23 @@ pub fn rows() -> Vec<Result<Opt, &'static str>> {
|
||||
HudServerCorner,
|
||||
HudCombo,
|
||||
HudComboCorner,
|
||||
HudFps,
|
||||
HudFpsCorner,
|
||||
HudCps,
|
||||
HudCpsCorner,
|
||||
HudKeys,
|
||||
HudKeysCorner,
|
||||
HudClock,
|
||||
HudClockCorner,
|
||||
HudReach,
|
||||
HudReachCorner,
|
||||
HudSpeed,
|
||||
HudSpeedCorner,
|
||||
HudBackground,
|
||||
HudOpacity,
|
||||
HudTextColour,
|
||||
HudShadow,
|
||||
HudEditorKey,
|
||||
]
|
||||
.map(Ok),
|
||||
);
|
||||
@@ -1043,6 +1274,8 @@ pub fn rows() -> Vec<Result<Opt, &'static str>> {
|
||||
rows.extend([ViewTime, ViewWeather].map(Ok));
|
||||
rows.push(Err("horizon.pvp.header.chat"));
|
||||
rows.extend([ChatTimestamps, ChatCompact, ChatCopy, ChatSearch].map(Ok));
|
||||
rows.push(Err("horizon.pvp.header.combat"));
|
||||
rows.extend([CombatFeel, CombatIndicator, CombatSounds, CombatRules].map(Ok));
|
||||
rows
|
||||
}
|
||||
impl Opt {
|
||||
@@ -1054,10 +1287,19 @@ impl Opt {
|
||||
OutlineColour | OutlineOpacity | OutlineThickness | OutlineFill => Some(Outline),
|
||||
HitColour | HitOpacity => Some(Hit),
|
||||
HudArmour | HudEffects | HudHeld | HudPing | HudCoords | HudServer | HudCombo
|
||||
| HudBackground | NumericEnchants => None,
|
||||
| HudFps | HudCps | HudKeys | HudClock | HudReach | HudSpeed | HudBackground
|
||||
| HudTextColour | HudShadow | HudEditorKey | NumericEnchants => None,
|
||||
HudOpacity => Some(HudBackground),
|
||||
HudFpsCorner => Some(HudFps),
|
||||
HudCpsCorner => Some(HudCps),
|
||||
HudKeysCorner => Some(HudKeys),
|
||||
HudClockCorner => Some(HudClock),
|
||||
HudReachCorner => Some(HudReach),
|
||||
HudSpeedCorner => Some(HudSpeed),
|
||||
FireLower | ShieldSize | TotemSize | GlintColour | ClearWater | ClearLava => None,
|
||||
ViewTime | ViewWeather => None,
|
||||
ChatTimestamps | ChatCompact | ChatCopy | ChatSearch => None,
|
||||
CombatFeel | CombatIndicator | CombatSounds | CombatRules => None,
|
||||
HudPingCorner => Some(HudPing),
|
||||
HudCoordsCorner => Some(HudCoords),
|
||||
HudServerCorner => Some(HudServer),
|
||||
@@ -1081,10 +1323,17 @@ impl Opt {
|
||||
Opt::HudEffects => p.hud.effects,
|
||||
Opt::HudHeld => p.hud.held,
|
||||
Opt::HudBackground => p.hud.background,
|
||||
Opt::HudShadow => p.hud.shadow,
|
||||
Opt::HudPing => p.hud.ping,
|
||||
Opt::HudCoords => p.hud.coords,
|
||||
Opt::HudServer => p.hud.server,
|
||||
Opt::HudCombo => p.hud.combo,
|
||||
Opt::HudFps => p.hud.fps,
|
||||
Opt::HudCps => p.hud.cps,
|
||||
Opt::HudKeys => p.hud.keys,
|
||||
Opt::HudClock => p.hud.clock,
|
||||
Opt::HudReach => p.hud.reach,
|
||||
Opt::HudSpeed => p.hud.speed,
|
||||
Opt::NumericEnchants => p.tweaks.numeric_enchants,
|
||||
Opt::ClearWater => p.tweaks.clear_water,
|
||||
Opt::ClearLava => p.tweaks.clear_lava,
|
||||
@@ -1092,6 +1341,9 @@ impl Opt {
|
||||
Opt::ChatCompact => p.chat.compact,
|
||||
Opt::ChatCopy => p.chat.copy,
|
||||
Opt::ChatSearch => p.chat.search,
|
||||
Opt::CombatIndicator => p.combat.indicator,
|
||||
Opt::CombatSounds => p.combat.sounds,
|
||||
Opt::CombatRules => p.combat.rules,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
@@ -1124,6 +1376,15 @@ impl Opt {
|
||||
Opt::HudCoordsCorner => corner_label(p.hud.coords_corner),
|
||||
Opt::HudServerCorner => corner_label(p.hud.server_corner),
|
||||
Opt::HudComboCorner => corner_label(p.hud.combo_corner),
|
||||
Opt::HudFpsCorner => corner_label(p.hud.fps_corner),
|
||||
Opt::HudCpsCorner => corner_label(p.hud.cps_corner),
|
||||
Opt::HudKeysCorner => corner_label(p.hud.keys_corner),
|
||||
Opt::HudClockCorner => corner_label(p.hud.clock_corner),
|
||||
Opt::HudReachCorner => corner_label(p.hud.reach_corner),
|
||||
Opt::HudSpeedCorner => corner_label(p.hud.speed_corner),
|
||||
Opt::HudOpacity => format!("{}%", p.hud.opacity),
|
||||
Opt::HudTextColour => p.hud.text.label(),
|
||||
Opt::HudEditorKey => crate::keybinds::display_name(&p.editor_key).plain(),
|
||||
Opt::FireLower => match p.tweaks.fire_lower {
|
||||
0 => crate::lang::get_or("Off", "Off"),
|
||||
v => format!("{v}%"),
|
||||
@@ -1134,6 +1395,10 @@ impl Opt {
|
||||
Some(colour) => colour.label(),
|
||||
None => crate::lang::get_or("Off", "Off"),
|
||||
},
|
||||
Opt::CombatFeel => match p.combat.legacy {
|
||||
true => crate::lang::get_or("horizon.pvp.combat_feel.legacy", "Legacy (1.8.9)"),
|
||||
false => crate::lang::get_or("horizon.pvp.combat_feel.modern", "Modern"),
|
||||
},
|
||||
Opt::ViewTime => p.tweaks.time.map_or_else(world_label, ViewTime::label),
|
||||
Opt::ViewWeather => p
|
||||
.tweaks
|
||||
@@ -1191,6 +1456,11 @@ impl Opt {
|
||||
Opt::HudHeld => u.held = !u.held,
|
||||
Opt::HudHeldCorner => u.held_corner = corner(u.held_corner),
|
||||
Opt::HudBackground => u.background = !u.background,
|
||||
Opt::HudOpacity => u.opacity = step_by(u.opacity, HUD_OPACITIES, 10, back),
|
||||
Opt::HudTextColour => u.text = u.text.step_solid(back),
|
||||
Opt::HudShadow => u.shadow = !u.shadow,
|
||||
// Bound through the controls' key capture (`Item::key_name`).
|
||||
Opt::HudEditorKey => {}
|
||||
Opt::HudPing => u.ping = !u.ping,
|
||||
Opt::HudPingCorner => u.ping_corner = corner(u.ping_corner),
|
||||
Opt::HudCoords => u.coords = !u.coords,
|
||||
@@ -1199,6 +1469,18 @@ impl Opt {
|
||||
Opt::HudServerCorner => u.server_corner = corner(u.server_corner),
|
||||
Opt::HudCombo => u.combo = !u.combo,
|
||||
Opt::HudComboCorner => u.combo_corner = corner(u.combo_corner),
|
||||
Opt::HudFps => u.fps = !u.fps,
|
||||
Opt::HudFpsCorner => u.fps_corner = corner(u.fps_corner),
|
||||
Opt::HudCps => u.cps = !u.cps,
|
||||
Opt::HudCpsCorner => u.cps_corner = corner(u.cps_corner),
|
||||
Opt::HudKeys => u.keys = !u.keys,
|
||||
Opt::HudKeysCorner => u.keys_corner = corner(u.keys_corner),
|
||||
Opt::HudClock => u.clock = !u.clock,
|
||||
Opt::HudClockCorner => u.clock_corner = corner(u.clock_corner),
|
||||
Opt::HudReach => u.reach = !u.reach,
|
||||
Opt::HudReachCorner => u.reach_corner = corner(u.reach_corner),
|
||||
Opt::HudSpeed => u.speed = !u.speed,
|
||||
Opt::HudSpeedCorner => u.speed_corner = corner(u.speed_corner),
|
||||
Opt::NumericEnchants => p.tweaks.numeric_enchants = !p.tweaks.numeric_enchants,
|
||||
Opt::FireLower => t.fire_lower = step_by(t.fire_lower, FIRE_LOWER, 10, back),
|
||||
Opt::ShieldSize => t.shield_size = step_by(t.shield_size, SHIELD_SIZES, 10, back),
|
||||
@@ -1216,6 +1498,10 @@ impl Opt {
|
||||
Opt::ChatCompact => p.chat.compact = !p.chat.compact,
|
||||
Opt::ChatCopy => p.chat.copy = !p.chat.copy,
|
||||
Opt::ChatSearch => p.chat.search = !p.chat.search,
|
||||
Opt::CombatFeel => p.combat.legacy = !p.combat.legacy,
|
||||
Opt::CombatIndicator => p.combat.indicator = !p.combat.indicator,
|
||||
Opt::CombatSounds => p.combat.sounds = !p.combat.sounds,
|
||||
Opt::CombatRules => p.combat.rules = !p.combat.rules,
|
||||
Opt::ViewWeather => {
|
||||
let all: Vec<_> = std::iter::once(None)
|
||||
.chain(ViewWeather::ALL.map(Some))
|
||||
@@ -1518,4 +1804,32 @@ mod tests {
|
||||
assert_eq!(en.get(key), Some(*english), "{key}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_leaves_out_what_1_9_added_to_a_hit() {
|
||||
use crate::particles::ParticleType as T;
|
||||
let modern = Combat::default();
|
||||
let legacy = Combat {
|
||||
legacy: true,
|
||||
..Combat::default()
|
||||
};
|
||||
let sounds = Combat {
|
||||
sounds: true,
|
||||
..legacy
|
||||
};
|
||||
for name in [
|
||||
"minecraft:entity.player.attack.strong",
|
||||
"entity.player.attack.sweep",
|
||||
"minecraft:entity.player.attack.nodamage",
|
||||
] {
|
||||
assert!(sounds.mutes_sound(name), "{name}");
|
||||
assert!(!legacy.mutes_sound(name), "only with the sounds option");
|
||||
assert!(!modern.mutes_sound(name));
|
||||
}
|
||||
// 1.8.9 had the hurt sound: it stays.
|
||||
assert!(!sounds.mutes_sound("minecraft:entity.player.hurt"));
|
||||
assert!(legacy.hides_particle(T::SweepAttack) && legacy.hides_particle(T::DamageIndicator));
|
||||
assert!(!legacy.hides_particle(T::Crit) && !legacy.hides_particle(T::EnchantedHit));
|
||||
assert!(!modern.hides_particle(T::SweepAttack));
|
||||
}
|
||||
}
|
||||
@@ -214,6 +214,320 @@ impl Combo {
|
||||
}
|
||||
}
|
||||
|
||||
/// Clicks per second: the presses of the last second, kept for the widgets that are on.
|
||||
#[derive(Default)]
|
||||
pub struct Clicks {
|
||||
/// Press times in seconds, oldest first; at most [`Clicks::KEEP`].
|
||||
times: std::collections::VecDeque<f64>,
|
||||
}
|
||||
impl Clicks {
|
||||
/// More than any hand clicks in a second: the ring never grows past it.
|
||||
const KEEP: usize = 64;
|
||||
pub fn press(&mut self, now: f64) {
|
||||
if self.times.len() == Self::KEEP {
|
||||
self.times.pop_front();
|
||||
}
|
||||
self.times.push_back(now);
|
||||
}
|
||||
/// The presses in the second up to `now`.
|
||||
pub fn per_second(&mut self, now: f64) -> u32 {
|
||||
while self.times.front().is_some_and(|&t| now - t >= 1.0) {
|
||||
self.times.pop_front();
|
||||
}
|
||||
self.times.len() as u32
|
||||
}
|
||||
}
|
||||
|
||||
/// Horizontal speed in blocks per second, from the player's own position sampled at most
|
||||
/// every [`Speed::SAMPLE`] seconds (nothing is asked of the server).
|
||||
#[derive(Default)]
|
||||
pub struct Speed {
|
||||
last: Option<([f64; 2], f64)>,
|
||||
/// Tenths of a block per second.
|
||||
tenths: u32,
|
||||
}
|
||||
impl Speed {
|
||||
pub const SAMPLE: f64 = 0.25;
|
||||
pub fn sample(&mut self, x: f64, z: f64, now: f64) -> u32 {
|
||||
match self.last {
|
||||
Some((_, at)) if now - at < Self::SAMPLE => {}
|
||||
Some(([lx, lz], at)) => {
|
||||
let d = ((x - lx).powi(2) + (z - lz).powi(2)).sqrt();
|
||||
// A teleport or respawn isn't speed.
|
||||
let v = d / (now - at);
|
||||
self.tenths = if v > 200.0 {
|
||||
0
|
||||
} else {
|
||||
(v * 10.0).round() as u32
|
||||
};
|
||||
self.last = Some(([x, z], now));
|
||||
}
|
||||
None => self.last = Some(([x, z], now)),
|
||||
}
|
||||
self.tenths
|
||||
}
|
||||
}
|
||||
|
||||
/// The reach display: how far the last hit this player made was, measured from the eyes to
|
||||
/// the point the crosshair's ray met the target's box. It measures what the client already
|
||||
/// computed for the attack and changes nothing about it.
|
||||
#[derive(Default)]
|
||||
pub struct Reach {
|
||||
last: Option<(f64, f64)>,
|
||||
}
|
||||
impl Reach {
|
||||
/// How long the last hit's distance stays shown.
|
||||
pub const SHOWN: f64 = 3.0;
|
||||
pub fn hit(&mut self, distance: f64, now: f64) {
|
||||
self.last = Some((distance, now));
|
||||
}
|
||||
/// Hundredths of a block, while the last hit is recent.
|
||||
pub fn hundredths(&self, now: f64) -> Option<u32> {
|
||||
let (d, at) = self.last?;
|
||||
(now - at <= Self::SHOWN).then(|| (d * 100.0).round() as u32)
|
||||
}
|
||||
}
|
||||
/// `n` hundredths as "3.05".
|
||||
pub fn hundredths(n: u32) -> String {
|
||||
format!("{}.{:02}", n / 100, n % 100)
|
||||
}
|
||||
/// `n` tenths as "4.3".
|
||||
pub fn tenths(n: u32) -> String {
|
||||
format!("{}.{}", n / 10, n % 10)
|
||||
}
|
||||
/// The local time as "14:05".
|
||||
pub fn clock(hour: u8, minute: u8) -> String {
|
||||
format!("{hour:02}:{minute:02}")
|
||||
}
|
||||
|
||||
/// The keystrokes widget's keys, in drawing order.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Key {
|
||||
Forward,
|
||||
Left,
|
||||
Back,
|
||||
Right,
|
||||
Attack,
|
||||
Use,
|
||||
Jump,
|
||||
}
|
||||
impl Key {
|
||||
pub const ALL: [Key; 7] = [
|
||||
Key::Forward,
|
||||
Key::Left,
|
||||
Key::Back,
|
||||
Key::Right,
|
||||
Key::Attack,
|
||||
Key::Use,
|
||||
Key::Jump,
|
||||
];
|
||||
/// The key mapping it shows.
|
||||
pub fn mapping(self) -> &'static str {
|
||||
match self {
|
||||
Key::Forward => "key.forward",
|
||||
Key::Left => "key.left",
|
||||
Key::Back => "key.back",
|
||||
Key::Right => "key.right",
|
||||
Key::Attack => "key.attack",
|
||||
Key::Use => "key.use",
|
||||
Key::Jump => "key.jump",
|
||||
}
|
||||
}
|
||||
}
|
||||
/// The keystrokes' key size and the gap between keys, in GUI pixels.
|
||||
pub const KEY: f32 = 22.0;
|
||||
pub const KEY_GAP: f32 = 2.0;
|
||||
/// The space bar's height.
|
||||
pub const SPACE: f32 = 12.0;
|
||||
/// Where each key of the keystrokes widget sits in its box: W above A S D, the two mouse
|
||||
/// buttons under them, the space bar at the bottom, as `(x, y, w, h)`.
|
||||
pub fn key_rect(key: Key) -> (f32, f32, f32, f32) {
|
||||
let step = KEY + KEY_GAP;
|
||||
let full = 3.0 * KEY + 2.0 * KEY_GAP;
|
||||
let half = (full - KEY_GAP) / 2.0;
|
||||
match key {
|
||||
Key::Forward => (step, 0.0, KEY, KEY),
|
||||
Key::Left => (0.0, step, KEY, KEY),
|
||||
Key::Back => (step, step, KEY, KEY),
|
||||
Key::Right => (2.0 * step, step, KEY, KEY),
|
||||
Key::Attack => (0.0, 2.0 * step, half, KEY),
|
||||
Key::Use => (half + KEY_GAP, 2.0 * step, half, KEY),
|
||||
Key::Jump => (0.0, 3.0 * step, full, SPACE),
|
||||
}
|
||||
}
|
||||
/// The keystrokes widget's size.
|
||||
pub fn keys_size() -> (f32, f32) {
|
||||
let (_, y, w, h) = key_rect(Key::Jump);
|
||||
(w, y + h)
|
||||
}
|
||||
|
||||
/// The HUD widgets, for their places on screen ([`Layout`]).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum WidgetId {
|
||||
Armour,
|
||||
Effects,
|
||||
Held,
|
||||
Ping,
|
||||
Coords,
|
||||
Server,
|
||||
Combo,
|
||||
Fps,
|
||||
Cps,
|
||||
Keys,
|
||||
Clock,
|
||||
Reach,
|
||||
Speed,
|
||||
}
|
||||
impl WidgetId {
|
||||
pub const ALL: [WidgetId; 13] = [
|
||||
WidgetId::Armour,
|
||||
WidgetId::Effects,
|
||||
WidgetId::Held,
|
||||
WidgetId::Ping,
|
||||
WidgetId::Coords,
|
||||
WidgetId::Server,
|
||||
WidgetId::Combo,
|
||||
WidgetId::Fps,
|
||||
WidgetId::Cps,
|
||||
WidgetId::Keys,
|
||||
WidgetId::Clock,
|
||||
WidgetId::Reach,
|
||||
WidgetId::Speed,
|
||||
];
|
||||
pub fn name(self) -> &'static str {
|
||||
match self {
|
||||
WidgetId::Armour => "armour",
|
||||
WidgetId::Effects => "effects",
|
||||
WidgetId::Held => "held",
|
||||
WidgetId::Ping => "ping",
|
||||
WidgetId::Coords => "coords",
|
||||
WidgetId::Server => "server",
|
||||
WidgetId::Combo => "combo",
|
||||
WidgetId::Fps => "fps",
|
||||
WidgetId::Cps => "cps",
|
||||
WidgetId::Keys => "keys",
|
||||
WidgetId::Clock => "clock",
|
||||
WidgetId::Reach => "reach",
|
||||
WidgetId::Speed => "speed",
|
||||
}
|
||||
}
|
||||
pub fn index(self) -> usize {
|
||||
self as usize
|
||||
}
|
||||
}
|
||||
|
||||
/// Where a widget sits: in its corner, stacked with the others there (`at` is `None`), or at a
|
||||
/// place the player dragged it to, as fractions of the room the screen leaves around the box
|
||||
/// (0 at the left or top edge, 1 at the right or bottom), so it stays on screen at any size.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Place {
|
||||
pub at: Option<(f32, f32)>,
|
||||
/// Its size in percent.
|
||||
pub scale: u8,
|
||||
}
|
||||
impl Default for Place {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
at: None,
|
||||
scale: 100,
|
||||
}
|
||||
}
|
||||
}
|
||||
pub const SCALES: (u8, u8) = (50, 200);
|
||||
/// Every widget's place.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
pub struct Layout(pub [Place; 13]);
|
||||
impl Layout {
|
||||
pub fn get(&self, id: WidgetId) -> Place {
|
||||
self.0[id.index()]
|
||||
}
|
||||
pub fn get_mut(&mut self, id: WidgetId) -> &mut Place {
|
||||
&mut self.0[id.index()]
|
||||
}
|
||||
/// One `hudPlace` value: `armour,0.25,0.5,150`, or `armour,corner,,150`.
|
||||
pub fn parse(&mut self, value: &str) {
|
||||
let mut parts = value.split(',');
|
||||
let Some(id) = parts
|
||||
.next()
|
||||
.and_then(|n| WidgetId::ALL.into_iter().find(|w| w.name() == n))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let (x, y, scale) = (parts.next(), parts.next(), parts.next());
|
||||
let frac = |v: Option<&str>| {
|
||||
v.and_then(|v| v.parse::<f32>().ok())
|
||||
.filter(|v| v.is_finite())
|
||||
.map(|v| v.clamp(0.0, 1.0))
|
||||
};
|
||||
let place = self.get_mut(id);
|
||||
place.at = frac(x).zip(frac(y));
|
||||
if let Some(s) = scale.and_then(|s| s.parse::<u8>().ok()) {
|
||||
place.scale = s.clamp(SCALES.0, SCALES.1);
|
||||
}
|
||||
}
|
||||
/// The `hudPlace` lines of the widgets that left their corner or their size.
|
||||
pub fn serialize(&self, out: &mut String) {
|
||||
use std::fmt::Write;
|
||||
for id in WidgetId::ALL {
|
||||
let p = self.get(id);
|
||||
if p == Place::default() {
|
||||
continue;
|
||||
}
|
||||
let _ = match p.at {
|
||||
Some((x, y)) => writeln!(out, "hudPlace:{},{x:.4},{y:.4},{}", id.name(), p.scale),
|
||||
None => writeln!(out, "hudPlace:{},corner,,{}", id.name(), p.scale),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
/// A box `w` x `h` placed at fractions `(fx, fy)` of the room left on a `screen`.
|
||||
pub fn from_fractions((fx, fy): (f32, f32), (w, h): (f32, f32), screen: (f32, f32)) -> (f32, f32) {
|
||||
(
|
||||
(fx * (screen.0 - w).max(0.0)).round(),
|
||||
(fy * (screen.1 - h).max(0.0)).round(),
|
||||
)
|
||||
}
|
||||
/// The fractions that put a `w` x `h` box at `(x, y)` on a `screen`, kept on screen.
|
||||
pub fn to_fractions((x, y): (f32, f32), (w, h): (f32, f32), screen: (f32, f32)) -> (f32, f32) {
|
||||
let frac = |v: f32, room: f32| {
|
||||
if room > 0.0 {
|
||||
(v / room).clamp(0.0, 1.0)
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
};
|
||||
(frac(x, screen.0 - w), frac(y, screen.1 - h))
|
||||
}
|
||||
/// How near, in GUI pixels, an edge must come to another to snap to it.
|
||||
pub const SNAP: f32 = 4.0;
|
||||
/// Snaps a box's start `x` of length `w` along one axis of length `screen`: to the screen's
|
||||
/// edges and middle, and to the edges of the `others` boxes (`(start, length)`), where one
|
||||
/// is within [`SNAP`]. Returns the new start and the line it snapped to, if any.
|
||||
pub fn snap(x: f32, w: f32, screen: f32, others: &[(f32, f32)]) -> (f32, Option<f32>) {
|
||||
let mut best: Option<(f32, f32, f32)> = None;
|
||||
let mut consider = |start: f32, line: f32| {
|
||||
let d = (start - x).abs();
|
||||
if d <= SNAP && best.is_none_or(|(b, _, _)| d < b) {
|
||||
best = Some((d, start, line));
|
||||
}
|
||||
};
|
||||
// The screen's edges, with the corners' margin, and its middle.
|
||||
consider(2.0, 0.0);
|
||||
consider(screen - 2.0 - w, screen);
|
||||
consider((screen - w) / 2.0, screen / 2.0);
|
||||
for &(o, ow) in others {
|
||||
// Side by side, or lined up on the same edge.
|
||||
consider(o + ow, o + ow);
|
||||
consider(o - w, o);
|
||||
consider(o, o);
|
||||
consider(o + ow - w, o + ow);
|
||||
}
|
||||
match best {
|
||||
Some((_, start, line)) => (start.clamp(0.0, (screen - w).max(0.0)), Some(line)),
|
||||
None => (x.clamp(0.0, (screen - w).max(0.0)), None),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -338,4 +652,141 @@ mod tests {
|
||||
assert_eq!(h, 18.0 + 10.0 + 20.0 + 2.0 * PAD - 2.0);
|
||||
assert!(size(&[]) == (0.0, 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clicks_count_the_presses_of_the_last_second() {
|
||||
let mut c = Clicks::default();
|
||||
for t in [0.0, 0.1, 0.5, 0.9] {
|
||||
c.press(t);
|
||||
}
|
||||
assert_eq!(c.per_second(0.95), 4);
|
||||
// 1.0 s after the first press it has left the window.
|
||||
assert_eq!(c.per_second(1.0), 3);
|
||||
assert_eq!(c.per_second(1.6), 1);
|
||||
assert_eq!(c.per_second(5.0), 0);
|
||||
// The ring never keeps more than 64 presses.
|
||||
for i in 0..200 {
|
||||
c.press(10.0 + f64::from(i) * 0.001);
|
||||
}
|
||||
assert_eq!(c.per_second(10.2), 64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn speed_is_horizontal_blocks_per_second_between_samples() {
|
||||
let mut s = Speed::default();
|
||||
assert_eq!(s.sample(0.0, 0.0, 0.0), 0);
|
||||
// Sprinting: 5.612 blocks per second along x, with a jump (y is ignored).
|
||||
assert_eq!(s.sample(0.5, 0.0, 0.1), 0, "too soon for a new sample");
|
||||
assert_eq!(s.sample(1.403, 0.0, 0.25), 56);
|
||||
// 3 east and 4 south in a second: 5 blocks per second.
|
||||
assert_eq!(s.sample(1.403 + 3.0, 4.0, 1.25), 50);
|
||||
// A teleport reads as standing still.
|
||||
assert_eq!(s.sample(1000.0, 4.0, 1.5), 0);
|
||||
assert_eq!(tenths(56), "5.6");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reach_shows_the_last_hit_for_three_seconds() {
|
||||
let mut r = Reach::default();
|
||||
assert_eq!(r.hundredths(0.0), None);
|
||||
r.hit(2.954, 1.0);
|
||||
assert_eq!(r.hundredths(1.5), Some(295));
|
||||
assert_eq!(hundredths(295), "2.95");
|
||||
assert_eq!(hundredths(300), "3.00");
|
||||
assert_eq!(r.hundredths(4.1), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_clock_reads_hours_and_minutes() {
|
||||
assert_eq!(clock(14, 5), "14:05");
|
||||
assert_eq!(clock(0, 0), "00:00");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keystrokes_keys_tile_their_box_without_overlap() {
|
||||
let (w, h) = keys_size();
|
||||
assert_eq!((w, h), (70.0, 3.0 * 24.0 + 12.0));
|
||||
for (i, a) in Key::ALL.iter().enumerate() {
|
||||
let (x, y, kw, kh) = key_rect(*a);
|
||||
assert!(
|
||||
x >= 0.0 && y >= 0.0 && x + kw <= w && y + kh <= h,
|
||||
"{a:?} inside"
|
||||
);
|
||||
for b in &Key::ALL[i + 1..] {
|
||||
let (bx, by, bw, bh) = key_rect(*b);
|
||||
let apart = x + kw <= bx || bx + bw <= x || y + kh <= by || by + bh <= y;
|
||||
assert!(apart, "{a:?} and {b:?} overlap");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn places_round_trip_and_only_moved_widgets_are_written() {
|
||||
let mut l = Layout::default();
|
||||
let mut text = String::new();
|
||||
l.serialize(&mut text);
|
||||
assert_eq!(text, "", "a vanilla layout writes nothing");
|
||||
*l.get_mut(WidgetId::Keys) = Place {
|
||||
at: Some((0.25, 1.0)),
|
||||
scale: 150,
|
||||
};
|
||||
l.get_mut(WidgetId::Fps).scale = 75;
|
||||
l.serialize(&mut text);
|
||||
assert_eq!(
|
||||
text,
|
||||
"hudPlace:fps,corner,,75\nhudPlace:keys,0.2500,1.0000,150\n"
|
||||
);
|
||||
let mut back = Layout::default();
|
||||
for line in text.lines() {
|
||||
back.parse(line.split_once(':').unwrap().1);
|
||||
}
|
||||
assert_eq!(back, l);
|
||||
// Out of range values are clamped, unknown widgets and bad numbers ignored.
|
||||
back.parse("speed,-3,7,250");
|
||||
assert_eq!(back.get(WidgetId::Speed).at, Some((0.0, 1.0)));
|
||||
assert_eq!(back.get(WidgetId::Speed).scale, 200);
|
||||
back.parse("radar,0.5,0.5,100");
|
||||
back.parse("clock,NaN,0.5,100");
|
||||
assert_eq!(back.get(WidgetId::Clock).at, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fractions_keep_a_box_on_screen_at_any_size() {
|
||||
let screen = (480.0, 270.0);
|
||||
let at = to_fractions((100.0, 50.0), (80.0, 20.0), screen);
|
||||
assert_eq!(from_fractions(at, (80.0, 20.0), screen), (100.0, 50.0));
|
||||
// A box at the right edge stays at the right edge on a wider screen.
|
||||
let right = to_fractions((400.0, 0.0), (80.0, 20.0), screen);
|
||||
assert_eq!(right, (1.0, 0.0));
|
||||
assert_eq!(
|
||||
from_fractions(right, (80.0, 20.0), (960.0, 540.0)),
|
||||
(880.0, 0.0)
|
||||
);
|
||||
// Dragged past the edge: kept inside.
|
||||
assert_eq!(
|
||||
to_fractions((-30.0, 900.0), (80.0, 20.0), screen),
|
||||
(0.0, 1.0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boxes_snap_to_screen_edges_middle_and_each_other() {
|
||||
// Near the left margin.
|
||||
assert_eq!(snap(4.5, 50.0, 480.0, &[]), (2.0, Some(0.0)));
|
||||
// Centred: the middle of 480 for a 50-wide box starts at 215.
|
||||
assert_eq!(snap(213.0, 50.0, 480.0, &[]), (215.0, Some(240.0)));
|
||||
// Beside another box (100..160): its start goes to 160.
|
||||
assert_eq!(
|
||||
snap(163.0, 50.0, 480.0, &[(100.0, 60.0)]),
|
||||
(160.0, Some(160.0))
|
||||
);
|
||||
// Lined up with its left edge.
|
||||
assert_eq!(
|
||||
snap(98.0, 50.0, 480.0, &[(100.0, 60.0)]),
|
||||
(100.0, Some(100.0))
|
||||
);
|
||||
// Too far from anything: stays, but never off screen.
|
||||
assert_eq!(snap(300.0, 50.0, 480.0, &[(100.0, 60.0)]), (300.0, None));
|
||||
assert_eq!(snap(470.0, 50.0, 480.0, &[]), (430.0, None));
|
||||
}
|
||||
}
|
||||
@@ -814,6 +814,7 @@ pub enum Opt {
|
||||
PadToggleSneak,
|
||||
PadMoveDeadzone,
|
||||
PadLookDeadzone,
|
||||
PadTriggerThreshold,
|
||||
PadGlyphs,
|
||||
PadKeyboard,
|
||||
/// `options.modelPart.*`: one skin layer (index into `options_file::MODEL_PARTS`).
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
gamepad::{Button as PadButton, GlyphStyle},
|
||||
graphics_style,
|
||||
hud::{HudAtlas, HudBatch, rgba},
|
||||
lang,
|
||||
lang, pad_glyph,
|
||||
text::Text,
|
||||
ui,
|
||||
};
|
||||
@@ -411,30 +411,23 @@ impl SplitProfiles {
|
||||
None => tr("horizon.splitscreen.profiles.move", "Move where you play"),
|
||||
};
|
||||
let keys = [PadButton::LeftBumper, PadButton::RightBumper];
|
||||
let key_width = |b: PadButton| width(b.label(style)) + 4.0;
|
||||
let key_width = |b: PadButton| pad_glyph::width(atlas, b, style);
|
||||
let total = keys.iter().map(|&b| key_width(b) + 4.0).sum::<f32>() + 2.0 + width(&words);
|
||||
let (mut x, y) = (((batch.width - total) / 2.0).floor(), 30.0);
|
||||
for b in keys {
|
||||
let [r, g, bl] = b.color(style);
|
||||
let kw = key_width(b);
|
||||
// A light edge around the dark key: it stands out on the menu's dark background.
|
||||
batch.fill(
|
||||
atlas,
|
||||
x - 2.0,
|
||||
y - 3.0,
|
||||
kw + 2.0,
|
||||
13.0,
|
||||
rgba(170, 170, 170, 255),
|
||||
);
|
||||
batch.fill(atlas, x - 1.0, y - 2.0, kw, 11.0, rgba(40, 40, 40, 255));
|
||||
batch.text(
|
||||
atlas,
|
||||
b.label(style),
|
||||
x + 1.0,
|
||||
y,
|
||||
rgba(r, g, bl, 255),
|
||||
false,
|
||||
);
|
||||
// A light edge around a dark key: it stands out on the menu's dark background.
|
||||
if pad_glyph::art(atlas, b, style).is_none() {
|
||||
batch.fill(
|
||||
atlas,
|
||||
x - 2.0,
|
||||
y - 3.0,
|
||||
kw + 2.0,
|
||||
pad_glyph::HEIGHT + 2.0,
|
||||
rgba(170, 170, 170, 255),
|
||||
);
|
||||
}
|
||||
pad_glyph::draw(batch, atlas, b, style, x, y);
|
||||
x += kw + 4.0;
|
||||
}
|
||||
batch.text(atlas, &words, x + 2.0, y, rgba(240, 240, 240, 255), true);
|
||||
|
||||
@@ -836,7 +836,14 @@ impl<G: ChunkGenerator> Source for Jobs<G> {
|
||||
Job::Mesh(..) => 5,
|
||||
_ => 6,
|
||||
};
|
||||
(kind, crate::spans::thread_cpu().unwrap_or_default())
|
||||
// The other jobs (saves, light views, freed meshes) are many and short: counted, not
|
||||
// timed, so the clock reads don't cost more than they do.
|
||||
let cpu = if kind == 6 {
|
||||
std::time::Duration::ZERO
|
||||
} else {
|
||||
crate::spans::thread_cpu().unwrap_or_default()
|
||||
};
|
||||
(kind, cpu)
|
||||
});
|
||||
let _tag = horizon_threads::heap_tag(match &job.job {
|
||||
Job::Generate(..) => "stream.generate",
|
||||
@@ -865,8 +872,13 @@ impl<G: ChunkGenerator> Source for Jobs<G> {
|
||||
} else {
|
||||
kind
|
||||
};
|
||||
let cpu = crate::spans::thread_cpu().unwrap_or_default();
|
||||
job_stats::add(kind, cpu.saturating_sub(started).as_nanos() as u64);
|
||||
let nanos = if kind == 6 {
|
||||
0
|
||||
} else {
|
||||
let cpu = crate::spans::thread_cpu().unwrap_or_default();
|
||||
cpu.saturating_sub(started).as_nanos() as u64
|
||||
};
|
||||
job_stats::add(kind, nanos);
|
||||
}
|
||||
if let Some(done) = done {
|
||||
self.shared.done.send(done);
|
||||
|
||||
@@ -614,7 +614,12 @@ fn button_with_room(
|
||||
room: f32,
|
||||
narration: impl FnOnce([f32; 4]) -> crate::widgets::Narratable,
|
||||
) {
|
||||
crate::widgets::record(&label.plain(), [x, y, w, h], active);
|
||||
let plain = label.plain();
|
||||
crate::widgets::record(&plain, [x, y, w, h], active);
|
||||
// An icon button has no label to record, and A presses it all the same.
|
||||
if active && plain.trim().is_empty() {
|
||||
crate::widgets::target([x, y, w, h]);
|
||||
}
|
||||
crate::widgets::narratable(|| narration([x, y, w, h]).hovered(hover));
|
||||
crate::widgets::widget_cursor(hover, active);
|
||||
let sprite = if !active {
|
||||
@@ -1846,6 +1851,43 @@ mod tests {
|
||||
assert_eq!(scrolled_left(10.0, 40.0, 1_000), 10.0 - offset);
|
||||
}
|
||||
|
||||
/// A controller reaches an icon button only through its target, and can't press a disabled one.
|
||||
#[test]
|
||||
fn an_active_icon_button_is_a_focus_target_and_a_disabled_one_is_not() {
|
||||
let atlas = atlas();
|
||||
let mut batch = HudBatch::new([640, 480]);
|
||||
// Whatever this thread drew before is the last frame's, then gone.
|
||||
crate::widgets::end_frame(u64::MAX - 1);
|
||||
button(
|
||||
&mut batch,
|
||||
&atlas,
|
||||
10.0,
|
||||
20.0,
|
||||
20.0,
|
||||
20.0,
|
||||
&Text::empty(),
|
||||
true,
|
||||
false,
|
||||
);
|
||||
button(
|
||||
&mut batch,
|
||||
&atlas,
|
||||
40.0,
|
||||
20.0,
|
||||
20.0,
|
||||
20.0,
|
||||
&Text::empty(),
|
||||
false,
|
||||
false,
|
||||
);
|
||||
crate::widgets::end_frame(u64::MAX);
|
||||
let targets: Vec<[f32; 4]> = crate::widgets::last_targets()
|
||||
.iter()
|
||||
.map(|t| t.rect)
|
||||
.collect();
|
||||
assert_eq!(targets, [[10.0, 20.0, 20.0, 20.0]]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_glyph_leaves_a_label_that_fits_centred() {
|
||||
let atlas = atlas();
|
||||
|
||||
@@ -201,9 +201,14 @@ pub fn set_action_glyphs(on: bool) {
|
||||
ACTION_GLYPHS.set(on);
|
||||
}
|
||||
|
||||
/// How much of a declared button's left the glyph takes from its label.
|
||||
/// How much of a declared button's left the glyph takes from its label: a face button's, the only
|
||||
/// kind declared buttons show, and no family's is wider than its art.
|
||||
pub fn action_glyph_room() -> f32 {
|
||||
if ACTION_GLYPHS.get() { 14.0 } else { 0.0 }
|
||||
if ACTION_GLYPHS.get() {
|
||||
crate::pad_glyph::FACE_WIDTH + 3.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
/// An interactive rectangle (GUI pixels) a controller can focus and click.
|
||||
@@ -244,6 +249,41 @@ pub fn clip_targets(mark: usize, y0: f32, y1: f32) {
|
||||
});
|
||||
}
|
||||
|
||||
/// How many labelled widgets and targets the frame has so far, for [`clip_frame`].
|
||||
pub fn frame_mark() -> (usize, usize) {
|
||||
let labelled = WIDGETS.with_borrow(|(current, _, _)| current.len());
|
||||
(labelled, targets_mark())
|
||||
}
|
||||
|
||||
/// Drop the labelled widgets and targets since `mark` whose middle isn't in `y0..y1` (GUI pixels):
|
||||
/// what a scrolling body clipped away, where a press wouldn't land.
|
||||
pub fn clip_frame((labelled, targets): (usize, usize), y0: f32, y1: f32) {
|
||||
let shown = |y: f32, h: f32| (y0..y1).contains(&(y + h / 2.0));
|
||||
WIDGETS.with_borrow_mut(|(current, _, _)| {
|
||||
let mut at = 0;
|
||||
current.retain(|w| {
|
||||
at += 1;
|
||||
at <= labelled || shown(w.y, w.h)
|
||||
});
|
||||
});
|
||||
TARGETS.with_borrow_mut(|(current, _, _)| {
|
||||
let mut at = 0;
|
||||
current.retain(|t| {
|
||||
at += 1;
|
||||
at <= targets || shown(t.rect[1], t.rect[3])
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// A prompt comes up over what the frame drew so far: what it covers can't be reached, by the
|
||||
/// pointer or a controller, so its widgets, targets, declared buttons and lists go.
|
||||
pub fn cover_frame() {
|
||||
WIDGETS.with_borrow_mut(|(current, _, _)| current.clear());
|
||||
TARGETS.with_borrow_mut(|(current, _, _)| current.clear());
|
||||
ACTIONS.with_borrow_mut(|(current, _)| current.clear());
|
||||
SCROLL_AREAS.with_borrow_mut(|(current, _)| current.clear());
|
||||
}
|
||||
|
||||
/// This frame drew a list with something to scroll, in `rect`, scrolled `amount`
|
||||
/// of `max`: a focus step goes through its hidden rows before it leaves
|
||||
/// (`gamepad::list_step`).
|
||||
@@ -1349,4 +1389,42 @@ mod tests {
|
||||
"the clipped row is gone, earlier ones stay"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_scrolled_body_loses_what_it_clips_and_nothing_before_it() {
|
||||
let _ = take();
|
||||
target([0.0, 0.0, 5.0, 5.0]);
|
||||
record("warning", [0.0, 0.0, 5.0, 5.0], true);
|
||||
let mark = frame_mark();
|
||||
record("shown", [0.0, 10.0, 5.0, 10.0], true);
|
||||
target([0.0, 10.0, 5.0, 10.0]);
|
||||
// Half in view is still pressed at its middle, which a mouse could reach.
|
||||
record("half", [0.0, 25.0, 5.0, 10.0], true);
|
||||
target([0.0, 25.0, 5.0, 10.0]);
|
||||
record("gone", [0.0, 40.0, 5.0, 10.0], true);
|
||||
target([0.0, 40.0, 5.0, 10.0]);
|
||||
clip_frame(mark, 8.0, 32.0);
|
||||
end_frame(101);
|
||||
let names: Vec<String> = last_labelled().into_iter().map(|w| w.label).collect();
|
||||
assert_eq!(names, ["warning", "shown", "half"]);
|
||||
assert_eq!(last_targets().len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_prompt_covers_what_was_drawn_before_it() {
|
||||
let _ = take();
|
||||
record("under", [0.0, 0.0, 5.0, 5.0], true);
|
||||
target([0.0, 10.0, 5.0, 5.0]);
|
||||
declare_action(Action::Done, [0.0, 0.0, 5.0, 5.0], true);
|
||||
scroll_area([0.0, 0.0, 5.0, 5.0], 0.0, 5.0);
|
||||
cover_frame();
|
||||
record("over", [0.0, 20.0, 5.0, 5.0], true);
|
||||
set_action_screen(7);
|
||||
end_frame(102);
|
||||
let names: Vec<String> = last_labelled().into_iter().map(|w| w.label).collect();
|
||||
assert_eq!(names, ["over"]);
|
||||
assert!(last_targets().is_empty());
|
||||
assert!(last_actions(7).is_empty());
|
||||
assert!(last_scroll_areas().is_empty());
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,11 @@ struct Taa {
|
||||
jitter: vec4<f32>,
|
||||
};
|
||||
@group(0) @binding(2) var history: texture_2d<f32>;
|
||||
@group(0) @binding(3) var depth: texture_depth_2d;
|
||||
// The world's depth as an unfilterable float texture: naga's GLSL backend
|
||||
// cannot emit `textureLoad` from a depth texture, and a depth view bound as
|
||||
// `TextureSampleType::Float { filterable: false }` gives the same texel on
|
||||
// every backend.
|
||||
@group(0) @binding(3) var depth: texture_2d<f32>;
|
||||
@group(0) @binding(4) var<uniform> taa: Taa;
|
||||
|
||||
// Catmull-Rom over the history in 5 bilinear taps: sharper than one under motion.
|
||||
@@ -113,7 +117,7 @@ fn sample_history(uv: vec2<f32>) -> vec3<f32> {
|
||||
let c = textureLoad(world, q, 0).rgb;
|
||||
lo = min(lo, c);
|
||||
hi = max(hi, c);
|
||||
near = max(near, textureLoad(depth, q, 0));
|
||||
near = max(near, textureLoad(depth, q, 0).r);
|
||||
}
|
||||
}
|
||||
let ndc = vec2(in.uv.x * 2.0 - 1.0, 1.0 - in.uv.y * 2.0);
|
||||
|
||||
@@ -54,6 +54,8 @@ sha2 = "0.10.9"
|
||||
sha1 = "0.10"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = { version = "1.0.151", features = ["raw_value"] }
|
||||
# server.toml's [combat], which the dedicated server reads itself.
|
||||
toml = "1.1"
|
||||
# hlsl-out: the gfx.rs tests translate every shader as wgpu's DirectX 12 backend does. A normal feature,
|
||||
# not a dev-dependency's, so builds, tests and examples share one build of naga, wgpu and this crate.
|
||||
naga = { version = "30.0.1", features = ["wgsl-in", "hlsl-out"] }
|
||||
|
||||
@@ -326,7 +326,6 @@
|
||||
"horizon.osk.close": "Maak toe",
|
||||
"horizon.extras.chat_animation.tooltip": "'n Nuwe kletslyn gly van links af terwyl dit vervaag, in plaas daarvan om heel te lyk.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Die gekleurde balk aan die linkerkant van 'n kletslyn.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Die rolwiel oor 'n knoppie in die instellings verander die waarde daarvan, soos in vanielje. Af, die wiel blaai net deur die lys.",
|
||||
"horizon.extras.pack_browser.tooltip": "Select Resource Packs kry 'n Voeg by-knoppie wat Modrinth deursoek en 'n pakket in jou pakke-lêergids installeer. Af, die speletjie kontak nooit Modrinth nie.",
|
||||
"horizon.extras.splitscreen.tooltip": "In 'n wêreld kan 'n ander beheerder as 'n nuwe speler aansluit: druk A wanneer dit vra, kies dan 'n profiel en waar om te speel.",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "'n monitor",
|
||||
@@ -472,8 +471,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Versteek: elke Horizon-speler sien jou sonder jou skoonheidsmiddels, jy ingesluit. Dieselfde skakelaar as die lanseerder se klerekas.",
|
||||
"horizon.extras.cosmetics_others": "Ander spelers se skoonheidsmiddels",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Wat ander Horizon-spelers dra: alles, net hul naamplaatjiestyle, of niks. Versteek, die speletjie vra nooit wat hulle dra nie.",
|
||||
"horizon.extras.cosmetics_auras": "Ander se aura's en roetes",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auras en roetes wat ander spelers dra. Af by verstek.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Versteek ander spelers se spore en auras",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Ander spelers se spore en auras bly versteek; hul ander kosmetiese items wys steeds. By verstek af.",
|
||||
"horizon.extras.cosmetics.shown": "Getoon",
|
||||
"horizon.extras.cosmetics.hidden": "Versteek",
|
||||
"horizon.extras.cosmetics.name_tags": "Slegs naamplaatjies"
|
||||
|
||||
@@ -341,7 +341,6 @@
|
||||
"horizon.osk.close": "إغلاق",
|
||||
"horizon.extras.chat_animation.tooltip": "ينزلق خط الدردشة الجديد من اليسار بينما يتلاشى للداخل، بدلاً من أن يظهر كاملاً.",
|
||||
"horizon.extras.chat_indicator.tooltip": "الشريط الملون الموجود على يسار خط الدردشة.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "عجلة التمرير فوق زر في الإعدادات تغير قيمته، كما هو الحال في الفانيليا. مع إيقاف التشغيل، تقوم العجلة بتمرير القائمة فقط.",
|
||||
"horizon.extras.pack_browser.tooltip": "تحديد حزم الموارد يحصل على زر \"إضافة\" الذي يبحث في Modrinth ويقوم بتثبيت حزمة في مجلد الحزم الخاص بك. مع إيقاف التشغيل، لن تتصل اللعبة أبدًا بـ Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "في عالم ما، يمكن لوحدة تحكم أخرى الانضمام كلاعب جديد: اضغط على A عندما يُطلب منك ذلك، ثم اختر ملفًا شخصيًا ومكان اللعب.",
|
||||
"horizon.splitscreen.setup.monitor": "مراقب %s",
|
||||
@@ -491,8 +490,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "مخفي: يراك كل لاعب في Horizon بدون مستحضرات التجميل الخاصة بك، بما في ذلك أنت. نفس مفتاح خزانة المشغل.",
|
||||
"horizon.extras.cosmetics_others": "مستحضرات التجميل للاعبين الآخرين",
|
||||
"horizon.extras.cosmetics_others.tooltip": "ما يرتديه لاعبو Horizon الآخرون: كل شيء، فقط أنماط بطاقة الاسم الخاصة بهم، أو لا شيء. مخفيًا، اللعبة لا تسأل أبدًا عما يرتدونه.",
|
||||
"horizon.extras.cosmetics_auras": "هالات ومسارات الآخرين",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "الهالات والمسارات التي يرتديها اللاعبون الآخرون. إيقاف بشكل افتراضي.",
|
||||
"horizon.extras.cosmetics_hide_fx": "إخفاء آثار وهالات اللاعبين الآخرين",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "تبقى آثار وهالات اللاعبين الآخرين مخفية؛ وتظل مستحضراتهم الأخرى ظاهرة. معطّل افتراضيًا.",
|
||||
"horizon.extras.cosmetics.shown": "معروض",
|
||||
"horizon.extras.cosmetics.hidden": "مخفي",
|
||||
"horizon.extras.cosmetics.name_tags": "علامات الاسم فقط"
|
||||
|
||||
@@ -317,7 +317,6 @@
|
||||
"horizon.osk.close": "Bağlayın",
|
||||
"horizon.extras.chat_animation.tooltip": "Yeni söhbət xətti bütöv görünmək əvəzinə soldan içəri sürüşür.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Söhbət xəttinin solunda olan rəngli bar.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Parametrlərdəki düymənin üzərindəki sürüşdürmə çarxı vanildə olduğu kimi dəyərini dəyişir. Söndürülmüş, təkər yalnız siyahını sürüşdürür.",
|
||||
"horizon.extras.pack_browser.tooltip": "Resurs Paketlərini seçin Modrinth-də axtarış aparan və paketlər qovluğuna paket quraşdıran Əlavə et düyməsini alır. Off, oyun Modrinthlə heç vaxt əlaqə saxlamır.",
|
||||
"horizon.extras.splitscreen.tooltip": "Dünyada başqa bir nəzarətçi yeni oyunçu kimi qoşula bilər: soruşduqda A düyməsini basın, sonra profil və harada oynayacağınızı seçin.",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "monitor",
|
||||
@@ -463,8 +462,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Gizli: hər Horizon oyunçu sizi kosmetikanız olmadan görür. Başlatma şkafı ilə eyni açar.",
|
||||
"horizon.extras.cosmetics_others": "Digər oyunçuların kosmetikası",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Digər Horizon oyunçular nə geyinirlər: hər şey, yalnız ad etiketi üslubları və ya heç nə. Gizli, oyun heç vaxt nə geyindiklərini soruşmur.",
|
||||
"horizon.extras.cosmetics_auras": "Başqalarının auraları və izləri",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Digər oyunçuların geydiyi auralar və izlər. Defolt olaraq söndürülür.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Digər oyunçuların izlərini və auralarını gizlət",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Digər oyunçuların izləri və auraları gizli qalır; digər kosmetikaları görünməyə davam edir. Defolt olaraq söndürülüb.",
|
||||
"horizon.extras.cosmetics.shown": "Göstərildi",
|
||||
"horizon.extras.cosmetics.hidden": "Gizli",
|
||||
"horizon.extras.cosmetics.name_tags": "Yalnız ad etiketləri"
|
||||
|
||||
@@ -333,7 +333,6 @@
|
||||
"horizon.osk.close": "Ябыҡ",
|
||||
"horizon.extras.chat_animation.tooltip": "Яңы чат һыҙығы һулдан шыуып инә, ә ул һүнә, урынына бөтөн күренә.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Чат һыҙығының һул яғында төҫлө һыҙыҡ.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Көйләүҙәрҙәге төймә өҫтөндәге прокрутка тәгәрмәсе үҙенең ҡиммәтен үҙгәртә, ванильдәге кеүек. Һүндерелгән, тәгәрмәс исемлекте генә йөрөтә.",
|
||||
"horizon.extras.pack_browser.tooltip": "Һайлау ресурс пакеттары ала өҫтәү төймәһе, эҙләү Modrinth һәм һеҙҙең пакеттар папкаһына пакет ҡуя. Һүндерелгән, уйын бер ҡасан да бәйләнешкә инә Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "Донъяла, икенсе контроллер яңы уйынсы булараҡ ҡушыла ала: баҫығыҙ А, ҡасан ул һорай, һуңынан профиль һайлап һәм ҡайҙа уйнау.",
|
||||
"horizon.splitscreen.setup.monitor": "Монитор %s",
|
||||
@@ -489,8 +488,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Йәшерен: һәр Horizon уйынсы һеҙҙең косметикаһыҙ күрә, һеҙ ҙә индерелгән. Шул уҡ переключатель стартер’s Гардероб.",
|
||||
"horizon.extras.cosmetics_others": "Башҡа уйынсыларҙың косметикаһы",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Башҡа ниндәй Horizon уйынсылар кейә: барыһы ла, тик уларҙы исем биркаһы стилдәре, йәки бер нәмә лә. Йәшерен, уйын бер ҡасан да һорамай, улар нимә кейә.",
|
||||
"horizon.extras.cosmetics_auras": "Башҡаларҙың ауралары һәм эҙҙәре",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Аура һәм эҙҙәр башҡа уйынсылар кейә. Һүндерелгән по умолчанию.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Башҡа уйынсыларҙың эҙҙәрен һәм ауралары йәшереү",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Башҡа уйынсыларҙың эҙҙәре һәм ауралары йәшерен ҡала; уларҙың башҡа косметикаһы күренә. Ғәҙәттә һүндерелгән.",
|
||||
"horizon.extras.cosmetics.shown": "Күрһәтелгән",
|
||||
"horizon.extras.cosmetics.hidden": "Йәшерен",
|
||||
"horizon.extras.cosmetics.name_tags": "Исем билдәләре генә"
|
||||
|
||||
@@ -326,7 +326,6 @@
|
||||
"horizon.osk.close": "Блізка",
|
||||
"horizon.extras.chat_animation.tooltip": "Новы радок чата слізгае злева, пакуль ён знікае, замест таго, каб выглядаць цалкам.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Каляровая паласа злева ад радка чата.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Кола пракруткі над кнопкай у наладах змяняе яе значэнне, як у vanilla. Выключана, кола толькі пракручвае спіс.",
|
||||
"horizon.extras.pack_browser.tooltip": "Select Resource Packs атрымлівае кнопку «Дадаць», якая выконвае пошук у Modrinth і ўсталёўвае пакет у вашу тэчку пакетаў. Выключана, гульня ніколі не звязваецца з Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "У свеце іншы кантролер можа далучыцца як новы гулец: націсніце A, калі ён запытае, затым выберыце профіль і дзе гуляць.",
|
||||
"horizon.splitscreen.setup.monitor": "Манітор %s",
|
||||
@@ -475,8 +474,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Схаваны: кожны гулец Horizon бачыць вас без вашай касметыкі, у тым ліку і вас. The same switch as the launcher's Wardrobe.",
|
||||
"horizon.extras.cosmetics_others": "Касметыка іншых гульцоў",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Што носяць іншыя гульцы Horizon: усё, толькі стылі тэгаў з імёнамі або нічога. Hidden, the game never asks what they wear.",
|
||||
"horizon.extras.cosmetics_auras": "Чужыя аўры і шлейфы",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auras and trails other players wear. Выключана па змаўчанні.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Схаваць сляды і аўры іншых гульцоў",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Сляды і аўры іншых гульцоў схаваныя; іх астатняя касметыка бачная. Па змаўчанні выкл.",
|
||||
"horizon.extras.cosmetics.shown": "Паказана",
|
||||
"horizon.extras.cosmetics.hidden": "Схаваны",
|
||||
"horizon.extras.cosmetics.name_tags": "Толькі біркі з імёнамі"
|
||||
|
||||
@@ -324,7 +324,6 @@
|
||||
"horizon.osk.close": "затвори",
|
||||
"horizon.extras.chat_animation.tooltip": "Нова линия за чат се плъзга отляво, докато избледнява, вместо да се показва цяла.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Цветната лента отляво на линия за чат.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Колелцето за превъртане над бутон в настройките променя стойността му, както при vanilla. Изключено, колелото само превърта списъка.",
|
||||
"horizon.extras.pack_browser.tooltip": "Select Resource Packs получава бутон за добавяне, който търси в Modrinth и инсталира пакет във вашата папка с пакети. Off, играта никога не се свързва с Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "В един свят друг контролер може да се присъедини като нов играч: натиснете A, когато поиска, след това изберете профил и къде да играете.",
|
||||
"horizon.splitscreen.setup.monitor": "Монитор %s",
|
||||
@@ -470,8 +469,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Скрит: всеки играч на Horizon ви вижда без вашата козметика, включително и вие. Същият превключвател като гардероба на стартера.",
|
||||
"horizon.extras.cosmetics_others": "Козметика за други играчи",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Какво носят другите играчи на Horizon: всичко, само техните стилове на имената или нищо. Скрито, играта никога не пита какво носят.",
|
||||
"horizon.extras.cosmetics_auras": "Аури и следи на други",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Аури и пътеки, които другите играчи носят. Изключено по подразбиране.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Скриване на следите и аурите на другите играчи",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Следите и аурите на другите играчи остават скрити; другите им козметики се виждат. Изключено по подразбиране.",
|
||||
"horizon.extras.cosmetics.shown": "Показано",
|
||||
"horizon.extras.cosmetics.hidden": "Скрити",
|
||||
"horizon.extras.cosmetics.name_tags": "Само етикети с имена",
|
||||
|
||||
@@ -325,7 +325,6 @@
|
||||
"horizon.osk.close": "Serriñ",
|
||||
"horizon.extras.chat_animation.tooltip": "Ur linenn flapañ nevez a zeu eus an tu kleiz e-keit ma vez o vont da get, e-lec'h dont war wel a-bezh.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Ar varrenn livet a-gleiz d'ul linenn flapañ.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Ar rod riklañ war ur bouton en arventennoù a gemm e dalvoudegezh, evel e vanilha. Diweredekaet, ne ra ar rod nemet riklañ ar roll.",
|
||||
"horizon.extras.pack_browser.tooltip": "Dibab pakadoù arc'hwelioù a resev ur bouton Ouzhpennañ a glask Modrinth hag a staliañ ur pakad en ho teuliad pakadoù. Diweredekaet, ne vez ket darempredet ar c'hoari gant Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "En ur bed e c'hall ur c'hontroller all mont e-barzh evel c'hoarier nevez : pouezit war A pa c'houlenn, ha dibabit ur profil ha pelec'h c'hoari.",
|
||||
"horizon.splitscreen.setup.monitor": "Heuliañ %s",
|
||||
@@ -474,8 +473,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Kuzhet : pep c'hoarier Horizon a wel ac'hanoc'h hep ho kosmetikoù, c'hwi en o zouez. Ar memes treuzfurmadur hag armel al lañser.",
|
||||
"horizon.extras.cosmetics_others": "Kosmetikoù ar c'hoarierien all",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Ar pezh a vez gwisket gant ar c'hoarierien Horizon all : pep tra, o doareoù tikedenn anv hepken, pe netra. Kuzhet, ar c'hoari ne c'houlenn ket biskoazh petra a wiskont.",
|
||||
"horizon.extras.cosmetics_auras": "Auraoù ha roudoù tud all",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auraoù ha roudoù a vez gwisket gant c'hoarierien all. Diweredekaet dre ziouer.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Kuzhat roudoù hag aouraoù ar c'hoarierien all",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Kuzhet e chom roudoù hag aouraoù ar c'hoarierien all; diskouezet e vez o c'hinkladurioù all. Diweredekaet dre ziouer.",
|
||||
"horizon.extras.cosmetics.shown": "Diskouezet",
|
||||
"horizon.extras.cosmetics.hidden": "Kuzhet",
|
||||
"horizon.extras.cosmetics.name_tags": "Tikedennoù anvioù hepken"
|
||||
|
||||
@@ -281,7 +281,6 @@
|
||||
"horizon.osk.close": "Zatvori",
|
||||
"horizon.extras.chat_animation.tooltip": "Nova linija ćaskanja klizi s lijeve strane dok se gasi, umjesto da izgleda kao cijela.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Traka u boji lijevo od linije za ćaskanje.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Točkić za pomeranje preko dugmeta u podešavanjima menja svoju vrednost, kao u vaniliji. Isključeno, kotačić samo pomiče listu.",
|
||||
"horizon.extras.pack_browser.tooltip": "Select Resource Packs dobija dugme Dodaj koje pretražuje Modrinth i instalira paket u fasciklu sa paketima. Isključeno, igra nikada ne kontaktira Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "U svijetu se drugi kontrolor može pridružiti kao novi igrač: pritisnite A kada to zatraži, a zatim odaberite profil i gdje ćete igrati.",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "monitor",
|
||||
@@ -415,8 +414,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Skriveno: svaki Horizon igrač vas vidi bez vaše kozmetike, uključujući i vas. Isti prekidač kao Ormar lansera.",
|
||||
"horizon.extras.cosmetics_others": "Kozmetika drugih igrača",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Šta drugi Horizon igrači nose: sve, samo stilove oznaka sa imenom ili ništa. Skriveno, igra nikad ne pita šta nose.",
|
||||
"horizon.extras.cosmetics_auras": "Aure i tragovi drugih",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Aure i staze koje drugi igrači nose. Podrazumevano isključeno.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Sakrij tragove i aure drugih igrača",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Tragovi i aure drugih igrača ostaju skriveni; njihova ostala kozmetika se i dalje vidi. Isključeno po zadanom.",
|
||||
"horizon.extras.cosmetics.shown": "Prikazano",
|
||||
"horizon.extras.cosmetics.hidden": "Skriveno",
|
||||
"horizon.extras.cosmetics.name_tags": "Samo oznake sa imenom",
|
||||
|
||||
@@ -319,7 +319,6 @@
|
||||
"horizon.osk.close": "Tancar",
|
||||
"horizon.extras.chat_animation.tooltip": "Una nova línia de xat es desplaça des de l'esquerra mentre s'esvaeix, en lloc de semblar sencera.",
|
||||
"horizon.extras.chat_indicator.tooltip": "La barra de colors a l'esquerra d'una línia de xat.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "La roda de desplaçament sobre un botó a la configuració canvia el seu valor, com a la vainilla. Desactivada, la roda només desplaça la llista.",
|
||||
"horizon.extras.pack_browser.tooltip": "Select Resource Packs obté un botó Afegeix que cerca Modrinth i instal·la un paquet a la carpeta de paquets. Desactivat, el joc mai contacta amb Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "En un món, un altre controlador pot unir-se com a jugador nou: premeu A quan ho demani, després trieu un perfil i on jugar.",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "un monitor",
|
||||
@@ -463,8 +462,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Ocult: tots els jugadors d'Horizon et veuen sense els teus cosmètics, tu inclòs. El mateix interruptor que l'armari del llançador.",
|
||||
"horizon.extras.cosmetics_others": "Cosmètica d'altres jugadors",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Què porten altres jugadors d'Horizon: tot, només els seus estils d'etiquetes de nom o res. Amagat, el joc mai pregunta què porten.",
|
||||
"horizon.extras.cosmetics_auras": "Les aures i els camins dels altres",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Aures i rastres que porten altres jugadors. Desactivat per defecte.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Amaga les esteles i les aures dels altres jugadors",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Les esteles i les aures dels altres jugadors queden amagades; els seus altres cosmètics es continuen veient. Desactivat per defecte.",
|
||||
"horizon.extras.cosmetics.shown": "Es mostra",
|
||||
"horizon.extras.cosmetics.hidden": "Ocult",
|
||||
"horizon.extras.cosmetics.name_tags": "Només etiquetes de nom",
|
||||
|
||||
@@ -311,7 +311,6 @@
|
||||
"horizon.osk.close": "Zavřít",
|
||||
"horizon.extras.chat_animation.tooltip": "Nová čára chatu se vsune zleva, zatímco se mizí, místo aby vypadala celá.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Barevný pruh nalevo od chatovací linky.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Rolovací kolečko nad tlačítkem v nastavení mění jeho hodnotu, jako u vanilky. Vypnuto, kolečko pouze posouvá seznam.",
|
||||
"horizon.extras.pack_browser.tooltip": "Select Resource Packs dostane tlačítko Přidat, které prohledá Modrinth a nainstaluje balíček do vaší složky balíčků. Vypnuto, hra nikdy nekontaktuje Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "Ve světě se může jako nový hráč připojit další ovladač: stiskněte A, když se zeptá, pak vyberte profil a kde hrát.",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "monitor",
|
||||
@@ -453,8 +452,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Skryté: každý hráč Horizon vás vidí bez vaší kosmetiky, včetně vás. Stejný vypínač jako skříň odpalovacího zařízení.",
|
||||
"horizon.extras.cosmetics_others": "Kosmetika ostatních hráčů",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Co nosí ostatní hráči Horizon: všechno, jen styly jmenovek nebo nic. Skryto, hra se nikdy neptá, co nosí.",
|
||||
"horizon.extras.cosmetics_auras": "Aury a stezky ostatních",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Aury a stezky, které nosí ostatní hráči. Ve výchozím nastavení vypnuto.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Skrýt stopy a aury ostatních hráčů",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Stopy a aury ostatních hráčů zůstanou skryté; jejich ostatní kosmetika je dál vidět. Ve výchozím stavu vypnuto.",
|
||||
"horizon.extras.cosmetics.shown": "Zobrazeno",
|
||||
"horizon.extras.cosmetics.hidden": "Skrytá",
|
||||
"horizon.extras.cosmetics.name_tags": "Pouze jmenovky"
|
||||
|
||||
@@ -330,7 +330,6 @@
|
||||
"horizon.osk.close": "Хуп",
|
||||
"horizon.extras.chat_animation.tooltip": "Ҫӗнӗ чат йӗрки сулахай енчен шуса кӗрет, вӑл вара пӗтӗмӗшпех курӑнмасть.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Чат йӗркин сулахай енчи тӗслӗ йӗр.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Настройкӑсенчи кнопка урлӑ ҫаврӑнакан ҫаврашка унӑн хакне улӑштарать, ваниль ҫинчи пекех. Сӳнтерсен, урапа списока ҫеҫ куҫарать.",
|
||||
"horizon.extras.pack_browser.tooltip": "Ресурс пакетӗсене суйласа илсен, хушма кнопка пулать, вӑл Modrinth шырать те сирӗн пакетсен папкине пакет вырнаҫтарать. Ҫук, вӑйӑ нихҫан та Modrinth ҫыхӑнмасть.",
|
||||
"horizon.extras.splitscreen.tooltip": "Тӗнчере тепӗр контроллер ҫӗнӗ вылякан пулса хутшӑнма пултарать: вӑл ыйтсан А пусмалла, унтан профиль суйласа илмелле тата ӑҫта вылямаллине суйласа илмелле.",
|
||||
"horizon.splitscreen.setup.monitor": "Монитор %s",
|
||||
@@ -485,8 +484,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Пытарнӑ: кашни Horizon вылякан сире косметикӑсӑр курать, эсир те ҫав шутра. Пуҫкаҫӑн гардеробӗнчи пекех переключатель.",
|
||||
"horizon.extras.cosmetics_others": "Ытти вылякансен косметики",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Ытти Horizon вылякансем мӗн тӑхӑнаҫҫӗ: пурте, вӗсен ячӗсен стилӗсем ҫеҫ, е нимӗн те мар. Вӑрттӑн, вӑйӑ нихҫан та вӗсем мӗн тӑхӑннине ыйтмасть.",
|
||||
"horizon.extras.cosmetics_auras": "Ыттисен аурисем тата йӗрӗсем",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Ытти вылякансем тӑхӑнакан аурӑсемпе йӗрсем. Выключен по умолчанию.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Ытти вӑййисен йӗрӗсене тата аурисене пытар",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Ытти вӑййисен йӗрӗсем тата аурисем пытарӑнса юлаҫҫӗ; вӗсен ытти косметики курӑнать. Йӑлана кура сӳнтернӗ.",
|
||||
"horizon.extras.cosmetics.shown": "Кӑтартнӑ",
|
||||
"horizon.extras.cosmetics.hidden": "Пытарнӑ",
|
||||
"horizon.extras.cosmetics.name_tags": "Ят паллисем кӑна"
|
||||
|
||||
@@ -317,7 +317,6 @@
|
||||
"horizon.osk.close": "Cau",
|
||||
"horizon.extras.chat_animation.tooltip": "Mae llinell sgwrsio newydd yn llithro i mewn o'r chwith tra mae'n pylu i mewn, yn lle ymddangos yn gyfan.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Y bar lliw ar ochr chwith llinell sgwrsio.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Mae'r olwyn sgrolio dros fotwm yn y gosodiadau yn newid ei werth, fel yn y fanila. I ffwrdd, mae'r olwyn yn sgrolio'r rhestr yn unig.",
|
||||
"horizon.extras.pack_browser.tooltip": "Mae Dewis Pecynnau Adnoddau yn cael botwm Ychwanegu sy'n chwilio Modrinth ac yn gosod pecyn yn eich ffolder pecynnau. I ffwrdd, nid yw'r gêm byth yn cysylltu â Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "Mewn byd, gall rheolydd arall ymuno fel chwaraewr newydd: pwyswch A pan fydd yn gofyn, yna dewiswch broffil a ble i chwarae.",
|
||||
"horizon.splitscreen.setup.monitor": "Monitro %s",
|
||||
@@ -460,8 +459,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Cudd: mae pob chwaraewr Horizon yn eich gweld heb eich colur, fe wnaethoch chi gynnwys. Yr un switsh â Wardrob y lansiwr.",
|
||||
"horizon.extras.cosmetics_others": "Colur chwaraewyr eraill",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Beth mae chwaraewyr Horizon eraill yn ei wisgo: popeth, dim ond eu steil tag enw, neu ddim byd. Yn gudd, nid yw'r gêm byth yn gofyn beth maen nhw'n ei wisgo.",
|
||||
"horizon.extras.cosmetics_auras": "Auras a llwybrau eraill",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auras a llwybrau mae chwaraewyr eraill yn eu gwisgo. Wedi'i ddiffodd yn ddiofyn.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Cuddio llwybrau ac awrâu chwaraewyr eraill",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Mae llwybrau ac awrâu chwaraewyr eraill yn aros yn gudd; mae eu colur arall yn dal i ddangos. I ffwrdd yn ddiofyn.",
|
||||
"horizon.extras.cosmetics.shown": "Dangosir",
|
||||
"horizon.extras.cosmetics.hidden": "Cudd",
|
||||
"horizon.extras.cosmetics.name_tags": "Tagiau enw yn unig"
|
||||
|
||||
@@ -301,7 +301,6 @@
|
||||
"horizon.osk.close": "Luk",
|
||||
"horizon.extras.chat_animation.tooltip": "En ny chatlinje glider ind fra venstre, mens den toner ind, i stedet for at virke hel.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Den farvede bjælke til venstre for en chatlinje.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Scrollhjulet over en knap i indstillingerne ændrer dens værdi, som i vanilje. Fra, hjulet ruller kun listen.",
|
||||
"horizon.extras.pack_browser.tooltip": "Select Resource Packs får en Tilføj knap, der søger mod Modrinth og installerer en pakke i din pakkemappe. Off, spillet kontakter aldrig Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "I en verden kan en anden controller deltage som en ny spiller: Tryk på A, når den spørger, vælg derefter en profil og hvor du vil spille.",
|
||||
"horizon.splitscreen.setup.monitor": "Overvåg %s",
|
||||
@@ -440,8 +439,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Skjult: alle Horizon-spillere ser dig uden din kosmetik, inklusive dig. Den samme kontakt som løfterakettens garderobe.",
|
||||
"horizon.extras.cosmetics_others": "Andre spilleres kosmetik",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Hvad andre Horizon-spillere bærer: alt, kun deres navnemærke eller intet. Skjult spørger spillet aldrig, hvad de har på.",
|
||||
"horizon.extras.cosmetics_auras": "Andres auraer og spor",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auraer og spor, som andre spillere bærer. Fra som standard.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Skjul andre spilleres spor og auraer",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Andre spilleres spor og auraer forbliver skjulte; deres andre kosmetikker vises stadig. Slået fra som standard.",
|
||||
"horizon.extras.cosmetics.shown": "Vist",
|
||||
"horizon.extras.cosmetics.hidden": "Skjult",
|
||||
"horizon.extras.cosmetics.name_tags": "Kun navneskilte",
|
||||
|
||||
@@ -357,10 +357,14 @@
|
||||
"horizon.extras.header.cosmetics": "Kosmetik",
|
||||
"horizon.extras.cosmetics_mine": "Meine Kosmetik",
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Ausgeblendet: Alle Horizon-Spieler sehen dich ohne deine Kosmetik, du selbst auch. Derselbe Schalter wie in der Garderobe des Launchers.",
|
||||
"key.emote": "Emote-Rad",
|
||||
"horizon.emote_wheel.title": "Emotes",
|
||||
"horizon.emote_wheel.empty": "Noch keine Emotes",
|
||||
"horizon.emote_wheel.help": "Loslassen oder klicken zum Abspielen · 1–8 · Esc schließt",
|
||||
"horizon.extras.cosmetics_others": "Kosmetik anderer Spieler",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Was andere Horizon-Spieler tragen: alles, nur ihre Namensschild-Stile oder nichts. Ausgeblendet fragt das Spiel nie, was sie tragen.",
|
||||
"horizon.extras.cosmetics_auras": "Auren und Spuren anderer",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auren und Spuren, die andere Spieler tragen. Standardmäßig aus.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Spuren und Auren anderer Spieler ausblenden",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Spuren und Auren anderer Spieler bleiben ausgeblendet; ihre anderen Kosmetika sind weiter sichtbar. Standardmäßig aus.",
|
||||
"horizon.extras.cosmetics.shown": "Angezeigt",
|
||||
"horizon.extras.cosmetics.hidden": "Ausgeblendet",
|
||||
"horizon.extras.cosmetics.name_tags": "Nur Namensschilder",
|
||||
|
||||
@@ -317,7 +317,6 @@
|
||||
"horizon.osk.close": "Κλείσιμο",
|
||||
"horizon.extras.chat_animation.tooltip": "Μια νέα γραμμή συνομιλίας ολισθαίνει από τα αριστερά ενώ σβήνει, αντί να εμφανίζεται ολόκληρη.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Η έγχρωμη γραμμή στα αριστερά μιας γραμμής συνομιλίας.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Ο τροχός κύλισης πάνω από ένα κουμπί στις ρυθμίσεις αλλάζει την τιμή του, όπως στη βανίλια. Ανενεργό, ο τροχός κάνει κύλιση μόνο στη λίστα.",
|
||||
"horizon.extras.pack_browser.tooltip": "Το Select Resource Packs παίρνει ένα κουμπί Προσθήκη που αναζητά το Modrinth και εγκαθιστά ένα πακέτο στο φάκελο των πακέτων σας. Εκτός, το παιχνίδι δεν έρχεται ποτέ σε επαφή με τη Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "Σε έναν κόσμο, ένας άλλος ελεγκτής μπορεί να εγγραφεί ως νέος παίκτης: πατήστε A όταν σας ζητήσει, μετά επιλέξτε ένα προφίλ και πού θα παίξετε.",
|
||||
"horizon.splitscreen.setup.monitor": "Οθόνη %s",
|
||||
@@ -462,8 +461,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Κρυφό: κάθε παίκτης του Horizon σας βλέπει χωρίς τα καλλυντικά σας, συμπεριλαμβανομένων εσείς. Ο ίδιος διακόπτης με την ντουλάπα του εκτοξευτή.",
|
||||
"horizon.extras.cosmetics_others": "Καλλυντικά άλλων παικτών",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Τι φοράνε οι άλλοι παίκτες του Horizon: τα πάντα, μόνο τα στυλ των ετικετών τους ή τίποτα. Κρυφά, το παιχνίδι δεν ρωτά ποτέ τι φοράνε.",
|
||||
"horizon.extras.cosmetics_auras": "Οι αύρες και τα μονοπάτια των άλλων",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Αύρες και μονοπάτια φορούν άλλοι παίκτες. Απενεργοποίηση από προεπιλογή.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Απόκρυψη ιχνών και αυρών άλλων παικτών",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Τα ίχνη και οι αύρες των άλλων παικτών μένουν κρυφά· τα άλλα διακοσμητικά τους εξακολουθούν να φαίνονται. Ανενεργό από προεπιλογή.",
|
||||
"horizon.extras.cosmetics.shown": "Εμφανίζεται",
|
||||
"horizon.extras.cosmetics.hidden": "Κρυφό",
|
||||
"horizon.extras.cosmetics.name_tags": "Μόνο ετικέτες ονομάτων",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"horizon.options.sneak_button": "Sneak Button",
|
||||
"horizon.options.move_deadzone": "Move Deadzone",
|
||||
"horizon.options.look_deadzone": "Look Deadzone",
|
||||
"horizon.options.trigger_threshold": "Trigger Threshold",
|
||||
"horizon.options.button_glyphs": "Button Glyphs",
|
||||
"horizon.options.on_screen_keyboard": "On-Screen Keyboard",
|
||||
"horizon.options.chat_delay": "Chat Delay",
|
||||
@@ -80,7 +81,7 @@
|
||||
"horizon.pages.open.mods": "Mods...",
|
||||
"horizon.pages.open.agent": "AI Agent...",
|
||||
"horizon.pages.open.horizon_graphics": "Horizon Graphics...",
|
||||
"horizon.pages.open.controller_binds": "Controller Binds...",
|
||||
"horizon.pages.open.controller_binds": "Bindings...",
|
||||
"horizon.pages.open.horizon_settings": "Horizon Settings...",
|
||||
"horizon.pages.open.studio": "Replay Studio...",
|
||||
"horizon.studio.saved_recordings": "Saved Recordings",
|
||||
@@ -97,6 +98,28 @@
|
||||
"horizon.mods.settings": "Settings",
|
||||
"horizon.mods.in_world": " (leave the world to change)",
|
||||
"horizon.mods.open_folder": "Open Mod Folder",
|
||||
"horizon.mods.browse": "Browse Mods...",
|
||||
"horizon.mods.browse.title": "Browse Mods",
|
||||
"horizon.mods.browse.search": "Search mods...",
|
||||
"horizon.mods.browse.project": "Mod",
|
||||
"horizon.mods.browse.loading": "Reading Horizon's mod registry...",
|
||||
"horizon.mods.browse.failed": "Couldn't read the mod registry: %s",
|
||||
"horizon.mods.browse.none": "No mod found",
|
||||
"horizon.mods.browse.has_update": " (update)",
|
||||
"horizon.mods.browse.is_installed": " (installed)",
|
||||
"horizon.mods.browse.by": "by %s",
|
||||
"horizon.mods.browse.installing": "Installing...",
|
||||
"horizon.mods.browse.install": "Install",
|
||||
"horizon.mods.browse.update": "Update",
|
||||
"horizon.mods.browse.installed": "Installed",
|
||||
"horizon.mods.browse.open_page": "Open Page",
|
||||
"horizon.mods.browse.enabled": "Mod",
|
||||
"horizon.mods.browse.changelog": "Changelog",
|
||||
"horizon.mods.browse.done": "%s %s is installed and on.",
|
||||
"horizon.mods.browse.install_failed": "Couldn't install it: %s",
|
||||
"horizon.mods.switch.worldgen": "changes world generation",
|
||||
"horizon.mods.switch.registries": "adds blocks, items or other registry entries",
|
||||
"horizon.mods.switch.restart": "Restart the game to apply %s: they add blocks, items or world generation.",
|
||||
"horizon.shader_packs.empty": "No shader packs: put them in the shaderpacks folder",
|
||||
"horizon.shader_packs.kind_horizon": " (Horizon)",
|
||||
"horizon.shader_options.open": "Shader Options...",
|
||||
@@ -302,6 +325,7 @@
|
||||
"horizon.extras.splitscreen": "Splitscreen",
|
||||
"horizon.extras.chat_animation": "Smooth chat",
|
||||
"horizon.extras.chat_indicator": "Message indicator",
|
||||
"horizon.extras.wheel_cycles_list_settings.tooltip": "The scroll wheel over a button in a settings list changes its value. Off, as in vanilla, the wheel scrolls the list.",
|
||||
"horizon.extras.wheel_cycles_settings": "Scroll wheel changes settings",
|
||||
"horizon.extras.pack_browser": "Resource pack browser",
|
||||
"horizon.extras.fullbright.tooltip": "Show blocks and entities at full light, even in dark areas.",
|
||||
@@ -374,7 +398,6 @@
|
||||
"horizon.osk.close": "Close",
|
||||
"horizon.extras.chat_animation.tooltip": "A new chat line slides in from the left while it fades in, instead of appearing whole.",
|
||||
"horizon.extras.chat_indicator.tooltip": "The coloured bar at the left of a chat line.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "The scroll wheel over a button in the settings changes its value, as in vanilla. Off, the wheel only scrolls the list.",
|
||||
"horizon.extras.pack_browser.tooltip": "Select Resource Packs gets an Add button that searches Modrinth and installs a pack into your packs folder. Off, the game never contacts Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "In a world, another controller can join as a new player: press A when it asks, then pick a profile and where to play.",
|
||||
"horizon.splitscreen.setup.monitor": "Monitor %s",
|
||||
@@ -557,10 +580,14 @@
|
||||
"horizon.extras.header.cosmetics": "Cosmetics",
|
||||
"horizon.extras.cosmetics_mine": "My cosmetics",
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Hidden: every Horizon player sees you without your cosmetics, you included. The same switch as the launcher's Wardrobe.",
|
||||
"key.emote": "Emote Wheel",
|
||||
"horizon.emote_wheel.title": "Emotes",
|
||||
"horizon.emote_wheel.empty": "No emotes yet",
|
||||
"horizon.emote_wheel.help": "Release or click to play · 1–8 · Esc closes",
|
||||
"horizon.extras.cosmetics_others": "Other players' cosmetics",
|
||||
"horizon.extras.cosmetics_others.tooltip": "What other Horizon players wear: everything, only their name tag styles, or nothing. Hidden unless you own cosmetics, until you choose. Hidden, the game never asks what they wear.",
|
||||
"horizon.extras.cosmetics_auras": "Others' auras and trails",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auras and trails other players wear. Off by default.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Hide other players' trails and auras",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Other players' trails and auras stay hidden; their other cosmetics still show. Off by default.",
|
||||
"horizon.extras.cosmetics.shown": "Shown",
|
||||
"horizon.extras.cosmetics.hidden": "Hidden",
|
||||
"horizon.extras.cosmetics.name_tags": "Name tags only",
|
||||
@@ -676,6 +703,12 @@
|
||||
"horizon.pvp.hud_held_corner.tooltip": "Where the count shows.",
|
||||
"horizon.pvp.hud_background": "Widget backgrounds",
|
||||
"horizon.pvp.hud_background.tooltip": "A dark box behind each widget.",
|
||||
"horizon.pvp.hud_opacity": "Background opacity",
|
||||
"horizon.pvp.hud_opacity.tooltip": "How dark the boxes behind the widgets are.",
|
||||
"horizon.pvp.hud_text_colour": "Text colour",
|
||||
"horizon.pvp.hud_text_colour.tooltip": "The widgets' text colour. Durability keeps its green to red.",
|
||||
"horizon.pvp.hud_shadow": "Text shadow",
|
||||
"horizon.pvp.hud_shadow.tooltip": "A shadow under the widgets' text, as vanilla's HUD text has.",
|
||||
"horizon.pvp.header.hud": "HUD widgets",
|
||||
"horizon.pvp.corner.top_left": "Top left",
|
||||
"horizon.pvp.corner.top_right": "Top right",
|
||||
@@ -705,6 +738,37 @@
|
||||
"horizon.pvp.hud.direction.south": "South",
|
||||
"horizon.pvp.hud.direction.east": "East",
|
||||
"horizon.pvp.hud.direction.west": "West",
|
||||
"horizon.pvp.hud_fps": "FPS",
|
||||
"horizon.pvp.hud_fps.tooltip": "Frames drawn in the last second.",
|
||||
"horizon.pvp.hud_fps_corner": "FPS corner",
|
||||
"horizon.pvp.hud_fps_corner.tooltip": "Where the FPS shows.",
|
||||
"horizon.pvp.hud_cps": "Clicks per second",
|
||||
"horizon.pvp.hud_cps.tooltip": "Your left and right clicks in the last second.",
|
||||
"horizon.pvp.hud_cps_corner": "CPS corner",
|
||||
"horizon.pvp.hud_cps_corner.tooltip": "Where the clicks per second show.",
|
||||
"horizon.pvp.hud_keys": "Keystrokes",
|
||||
"horizon.pvp.hud_keys.tooltip": "Your movement keys, mouse buttons and jump, lit while you hold them, with clicks per second.",
|
||||
"horizon.pvp.hud_keys_corner": "Keystrokes corner",
|
||||
"horizon.pvp.hud_keys_corner.tooltip": "Where the keystrokes show.",
|
||||
"horizon.pvp.hud_clock": "Clock",
|
||||
"horizon.pvp.hud_clock.tooltip": "Your computer's time.",
|
||||
"horizon.pvp.hud_clock_corner": "Clock corner",
|
||||
"horizon.pvp.hud_clock_corner.tooltip": "Where the clock shows.",
|
||||
"horizon.pvp.hud_reach": "Reach",
|
||||
"horizon.pvp.hud_reach.tooltip": "How far from your eyes your last hit landed, for 3 seconds. It only measures: your reach stays vanilla's.",
|
||||
"horizon.pvp.hud_reach_corner": "Reach corner",
|
||||
"horizon.pvp.hud_reach_corner.tooltip": "Where the reach shows.",
|
||||
"horizon.pvp.hud_speed": "Speed",
|
||||
"horizon.pvp.hud_speed.tooltip": "How fast you move across the ground, in blocks per second.",
|
||||
"horizon.pvp.hud_speed_corner": "Speed corner",
|
||||
"horizon.pvp.hud_speed_corner.tooltip": "Where the speed shows.",
|
||||
"horizon.pvp.hud.fps": "%s FPS",
|
||||
"horizon.pvp.hud.cps": "CPS: %s | %s",
|
||||
"horizon.pvp.hud.reach": "Reach: %s",
|
||||
"horizon.pvp.hud.speed": "Speed: %s b/s",
|
||||
"horizon.pvp.hud.keys.attack": "LMB",
|
||||
"horizon.pvp.hud.keys.use": "RMB",
|
||||
"horizon.pvp.hud.keys.cps": "%s CPS",
|
||||
"horizon.pvp.numeric_enchants": "Enchantment levels as numbers",
|
||||
"horizon.pvp.numeric_enchants.tooltip": "Tooltips and the enchanting table show Sharpness 5 instead of Sharpness V.",
|
||||
"horizon.pvp.header.items": "Items",
|
||||
@@ -749,7 +813,24 @@
|
||||
"horizon.pvp.chat_search.tooltip": "Ctrl+F in the open chat: what you type shows only the messages holding it. Enter or Ctrl+F again stops.",
|
||||
"horizon.pvp.preset": "PvP preset",
|
||||
"horizon.pvp.preset.tooltip": "Turns on the custom crosshair, the armour and effects widgets, toggle sprint and sneak, lower fire, and the FPS counter and keystrokes mods if installed. Your settings for each stay.",
|
||||
"horizon.pvp.hud_editor_key": "HUD editor key",
|
||||
"horizon.pvp.hud_editor_key.tooltip": "Opens the HUD editor from the game. Click, then press a key; Escape unbinds it.",
|
||||
"horizon.pvp.editor": "Edit HUD",
|
||||
"horizon.pvp.editor.tooltip": "Move the widgets on the game: drag them anywhere, resize them with the wheel, right click one to put it back in its corner.",
|
||||
"horizon.pvp.editor.hint": "Drag widgets · Wheel: size · Right click: back to its corner",
|
||||
"horizon.pvp.combat_feel": "Combat feel",
|
||||
"horizon.pvp.combat_feel.tooltip": "Legacy draws fights as 1.8.9 did: the item in your hand, its swing, eating, the bow and a sword held up while you hold use, without the dip after each hit or the attack indicator. Only the look: what you send a server stays the same.",
|
||||
"horizon.pvp.combat_indicator": "Attack indicator in Legacy",
|
||||
"horizon.pvp.combat_indicator.tooltip": "Keeps the attack indicator under Legacy, which 1.8.9 didn't have.",
|
||||
"horizon.pvp.combat_feel.modern": "Modern",
|
||||
"horizon.pvp.combat_feel.legacy": "Legacy (1.8.9)",
|
||||
"horizon.pvp.header.combat": "Combat feel",
|
||||
"horizon.pvp.combat_sounds": "1.8 hit sounds",
|
||||
"horizon.pvp.combat_sounds.tooltip": "Under Legacy, leaves out the attack sounds 1.9 added (strong, weak, sweep, critical...). Hurt sounds stay.",
|
||||
"horizon.pvp.combat_rules": "1.8 combat in worlds you host",
|
||||
"horizon.pvp.combat_rules.tooltip": "Single player and the worlds you open to LAN fight by 1.8.9's rules: no attack cooldown or sweep, 1.8 weapon damage, Strength, Sharpness and critical hits. Servers you join keep their own rules.",
|
||||
"Bindings": "Bindings",
|
||||
"Play": "Play",
|
||||
"Device": "Device",
|
||||
"Look": "Look",
|
||||
"Movement": "Movement",
|
||||
@@ -771,6 +852,7 @@
|
||||
"How far the right stick moves before the view turns. Raise it if the view drifts.": "How far the right stick moves before the view turns. Raise it if the view drifts.",
|
||||
"Hold the button to sneak, or press it once to start and again to stop.": "Hold the button to sneak, or press it once to start and again to stop.",
|
||||
"How far the left stick moves before you walk. Raise it if you drift.": "How far the left stick moves before you walk. Raise it if you drift.",
|
||||
"How far a trigger is pulled before it counts as pressed.": "How far a trigger is pulled before it counts as pressed.",
|
||||
"How fast the stick moves the crosshair in inventories.": "How fast the stick moves the crosshair in inventories.",
|
||||
"How fast the right stick scrolls a list.": "How fast the right stick scrolls a list.",
|
||||
"How long a held direction waits before it repeats.": "How long a held direction waits before it repeats.",
|
||||
|
||||
@@ -322,7 +322,6 @@
|
||||
"horizon.osk.close": "Fermu",
|
||||
"horizon.extras.chat_animation.tooltip": "Nova babillinio glitas enen de maldekstre dum ĝi fadas, anstataŭ aperi tute.",
|
||||
"horizon.extras.chat_indicator.tooltip": "La kolora stango maldekstre de babillinio.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "La rula rado super butono en la agordoj ŝanĝas sian valoron, kiel en vanilo. Malŝaltita, la rado nur rulumas la liston.",
|
||||
"horizon.extras.pack_browser.tooltip": "Elektu Rimedo-Pakojn ricevas butonon Aldoni, kiu serĉas Modrinth kaj instalas pakon en viajn pakajn dosierujojn. Malŝaltita, la ludo neniam kontaktas Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "En mondo, alia regilo povas aliĝi kiel nova ludanto: premu A kiam ĝi petas, tiam elektu profilon kaj kie ludi.",
|
||||
"horizon.splitscreen.setup.monitor": "Monitoro %s",
|
||||
@@ -468,8 +467,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Kaŝita: ĉiu Horizon-ludanto vidas vin sen viaj kosmetikaĵoj, vi inkluzivis. La sama ŝaltilo kiel la Vestoŝranko de la lanĉilo.",
|
||||
"horizon.extras.cosmetics_others": "Kosmetikaĵoj de aliaj ludantoj",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Kion aliaj Horizon ludantoj portas: ĉion, nur siajn nometikedstilojn, aŭ nenion. Kaŝita, la ludo neniam demandas kion ili portas.",
|
||||
"horizon.extras.cosmetics_auras": "Aŭroj kaj spuroj de aliaj",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Aŭroj kaj spuroj kiujn aliaj ludantoj portas. Malŝaltita defaŭlte.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Kaŝi spurojn kaj aŭrojn de aliaj ludantoj",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Spuroj kaj aŭroj de aliaj ludantoj restas kaŝitaj; iliaj aliaj kosmetikaĵoj ankoraŭ montriĝas. Defaŭlte malŝaltita.",
|
||||
"horizon.extras.cosmetics.shown": "Montrita",
|
||||
"horizon.extras.cosmetics.hidden": "Kaŝita",
|
||||
"horizon.extras.cosmetics.name_tags": "Nomaj etikedoj nur"
|
||||
|
||||
@@ -321,7 +321,6 @@
|
||||
"horizon.osk.close": "Sule",
|
||||
"horizon.extras.chat_animation.tooltip": "Uus vestlusliin libiseb vasakult sisse, kui see sumbub, selle asemel, et paista tervena.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Vestlusrea vasakul olev värviline riba.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Seadetes nupu kohal olev kerimisratas muudab selle väärtust, nagu vanilje puhul. Väljas, ratas kerib ainult loendit.",
|
||||
"horizon.extras.pack_browser.tooltip": "Ressursipaketi valikul kuvatakse nupp Lisa, mis otsib Modrinthi ja installib paketi teie pakettide kausta. Väljas, mäng ei võta kunagi Modrinthiga ühendust.",
|
||||
"horizon.extras.splitscreen.tooltip": "Maailmas saab uue mängijana liituda teine kontroller: vajutage A, kui see küsib, seejärel valige profiil ja kus mängida.",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "monitor",
|
||||
@@ -464,8 +463,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Varjatud: iga Horizoni mängija näeb teid ilma teie kosmeetikata, kaasa arvatud. Sama lüliti, mis käivitaja riidekapil.",
|
||||
"horizon.extras.cosmetics_others": "Teiste mängijate kosmeetika",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Mida teised Horizoni mängijad kannavad: kõike, ainult nende nimesiltide stiile või mitte midagi. Varjatud, mäng ei küsi kunagi, mida nad kannavad.",
|
||||
"horizon.extras.cosmetics_auras": "Teiste aurad ja jäljed",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Aurad ja jäljed, mida teised mängijad kannavad. Vaikimisi väljas.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Peida teiste mängijate jäljed ja aurad",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Teiste mängijate jäljed ja aurad jäävad peidetuks; nende muu kosmeetika on endiselt näha. Vaikimisi väljas.",
|
||||
"horizon.extras.cosmetics.shown": "Näidatud",
|
||||
"horizon.extras.cosmetics.hidden": "Peidetud",
|
||||
"horizon.extras.cosmetics.name_tags": "Ainult nimesildid"
|
||||
|
||||
@@ -323,7 +323,6 @@
|
||||
"horizon.osk.close": "Itxi",
|
||||
"horizon.extras.chat_animation.tooltip": "Txat-lerro berri bat ezkerretik sartzen da desagertzen den bitartean, osorik agertu beharrean.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Txat-lerro baten ezkerreko koloretako barra.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Ezarpenetako botoi baten gainean korritzeko gurpilak bere balioa aldatzen du, bainilan bezala. Desaktibatuta, gurpilak zerrendan bakarrik mugitzen du.",
|
||||
"horizon.extras.pack_browser.tooltip": "Aukeratu Baliabide Paketeak Gehitu botoi bat lortzen du, Modrinth bilatzen duena eta pakete bat instalatzen du zure paketeen karpetan. Desaktibatuta, jokoa ez da inoiz Modrinth-ekin harremanetan jartzen.",
|
||||
"horizon.extras.splitscreen.tooltip": "Mundu batean, beste kontrolagailu bat sar daiteke jokalari berri gisa: sakatu A eskatzen dionean, eta hautatu profil bat eta non jokatu.",
|
||||
"horizon.splitscreen.setup.monitor": "Monitorea %s",
|
||||
@@ -469,8 +468,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Ezkutuan: Horizon jokalari bakoitzak zure kosmetikarik gabe ikusten zaitu, zuk barne. Abiarazlearen armairuaren etengailu bera.",
|
||||
"horizon.extras.cosmetics_others": "Beste jokalarien kosmetika",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Beste Horizon jokalariek zer janzten duten: denetarik, izenaren etiketa estiloak bakarrik, edo ezer ez. Ezkutuan, jokoak ez du inoiz galdetzen zer janzten duten.",
|
||||
"horizon.extras.cosmetics_auras": "Besteen aura eta ibilbideak",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Beste jokalariek janzten dituzten aura eta arrastoak. Desaktibatuta lehenespenez.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Ezkutatu beste jokalarien arrastoak eta aurak",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Beste jokalarien arrastoak eta aurak ezkutuan geratzen dira; haien beste kosmetikoak ikusten jarraitzen dute. Desaktibatuta lehenespenez.",
|
||||
"horizon.extras.cosmetics.shown": "Erakutsita",
|
||||
"horizon.extras.cosmetics.hidden": "Ezkutuan",
|
||||
"horizon.extras.cosmetics.name_tags": "Izena etiketak soilik"
|
||||
|
||||
@@ -322,7 +322,6 @@
|
||||
"horizon.osk.close": "بستن",
|
||||
"horizon.extras.chat_animation.tooltip": "یک خط چت جدید به جای اینکه کامل نشان داده شود، از سمت چپ وارد می شود و در حالی که محو می شود.",
|
||||
"horizon.extras.chat_indicator.tooltip": "نوار رنگی در سمت چپ یک خط چت.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "چرخ اسکرول روی یک دکمه در تنظیمات مقدار آن را تغییر می دهد، مانند وانیل. خاموش، چرخ فقط فهرست را پیمایش می کند.",
|
||||
"horizon.extras.pack_browser.tooltip": "Select Resource Packs یک دکمه Add دریافت می کند که Modrinth را جستجو می کند و یک بسته را در پوشه بسته های شما نصب می کند. خاموش، بازی هرگز با Modrinth تماس نمی گیرد.",
|
||||
"horizon.extras.splitscreen.tooltip": "در یک دنیا، کنترلکننده دیگری میتواند بهعنوان یک بازیکن جدید ملحق شود: وقتی A را درخواست کرد، A را فشار دهید، سپس یک نمایه و مکان بازی را انتخاب کنید.",
|
||||
"horizon.splitscreen.setup.monitor": "مانیتور %s",
|
||||
@@ -474,8 +473,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "پنهان: هر بازیکن Horizon شما را بدون لوازم آرایشی شما، از جمله شما، می بیند. همان سوئیچ کمد لانچر.",
|
||||
"horizon.extras.cosmetics_others": "لوازم آرایشی دیگر بازیکنان",
|
||||
"horizon.extras.cosmetics_others.tooltip": "سایر بازیکنان Horizon چه می پوشند: همه چیز، فقط سبک برچسب نام آنها یا هیچ چیز. پنهان، بازی هرگز نمی پرسد آنها چه می پوشند.",
|
||||
"horizon.extras.cosmetics_auras": "هاله ها و مسیرهای دیگران",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "هاله ها و دنباله های دیگر بازیکنان می پوشند. به طور پیش فرض خاموش است.",
|
||||
"horizon.extras.cosmetics_hide_fx": "پنهان کردن ردها و هالههای بازیکنان دیگر",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "ردها و هالههای بازیکنان دیگر پنهان میمانند؛ دیگر آرایههایشان همچنان دیده میشوند. بهطور پیشفرض خاموش.",
|
||||
"horizon.extras.cosmetics.shown": "نشان داده شده است",
|
||||
"horizon.extras.cosmetics.hidden": "پنهان شده است",
|
||||
"horizon.extras.cosmetics.name_tags": "فقط برچسب های نام"
|
||||
|
||||
@@ -306,7 +306,6 @@
|
||||
"horizon.osk.close": "Sulje",
|
||||
"horizon.extras.chat_animation.tooltip": "Uusi chat-linja liukuu sisään vasemmalta sen häipyessä sen sijaan, että se näyttäisi kokonaisena.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Värillinen palkki keskustelupalkin vasemmalla puolella.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Asetuksissa painikkeen päällä oleva vierityspyörä muuttaa sen arvoa, kuten vaniljassa. Pois päältä, pyörä vain vierittää luetteloa.",
|
||||
"horizon.extras.pack_browser.tooltip": "Valitse Resurssipaketit saa Lisää-painikkeen, joka etsii Modrinthista ja asentaa paketin pakettikansioosi. Pois päältä, peli ei koskaan saa yhteyttä Modrinthiin.",
|
||||
"horizon.extras.splitscreen.tooltip": "Maailmassa toinen ohjain voi liittyä uutena pelaajana: paina A, kun se pyytää, ja valitse sitten profiili ja pelipaikka.",
|
||||
"horizon.splitscreen.setup.monitor": "Monitori %s",
|
||||
@@ -449,8 +448,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Piilotettu: jokainen Horizon-pelaaja näkee sinut ilman kosmetiikkaasi, sinä mukaan lukien. Sama kytkin kuin kantoraketin vaatekaappi.",
|
||||
"horizon.extras.cosmetics_others": "Muiden pelaajien kosmetiikkaa",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Mitä muut Horizon-pelaajat käyttävät: kaikki, vain heidän nimilaputyylinsä tai ei mitään. Piilotettu, peli ei koskaan kysy, mitä he käyttävät.",
|
||||
"horizon.extras.cosmetics_auras": "Toisten aurat ja polut",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Aurat ja jäljet, joita muut pelaajat käyttävät. Oletuksena pois päältä.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Piilota muiden pelaajien jäljet ja aurat",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Muiden pelaajien jäljet ja aurat pysyvät piilossa; heidän muut kosmetiikkansa näkyvät yhä. Oletuksena pois.",
|
||||
"horizon.extras.cosmetics.shown": "Näytetään",
|
||||
"horizon.extras.cosmetics.hidden": "Piilotettu",
|
||||
"horizon.extras.cosmetics.name_tags": "Vain nimilaput",
|
||||
|
||||
@@ -285,7 +285,6 @@
|
||||
"horizon.osk.close": "Isara",
|
||||
"horizon.extras.chat_animation.tooltip": "Ang isang bagong linya ng chat ay dumudulas mula sa kaliwa habang ito ay nawawala, sa halip na lumabas nang buo.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Ang may kulay na bar sa kaliwa ng isang chat line.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Ang scroll wheel sa ibabaw ng isang button sa mga setting ay nagbabago ng halaga nito, tulad ng sa vanilla. Naka-off, ini-scroll lang ng gulong ang listahan.",
|
||||
"horizon.extras.pack_browser.tooltip": "Ang Select Resource Packs ay nakakakuha ng Add button na naghahanap sa Modrinth at nag-i-install ng pack sa iyong pack folder. Wala, ang laro ay hindi kailanman nakikipag-ugnayan sa Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "Sa isang mundo, maaaring sumali ang isa pang controller bilang bagong player: pindutin ang A kapag nagtanong ito, pagkatapos ay pumili ng profile at kung saan maglaro.",
|
||||
"horizon.splitscreen.setup.monitor": "Subaybayan %s",
|
||||
@@ -421,8 +420,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Nakatago: makikita ka ng bawat Horizon na manlalaro nang wala ang iyong mga pampaganda, kasama mo. Ang parehong switch bilang Wardrobe ng launcher.",
|
||||
"horizon.extras.cosmetics_others": "Mga pampaganda ng ibang manlalaro",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Ano ang isinusuot ng iba pang Horizon na manlalaro: lahat, mga istilo lang ng name tag nila, o wala. Nakatago, ang laro ay hindi nagtatanong kung ano ang kanilang isinusuot.",
|
||||
"horizon.extras.cosmetics_auras": "Mga aura at landas ng iba",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Mga aura at trail na isinusuot ng ibang mga manlalaro. Naka-off bilang default.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Itago ang mga bakas at aura ng ibang manlalaro",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Nakatago ang mga bakas at aura ng ibang manlalaro; nakikita pa rin ang iba nilang cosmetics. Naka-off bilang default.",
|
||||
"horizon.extras.cosmetics.shown": "Ipinakita",
|
||||
"horizon.extras.cosmetics.hidden": "Nakatago",
|
||||
"horizon.extras.cosmetics.name_tags": "Mga name tag lang",
|
||||
|
||||
@@ -319,7 +319,6 @@
|
||||
"horizon.osk.close": "Lat aftur",
|
||||
"horizon.extras.chat_animation.tooltip": "Ein nýggj kjaklinja rennur inn frá vinstru, meðan hon doyvir inn, í staðin fyri at síggja heil út.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Litaða strikan til vinstru á kjaklinju.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Rulluhjólið yvir einum knøtti í innstillingunum broytir virðið, eins og í vanilju. Av, hjólið rullar bara listan.",
|
||||
"horizon.extras.pack_browser.tooltip": "Vel Tilfeingispakkar fær ein Legg til knøtt, sum leitar Modrinth og setur ein pakka upp í tína pakka mappu. Sløkt, spælið ongantíð kontaktar Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "Í einum heimi kann ein annar stýrari melda seg til sum nýggjur spælari: trýst á A tá tað spyr, vel síðani ein profil og hvar tú skalt spæla.",
|
||||
"horizon.splitscreen.setup.monitor": "Skjár %s",
|
||||
@@ -471,8 +470,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Dult: hvør Horizon spælari sær teg uttan títt kosmetikk, tú eisini. Sama rotari sum Klædnaskápið hjá sjósetaranum.",
|
||||
"horizon.extras.cosmetics_others": "Kosmetikkur hjá øðrum spælarum",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Hvat aðrir Horizon spælarar eru í: alt, bara teirra navnamerkjastílar, ella einki. Dult spyr spælið ongantíð, hvat tey eru í.",
|
||||
"horizon.extras.cosmetics_auras": "Aura og slóðir hjá øðrum",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auras og slóðir aðrir spælarar bera. Sløkt sum standard.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Fjal spor og aurur hjá øðrum leikarum",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Spor og aurur hjá øðrum leikarum verða fjald; teirra aðrir prýðislutir síggjast framvegis. Sløkt sum standard.",
|
||||
"horizon.extras.cosmetics.shown": "Víst",
|
||||
"horizon.extras.cosmetics.hidden": "Dult",
|
||||
"horizon.extras.cosmetics.name_tags": "Einans navnamerki"
|
||||
|
||||
@@ -46,6 +46,28 @@
|
||||
"horizon.mods.dev": " (dév.)",
|
||||
"horizon.mods.settings": "Options",
|
||||
"horizon.mods.open_folder": "Ouvrir le dossier des mods",
|
||||
"horizon.mods.browse": "Parcourir les mods...",
|
||||
"horizon.mods.browse.title": "Parcourir les mods",
|
||||
"horizon.mods.browse.search": "Rechercher des mods...",
|
||||
"horizon.mods.browse.project": "Mod",
|
||||
"horizon.mods.browse.loading": "Lecture du registre des mods Horizon...",
|
||||
"horizon.mods.browse.failed": "Impossible de lire le registre des mods : %s",
|
||||
"horizon.mods.browse.none": "Aucun mod trouvé",
|
||||
"horizon.mods.browse.has_update": " (mise à jour)",
|
||||
"horizon.mods.browse.is_installed": " (installé)",
|
||||
"horizon.mods.browse.by": "par %s",
|
||||
"horizon.mods.browse.installing": "Installation...",
|
||||
"horizon.mods.browse.install": "Installer",
|
||||
"horizon.mods.browse.update": "Mettre à jour",
|
||||
"horizon.mods.browse.installed": "Installé",
|
||||
"horizon.mods.browse.open_page": "Ouvrir la page",
|
||||
"horizon.mods.browse.enabled": "Mod",
|
||||
"horizon.mods.browse.changelog": "Nouveautés",
|
||||
"horizon.mods.browse.done": "%s %s est installé et activé.",
|
||||
"horizon.mods.browse.install_failed": "Installation impossible : %s",
|
||||
"horizon.mods.switch.worldgen": "change la génération du monde",
|
||||
"horizon.mods.switch.registries": "ajoute des blocs, des objets ou d'autres entrées de registre",
|
||||
"horizon.mods.switch.restart": "Redémarre le jeu pour appliquer %s : ils ajoutent des blocs, des objets ou de la génération de monde.",
|
||||
"horizon.shader_packs.empty": "Aucun pack de shaders : placez-les dans le dossier shaderpacks",
|
||||
"horizon.shader_options.open": "Options des shaders...",
|
||||
"horizon.shader_options.profile": "Profil",
|
||||
@@ -394,10 +416,14 @@
|
||||
"horizon.extras.header.cosmetics": "Cosmétiques",
|
||||
"horizon.extras.cosmetics_mine": "Mes cosmétiques",
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Masqués : tous les joueurs Horizon vous voient sans vos cosmétiques, vous compris. Le même réglage que la garde-robe du launcher.",
|
||||
"key.emote": "Roue des emotes",
|
||||
"horizon.emote_wheel.title": "Emotes",
|
||||
"horizon.emote_wheel.empty": "Pas encore d'emote",
|
||||
"horizon.emote_wheel.help": "Relâche ou clique pour jouer · 1–8 · Échap ferme",
|
||||
"horizon.extras.cosmetics_others": "Cosmétiques des autres",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Ce que portent les autres joueurs Horizon : tout, seulement leur style d'étiquette de nom, ou rien. Masqués si vous n'avez pas de cosmétiques, tant que vous n'avez pas choisi. Masqués, le jeu ne demande jamais ce qu'ils portent.",
|
||||
"horizon.extras.cosmetics_auras": "Auras et traînées des autres",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Les auras et traînées que portent les autres joueurs. Désactivé par défaut.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Masquer les traînées et auras des autres joueurs",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Les traînées et auras des autres joueurs restent masquées ; leurs autres cosmétiques restent visibles. Désactivé par défaut.",
|
||||
"horizon.extras.cosmetics.shown": "Affichés",
|
||||
"horizon.extras.cosmetics.hidden": "Masqués",
|
||||
"horizon.extras.cosmetics.name_tags": "Étiquettes seulement",
|
||||
@@ -432,8 +458,8 @@
|
||||
"Results for \"%s\"": "Résultats pour \"%s\"",
|
||||
"No settings match.": "Aucun paramètre ne correspond.",
|
||||
"horizon.extras.discord_presence": "Présence Discord",
|
||||
"horizon.extras.wheel_cycles_list_settings.tooltip": "La molette sur un bouton d'une liste de paramètres modifie sa valeur. Désactivé, comme dans le jeu d'origine, la molette fait défiler la liste.",
|
||||
"horizon.extras.wheel_cycles_settings": "La molette modifie les paramètres",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "La molette de défilement sur un bouton des paramètres modifie sa valeur, comme dans le jeu d'origine. Désactivé, la molette fait seulement défiler la liste.",
|
||||
"Block Entities": "Entités de bloc",
|
||||
"Shulker Boxes": "Boîtes de shulker",
|
||||
"Reuses settled boxes; lid movement stays animated.": "Réutilise les boîtes immobiles ; le mouvement du couvercle reste animé.",
|
||||
|
||||
@@ -315,7 +315,6 @@
|
||||
"horizon.osk.close": "Sierâ",
|
||||
"horizon.extras.chat_animation.tooltip": "Une gnove linie di chat e slungje dentri di çampe intant che e svanìs, invezit di comparî interie.",
|
||||
"horizon.extras.chat_indicator.tooltip": "La barre colorade a çampe di une rie di chat.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "La ruede di scroll suntun botôn intes impostazions e cambie il so valôr, come inte vanilie. Off, la ruede e scrole dome la liste.",
|
||||
"horizon.extras.pack_browser.tooltip": "Selezionâ Pachetis di risorsis al oten un botôn Azontâ che al cîr Modrinth e al instale un pachete inte cjarte dai pachetis. Off, il zûc nol contat mai Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "Intun mont, un altri controladôr al pues jentrâ come gnûf zuiadôr: premi A cuant che al domande, daspò sielzi un profîl e dulà zuiâ.",
|
||||
"horizon.splitscreen.setup.monitor": "Monitôr %s",
|
||||
@@ -467,8 +466,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Scuindût: ogni zuiadôr Horizon al viôt cence il to cosmetic, tu ancje. Il stes interrutôr dal Armadi dal lançadôr.",
|
||||
"horizon.extras.cosmetics_others": "I cosmetics dai altris zuiadôrs",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Ce che a metin sù altris zuiadôrs Horizon: dut, dome i lôr stîi di etichetis, o nuie. Scuindût, il zûc nol domande mai ce che a metin sù.",
|
||||
"horizon.extras.cosmetics_auras": "Auris e sentiers di altris",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auris e pistis che a metin sù altris zuiadôrs. Disactivât par automatic.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Plate lis olmis e lis aureis dai altris zuiadôrs",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Lis olmis e lis aureis dai altris zuiadôrs a restin platadis; i lôr altris cosmetics si viodin ancjemò. Distudât di predefinît.",
|
||||
"horizon.extras.cosmetics.shown": "Mostrât",
|
||||
"horizon.extras.cosmetics.hidden": "Scuindût",
|
||||
"horizon.extras.cosmetics.name_tags": "Dome lis etichetis dai nons"
|
||||
|
||||
@@ -271,7 +271,6 @@
|
||||
"horizon.osk.close": "Slút",
|
||||
"horizon.extras.chat_animation.tooltip": "In nije petearrigel glydt fan links yn, wylst it yn ferdwynt, ynstee fan folslein te ferskinen.",
|
||||
"horizon.extras.chat_indicator.tooltip": "De kleurde balke oan de linkerkant fan in petear line.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "It scrollrad oer in knop yn 'e ynstellings feroaret syn wearde, lykas yn vanille. Off, it tsjil rôlet allinnich de list.",
|
||||
"horizon.extras.pack_browser.tooltip": "Selektearje boarnepakketten krijt in knop taheakje dy't Modrinth siket en in pakket ynstalleart yn jo pakketmap. Ut, it spultsje nea kontakt Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "Yn in wrâld kin in oare controller meidwaan as in nije spiler: druk op A as it freget, kies dan in profyl en wêr te spyljen.",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "in monitor",
|
||||
@@ -399,8 +398,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Ferburgen: elke Horizon-spiler sjocht jo sûnder jo kosmetika, jo ynbegrepen. Deselde switch as de Launcher syn Wardrobe.",
|
||||
"horizon.extras.cosmetics_others": "Oare spilers 'cosmetica",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Wat oare Horizon-spilers drage: alles, allinich har nammelabelstilen, of neat. Ferburgen, it spultsje freget noait wat se drage.",
|
||||
"horizon.extras.cosmetics_auras": "Oare aura's en spoaren",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auras en spoaren oare spilers drage. Standert út.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Spoaren en aura's fan oare spilers ferbergje",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Spoaren en aura's fan oare spilers bliuwe ferburgen; harren oare kosmetika bliuwt sichtber. Standert út.",
|
||||
"horizon.extras.cosmetics.shown": "Oanjûn",
|
||||
"horizon.extras.cosmetics.hidden": "Ferburgen",
|
||||
"horizon.extras.cosmetics.name_tags": "Allinnich nammelabels"
|
||||
|
||||
@@ -319,7 +319,6 @@
|
||||
"horizon.osk.close": "Dún",
|
||||
"horizon.extras.chat_animation.tooltip": "Sleamhnaíonn líne comhrá nua isteach ón taobh clé agus í ag dul i léig, in ionad a bheith ina hiomláine.",
|
||||
"horizon.extras.chat_indicator.tooltip": "An barra daite ar thaobh na láimhe clé den líne chomhrá.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Athraíonn an roth scrollaithe thar chnaipe sna socruithe a luach, mar atá i vanilla. Lasmuigh de, ní scrollaíonn an roth ach an liosta.",
|
||||
"horizon.extras.pack_browser.tooltip": "Faigheann Roghnaigh Acmhainne Pacáistí cnaipe Cuir leis a chuardaíonn Modrinth agus a shuiteálann pacáiste isteach i d’fhillteán pacaí. Lasmuigh de, ní théann an cluiche i dteagmháil le Modrinth riamh.",
|
||||
"horizon.extras.splitscreen.tooltip": "I ndomhan, is féidir le rialtóir eile a bheith páirteach mar imreoir nua: brúigh A nuair a iarrann sé, ansin roghnaigh próifíl agus cá háit le himirt.",
|
||||
"horizon.splitscreen.setup.monitor": "Monatóir %s",
|
||||
@@ -463,8 +462,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "I bhfolach: feiceann gach imreoirHorizon tú gan do chosmaidí, tú san áireamh. An lasc céanna le Wardrobe an tosaitheoir.",
|
||||
"horizon.extras.cosmetics_others": "Cosmaidí imreoirí eile",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Cad a chaitheann imreoirí Horizon eile: gach rud, gan ach a stíleanna clibeanna ainm, nó rud ar bith. I bhfolach, ní iarrann an cluiche riamh cad a chaitheann siad.",
|
||||
"horizon.extras.cosmetics_auras": "Auras agus cosáin daoine eile",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auras agus rianta a chaitheamh imreoirí eile. Off de réir réamhshocraithe.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Folaigh rianta agus aurataí imreoirí eile",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Fanann rianta agus aurataí imreoirí eile i bhfolach; feictear a gcuid maisithe eile fós. As de réir réamhshocraithe.",
|
||||
"horizon.extras.cosmetics.shown": "Taispeántar",
|
||||
"horizon.extras.cosmetics.hidden": "i bhfolach",
|
||||
"horizon.extras.cosmetics.name_tags": "Clibeanna ainm amháin"
|
||||
|
||||
@@ -320,7 +320,6 @@
|
||||
"horizon.osk.close": "Dùin",
|
||||
"horizon.extras.chat_animation.tooltip": "Bidh loidhne cabadaich ùr a’ sleamhnachadh a-steach bhon taobh chlì fhad ‘s a tha e a’ seargadh a-steach, an àite a bhith a ’nochdadh slàn.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Am bàr dathte air taobh clì loidhne cabadaich.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Bidh a’ chuibhle scrollaidh thairis air putan anns na roghainnean ag atharrachadh a luach, mar ann am vanilla. Air falbh, cha sgrolaich a’ chuibhle ach an liosta.",
|
||||
"horizon.extras.pack_browser.tooltip": "Gheibh Tagh Pacaidean Goireasan putan Cuir ris a nì sgrùdadh air Modrinth agus a chuireas pacaid a-steach don phasgan pacaidean agad. Air falbh, cha chuir an geama fios gu Modrinth a-riamh.",
|
||||
"horizon.extras.splitscreen.tooltip": "Ann an saoghal, faodaidh rianadair eile a dhol còmhla mar chluicheadair ùr: brùth A nuair a dh’ iarras e, an uairsin tagh ìomhaigh agus càite an cluich thu.",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "sgrùdair",
|
||||
@@ -464,8 +463,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Falaichte: chì a h-uile cluicheadair Horizon thu às aonais na cungaidhean maise agad, thug thu a-steach. An aon tionndadh ri Wardrobe an lannsair.",
|
||||
"horizon.extras.cosmetics_others": "Coltas cluicheadairean eile",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Na bhios cluicheadairean Horizon eile a’ caitheamh: a h-uile càil, dìreach na stoidhlichean tag ainm aca, no dad. Falaichte, cha bhith an geama a-riamh a’ faighneachd dè a bhios orra.",
|
||||
"horizon.extras.cosmetics_auras": "Auras agus slighean cuid eile",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auras agus slighean bidh cluicheadairean eile a’ caitheamh. Cuir dheth gu bunaiteach.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Falaich slighean is aurathan chluicheadairean eile",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Fanaidh slighean is aurathan chluicheadairean eile falaichte; chithear an cuid maise eile fhathast. Dheth a ghnàth.",
|
||||
"horizon.extras.cosmetics.shown": "Air a shealltainn",
|
||||
"horizon.extras.cosmetics.hidden": "Falaichte",
|
||||
"horizon.extras.cosmetics.name_tags": "Tagaichean ainmean a-mhàin"
|
||||
|
||||
@@ -318,7 +318,6 @@
|
||||
"horizon.osk.close": "Pechar",
|
||||
"horizon.extras.chat_animation.tooltip": "Unha nova liña de chat deslízase dende a esquerda mentres se esvae, en lugar de aparecer enteira.",
|
||||
"horizon.extras.chat_indicator.tooltip": "A barra de cores á esquerda dunha liña de chat.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "A roda de desprazamento sobre un botón na configuración cambia o seu valor, como en vainilla. Desactivada, a roda só despraza a lista.",
|
||||
"horizon.extras.pack_browser.tooltip": "Select Resource Packs obtén un botón Engadir que busca en Modrinth e instala un paquete no cartafol de paquetes. Desactivado, o xogo nunca contacta con Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "Nun mundo, outro controlador pode unirse como xogador novo: preme A cando o solicite, despois escolle un perfil e onde xogar.",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "un monitor",
|
||||
@@ -463,8 +462,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Oculto: todos os xogadores de Horizon venche sen os teus cosméticos, ti incluído. O mesmo interruptor que o garda-roupa do lanzador.",
|
||||
"horizon.extras.cosmetics_others": "Cosmética doutros xogadores",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Que usan outros Horizon xogadores: todo, só os seus estilos de etiqueta de nome ou nada. Oculto, o xogo nunca pregunta que visten.",
|
||||
"horizon.extras.cosmetics_auras": "Auras e sendeiros doutros",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auras e rastros que levan outros xogadores. Desactivado por defecto.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Agochar as estelas e auras doutros xogadores",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "As estelas e auras doutros xogadores quedan agochadas; os seus outros cosméticos séguense vendo. Desactivado por defecto.",
|
||||
"horizon.extras.cosmetics.shown": "Amosado",
|
||||
"horizon.extras.cosmetics.hidden": "Oculto",
|
||||
"horizon.extras.cosmetics.name_tags": "Só etiquetas de nome"
|
||||
|
||||
@@ -315,7 +315,6 @@
|
||||
"horizon.osk.close": "Pani",
|
||||
"horizon.extras.chat_animation.tooltip": "He laina kamaʻilio hou e paheʻe mai ka hema i ka nalo ʻana, ma kahi o ka ʻike ʻia ʻana.",
|
||||
"horizon.extras.chat_indicator.tooltip": "ʻO ka pahu kala ma ka hema o kahi laina kamaʻilio.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "ʻO ka huila ʻōwili ma luna o kahi pihi ma nā hoʻonohonoho e hoʻololi i kona waiwai, e like me ka vanilla. Paʻa, ʻōwili wale ka huila i ka papa inoa.",
|
||||
"horizon.extras.pack_browser.tooltip": "E koho i nā Pūke Punawai Loaʻa ke pihi Hoʻohui e ʻimi ana iā Modrinth a hoʻokomo i kahi pūʻolo i loko o kāu waihona waihona. Paʻa, ʻaʻole pili ka pāʻani iā Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "I loko o ke ao, hiki i kekahi mea hoʻoponopono ke hui pū me he mea pāʻani hou: e kaomi iā A ke nīnau ʻo ia, a laila koho i kahi ʻaoʻao a me kahi e pāʻani ai.",
|
||||
"horizon.splitscreen.setup.monitor": "Nānā %s",
|
||||
@@ -458,8 +457,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Huna: ʻike kēlā me kēia Horizon iā ʻoe me ka ʻole o kāu mea hoʻonaninani, ua hoʻokomo ʻoe. ʻO ka hoʻololi like e like me ka mea hoʻolele lole.",
|
||||
"horizon.extras.cosmetics_others": "ʻO nā mea hoʻonani o nā mea pāʻani ʻē aʻe",
|
||||
"horizon.extras.cosmetics_others.tooltip": "He aha nā mea pāʻani Horizon ʻē aʻe e ʻaʻahu ai: nā mea āpau, nā ʻano inoa inoa wale nō, a ʻaʻohe mea. Huna, ʻaʻole nīnau ka pāʻani i kā lākou lole.",
|
||||
"horizon.extras.cosmetics_auras": "ʻO nā aura a me nā alahele o nā poʻe ʻē aʻe",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "ʻO Auras a me nā alahele e hoʻohana ai nā mea pāʻani. Paʻa ma ka paʻamau.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Hūnā i nā meheu a me nā aura o nā mea pāʻani ʻē aʻe",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Hūnā ʻia nā meheu a me nā aura o nā mea pāʻani ʻē aʻe; ʻike ʻia nō kā lākou mau mea hoʻonani ʻē aʻe. Pio ma ke ʻano maʻamau.",
|
||||
"horizon.extras.cosmetics.shown": "Hōʻike ʻia",
|
||||
"horizon.extras.cosmetics.hidden": "Huna",
|
||||
"horizon.extras.cosmetics.name_tags": "Nā inoa inoa wale nō"
|
||||
|
||||
@@ -321,7 +321,6 @@
|
||||
"horizon.osk.close": "סגור",
|
||||
"horizon.extras.chat_animation.tooltip": "קו צ'אט חדש גולש פנימה משמאל בזמן שהוא נמוג פנימה, במקום להיראות שלם.",
|
||||
"horizon.extras.chat_indicator.tooltip": "הסרגל הצבעוני בצד שמאל של קו צ'אט.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "גלגל הגלילה מעל כפתור בהגדרות משנה את ערכו, כמו בוניל. כבוי, הגלגל רק גולל את הרשימה.",
|
||||
"horizon.extras.pack_browser.tooltip": "Select Resource Packs מקבל כפתור הוסף שמחפש את Modrinth ומתקין חבילה בתיקיית החבילות שלך. כבוי, המשחק אף פעם לא יוצר קשר עם Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "בעולם, בקר אחר יכול להצטרף כשחקן חדש: הקש A כאשר הוא שואל, ואז בחר פרופיל ואיפה לשחק.",
|
||||
"horizon.splitscreen.setup.monitor": "צג %s",
|
||||
@@ -465,8 +464,8 @@
|
||||
"horizon.extras.cosmetics_mine": "הקוסמטיקה שלי",
|
||||
"horizon.extras.cosmetics_others": "מוצרי קוסמטיקה של שחקנים אחרים",
|
||||
"horizon.extras.cosmetics_others.tooltip": "מה שחקני Horizon אחרים לובשים: הכל, רק סגנונות תג השם שלהם, או כלום. נסתר, המשחק אף פעם לא שואל מה הם לובשים.",
|
||||
"horizon.extras.cosmetics_auras": "ההילות והשבילים של אחרים",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "הילות ושבילים ששחקנים אחרים לובשים. כבוי כברירת מחדל.",
|
||||
"horizon.extras.cosmetics_hide_fx": "הסתרת שבילים והילות של שחקנים אחרים",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "השבילים וההילות של שחקנים אחרים נשארים מוסתרים; שאר הקוסמטיקה שלהם עדיין מוצגת. כבוי כברירת מחדל.",
|
||||
"horizon.extras.cosmetics.shown": "מוצג",
|
||||
"horizon.extras.cosmetics.hidden": "מוסתר",
|
||||
"horizon.extras.cosmetics.name_tags": "תגי שם בלבד",
|
||||
|
||||
@@ -312,7 +312,6 @@
|
||||
"horizon.osk.close": "बंद करें",
|
||||
"horizon.extras.chat_animation.tooltip": "एक नई चैट लाइन पूरी तरह से प्रकट होने के बजाय फीका पड़ने के दौरान बाईं ओर से स्लाइड करती है।",
|
||||
"horizon.extras.chat_indicator.tooltip": "चैट लाइन के बाईं ओर रंगीन पट्टी।",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "सेटिंग्स में किसी बटन पर स्क्रॉल व्हील उसका मान बदलता है, जैसा कि वैनिला में होता है। बंद होने पर, व्हील केवल सूची को स्क्रॉल करता है।",
|
||||
"horizon.extras.splitscreen.tooltip": "एक दुनिया में, एक अन्य कंट्रोलर नए खिलाड़ी के रूप में जुड़ सकता है: पूछने पर A दबाएं, फिर एक प्रोफ़ाइल चुनें और कहाँ खेलना है।",
|
||||
"horizon.splitscreen.setup.monitor": "मॉनिटर %s",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "एक मॉनिटर",
|
||||
|
||||
@@ -306,7 +306,6 @@
|
||||
"horizon.osk.close": "Zatvori",
|
||||
"horizon.extras.chat_animation.tooltip": "Nova linija chata klizi s lijeve strane dok blijedi, umjesto da se pojavi cijela odjednom.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Traka u boji s lijeve strane linije chata.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Kotačić miša preko gumba u postavkama mijenja njegovu vrijednost, kao u vanila verziji. Kada je isključeno, kotačić samo pomiče popis.",
|
||||
"horizon.extras.splitscreen.tooltip": "U svijetu se drugi upravljač može pridružiti kao novi igrač: pritisnite A kada se zatraži, zatim odaberite profil i mjesto igranja.",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "monitor",
|
||||
"horizon.splitscreen.setup.window": "Prozor %s",
|
||||
|
||||
@@ -316,7 +316,6 @@
|
||||
"horizon.osk.close": "Bezárás",
|
||||
"horizon.extras.chat_animation.tooltip": "Egy új csevegősor balról csúszik be, miközben elhalványul, ahelyett, hogy egészben jelenne meg.",
|
||||
"horizon.extras.chat_indicator.tooltip": "A színes sáv a csevegősor bal oldalán.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "A beállításokban a gomb feletti görgő megváltoztatja annak értékét, mint a vanília esetében. Kikapcsolva a kerék csak a listát görgeti.",
|
||||
"horizon.extras.pack_browser.tooltip": "Az Erőforrás-csomagok kiválasztásakor megjelenik egy Hozzáadás gomb, amely megkeresi a Modrinth-t, és telepít egy csomagot a csomagok mappájába. Ki, a játék soha nem lép kapcsolatba a Modrinth-szal.",
|
||||
"horizon.extras.splitscreen.tooltip": "Egy világban egy másik kontroller is csatlakozhat új játékosként: nyomja meg az A gombot, amikor kéri, majd válasszon profilt és hol játszani.",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "egy monitort",
|
||||
@@ -458,8 +457,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Rejtett: minden Horizon játékos lát téged a kozmetikumai nélkül, beleértve Önt is. Ugyanaz a kapcsoló, mint az indító gardróbjában.",
|
||||
"horizon.extras.cosmetics_others": "Más játékosok kozmetikumai",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Amit a többi Horizon játékos visel: mindent, csak a névcímkék stílusát, vagy semmit. Rejtett, a játék soha nem kérdezi, mit viselnek.",
|
||||
"horizon.extras.cosmetics_auras": "Mások aurái és nyomai",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Más játékosok által viselt aurák és nyomvonalak. Alapértelmezés szerint ki.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Más játékosok nyomainak és auráinak elrejtése",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Más játékosok nyomai és aurái rejtve maradnak; a többi kozmetikumuk továbbra is látszik. Alapértelmezetten ki.",
|
||||
"horizon.extras.cosmetics.shown": "Megjelenítve",
|
||||
"horizon.extras.cosmetics.hidden": "Rejtett",
|
||||
"horizon.extras.cosmetics.name_tags": "Csak névcímkék"
|
||||
|
||||
@@ -317,7 +317,6 @@
|
||||
"horizon.osk.close": "Փակել",
|
||||
"horizon.extras.chat_animation.tooltip": "Նոր զրույցի գիծը սահում է ձախից, մինչդեռ այն խամրում է, այլ ոչ թե ամբողջությամբ երևալու:",
|
||||
"horizon.extras.chat_indicator.tooltip": "Զրույցի գծի ձախ կողմում գտնվող գունավոր գիծը:",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Ոլորման անիվը կոճակի վրա կարգավորումներում փոխում է իր արժեքը, ինչպես վանիլինը: Անջատված, անիվը միայն ոլորում է ցուցակը:",
|
||||
"horizon.extras.pack_browser.tooltip": "Ընտրված ռեսուրսների փաթեթները ստանում են Ավելացնել կոճակ, որը որոնում է Modrinth-ը և տեղադրում փաթեթը ձեր փաթեթների թղթապանակում: Անջատված է, խաղը երբեք չի կապվում Մոդրինթի հետ:",
|
||||
"horizon.extras.splitscreen.tooltip": "Աշխարհում մեկ այլ կարգավորիչ կարող է միանալ որպես նոր խաղացող. սեղմել A-ն, երբ նա հարցնում է, այնուհետև ընտրել պրոֆիլ և որտեղ խաղալ:",
|
||||
"horizon.splitscreen.setup.monitor": "Մոնիտոր %s",
|
||||
@@ -464,8 +463,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Թաքնված. յուրաքանչյուր Horizon խաղացող ձեզ տեսնում է առանց ձեր կոսմետիկայի, այդ թվում՝ դուք: Նույն անջատիչը, ինչ գործարկիչի զգեստապահարան:",
|
||||
"horizon.extras.cosmetics_others": "Այլ խաղացողների կոսմետիկա",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Ինչ են հագնում մյուս Horizon խաղացողները. ամեն ինչ, միայն նրանց անվան պիտակի ոճերը, կամ ոչինչ: Թաքնված, խաղը երբեք չի հարցնում, թե ինչ են նրանք հագնում:",
|
||||
"horizon.extras.cosmetics_auras": "Ուրիշների աուրաներն ու արահետները",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Աուրաները և արահետները կրում են այլ խաղացողներ: Լռելյայն անջատված է:",
|
||||
"horizon.extras.cosmetics_hide_fx": "Թաքցնել այլ խաղացողների հետքերն ու աուրաները",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Այլ խաղացողների հետքերն ու աուրաները մնում են թաքնված. նրանց մյուս կոսմետիկան դեռ երևում է։ Լռելյայն անջատված է։",
|
||||
"horizon.extras.cosmetics.shown": "Ցուցադրված է",
|
||||
"horizon.extras.cosmetics.hidden": "Թաքնված",
|
||||
"horizon.extras.cosmetics.name_tags": "Միայն անունների պիտակներ"
|
||||
|
||||
@@ -300,7 +300,6 @@
|
||||
"horizon.osk.close": "Tutup",
|
||||
"horizon.extras.chat_animation.tooltip": "Baris obrolan baru meluncur dari kiri sambil memudar, alih-alih langsung muncul seutuhnya.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Bilah berwarna di sebelah kiri baris obrolan.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Roda gulir di atas tombol pengaturan akan mengubah nilainya, seperti pada versi vanilla. Jika nonaktif, roda gulir hanya menggulir daftar.",
|
||||
"horizon.extras.splitscreen.tooltip": "Di dalam dunia, kontroler lain dapat bergabung sebagai pemain baru: tekan A saat diminta, lalu pilih profil dan tempat bermain.",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "sebuah monitor",
|
||||
"horizon.splitscreen.setup.window": "Jendela %s",
|
||||
|
||||
@@ -314,7 +314,6 @@
|
||||
"horizon.osk.close": "Mechie",
|
||||
"horizon.extras.chat_animation.tooltip": "Ahịrị nkata ọhụrụ na-esi n'aka ekpe bata ka ọ na-adaba, kama ịpụta zuru oke.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Ogwe nwere agba dị n'aka ekpe nke ahịrị nkata.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Mpempe akwụkwọ mpịakọta n'elu bọtịnụ dị na ntọala na-agbanwe uru ya, dịka ọ dị na vanilla. Gbanyụọ, wiil ahụ na-atụgharị naanị ndepụta ahụ.",
|
||||
"horizon.extras.pack_browser.tooltip": "Họrọ Ngwungwu akụrụngwa na-enweta bọtịnụ Tinye nke na-achọ Modrinth ma tinye otu mkpọ n'ime folda mkpọ gị. Gbanyụọ, egwuregwu anaghị akpọtụrụ Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "N'ime ụwa, onye njikwa ọzọ nwere ike isonyere dị ka onye ọkpụkpọ ọhụrụ: pịa A mgbe ọ jụrụ, wee họrọ profaịlụ na ebe ị ga-egwu.",
|
||||
"horizon.splitscreen.setup.monitor": "Nyochaa %s",
|
||||
@@ -460,8 +459,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Zoro ezo: Onye ọkpụkpọ ọ bụla Horizon na-ahụ gị na-enweghị ihe ịchọ mma gị, ị gụnyere. Otu mgbanwe dị ka Wardrobe nke onye mbido.",
|
||||
"horizon.extras.cosmetics_others": "Ngwa mma ndị egwuregwu ndị ọzọ",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Ihe ndị ọzọ Horizon Player na-eyi: ihe niile, naanị aha mkpado styles, ma ọ bụ ihe ọ bụla. Na nzuzo, egwuregwu anaghị ajụ ihe ha na-eyi.",
|
||||
"horizon.extras.cosmetics_auras": "Auras na ụzọ ndị ọzọ",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auras na ụzọ ndị egwuregwu ndị ọzọ na-eyi. Gbanyụọ na ndabara.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Zoo akara ụkwụ na aura ndị egwuregwu ndị ọzọ",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Akara ụkwụ na aura ndị egwuregwu ndị ọzọ na-anọgide zoro ezo; ihe ịchọ mma ha ndị ọzọ ka na-egosi. Gbanyụrụ na ndabara.",
|
||||
"horizon.extras.cosmetics.shown": "Egosiri",
|
||||
"horizon.extras.cosmetics.hidden": "Zoro ezo",
|
||||
"horizon.extras.cosmetics.name_tags": "Naanị mkpado aha"
|
||||
|
||||
@@ -320,7 +320,6 @@
|
||||
"horizon.osk.close": "Loka",
|
||||
"horizon.extras.chat_animation.tooltip": "Ný spjalllína rennur inn frá vinstri á meðan hún dofnar inn, í stað þess að virðast heil.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Litaða stikan vinstra megin á spjalllínu.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Skrunahjólið yfir hnapp í stillingunum breytir gildi þess, eins og í vanillu. Slökkt, hjólið flettir aðeins listann.",
|
||||
"horizon.extras.pack_browser.tooltip": "Select Resource Packs fær hnappinn Bæta við sem leitar í Modrinth og setur upp pakka í pakkamöppuna þína. Slökkt, leikurinn hefur aldrei samband við Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "Í heimi getur annar stjórnandi tekið þátt sem nýr leikmaður: ýttu á A þegar hann spyr, veldu síðan prófíl og hvar á að spila.",
|
||||
"horizon.splitscreen.setup.monitor": "Fylgjast með %s",
|
||||
@@ -465,8 +464,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Falinn: sérhver Horizon leikmaður sér þig án snyrtivörunnar þinnar, þú meðtalin. Sami rofi og fataskápur sjósetjunnar.",
|
||||
"horizon.extras.cosmetics_others": "Snyrtivörur annarra leikmanna",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Það sem aðrir Horizon-spilarar klæðast: allt, aðeins nafnmerkisstíllinn eða ekkert. Falinn, leikurinn spyr aldrei hverju þeir klæðist.",
|
||||
"horizon.extras.cosmetics_auras": "Aurar og slóðir annarra",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auras og slóðir sem aðrir leikmenn klæðast. Slökkt sjálfgefið.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Fela slóðir og árur annarra leikmanna",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Slóðir og árur annarra leikmanna eru faldar; aðrir skrautmunir þeirra sjást áfram. Slökkt sjálfgefið.",
|
||||
"horizon.extras.cosmetics.shown": "Sýnd",
|
||||
"horizon.extras.cosmetics.hidden": "Falinn",
|
||||
"horizon.extras.cosmetics.name_tags": "Eingöngu nafnamerki",
|
||||
|
||||
@@ -331,7 +331,6 @@
|
||||
"horizon.osk.close": "Chiudi",
|
||||
"horizon.extras.chat_animation.tooltip": "Una nuova linea di chat scorre da sinistra mentre si dissolve, invece di apparire intera.",
|
||||
"horizon.extras.chat_indicator.tooltip": "La barra colorata a sinistra di una chat line.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "La rotella di scorrimento sopra un pulsante nelle impostazioni ne modifica il valore, come in Vanilla. Spento, la rotella scorre solo l'elenco.",
|
||||
"horizon.extras.pack_browser.tooltip": "Seleziona pacchetti di risorse ottiene un pulsante Aggiungi che cerca Modrinth e installa un pacchetto nella cartella dei pacchetti. Spento, il gioco non contatta mai Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "In un mondo, un altro controller può unirsi come nuovo giocatore: premi A quando richiesto, quindi scegli un profilo e dove giocare.",
|
||||
"horizon.splitscreen.setup.monitor": "Monitorare %s",
|
||||
@@ -480,8 +479,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Nascosto: ogni giocatore di Horizon ti vede senza i tuoi cosmetici, incluso te. Lo stesso interruttore dell'armadio del lanciatore.",
|
||||
"horizon.extras.cosmetics_others": "Cosmetici di altri giocatori",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Cosa indossano gli altri giocatori di Horizon: tutto, solo lo stile delle etichette con il nome o niente. Nascosto, il gioco non chiede mai cosa indossano.",
|
||||
"horizon.extras.cosmetics_auras": "Aure e tracce degli altri",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Aure e tracce indossate da altri giocatori. Disattivato per impostazione predefinita.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Nascondi scie e aure degli altri giocatori",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Le scie e le aure degli altri giocatori restano nascoste; gli altri loro cosmetici restano visibili. Disattivato per impostazione predefinita.",
|
||||
"horizon.extras.cosmetics.shown": "Mostrato",
|
||||
"horizon.extras.cosmetics.hidden": "Nascosto",
|
||||
"horizon.extras.cosmetics.name_tags": "Solo targhette con nome"
|
||||
|
||||
@@ -334,7 +334,6 @@
|
||||
"horizon.osk.close": "閉じる",
|
||||
"horizon.extras.chat_animation.tooltip": "新しいチャット ラインは全体として表示されるのではなく、フェードインしながら左側からスライドして表示されます。",
|
||||
"horizon.extras.chat_indicator.tooltip": "チャットラインの左側にある色付きのバー。",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "バニラと同様に、設定内のボタン上でホイールをスクロールすると、その値が変更されます。オフの場合、ホイールはリストのみをスクロールします。",
|
||||
"horizon.extras.pack_browser.tooltip": "[リソース パック] を選択すると、Modrinth を検索してパック フォルダーにパックをインストールする [追加] ボタンが表示されます。オフ、ゲームは決してモドリンスに接触しません。",
|
||||
"horizon.extras.splitscreen.tooltip": "ワールドでは、別のコントローラーが新しいプレーヤーとして参加できます。メッセージが表示されたら A を押し、プロフィールとプレイする場所を選択します。",
|
||||
"horizon.splitscreen.setup.monitor": "モニター%s",
|
||||
@@ -487,8 +486,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "非表示: すべての Horizon プレイヤーは、あなたも含め、化粧品を着用していないあなたを見ます。ランチャーのワードローブと同じスイッチ。",
|
||||
"horizon.extras.cosmetics_others": "他のプレイヤーの化粧品",
|
||||
"horizon.extras.cosmetics_others.tooltip": "他の Horizon プレイヤーが着ているもの: すべて、名札のスタイルのみ、または何も着ていません。隠されていますが、ゲームでは彼らが何を着ているかは尋ねられません。",
|
||||
"horizon.extras.cosmetics_auras": "他人のオーラと軌跡",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "他のプレイヤーが身に着けているオーラと軌跡。デフォルトではオフです。",
|
||||
"horizon.extras.cosmetics_hide_fx": "他のプレイヤーの軌跡とオーラを隠す",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "他のプレイヤーの軌跡とオーラを非表示にします。それ以外のコスメティックは表示されたままです。デフォルトはオフ。",
|
||||
"horizon.extras.cosmetics.shown": "示されている",
|
||||
"horizon.extras.cosmetics.hidden": "隠された",
|
||||
"horizon.extras.cosmetics.name_tags": "ネームタグのみ"
|
||||
|
||||
@@ -324,7 +324,6 @@
|
||||
"horizon.osk.close": "დახურვა",
|
||||
"horizon.extras.chat_animation.tooltip": "ჩეთის ახალი ხაზი შემოდის მარცხნიდან, სანამ ის ქრება, ნაცვლად იმისა, რომ მთლიანად გამოჩნდეს.",
|
||||
"horizon.extras.chat_indicator.tooltip": "ფერადი ზოლი ჩატის ხაზის მარცხნივ.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "ღილაკზე გადახვევის ბორბალი პარამეტრებში ცვლის მის მნიშვნელობას, როგორც ვანილის შემთხვევაში. გამორთულია, საჭე მხოლოდ გადახვევს სიას.",
|
||||
"horizon.extras.pack_browser.tooltip": "რესურსების პაკეტების არჩევა მიიღებს დამატებას ღილაკს, რომელიც ეძებს Modrinth-ს და დააინსტალირებს პაკეტს თქვენს პაკეტების საქაღალდეში. გამორთულია, თამაში არასოდეს დაუკავშირდება მოდრინთს.",
|
||||
"horizon.extras.splitscreen.tooltip": "მსოფლიოში, სხვა კონტროლერს შეუძლია შეუერთდეს ახალ მოთამაშეს: დააჭირე A-ს, როცა ის ითხოვს, შემდეგ აირჩიე პროფილი და სად უნდა ითამაშოს.",
|
||||
"horizon.splitscreen.setup.monitor": "მონიტორი %s",
|
||||
@@ -472,8 +471,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "დამალული: ყოველი Horizon მოთამაშე გხედავთ თქვენი კოსმეტიკის გარეშე, თქვენ ჩათვლით. იგივე გადამრთველი, როგორც გამშვების გარდერობი.",
|
||||
"horizon.extras.cosmetics_others": "სხვა მოთამაშეების კოსმეტიკა",
|
||||
"horizon.extras.cosmetics_others.tooltip": "რას აცვია სხვა Horizon მოთამაშეები: ყველაფერი, მხოლოდ მათი სახელების სტილები, ან არაფერი. დამალული, თამაში არასდროს ეკითხება, რა აცვიათ.",
|
||||
"horizon.extras.cosmetics_auras": "სხვების აურა და ბილიკები",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "აურები და ბილიკები ატარებენ სხვა მოთამაშეებს. ნაგულისხმევად გამორთულია.",
|
||||
"horizon.extras.cosmetics_hide_fx": "სხვა მოთამაშეების კვალისა და აურების დამალვა",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "სხვა მოთამაშეების კვალი და აურები დამალული რჩება; მათი სხვა კოსმეტიკა კვლავ ჩანს. ნაგულისხმევად გამორთულია.",
|
||||
"horizon.extras.cosmetics.shown": "ნაჩვენებია",
|
||||
"horizon.extras.cosmetics.hidden": "დამალული",
|
||||
"horizon.extras.cosmetics.name_tags": "მხოლოდ სახელების ტეგები"
|
||||
|
||||
@@ -325,7 +325,6 @@
|
||||
"horizon.osk.close": "Жабу",
|
||||
"horizon.extras.chat_animation.tooltip": "Жаңа сөйлесу сызығы тұтас болып көрінудің орнына, өшіп жатқанда, сол жақтан сырғытады.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Сөйлесу жолының сол жағындағы түсті жолақ.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Параметрлердегі түйменің үстіндегі айналдыру дөңгелегі ванильдегі сияқты оның мәнін өзгертеді. Өшірулі, дөңгелек тек тізімді айналдырады.",
|
||||
"horizon.extras.pack_browser.tooltip": "Ресурс бумаларын таңдау Modrinth-ті іздейтін және буманы бумалар қалтасына орнататын Қосу түймешігін алады. Өшірулі, ойын ешқашан Модринтпен байланыспайды.",
|
||||
"horizon.extras.splitscreen.tooltip": "Әлемде басқа контроллер жаңа ойыншы ретінде қосыла алады: ол сұрағанда A түймесін басыңыз, содан кейін профильді және ойнайтын орынды таңдаңыз.",
|
||||
"horizon.splitscreen.setup.monitor": "Монитор %s",
|
||||
@@ -474,8 +473,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Жасырын: әрбір Horizon ойыншы сізді косметикасыз көреді, сіз қосасыз. Іске қосу құрылғысының гардеробымен бірдей қосқыш.",
|
||||
"horizon.extras.cosmetics_others": "Басқа ойыншылардың косметикасы",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Басқа Horizon ойыншылары не киеді: барлығы, тек олардың атау тегінің стильдері немесе ештеңе жоқ. Жасырын, ойын ешқашан не киетінін сұрамайды.",
|
||||
"horizon.extras.cosmetics_auras": "Басқалардың ауралары мен жолдары",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Басқа ойыншылар киетін ауралар мен жолдар. Әдепкі бойынша өшірулі.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Басқа ойыншылардың іздері мен аураларын жасыру",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Басқа ойыншылардың іздері мен ауралары жасырын қалады; олардың басқа косметикасы көрінеді. Әдепкі бойынша өшірулі.",
|
||||
"horizon.extras.cosmetics.shown": "Көрсетілген",
|
||||
"horizon.extras.cosmetics.hidden": "Жасырын",
|
||||
"horizon.extras.cosmetics.name_tags": "Тек атау тегтері"
|
||||
|
||||
@@ -331,7 +331,6 @@
|
||||
"horizon.osk.close": "ಮುಚ್ಚಿ",
|
||||
"horizon.extras.chat_animation.tooltip": "ಹೊಸ ಚಾಟ್ ಲೈನ್ ಸಂಪೂರ್ಣವಾಗಿ ಕಾಣಿಸಿಕೊಳ್ಳುವ ಬದಲು ಮಸುಕಾಗುವಾಗ ಎಡದಿಂದ ಸ್ಲೈಡ್ ಆಗುತ್ತದೆ.",
|
||||
"horizon.extras.chat_indicator.tooltip": "ಚಾಟ್ ಲೈನ್ನ ಎಡಭಾಗದಲ್ಲಿ ಬಣ್ಣದ ಪಟ್ಟಿ.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿನ ಬಟನ್ನ ಮೇಲಿನ ಸ್ಕ್ರಾಲ್ ಚಕ್ರವು ವೆನಿಲ್ಲಾದಂತೆ ಅದರ ಮೌಲ್ಯವನ್ನು ಬದಲಾಯಿಸುತ್ತದೆ. ಆಫ್, ಚಕ್ರವು ಪಟ್ಟಿಯನ್ನು ಮಾತ್ರ ಸ್ಕ್ರಾಲ್ ಮಾಡುತ್ತದೆ.",
|
||||
"horizon.extras.pack_browser.tooltip": "ಸಂಪನ್ಮೂಲ ಪ್ಯಾಕ್ಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ ಆಡ್ ಬಟನ್ ಅನ್ನು ಪಡೆಯುತ್ತದೆ ಅದು Modrinth ಅನ್ನು ಹುಡುಕುತ್ತದೆ ಮತ್ತು ನಿಮ್ಮ ಪ್ಯಾಕ್ಗಳ ಫೋಲ್ಡರ್ಗೆ ಪ್ಯಾಕ್ ಅನ್ನು ಸ್ಥಾಪಿಸುತ್ತದೆ. ಆಫ್, ಆಟವು Modrinth ಅನ್ನು ಎಂದಿಗೂ ಸಂಪರ್ಕಿಸುವುದಿಲ್ಲ.",
|
||||
"horizon.extras.splitscreen.tooltip": "ಜಗತ್ತಿನಲ್ಲಿ, ಮತ್ತೊಂದು ನಿಯಂತ್ರಕವು ಹೊಸ ಆಟಗಾರನಾಗಿ ಸೇರಿಕೊಳ್ಳಬಹುದು: ಅದು ಕೇಳಿದಾಗ A ಅನ್ನು ಒತ್ತಿ, ನಂತರ ಪ್ರೊಫೈಲ್ ಅನ್ನು ಮತ್ತು ಎಲ್ಲಿ ಪ್ಲೇ ಮಾಡಬೇಕೆಂದು ಆಯ್ಕೆಮಾಡಿ.",
|
||||
"horizon.splitscreen.setup.monitor": "ಮಾನಿಟರ್ %s",
|
||||
@@ -481,8 +480,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "ಮರೆಮಾಡಲಾಗಿದೆ: ಪ್ರತಿ Horizon ಪ್ಲೇಯರ್ ನಿಮ್ಮ ಸೌಂದರ್ಯವರ್ಧಕಗಳಿಲ್ಲದೆ ನಿಮ್ಮನ್ನು ನೋಡುತ್ತಾರೆ, ನೀವು ಸೇರಿಸಿದ್ದೀರಿ. ಲಾಂಚರ್ನ ವಾರ್ಡ್ರೋಬ್ನಂತೆಯೇ ಅದೇ ಸ್ವಿಚ್.",
|
||||
"horizon.extras.cosmetics_others": "ಇತರ ಆಟಗಾರರ ಸೌಂದರ್ಯವರ್ಧಕಗಳು",
|
||||
"horizon.extras.cosmetics_others.tooltip": "ಇತರ Horizon ಆಟಗಾರರು ಏನು ಧರಿಸುತ್ತಾರೆ: ಎಲ್ಲವೂ, ಅವರ ಹೆಸರಿನ ಟ್ಯಾಗ್ ಶೈಲಿಗಳು ಅಥವಾ ಏನೂ ಇಲ್ಲ. ಮರೆಮಾಡಲಾಗಿದೆ, ಆಟವು ಅವರು ಏನು ಧರಿಸುತ್ತಾರೆ ಎಂದು ಎಂದಿಗೂ ಕೇಳುವುದಿಲ್ಲ.",
|
||||
"horizon.extras.cosmetics_auras": "ಇತರರ ಸೆಳವು ಮತ್ತು ಹಾದಿಗಳು",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "ಔರಾಸ್ ಮತ್ತು ಟ್ರೇಲ್ಸ್ ಇತರ ಆಟಗಾರರು ಧರಿಸುತ್ತಾರೆ. ಡೀಫಾಲ್ಟ್ ಆಗಿ ಆಫ್.",
|
||||
"horizon.extras.cosmetics_hide_fx": "ಇತರ ಆಟಗಾರರ ಹಾದಿಗಳು ಮತ್ತು ಪ್ರಭಾವಳಿಗಳನ್ನು ಮರೆಮಾಡಿ",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "ಇತರ ಆಟಗಾರರ ಹಾದಿಗಳು ಮತ್ತು ಪ್ರಭಾವಳಿಗಳು ಮರೆಯಾಗಿರುತ್ತವೆ; ಅವರ ಇತರ ಅಲಂಕಾರಗಳು ಕಾಣುತ್ತವೆ. ಪೂರ್ವನಿಯೋಜಿತವಾಗಿ ಆಫ್.",
|
||||
"horizon.extras.cosmetics.shown": "ತೋರಿಸಲಾಗಿದೆ",
|
||||
"horizon.extras.cosmetics.hidden": "ಮರೆಮಾಡಲಾಗಿದೆ",
|
||||
"horizon.extras.cosmetics.name_tags": "ಹೆಸರು ಟ್ಯಾಗ್ಗಳು ಮಾತ್ರ"
|
||||
|
||||
@@ -308,7 +308,6 @@
|
||||
"horizon.osk.close": "닫기",
|
||||
"horizon.extras.chat_animation.tooltip": "새 채팅 줄이 통째로 나타나는 대신 왼쪽에서 미끄러지듯 나타나며 서서히 페이드 인됩니다.",
|
||||
"horizon.extras.chat_indicator.tooltip": "채팅 줄 왼쪽에 있는 색상 바입니다.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "설정에서 버튼 위에 스크롤 휠을 올리면 바닐라처럼 값이 변경됩니다. 끄면 휠이 목록 스크롤만 수행합니다.",
|
||||
"horizon.extras.splitscreen.tooltip": "월드 내에서 다른 컨트롤러로 새 플레이어로 참여할 수 있습니다: 메시지가 나타날 때 A를 누르고 프로필과 플레이할 위치를 선택하세요.",
|
||||
"horizon.splitscreen.setup.monitor": "모니터 %s",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "모니터",
|
||||
|
||||
@@ -301,7 +301,6 @@
|
||||
"horizon.osk.close": "Жабуу",
|
||||
"horizon.extras.chat_animation.tooltip": "Жаңы чат сызыгы сол жактан жылдырып, ал толугу менен көрүнбөй, өчүп калат.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Чат сызыгынын сол жагындагы түстүү тилке.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Жөндөөлөрдөгү баскычтын үстүнөн жылдыргыч дөңгөлөк ванильдегидей анын маанисин өзгөртөт. Өчүк, дөңгөлөк тизмени гана жылдырат.",
|
||||
"horizon.extras.pack_browser.tooltip": "Ресурстук топтомдорду тандоо Modrinth издеген Кошуу баскычын алат жана пакеттер папкаңызга пакетти орнотот. Өчүк, оюн эч качан Modrinth менен байланышпайт.",
|
||||
"horizon.extras.splitscreen.tooltip": "Дүйнөдө башка контроллер жаңы оюнчу катары кошула алат: ал сураганда A баскычын басыңыз, андан кийин профилди жана кайда ойноону тандаңыз.",
|
||||
"horizon.splitscreen.setup.monitor": "Монитор %s",
|
||||
@@ -448,8 +447,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Жашырылган: ар бир Horizon оюнчу сизди косметикасыз көрөт. Ишке киргизгичтин гардеробу сыяктуу эле которгуч.",
|
||||
"horizon.extras.cosmetics_others": "Башка оюнчулардын косметикасы",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Башка Horizon оюнчулары кандай кийимдерди кийишет: бардыгы, алардын аты тегинин стили гана же эч нерсе. Жашыруун, оюн эч качан алар эмне кийип жүргөнүн сурабайт.",
|
||||
"horizon.extras.cosmetics_auras": "Башкалардын ауралары жана жолдору",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Ауралар жана башка оюнчулар кийген жолдор. Демейки боюнча өчүк.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Башка оюнчулардын издерин жана ауралары жашыруу",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Башка оюнчулардын издери жана ауралары жашыруун калат; алардын башка косметикасы көрүнөт. Демейки боюнча өчүк.",
|
||||
"horizon.extras.cosmetics.shown": "Көрсөтүлдү",
|
||||
"horizon.extras.cosmetics.hidden": "Жашырылган",
|
||||
"horizon.extras.cosmetics.name_tags": "Аталышы гана"
|
||||
|
||||
@@ -264,7 +264,6 @@
|
||||
"horizon.osk.symbols": "Symbola",
|
||||
"horizon.extras.chat_animation.tooltip": "Novum chat linea labitur in a sinistra, dum recedit in, non apparere totum.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Talea colorata ad sinistram lineae chat.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Volubilis rota super bullam in uncinis mutat valorem suum, ut in vanilla. Abi, rota tantum volumina ponit.",
|
||||
"horizon.extras.pack_browser.tooltip": "Lego Resource Sarcinis tesseram additam accipit quae 0⟧ scrutatur et sarcinam in folder sarcinis condit. Abi, ludum numquam contactus Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "In mundo, alius moderatoris ut novus lusor iungere potest: instare A cum quaerit, deinde figuram carpe et ubi ludere.",
|
||||
"horizon.splitscreen.setup.monitor": "Monitor%s",
|
||||
@@ -399,8 +398,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Occultus: omnisHorizonlusor te sine tuis fucis videt, te comprehendit. Idem transitum cum vestiario launcher.",
|
||||
"horizon.extras.cosmetics_others": "Alii histriones medicamentis",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Quid aliud?Horizonhistriones gerunt: omnia, solum nomen tag stili, vel nihil. Occultus, ludus numquam quaerit quod gerunt.",
|
||||
"horizon.extras.cosmetics_auras": "Aliorum auras et calles",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auras et calles alii lusores gerunt. Off per defaltam.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Vestigia et auras aliorum lusorum celare",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Vestigia et aurae aliorum lusorum celata manent; cetera ornamenta eorum adhuc videntur. Ex defalta exstinctum.",
|
||||
"horizon.extras.cosmetics.shown": "Ostensum",
|
||||
"horizon.extras.cosmetics.hidden": "Occultus",
|
||||
"horizon.extras.cosmetics.name_tags": "Nomen tags tantum"
|
||||
|
||||
@@ -293,7 +293,6 @@
|
||||
"horizon.osk.close": "Zoumaachen",
|
||||
"horizon.extras.chat_animation.tooltip": "Eng nei Chat-Linn rutscht vu lénks eran, während se verschwënnt, amplaz ganz ze erschéngen.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Déi faarweg Bar op der lénker Säit vun enger Chat Linn.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "De Scrollrad iwwer e Knäppchen an den Astellungen ännert säi Wäert, wéi an Vanille. Off, d'Rad scrollt nëmmen d'Lëscht.",
|
||||
"horizon.extras.pack_browser.tooltip": "Wielt Ressource Packs kritt en Add Button deen Modrinth sicht an e Pack an Äre Packs Dossier installéiert. Off, d'Spill kontaktéiert ni Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "An enger Welt kann en anere Controller als neie Spiller matmaachen: dréckt A wann et freet, wielt dann e Profil a wou Dir spillt.",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "engem Monitor",
|
||||
@@ -435,8 +434,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Verstoppt: all Horizon Spiller gesäit Iech ouni Är Kosmetik, Dir abegraff. Deeselwechte Schalter wéi de Launcher Kleederschaf.",
|
||||
"horizon.extras.cosmetics_others": "Aner Spiller Kosmetik",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Wat aner Horizon Spiller droen: alles, nëmmen hir Numm Tag Stiler, oder näischt. Verstoppt, d'Spill freet ni wat se droen.",
|
||||
"horizon.extras.cosmetics_auras": "Anerer hir Auraen an Trëppelweeër",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auras an Trëppelweeër aner Spiller zouzedrécken. Off par défaut.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Spueren an Auraen vun anere Spiller verstoppen",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Spueren an Auraen vun anere Spiller bleiwe verstoppt; hir aner Kosmetik ass ëmmer nach ze gesinn. Standardméisseg aus.",
|
||||
"horizon.extras.cosmetics.shown": "gewisen",
|
||||
"horizon.extras.cosmetics.hidden": "Verstoppt",
|
||||
"horizon.extras.cosmetics.name_tags": "Numm Tags nëmmen"
|
||||
|
||||
@@ -291,7 +291,6 @@
|
||||
"horizon.osk.close": "Sluite",
|
||||
"horizon.extras.chat_animation.tooltip": "‘n Nuie chatlijn glijdt vaanaaf links in onderwieles dat ‘t in vervaag, in plaats vaan gans te versjiene.",
|
||||
"horizon.extras.chat_indicator.tooltip": "De gekleurde balk links vaan ‘n chatlijn.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "‘t Scrollwiel euver ‘n knop in de insjtellinge verandert de waarde, wie in vanille. Oet, ‘t wiel scrollt allein de lies.",
|
||||
"horizon.extras.pack_browser.tooltip": "Selecteer Resource Packs kriegste ‘n Add-knop dee Modrinth zeuk en ‘n pack in de packs-map installeert. Oet, ‘t spel contacteert noets Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "In ‘n wereld kin ‘n aandere controller bijtrejje es ‘n nuie speuler: druk op A es ‘t vraog, kies daonao ‘n profiel en boe te speule.",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "un monitor",
|
||||
@@ -434,8 +433,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Verborge: eder Horizon speuler ziet dich zoonder dien cosmetica, inclusief dich. Dezelfde schakelaar es de garderobe vaan de launcher.",
|
||||
"horizon.extras.cosmetics_others": "Cosmetica vaan aandere speulers",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Wat aandere Horizon speulers drage: alles, allein hun stijl vaan naamlabel, of niks. Verborge, vraog ‘t spel noets wat ze drage.",
|
||||
"horizon.extras.cosmetics_auras": "Auras en spore vaan aandere",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auras en spore die aandere speulers drage. Standaard oet.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Spore en aura's van anger spelers verberge",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Spore en aura's van anger spelers blieve verborge; hun anger cosmetica blieve zichbaar. Standaard oet.",
|
||||
"horizon.extras.cosmetics.shown": "Getoend",
|
||||
"horizon.extras.cosmetics.hidden": "Verborge",
|
||||
"horizon.extras.cosmetics.name_tags": "Allein naamtags"
|
||||
|
||||
@@ -321,7 +321,6 @@
|
||||
"horizon.osk.close": "Serra",
|
||||
"horizon.extras.chat_animation.tooltip": "Una nœuva linea de chat scivola denter de sinistra menter la sbiadiss, inveci de cumparir intera.",
|
||||
"horizon.extras.chat_indicator.tooltip": "La barra culurada a sinistra de una linea de chat.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "La rœuda de scorriment sü un buttun in di impostaziun cambia el sò valur, cuma in de la vaniglia. Disattiva, la rœuda scorre sultant la lista.",
|
||||
"horizon.extras.pack_browser.tooltip": "Seleziuna Pachett de risurs g’he un buttun Aggiungi che cerca Modrinth e instala un pachett in de la cartella di pachett. Off, el giœugh se cuntat mai Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "En un mund, un olter cuntroladur pœu entrà denter cuma nœuf giucadur: premet A quand che dumanda, e dopu seleziuna un profil e indué giugar.",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "un monitor",
|
||||
@@ -474,5 +473,5 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Ascüs: tüt i giucadur Horizon te veden senza i tò cosmetich, anca tì. L’istess interruttur del guardaroba del lanciador.",
|
||||
"horizon.extras.cosmetics_others": "Cosmetich di olter giucadur",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Che ròbb che purtan sü i olter giucadur Horizon: tüt, sultant i luur stil de etichett o nient. Ascüs, el giœugh dumanda mai cus’è che g’han sü.",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auras and trails other players wear. Disattiva per predefiniziun."
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "I strasc e i aur di olter giugadur resten scondu; i sò olter cosmetegh se veden anmò. Smorzaa de default."
|
||||
}
|
||||
@@ -320,7 +320,6 @@
|
||||
"horizon.osk.close": "ປິດ",
|
||||
"horizon.extras.chat_animation.tooltip": "ແຖວສົນທະນາໃໝ່ເລື່ອນເຂົ້າມາຈາກຊ້າຍໃນຂະນະທີ່ມັນຈາງລົງ, ແທນທີ່ຈະປາກົດທັງໝົດ.",
|
||||
"horizon.extras.chat_indicator.tooltip": "ແຖບສີຢູ່ເບື້ອງຊ້າຍຂອງສາຍສົນທະນາ.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "ລໍ້ເລື່ອນຜ່ານປຸ່ມໃດນຶ່ງໃນການຕັ້ງຄ່າຈະປ່ຽນຄ່າຂອງມັນ, ຄືກັບໃນ vanilla. ປິດ, ລໍ້ພຽງແຕ່ເລື່ອນບັນຊີລາຍຊື່.",
|
||||
"horizon.extras.pack_browser.tooltip": "ເລືອກ Resource Packs ໄດ້ຮັບປຸ່ມ Add ທີ່ຊອກຫາ Modrinth ແລະຕິດຕັ້ງຊຸດໃສ່ໃນໂຟນເດີ packs ຂອງທ່ານ. ປິດ, ເກມບໍ່ເຄີຍຕິດຕໍ່ Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "ໃນໂລກ, ຜູ້ຄວບຄຸມອື່ນສາມາດເຂົ້າຮ່ວມເປັນຜູ້ນໃຫມ່: ກົດ A ເມື່ອມັນຖາມ, ຫຼັງຈາກນັ້ນເລືອກໂປຣໄຟລ໌ແລະບ່ອນທີ່ຈະຫຼິ້ນ.",
|
||||
"horizon.splitscreen.setup.monitor": "ຈໍພາບ %s",
|
||||
@@ -464,8 +463,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "ເຊື່ອງໄວ້: ຜູ້ນ Horizon ທຸກຄົນເຫັນທ່ານໂດຍບໍ່ມີເຄື່ອງສໍາອາງຂອງທ່ານ, ທ່ານລວມ. ສະຫຼັບດຽວກັນກັບຕູ້ເສື້ອຜ້າຂອງຕົວເປີດ.",
|
||||
"horizon.extras.cosmetics_others": "ເຄື່ອງສໍາອາງຂອງຜູ້ນອື່ນໆ",
|
||||
"horizon.extras.cosmetics_others.tooltip": "ສິ່ງທີ່ຜູ້ຫຼິ້ນ Horizon ຄົນອື່ນໆໃສ່: ທຸກຢ່າງ, ພຽງແຕ່ຮູບແບບປ້າຍຊື່ຂອງເຂົາເຈົ້າ, ຫຼືບໍ່ມີຫຍັງ. ເຊື່ອງໄວ້, ເກມບໍ່ເຄີຍຖາມວ່າພວກເຂົາໃສ່ຫຍັງ.",
|
||||
"horizon.extras.cosmetics_auras": "ອໍຣາ ແລະເສັ້ນທາງຂອງຄົນອື່ນ",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Aura ແລະ trails ຜູ້ນອື່ນໆໃສ່. ປິດຕາມຄ່າເລີ່ມຕົ້ນ.",
|
||||
"horizon.extras.cosmetics_hide_fx": "ເຊື່ອງຮອຍ ແລະ ອໍຣາຂອງຜູ້ຫຼິ້ນອື່ນ",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "ຮອຍ ແລະ ອໍຣາຂອງຜູ້ຫຼິ້ນອື່ນຖືກເຊື່ອງໄວ້; ເຄື່ອງປະດັບອື່ນຂອງເຂົາເຈົ້າຍັງເຫັນຢູ່. ປິດໄວ້ຕາມຄ່າເລີ່ມຕົ້ນ.",
|
||||
"horizon.extras.cosmetics.shown": "ສະແດງໃຫ້ເຫັນ",
|
||||
"horizon.extras.cosmetics.hidden": "ເຊື່ອງໄວ້",
|
||||
"horizon.extras.cosmetics.name_tags": "ປ້າຍຊື່ເທົ່ານັ້ນ",
|
||||
|
||||
@@ -323,7 +323,6 @@
|
||||
"horizon.osk.close": "Uždaryti",
|
||||
"horizon.extras.chat_animation.tooltip": "Nauja pokalbio linija slenka iš kairės, kol ji išnyksta, o ne atrodo visa.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Spalvota juosta pokalbio linijos kairėje.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Slinkties ratukas virš mygtuko nustatymuose keičia jo reikšmę, kaip ir vanilės atveju. Išjungtas, ratas tik slenka sąrašu.",
|
||||
"horizon.extras.pack_browser.tooltip": "Pasirinkite Išteklių paketai gauna mygtuką Pridėti, kuris ieško Modrinth ir įdiegia paketą į jūsų paketų aplanką. Išjungtas, žaidimas niekada nesusisiekia su Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "Pasaulyje kitas valdiklis gali prisijungti kaip naujas žaidėjas: paspauskite A, kai jis paprašys, tada pasirinkite profilį ir kur žaisti.",
|
||||
"horizon.splitscreen.setup.monitor": "Monitorius %s",
|
||||
@@ -467,8 +466,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Paslėpta: kiekvienas „Horizon“ žaidėjas mato jus be jūsų kosmetikos, įskaitant jus. Tas pats jungiklis kaip ir paleidimo priemonės drabužių spinta.",
|
||||
"horizon.extras.cosmetics_others": "Kitų žaidėjų kosmetika",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Ką dėvi kiti „Horizon“ žaidėjai: viskas, tik jų vardų etiketės arba nieko. Paslėptas, žaidimas niekada neklausia, ką jie dėvi.",
|
||||
"horizon.extras.cosmetics_auras": "Kitų auros ir takai",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auros ir takai, kuriuos nešioja kiti žaidėjai. Išjungta pagal numatytuosius nustatymus.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Slėpti kitų žaidėjų pėdsakus ir auras",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Kitų žaidėjų pėdsakai ir auros lieka paslėpti; kita jų kosmetika vis dar matoma. Numatytai išjungta.",
|
||||
"horizon.extras.cosmetics.shown": "Parodyta",
|
||||
"horizon.extras.cosmetics.hidden": "Paslėpta",
|
||||
"horizon.extras.cosmetics.name_tags": "Tik vardų žymos"
|
||||
|
||||
@@ -313,7 +313,6 @@
|
||||
"horizon.osk.close": "Aizvērt",
|
||||
"horizon.extras.chat_animation.tooltip": "Jauna tērzēšanas līnija ieslīd no kreisās puses, kamēr tā pazūd, nevis tiek rādīta vesela.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Krāsaina josla pa kreisi no tērzēšanas līnijas.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Ritināšanas ritenītis virs pogas iestatījumos maina tās vērtību, tāpat kā vaniļas gadījumā. Izslēgts, ritenis tikai ritina sarakstu.",
|
||||
"horizon.extras.pack_browser.tooltip": "Atlasīt Resursu pakotnes saņem pogu Pievienot, kas meklē Modrinth un instalē pakotni jūsu pakotņu mapē. Izslēgts, spēle nekad nesazinās ar Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "Pasaulē cits kontrolieris var pievienoties kā jauns spēlētājs: nospiediet A, kad tas jautā, pēc tam izvēlieties profilu un vietu, kur spēlēt.",
|
||||
"horizon.splitscreen.setup.monitor": "Monitors %s",
|
||||
@@ -456,8 +455,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Slēpts: katrs Horizon spēlētājs redz jūs bez jūsu kosmētikas, ieskaitot jūs. Tas pats slēdzis kā palaišanas ierīces Garderobe.",
|
||||
"horizon.extras.cosmetics_others": "Pārējo spēlētāju kosmētika",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Ko valkā citi Horizon spēlētāji: viss, tikai viņu vārda birku stili vai nekas. Slēpts, spēle nekad nejautā, ko viņi valkā.",
|
||||
"horizon.extras.cosmetics_auras": "Citu auras un takas",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auras un takas, ko valkā citi spēlētāji. Pēc noklusējuma izslēgts.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Slēpt citu spēlētāju pēdas un auras",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Citu spēlētāju pēdas un auras paliek paslēptas; viņu pārējā kosmētika joprojām redzama. Pēc noklusējuma izslēgts.",
|
||||
"horizon.extras.cosmetics.shown": "Parādīts",
|
||||
"horizon.extras.cosmetics.hidden": "Slēpts",
|
||||
"horizon.extras.cosmetics.name_tags": "Tikai vārdu birkas"
|
||||
|
||||
@@ -330,7 +330,6 @@
|
||||
"horizon.osk.close": "Затвори",
|
||||
"horizon.extras.chat_animation.tooltip": "Новата линија за разговор се лизга од лево додека бледне, наместо да се појавува целина.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Лентата во боја лево од линијата за разговор.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Тркалото за лизгање над копче во поставките ја менува својата вредност, како кај ванилата. Исклучено, тркалото само ја прелистува листата.",
|
||||
"horizon.extras.pack_browser.tooltip": "Изборот на пакети со ресурси добива копче „Додај“ што пребарува на Modrinth и инсталира пакет во папката со пакети. Кога е исклучено, играта никогаш не контактира со Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "Во светот, друг контролер може да се приклучи како нов играч: притиснете A кога ќе побара, потоа изберете профил и каде да играте.",
|
||||
"horizon.splitscreen.setup.monitor": "Монитор %s",
|
||||
@@ -480,8 +479,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Скриено: секој играч Horizon ве гледа без вашата козметика, вклучително и вие. Истиот прекинувач како и гардеробата на фрлачот.",
|
||||
"horizon.extras.cosmetics_others": "Козметика за други играчи",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Што носат другите Horizon играчи: сè, само стилови на нивните ознаки или ништо. Скриено, играта никогаш не прашува што носат.",
|
||||
"horizon.extras.cosmetics_auras": "Аури и патеки на другите",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Аури и патеки што ги носат другите играчи. Исклучено стандардно.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Скриј ги трагите и аурите на другите играчи",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Трагите и аурите на другите играчи остануваат скриени; нивната друга козметика и понатаму се гледа. Стандардно исклучено.",
|
||||
"horizon.extras.cosmetics.shown": "Прикажани",
|
||||
"horizon.extras.cosmetics.hidden": "Скриени",
|
||||
"horizon.extras.cosmetics.name_tags": "Само ознаки со имиња"
|
||||
|
||||
@@ -327,7 +327,6 @@
|
||||
"horizon.osk.close": "Хаах",
|
||||
"horizon.extras.chat_animation.tooltip": "Шинэ чатын шугам нь бүхэлдээ харагдахын оронд бүдгэрч байх хооронд зүүн талаас гулсуулна.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Чатын шугамын зүүн талд байгаа өнгөт мөр.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Тохиргооны товчлуур дээр гүйлгэх дугуй нь ванилийн адил утгыг өөрчилдөг. Унтраах үед дугуй нь зөвхөн жагсаалтыг гүйлгэдэг.",
|
||||
"horizon.extras.pack_browser.tooltip": "\"Нөөцийн багцуудыг сонгох\" нь Modrinth-ээс хайлт хийж, багцыг таны багц хавтсанд суулгадаг Нэмэх товчийг авна. Унтраах, тоглоом хэзээ ч Модринттой холбоо барихгүй.",
|
||||
"horizon.extras.splitscreen.tooltip": "Дэлхий дээр өөр хянагч шинэ тоглогчоор нэгдэж болно: асуухад A товчийг дарж, профайл болон хаана тоглохоо сонго.",
|
||||
"horizon.splitscreen.setup.monitor": "Монитор %s",
|
||||
@@ -475,8 +474,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Далд: Horizon тоглогч бүр таныг гоо сайхны бүтээгдэхүүнгүйгээр хардаг. Эхлүүлэгчийн хувцасны шүүгээтэй ижил унтраалга.",
|
||||
"horizon.extras.cosmetics_others": "Бусад тоглогчдын гоо сайхны бүтээгдэхүүн",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Бусад Horizon тоглогчид юу өмсдөг вэ: бүх зүйл, зөвхөн нэрийн шошго, эсвэл юу ч биш. Нуугдсан, тоглоом хэзээ ч юу өмсдөгийг асуудаггүй.",
|
||||
"horizon.extras.cosmetics_auras": "Бусдын аура, замууд",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Аура болон бусад тоглогчдын өмсдөг замууд. Өгөгдмөлөөр унтраа.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Бусад тоглогчийн мөр ба аурыг нуух",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Бусад тоглогчийн мөр ба аур нуугдсан хэвээр байна; тэдний бусад гоёл харагдсаар байна. Анхдагчаар унтраалттай.",
|
||||
"horizon.extras.cosmetics.shown": "Үзүүлсэн",
|
||||
"horizon.extras.cosmetics.hidden": "Нуугдсан",
|
||||
"horizon.extras.cosmetics.name_tags": "Зөвхөн нэрийн шошго"
|
||||
|
||||
@@ -315,7 +315,6 @@
|
||||
"horizon.osk.close": "tutup",
|
||||
"horizon.extras.chat_animation.tooltip": "Baris sembang baharu meluncur masuk dari kiri semasa ia memudar, bukannya kelihatan keseluruhan.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Bar berwarna di sebelah kiri baris sembang.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Roda tatal di atas butang dalam tetapan menukar nilainya, seperti dalam vanila. Mati, roda hanya menatal senarai.",
|
||||
"horizon.extras.pack_browser.tooltip": "Pilih Pek Sumber mendapat butang Tambah yang mencari Modrinth dan memasang pek ke dalam folder pek anda. Padam, permainan tidak pernah menghubungi Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "Dalam dunia, pengawal lain boleh menyertai sebagai pemain baharu: tekan A apabila ia bertanya, kemudian pilih profil dan tempat untuk bermain.",
|
||||
"horizon.splitscreen.setup.monitor": "Pantau %s",
|
||||
@@ -460,8 +459,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Tersembunyi: setiap pemain Horizon melihat anda tanpa kosmetik anda, termasuk anda. Suis yang sama seperti Almari Pakaian pelancar.",
|
||||
"horizon.extras.cosmetics_others": "Kosmetik pemain lain",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Perkara yang dipakai oleh pemain Horizon lain: segala-galanya, hanya gaya tanda nama mereka, atau tiada apa-apa. Tersembunyi, permainan tidak pernah bertanya apa yang mereka pakai.",
|
||||
"horizon.extras.cosmetics_auras": "Aura dan jejak orang lain",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Aura dan jejak yang dipakai pemain lain. Mati secara lalai.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Sembunyikan jejak dan aura pemain lain",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Jejak dan aura pemain lain kekal tersembunyi; kosmetik mereka yang lain masih kelihatan. Mati secara lalai.",
|
||||
"horizon.extras.cosmetics.shown": "Ditunjukkan",
|
||||
"horizon.extras.cosmetics.hidden": "Tersembunyi",
|
||||
"horizon.extras.cosmetics.name_tags": "Tanda nama sahaja"
|
||||
|
||||
@@ -293,7 +293,6 @@
|
||||
"horizon.osk.close": "Agħlaq",
|
||||
"horizon.extras.chat_animation.tooltip": "Linja ta' chat ġdida tiżżerżaq 'il ġewwa mix-xellug waqt li tgħib, minflok tidher sħiħa.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Il-bar ikkulurit fuq ix-xellug ta 'chat line.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Ir-rota tal-iscroll fuq buttuna fis-settings tibdel il-valur tagħha, bħal fil-vanilla. Mitfija, ir-rota tiskrollja biss il-lista.",
|
||||
"horizon.extras.pack_browser.tooltip": "Agħżel Pakketti tar-Riżorsi tikseb buttuna Żid li tfittex Modrinth u tinstalla pakkett fil-folder tal-pakketti tiegħek. Mitfija, il-logħba qatt ma tikkuntattja Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "F'dinja, kontrollur ieħor jista' jingħaqad bħala plejer ġdid: agħfas A meta jitlob, imbagħad agħżel profil u fejn tilgħab.",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "monitor",
|
||||
@@ -432,8 +431,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Moħbija: kull plejer Horizon jarak mingħajr il-kożmetiċi tiegħek, int inkluż. L-istess swiċċ bħall-Gwardarobba tal-lanċjar.",
|
||||
"horizon.extras.cosmetics_others": "Kosmetiċi ta' plejers oħra",
|
||||
"horizon.extras.cosmetics_others.tooltip": "X'jilbsu plejers oħra ta' Horizon: kollox, l-istili tat-tag tal-isem tagħhom biss, jew xejn. Moħbija, il-logħba qatt ma tistaqsi dak li jilbsu.",
|
||||
"horizon.extras.cosmetics_auras": "Awra u traċċi ta’ ħaddieħor",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auras u trails plejers oħra jilbsu. Mitfi awtomatikament.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Aħbi t-traċċi u l-awri ta' plejers oħra",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "It-traċċi u l-awri ta' plejers oħra jibqgħu moħbija; il-kożmetiċi l-oħra tagħhom jibqgħu jidhru. Mitfi b'mod awtomatiku.",
|
||||
"horizon.extras.cosmetics.shown": "murija",
|
||||
"horizon.extras.cosmetics.hidden": "Moħbija",
|
||||
"horizon.extras.cosmetics.name_tags": "Tikketti tal-isem biss",
|
||||
|
||||
@@ -322,7 +322,6 @@
|
||||
"horizon.osk.close": "Sluiten",
|
||||
"horizon.extras.chat_animation.tooltip": "Een nieuwe chatlijn schuift van links naar binnen terwijl deze invaagt, in plaats van geheel te lijken.",
|
||||
"horizon.extras.chat_indicator.tooltip": "De gekleurde balk links van een chatlijn.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Het scrollwiel boven een knop in de instellingen verandert de waarde, zoals in vanille. Uit: het wiel scrollt alleen door de lijst.",
|
||||
"horizon.extras.pack_browser.tooltip": "Select Resource Packs krijgt een knop Toevoegen die Modrinth doorzoekt en een pakket in uw packs-map installeert. Uit: het spel maakt nooit contact met Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "In een wereld kan een andere controller meedoen als nieuwe speler: druk op A wanneer dit wordt gevraagd, kies vervolgens een profiel en waar je wilt spelen.",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "een monitor",
|
||||
@@ -467,8 +466,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Verborgen: elke Horizon-speler ziet je zonder je cosmetica, inclusief jij. Dezelfde schakelaar als de kledingkast van de draagraket.",
|
||||
"horizon.extras.cosmetics_others": "Cosmetica van andere spelers",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Wat andere Horizon-spelers dragen: alles, alleen hun naamplaatjestijlen, of niets. Verborgen vraagt het spel nooit wat ze dragen.",
|
||||
"horizon.extras.cosmetics_auras": "De aura's en sporen van anderen",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Aura's en sporen die andere spelers dragen. Standaard uitgeschakeld.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Sporen en aura's van andere spelers verbergen",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Sporen en aura's van andere spelers blijven verborgen; hun andere cosmetica blijven zichtbaar. Standaard uit.",
|
||||
"horizon.extras.cosmetics.shown": "Getoond",
|
||||
"horizon.extras.cosmetics.hidden": "Verborgen",
|
||||
"horizon.extras.cosmetics.name_tags": "Alleen naamplaatjes"
|
||||
|
||||
@@ -309,7 +309,6 @@
|
||||
"horizon.osk.close": "Lukk",
|
||||
"horizon.extras.chat_animation.tooltip": "En ny chat-linje glir inn fra venstre mens den toner inn, i stedet for å virke hel.",
|
||||
"horizon.extras.chat_indicator.tooltip": "Den fargede linjen til venstre for en chattelinje.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "Rullehjulet over en knapp i innstillingene endrer verdien, som i vanilje. Av, hjulet ruller bare listen.",
|
||||
"horizon.extras.pack_browser.tooltip": "Select Resource Packs får en Legg til-knapp som søker i Modrinth og installerer en pakke i pakkemappen din. Av, spillet kontakter aldri Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "I en verden kan en annen kontroller bli med som en ny spiller: trykk A når den spør, velg deretter en profil og hvor du vil spille.",
|
||||
"horizon.splitscreen.setup.monitor": "Overvåk %s",
|
||||
@@ -449,8 +448,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Skjult: hver Horizon-spiller ser deg uten kosmetikken din, inkludert du. Samme bryter som bærerakettens garderobe.",
|
||||
"horizon.extras.cosmetics_others": "Andre spilleres kosmetikk",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Hva andre Horizon-spillere har på seg: alt, bare navnelappene deres, eller ingenting. Skjult spør spillet aldri hva de har på seg.",
|
||||
"horizon.extras.cosmetics_auras": "Andres auraer og stier",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auraer og stier andre spillere har på seg. Av som standard.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Skjul andre spilleres spor og auraer",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Andre spilleres spor og auraer forblir skjult; deres andre kosmetikk vises fortsatt. Av som standard.",
|
||||
"horizon.extras.cosmetics.shown": "Vist",
|
||||
"horizon.extras.cosmetics.hidden": "Skjult",
|
||||
"horizon.extras.cosmetics.name_tags": "Kun navnelapper",
|
||||
|
||||
@@ -314,7 +314,6 @@
|
||||
"horizon.osk.close": "Barrar",
|
||||
"horizon.extras.chat_animation.tooltip": "Una novèla linha de chat glissa de l'esquèrra del temps qu'esvaís, en luòc d'aparéisser entièra.",
|
||||
"horizon.extras.chat_indicator.tooltip": "La barra colorada a esquèrra d'una linha de chat.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "La ròda de desfilament sus un boton dins los paramètres càmbia sa valor, coma dins la vanilha. Desactivat, la ròda desfila pas que la lista.",
|
||||
"horizon.extras.pack_browser.tooltip": "Seleccionar los paquets de ressorsas obten un boton Apondre que cèrca Modrinth e installa un paquet dins vòstre dorsièr de paquets. Apagat, lo jòc contacta pas jamai Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "Dins un mond, un autre contrarotlaire pòt rejónher coma jogaire novèl: quichatz sus A quand demanda, puèi causissètz un perfil e ont jogar.",
|
||||
"horizon.splitscreen.setup.monitor_unknown": "un ecran",
|
||||
@@ -465,8 +464,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Amagat: cada Horizon jogaire vos vei sens vòstres cosmetics, vos tanben. Lo meteis interruptor que l'armari del lançaire.",
|
||||
"horizon.extras.cosmetics_others": "Los cosmetics dels autres jogaires",
|
||||
"horizon.extras.cosmetics_others.tooltip": "Çò que pòrtan los autres jogaires Horizon : tot, sonque lors estils de etiqueta de nom, o res. Amagat, lo jòc demanda pas jamai çò que pòrtan.",
|
||||
"horizon.extras.cosmetics_auras": "Las auras e las pistas dels autres",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auras e pistas que los autres jogaires pòrtan. Desactivat per defaut.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Amagar las traças e las auras dels autres jogaires",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Las traças e las auras dels autres jogaires demòran amagadas; lors autres cosmetics se vesan totjorn. Desactivat per defaut.",
|
||||
"horizon.extras.cosmetics.shown": "Mostrat",
|
||||
"horizon.extras.cosmetics.hidden": "Amagat",
|
||||
"horizon.extras.cosmetics.name_tags": "D'etiquetas de nom solament"
|
||||
|
||||
@@ -334,7 +334,6 @@
|
||||
"horizon.osk.close": "Fechar",
|
||||
"horizon.extras.chat_animation.tooltip": "Uma nova linha de bate-papo aparece da esquerda enquanto desaparece, em vez de aparecer inteira.",
|
||||
"horizon.extras.chat_indicator.tooltip": "A barra colorida à esquerda de uma linha de bate-papo.",
|
||||
"horizon.extras.wheel_cycles_settings.tooltip": "A roda de rolagem sobre um botão nas configurações altera seu valor, como no vanilla. Desligado, a roda apenas rola a lista.",
|
||||
"horizon.extras.pack_browser.tooltip": "Select Resource Packs obtém um botão Adicionar que pesquisa Modrinth e instala um pacote em sua pasta de pacotes. Desligado, o jogo nunca entra em contato com Modrinth.",
|
||||
"horizon.extras.splitscreen.tooltip": "Num mundo, outro controlador pode entrar como novo jogador: pressione A quando solicitado e escolha um perfil e onde jogar.",
|
||||
"horizon.splitscreen.setup.monitor": "Monitorar %s",
|
||||
@@ -484,8 +483,8 @@
|
||||
"horizon.extras.cosmetics_mine.tooltip": "Oculto: todo jogador do Horizon vê você sem seus cosméticos, inclusive você. A mesma opção do guarda-roupa do lançador.",
|
||||
"horizon.extras.cosmetics_others": "Cosméticos de outros jogadores",
|
||||
"horizon.extras.cosmetics_others.tooltip": "O que os outros jogadores do Horizon usam: tudo, apenas os estilos dos crachás ou nada. Escondido, o jogo nunca pergunta o que eles vestem.",
|
||||
"horizon.extras.cosmetics_auras": "Auras e trilhas de outros",
|
||||
"horizon.extras.cosmetics_auras.tooltip": "Auras e trilhas que outros jogadores usam. Desativado por padrão.",
|
||||
"horizon.extras.cosmetics_hide_fx": "Ocultar rastros e auras de outros jogadores",
|
||||
"horizon.extras.cosmetics_hide_fx.tooltip": "Os rastros e auras de outros jogadores ficam ocultos; os demais cosméticos deles continuam visíveis. Desativado por padrão.",
|
||||
"horizon.extras.cosmetics.shown": "Mostrado",
|
||||
"horizon.extras.cosmetics.hidden": "Oculto",
|
||||
"horizon.extras.cosmetics.name_tags": "Somente tags de nome"
|
||||
|
||||
Loaded 100 of 488 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user