mirror of
https://github.com/horizon-rust/horizon-mods.git
synced 2026-11-07 11:05:58 -10:00
Compare commits
36
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6480bf8b8d | ||
|
|
f64587952e | ||
|
|
ad5e3b12b7 | ||
|
|
f1cdfcb3e8 | ||
|
|
4931f3a819 | ||
|
|
7b74fe93b1 | ||
|
|
4ccc44d612 | ||
|
|
08e63434d8 | ||
|
|
40dc440cbb | ||
|
|
7581c129bb | ||
|
|
259d022945 | ||
|
|
78853c19a6 | ||
|
|
e53ad8220b | ||
|
|
43fb2513a2 | ||
|
|
dfd2102274 | ||
|
|
35ce93db53 | ||
|
|
3f3103d070 | ||
|
|
ed4aa85442 | ||
|
|
43010d3eb2 | ||
|
|
546deac561 | ||
|
|
2eca4bc2ec | ||
|
|
a2ae910123 | ||
|
|
88b20af247 | ||
|
|
ceb5b2275d | ||
|
|
e980583b96 | ||
|
|
5c094254e5 | ||
|
|
d8a3327ee3 | ||
|
|
239ce420fa | ||
|
|
ee1620f438 | ||
|
|
a0dba26666 | ||
|
|
82e29462a9 | ||
|
|
725ff7a410 | ||
|
|
64b5237f7c | ||
|
|
7d17c4d63a | ||
|
|
aab9aa4c22 | ||
|
|
6d9a7492e2 |
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 990 B |
Binary file not shown.
|
After Width: | Height: | Size: 660 B |
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.7 KiB |
@@ -18,6 +18,7 @@ Some servers see fast, accurate placing as a cheat: check their rules, or turn "
|
||||
A port of [Accurate Block Placement: Reborn](https://modrinth.com/mod/accurate-block-placement-reborn) (MIT) by Clayborn, Flourick, schwar and KadTheHunter, rewritten in Rust for Horizon."""
|
||||
authors = ["Lutziano888"]
|
||||
owners = ["Lutziano888"]
|
||||
owner_ids = [129792568]
|
||||
license = "MIT"
|
||||
source = "https://github.com/Lutziano888/horizon-accurate-block-placement"
|
||||
icon = "https://raw.githubusercontent.com/Lutziano888/horizon-accurate-block-placement/main/icon.png"
|
||||
|
||||
@@ -11,6 +11,7 @@ Based on AppleSkin by squeek502. See the LICENSE in the source for the
|
||||
original copyright and licence notices."""
|
||||
authors = ["soufi"]
|
||||
owners = ["soufied"]
|
||||
owner_ids = [47915535]
|
||||
license = "MIT"
|
||||
source = "https://github.com/soufied/AppleSkin-Horizon"
|
||||
icon = "https://raw.githubusercontent.com/soufied/AppleSkin-Horizon/refs/heads/main/icons/appleskin.png"
|
||||
|
||||
@@ -11,6 +11,7 @@ Food Value Preview: Pulsing preview drumsticks and preview saturation border whe
|
||||
Clean Integration: Easily toggled via Options > Mods > AppleSkin."""
|
||||
authors = ["CayMac"]
|
||||
owners = ["CayMac97"]
|
||||
owner_ids = [197672048]
|
||||
license = "MIT"
|
||||
source = "https://github.com/CayMac97/appleskin"
|
||||
key = "RWS411U2KXdTN4/y5bQhqBYxNlZvCtSZ5lNy46eeSAcgy0cxSJo2+Sm3"
|
||||
|
||||
@@ -12,6 +12,7 @@ Item count display for non-damageable held items (torches, food, blocks).
|
||||
Default position: Bottom Right (freely customizable in Options > Mods)."""
|
||||
authors = ["CayMac"]
|
||||
owners = ["CayMac97"]
|
||||
owner_ids = [197672048]
|
||||
license = "MIT"
|
||||
source = "https://github.com/CayMac97/armor-status"
|
||||
key = "RWS411U2KXdTN4/y5bQhqBYxNlZvCtSZ5lNy46eeSAcgy0cxSJo2+Sm3"
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
id = "armorhud"
|
||||
kind = "native-mod"
|
||||
name = "Armor HUD"
|
||||
summary = "Armor HUD mod"
|
||||
description = "A mod that adds 4 armor durability viewing slots to the hotbar."
|
||||
authors = ["Thomas9950"]
|
||||
owners = ["Thomas-Slot"]
|
||||
owner_ids = [265566914]
|
||||
license = "MIT"
|
||||
source = "https://github.com/Thomas-Slot/horizon-armor-hud"
|
||||
icon = "https://github.com/Thomas-Slot/horizon-armor-hud/blob/main/icon.png"
|
||||
key = "RWQNLUB+Hf9pa54kEx/ItgJHmDivzpAZxK9lqvv9wnecj34yzNVQHQur"
|
||||
|
||||
[[versions]]
|
||||
version = "1.0.0"
|
||||
published = "2026-11-04"
|
||||
game = "26.3"
|
||||
file = "armorhud-1.0.0.hnmod"
|
||||
sha256 = "8bd85e1584f307201bce5de2e60466d603b61d4807803d4ebc02772b4f6b704b"
|
||||
signature = """
|
||||
untrusted comment: signature from minisign secret key
|
||||
RUQNLUB+Hf9pa0KnzbNFmdhyi6xpMr56two0l9g3QlnoBYyrHFtJt5luXXEl9FnJQpP6GH7Y9KDqYUSAac7QZEQDejqrM2gA7A4=
|
||||
trusted comment: timestamp:1793750475\tfile:armorhud-1.0.0.hnmod\thashed
|
||||
q840egwiCqF5Ptn8MCP/0VqaPUQQgaWzyXwhnFhD47GLeblsZWdvPyaTnEUbsGxHCgHGT5FFrqJ54Mu1OSz/DQ=="""
|
||||
changelog = "First upload."
|
||||
@@ -20,6 +20,7 @@ Weapons hurt the mobs they hit. With blasting turned on in the control panel (of
|
||||
Built for this Horizon commit only: `68ac6f647e7022b2c696e81863d7cface28c9416`."""
|
||||
authors = ["repositorykreml1n"]
|
||||
owners = ["repositorykreml1n"]
|
||||
owner_ids = [271232376]
|
||||
license = "MIT"
|
||||
source = "https://github.com/repositorykreml1n/astral-cruiser"
|
||||
|
||||
|
||||
+32
-26
@@ -1,44 +1,50 @@
|
||||
id = "basic_hud"
|
||||
kind = "native-mod"
|
||||
name = "The Basic HUD"
|
||||
summary = "A visual HUD editor with movable widgets, FPS, coordinates, effect timers, cooldowns, mini armour and a customizable Tab list."
|
||||
summary = "A visual HUD editor with 14 customizable widgets, recent pickups, effect timers, durability, coordinates, FPS and a customizable Tab list."
|
||||
description = """
|
||||

|
||||
A client-side visual HUD for Horizon. Move, resize and style fourteen independent widgets directly over the game.
|
||||
|
||||
## Your HUD, your layout
|
||||
Includes effects and timers, consumable counts, durability, observed totem uses, available player health, item cooldowns, coordinates, FPS, ping, speed, CPS, world clock, mini armour and recent item pickups.
|
||||
|
||||
Arrange 13 independent widgets directly over the game. Drag, resize and style each block with colours, opacity, padding and shadows. Use full labels or compact icons with useful values such as ×16 and 1:45.
|
||||
Recent pickups aggregates matching items and refreshes their expiry: 4 torches + 2 torches becomes x6. Five seconds by default, adjustable from 1 to 60 seconds. Includes icon + count mode.
|
||||
|
||||
Includes effects, consumables, durability, observed totem uses, available player-health data, item cooldowns, coordinates, FPS, ping, optional speed/CPS/clock, and mini armour next to the hotbar. Customize the Tab list with coloured numeric ping, bars, counts, sorting, scale and opacity.
|
||||
Customize each widget's position, scale, colours, opacity, padding and shadows. Includes a customizable Tab list and ten interface languages. A short tutorial appears on the first settings visit and can be replayed with ?. Reset requires confirmation; leaving with edits offers Save / Discard / Stay.
|
||||
|
||||
Ten interface languages plus Auto. A short first-visit guide can be replayed through ?. Reset requires confirmation; leaving unsaved edits offers Save / Discard changes / Stay. Failed writes retain the draft.
|
||||
Controls: Right Shift, or Options > Mods > The Basic HUD > Settings > Edit HUD.
|
||||
|
||||
## Install and compatibility
|
||||
Install the .hnmod through Horizon and choose Performance. Minecraft 26.3; pinned Horizon 68ac6f647e7022b2c696e81863d7cface28c9416. Compatibility with other Nightlies is not claimed.
|
||||
|
||||
Native .hnmod for Minecraft 26.3, pinned to Horizon **68ac6f647e7022b2c696e81863d7cface28c9416**. Install through the launcher and select **Performance**. Dev can overflow the startup stack on this Windows baseline. Other Nightlies require separate verification.
|
||||
Client-side only: unknown health stays unknown, unobserved totem uses cannot be counted, and server-plugin-only cooldowns are unavailable. No FPS improvement is claimed.
|
||||
|
||||
Client-side only. Unknown player health remains unknown, unseen totem uses cannot be counted and server-plugin-only timers are unavailable. No server mod is required. No FPS improvement is promised. Native mods compile into the game and run with the player’s rights, without a sandbox. Tested on Windows; other platforms were not tested.
|
||||
|
||||
## Other languages
|
||||
|
||||
**Русский:** редактор поверх игры, размер и оформление каждого блока, значки с числами, FPS, координаты, кулдауны, мини-броня и настройка Tab. Обучение один раз; подтверждение сброса и выхода с правками.
|
||||
|
||||
**Українська:** редактор поверх гри, розмір і стиль кожного віджета, значки зі значеннями, FPS, координати, час відновлення, мініброня та налаштування Tab. Навчання під час першого входу й захист незбережених змін.
|
||||
|
||||
**Deutsch:** Editor direkt im Spiel, frei verschiebbare und skalierbare Widgets, Symbole mit Werten, FPS, Koordinaten, Abklingzeiten, Rüstungsanzeige und anpassbare Tab-Liste. Einführung beim ersten Besuch und Schutz ungespeicherter Änderungen.
|
||||
|
||||
**Français :** éditeur superposé au jeu, widgets déplaçables et redimensionnables, icônes avec valeurs, FPS, coordonnées, délais de récupération, petite vue de l’armure et liste Tab personnalisable. Tutoriel initial et protection des modifications non enregistrées.
|
||||
|
||||
**Español:** editor superpuesto al juego, widgets movibles y ajustables, iconos con valores, FPS, coordenadas, tiempos de recarga, vista pequeña de la armadura y lista Tab personalizable. Tutorial inicial y protección de cambios sin guardar.
|
||||
|
||||
[Full update notes in six languages](https://github.com/repositorykreml1n/the-basic-hud/blob/main/CHANGELOG.md)
|
||||
|
||||
MIT. Not an official Horizon, Mojang or Microsoft product. Cover is a generated promotional illustration, not a gameplay screenshot or measured FPS claim."""
|
||||
Release notes and verification: https://github.com/repositorykreml1n/the-basic-hud/releases/tag/v0.5.0"""
|
||||
authors = ["repositorykreml1n"]
|
||||
owners = ["repositorykreml1n"]
|
||||
owner_ids = [271232376]
|
||||
license = "MIT"
|
||||
source = "https://github.com/repositorykreml1n/the-basic-hud"
|
||||
icon = "https://raw.githubusercontent.com/repositorykreml1n/the-basic-hud/main/icon.png"
|
||||
icon = "https://raw.githubusercontent.com/repositorykreml1n/the-basic-hud/v0.5.0/icon.png"
|
||||
|
||||
[[versions]]
|
||||
version = "0.5.0"
|
||||
published = "2026-11-04"
|
||||
game = "26.3"
|
||||
file = "basic_hud-0.5.0.hnmod"
|
||||
sha256 = "97011cc7374354c6293678c7ab593240d0512bffde1d7b642dad521e1df3f8ac"
|
||||
changelog = """
|
||||
## 0.5.0 — Recent pickups
|
||||
- New movable widget for recently collected items: pick up 4 torches, then 2 more before expiry, and it shows x6. Each repeat pickup refreshes that item's timer.
|
||||
- Default display time: 5 seconds; adjustable from 1 to 60 seconds.
|
||||
- Supports icon + count mode, dragging, scale, colours and opacity.
|
||||
- Partial pickups count only the amount that fits. Giving, crafting and inventory rearrangement do not by themselves create entries.
|
||||
- New pixel-art totem icon; new labels translated into all ten interface languages. Existing layouts and settings are preserved.
|
||||
|
||||
Verified: 75 strict Rust test executions, warning-free pinned native build, real in-game aggregation/expiry/partial-pickup checks, editor persistence and ordinary startup without a controller.
|
||||
|
||||
Русский: добавлен блок недавнего подбора с суммированием количества, обновлением таймера и настройкой 1–60 секунд (по умолчанию 5). Новая иконка — тотем.
|
||||
Deutsch: Neues Widget für aufgehobene Gegenstände mit addierten Mengen und einstellbarer Dauer von 1–60 Sekunden.
|
||||
Français : nouveau widget des objets ramassés, quantités cumulées et durée réglable de 1 à 60 secondes.
|
||||
Español: nuevo widget de objetos recogidos, cantidades acumuladas y duración ajustable de 1 a 60 segundos."""
|
||||
|
||||
[[versions]]
|
||||
version = "0.4.1"
|
||||
|
||||
@@ -14,6 +14,7 @@ Craft a **Biome Compass** (a compass with a sapling on each of its four sides, a
|
||||
Inspired by [Nature's Compass](https://modrinth.com/mod/natures-compass); not affiliated, no code from it."""
|
||||
authors = ["d3grando"]
|
||||
owners = ["d3grando"]
|
||||
owner_ids = [36463198]
|
||||
license = "MIT"
|
||||
source = "https://github.com/d3grando/biomecompass"
|
||||
icon = "icons/biomecompass.png"
|
||||
|
||||
@@ -5,6 +5,7 @@ summary = "The block under your crosshair: its name, id, distance and icon, plus
|
||||
description = "A Jade-style tooltip for Horizon: the block under your crosshair with its name in your language, block id, distance and icon, plus its hardness and the tool it wants, its state properties, a crop's growth and, for a container, its slots. Ten switches on its own settings page."
|
||||
authors = ["Domanffe"]
|
||||
owners = ["Domanffe"]
|
||||
owner_ids = [94391234]
|
||||
license = "MIT"
|
||||
source = "https://github.com/Domanffe/blockscope"
|
||||
icon = "https://horizon-rust.github.io/horizon-mods/icons/blockscope.png"
|
||||
|
||||
@@ -15,6 +15,7 @@ Sneak and right-click with an empty hand to pick up a block with its contents (c
|
||||
Inspired by [Carry On](https://modrinth.com/mod/carry-on); not affiliated, no code from it."""
|
||||
authors = ["d3grando"]
|
||||
owners = ["d3grando"]
|
||||
owner_ids = [36463198]
|
||||
license = "MIT"
|
||||
source = "https://github.com/d3grando/carryblock"
|
||||
icon = "icons/carryblock.png"
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
id = "chat_animation"
|
||||
kind = "native-mod"
|
||||
name = "ChatAnimationHorizon"
|
||||
summary = "ChatAnimation for Horizon"
|
||||
description = """
|
||||
By tariksabanuz
|
||||
|
||||
ChatAnimationHorizon brings smooth chat animations to Horizon, recreating Ezzenix's Chat Animation mod. New chat messages slide and fade in instead of popping onto the screen.
|
||||
|
||||
Features
|
||||
- Messages animate in smoothly when they arrive
|
||||
- Works with the game's normal chat, no setup needed
|
||||
|
||||
Notes
|
||||
- Native Horizon mod, built from source by the launcher.
|
||||
- Based on Ezzenix's Chat Animation (MIT licensed), recreated for Horizon.
|
||||
|
||||
Source code: https://github.com/tariksabanuz/ChatAnimationHorizon"""
|
||||
authors = ["tariksabanuz"]
|
||||
owners = ["tariksabanuz"]
|
||||
license = "MIT"
|
||||
source = "https://github.com/tariksabanuz/ChatAnimationHorizon"
|
||||
icon = "https://raw.githubusercontent.com/tariksabanuz/ChatAnimationHorizon/3794c3c1ede07a1c7ebb80856b246a1ad4339101/icon.png"
|
||||
|
||||
[[versions]]
|
||||
version = "0.1.0"
|
||||
published = "2026-11-04"
|
||||
game = "26.3"
|
||||
file = "chat_animation-0.1.0.hnmod"
|
||||
sha256 = "aee273e2861a185ed2667679c0f943dcc73693b46eded6c078cde8bdf13ee49a"
|
||||
+10
-6
@@ -2,18 +2,22 @@ id = "chatheads"
|
||||
kind = "native-mod"
|
||||
name = "ChatHeads"
|
||||
summary = "A player's head left of every chat line they wrote."
|
||||
description = """
|
||||
Each chat line from a player gets their 8x8 skin face in the strip to its left, like the Chat Heads Java mod. Lines without a player (system messages, /me, wrapped continuations) stay as they are; the face fades with its line.
|
||||
|
||||
Settings: Options > Mods > ChatHeads > Settings (on or off).
|
||||
|
||||
Known limit: the chat's hit-test does not know about the 10 px indent, so clicking a link in chat lands 10 px left of its pixels."""
|
||||
description = "Each chat line from a player gets their 8x8 skin face in the strip to its left; lines without a player stay as they are, and the face fades with its line. The switch lives in Options > Mods."
|
||||
authors = ["Domanffe"]
|
||||
owners = ["Domanffe"]
|
||||
owner_ids = [94391234]
|
||||
license = "MIT"
|
||||
source = "https://github.com/Domanffe/chatheads"
|
||||
icon = "https://horizon-rust.github.io/horizon-mods/icons/chatheads.png"
|
||||
|
||||
[[versions]]
|
||||
version = "0.1.1"
|
||||
published = "2026-11-02"
|
||||
game = "26.3"
|
||||
file = "chatheads-0.1.1.hnmod"
|
||||
sha256 = "16f5e1db33255c550c087fcf3ed982a4be9abf61b547b470a08f471d309ad1fe"
|
||||
changelog = "Rebuilt on the 2026-11-01 Nightly (Horizon `4aa6854`). The three hook points it shares — `mods`, `settings`, `settings_click` — are named now instead of pinned to raw source targets, so a Horizon update that moves them no longer breaks the bundle."
|
||||
|
||||
[[versions]]
|
||||
version = "0.1.0"
|
||||
published = "2026-10-30"
|
||||
|
||||
@@ -22,6 +22,7 @@ Native source mod: install it on the launcher's Mods page, press Build there, an
|
||||
Original mod by Mayonesu0. All rights reserved, except the grants in its [LICENSE](https://github.com/Mayonesu0/chop/blob/main/LICENSE): players may use it unchanged, Horizon's registry may distribute it, and Horizon's reviewers may read it to review it."""
|
||||
authors = ["Mayonesu0", "Claude"]
|
||||
owners = ["Mayonesu0"]
|
||||
owner_ids = [350298764]
|
||||
license = "LicenseRef-Chop-1.0"
|
||||
source = "https://github.com/Mayonesu0/chop"
|
||||
icon = "https://raw.githubusercontent.com/Mayonesu0/chop/main/registry/icon.png"
|
||||
|
||||
@@ -37,6 +37,7 @@ Both can be installed at once: they use different keys.
|
||||
Source and issues: [github.com/SU4G3/classic-zoom](https://github.com/SU4G3/classic-zoom)"""
|
||||
authors = ["Surge"]
|
||||
owners = ["SU4G3"]
|
||||
owner_ids = [92383635]
|
||||
license = "GPL-3.0-or-later"
|
||||
source = "https://github.com/SU4G3/classic-zoom"
|
||||
icon = "icons/classic_zoom.png"
|
||||
|
||||
@@ -16,6 +16,7 @@ A compass bar at the top of your screen that shows where you are facing, in degr
|
||||
Settings are in Options → Mods → Compass Bar. Death points are not saved: they vanish when you close the game."""
|
||||
authors = ["Isaac"]
|
||||
owners = ["Fernandes585"]
|
||||
owner_ids = [344517364]
|
||||
license = "MIT"
|
||||
source = "https://github.com/Fernandes585/Compass-Enhanced"
|
||||
key = "RWScU5xtTnTnu55tXSTPbhM2BQ6KG2FLeMD9aTeX111NlQPh7cCafm1w"
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
id = "connected_textures"
|
||||
kind = "native-mod"
|
||||
name = "ContinuityHorizon"
|
||||
summary = "Continuity for Horizon"
|
||||
description = """
|
||||
By tariksabanuz
|
||||
|
||||
ContinuityHorizon brings Continuity-style connected textures to Horizon. Neighbouring blocks of the same kind join up into one seamless surface instead of showing a border on every block.
|
||||
|
||||
What it connects
|
||||
- Glass and glass panes
|
||||
- Bookshelves
|
||||
- Sandstone
|
||||
|
||||
Notes
|
||||
- Works with the game's own textures; no resource pack needed.
|
||||
- Native Horizon mod, built from source by the launcher.
|
||||
- Inspired by Continuity (MIT licensed), recreated for Horizon.
|
||||
|
||||
Source code: https://github.com/tariksabanuz/ContinuityHorizon"""
|
||||
authors = ["tariksabanuz"]
|
||||
owners = ["tariksabanuz"]
|
||||
license = "MIT"
|
||||
source = "https://github.com/tariksabanuz/ContinuityHorizon"
|
||||
icon = "https://raw.githubusercontent.com/tariksabanuz/ContinuityHorizon/cd5301759efef6b1819ed7a8590a0d76e15d6d7e/icon.png"
|
||||
|
||||
[[versions]]
|
||||
version = "0.1.2"
|
||||
published = "2026-11-04"
|
||||
game = "26.3"
|
||||
file = "connected_textures-0.1.2.hnmod"
|
||||
sha256 = "e86d1d3fd92045737b90218de51c158b60519afc6618c063c87f64ca68d36adf"
|
||||
@@ -18,6 +18,7 @@ rests at once, and the next build leaves it out. Its source is
|
||||
"""
|
||||
authors = ["Horizon"]
|
||||
owners = ["ludovic111"]
|
||||
owner_ids = [136255825]
|
||||
license = "MIT"
|
||||
source = "https://github.com/horizon-rust/horizon-mods/tree/main/native/coords-hud"
|
||||
icon = "icons/coords_hud.png"
|
||||
|
||||
@@ -15,6 +15,7 @@ Cooldown Bar: Optional subtle weapon cooldown progress indicator below the cross
|
||||
Configurable: Easily customize styles, themes, and keybinds in Options > Mods > Crosshair Indicator."""
|
||||
authors = ["CayMac"]
|
||||
owners = ["CayMac97"]
|
||||
owner_ids = [197672048]
|
||||
license = "MIT"
|
||||
source = "https://github.com/CayMac97/crosshair-indicator"
|
||||
key = "RWS411U2KXdTN4/y5bQhqBYxNlZvCtSZ5lNy46eeSAcgy0cxSJo2+Sm3"
|
||||
|
||||
@@ -7,6 +7,7 @@ Hop versions, offline.
|
||||
Uses ViaProxy as an inbetween for now."""
|
||||
authors = ["JustKarkat"]
|
||||
owners = ["JustKarkat"]
|
||||
owner_ids = [50185858]
|
||||
license = "MIT"
|
||||
source = "https://github.com/JustKarkat/horizon-crossversion/"
|
||||
icon = "https://github.com/JustKarkat/horizon-crossversion/blob/master/icon.png?raw=true"
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
id = "customhud"
|
||||
kind = "native-mod"
|
||||
name = "Beautiful Day Counter"
|
||||
summary = "A lightweight day counter, clock and day/night cycle bar for the HUD, with an in-game settings screen."
|
||||
description = """
|
||||
Beautiful Day Counter for Horizon, a native mod.
|
||||
|
||||
Shows the current survival day, an in-game clock and a color-shifting bar that follows the day/night cycle, without changing gameplay. It is a port of the author's own Fabric/NeoForge mod [CustomHUD](https://github.com/sonoFrangu/CustomHUD), with the same HUD and the same settings screen.
|
||||
|
||||
- Day counter with the game mode icon; a golden apple or totem, a rainbow counter and a sound on milestone days (50, 100, 365, every 100).
|
||||
- 12-hour or 24-hour clock; the clock icon turns into a bed when you can sleep.
|
||||
- Day/night bar with four color themes (Classic, Pastel, Colorblind, Mono), night marks, countdown to night or day, moon phase.
|
||||
- Top or bottom placement, size, opacity, offsets, optional background; vanilla boss bars move below the HUD when it is at the top.
|
||||
- Settings screen opened with H (or from Options > Mods): Elements, Look and Behavior, with tooltips.
|
||||
- English, Italian, German, Spanish, French and Portuguese."""
|
||||
authors = ["SCARABOCCHIO100"]
|
||||
owners = ["sonoFrangu"]
|
||||
owner_ids = [205944444]
|
||||
license = "MIT"
|
||||
source = "https://github.com/sonoFrangu/CustomHUD-Horizon"
|
||||
icon = "icons/customhud.png"
|
||||
|
||||
[[versions]]
|
||||
version = "1.6.0"
|
||||
published = "2026-10-31"
|
||||
game = "26.3"
|
||||
file = "customhud-1.6.0.hnmod"
|
||||
sha256 = "ab255d3c722b40f4d068a762857d6c027c11381308bfe23196e2974307814a7f"
|
||||
changelog = "First release for Horizon: the CustomHUD 1.6.0 HUD and settings screen as a native mod."
|
||||
@@ -0,0 +1,24 @@
|
||||
id = "deepbore_quarry"
|
||||
kind = "mod"
|
||||
name = "Deepbore Quarry (assets)"
|
||||
summary = "The textures, text and recipes of Deepbore Quarry. Needs the Deepbore Quarry native mod."
|
||||
description = """
|
||||
**This is one of the two parts of [Deepbore Quarry](https://playhorizon.dev/mods/deepbore_quarry_native), and does nothing alone.** Install both: this one, the assets, and the native part. If one is missing, the game says so in chat.
|
||||
|
||||
It holds the mod's models and textures, its English and Russian text, its recipes and drops, and its screen's menu. It has no code. The code, and the description of what the mod does, are in Deepbore Quarry.
|
||||
|
||||
All the art is original. No Minecraft assets are included. MIT licensed."""
|
||||
authors = ["bioneisme"]
|
||||
owners = ["Bioneisme"]
|
||||
owner_ids = [92920845]
|
||||
license = "MIT"
|
||||
source = "https://github.com/Bioneisme/deepbore-quarry"
|
||||
icon = "https://raw.githubusercontent.com/Bioneisme/deepbore-quarry/main/icon.png"
|
||||
|
||||
[[versions]]
|
||||
version = "0.1.0"
|
||||
published = "2026-11-05"
|
||||
game = "26.3"
|
||||
file = "deepbore_quarry-0.1.0.hmod"
|
||||
sha256 = "2a75f3adc59aaf273fd833545c50a63a7f829916156b74d85bebbb6273b0d8b6"
|
||||
changelog = "First release."
|
||||
@@ -0,0 +1,34 @@
|
||||
id = "deepbore_quarry_native"
|
||||
kind = "native-mod"
|
||||
name = "Deepbore Quarry"
|
||||
summary = "A machine that digs a square pit down to bedrock on furnace fuel and fills a chest, under a moving gantry with a drill."
|
||||
description = """
|
||||
**This mod has two parts, and you need both.** Install this one, the native part, and [Deepbore Quarry (assets)](https://playhorizon.dev/mods/deepbore_quarry), then press Build. If one is missing, the game says so in chat.
|
||||
|
||||
- **Place** a quarry. The pit lies beyond it, the way you are looking, and goes from the quarry's own level down to bedrock.
|
||||
- **Put a chest** against another side of it, and fuel in its slot: anything a furnace burns. One coal digs eight blocks.
|
||||
- **Watch** the gantry: a drill runs over the pit on a moving bridge, row by row. The screen says what the quarry is doing, and why when it stands still.
|
||||
- **Choose** four upgrades: Area up to 17×17, Speed up to a block in 0.25 s, Fluids to plug water and lava and dig on, Chunk to keep working when you are away.
|
||||
- **Keep** what matters: it leaves bedrock and any block with contents, and only its owner or an operator can open or break it.
|
||||
|
||||
English and Russian.
|
||||
|
||||
**Commands:** operators have `/deepbore maxsize` and `/deepbore maxsize <5|9|13|17>`, the largest area a world allows.
|
||||
|
||||
Native source mod: install it on the launcher's Mods page, press Build there, and confirm the native-code warning. It runs with your account's permissions. It is pinned to one Horizon Nightly and Horizon Mixins revision, named in each version's changelog.
|
||||
|
||||
No Minecraft code or assets are included. MIT licensed."""
|
||||
authors = ["bioneisme"]
|
||||
owners = ["Bioneisme"]
|
||||
owner_ids = [92920845]
|
||||
license = "MIT"
|
||||
source = "https://github.com/Bioneisme/deepbore-quarry"
|
||||
icon = "https://raw.githubusercontent.com/Bioneisme/deepbore-quarry/main/icon.png"
|
||||
|
||||
[[versions]]
|
||||
version = "0.1.0"
|
||||
published = "2026-11-05"
|
||||
game = "26.3"
|
||||
file = "deepbore_quarry_native-0.1.0.hnmod"
|
||||
sha256 = "bda03ffe35cca7973ec8a19cbcd5b50dade5f698c30faea8f84b9343cfeb31ef"
|
||||
changelog = "First release. Pinned to Nightly 2026-11-04 (67dad0d) and Horizon Mixins e8cd0aa."
|
||||
@@ -21,6 +21,7 @@ Known limits: heavy at 256+ chunks (about 30–40 fps on an Apple M5 MacBook Pro
|
||||
LGPL-3.0, as Distant Horizons; its copyright notices are kept. Not made or endorsed by the Distant Horizons team. No Minecraft code or assets are included. Ported by Deniz Şahin with Claude (Anthropic)."""
|
||||
authors = ["denizsahin7744", "Deniz7744"]
|
||||
owners = ["denizsahin7744"]
|
||||
owner_ids = [144704661]
|
||||
license = "LGPL-3.0-only"
|
||||
source = "https://github.com/denizsahin7744/distant-horizons-for-horizon-client"
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
id = "dynamic_fps_for_rust"
|
||||
kind = "native-mod"
|
||||
name = "Dynamic FPS for rust"
|
||||
summary = "Reduce resource usage while Minecraft is in the background, idle, or on battery"
|
||||
description = """
|
||||
Dynamic FPS for Horizon: it lowers the frame rate, and optionally the volume, while the game window is in the background, hovered but not focused, minimised or idle, or while Horizon's battery mode is on.
|
||||
|
||||
- Caps by default: 30 FPS hovered, 10 unfocused (after half a second), 1 minimised, 20 after a minute idle, 30 on battery; no cap while you play.
|
||||
- At 1 FPS the game still runs its input, network and ticks up to 20 times a second.
|
||||
- Volume lowering is off by default.
|
||||
- Settings: `config/dynamic-fps-for-rust.json` in the game folder, written with the defaults on first start. There is no settings screen yet.
|
||||
|
||||
An independent implementation after the public description of Dynamic FPS; it holds none of its code. It changes no shared game settings and has no network access."""
|
||||
authors = ["Lyvt (dc: _.lyvt)"]
|
||||
owners = ["Lyvt-Dev"]
|
||||
license = "MIT"
|
||||
source = "https://github.com/Lyvt-Dev/dynamic-fps-for-rust"
|
||||
icon = "https://raw.githubusercontent.com/Lyvt-Dev/dynamic-fps-for-rust/main/logo/dynamic-fps.png"
|
||||
key = "RWQ84d8BvD1EaarVDgHgXyz+MDiL7ONsCZc0BUrJqTmqzXBItnJy918A"
|
||||
|
||||
[[versions]]
|
||||
version = "0.1.0"
|
||||
published = "2026-11-04"
|
||||
game = "26.3"
|
||||
file = "dynamic_fps_for_rust-0.1.0.hnmod"
|
||||
sha256 = "50fad9a8d1d481a3862479b313c04e0dc6f8494a84d4cd007583822f5671e17e"
|
||||
signature = """
|
||||
untrusted comment: signature from minisign secret key\r
|
||||
RUQ84d8BvD1EaVhURO1FuYGMB/oFMSego05WqzetlxYu+Xqw833gPsVaui5/duBbZ3lGVErA0gcc/vxslYvhhaGkPRciB+/40wg=\r
|
||||
trusted comment: Dynamic FPS for rust 0.1.0; dynamic-fps-for-rust-0.1.0.hnmod\r
|
||||
V1fosOt0MvcQQm5uFTaFubXeoX1nt3411VAeox/pIV+R/GbcsiJGGRO/p1NcY39sOFOFkF3qWg9pNt/J4EXaDA=="""
|
||||
changelog = """
|
||||
First release: frame rate and optional volume caps for an unfocused, hovered, minimised or idle window, and on battery."""
|
||||
@@ -12,6 +12,7 @@ The frames come from a resource pack you build from your own copy of the EatingA
|
||||
~~Or you can just download it from GitHub Releases...~~"""
|
||||
authors = ["soufi"]
|
||||
owners = ["soufied"]
|
||||
owner_ids = [47915535]
|
||||
license = "MIT"
|
||||
source = "https://github.com/soufied/EatingAnimation-Horizon"
|
||||
icon = "icons/eatinganimation.png"
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
id = "elytratrails"
|
||||
kind = "native-mod"
|
||||
name = "ElytraTrails"
|
||||
summary = "A Horizon mod that adds elytra trails with additional features."
|
||||
description = """
|
||||
A native mod for Horizon. The idea comes from [ElytraTrails](https://github.com/dbrighthd/elytratrails) by dbrighthd.
|
||||
|
||||
What it does
|
||||
|
||||
Lines stream from the outer tip of each wing while you glide and fade out over time. Only players get trails.
|
||||
|
||||
- Colors: ice, gold, rose, violet, white, rainbow, or speed (cold blue when slow, orange and thicker when fast).
|
||||
- Patterns: solid, dashed or wavy. Built in only.
|
||||
- Pulse: gently breathing brightness.
|
||||
- Night: lines are brighter at night and dimmer in the day.
|
||||
- Boost: a sudden speed jump, such as a firework, lights the whole trail up for two seconds.
|
||||
- Formation: gliding players close to each other are joined by white lines between their wingtips.
|
||||
- Lines taper toward the tail, the wingtip has a soft glow, and fading eases in and out.
|
||||
- Singleplayer: without LAN or a server, all code for other players is skipped.
|
||||
- Sharing: your color and pattern are sent through the server, so other players see them (single player, LAN and servers). (Untested, I don't have friends)
|
||||
|
||||
Settings
|
||||
|
||||
Open Options > Mods > ElytraTrails:
|
||||
|
||||
- **Trails**: turns every line on or off.
|
||||
- **Your trail**: your own lines.
|
||||
- **Your trail in first person**: on by default. Turn it off to see your lines only in third person (F5).
|
||||
- **Other players' trails**: the lines of other players within 48 blocks.
|
||||
- **Color**: see the list above.
|
||||
- **Duration**: how long a line lasts, 1 to 12 seconds (default 5).
|
||||
- **Width**: thin, normal or wide.
|
||||
- **Pattern**: solid, dashed or wavy.
|
||||
- **Pulse**: on or off.
|
||||
- **Day/night brightness**: off by default (same brightness at any time). On: lines are brighter at night and dimmer in the day.
|
||||
- **Optimization** (submenu):
|
||||
- **Your wings**: dynamic (the lines follow the real wing movement) or fixed (the line is computed once when gliding starts and then only moves with your body, which is cheaper).
|
||||
- **Other players' wings**: dynamic or fixed, the same for other players.
|
||||
- **Line detail**: full or half (half draws every second segment, about half the lines).
|
||||
- **Other players' range**: 24, 48 or 96 blocks.
|
||||
- **Formation lines**: on or off.
|
||||
- **Boost flash**: on or off.
|
||||
|
||||
## Credits
|
||||
|
||||
- ElytraTrails by [dbrighthd](https://github.com/dbrighthd/elytratrails) (MPL-2.0), the original mod. The idea of wing trails comes from it.
|
||||
- This is a separate implementation for Horizon, written from scratch in Rust. It does not contain code from the original mod.
|
||||
|
||||
License
|
||||
|
||||
MIT. See LICENSE."""
|
||||
authors = ["soufi"]
|
||||
owners = ["soufied"]
|
||||
license = "MIT"
|
||||
source = "https://github.com/soufied/ElytraTrails-Horizon"
|
||||
icon = "https://raw.githubusercontent.com/soufied/ElytraTrails-Horizon/refs/heads/main/icons/elytratrails.png"
|
||||
key = "RWT6eNq4e5WfVDA2lzOOi6XCm/+jJgilqT1+HUUM33ie5MCjcRfSg760"
|
||||
|
||||
[[versions]]
|
||||
version = "0.1.1"
|
||||
published = "2026-11-04"
|
||||
game = "26.3"
|
||||
file = "elytratrails-0.1.1.hnmod"
|
||||
sha256 = "feea003d336b3275be563c1a1ae7c4d455385d6c1b75712ef879861e121ba198"
|
||||
signature = """
|
||||
untrusted comment: signature from minisign secret key
|
||||
RUT6eNq4e5WfVKBjiTqR7gbjglmDfuhOIdATC/awPevLUyV9sM16EwDCZwn4VxOgfNuuN0TmYr2ibtINbtsiO5NM2H8yzVwmkAw=
|
||||
trusted comment: timestamp:1793818562\tfile:elytratrails-0.1.1.hnmod\thashed
|
||||
+74DRK4ausg/UhM7gcmF5VgafBkcLHJ8TmIk2V1dRk/h+9rMuNhLV7YIeCnplg8BOstJUuBRauVxNkcn9uEnAw=="""
|
||||
changelog = "Rewritten."
|
||||
@@ -13,6 +13,7 @@ Every enchantment on books and gear gets a one-line description in its tooltip:
|
||||
Settings: Options → Mods → Enchantment Hints. Inspired by [Enchantment Descriptions](https://modrinth.com/mod/enchantment-descriptions); not affiliated, no code or text from it."""
|
||||
authors = ["d3grando"]
|
||||
owners = ["d3grando"]
|
||||
owner_ids = [36463198]
|
||||
license = "MIT"
|
||||
source = "https://github.com/d3grando/enchantmenthints"
|
||||
icon = "icons/enchantmenthints.png"
|
||||
|
||||
@@ -21,6 +21,7 @@ While flying, your player doesn't move, attack, mine or use items.
|
||||
Some servers don't allow freecam, so check their rules first."""
|
||||
authors = ["Lutziano888"]
|
||||
owners = ["Lutziano888"]
|
||||
owner_ids = [129792568]
|
||||
license = "MIT"
|
||||
source = "https://github.com/Lutziano888/horizon-freecam"
|
||||
icon = "icons/fair_freecam.png"
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
id = "fast_items"
|
||||
kind = "native-mod"
|
||||
name = "Fast Items for Horizon"
|
||||
summary = "Optimize item rendering by transforming item entities to 2D"
|
||||
description = """
|
||||
Fast Items (2D Items)
|
||||
Optimizes item rendering by transforming items to 2D forms.
|
||||
|
||||
For 2D items(such as potions), this mod avoids rendering their sides, so they are completely flat
|
||||
|
||||
For 3D items(such as cube blocks), this mod makes them always face to camera, just as the same behaviour in Lunar Client/BLC."""
|
||||
authors = ["Lyvt (dc: _.lyvt)"]
|
||||
owners = ["Lyvt-Dev"]
|
||||
owner_ids = [138261776]
|
||||
license = "MIT"
|
||||
source = "https://github.com/Lyvt-Dev/FastItems-for-Horizon"
|
||||
icon = "https://github.com/Lyvt-Dev/FastItems-for-Horizon/tree/main/logo/fastitems.png"
|
||||
key = "RWQ84d8BvD1EaarVDgHgXyz+MDiL7ONsCZc0BUrJqTmqzXBItnJy918A"
|
||||
|
||||
[[versions]]
|
||||
version = "0.1.0"
|
||||
published = "2026-11-04"
|
||||
game = "26.3"
|
||||
file = "fast_items-0.1.0.hnmod"
|
||||
sha256 = "c3acd8adfeac6450c3a13b5172c8abb298cb3ceb355dfc539b4bdd653ef15502"
|
||||
signature = """
|
||||
untrusted comment: signature from minisign secret key\r
|
||||
RUQ84d8BvD1EaR6fPq1+iCBwAhUosfQDHMjVQLYQkIRcmU7I9Ls7Z3nC8wFUF4BOzgB4DaP5UonvlkAG4wqNMZH6EnRLmwaGqgw=\r
|
||||
trusted comment: Fast Items for Horizon 0.1.0; fast-items-0.1.0.hnmod\r
|
||||
iqcDMYeA2LekavHGy7yMhtCtH6kCyjq7x76cVPD994O+CYj0lfW4tZL1HSgtXQJFM+3Gw3h2xr55DndjcqFiAA=="""
|
||||
changelog = """
|
||||
Optimizes item rendering by transforming items to 2D forms.
|
||||
|
||||
For 2D items(such as potions), this mod avoids rendering their sides, so they are completely flat
|
||||
|
||||
For 3D items(such as cube blocks), this mod makes them always face to camera, just as the same behaviour in Lunar Client/BLC."""
|
||||
@@ -5,6 +5,7 @@ summary = "Custom accessory mod"
|
||||
description = "A cosmetics catalog for Horizon. Open it from the title screen or the pause menu and wear HD capes that move like cloth, animated bandanas with Japanese script, hats, wings, shoulder pets and auras with colour themes. Turn your player in a live preview, hide your armor, and add your own capes and Blockbench cosmetics with animations. Only you see them."
|
||||
authors = ["_idom_"]
|
||||
owners = ["umbx-dot"]
|
||||
owner_ids = [203141410]
|
||||
license = "MIT"
|
||||
source = "https://github.com/umbx-dot/horizon-flair"
|
||||
icon = "https://raw.githubusercontent.com/umbx-dot/horizon-flair/main/icon.png"
|
||||
|
||||
@@ -5,6 +5,7 @@ summary = "Allows you to change your fly speed"
|
||||
description = "Fly 0.1x to 20x your normal speed: /flyspeed, /flyspeed +1 to step, /flyspeed reset for 1x. Native Horizon mod."
|
||||
authors = ["ImmortalDev"]
|
||||
owners = ["ImmortalWhisper"]
|
||||
owner_ids = [147957682]
|
||||
license = "All Rights Reserved"
|
||||
source = "https://github.com/ImmortalWhisper/Fly-Speed"
|
||||
key = "RWSy3RAfbzD2a0WMm0tQlVl+3GHigjwvwXKvzvnwfmoYrIAoIffbrSbP"
|
||||
|
||||
@@ -18,6 +18,7 @@ Options > Mods: it rests at once, and the next build leaves it out. Its source i
|
||||
"""
|
||||
authors = ["Horizon"]
|
||||
owners = ["ludovic111"]
|
||||
owner_ids = [136255825]
|
||||
license = "MIT"
|
||||
source = "https://github.com/horizon-rust/horizon-mods/tree/main/native/fps-overlay"
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ summary = "Freecam mod ported for Horizon"
|
||||
description = "A rewrite for Horizon of the Freecam mod by the Freecam authors (https://github.com/MinecraftFreecam/Freecam, MIT; on Modrinth as freecam). Its licence notice is kept in the source."
|
||||
authors = ["hakergeniusz"]
|
||||
owners = ["hakergeniusz"]
|
||||
owner_ids = [117102435]
|
||||
license = "MIT"
|
||||
source = "https://github.com/hakergeniusz/freecam"
|
||||
key = "RWR4cJC6Jj3EWGi26o29Rs3hAIPWqf8koFdXV4bBLF/Tm84QPfRAYjO5"
|
||||
|
||||
@@ -8,6 +8,7 @@ Press U and your camera leaves your body. Fly it with WASD, Space and Shift, hol
|
||||
Since this is a native mod it will not work in v0.2.97 as there are no native hooks possible. This works on all nightly releases and LIKELY on version upwards from v0.2.97."""
|
||||
authors = ["_idom_"]
|
||||
owners = ["umbx-dot"]
|
||||
owner_ids = [203141410]
|
||||
license = "MIT"
|
||||
source = "https://github.com/umbx-dot/horizon-freecam"
|
||||
icon = "icons/freecam_plus.png"
|
||||
|
||||
@@ -7,6 +7,7 @@ Freelook is a Freelook/Perspective Mod for Fabric, allowing you to move your cam
|
||||
Camera distance can be adjusted by scrolling."""
|
||||
authors = ["Krzysix17"]
|
||||
owners = ["Krzysix17"]
|
||||
owner_ids = [350813197]
|
||||
license = "MIT"
|
||||
source = "https://github.com/Krzysix17/horizon-freelook"
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ See in the dark without torches or night vision. MoreGamma lets you set the brig
|
||||
Client-side: the commands are handled by your game before anything reaches a server, so it works in single player, on LAN and on servers. Your brightness slider and `options.txt` are never changed, so turning it off gives your own brightness back. Settings are remembered between sessions."""
|
||||
authors = ["bezi5kt"]
|
||||
owners = ["bezi5kt"]
|
||||
owner_ids = [45046654]
|
||||
license = "MIT"
|
||||
source = "https://github.com/bezi5kt/gamma-rust"
|
||||
icon = "icons/fullbright.png"
|
||||
|
||||
@@ -11,6 +11,7 @@ Smooth 250ms brightness fade transition and HUD status notification.
|
||||
100% client-side lightmap modification, safe for multiplayer."""
|
||||
authors = ["CayMac"]
|
||||
owners = ["CayMac97"]
|
||||
owner_ids = [197672048]
|
||||
license = "MIT"
|
||||
source = "https://github.com/CayMac97/fullbright"
|
||||
key = "RWS411U2KXdTN4/y5bQhqBYxNlZvCtSZ5lNy46eeSAcgy0cxSJo2+Sm3"
|
||||
|
||||
@@ -13,6 +13,7 @@ A native Horizon mod: the launcher builds it into the game (install it on the la
|
||||
Toggling is silent (no chat messages). Client-side: works in single player, on LAN and on servers. Settings are remembered between sessions."""
|
||||
authors = ["Nirokx"]
|
||||
owners = ["Nirokx"]
|
||||
owner_ids = [95123456]
|
||||
license = "MIT"
|
||||
source = "https://github.com/Nirokx/horizon-fullbright-nofog"
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
id = "gyro"
|
||||
kind = "native-mod"
|
||||
name = "Gyro"
|
||||
summary = "Full 6DOF flight physics and immersive gyro camera tilt for Minecraft Horizon, inspired by Do a Barrel Roll."
|
||||
description = """
|
||||
**Fly an elytra like an aircraft.** Gyro swaps Horizon's locked horizon for full six-degree-of-freedom control: bank into a roll, pull through it to carve a turn, push the nose past vertical and loop. Inspired by the feel of the Java mod *Do a Barrel Roll*, written from scratch on Horizon's native hook points.
|
||||
|
||||
| While gliding | What it does |
|
||||
|---|---|
|
||||
| Mouse left / right | Roll — a full 360° with no snap-back |
|
||||
| Mouse up / down | Pitch through your current bank, so a wing-down pull carves a turn |
|
||||
| `A` / `D` | Rudder yaw for fine heading corrections |
|
||||
|
||||
In third person your avatar, elytra and held item stay pinned to the camera while the world, sky and entities roll around you. When you stop gliding, the view eases back to level and the mouse is an ordinary camera again. Everything else stays vanilla — no keybinds, no config, no setup, just one row in **Options > Mods**.
|
||||
|
||||
**What it actually touches.** Client-side camera matrices and the player's own model only. There is no network, filesystem or process access anywhere in the source, no server hooks and no server component; the server never hears about the roll. It declares `hooks: 3`, so the launcher builds it on any Horizon whose native hook API is ≥ 3 rather than one pinned commit. Built and tested against Horizon `ba6344a` with horizon-mixins `d9c83d70`. The whole mod is seven short Rust files plus one mixin manifest under `mixins/gyro/` — read them before you build.
|
||||
|
||||
*Native mods are unsandboxed code running with your account's permissions; install only from authors you trust.*
|
||||
|
||||
Source & build guide: https://github.com/CGRLabs/gyro
|
||||
|
||||
Community discovery & mod docs: https://horizon-hub-seven.vercel.app (independent, unofficial — indexes the official registry)
|
||||
"""
|
||||
authors = ["CGRLabs"]
|
||||
owners = ["CGRLabs"]
|
||||
license = "MIT"
|
||||
source = "https://github.com/CGRLabs/gyro"
|
||||
icon = "https://weaigubwhdajcehreyau.supabase.co/storage/v1/object/public/icons/gyro.png"
|
||||
key = "RWSjKGIJUKKXHvvhxzNN2VPPrXSEr47JXVgGpCwrOOHoneuQjTWWvk1g"
|
||||
|
||||
[[versions]]
|
||||
version = "1.4.1"
|
||||
published = "2026-11-05"
|
||||
game = "26.3"
|
||||
file = "gyro-1.4.1.hnmod"
|
||||
sha256 = "2271d5b7e1559544a12e9198523c643bddd2551828ecf0a39b92f7d120b1921b"
|
||||
signature = """
|
||||
untrusted comment: signature from Horizon Hub signing key (CGRLabs)
|
||||
RUSjKGIJUKKXHmpe6jlROY1yCZ3ZOf3ud+zshvosQahcCBCr1PIsSnnLgeLiuaxjxr55MkO9i9r4pKIUqRkcIY8y/R3pfi+QSgY=
|
||||
trusted comment: timestamp:1793853001\tfile:gyro-1.4.1.hnmod
|
||||
uqpsuNh06lQVrKGSKBbJ9J6y4A3bg0TlGEepMy+5F3G3Iuokw6bu2WqSOjNWS7u8qZdzVBLXontxfjb6gzUkBQ=="""
|
||||
changelog = """
|
||||
The bundle now declares native hook points (`hooks: 3`), so the launcher builds it on any Horizon with native hook API v3 or newer instead of only the pinned commit.
|
||||
|
||||
- Deterministic packaging: identical sources give identical bytes and SHA-256; `tools/package.py --check-hooks` cross-checks the pinned commit's real API.
|
||||
- Mod row (id, version, description) now comes from `runtime.rs` constants.
|
||||
- Added `proof.py`, an in-game proof harness driving the modded client on throwaway data folders.
|
||||
- Player-facing README rewrite.
|
||||
|
||||
Client-side camera and player-model changes only; no network writes and no server component."""
|
||||
@@ -0,0 +1,18 @@
|
||||
id = "held_item_info"
|
||||
kind = "native-mod"
|
||||
name = "Held Item Info"
|
||||
summary = "Selected-item durability, count and enchantment names above the hotbar."
|
||||
description = "Minimal client-only selected-item text HUD for Horizon 26.3. Displays remaining/maximum durability, stack count and up to six translated enchantment names; long strings are shortened to GUI width. Original MIT implementation inspired by A5b84/held-item-info; no LGPL source was read or included. Potion details, layout options and fading are not implemented. Existing Armor Status covers equipped-item icons/durability; this mod focuses on selected-item text and enchantment names. See the source for validation and exact engine/mixin pins."
|
||||
authors = ["AltMugen"]
|
||||
owners = ["AltMugen"]
|
||||
owner_ids = [108084724]
|
||||
license = "MIT"
|
||||
source = "https://github.com/AltMugen/horizon-held-item-info"
|
||||
|
||||
[[versions]]
|
||||
version = "0.1.0"
|
||||
published = "2026-11-02"
|
||||
game = "26.3"
|
||||
file = "held_item_info-0.1.0.hnmod"
|
||||
sha256 = "37a11d98f0afabe37f8ffa9982dc710ad2985817db6b5e094d807c24d30a6a6a"
|
||||
changelog = "Initial reviewed native release."
|
||||
@@ -21,6 +21,7 @@ Press H to toggle hitboxes on/off.
|
||||
Access Options > Mods > Hitbox+ to customize all colors, thickness, and HUD positions."""
|
||||
authors = ["taxtii", "itzglecni"]
|
||||
owners = ["taxtii"]
|
||||
owner_ids = [347672997]
|
||||
license = "MIT"
|
||||
source = "https://github.com/taxtii/hitbox_plus.git"
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
id = "horizon_physics"
|
||||
kind = "native-mod"
|
||||
name = "PhysicsModHorizon"
|
||||
summary = "Physics for Horizon, inspired by PhysicsMod: ragdolls, waves, physical particles and capes."
|
||||
description = """
|
||||
By tariksabanuz
|
||||
|
||||
Physics for Horizon, inspired by PhysicsMod by Haubna. It is a clean-room version for Horizon: it contains none of PhysicsMod's code or assets and is not made or endorsed by Haubna. Everything is built as a native Horizon mod, so no other mods or loaders are needed.
|
||||
|
||||
Features
|
||||
|
||||
- **Realistic water**: waves that never repeat, built from several swells with wave groups, wind gusts, sun glints, foam on the crests and along the shore, and rain rings on the surface. Boats and the camera follow the same waves you see.
|
||||
- **Ragdolls**: mobs fall and tumble with their bodies instead of just disappearing.
|
||||
- **Physical particles**: block cracks, sprint dust and broken or eaten items become small physical pieces that fall, bounce and settle.
|
||||
- **Capes** that move with you.
|
||||
- **Quick Setup**: on first launch everything is off, so the game starts fast. A short setup lets you pick how much you want, and then turns it on.
|
||||
- **Physics Settings** in Options, with categories, switches and sliders for every effect, so you can tune it to your PC.
|
||||
|
||||
Notes
|
||||
|
||||
- Built for Horizon Nightly `6475f62`. Other versions may need an updated build.
|
||||
- Works with Vulkan and DirectX 12.
|
||||
- If the game feels slow, lower the wave height or turn effects off in Physics Settings.
|
||||
|
||||
Source code and issues: https://github.com/tariksabanuz/PhysicsModHorizon"""
|
||||
authors = ["tariksabanuz"]
|
||||
owners = ["tariksabanuz"]
|
||||
owner_ids = [332662152]
|
||||
license = "MIT"
|
||||
source = "https://github.com/tariksabanuz/PhysicsModHorizon"
|
||||
icon = "icons/horizon_physics.png"
|
||||
|
||||
[[versions]]
|
||||
version = "0.18.0"
|
||||
published = "2026-11-04"
|
||||
game = "26.3"
|
||||
file = "horizon_physics-0.18.0.hnmod"
|
||||
sha256 = "213f6264a20bed7fef64f12f539d0b2364f710857a3acaa351b96e78b3239dc1"
|
||||
|
||||
[[versions]]
|
||||
version = "0.15.1"
|
||||
published = "2026-11-03"
|
||||
game = "26.3"
|
||||
file = "horizon_physics-0.15.1.hnmod"
|
||||
sha256 = "d7cf1201f0a47a852aa1a4c9684ef9e658ec3c86852104d44716d05f11281ca3"
|
||||
changelog = "First release: block, mob, item, snow, water, smoke, cloth, weather and collapse physics, with a Physics Settings screen."
|
||||
@@ -34,6 +34,7 @@ The idea, the way it's used, the file format and the behaviour come from **[Lite
|
||||
Menus built with [horizon-menus](https://github.com/keksi2k/horizon-menus). MIT licensed."""
|
||||
authors = ["keksi2k"]
|
||||
owners = ["keksi2k"]
|
||||
owner_ids = [32579030]
|
||||
license = "MIT"
|
||||
source = "https://github.com/keksi2k/horizonmatica"
|
||||
icon = "https://raw.githubusercontent.com/keksi2k/horizonmatica/main/icon.png"
|
||||
|
||||
@@ -5,6 +5,7 @@ summary = "R-key & UI sorting (Category, A-Z, Count, ID), Auto-Refill depleted s
|
||||
description = "A 1:1 native Rust port of Inventory Profiles Next & Inventory Sorter for Horizon. Press R or click the UI sort buttons to merge partial stacks and sort chests or your inventory using 4 selectable modes: Category (Creative Tab), Alphabetical (A-Z), Stack Count (High-Low), or Item ID. Includes automatic hand stack refilling when placing blocks or consuming items, Tool Break Protection that swaps out low-durability tools before they break, and G-key Quick-Stacking to move matching items into open containers."
|
||||
authors = ["CayMac"]
|
||||
owners = ["CayMac97"]
|
||||
owner_ids = [197672048]
|
||||
license = "MIT"
|
||||
source = "https://github.com/CayMac97/inventory-profiles"
|
||||
key = "RWS411U2KXdTN4/y5bQhqBYxNlZvCtSZ5lNy46eeSAcgy0cxSJo2+Sm3"
|
||||
|
||||
@@ -20,6 +20,7 @@ description = """
|
||||
server**: every sort is made of ordinary left clicks, as a player could click them, so a vanilla server accepts it"""
|
||||
authors = ["VanishingTacos"]
|
||||
owners = ["VanishingTacos"]
|
||||
owner_ids = [16919284]
|
||||
license = "MIT"
|
||||
source = "https://github.com/VanishingTacos/InventoryWizard-Horizon"
|
||||
icon = "https://cdn.modrinth.com/data/cU0ynk3p/21b58402405493964af438e3e3b9551676b68433.png"
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
id = "invisible_frames"
|
||||
kind = "native-mod"
|
||||
name = "Invisible Frames"
|
||||
summary = "Sneak-right-click a filled frame to hide and lock it, or restore it."
|
||||
description = "Server-side frame visibility toggling for Horizon 26.3. Ordinary and glowing frames are supported. Empty visible frames remain visible. Original MIT implementation inspired by ADHDMC/InvisibleFrames; no Java source or assets included. See the source repository for tested engine/mixin pins, build instructions and validation scope."
|
||||
authors = ["AltMugen"]
|
||||
owners = ["AltMugen"]
|
||||
owner_ids = [108084724]
|
||||
license = "MIT"
|
||||
source = "https://github.com/AltMugen/horizon-invisible-frames"
|
||||
|
||||
[[versions]]
|
||||
version = "0.1.1"
|
||||
published = "2026-11-03"
|
||||
game = "26.3"
|
||||
file = "invisible_frames-0.1.1.hnmod"
|
||||
sha256 = "76128b48ce5c826007379c85634b934ae6bad51c134fbaead7c46b36fecd9274"
|
||||
changelog = "Use the acting player's authoritative server sneak input instead of physical keyboard state."
|
||||
@@ -13,6 +13,7 @@ Items you just got (picked up, crafted, taken from a chest, given) get a soft gl
|
||||
Settings: Options → Mods → Item New. Inspired by [Item Highlighter](https://modrinth.com/mod/item-highlighter); not affiliated, no code from it."""
|
||||
authors = ["d3grando"]
|
||||
owners = ["d3grando"]
|
||||
owner_ids = [36463198]
|
||||
license = "MIT"
|
||||
source = "https://github.com/d3grando/itemnew"
|
||||
icon = "icons/itemnew.png"
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
id = "jade"
|
||||
kind = "native-mod"
|
||||
name = "Jade"
|
||||
summary = "The look-at tooltip: what you're pointing at, its mod, its details and its state."
|
||||
description = """
|
||||
An unofficial port of [Jade](https://github.com/Snownee/Jade) 26.3.5 by Snownee to Horizon: the look-at overlay for blocks and entities, with its providers, its settings under Options > Mods, its keys, and the `/jade` and `/jadec` commands.
|
||||
|
||||
Rewritten in Rust for Horizon's hooks, so no original code runs. Not made or endorsed by Snownee, so please report problems to this project, not to them.
|
||||
|
||||
**Ported:** themes, opacity, text background, profiles, ignore lists, boss bar overlap, TTS, perspective mode, reach, icon positions, the item mod name and the plugin sub-options.
|
||||
|
||||
**What differs from Jade:**
|
||||
- Themes are drawn with solid fills in the theme's colours, not Jade's sprites.
|
||||
- The position adjust screen is a setting you cycle through, not a screen you drag on.
|
||||
- The sniffer's dig cooldown isn't shown.
|
||||
- Energy Storage has nothing to show, because Horizon has no shared energy API for other mods.
|
||||
- Access variant and NPC details are approximate.
|
||||
|
||||
Tested on Windows, in single player and on LAN with one guest. Dedicated servers don't build native mods, so "server" here means a LAN host.
|
||||
|
||||
Licence: CC BY-NC-SA 4.0, noncommercial use only, the same licence as Jade."""
|
||||
authors = ["Snownee", "Joeypc6"]
|
||||
owners = ["Joeypc6"]
|
||||
owner_ids = [35495484]
|
||||
license = "CC-BY-NC-SA-4.0"
|
||||
source = "https://github.com/Joeypc6/Jade-Horizon"
|
||||
icon = "https://media.forgecdn.net/avatars/thumbnails/207/323/256/256/636965628804677340.png"
|
||||
|
||||
[[versions]]
|
||||
version = "26.3.5"
|
||||
published = "2026-11-04"
|
||||
game = "26.3"
|
||||
file = "jade-26.3.5.hnmod"
|
||||
sha256 = "676a129f55db075284313a39ff4387674c426ebd9be04a8830ffb8955281c7ec"
|
||||
changelog = "First Horizon release, a port of Jade 26.3.5. Built and tested on Horizon commit c9d474d."
|
||||
@@ -37,6 +37,7 @@ Horizon JEI reads the game's tables, container interface, chat, and its own sett
|
||||
*Unofficial project. Not affiliated with mezz, Mojang, Microsoft, or the Horizon project.*"""
|
||||
authors = ["enderliker"]
|
||||
owners = ["enderliker"]
|
||||
owner_ids = [166346005]
|
||||
license = "MIT"
|
||||
source = "https://github.com/enderliker/horizon-jei"
|
||||
key = "RWRqzx718YYz/OHRM+pAnFWlDv8Rrgar5C4NszQg3EKJWQZWNel5Sslf"
|
||||
|
||||
@@ -18,6 +18,7 @@ Options > Mods: it rests at once, and the next build leaves it out. Its source i
|
||||
"""
|
||||
authors = ["Horizon"]
|
||||
owners = ["ludovic111"]
|
||||
owner_ids = [136255825]
|
||||
license = "MIT"
|
||||
source = "https://github.com/horizon-rust/horizon-mods/tree/main/native/keystrokes"
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
id = "killc"
|
||||
kind = "native-mod"
|
||||
owners = ["Vakarux12"]
|
||||
owner_ids = [167799729]
|
||||
key = "RWS5u/7lEyKbzVHC6uMJRlhM/N7vzKp5fZe2LCDUlHsDK4lvPupxJbqQ"
|
||||
name = "KillC"
|
||||
summary = "/killc, /killc <player> and /suicide, with an optional random respawn near (0, 0)."
|
||||
description = """
|
||||
A Horizon port of [adasjusk's KillC](https://github.com/adasjusk/KillC), the kill command plugin for Paper, Folia, Velocity and Fabric.
|
||||
|
||||
- `/killc` and `/suicide` kill you.
|
||||
- `/killc <player>` kills another player (operator level 2).
|
||||
- After a death in the Overworld, players respawn at a random surface spot within `randomSpawnRadius` of (0, 0).
|
||||
- Config: `config/killc.json` (`enabled`, `useRandomSpawn`, `randomSpawnRadius`), the same file as the Fabric build.
|
||||
|
||||
Vanilla keeps `/kill`. Runs where the world is hosted: single player and worlds opened to LAN (Horizon's dedicated server cannot build native mods yet). Native mods run with the player's account permissions and are not sandboxed.
|
||||
|
||||
Source: `horizon/` in https://github.com/Vakarux12/KillC, a fork of the original. CC-BY-SA-4.0, the original's licence, with a grant for Horizon's reviewers in `horizon/REVIEW-GRANT.md`.
|
||||
"""
|
||||
authors = ["adasjusk", "Vakarux12"]
|
||||
license = "CC-BY-SA-4.0"
|
||||
source = "https://github.com/Vakarux12/KillC/tree/main/horizon"
|
||||
|
||||
[[versions]]
|
||||
version = "5.1.0"
|
||||
published = "2026-11-01"
|
||||
game = "26.3"
|
||||
file = "killc-5.1.0.hnmod"
|
||||
sha256 = "6c44d0a5e93aef42f2a33df8286980d9957a04526ff2c1884dfe7aa83f837086"
|
||||
signature = """
|
||||
untrusted comment: signature from minisign secret key
|
||||
RUS5u/7lEyKbzbHxeuREFeXgBYM6FjB/wiwBtmdu5na35k0DXINdt3FILWvtW7yDekrFYjn4Xrl5pv9ttijPbocgpNbqZpPqLgY=
|
||||
trusted comment: timestamp:1793694719 file:killc-5.1.0.hnmod hashed
|
||||
vC2AGwA5JYAw1yjFLScjYyy+ef03gA5lxE3mwpD/D6CqKRblrpIKtHacgjYViC714ob5w1fgXFPya8AobYmVBg=="""
|
||||
changelog = "First Horizon release, ported from adasjusk's KillC."
|
||||
@@ -5,6 +5,7 @@ summary = "1:1 Light Overlay & MiniHUD (F7): visualizes mob-spawnable blocks wit
|
||||
description = "A 1:1 native Rust port of Light Overlay and MiniHUD's F7 Light Level Overlay for Horizon. Press F7 during gameplay to scan nearby spawnable block surfaces and render depth-tested 3D markers directly on block tops. Red markers indicate blocks where hostile mobs can spawn at any time (caves/indoor darkness), Yellow markers indicate blocks where hostile mobs can spawn at night, and optional Green markers show safe blocks. Supports Crosses (X), 3D Numbers (0-15), or Both, with configurable scan radius (8 to 20 blocks) and Modern (== 0) or Legacy (<= 7) spawn thresholds."
|
||||
authors = ["CayMac"]
|
||||
owners = ["CayMac97"]
|
||||
owner_ids = [197672048]
|
||||
license = "MIT"
|
||||
source = "https://github.com/CayMac97/light-overlay"
|
||||
key = "RWS411U2KXdTN4/y5bQhqBYxNlZvCtSZ5lNy46eeSAcgy0cxSJo2+Sm3"
|
||||
|
||||
@@ -14,6 +14,7 @@ The block or entity under your crosshair at the top of the screen, drawn as a va
|
||||
**Now a native mod (0.2.1)**: the launcher builds it into the game (install it on the launcher's Mods page, then press Build there). Native mods are not sandboxed and run with your account's permissions, so read the source before you build it. Tested against Horizon `142388e1c136e622652ec7505af15d8ad195e041` with Horizon Mixins `0d6b103c70633d8fd9443d8d0acd3aadaacedb06`; the launcher builds these exact pinned revisions. Settings from 0.1.0 carry over."""
|
||||
authors = ["d3grando"]
|
||||
owners = ["d3grando"]
|
||||
owner_ids = [36463198]
|
||||
license = "MIT"
|
||||
source = "https://github.com/d3grando/lookat"
|
||||
icon = "icons/lookat.png"
|
||||
|
||||
@@ -18,6 +18,7 @@ The pack only changes `assets/minecraft/textures/gui/title/background/panorama_0
|
||||
MIT licensed. Source, the browser generator and the CLI are in the repository."""
|
||||
authors = ["form1t"]
|
||||
owners = ["form1t"]
|
||||
owner_ids = [230844871]
|
||||
license = "MIT"
|
||||
source = "https://github.com/form1t/menu-panorama"
|
||||
icon = "icons/menu_panorama.png"
|
||||
|
||||
@@ -18,6 +18,7 @@ Options > Mods: it rests at once, and the next build leaves it out. Its source i
|
||||
"""
|
||||
authors = ["Horizon"]
|
||||
owners = ["ludovic111"]
|
||||
owner_ids = [136255825]
|
||||
license = "MIT"
|
||||
source = "https://github.com/horizon-rust/horizon-mods/tree/main/native/minimap"
|
||||
|
||||
|
||||
+9
-1
@@ -10,9 +10,17 @@ Adds a Mods button next to Accessibility on the title screen. It opens a Mod Men
|
||||
- Open Mods Folder and Done at the bottom, as in the Java mod."""
|
||||
authors = ["tariksabanuz"]
|
||||
owners = ["tariksabanuz"]
|
||||
owner_ids = [332662152]
|
||||
license = "MIT"
|
||||
source = "https://github.com/tariksabanuz/ModMenuHorizon"
|
||||
icon = "https://raw.githubusercontent.com/tariksabanuz/ModMenuHorizon/main/icon.png"
|
||||
icon = "https://raw.githubusercontent.com/tariksabanuz/ModMenuHorizon/caec7c4c3a2c6c7746a97d137ccf8f8904801e72/icon.png"
|
||||
|
||||
[[versions]]
|
||||
version = "0.3.3"
|
||||
published = "2026-11-05"
|
||||
game = "26.3"
|
||||
file = "mod_menu-0.3.3.hnmod"
|
||||
sha256 = "657b0b6ce1f1ac44afa8d7410aa5b16ccf9b6a271024b4e5c1767fa377f98825"
|
||||
|
||||
[[versions]]
|
||||
version = "0.3.0"
|
||||
|
||||
@@ -10,6 +10,7 @@ Each frame the mod works out every pixel's motion since the last frame (the worl
|
||||
Settings are in Options → Mods → Motion Blur: strength for moving things and the camera, shutter angle or a fixed exposure, the longest blur, quality, blurring yourself in third person, and a view that shows each pixel's motion."""
|
||||
authors = ["levzzz5154"]
|
||||
owners = ["levzzz5154"]
|
||||
owner_ids = [86072227]
|
||||
license = "MIT"
|
||||
source = "https://github.com/levzzz5154/horizon-motion-blur"
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ description = """
|
||||
Everything goes through the game's own container clicks, so servers accept it. Each trick is a setting (Options → Mods → Mouse Tricks). Inspired by [Mouse Tweaks](https://modrinth.com/mod/mouse-tweaks); not affiliated, no code from it."""
|
||||
authors = ["d3grando"]
|
||||
owners = ["d3grando"]
|
||||
owner_ids = [36463198]
|
||||
license = "MIT"
|
||||
source = "https://github.com/d3grando/mousetricks"
|
||||
icon = "icons/mousetricks.png"
|
||||
|
||||
@@ -2,3 +2,4 @@ id = "radiant"
|
||||
kind = "shader"
|
||||
repo = "expensiverest/radiant"
|
||||
owners = ["expensiverest"]
|
||||
owner_ids = [281485024]
|
||||
@@ -22,6 +22,7 @@ once, and the next build leaves it out. Its source is
|
||||
"""
|
||||
authors = ["Horizon"]
|
||||
owners = ["ludovic111"]
|
||||
owner_ids = [136255825]
|
||||
license = "MIT"
|
||||
source = "https://github.com/horizon-rust/horizon-mods/tree/main/native/recipes"
|
||||
|
||||
|
||||
+23
-8
@@ -1,21 +1,36 @@
|
||||
id = "resourcify"
|
||||
kind = "native-mod"
|
||||
name = "Resourcify"
|
||||
summary = "Download resource packs in Horizon"
|
||||
name = "ResourcifyHorizon"
|
||||
summary = "Resourcify for Horizon"
|
||||
description = """
|
||||
Resourcify lets you browse and download resource packs without ever leaving Horizon.
|
||||
Find and install resource packs from Modrinth without leaving the game. By tariksabanuz.
|
||||
|
||||
Features:
|
||||
- Browse resource packs right inside the game
|
||||
- One-click download and install
|
||||
- Search to find the pack you want quickly
|
||||
- Native Horizon mod, fast and lightweight"""
|
||||
A **+** button appears in the top right of the Resource Packs screen. It opens a browser with:
|
||||
|
||||
- a search box that searches while you type, a sort button (Relevance, Downloads, Newest, Updated) and a switch between "for this game version" and "all versions";
|
||||
- a list of packs with pictures, authors and download counts, loaded as you scroll;
|
||||
- a detail pane with the versions of the selected pack and an **Install** button.
|
||||
|
||||
Installing downloads the file from Modrinth's own server (`cdn.modrinth.com` only), checks it against the SHA-1 hash Modrinth provides, and saves it in `resourcepacks/`. Nothing is turned on for you.
|
||||
|
||||
Source code: https://github.com/tariksabanuz/ResourcifyHorizon"""
|
||||
authors = ["tariksabanuz"]
|
||||
owners = ["tariksabanuz"]
|
||||
owner_ids = [332662152]
|
||||
license = "MIT"
|
||||
source = "https://github.com/tariksabanuz/ResourcifyHorizon"
|
||||
icon = "https://github.com/tariksabanuz/ResourcifyHorizon/blob/364648324ac0e67489a3f06faa716d96d24668ed/icon.png"
|
||||
|
||||
[[versions]]
|
||||
version = "0.1.1"
|
||||
published = "2026-11-04"
|
||||
game = "26.3"
|
||||
file = "resourcify-0.1.1.hnmod"
|
||||
sha256 = "a6611819c9401dc7370dece7453cdda2356747f2a34c32940bf82ccd6abdcf53"
|
||||
changelog = """
|
||||
- Version 0.1.1.
|
||||
- Rebuilt and re-checked against the current Horizon revision."""
|
||||
|
||||
[[versions]]
|
||||
version = "0.1.0"
|
||||
published = "2026-11-03"
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
id = "right_click_crops"
|
||||
kind = "native-mod"
|
||||
name = "Right-Click Crops"
|
||||
summary = "Right-click harvest for 16 plant families, with replanting and individual settings."
|
||||
description = """
|
||||
Harvest mature plants with a right-click, replant crops, and keep your garden growing.
|
||||
|
||||
### Features
|
||||
- Supports 16 plant families.
|
||||
- Replants crops using one planting item from the harvest, or from your inventory.
|
||||
- Keeps the base of tall plants and the stems of pumpkins and melons.
|
||||
- Lets you enable or disable each plant separately in **Options > Mods > Right-Click Crops > Settings**.
|
||||
|
||||
### Supported plants
|
||||
Wheat, carrots, potatoes, beetroot, nether wart, cocoa, torchflower, pitcher crop, sugar cane, cactus, bamboo, kelp, twisting vines, weeping vines, pumpkin and melon.
|
||||
|
||||
### Good to know
|
||||
- Harvesting works in survival, one targeted plant at a time.
|
||||
- Without a suitable planting item, the crop is harvested but not replanted.
|
||||
- Torchflower needs spare seeds; pitcher crop needs spare pitcher pods.
|
||||
- Twisting and weeping vines can drop nothing when harvested by hand. Use shears for guaranteed drops.
|
||||
- Bone meal, ordinary breaking and planting, sweet berries and glow berries keep their normal behaviour.
|
||||
|
||||
### Compatibility
|
||||
Last checked on **November 1, 2026**, for Minecraft 26.3. The release is pinned to Horizon Nightly 2026-10-31 (`9d979ca`), where single-player and LAN were tested. A compilation-only check also passed on the newest Nightly available on that date (`d91cab6`); gameplay on that newer revision was not tested.
|
||||
|
||||
This is a native mod: install only if you trust its source and accept the launcher's native-code warning.
|
||||
|
||||
[Watch the demo](https://youtu.be/kll-qvj5s54) · [Harvesting guide](https://github.com/AyanoKaito/horizon-right-click-crops/blob/main/docs/harvesting.md)
|
||||
"""
|
||||
authors = ["AyanoKaito"]
|
||||
owners = ["AyanoKaito"]
|
||||
owner_ids = [82058189]
|
||||
license = "MIT"
|
||||
source = "https://github.com/AyanoKaito/horizon-right-click-crops"
|
||||
icon = "icons/right_click_crops.png"
|
||||
key = "RWRwbuaByP5mM4VUm4kZEQVPiuw+3IEVJ7CZcEHIjY5fPKf9lhQKUZRy"
|
||||
|
||||
[[versions]]
|
||||
version = "1.0.0"
|
||||
published = "2026-11-01"
|
||||
game = "26.3"
|
||||
file = "right_click_crops-1.0.0.hnmod"
|
||||
sha256 = "681ce896c50dd041132900e7bbab71167f133b3f3862f86e8b1a0cac52754b97"
|
||||
signature = """
|
||||
untrusted comment: signature from minisign secret key
|
||||
RURwbuaByP5mM4x8HGWHv/S/WlHPsmamRwsi70PTq7E5u4uJ7wyNzNT4rwpR/vGA9V2H6w9yLhmF1CPtS1grEfWorPixnk6efAw=
|
||||
trusted comment: timestamp:1793523764\tfile:right_click_crops-1.0.0.hnmod\thashed
|
||||
seiWz0D/X34FIKhiE1yfZc2uAkkSGuwAnTHbGrK63sHg+HdL+EgqEkzaxNgJTiMOdnf/iIdSkDVVwuF+Od/SCQ==
|
||||
"""
|
||||
changelog = "First public release: right-click harvesting for 16 plant families, ordinary Horizon loot, paid replanting, retained bases and stems, and individual plant settings. Validated in single-player and for LAN host and guest."
|
||||
@@ -5,6 +5,7 @@ summary = "Right-click a ripe crop to pick it and sow it again."
|
||||
description = "With an empty hand, right-click a ripe wheat, carrot, potato, beetroot or nether wart: the crop is harvested with the game's own drops, sound and statistics, and sown again from a seed in your inventory."
|
||||
authors = ["Domanffe"]
|
||||
owners = ["Domanffe"]
|
||||
owner_ids = [94391234]
|
||||
license = "MIT"
|
||||
source = "https://github.com/Domanffe/rightharvest"
|
||||
icon = "https://horizon-rust.github.io/horizon-mods/icons/rightharvest.png"
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
id = "rmotd"
|
||||
kind = "native-mod"
|
||||
owners = ["Vakarux12"]
|
||||
owner_ids = [167799729]
|
||||
key = "RWS5u/7lEyKbzVHC6uMJRlhM/N7vzKp5fZe2LCDUlHsDK4lvPupxJbqQ"
|
||||
name = "RMotd"
|
||||
summary = "Rotating server list MOTDs, managed in game with /randommotd and /motd."
|
||||
description = """
|
||||
A Horizon port of [adasjusk's RMotd](https://github.com/adasjusk/RMotd), the rotating MOTD plugin.
|
||||
|
||||
- The MOTD in the server list changes every 10 seconds, through the list in `config/rmotd.json` (the Fabric build's file).
|
||||
- `/randommotd` turns rotation on and off.
|
||||
- `/motd create <name> <motd>` adds an MOTD, `/motd reload` reads the file again (operator level 2).
|
||||
- MOTDs take `&` colour codes.
|
||||
|
||||
It sets the MOTD of a world opened to LAN. The server list ping shows the rotation, the LAN broadcast keeps the first one. Horizon has no MOTD hook point, so a support module in `horizon_client::lan` writes the private MOTD field of the LAN server. Its injections are all hook points, but that field is not a stable API, so a later Nightly can break the build. Runs where the world is hosted: single player and worlds opened to LAN (Horizon's dedicated server cannot build native mods yet). Native mods run with the player's account permissions and are not sandboxed.
|
||||
|
||||
Source: `horizon/` in https://github.com/Vakarux12/RMotd, a fork of the original. CC-BY-SA-4.0, the original's licence, with a grant for Horizon's reviewers in `horizon/REVIEW-GRANT.md`.
|
||||
"""
|
||||
authors = ["adasjusk", "Vakarux12"]
|
||||
license = "CC-BY-SA-4.0"
|
||||
source = "https://github.com/Vakarux12/RMotd/tree/main/horizon"
|
||||
|
||||
[[versions]]
|
||||
version = "1.0.0"
|
||||
published = "2026-11-01"
|
||||
game = "26.3"
|
||||
file = "rmotd-1.0.0.hnmod"
|
||||
sha256 = "68d43b760e95178a67bc1c7d54fbde004eee4bb764d9891d83bd4e4fa4bd8ce5"
|
||||
signature = """
|
||||
untrusted comment: signature from minisign secret key
|
||||
RUS5u/7lEyKbzSnV+LLOgmX/1ryZ/5d3asf4KyfdUUOJKALk9isprehFuFIDN5X+GZy0C7jzHAImFnq9FRt2yc1J3N5lbA1oUgM=
|
||||
trusted comment: timestamp:1793694734 file:rmotd-1.0.0.hnmod hashed
|
||||
x+Ecd8KV0OFBcUEONAvAHxw87IG2MnjWZzcrIiMLjRNtl7KcuZQXvakrAm6slC1qADzj+n+HpwYoGT1QmQ07Bg=="""
|
||||
changelog = "First Horizon release, ported from adasjusk's RMotd."
|
||||
@@ -38,6 +38,7 @@ Install it from the launcher's Mods page, then press Build there, and play that
|
||||
Native mods are not sandboxed. The source is public and licensed GPL-3.0-only: https://github.com/Louchatfroff/serverify/tree/main/horizon/serverify"""
|
||||
authors = ["Louchatfroff"]
|
||||
owners = ["Louchatfroff"]
|
||||
owner_ids = [87802271]
|
||||
license = "GPL-3.0-only"
|
||||
source = "https://github.com/Louchatfroff/serverify"
|
||||
icon = "icons/serverify.png"
|
||||
|
||||
@@ -19,6 +19,7 @@ It never changes items, inventories or worlds. Turn it off any time in Options >
|
||||
This is a native mod built for Horizon Nightly 2026-10-29 (`5e391eb`). It builds together with Horizon's official native mods (FPS Counter, Zoom, Minimap, Recipes and others)."""
|
||||
authors = ["vstxx"]
|
||||
owners = ["vstxx"]
|
||||
owner_ids = [106024432]
|
||||
license = "MIT"
|
||||
source = "https://github.com/vstxx/shulkerpeek"
|
||||
icon = "icons/shulker_peek.png"
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
id = "skins_3d"
|
||||
kind = "native-mod"
|
||||
name = "3D Skins"
|
||||
summary = "The outer layer of every player's skin drawn as real 3D pixels."
|
||||
description = "The outer layer of every player’s skin drawn as real 3D pixels: hat, jacket, sleeves and pants follow the head, arms and legs exactly as the game moves them. Works for everyone you can see, with downloaded, launcher and default skins, classic and slim arms and HD skins, respects Skin Customization, and switches to the flat layer further away. Client side only."
|
||||
authors = ["_idom_"]
|
||||
owners = ["umbx-dot"]
|
||||
license = "MIT"
|
||||
source = "https://github.com/umbx-dot/horizon-3d-skins"
|
||||
icon = "https://raw.githubusercontent.com/umbx-dot/horizon-3d-skins/main/icon.png"
|
||||
key = "RWT9Ujo/oCprPGHHiSLHFNOJV1ZzoJdAU6EdZyiQcBwT2iMXfiYms4hD"
|
||||
|
||||
[[versions]]
|
||||
version = "1.0.1"
|
||||
published = "2026-11-04"
|
||||
game = "26.3"
|
||||
file = "skins_3d-1.0.1.hnmod"
|
||||
sha256 = "c9d618c4bedaa92c9373d7363b182eff9f4723889dcb5ba8a877c78ffc0fb6ae"
|
||||
signature = """
|
||||
untrusted comment: signature from minisign secret key\r
|
||||
RUT9Ujo/oCprPNTOokDM8JeIMRDlcc/O1BsND0UNQzPl9RO81svhFlu2yiSfc+uaeR9fARo8pJeE2Mw/+zvPKqb8awVQUHjozAM=\r
|
||||
trusted comment: timestamp:1793812703\tfile:skins_3d-1.0.1.hnmod\thashed\r
|
||||
z9cSZKR27HkOB6zuxDSkT9Bz4GHtm3WaQpy9VkZ5aPTs0TZgtgF4qp/ijHPUJP2jRpJl/fWYwAhEl0A2UrK5Aw=="""
|
||||
changelog = "Initial Release. "
|
||||
@@ -14,6 +14,7 @@ The F3 debug screen, grouped into 14 modules: FPS, server, coordinates, chunk, f
|
||||
Settings: Options → Mods → SmartF3. Inspired by [BetterF3](https://modrinth.com/mod/betterf3); not affiliated, no code from it."""
|
||||
authors = ["d3grando"]
|
||||
owners = ["d3grando"]
|
||||
owner_ids = [36463198]
|
||||
license = "MIT"
|
||||
source = "https://github.com/d3grando/smartf3"
|
||||
icon = "icons/smartf3.png"
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
id = "smoothie_capes"
|
||||
kind = "native-mod"
|
||||
name = "Smoothie Capes"
|
||||
summary = "An Wavey Capes inspired mod for Horizon"
|
||||
authors = ["Nully"]
|
||||
owners = ["dumbnully"]
|
||||
owner_ids = [268078454]
|
||||
license = "MIT"
|
||||
source = "https://github.com/dumbnully/smoothie-capes"
|
||||
key = "RWRJPBl24rTE23dFezewHWl465FAcgTsmeJnDkANtUPT19YfVmQ1gYCu"
|
||||
|
||||
[[versions]]
|
||||
version = "0.2.0"
|
||||
published = "2026-11-02"
|
||||
game = "26.3"
|
||||
file = "smoothie_capes-0.2.0.hnmod"
|
||||
sha256 = "303b88bf5dd2c518ed9f5186252813b5a98716289c42e60838d0cb9846fc6429"
|
||||
signature = """
|
||||
untrusted comment: signature from minisign secret key
|
||||
RURJPBl24rTE247uqvyB1Bh5PIfR4C2XUVWvCx4/lt5jAO8sgDBEgtfs9adTKkv+L89ENRyYUVCLG1c8yptts8AYfcmPEI7bcQs=
|
||||
trusted comment: timestamp:1793663973\tfile:smoothie_capes.hnmod\thashed
|
||||
f+QyN8EVz5M41RHayEZGX7Fk1Z+FZnhUTv7MwH5Y3GTBYXiNMAHAIMdox4i62RDTx3wQhVUuIOBa3GqJ2H7KAQ=="""
|
||||
@@ -0,0 +1,26 @@
|
||||
id = "speedrun_timer"
|
||||
kind = "native-mod"
|
||||
name = "Speedrun Timer"
|
||||
summary = "Speedrun Timer Is a mod made to count the time it took you to beat the Game"
|
||||
description = """
|
||||
Speedrun Timer Is a mod made to count the time it took you to beat the Game
|
||||
An RTA timer in a corner: HH:MM:SS.mmm, starts on your first move, stops when you leave the End. Info gets saved locally."""
|
||||
authors = ["Demerobit"]
|
||||
owners = ["Demerobit"]
|
||||
license = "MIT"
|
||||
source = "https://github.com/Demerobit/Speedrun-Timer-Horizon-Rust-Minecraft"
|
||||
icon = "https://raw.githubusercontent.com/Demerobit/Speedrun-Timer-Horizon-Rust-Minecraft/main/icon.png"
|
||||
key = "RWREE8bzxgQZoRDo1MEbx56BjXA/udjM64FBEjMFmXQhhgx1Z6oSrt69"
|
||||
|
||||
[[versions]]
|
||||
version = "1.0.1"
|
||||
published = "2026-11-04"
|
||||
game = "26.3"
|
||||
file = "speedrun_timer-1.0.1.hnmod"
|
||||
sha256 = "884efd123b6e3a99e783603fd18f94b4fe6cf4b2ce6297e8e58fbb81c699c22e"
|
||||
signature = """
|
||||
untrusted comment: signature from minisign secret key
|
||||
RUREE8bzxgQZoSnNYQKsFGHKDXOabxzO/Svz7gImNoBk9aoGEfRrGHq4Q8FEy4gK6kSdppg4BOj+ppW8+xSMZfFUldfYNXmJmwo=
|
||||
trusted comment: timestamp:1793811255\tfile:speedrun_timer-1.0.1.hnmod\thashed
|
||||
c8UAoVvQRjR97rkLv42ZqaAQkHZfjlICWRV+kEdV3ECDL7yrwICmImSwDTaV7KMl/fQcFouC8yvUbr4fpDWZAQ=="""
|
||||
changelog = "This is he first Release of the mod "
|
||||
@@ -5,6 +5,7 @@ summary = "1:1 Status Effect Bars & Inventory HUD+ Potion HUD: displays active p
|
||||
description = "A 1:1 native Rust port of Status Effect Bars and Inventory HUD+ (Potion HUD) for Horizon. Renders all active potion and beacon status effects directly on the HUD during gameplay with their official mob_effect sprites, translated effect names, and Roman numeral potency levels (II, III, IV). Includes color-coded mm:ss countdown timers (White for >30s, Yellow for 10-30s, Flashing Red for <=10s urgent expiration warning, and Aqua infinity symbol for permanent effects) alongside smooth duration progress bars colored in each effect's authentic vanilla potion color."
|
||||
authors = ["CayMac"]
|
||||
owners = ["CayMac97"]
|
||||
owner_ids = [197672048]
|
||||
license = "MIT"
|
||||
source = "https://github.com/CayMac97/status-effect-hud"
|
||||
key = "RWS411U2KXdTN4/y5bQhqBYxNlZvCtSZ5lNy46eeSAcgy0cxSJo2+Sm3"
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
id = "stuck"
|
||||
kind = "native-mod"
|
||||
owners = ["Vakarux12"]
|
||||
owner_ids = [167799729]
|
||||
key = "RWS5u/7lEyKbzVHC6uMJRlhM/N7vzKp5fZe2LCDUlHsDK4lvPupxJbqQ"
|
||||
name = "Stuck?"
|
||||
summary = "/stuck and /escape move you to a safe spot nearby, off the Nether roof too."
|
||||
description = """
|
||||
A Horizon port of [adasjusk's Stuck?](https://github.com/adasjusk/Stuck), the unstuck plugin for Spigot, Paper, Folia and Fabric.
|
||||
|
||||
- `/stuck` and `/escape` search random columns near you for solid ground with two blocks of air above, and teleport you there.
|
||||
- Skips lava, fire, magma, cactus and other hazards, and leaves and snow.
|
||||
- A player on the Nether's bedrock roof is brought back under it.
|
||||
- Cooldown between uses, 30 seconds by default.
|
||||
- Config: `config/stuck.properties`, the same keys as the Fabric build.
|
||||
|
||||
Only loaded chunks are searched. Runs where the world is hosted: single player and worlds opened to LAN (Horizon's dedicated server cannot build native mods yet). Native mods run with the player's account permissions and are not sandboxed.
|
||||
|
||||
Source: `horizon/` in https://github.com/Vakarux12/Stuck, a fork of the original. CC-BY-SA-4.0, the original's licence, with a grant for Horizon's reviewers in `horizon/REVIEW-GRANT.md`.
|
||||
"""
|
||||
authors = ["adasjusk", "Vakarux12"]
|
||||
license = "CC-BY-SA-4.0"
|
||||
source = "https://github.com/Vakarux12/Stuck/tree/main/horizon"
|
||||
|
||||
[[versions]]
|
||||
version = "1.0.0"
|
||||
published = "2026-11-01"
|
||||
game = "26.3"
|
||||
file = "stuck-1.0.0.hnmod"
|
||||
sha256 = "8e6292d9c89f660e72f01e54be4103b5273017955c7bd8034dd5ae8ec2e98630"
|
||||
signature = """
|
||||
untrusted comment: signature from minisign secret key
|
||||
RUS5u/7lEyKbzf0/fn5Is2b8jJq55073yA8Sl7hSeLN1jAg9itcMlCO2kcEMVJaGyYrxFZIwPYfQJMCb61o/0g8XD8iYIUCeswU=
|
||||
trusted comment: timestamp:1793694727 file:stuck-1.0.0.hnmod hashed
|
||||
/JDVcy8eoaPHppahV7p+f+3LTx0CtfE34jqqZq7bU4TNvnSOx7ISCgp6Eg3+X1xYMRJRwTYM2aJwgbytRGzOCA=="""
|
||||
changelog = "First Horizon release, ported from adasjusk's Stuck."
|
||||
@@ -0,0 +1,31 @@
|
||||
id = "tectonic_horizon"
|
||||
kind = "native-mod"
|
||||
name = "Tectonic-Horizon"
|
||||
summary = "Unofficial native port of Tectonic: mountain ranges, underground rivers and configurable terrain."
|
||||
description = """
|
||||
Unofficial native Rust port of Tectonic 3.0.1 by Apollo (Apollounknowndev), revision 0761b561062de01b701d2571401f625c31059a9a under CC0-1.0. Not made or endorsed by Apollo or the Horizon project.
|
||||
|
||||
Mountain ranges, terrain density recipes, underground rivers and configurable terrain for Horizon 26.3. Source-only native mod: install in the launcher, then Build; runs with the game's account permissions. No Minecraft code or assets are included.
|
||||
|
||||
Enable under Options > Mods before opening a world. The mod's Settings page edits terrain and biome configuration. Config: config/tectonic-horizon.json in the game home. Invalid configuration logs a diagnostic and uses defaults. Restart after editing; new chunks only. Select increased height before world creation.
|
||||
|
||||
Pins upstream Horizon 8dbbde43501924d16497d251e6e9d08f812eafcd and Mixins e8cd0aa38cffef49cb2e088ef716baa107148c02. See docs/REVIEW-0.3.4.md for current validation and docs/VALIDATION.md for historical checks with their original revisions.
|
||||
|
||||
No block-for-block Java parity claim. Native river-decoration placement differs from Lithostitched. Java config UI and Terralith/CliffTree overlays are omitted. Other biome mods need their own Horizon ports.
|
||||
|
||||

|
||||
|
||||
Author-supplied screenshot captured November 1 in our Horizon fork; illustrative, not evidence of current upstream validation. Minimap is a separate mod. Original Tectonic by Apollo; Horizon adaptation by eltanschauung with Codex assistance. Source licenses, notices and provenance hashes retained."""
|
||||
authors = ["eltanschauung", "Apollo (original Tectonic)"]
|
||||
owners = ["eltanschauung"]
|
||||
owner_ids = [158000645]
|
||||
license = "CC0-1.0"
|
||||
source = "https://github.com/eltanschauung/tectonic-horizon"
|
||||
|
||||
[[versions]]
|
||||
version = "0.3.4"
|
||||
published = "2026-11-04"
|
||||
game = "26.3"
|
||||
file = "tectonic_horizon-0.3.4.hnmod"
|
||||
sha256 = "590198d7df43b5c9474c41648db89a15bd4d388be0fe860a2f84a84b59285cec"
|
||||
changelog = "Revision of PR #160: removes unsupported runtime-toggle.json, falls back to defaults with a diagnostic for invalid/unreadable/oversized configuration, and fixes the GitHub formatting failure. Includes the 0.3.2 in-game settings editor and 0.3.3 upstream height adapter. Pins Horizon 8dbbde43501924d16497d251e6e9d08f812eafcd and Mixins e8cd0aa38cffef49cb2e088ef716baa107148c02. All 13 Rust tests, Clippy, formatting, native-package checking, reproducible packaging, mixin plan/apply, and the patched horizon-world compile check pass. No new interactive game test is claimed. Source: https://github.com/eltanschauung/tectonic-horizon/tree/6f69f2b64a41f0cad09f87840cbdb104138c54c3"
|
||||
@@ -5,6 +5,7 @@ summary = "1:1 Better Sprinting & Lunar ToggleSprint HUD: toggle sprint and togg
|
||||
description = "A 1:1 native Rust port of Better Sprinting and the Lunar/Badlion ToggleSprint HUD for Horizon. Allows toggling continuous sprint (V or vanilla Sprint key) and sneak (C) without holding keys down, while automatically pausing toggled sneak inside inventory and chest screens so Shift-clicking items works normally. Renders the classic movement status HUD ([Sprinting (Toggled)], [Sprinting (Key Held)], [Sneaking (Toggled)], [Sneaking (Key Held)], [Flying (2.0x Boost)], [Swimming], [Gliding (Elytra)], [Riding]) and includes a configurable Creative Fly Speed Boost multiplier (1.0x to 4.0x)."
|
||||
authors = ["CayMac"]
|
||||
owners = ["CayMac97"]
|
||||
owner_ids = [197672048]
|
||||
license = "MIT"
|
||||
source = "https://github.com/CayMac97/toggle-sprint"
|
||||
key = "RWS411U2KXdTN4/y5bQhqBYxNlZvCtSZ5lNy46eeSAcgy0cxSJo2+Sm3"
|
||||
|
||||
@@ -14,6 +14,7 @@ Cut one log of a natural tree with an axe and the whole tree comes down. Every l
|
||||
In the spirit of FallingTree; not affiliated, no code from it. Pinned to Horizon 142388e and horizon-mixins 0d6b103."""
|
||||
authors = ["d3grando"]
|
||||
owners = ["d3grando"]
|
||||
owner_ids = [36463198]
|
||||
license = "MIT"
|
||||
source = "https://github.com/d3grando/treefall"
|
||||
icon = "icons/treefall.png"
|
||||
|
||||
@@ -26,6 +26,7 @@ Independently written Rust code; no upstream mod code or artwork is bundled. MIT
|
||||
"""
|
||||
authors = ["levzzz5154"]
|
||||
owners = ["levzzz5154"]
|
||||
owner_ids = [86072227]
|
||||
license = "MIT"
|
||||
source = "https://github.com/levzzz5154/horizon-veinminer"
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
id = "visuality_horizon"
|
||||
kind = "native-mod"
|
||||
name = "Visuality"
|
||||
summary = "Small pixel particles: hit fragments, slime blobs, creeper sparks, glints, soul wisps and rain ripples."
|
||||
description = """
|
||||
An original client-side Rust implementation of the effects of PinkGoosik's Visuality (MIT), with its own art made in Blender.
|
||||
|
||||
Effects
|
||||
Hit fragments: bone chips from skeletons, strays and skeleton horses, dark chips from wither skeletons, feathers in each chicken variant's colours, emerald chips from villagers.
|
||||
Slime blobs: chunky blobs in three sizes when a slime lands, in a colour you choose (magma cubes are left alone).
|
||||
Charged creeper sparks, amethyst sparkles on full clusters, armor glints on gold and diamond armor, ore glints on uncovered faces only, soul wisps over soul sand and soil and when sprinting on them, and flat rain ripples on open water.
|
||||
|
||||
Low preset by default, for integrated GPUs; Balanced and High add density. Settings in Options > Mods > Visuality, lists in config/visuality_horizon.toml.
|
||||
Client only: it sends nothing to the server and needs nothing installed there. Tested in single player on Windows; LAN, servers, Linux and macOS are not tested yet (see docs/VALIDATION.md).
|
||||
Pinned to Horizon c9d474d (Nightly 2026-11-01): it hooks private client functions, so it builds on that commit only.
|
||||
Not made or endorsed by Visuality's author."""
|
||||
authors = ["BenjiSyS"]
|
||||
owners = ["BenjiSyS"]
|
||||
owner_ids = [220019460]
|
||||
license = "MIT"
|
||||
source = "https://github.com/BenjiSyS/visuality-horizon"
|
||||
icon = "icons/visuality_horizon.png"
|
||||
|
||||
[[versions]]
|
||||
version = "1.0.2"
|
||||
published = "2026-11-03"
|
||||
game = "26.3"
|
||||
file = "visuality_horizon-1.0.2.hnmod"
|
||||
sha256 = "b764f15a062e44b27c55f478b2d02e163bfb01c3d8e222fcd6d83bcc66f19a0d"
|
||||
changelog = "First release, for Horizon Nightly 2026-11-01 (c9d474d). 1.0.1: entity pass skips entities out of reach (client tick 20-31 us instead of 71-157 us); package entries stored, so the file is byte-identical whatever Python builds it. 1.0.2: atlas and notices carried as Rust source, so the bundle holds only .rs and .toml files."
|
||||
@@ -30,6 +30,7 @@ The engine's unit tests pass, and the modded client type-checks. Live, against P
|
||||
"""
|
||||
authors = ["Nirokx"]
|
||||
owners = ["Nirokx"]
|
||||
owner_ids = [95123456]
|
||||
license = "MIT"
|
||||
source = "https://github.com/Nirokx/horizon-voice-chat"
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
id = "wayfarer_stones"
|
||||
kind = "mod"
|
||||
name = "Wayfarer Stones (assets)"
|
||||
summary = "The textures, text, recipes and villages of Wayfarer Stones. Needs the Wayfarer Stones mod."
|
||||
description = """
|
||||
**This is one of the two parts of [Wayfarer Stones](https://playhorizon.dev/mods/wayfarer_stones_native), and does nothing alone.** Install both.
|
||||
|
||||
It holds the mod's models and textures, its English and Russian text, its recipes and drops, and the stones villages build. It has no code. The code, and the description of what the mod does, are in Wayfarer Stones.
|
||||
|
||||
All the art is original. No Minecraft assets are included. MIT licensed."""
|
||||
authors = ["bioneisme"]
|
||||
owners = ["Bioneisme"]
|
||||
owner_ids = [92920845]
|
||||
license = "MIT"
|
||||
source = "https://github.com/Bioneisme/wayfarer-stones"
|
||||
icon = "https://raw.githubusercontent.com/Bioneisme/wayfarer-stones/main/content/assets/wayfarer_stones/textures/item/wayfarer_stone.png"
|
||||
|
||||
[[versions]]
|
||||
version = "0.6.0"
|
||||
published = "2026-11-04"
|
||||
game = "26.3"
|
||||
file = "wayfarer_stones-0.6.0.hmod"
|
||||
sha256 = "87bec46b6bbe05d95563a4bf7b8a3b92340ca1dd54c2d5709b6a0a41989d7176"
|
||||
changelog = "First release."
|
||||
@@ -0,0 +1,34 @@
|
||||
id = "wayfarer_stones_native"
|
||||
kind = "native-mod"
|
||||
name = "Wayfarer Stones"
|
||||
summary = "Place stones around your world, activate them, and travel between the ones you know."
|
||||
description = """
|
||||
**This mod has two parts, and you need both.** Install this one and [Wayfarer Stones (assets)](https://playhorizon.dev/mods/wayfarer_stones), then press Build.
|
||||
|
||||
- **Place** a stone, or find one in a village. Right-click it once to add it to your list.
|
||||
- **Travel** from any stone you know: pick a destination on the screen, or search for it. You land beside the stone, on solid ground.
|
||||
- **Pay** in experience levels: 1, plus 1 for each 500 blocks, up to 5. Travel between dimensions costs 3.
|
||||
- **Carry** a Wayfarer's Scroll to travel from anywhere. Each travel uses one up.
|
||||
- **Stones are safe:** only the owner or an operator can break one, and pistons won't move it.
|
||||
|
||||
Every world has its own settings: cost, cooldown, warmup and more. English and Russian.
|
||||
|
||||
**Commands:** `/wayfarer`, `/wayfarer list`, `/wayfarer to <name>`, `/wayfarer name <name>`, `/wayfarer forget <name>`. Operators also have `/wayfarer global <name>` and `/wayfarer remove <name>`.
|
||||
|
||||
Native source mod: install it on the launcher's Mods page, press Build there, and confirm the native-code warning. It runs with your account's permissions. It is pinned to one Horizon Nightly and Horizon Mixins revision, named in each version's changelog.
|
||||
|
||||
No Minecraft code or assets are included. MIT licensed."""
|
||||
authors = ["bioneisme"]
|
||||
owners = ["Bioneisme"]
|
||||
owner_ids = [92920845]
|
||||
license = "MIT"
|
||||
source = "https://github.com/Bioneisme/wayfarer-stones"
|
||||
icon = "https://raw.githubusercontent.com/Bioneisme/wayfarer-stones/main/content/assets/wayfarer_stones/textures/item/wayfarer_stone.png"
|
||||
|
||||
[[versions]]
|
||||
version = "0.6.0"
|
||||
published = "2026-11-04"
|
||||
game = "26.3"
|
||||
file = "wayfarer_stones_native-0.6.0.hnmod"
|
||||
sha256 = "0c5929425bea6c570ad455229b519fc411ea2b769ef08be8a91bb8b7793d9dba"
|
||||
changelog = "First release. Pinned to Nightly 2026-11-04 (67dad0d) and Horizon Mixins e8cd0aa."
|
||||
@@ -14,6 +14,7 @@ Features:
|
||||
- Keys: N = add, B = toggle list"""
|
||||
authors = ["Aron"]
|
||||
owners = ["Lauch2"]
|
||||
owner_ids = [339589475]
|
||||
license = "MIT"
|
||||
source = "https://github.com/Lauch2/horizon-waymap-mod"
|
||||
key = "RWS1B1QZPeYaSNKJr9rffnfHDSvB0AZj/Ul1B2pKrJ2XFBgKXL0FC96u"
|
||||
|
||||
@@ -15,6 +15,7 @@ Waypoints for Horizon.
|
||||
Pinned to Horizon `c9d474d`, because it reads the world folder, server address and last frame's camera from the game itself."""
|
||||
authors = ["Nirokx"]
|
||||
owners = ["Nirokx"]
|
||||
owner_ids = [95123456]
|
||||
license = "MIT"
|
||||
source = "https://github.com/Nirokx/horizon-waypoints"
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
id = "wisdom_horizon"
|
||||
kind = "shader"
|
||||
name = "Wisdom Shaders V3 (Horizon port)"
|
||||
summary = "bobcao3's Wisdom Shaders V3, ported to Horizon's own WGSL shader format."
|
||||
description = """
|
||||
An unofficial port of **Wisdom Shaders V3 (Build 3300)** by bobcao3 (https://github.com/bobcao3/Wisdom-Shaders) to Horizon's shader API (schema 1, API 0.1), translated from GLSL to WGSL. Wisdom Shaders is licensed under the Apache License 2.0, and so is this port. It is not an official Wisdom Shaders release.
|
||||
|
||||
## Ported
|
||||
- Wisdom's sun, ambient and torch colours by time of day, and its exposure curve.
|
||||
- Oren-Nayar diffuse and GGX specular lighting, light through thin plants, and soft shadows from a jittered 3x3 kernel.
|
||||
- The atmosphere, 2D clouds and their shadows, the sun and moon discs, and height fog.
|
||||
- The AgX tonemap with its lookup table (ACES as an option), plus bloom, the vignette and waving foliage.
|
||||
|
||||
## Changed for Horizon
|
||||
- Lighting runs per surface in the material stage, since Horizon has no G-buffers.
|
||||
- Shadows use Horizon's shadow map with PCF, and water and stained glass tint the light passing through them.
|
||||
- There is no SSAO, SSR, volumetric clouds, god rays, caustics or TAA. FXAA replaces TAA.
|
||||
- The water is rewritten for Horizon's translucent pass.
|
||||
|
||||
The full list of changes is in the README. For integrated graphics there is a Lite version (`wisdom_horizon_lite`) based on Wisdom's LITE profile."""
|
||||
authors = ["bobcao3", "tomasmendes01"]
|
||||
owners = ["tomasmendes01"]
|
||||
owner_ids = [68385376]
|
||||
license = "Apache-2.0"
|
||||
source = "https://github.com/tomasmendes01/wisdom-horizon"
|
||||
|
||||
[[versions]]
|
||||
version = "1.1.0"
|
||||
published = "2026-11-04"
|
||||
game = "26.3"
|
||||
file = "wisdom_horizon-1.1.0.hshader"
|
||||
sha256 = "2b7b4e11e3506b65e8c4162ed56fbf3c33bc51bbc56038a4b1297219d917dd1e"
|
||||
changelog = "First release: a port of Wisdom Shaders V3 (Build 3300) to Horizon's shader API."
|
||||
@@ -0,0 +1,26 @@
|
||||
id = "wisdom_horizon_lite"
|
||||
kind = "shader"
|
||||
name = "Wisdom Shaders V3 Lite (Horizon port)"
|
||||
summary = "The LITE profile of bobcao3's Wisdom Shaders V3 for Horizon, made for integrated graphics."
|
||||
description = """
|
||||
The LITE profile of the unofficial Horizon port of **Wisdom Shaders V3 (Build 3300)** by bobcao3 (https://github.com/bobcao3/Wisdom-Shaders). Like the original, it is licensed under the Apache License 2.0. It is not an official Wisdom Shaders release.
|
||||
|
||||
It has the same lighting, sky, clouds, fog and AgX tonemap as the full version (`wisdom_horizon`), tuned to Wisdom's LITE profile for integrated graphics:
|
||||
- a 1024 shadow map over 64 blocks, with single-tap shadows
|
||||
- no cloud shadows and no water mirror
|
||||
- no bloom and no FXAA
|
||||
|
||||
The README in the repository lists everything that was ported and what changed."""
|
||||
authors = ["bobcao3", "tomasmendes01"]
|
||||
owners = ["tomasmendes01"]
|
||||
owner_ids = [68385376]
|
||||
license = "Apache-2.0"
|
||||
source = "https://github.com/tomasmendes01/wisdom-horizon"
|
||||
|
||||
[[versions]]
|
||||
version = "1.1.0"
|
||||
published = "2026-11-04"
|
||||
game = "26.3"
|
||||
file = "wisdom_horizon_lite-1.1.0.hshader"
|
||||
sha256 = "662570653111bd34fec37a7482791b8b1d0bdcee1bcba5351c4137acababb818"
|
||||
changelog = "First release: the LITE profile of Wisdom Shaders V3 (Build 3300), ported to Horizon's shader API."
|
||||
@@ -8,6 +8,7 @@ description = """
|
||||
working schematics."""
|
||||
authors = ["_cykoo_"]
|
||||
owners = ["XnotCykoX"]
|
||||
owner_ids = [215324132]
|
||||
license = "MIT"
|
||||
source = "https://github.com/XnotCykoX/WorldEdit-Horizon"
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ Instalation:
|
||||
"""
|
||||
authors = ["1_st_Cat"]
|
||||
owners = ["RealCat1st"]
|
||||
owner_ids = [260922535]
|
||||
license = "Apache-2.0"
|
||||
source = "https://github.com/RealCat1st/XToXrayRust"
|
||||
icon = "icons/xtoxray.png"
|
||||
|
||||
@@ -19,6 +19,7 @@ Options > Mods: it rests at once, and the next build leaves it out. Its source i
|
||||
"""
|
||||
authors = ["Horizon"]
|
||||
owners = ["ludovic111"]
|
||||
owner_ids = [136255825]
|
||||
license = "MIT"
|
||||
source = "https://github.com/horizon-rust/horizon-mods/tree/main/native/zoom"
|
||||
|
||||
|
||||
Generated
+1
-1
@@ -4,7 +4,7 @@ version = 4
|
||||
|
||||
[[package]]
|
||||
name = "horizon-mod-smp"
|
||||
version = "0.31.0"
|
||||
version = "0.33.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "horizon-mod-smp"
|
||||
version = "0.31.0"
|
||||
version = "0.33.0"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
publish = false
|
||||
|
||||
+69
-28
@@ -9,7 +9,7 @@ lines, vanilla chest menus the server fills with ordinary items (the trade menu)
|
||||
bar, boss bars and the sidebar).
|
||||
|
||||
This version: the economy, the auction house, the server market and its `/shop` menu, player trades, the coinflip, the lottery, places (spawn, homes, warps,
|
||||
teleport requests, the rules), land claims, and the scoreboard (a sidebar with your balance, playtime and the richest players, the Tab list's
|
||||
teleport requests, the rules), land claims, and the scoreboard (a sidebar with your balance and playtime, the Tab list's
|
||||
header and footer).
|
||||
|
||||
| Command | Does |
|
||||
@@ -27,7 +27,7 @@ header and footer).
|
||||
| `/ah cancel <listing>`, `/ah claim`, `/ah search <words>`, `/ah mine`, `/ah mail` | takes back a listing nobody bid on; gives you your mail; the menu on a search, your listings, your mail |
|
||||
| `/shop` | the market as a chest menu: categories, a click buys one, a shift-click 64, a click on a stack of yours sells it, "Sell all" |
|
||||
| `/market [item]` | the moving market as a chest menu: categories, each item's price, its move over 1 h and 24 h and its volume; an item's page buys or sells 1, 16 or 64, sells all you carry, and gives its price chart as a map |
|
||||
| `/worth [item]` | an item's price, the same to buy and to sell (the one in your hand without a name), and what you may still sell today |
|
||||
| `/worth [item]` | an item's price, the same to buy and to sell (the one in your hand without a name) |
|
||||
| `/sell hand [count]`, `/sell <item> [count]`, `/sell all` | sells to the market: the stack in your hand, an item, or everything it buys |
|
||||
| `/buy <item> [count]` | buys from the market, only what fits in your inventory |
|
||||
| `/trade <player>` | asks a player to trade (60 s to answer) |
|
||||
@@ -43,6 +43,7 @@ header and footer).
|
||||
| `/home [name]`, `/sethome [name]`, `/delhome [name]`, `/homes` | your homes (`home` when unnamed): go, set or move one where you stand, delete, list |
|
||||
| `/warp <name>`, `/warps` | to a warp, the list; `/setwarp <name>`, `/delwarp <name>` for operators of level 3 |
|
||||
| `/tpa <player>`, `/tpaccept [player]`, `/tpdeny [player]` | asks to go to a player (60 s to answer); answers a request |
|
||||
| `/rtp`, `/wild` | to a random safe spot 500 to 5,000 blocks from the spawn, in the Overworld only; once every 30 minutes |
|
||||
| `/rules` | the server's rules |
|
||||
| `/back` | back to where you died, or where your last teleport took you from (warmup and cooldown as any teleport) |
|
||||
| `/afk` | marks you away from the keyboard until you move, turn or run a command |
|
||||
@@ -54,14 +55,16 @@ header and footer).
|
||||
| `/nick <name>`, `/nick off`, `/nick` | operators of level 3 (with `ops_only`, the default): another name for you in chat and the Tab list, shown as `~name` (hovering it gives your real one); `/nick off` (anyone with a nickname) takes it off; `/nick` alone says how |
|
||||
| `/nick <player> <name\|off>` | operators of level 3: sets or takes off a player's nickname |
|
||||
| `/realname <nick>` | who goes by that nickname |
|
||||
| `/vanish`, `/vanish list` | operators of level 3: hides you from the other players, or shows you again; who is vanished |
|
||||
| `/invsee <player>`, `/endersee <player>` | operators of level 3: the player's inventory (main, hotbar, armour, off hand) or ender chest in a chest menu, on or off the server; click one of theirs to take it, one of yours to give it; every move in `audit.log`, a summary in `staff.log` |
|
||||
| `/sidebar off`, `/sidebar on`, `/sidebar` | hides the scoreboard sidebar for you (remembered), shows it again |
|
||||
| `/smp status`, `/smp off [part\|all] [reason]`, `/smp on [part\|all]` | operators of level 3: the emergency switches (below) |
|
||||
|
||||
The market trades only plain items (not worn, named or enchanted), and only with players in survival or adventure
|
||||
mode. Items move first, then money, both logged; a sale the log refuses gives the items back, and a purchase checks the
|
||||
money and the room before anything moves.
|
||||
|
||||
**No way to get rich from the market.** The market makes money only from what players gather, and only so much a day:
|
||||
**No way to get rich from the market.** The market makes money only from what players gather:
|
||||
- One price per item, to buy and to sell (since 0.29): a config that still has two (`buy` 400, `sell` 100) gets
|
||||
their geometric mean (200) at load, logged ("one price now"); `buy` or `sell` 0 still closes that side. A trade
|
||||
of N items is priced along the curve it moves the price on (below), so buying N and selling them back gives back
|
||||
@@ -74,12 +77,10 @@ money and the room before anything moves.
|
||||
copper, diamond, emerald, coal, redstone, lapis, wheat, wood and food chains at each start.
|
||||
- Items a recipe links (an ingot and its block, wheat and hay...) share one moving price, the block moving it as
|
||||
its 9 ingots do, so crafting never beats the market however prices move; the vanilla-recipe test checks every item
|
||||
at the worst drift (what makes it at 0.6 times its price, it at 1.6) too.
|
||||
- Each player sells at most `daily_sell_coins` (2,000 coins) and `daily_sell_items` (640 of each item) a UTC day,
|
||||
`/worth` and the shop show what is left, and `market.json` keeps the day's count across restarts.
|
||||
- Money received with `/pay` counts toward the receiver's same `daily_sell_coins`: selling up to the limit on alts
|
||||
and paying it all to a main is refused once the main's limit is reached ("can only receive N more today").
|
||||
Trades (`/trade`) and auction house sales are not counted.
|
||||
at the worst drift (what makes it at 0.2 times its price, it at 1.6) too.
|
||||
- There are no daily coin or per-item limits on market sales, or daily limits on `/pay` receipts.
|
||||
The former `daily_sell_coins` and `daily_sell_items` settings and `market.json` counts are ignored,
|
||||
including in existing server files.
|
||||
- Amounts are checked: no price above 1,000,000,000,000, no sum that overflows.
|
||||
- The coinflip and the lottery give the winner all the stakes: they move money between players and never make any.
|
||||
|
||||
@@ -110,6 +111,14 @@ before it is made (`invsee_take`, `invsee_put`: operator, player, inventory, ite
|
||||
its reason); closing the menu writes one line to the server log (`[smp] staff: ...`), `staff.log`, and the chat of
|
||||
the other operators on. Needs horizon#6106 (`native_inventory`, `native_move_stack`).
|
||||
|
||||
**Vanish.** `/vanish` takes an operator of level 3 out of the Tab list and the world of every player below level 3,
|
||||
whatever the game mode (survival and creative too), and those players don't read them joining or leaving; operators of
|
||||
level 3 or more still see them, and are told who vanished. A vanished operator reads a subtitle the whole time
|
||||
(*Vanished: only operators see you*), isn't counted online (the sidebar, the Tab list's footer), doesn't hold the night up, isn't
|
||||
announced as AFK or on a first join, and can't be reached by name by those who don't see them (`/tpa`, `/trade`,
|
||||
`/coinflip`, `/playtime`). It lasts over a relog and a restart (`vanish.json`) until `/vanish` again. Needs
|
||||
horizon#7003 (`native_hide_player`).
|
||||
|
||||
**Nicknames.** With `ops_only` (the default since 0.22) only operators of level 3 set nicknames: their own, or a
|
||||
player's with `/nick <player> <name>`; a player may take off the one given to them (`/nick off`). At each start, a
|
||||
nickname a player who isn't an operator picked themselves is taken off and logged (`for operators only`). 3 to 16 letters, digits or `_`, shown with a `~` in front (`~Rocky`) so a nickname never passes for
|
||||
@@ -130,15 +139,19 @@ names), `reserved` (the words above).
|
||||
**Moving prices** (`market.dynamic`, on by default since 0.26). Each item has one price: its base price (`items`)
|
||||
times a factor that supply and demand move as each trade happens. Every item bought raises the log of the factor by
|
||||
`1% / depth`, every item sold lowers it as much, where the depth follows the item's rarity (400 / its price, between
|
||||
2 and 400: a diamond moves 50 times faster per item than cobblestone; `depth` overrides it per item). A trade of N is
|
||||
2 and 50: a diamond moves 25 times faster per item than cobblestone, and any item reaches the floor within 10,000
|
||||
sold; `depth` overrides it per item). A trade of N is
|
||||
worth the area under that curve (`price / step × (e^(N × step) - 1)`), rounded up to whole coins when buying and down
|
||||
when selling: one diamond at 200 costs 201 and sells for 199, 64 cobblestone at 1 cost 65, and a sale walks back the
|
||||
exact path of the purchase. All trades together move an item at most `max_minute_move_percent` (5%) a minute from
|
||||
where it started that minute: past that, a purchase is cut short and a sale keeps the rest ("sell them in a
|
||||
minute"). The factor goes back to 1 with a half-life of `half_life_hours` (6) and stays within `min_factor` (0.6)
|
||||
and `max_factor` (1.6), where the rest of a trade goes at that price. There is no tax on trades (an older config's
|
||||
`tax_percent` is ignored): the receipt is the price ("Sold 3 diamond at 198 coins each: 595 coins."). Items a player bought from the market in the last `round_trip_hours` (24) can't be
|
||||
sold back to it. (`max_trade_impact_percent` and `max_player_impact_percent` are no longer used: the curve and the
|
||||
minute"). The factor goes back to 1 with a half-life of `half_life_hours` (6) and stays within `min_factor` (0.2)
|
||||
and `max_factor` (1.6), where the rest of a trade goes at that price. With no daily sale caps (since 0.33), the floor
|
||||
is what bounds a farm: selling one item without end takes its price down to a fifth of its base, where it stays while
|
||||
the selling goes on; a config with the old floor (0.6) gets 0.2 when it loads. There is no tax on trades (an older config's
|
||||
`tax_percent` is ignored): the receipt is the price ("Sold 3 diamond at 198 coins each: 595 coins."). Market purchases can be sold back immediately;
|
||||
the former `round_trip_hours` setting and saved purchase receipts are ignored. (`max_trade_impact_percent` and
|
||||
`max_player_impact_percent` are no longer used: the curve and the
|
||||
minute's band do their work.) History: one point a minute for 24 hours and one an hour for 30 days (the mid price, the
|
||||
items traded and those sold), in `market_prices.json`, written by a thread every 5 minutes and when the world closes.
|
||||
At the first minute of each UTC day, the day's three biggest risers and fallers (at least 1%) and news from real
|
||||
@@ -148,7 +161,7 @@ fixed prices of 0.23. For other mods: `Market::spot`, `volume(id, now, hours)`,
|
||||
|
||||
**`/market`.** A 6-row chest menu: the shop's categories and the day's movers; a category's items, each with its
|
||||
price, its change over 1 h and 24 h (green ▲, red ▼) and what was traded in 24 h; an item's page with its
|
||||
base price and what you may still sell today, buttons to buy or sell 1, 16 and 64 (what they come to
|
||||
base price , buttons to buy or sell 1, 16 and 64 (what they come to
|
||||
along the curve), sell all of it you carry, and the price chart over 24 hours or 7 days, given as a filled map (128 × 128,
|
||||
drawn by the server: the curve green when it rose, red when it fell, the price, the change, the low and high). A
|
||||
chart map is drawn again each minute for whoever carries it, and once a minute the open menus follow the prices.
|
||||
@@ -157,8 +170,8 @@ chart map is drawn again each minute for whoever carries it, and once a minute t
|
||||
**The shop.** `/shop` opens a 6-row chest menu on the market's prices: the categories of `config.json`'s `shop` (an
|
||||
item in none goes under "Other"), each item named with its prices. A click buys one, a shift-click 64; a click on a
|
||||
stack in your own inventory, below the menu, sells it; "Sell all" sells everything the market buys. The bottom row
|
||||
shows your balance and today's selling limit. It goes through the market's own rules (survival or adventure only,
|
||||
items then money, logged, the daily limits); the items move by the console's `clear` and `give`, measured on the
|
||||
shows your balance. It goes through the market's own rules (survival or adventure only,
|
||||
items then money, logged); the items move by the console's `clear` and `give`, measured on the
|
||||
inventory before and after, so the money follows what really moved.
|
||||
|
||||
**Trades.** Once accepted, both players see a 6-row chest menu: their own offer on the left (16 stacks), the other's
|
||||
@@ -216,6 +229,20 @@ was on (`homes`: `[hours, homes]`, by default 1 home, 2 from 5 hours, 3 from 20,
|
||||
lines. `/spawn` lands on a random safe column within `spawn_radius` (250) blocks of the spawn point, like vanilla's `respawn_radius` for a first join or a respawn: after the `tp`, vanilla's `spreadplayers` moves the player off fluid, fire and magma onto solid ground (0: exactly the spawn point; if no column is found the player stays at the spawn point). Teleports are vanilla's `tp`, run as the server (`execute in <level> run tp`), so they cross levels and land
|
||||
as any teleport does.
|
||||
|
||||
`/rtp` (or `/wild`) draws `rtp_attempts` (6) chunks at random between `rtp_min_distance` (500) and `rtp_max_distance`
|
||||
(5000) blocks from the spawn (`/setspawn`'s point, 0 0 without one), uniform over the ring's area and at least 16 blocks
|
||||
inside the world border (as last saved in `world_border.dat`), then holds them loaded one after another with vanilla's
|
||||
`forceload` while the warmup runs: the game generates them on its worker threads, never on the server tick. Once a
|
||||
chunk is loaded, the Overworld's tick reads its 256 columns (`native_read_columns`, the top block below the
|
||||
`MOTION_BLOCKING_NO_LEAVES` height and the two above it) and the player lands, still standing still, on a random one
|
||||
whose top block is solid ground (no fluid, fire, cactus, magma, powder snow, berry bush or dripstone) with air or grass
|
||||
above it, outside another player's claim and still in the ring and the border; with none the next chunk is tried. With
|
||||
no spot after the six, or no land loaded `rtp_search_seconds` (30) after the warmup, it says so and nothing is spent.
|
||||
A teleport made starts `rtp_cooldown_seconds` (1800) for that player, kept in `places.json` over restarts ("Wait 12 min
|
||||
before your next /rtp."); a teleport called off (a move, a hit, the game failing it) doesn't. Elsewhere than the
|
||||
Overworld it refuses. The chunks it forced are let go 10 s after the landing, or at the next start if the server
|
||||
stopped first; a chunk an operator forced already is used and left forced. `/back` returns from it as from any teleport.
|
||||
|
||||
**Claims.** In a claimed chunk, only its owner, the players they trust and operators of level 3 break blocks or right
|
||||
click blocks: doors, chests, levers, buttons, placing a block, a bucket or flint on a block. Anyone else's click does
|
||||
nothing, and the action bar says whose land it is. The chunks a player may hold grow with the time played here
|
||||
@@ -230,14 +257,14 @@ items, shearing, leads), which waits for Horizon's hook (#4648). The server's `s
|
||||
the spawn.
|
||||
|
||||
**The scoreboard.** A sidebar on the right, titled `board.title` ("Horizon SMP"): your own block first (your name, or
|
||||
your nickname; your balance, `125.4k`, `3.2M`; your playtime, `2h 05m`; the players online), then the `board.top` (3)
|
||||
richest players with their balance, then `board.footer` (`play.playhorizon.dev`, empty for none). The ranking is
|
||||
built once for everyone, only when a balance changed (at most every `board.min_seconds`, 5) or every
|
||||
`board.refresh_seconds` (60); each player's own lines are looked at on the same `board.refresh_seconds`, and a
|
||||
your nickname; your balance, `125.4k`, `3.2M`; your playtime, `2h 05m`; the players online), then `board.footer`
|
||||
(`play.playhorizon.dev`, empty for none). It lists no ranking: `/baltop` does (`board.top`, of 0.14 to 0.29, is
|
||||
ignored). The balances are read once for everyone, only when one changed (at most every `board.min_seconds`, 5) or
|
||||
every `board.refresh_seconds` (60); each player's own lines are looked at on the same `board.refresh_seconds`, and a
|
||||
sidebar is sent only to the players whose lines changed and to newcomers: nothing runs per tick per player. The Tab
|
||||
list shows `board.tab_header` and `board.tab_footer` (`{server}`, `{tps}`, `{players}` filled in, every
|
||||
`board.tab_refresh_seconds`, 5). `board.sidebar` and `board.tab` turn either off; `board.server_name` names the Tab
|
||||
list. A server that kept 0.13's defaults (`Richest players`, the top 10) gets these at its first start of 0.14. The
|
||||
list. A server that kept 0.13's title (`Richest players`) gets `Horizon SMP` at its first start. The
|
||||
sidebar uses one scoreboard objective of its own (`horizon:native_sidebar`) and leaves the world's scoreboard alone.
|
||||
|
||||
**Presence.** A player's first join is announced to everyone (`presence.first_join`, empty for none) and they read
|
||||
@@ -297,6 +324,19 @@ pool: the stake goes in, the payout comes out, never more than the pool holds),
|
||||
|
||||
The currency is earned in game only: it can't be bought with real money or cashed out (Mojang's usage guidelines).
|
||||
|
||||
**Emergency switches.** An operator of level 3 turns parts of the SMP off while the server runs: `/smp off`
|
||||
(everything but land protection), `/smp off <part> [reason]`, `/smp on [part]`, `/smp status` (what is off, by whom,
|
||||
when and why). Parts: `economy` (balances, `/pay`, `/eco`, the daily
|
||||
reward), `market` (`/worth`, `/sell`, `/buy`, `/shop`, `/market`), `auction`, `trades`, `gambling` (coinflips, the
|
||||
lottery, the casino), `places` (spawn, homes, warps, `/tpa`, `/back`), `claims` (land protection and
|
||||
`/claim`: only by name, `/smp off claims`), `board` (the sidebar and the Tab list's header and footer), `presence`
|
||||
(welcomes, AFK, the commands' rate limit), `nicks`, `invsee`. A part that is off answers its commands with "... is
|
||||
turned off for now by the server's staff", and its menus close at the next click. Turning one off ends open trades
|
||||
with nothing moved, closes the shop, market and auction menus, calls off teleports waiting, takes the
|
||||
sidebars and nicknames away. Its timed work waits (the lottery's draw, auctions ending, the daily reward); nothing held is lost: coinflips not taken still go back and a casino
|
||||
round under way still ends and is paid. The other operators on are told, and each change is in the server's log and
|
||||
`audit.log` (`switch_off`, `switch_on`). What is off is kept in `switches.json`, so a restart keeps it off.
|
||||
|
||||
## Data
|
||||
|
||||
Everything is in the world's folder, `world/horizon-smp/`, so a backup of the world carries it. A world without that
|
||||
@@ -304,16 +344,17 @@ folder is untouched until the mod first runs on it; it changes no block and no p
|
||||
|
||||
| File | Holds |
|
||||
|---|---|
|
||||
| `config.json` | `currency` (default `coins`), `starting_balance` (default 0), `market`: `enabled`, `items` (each item's `buy`, what the market asks, and `sell`, what it pays, 0 closing that side), `daily_sell_coins` (2000) and `daily_sell_items` (640), 0 for no limit; `shop`: `enabled`, `title`, `categories` (`name`, `icon`, `items`); `web`: `url` (Kira's `/smp/ingest`) and `token` (empty: nothing is sent), where the pages of `web/` go; written with the defaults the first time, and again when a newer version adds keys (your values kept) |
|
||||
| `config.json` | `currency` (default `coins`), `starting_balance` (default 0), `market`: `enabled`, `items` (each item's `buy`, what the market asks, and `sell`, what it pays, 0 closing that side), `shop`: `enabled`, `title`, `categories` (`name`, `icon`, `items`); `web`: `url` (Kira's `/smp/ingest`) and `token` (empty: nothing is sent), where the pages of `web/` go; written with the defaults the first time, and again when a newer version adds keys (your values kept) |
|
||||
| `economy.json` | balances by player UUID, with the name last seen; `held`: coinflip stakes and lottery tickets being decided; `pools`: the casino's pot and the auction house's bids; `ext`: other parts' state written in the same step as the money (`auction`: the listings, bids, mail and notes) |
|
||||
| `places.json` | the spawn, the warps, each player's homes, and the seconds each player has played (written once a minute) |
|
||||
| `nicks.json` | each player's nickname and when they last changed it |
|
||||
| `vanish.json` | the vanished operators by UUID, with their name |
|
||||
| `staff.log` | one line per `/invsee` or `/endersee` menu in which something moved: who, whose, what was taken and put |
|
||||
| `market.json` | what each player sold today (coins and items), for the daily limits |
|
||||
| `market_prices.json` | each item's price factor and history (minutes for 24 h, hours for 30 days: `[time, mid, traded, sold]`), what each player bought in the last day (no selling it back), the day's summary |
|
||||
| `market_prices.json` | each item's price factor and history (minutes for 24 h, hours for 30 days: `[time, mid, traded, sold]`), the day's summary |
|
||||
| `web/market.json` | for the website (`/hub/market`, through Kira), written by a thread each minute prices move: `schema` 1, `generated`, `currency`, `tax_percent` (always 0, for older readers), `daily_summary` (`day`, `lines`), `items` (`id`, `name`, `category`, `price` (the one price; `buy` and `sell` repeat it on the sides the market trades, for older readers), `mid`, `base`, `change_1h`, `change_24h`, `volume_24h`, `day`: 5-minute points, `month`: hours, each `{step, t, price, volume}`). When `web.token` is set, the same thread POSTs each page of `web/` that changed (gzipped, `Authorization: Bearer <token>`) to `<web.url>/<name>`: Kira keeps the last one and serves it to the site; the server needs no inbound access and Kira no key to it |
|
||||
| `presence.json` | the players who have joined (no welcome again), and the day each last got the daily reward |
|
||||
| `board.json` | the players who hid the sidebar (`/sidebar off`) |
|
||||
| `switches.json` | the parts operators turned off (`/smp off`): by whom, when and why |
|
||||
| `casino.json` | the day's ledger: each player's bets, wins and rounds today (the daily loss cap), the day's biggest wins, each game's totals, the pot's last sweep |
|
||||
| `claims.json` | each claimed chunk (`<dimension> <chunk x> <chunk z>`) with its owner, and whom each owner trusts |
|
||||
| `audit.log` | one JSON line per change, written before the change: `time` (ms), `kind` (`open`, `pay`, `grant`, `ah_listing_fee`...), `amount`, `from`/`to` with their balance before, `by` for an operator, `to_pool`/`from_pool` with `pool_before` and `reason` for a pool's movement; a trade's line (`trade`: both players, their items and money) comes before its swap, `trade_failed` after one that didn't go through |
|
||||
@@ -329,13 +370,13 @@ acting player's items (API 9), the server menus and `native_exchange_items` (API
|
||||
`native_run_command` (API 21), `native_destroy_block` (API 14), `native_right_click_block`, the acting player and the
|
||||
action bar (API 21), `native_block_spread`, `native_piston_move`, `native_explosion_detonate`, `native_fill_bucket` and
|
||||
`native_attack_entity` (API 22), `native_sidebar`, `native_tab_list` and `native_tick_stats` (API 49),
|
||||
`native_set_nickname` and `native_text_allowed` (API 62), the server tick and the world lifecycle.
|
||||
`native_set_nickname` and `native_text_allowed` (API 62), `native_hide_player` (API 314, horizon#7003), the server tick and the world lifecycle.
|
||||
`compatibility.json` names `"hooks": 58`.
|
||||
|
||||
| File | What it is |
|
||||
|---|---|
|
||||
| `mixins/smp/horizon-mixin.toml` | The bundle: twenty-seven modules and the HEAD hooks |
|
||||
| `mixins/smp/smp_store.rs`, `smp_economy.rs`, `smp_market.rs`, `smp_recipes.rs`, `smp_shop.rs`, `smp_nick.rs`, `smp_trade.rs`, `smp_games.rs`, `smp_lottery.rs`, `smp_places.rs`, `smp_claims.rs`, `smp_board.rs`, `smp_presence.rs`, `smp_auction.rs`, `smp_report.rs`, `smp_market_prices.rs`, `smp_market_menu.rs`, `smp_market_chart.rs`, `smp_invsee.rs` | The rules, no game types (a trade's stacks are a type parameter): added to the game as `game::smp_store`, `game::smp_economy`, `game::smp_market`, `game::smp_recipes`, `game::smp_shop`, `game::smp_nick`, `game::smp_trade`, `game::smp_games`, `game::smp_lottery`, `game::smp_places`, `game::smp_claims`, `game::smp_board`, `game::smp_presence`, `game::smp_auction`, `game::smp_report`, `game::smp_market_prices`, `game::smp_market_menu`, `game::smp_market_chart`, `game::smp_invsee`; also this crate's modules (`src/lib.rs`), for their tests |
|
||||
| `mixins/smp/horizon-mixin.toml` | The bundle: twenty-nine modules and the HEAD hooks |
|
||||
| `mixins/smp/smp_store.rs`, `smp_economy.rs`, `smp_market.rs`, `smp_recipes.rs`, `smp_shop.rs`, `smp_nick.rs`, `smp_trade.rs`, `smp_games.rs`, `smp_lottery.rs`, `smp_places.rs`, `smp_claims.rs`, `smp_board.rs`, `smp_presence.rs`, `smp_auction.rs`, `smp_report.rs`, `smp_market_prices.rs`, `smp_market_menu.rs`, `smp_market_chart.rs`, `smp_invsee.rs`, `smp_switch.rs`, `smp_vanish.rs` | The rules, no game types (a trade's stacks are a type parameter): added to the game as `game::smp_store`, `game::smp_economy`, `game::smp_market`, `game::smp_recipes`, `game::smp_shop`, `game::smp_nick`, `game::smp_trade`, `game::smp_games`, `game::smp_lottery`, `game::smp_places`, `game::smp_claims`, `game::smp_board`, `game::smp_presence`, `game::smp_auction`, `game::smp_report`, `game::smp_market_prices`, `game::smp_market_menu`, `game::smp_market_chart`, `game::smp_invsee`, `game::smp_switch`, `game::smp_vanish`; also this crate's modules (`src/lib.rs`), for their tests |
|
||||
| `mixins/smp/glue.rs` | The state for the open world and the edges to the game's types (`game::smp_glue`) |
|
||||
| `mixins/smp/{register,command,tick,lifecycle,menu_clicked,menu_closed,destroy_block,right_click_block,block_spread,piston_move,explosion_detonate,fill_bucket,attack_entity}.rs` | One small helper per hook |
|
||||
| `mixins/smp/smp_casino.rs`, `smp_casino_roulette.rs`, `smp_casino_slots.rs`, `smp_casino_crash.rs`, `smp_casino_blackjack.rs` | The casino's rules, menus and effects (`game::smp_casino*`) |
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"game": "26.3",
|
||||
"hooks": 62,
|
||||
"hooks": 314,
|
||||
"horizon_repository": "https://github.com/horizon-rust/horizon",
|
||||
"horizon_revision": "975c07d9151847e6af13afc4b5ab37b6bbe78ef5",
|
||||
"horizon_revision": "48c63bc09d9acc82f2074e75ae59720514796a9d",
|
||||
"horizon_mixins_repository": "https://github.com/horizon-rust/horizon-mixins",
|
||||
"horizon_mixins_revision": "d9c83d7096b7ea5b83678da2efdfd6161f74997e"
|
||||
}
|
||||
+405
-68
@@ -2,7 +2,7 @@
|
||||
// between the game's types and the rules (smp_economy, smp_market, smp_shop,
|
||||
// smp_trade, smp_games, smp_lottery, smp_places, smp_claims). Added to
|
||||
// the game as `game::smp_glue`; the helpers (register.rs, command.rs,
|
||||
// tick.rs, lifecycle.rs, menu_clicked.rs, menu_closed.rs, destroy_block.rs,
|
||||
// tick.rs, level_tick.rs, lifecycle.rs, menu_clicked.rs, menu_closed.rs, destroy_block.rs,
|
||||
// right_click_block.rs, block_spread.rs, piston_move.rs,
|
||||
// explosion_detonate.rs, fill_bucket.rs, attack_entity.rs) call it from the
|
||||
// hook points.
|
||||
@@ -27,7 +27,9 @@ use crate::game::{
|
||||
smp_nick::{self, Names as _, Nicks},
|
||||
smp_shop::{self, Shop},
|
||||
smp_store,
|
||||
smp_switch::{self, Part, Switches},
|
||||
smp_trade::{self, Act, Cell, Trades},
|
||||
smp_vanish::{self, Vanish},
|
||||
};
|
||||
use horizon_client::{
|
||||
items::ItemStack,
|
||||
@@ -39,7 +41,7 @@ use std::{path::PathBuf, sync::Mutex};
|
||||
/// The mod's id, name and version.
|
||||
pub const ID: &str = "smp";
|
||||
pub const NAME: &str = "SMP";
|
||||
pub const VERSION: &str = "0.31.0";
|
||||
pub const VERSION: &str = "0.33.0";
|
||||
|
||||
/// `config.json` in `world/horizon-smp/`, written with its defaults the
|
||||
/// first time, and again with the keys a newer version adds (values kept).
|
||||
@@ -123,6 +125,14 @@ pub struct Smp {
|
||||
/// `/claim show`: whose chunk border is drawn (level, chunk x, chunk z)
|
||||
/// until when.
|
||||
shows: std::collections::HashMap<u128, (String, i32, i32, i64)>,
|
||||
/// `/vanish`: an error when `vanish.json` can't be read (nobody is
|
||||
/// hidden then, and the command says so).
|
||||
vanish: Result<Vanish, String>,
|
||||
/// The vanished players on who were sent the subtitle: cleared and
|
||||
/// counted for sleeping again once they show themselves or leave.
|
||||
vanish_titled: std::collections::HashSet<u128>,
|
||||
/// The parts operators turned off (`/smp off`).
|
||||
switches: Switches,
|
||||
}
|
||||
|
||||
static STATE: Mutex<Option<Smp>> = Mutex::new(None);
|
||||
@@ -133,6 +143,11 @@ static BUSY: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(
|
||||
/// Sleep-count changes decided inside a player's command, for the next
|
||||
/// second's tick.
|
||||
static PENDING_SLEEP: Mutex<Vec<smp_presence::Act>> = Mutex::new(Vec::new());
|
||||
/// Whether an `/rtp` waits for its land: the Overworld's tick reads it then.
|
||||
static RTP: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
|
||||
/// What each `/rtp`'s reads cost the Overworld's tick, by player: reads,
|
||||
/// total and longest (µs), for the line logged when they land.
|
||||
static RTP_COST: Mutex<Vec<(u128, u32, u64, u64)>> = Mutex::new(Vec::new());
|
||||
|
||||
fn open(dir: PathBuf) -> Smp {
|
||||
let config_path = dir.join("config.json");
|
||||
@@ -164,7 +179,7 @@ fn open(dir: PathBuf) -> Smp {
|
||||
let mut config = config;
|
||||
// Prices never edited: today's defaults (the balances are untouched).
|
||||
let write = smp_market::migrate(&mut config.market) | write;
|
||||
// 0.13's sidebar (the richest ten), never edited: 0.14's scoreboard.
|
||||
// 0.13's sidebar title, never edited: today's.
|
||||
let write = smp_board::migrate(&mut config.board) | write;
|
||||
if write && let Err(e) = smp_store::write_json(&config_path, &config) {
|
||||
log::error!("[smp] {e}");
|
||||
@@ -194,6 +209,10 @@ fn open(dir: PathBuf) -> Smp {
|
||||
Err(e) => log::error!("[smp] claims are off: {e}"),
|
||||
}
|
||||
let board = Board::open(&dir, config.board);
|
||||
let vanish = Vanish::open(&dir);
|
||||
if let Err(e) = &vanish {
|
||||
log::error!("[smp] /vanish is off: {e}");
|
||||
}
|
||||
if let Err(e) = &board {
|
||||
log::error!("[smp] the sidebar is off: {e}");
|
||||
}
|
||||
@@ -218,6 +237,13 @@ fn open(dir: PathBuf) -> Smp {
|
||||
if let Err(e) = &presence {
|
||||
log::error!("[smp] welcomes, daily rewards and AFK are off: {e}");
|
||||
}
|
||||
let (switches, problem) = Switches::open(&dir);
|
||||
if let Some(e) = problem {
|
||||
log::error!("[smp] {e}: every part is on");
|
||||
}
|
||||
for p in Part::ALL.into_iter().filter(|&p| !switches.on(p)) {
|
||||
log::warn!("[smp] {} is turned off (/smp on {} turns it on)", p.name(), p.name());
|
||||
}
|
||||
let (casino, problem) = Casino::open(&dir, config.casino, config.gambling.enabled);
|
||||
if let Some(e) = problem {
|
||||
log::error!("[smp] casino.json can't be read, the day's ledger starts again (kept apart): {e}");
|
||||
@@ -244,6 +270,9 @@ fn open(dir: PathBuf) -> Smp {
|
||||
nicks,
|
||||
nicks_shown: Default::default(),
|
||||
shows: Default::default(),
|
||||
vanish,
|
||||
vanish_titled: Default::default(),
|
||||
switches,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,9 +310,6 @@ fn save(smp: &mut Smp) {
|
||||
{
|
||||
log::error!("[smp] {err}");
|
||||
}
|
||||
if let Err(err) = smp.market.save() {
|
||||
log::error!("[smp] {err}");
|
||||
}
|
||||
if let Ok(p) = &mut smp.places
|
||||
&& let Err(err) = p.save(chrono::Utc::now().timestamp_millis())
|
||||
{
|
||||
@@ -312,6 +338,14 @@ fn save(smp: &mut Smp) {
|
||||
if let Err(err) = smp.casino.save() {
|
||||
log::error!("[smp] {err}");
|
||||
}
|
||||
if let Ok(v) = &mut smp.vanish
|
||||
&& let Err(err) = v.save()
|
||||
{
|
||||
log::error!("[smp] {err}");
|
||||
}
|
||||
if let Err(err) = smp.switches.save() {
|
||||
log::error!("[smp] {err}");
|
||||
}
|
||||
}
|
||||
|
||||
/// The commands, for every player's command tree.
|
||||
@@ -360,6 +394,8 @@ pub fn commands(commands: &mut Vec<NativeCommand>) {
|
||||
"tpdeny [player:word]",
|
||||
"rules",
|
||||
"back",
|
||||
"rtp",
|
||||
"wild",
|
||||
"playtime [player:word]",
|
||||
"claim",
|
||||
"claim info",
|
||||
@@ -394,9 +430,14 @@ pub fn commands(commands: &mut Vec<NativeCommand>) {
|
||||
"setwarp <name:word>",
|
||||
"delwarp <name:word>",
|
||||
"nick <name:word> <nick:word>",
|
||||
"vanish",
|
||||
"vanish list",
|
||||
] {
|
||||
commands.push(NativeCommand::op(usage, 3));
|
||||
}
|
||||
for usage in ["smp", "smp status", "smp on [part:word]", "smp off [part:word] [reason:text]"] {
|
||||
commands.push(NativeCommand::op(usage, smp_switch::LEVEL));
|
||||
}
|
||||
// Operators make money (an event prize, a bug refund: with a reason
|
||||
// everyone reads) and take a cheat's gains away.
|
||||
for name in ["eco", "money"] {
|
||||
@@ -555,18 +596,91 @@ const COMMANDS: [&str; 16] = [
|
||||
"lottery", "ah", "casino",
|
||||
];
|
||||
|
||||
/// `/pay <player> <amount>`'s receiver (account, name) and amount, as
|
||||
/// `Economy::pay` reads them; None for another command.
|
||||
fn pay_target(e: &Economy, line: &str) -> Option<(u128, String, i64)> {
|
||||
let [cmd, who, amount] = line.split_whitespace().collect::<Vec<_>>()[..] else {
|
||||
return None;
|
||||
/// The world border, from the Overworld's `world_border.dat` (the root's in
|
||||
/// older worlds), as the game last saved it; None without one (the default
|
||||
/// border, 60 million blocks wide).
|
||||
fn world_border(world: &std::path::Path) -> Option<smp_places::Bounds> {
|
||||
let files = [
|
||||
world.join("dimensions/minecraft/overworld/data/minecraft/world_border.dat"),
|
||||
world.join("data/minecraft/world_border.dat"),
|
||||
];
|
||||
let root = files
|
||||
.iter()
|
||||
.find_map(|f| horizon_world::datafix::files::read_saved_dat(f).ok().flatten())?;
|
||||
let border = horizon_client::worldborder::WorldBorder::unpack(root.get("data")?);
|
||||
Some(border.bounds())
|
||||
}
|
||||
|
||||
/// After a level's tick: in the Overworld, each `/rtp` that waits reads the
|
||||
/// chunk it loads (the game reads only the level it ticks), five times a
|
||||
/// second. Columns in another player's claim are never a spot.
|
||||
pub(in crate::game) fn level_ticked(game: &mut Game<'_>, dimension: &str) {
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
if dimension != smp_places::OVERWORLD
|
||||
|| !RTP.load(Relaxed)
|
||||
|| TICKS.load(Relaxed) % 4 != 0
|
||||
|| !game.native_on(ID)
|
||||
{
|
||||
return;
|
||||
}
|
||||
let Some(dir) = game.native_world_dir() else {
|
||||
return;
|
||||
};
|
||||
if cmd != "pay" {
|
||||
let mut guard = state(dir.join(smp_store::DIR));
|
||||
let Some(smp) = guard.as_mut() else {
|
||||
return;
|
||||
};
|
||||
let (Ok(places), claims) = (&mut smp.places, &smp.claims) else {
|
||||
return;
|
||||
};
|
||||
let game = &*game;
|
||||
let mut cost = RTP_COST.lock().unwrap_or_else(|e| e.into_inner());
|
||||
places.rtp_look(&mut |uuid, (cx, cz)| {
|
||||
let started = std::time::Instant::now();
|
||||
let ground = read_chunk(game, claims, uuid, (cx, cz));
|
||||
let us = started.elapsed().as_micros() as u64;
|
||||
match cost.iter_mut().find(|c| c.0 == uuid) {
|
||||
Some(c) => *c = (uuid, c.1 + 1, c.2 + us, c.3.max(us)),
|
||||
None => cost.push((uuid, 1, us, us)),
|
||||
}
|
||||
ground
|
||||
});
|
||||
RTP.store(places.rtp_searching(), Relaxed);
|
||||
}
|
||||
|
||||
/// Chunk `(cx, cz)` of the Overworld as `/rtp` reads it, None while it isn't
|
||||
/// loaded; no columns when another player claimed it.
|
||||
fn read_chunk(
|
||||
game: &Game<'_>,
|
||||
claims: &Result<Claims, String>,
|
||||
uuid: u128,
|
||||
(cx, cz): (i32, i32),
|
||||
) -> Option<smp_places::Ground> {
|
||||
let columns: Vec<(i32, i32)> = (0..256).map(|i| (cx * 16 + i / 16, cz * 16 + i % 16)).collect();
|
||||
let read = game
|
||||
.native_read_columns(
|
||||
smp_places::OVERWORLD,
|
||||
&columns,
|
||||
crate::game::native_world_edit::NativeHeightmap::MotionBlockingNoLeaves,
|
||||
(1, 2),
|
||||
)
|
||||
.ok()?;
|
||||
if read.bases.iter().any(Option::is_none) {
|
||||
return None;
|
||||
}
|
||||
let amount = smp_economy::parse_amount(amount)?;
|
||||
let (to, account) = e.find(who)?;
|
||||
Some((to, account.name.clone(), amount))
|
||||
let mut ground = smp_places::Ground { palette: read.palette, columns: Vec::new() };
|
||||
let claimed = claims
|
||||
.as_ref()
|
||||
.ok()
|
||||
.and_then(|c| c.owner(smp_places::OVERWORLD, cx * 16, cz * 16))
|
||||
.is_some_and(|owner| owner != uuid);
|
||||
if !claimed {
|
||||
for (i, (&(x, z), base)) in columns.iter().zip(&read.bases).enumerate() {
|
||||
let b = &read.blocks[i * 3..i * 3 + 3];
|
||||
ground.columns.push((x, z, base.unwrap_or_default(), [b[0], b[1], b[2]]));
|
||||
}
|
||||
}
|
||||
Some(ground)
|
||||
}
|
||||
|
||||
/// The players on, as the places see them.
|
||||
@@ -618,6 +732,8 @@ pub(in crate::game) fn perform(
|
||||
&smp_board::COMMANDS,
|
||||
&smp_presence::COMMANDS,
|
||||
&smp_nick::COMMANDS,
|
||||
&smp_vanish::COMMANDS,
|
||||
&smp_switch::COMMANDS,
|
||||
];
|
||||
if !game.native_on(ID) || !ours.iter().any(|c| c.contains(&word)) {
|
||||
return;
|
||||
@@ -634,20 +750,44 @@ pub(in crate::game) fn perform(
|
||||
// Survival and adventure: a creative player could sell what they made.
|
||||
let trades = matches!(player.game_mode, 0 | 2);
|
||||
let players = game.native_server_players();
|
||||
let online = |name: &str| {
|
||||
players
|
||||
.iter()
|
||||
.find(|p| p.name.eq_ignore_ascii_case(name))
|
||||
.map(|p| (p.uuid, p.name.clone(), matches!(p.game_mode, 0 | 2)))
|
||||
};
|
||||
let now = chrono::Utc::now().timestamp_millis();
|
||||
let mut guard = state(dir.join(smp_store::DIR));
|
||||
let smp = guard.as_mut().expect("opened");
|
||||
// A vanished player is out of reach (trades, teleports, bets) of those
|
||||
// who don't see them.
|
||||
let hidden: Vec<u128> = smp.vanish.as_ref().map_or_else(
|
||||
|_| Vec::new(),
|
||||
|v| players.iter().filter(|p| !v.sees(me.level, p.uuid)).map(|p| p.uuid).collect(),
|
||||
);
|
||||
let online = |name: &str| {
|
||||
players
|
||||
.iter()
|
||||
.filter(|p| !hidden.contains(&p.uuid))
|
||||
.find(|p| p.name.eq_ignore_ascii_case(name))
|
||||
.map(|p| (p.uuid, p.name.clone(), matches!(p.game_mode, 0 | 2)))
|
||||
};
|
||||
// The switches: `/smp` itself, then a part turned off answers alone.
|
||||
if let Some((out, changed)) = smp.switches.command(&me, now, line) {
|
||||
let out = switched(game, smp, &me, &players, changed, now, out);
|
||||
drop(guard);
|
||||
*ran = true;
|
||||
show(game, out);
|
||||
return;
|
||||
}
|
||||
if let Some(part) = smp_switch::Part::of_command(word).filter(|&p| !smp.switches.on(p)) {
|
||||
let refusal = smp_switch::refusal(part);
|
||||
drop(guard);
|
||||
*ran = true;
|
||||
show(game, vec![refusal]);
|
||||
return;
|
||||
}
|
||||
let mut acts = Vec::new();
|
||||
// Any SMP command is activity (no longer AFK), and past the limit waits.
|
||||
let mut sleeps = Vec::new();
|
||||
let mut before = Vec::new();
|
||||
if let Ok(p) = &mut smp.presence {
|
||||
if let Ok(p) = &mut smp.presence
|
||||
&& smp.switches.on(Part::Presence)
|
||||
{
|
||||
match p.allow(&me, now) {
|
||||
Ok((o, a)) => (before, sleeps) = (o, a),
|
||||
Err(o) => {
|
||||
@@ -669,7 +809,24 @@ pub(in crate::game) fn perform(
|
||||
n.resolve(line, &is_name, &name_of)
|
||||
});
|
||||
let line = resolved.as_deref().unwrap_or(line);
|
||||
let out = if smp_nick::COMMANDS.contains(&word) {
|
||||
let out = if smp_vanish::COMMANDS.contains(&word) {
|
||||
let ops: Vec<u128> = players.iter().filter(|p| p.permission >= smp_vanish::LEVEL).map(|p| p.uuid).collect();
|
||||
match &mut smp.vanish {
|
||||
Ok(v) => {
|
||||
let out = v.command(&me, &ops, line).unwrap_or_default();
|
||||
// Hidden or shown at once; the subtitle comes with the next
|
||||
// second (commands can't run inside a player's).
|
||||
for (uuid, hidden) in v.changes() {
|
||||
game.native_hide_player(uuid, hidden.then_some(smp_vanish::LEVEL));
|
||||
}
|
||||
if let Ok(p) = &mut smp.presence {
|
||||
p.quiet = v.quiet();
|
||||
}
|
||||
out
|
||||
}
|
||||
Err(_) => vec![Out::Error("/vanish is off: vanish.json can't be read (see the server log).".into())],
|
||||
}
|
||||
} else if smp_nick::COMMANDS.contains(&word) {
|
||||
let names = ServerNames::read(&dir, smp.economy.as_ref().ok());
|
||||
match &mut smp.nicks {
|
||||
Ok(n) => {
|
||||
@@ -739,7 +896,15 @@ pub(in crate::game) fn perform(
|
||||
}
|
||||
} else if smp_places::COMMANDS.contains(&word) {
|
||||
match &mut smp.places {
|
||||
Ok(p) => p.command(&me, &bodies(&players), now, line).unwrap_or_default(),
|
||||
Ok(p) => {
|
||||
if matches!(word, "rtp" | "wild") {
|
||||
p.border = world_border(&dir);
|
||||
RTP_COST.lock().unwrap_or_else(|e| e.into_inner()).retain(|c| c.0 != me.uuid);
|
||||
BUSY.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
let seen: Vec<NativeServerPlayer> = players.iter().filter(|p| !hidden.contains(&p.uuid)).cloned().collect();
|
||||
p.command(&me, &bodies(&seen), now, line).unwrap_or_default()
|
||||
}
|
||||
Err(_) => vec![Out::Error(
|
||||
"Homes and warps are off: their file can't be read (see the server log).".into(),
|
||||
)],
|
||||
@@ -767,27 +932,7 @@ pub(in crate::game) fn perform(
|
||||
let report = smp.report.as_ref();
|
||||
report
|
||||
.and_then(|r| r.command(e, line))
|
||||
.or_else(|| {
|
||||
// A payment counts toward the receiver's daily selling
|
||||
// limit (alts can't sell for a main past it).
|
||||
let paid = pay_target(e, line);
|
||||
if let Some((to, name, amount)) = &paid
|
||||
&& let Err(left) = smp.market.may_receive(*to, *amount, now)
|
||||
{
|
||||
return Some(vec![Out::Error(format!(
|
||||
"{name} can only receive {} more today: payments count toward the same daily limit as selling to the market.",
|
||||
e.money(left)
|
||||
))]);
|
||||
}
|
||||
let before = paid.as_ref().map(|(to, ..)| e.balance(*to));
|
||||
let out = e.command(&me, &on, line);
|
||||
if let (Some((to, _, amount)), Some(b)) = (paid, before)
|
||||
&& e.balance(to) == b + amount
|
||||
{
|
||||
smp.market.count_received(to, now, amount);
|
||||
}
|
||||
out
|
||||
})
|
||||
.or_else(|| e.command(&me, &on, line))
|
||||
.or_else(|| smp.market.command(e, &me, trades, &mut bag, line))
|
||||
.or_else(|| {
|
||||
let (out, a) = smp.trades.command(e, &me, trades, &online, now, line)?;
|
||||
@@ -830,6 +975,9 @@ pub(in crate::game) fn guard(game: &mut Game<'_>, pos: [i32; 3], canceled: &mut
|
||||
};
|
||||
let mut guard = state(dir.join(smp_store::DIR));
|
||||
let smp = guard.as_mut().expect("opened");
|
||||
if !smp.switches.on(Part::Claims) {
|
||||
return;
|
||||
}
|
||||
let Ok(claims) = &mut smp.claims else {
|
||||
return;
|
||||
};
|
||||
@@ -864,7 +1012,7 @@ pub(in crate::game) fn attack(game: &mut Game<'_>, kind: &str, pos: [f64; 3], ca
|
||||
.lock()
|
||||
.unwrap_or_else(|e| e.into_inner())
|
||||
.as_ref()
|
||||
.is_some_and(|s| s.claims.as_ref().is_ok_and(|c| c.protects(kind)));
|
||||
.is_some_and(|s| s.switches.on(Part::Claims) && s.claims.as_ref().is_ok_and(|c| c.protects(kind)));
|
||||
if protected {
|
||||
guard(game, pos.map(|v| v.floor() as i32), canceled);
|
||||
}
|
||||
@@ -887,7 +1035,7 @@ pub(in crate::game) fn world_allows(dimension: &str, from: [i32; 3], to: &[[i32;
|
||||
let Ok(state) = STATE.try_lock() else {
|
||||
return true;
|
||||
};
|
||||
match state.as_ref().map(|s| &s.claims) {
|
||||
match state.as_ref().filter(|s| s.switches.on(Part::Claims)).map(|s| &s.claims) {
|
||||
Some(Ok(c)) => to.iter().all(|&t| c.spread_allowed(dimension, from, t)),
|
||||
_ => true,
|
||||
}
|
||||
@@ -902,7 +1050,7 @@ pub(in crate::game) fn explosion(game: &Game<'_>, dimension: &str, blocks: &mut
|
||||
let Ok(state) = STATE.try_lock() else {
|
||||
return;
|
||||
};
|
||||
if let Some(Ok(c)) = state.as_ref().map(|s| &s.claims) {
|
||||
if let Some(Ok(c)) = state.as_ref().filter(|s| s.switches.on(Part::Claims)).map(|s| &s.claims) {
|
||||
c.spare(dimension, blocks);
|
||||
}
|
||||
}
|
||||
@@ -1080,6 +1228,14 @@ pub(in crate::game) fn menu_clicked(game: &mut Game<'_>, uuid: u128, window: i32
|
||||
};
|
||||
let mut guard = state(dir.join(smp_store::DIR));
|
||||
let smp = guard.as_mut().expect("opened");
|
||||
// A menu of a part turned off since it opened: closed, nothing done.
|
||||
if let Some(part) = menu_part(smp, uuid, window).filter(|&p| !smp.switches.on(p)) {
|
||||
let refusal = smp_switch::refusal(part);
|
||||
drop(guard);
|
||||
game.native_close_menu(uuid);
|
||||
tell(game, uuid, vec![refusal]);
|
||||
return;
|
||||
}
|
||||
if smp.shop.views.get(&uuid).is_some_and(|v| v.0 == Some(window)) {
|
||||
shop_clicked(game, smp, uuid, slot, mode == 1);
|
||||
return;
|
||||
@@ -1128,6 +1284,133 @@ pub(in crate::game) fn menu_clicked(game: &mut Game<'_>, uuid: u128, window: i32
|
||||
show(game, out);
|
||||
}
|
||||
|
||||
/// The part whose menu `window` is, open for that player.
|
||||
fn menu_part(smp: &Smp, uuid: u128, window: i32) -> Option<Part> {
|
||||
let open = |v: Option<i32>| v == Some(window);
|
||||
if smp.shop.views.get(&uuid).is_some_and(|v| open(v.0)) || smp.market_menus.views.get(&uuid).is_some_and(|v| open(v.0)) {
|
||||
return Some(Part::Market);
|
||||
}
|
||||
if smp.auction.as_ref().is_some_and(|a| a.views.get(&uuid).is_some_and(|v| open(v.0))) {
|
||||
return Some(Part::Auction);
|
||||
}
|
||||
if smp.casino.owns(uuid, window) {
|
||||
return Some(Part::Gambling);
|
||||
}
|
||||
let trade = smp.trades.trade_of(uuid);
|
||||
trade.and_then(|t| t.side_of(uuid).map(|me| t.sides[me].window)).filter(|&w| open(w)).map(|_| Part::Trades)
|
||||
}
|
||||
|
||||
/// Parts turned off or on by `/smp`: what was under way in those turned off
|
||||
/// stops (trades end with nothing moved, menus close, teleports waiting are
|
||||
/// called off, sidebars and nicknames are taken away), the other operators
|
||||
/// on are told, and the change is in the server's log and `audit.log`.
|
||||
fn switched(
|
||||
game: &mut Game<'_>,
|
||||
smp: &mut Smp,
|
||||
me: &Player,
|
||||
players: &[NativeServerPlayer],
|
||||
changed: smp_switch::Changed,
|
||||
now: i64,
|
||||
mut out: Vec<Out>,
|
||||
) -> Vec<Out> {
|
||||
let names = |ps: &[Part]| ps.iter().map(|p| p.name()).collect::<Vec<_>>().join(", ");
|
||||
let reason = line_reason(&smp.switches, &changed);
|
||||
for (verb, parts) in [("off", &changed.off), ("on", &changed.on)] {
|
||||
if parts.is_empty() {
|
||||
continue;
|
||||
}
|
||||
log::warn!("[smp] {} turned {verb}: {}{reason}", me.name, names(parts));
|
||||
if let Ok(e) = &mut smp.economy
|
||||
&& let Err(err) = e.record(serde_json::json!({
|
||||
"time": now, "kind": format!("switch_{verb}"), "by": smp_store::uuid_string(me.uuid),
|
||||
"parts": parts.iter().map(|p| p.name()).collect::<Vec<_>>(), "reason": reason.trim_start_matches(": "),
|
||||
}))
|
||||
{
|
||||
log::error!("[smp] {err}");
|
||||
}
|
||||
for p in players.iter().filter(|p| p.permission >= smp_switch::LEVEL && p.uuid != me.uuid) {
|
||||
out.push(Out::Tell(p.uuid, format!("[SMP] {} turned {verb}: {}{reason}", me.name, names(parts))));
|
||||
}
|
||||
}
|
||||
for part in changed.off {
|
||||
match part {
|
||||
Part::Trades => {
|
||||
let sides: Vec<u128> = smp.trades.open.iter().map(|t| t.sides[0].uuid).collect();
|
||||
for uuid in sides {
|
||||
let (o, acts) = smp.trades.end(uuid, "trading was turned off");
|
||||
out.extend(o);
|
||||
act(game, smp, acts);
|
||||
}
|
||||
}
|
||||
Part::Market => {
|
||||
let open: Vec<u128> = smp.shop.views.keys().chain(smp.market_menus.views.keys()).copied().collect();
|
||||
open.into_iter().for_each(|u| game.native_close_menu(u));
|
||||
}
|
||||
Part::Auction => {
|
||||
let open: Vec<u128> = smp.auction.as_ref().map(|a| a.views.keys().copied().collect()).unwrap_or_default();
|
||||
open.into_iter().for_each(|u| game.native_close_menu(u));
|
||||
}
|
||||
// A round under way still ends and is paid when its menu closes
|
||||
// (`menu_clicked` closes it at the next click).
|
||||
Part::Gambling => {}
|
||||
Part::Places => {
|
||||
if let Ok(p) = &mut smp.places {
|
||||
for t in std::mem::take(&mut p.pending) {
|
||||
out.push(Out::Tell(t.uuid, "Teleport called off: teleporting was turned off.".into()));
|
||||
}
|
||||
}
|
||||
}
|
||||
Part::Claims => smp.shows.clear(),
|
||||
Part::Board => {
|
||||
if let Ok(b) = &mut smp.board {
|
||||
b.forget();
|
||||
}
|
||||
let blank = Text::literal("");
|
||||
for p in players {
|
||||
game.native_sidebar(p.uuid, None);
|
||||
game.native_tab_list(p.uuid, &blank, &blank);
|
||||
}
|
||||
}
|
||||
Part::Nicks => {
|
||||
for uuid in std::mem::take(&mut smp.nicks_shown) {
|
||||
game.native_set_nickname(uuid, None);
|
||||
}
|
||||
}
|
||||
// `/invsee` refuses and its open menus take no click.
|
||||
Part::Invsee | Part::Economy | Part::Presence => {}
|
||||
}
|
||||
}
|
||||
BUSY.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
out
|
||||
}
|
||||
|
||||
/// `: why` of the parts just turned off (as `switches.json` has it), or "".
|
||||
fn line_reason(switches: &Switches, changed: &smp_switch::Changed) -> String {
|
||||
changed
|
||||
.off
|
||||
.first()
|
||||
.and_then(|&p| switches.why(p))
|
||||
.filter(|r| !r.is_empty())
|
||||
.map(|r| format!(": {r}"))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
/// What a command of a part turned off answers.
|
||||
pub(in crate::game) fn refusal(part: Part) -> String {
|
||||
match smp_switch::refusal(part) {
|
||||
Out::Error(s) | Out::Reply(s) | Out::Tell(_, s) | Out::All(s) => s,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a part of the SMP is on (`/smp off` turns them off), for the
|
||||
/// SMP's other glue (`/invsee`).
|
||||
pub(in crate::game) fn part_on(game: &Game<'_>, part: Part) -> bool {
|
||||
let Some(dir) = game.native_world_dir() else {
|
||||
return true;
|
||||
};
|
||||
state(dir.join(smp_store::DIR)).as_ref().is_none_or(|s| s.switches.on(part))
|
||||
}
|
||||
|
||||
/// A native menu closed: a trade's ends, nothing moved.
|
||||
pub(in crate::game) fn menu_closed(game: &mut Game<'_>, uuid: u128, window: i32) {
|
||||
if !game.native_on(ID) {
|
||||
@@ -1288,10 +1571,6 @@ fn shop_items(smp: &Smp, uuid: u128) -> Vec<Option<ItemStack>> {
|
||||
button("minecraft:gold_ingot", &format!("Balance: {}", money(b)), "gold")
|
||||
}
|
||||
C::SellAll => button("minecraft:hopper", "Sell everything the market buys", "green"),
|
||||
C::Limits => {
|
||||
let line = economy.and_then(|e| smp.market.limits_line(e, uuid));
|
||||
button("minecraft:clock", &line.unwrap_or_else(|| "No selling limit".into()), "aqua")
|
||||
}
|
||||
C::Help => button("minecraft:book", smp_shop::HELP_TEXT, "white"),
|
||||
C::Close => button("minecraft:barrier", "Close", "red"),
|
||||
})
|
||||
@@ -1472,9 +1751,6 @@ fn market_items(smp: &Smp, uuid: u128, now: i64) -> Vec<Option<ItemStack>> {
|
||||
let mut lines = price_lines(smp, &id, now);
|
||||
let base = smp.market.base(&id);
|
||||
lines.push((format!("Base price: {}", money(base.buy.max(base.sell))), "gray"));
|
||||
if let Some(left) = smp.market.items_left(uuid, now, &id) {
|
||||
lines.push((format!("You may still sell {left} today"), "gray"));
|
||||
}
|
||||
with_lore(button(&id, &name(&id), "white"), &lines)
|
||||
}
|
||||
C::Buy(id, n) => {
|
||||
@@ -2019,7 +2295,31 @@ pub(in crate::game) fn tick(game: &mut Game<'_>) {
|
||||
};
|
||||
let now = chrono::Utc::now().timestamp_millis();
|
||||
let mut out = Vec::new();
|
||||
// `/vanish`, once a second: the game told who is hidden (after a
|
||||
// restart), and the subtitle sent to each vanished player on.
|
||||
let vanished = smp.vanish.as_ref().map(|v| v.quiet()).unwrap_or_default();
|
||||
let (mut subtitles, mut unvanished) = (Vec::new(), Vec::new());
|
||||
if n % 20 == 0
|
||||
&& let Ok(v) = &mut smp.vanish
|
||||
{
|
||||
for (uuid, hidden) in v.changes() {
|
||||
game.native_hide_player(uuid, hidden.then_some(smp_vanish::LEVEL));
|
||||
}
|
||||
for p in &players {
|
||||
if vanished.contains(&p.uuid) {
|
||||
subtitles.push((p.uuid, p.name.clone()));
|
||||
} else if smp.vanish_titled.contains(&p.uuid) {
|
||||
unvanished.push((p.uuid, p.name.clone()));
|
||||
}
|
||||
}
|
||||
smp.vanish_titled = subtitles.iter().map(|s| s.0).collect();
|
||||
if let Ok(p) = &mut smp.presence {
|
||||
p.quiet = vanished.clone();
|
||||
}
|
||||
}
|
||||
// Teleports: each tick while one warms up; requests expire once a second.
|
||||
// With places off only the playtime counts.
|
||||
let places_on = smp.switches.on(Part::Places);
|
||||
if let Ok(p) = &mut smp.places
|
||||
&& (n % 20 == 0 || warming)
|
||||
{
|
||||
@@ -2027,7 +2327,7 @@ pub(in crate::game) fn tick(game: &mut Game<'_>) {
|
||||
let on: Vec<u128> = players.iter().map(|p| p.uuid).collect();
|
||||
p.played(&on, now);
|
||||
}
|
||||
let (o, go) = p.tick(&bodies(&players), now);
|
||||
let (o, go) = if places_on { p.tick(&bodies(&players), now) } else { Default::default() };
|
||||
out.extend(o);
|
||||
for t in go {
|
||||
// `.3`: a whole number would be a block's centre to `tp`.
|
||||
@@ -2048,8 +2348,20 @@ pub(in crate::game) fn tick(game: &mut Game<'_>) {
|
||||
{
|
||||
log::warn!("[smp] no safe spot near the spawn: {spread}");
|
||||
}
|
||||
if t.what == "a random spot" {
|
||||
let mut cost = RTP_COST.lock().unwrap_or_else(|e| e.into_inner());
|
||||
let (_, n, total, max) = cost.iter().find(|c| c.0 == t.uuid).copied().unwrap_or_default();
|
||||
cost.retain(|c| c.0 != t.uuid);
|
||||
log::info!(
|
||||
"[smp] /rtp: {} to {:.1} {:.1} {:.1} ({n} chunk reads on the tick: {total} µs, longest {max} µs)",
|
||||
t.name, t.to.x, t.to.y, t.to.z
|
||||
);
|
||||
}
|
||||
out.push(Out::Tell(t.uuid, t.done.clone()));
|
||||
}
|
||||
// `/rtp`'s chunks: held loaded while it looks, let go after.
|
||||
p.force(now, &mut |line| game.native_run_command(line));
|
||||
RTP.store(p.rtp_searching(), Relaxed);
|
||||
}
|
||||
if let Ok(e) = &mut smp.economy
|
||||
&& n % 20 == 0
|
||||
@@ -2069,9 +2381,16 @@ pub(in crate::game) fn tick(game: &mut Game<'_>) {
|
||||
for uuid in gone {
|
||||
smp.games.left(e, uuid);
|
||||
}
|
||||
out.extend(smp.lottery.tick(&smp.games.config, e, now, &mut below));
|
||||
// A part turned off waits: its draws, endings, markings and payments
|
||||
// happen once it is on again (coinflips not taken still go back).
|
||||
let on = |p| smp.switches.on(p);
|
||||
if on(Part::Gambling) {
|
||||
out.extend(smp.lottery.tick(&smp.games.config, e, now, &mut below));
|
||||
}
|
||||
// The auction house: notes for who came on, listings that ended.
|
||||
if let Some(a) = &mut smp.auction {
|
||||
if let Some(a) = &mut smp.auction
|
||||
&& on(Part::Auction)
|
||||
{
|
||||
let on: Vec<u128> = players.iter().map(|p| p.uuid).collect();
|
||||
out.extend(a.tick(e, &on, now));
|
||||
}
|
||||
@@ -2146,7 +2465,9 @@ pub(in crate::game) fn tick(game: &mut Game<'_>) {
|
||||
}
|
||||
// Welcomes, daily rewards and AFK, once a second.
|
||||
let mut sleeps = std::mem::take(&mut *PENDING_SLEEP.lock().unwrap_or_else(|e| e.into_inner()));
|
||||
let economy_on = smp.switches.on(Part::Economy);
|
||||
if n % 20 == 0
|
||||
&& smp.switches.on(Part::Presence)
|
||||
&& let Ok(pr) = &mut smp.presence
|
||||
{
|
||||
let seen: Vec<smp_presence::Seen> = players
|
||||
@@ -2165,7 +2486,9 @@ pub(in crate::game) fn tick(game: &mut Game<'_>) {
|
||||
for a in acts {
|
||||
match a {
|
||||
smp_presence::Act::Reward(uuid) => {
|
||||
if let Ok(e) = &mut smp.economy {
|
||||
if let Ok(e) = &mut smp.economy
|
||||
&& economy_on
|
||||
{
|
||||
match e.adjust("daily_reward", uuid, reward, "") {
|
||||
Ok(_) => out.push(Out::Tell(uuid, format!("Daily reward: {}.", e.money(reward)))),
|
||||
Err(err) => log::warn!("[smp] daily reward: {err}"),
|
||||
@@ -2204,6 +2527,7 @@ pub(in crate::game) fn tick(game: &mut Game<'_>) {
|
||||
let mut tab = None;
|
||||
let (nicks, places) = (&smp.nicks, &smp.places);
|
||||
if n % 20 == 0
|
||||
&& smp.switches.on(Part::Board)
|
||||
&& let Ok(b) = &mut smp.board
|
||||
{
|
||||
let on: Vec<u128> = players.iter().map(|p| p.uuid).collect();
|
||||
@@ -2223,16 +2547,20 @@ pub(in crate::game) fn tick(game: &mut Game<'_>) {
|
||||
.unwrap_or_default(),
|
||||
played_seconds: places.as_ref().map_or(0, |p| p.played_seconds(uuid)),
|
||||
};
|
||||
b.quiet = vanished.clone();
|
||||
sidebars = b.updates(now, &on, me);
|
||||
let to = b.tab_due(now, &on);
|
||||
if !to.is_empty() {
|
||||
let (rate, mspt) = game.native_tick_stats();
|
||||
let tps = f64::from(rate).min(1000.0 / mspt.max(0.001));
|
||||
tab = Some((to, b.tab_lines(tps, mspt, on.len())));
|
||||
// Vanished players aren't counted: the footer is everyone's.
|
||||
let shown = on.iter().filter(|u| !b.quiet.contains(u)).count();
|
||||
tab = Some((to, b.tab_lines(tps, mspt, shown)));
|
||||
}
|
||||
}
|
||||
// Nicknames: the changes asked for, then those of players who came on.
|
||||
if n % 20 == 0
|
||||
&& smp.switches.on(Part::Nicks)
|
||||
&& let Ok(nicks) = &mut smp.nicks
|
||||
{
|
||||
let pending = std::mem::take(&mut nicks.pending);
|
||||
@@ -2288,6 +2616,23 @@ pub(in crate::game) fn tick(game: &mut Game<'_>) {
|
||||
game.native_sleep_ignored(uuid, ignored);
|
||||
}
|
||||
}
|
||||
// A vanished player doesn't hold the night up; the subtitle lasts a
|
||||
// little past the next second's (1.5 s), so it never blinks.
|
||||
for (uuid, name) in subtitles {
|
||||
game.native_sleep_ignored(uuid, true);
|
||||
let subtitle = serde_json::json!({"text": smp_vanish::SUBTITLE, "color": "aqua"});
|
||||
for line in [
|
||||
format!("title {name} times 0 30 5"),
|
||||
format!("title {name} subtitle {subtitle}"),
|
||||
format!("title {name} title \"\""),
|
||||
] {
|
||||
game.native_run_command(&line);
|
||||
}
|
||||
}
|
||||
for (uuid, name) in unvanished {
|
||||
game.native_sleep_ignored(uuid, false);
|
||||
game.native_run_command(&format!("title {name} clear"));
|
||||
}
|
||||
for line in particles.into_iter().chain(casino) {
|
||||
game.native_run_command(&line);
|
||||
}
|
||||
@@ -2402,14 +2747,6 @@ fn sidebar(title: &str, lines: &[Line]) -> NativeSidebar {
|
||||
)
|
||||
}
|
||||
Line::Gap => (Text::literal(""), None),
|
||||
Line::Heading => (Text::literal("Richest").with_color(text::YELLOW), None),
|
||||
Line::Top { rank, name, amount, me } => {
|
||||
let color = if *me { text::GREEN } else { text::WHITE };
|
||||
(
|
||||
Text::literal(format!("{rank}. ")).with_color(text::GRAY).append(Text::literal(name.clone()).with_color(color)),
|
||||
Some(Text::literal(amount.clone()).with_color(text::GOLD)),
|
||||
)
|
||||
}
|
||||
Line::Footer(address) => (Text::literal(address.clone()).with_color(text::AQUA), None),
|
||||
})
|
||||
.collect();
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
[mod]
|
||||
id = "smp"
|
||||
version = "0.31.0"
|
||||
version = "0.33.0"
|
||||
priority = 100
|
||||
|
||||
# The commit of horizon it was tested on: it needs horizon#6106 (merged at 975c07d): native_inventory,
|
||||
# native_move_stack, native_send_map, lore on the wire.
|
||||
# The commit of horizon it was tested on: it needs native_hide_player (horizon#7003, API 314, stamped at 48c63bc),
|
||||
# on top of horizon#6106's native_inventory, native_move_stack, native_send_map and lore on the wire.
|
||||
[compatibility]
|
||||
horizon_revision = "975c07d9151847e6af13afc4b5ab37b6bbe78ef5"
|
||||
horizon_revision = "48c63bc09d9acc82f2074e75ae59720514796a9d"
|
||||
|
||||
[[module]]
|
||||
id = "store"
|
||||
@@ -74,6 +74,12 @@ file = "crates/horizon-client/src/game.rs"
|
||||
name = "smp_trade"
|
||||
source = "smp_trade.rs"
|
||||
|
||||
[[module]]
|
||||
id = "vanish"
|
||||
file = "crates/horizon-client/src/game.rs"
|
||||
name = "smp_vanish"
|
||||
source = "smp_vanish.rs"
|
||||
|
||||
[[module]]
|
||||
id = "games"
|
||||
file = "crates/horizon-client/src/game.rs"
|
||||
@@ -164,6 +170,12 @@ file = "crates/horizon-client/src/game.rs"
|
||||
name = "smp_invsee_glue"
|
||||
source = "invsee_glue.rs"
|
||||
|
||||
[[module]]
|
||||
id = "switch"
|
||||
file = "crates/horizon-client/src/game.rs"
|
||||
name = "smp_switch"
|
||||
source = "smp_switch.rs"
|
||||
|
||||
[[module]]
|
||||
id = "glue"
|
||||
file = "crates/horizon-client/src/game.rs"
|
||||
@@ -201,6 +213,16 @@ bindings = [
|
||||
{ name = "phase", ty = "crate::game::native_lifecycle::ServerTickPhase", value = "phase" },
|
||||
]
|
||||
|
||||
[[injection]]
|
||||
id = "level_tick"
|
||||
kind = "HEAD"
|
||||
target = "horizon_client::game::native_hooks::Game<'_>::native_level_tick_post"
|
||||
helper = "level_tick.rs"
|
||||
bindings = [
|
||||
{ name = "game", ty = "&mut crate::game::Game<'_>", value = "&mut *self" },
|
||||
{ name = "dimension", ty = "&str", value = "dimension" },
|
||||
]
|
||||
|
||||
[[injection]]
|
||||
id = "lifecycle"
|
||||
kind = "HEAD"
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::game::{
|
||||
smp_economy::{Out, Player},
|
||||
smp_glue,
|
||||
smp_invsee::{self, Cell, Invsee, Kind, Move},
|
||||
smp_store,
|
||||
smp_store, smp_switch,
|
||||
};
|
||||
use horizon_client::{
|
||||
items::ItemStack,
|
||||
@@ -88,6 +88,11 @@ pub(in crate::game) fn perform(game: &mut Game<'_>, player: &NativeServerPlayer,
|
||||
return;
|
||||
}
|
||||
*ran = true;
|
||||
if !smp_glue::part_on(game, smp_switch::Part::Invsee) {
|
||||
let refusal = smp_glue::refusal(smp_switch::Part::Invsee);
|
||||
game.native_feedback(Text::literal(refusal).with_color(text::RED));
|
||||
return;
|
||||
}
|
||||
let me = Player {
|
||||
uuid: player.uuid,
|
||||
name: player.name.clone(),
|
||||
@@ -251,6 +256,12 @@ pub(in crate::game) fn menu_clicked(game: &mut Game<'_>, uuid: u128, window: i32
|
||||
if !matches!(mode, 0 | 1) {
|
||||
return true;
|
||||
}
|
||||
// Turned off since it opened: closed, nothing moves.
|
||||
if !smp_glue::part_on(game, smp_switch::Part::Invsee) {
|
||||
drop(guard);
|
||||
game.native_close_menu(uuid);
|
||||
return true;
|
||||
}
|
||||
let mut asked: Option<Move> = None;
|
||||
// Each move is in the audit log before it is made; one the log
|
||||
// refuses isn't made.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// After a level's tick: in the Overworld, the `/rtp`s waiting for their land
|
||||
// read it (the game reads only the level it ticks).
|
||||
fn smp_level_tick_post(game: &mut crate::game::Game<'_>, dimension: &str) {
|
||||
crate::game::smp_glue::level_ticked(game, dimension);
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
// The SMP's screens: a scoreboard sidebar, the player's own block first (their
|
||||
// name or nickname, balance, playtime, the players on), then the three
|
||||
// richest and the server's address, which a player hides with `/sidebar off`
|
||||
// (kept in `board.json`); and the Tab list's header and footer (the server's
|
||||
// name, its TPS and the players on). Nothing happens per tick: the ranking is
|
||||
// built once for everyone when a balance changed (at most once each
|
||||
// `min_seconds`) or every `refresh_seconds`; the players' own lines are looked
|
||||
// name or nickname, balance, playtime, the players on), then the server's
|
||||
// address, which a player hides with `/sidebar off` (kept in `board.json`);
|
||||
// and the Tab list's header and footer (the server's name, its TPS and the
|
||||
// players on). Nothing happens per tick: the balances are read once for
|
||||
// everyone when one changed (at most once each `min_seconds`) or every
|
||||
// `refresh_seconds`; the players' own lines are looked
|
||||
// at on the same `refresh_seconds`, and a sidebar is sent only to the players
|
||||
// whose lines changed and to newcomers. The Tab list goes out every
|
||||
// `tab_refresh_seconds`, and to a player who just joined. No game types. Added to the game as `game::smp_board`;
|
||||
@@ -16,10 +16,6 @@ use serde::{Deserialize, Serialize};
|
||||
use std::collections::{BTreeSet, HashMap, HashSet};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// The most of the richest a sidebar lists: vanilla's 15 lines, less the
|
||||
/// player's four, the two gaps, the heading and the address.
|
||||
const MAX_TOP: usize = 7;
|
||||
|
||||
/// `config.json`'s `board` section.
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
@@ -27,11 +23,9 @@ pub struct Config {
|
||||
/// The sidebar (each player may hide it).
|
||||
pub sidebar: bool,
|
||||
pub title: String,
|
||||
/// How many of the richest players it lists (at most 7; 0: none).
|
||||
pub top: usize,
|
||||
/// Its last line, the server's address ("": none).
|
||||
pub footer: String,
|
||||
/// The ranking is built again, and the players' own lines looked at,
|
||||
/// The balances are read again, and the players' own lines looked at,
|
||||
/// at least this often.
|
||||
pub refresh_seconds: i64,
|
||||
/// After a balance changed, at most this often.
|
||||
@@ -50,7 +44,6 @@ impl Default for Config {
|
||||
Self {
|
||||
sidebar: true,
|
||||
title: "Horizon SMP".into(),
|
||||
top: 3,
|
||||
footer: "play.playhorizon.dev".into(),
|
||||
refresh_seconds: 60,
|
||||
min_seconds: 5,
|
||||
@@ -63,19 +56,14 @@ impl Default for Config {
|
||||
}
|
||||
}
|
||||
|
||||
/// SMP 0.13's sidebar defaults (the richest ten), moved to 0.14's when a
|
||||
/// server never changed them. True when something changed.
|
||||
/// SMP 0.13's sidebar title (`Richest players`), moved to today's when a
|
||||
/// server never changed it. True when something changed.
|
||||
pub fn migrate(config: &mut Config) -> bool {
|
||||
let mut changed = false;
|
||||
if config.title == "Richest players" {
|
||||
config.title = Config::default().title;
|
||||
changed = true;
|
||||
return true;
|
||||
}
|
||||
if config.top == 10 {
|
||||
config.top = Config::default().top;
|
||||
changed = true;
|
||||
}
|
||||
changed
|
||||
false
|
||||
}
|
||||
|
||||
/// `board.json`.
|
||||
@@ -95,16 +83,6 @@ pub enum Line {
|
||||
Stat { label: &'static str, value: String },
|
||||
/// An empty line.
|
||||
Gap,
|
||||
/// The heading over the richest.
|
||||
Heading,
|
||||
/// One of the richest: their rank, name and balance; `me` on the
|
||||
/// player's own line.
|
||||
Top {
|
||||
rank: usize,
|
||||
name: String,
|
||||
amount: String,
|
||||
me: bool,
|
||||
},
|
||||
/// The server's address.
|
||||
Footer(String),
|
||||
}
|
||||
@@ -131,10 +109,9 @@ pub struct Board {
|
||||
path: PathBuf,
|
||||
data: Data,
|
||||
changed: bool,
|
||||
/// Every account by balance, richest first: UUID, name, balance.
|
||||
ranked: Vec<(u128, String, i64)>,
|
||||
rank_of: HashMap<u128, usize>,
|
||||
/// When the ranking was built, and over which economy revision.
|
||||
/// Every account's balance.
|
||||
balances: HashMap<u128, i64>,
|
||||
/// When the balances were read, and at which economy revision.
|
||||
built_at: Option<i64>,
|
||||
built_revision: u64,
|
||||
/// What each player on was last sent.
|
||||
@@ -145,6 +122,8 @@ pub struct Board {
|
||||
personal_at: Option<i64>,
|
||||
tab_at: Option<i64>,
|
||||
tab_sent: HashSet<u128>,
|
||||
/// The vanished players (the glue sets it): on, but not counted online.
|
||||
pub quiet: HashSet<u128>,
|
||||
}
|
||||
|
||||
/// `1,250`, `125.4k`, `3.2M`: short enough for the sidebar's right side.
|
||||
@@ -180,8 +159,7 @@ impl Board {
|
||||
path,
|
||||
data,
|
||||
changed: false,
|
||||
ranked: Vec::new(),
|
||||
rank_of: HashMap::new(),
|
||||
balances: HashMap::new(),
|
||||
built_at: None,
|
||||
built_revision: 0,
|
||||
sent: HashMap::new(),
|
||||
@@ -189,6 +167,7 @@ impl Board {
|
||||
personal_at: None,
|
||||
tab_at: None,
|
||||
tab_sent: HashSet::new(),
|
||||
quiet: HashSet::new(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -200,11 +179,19 @@ impl Board {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Forgets what each player was sent: everyone gets theirs again at the
|
||||
/// next update (the sidebars were taken away).
|
||||
pub fn forget(&mut self) {
|
||||
self.sent.clear();
|
||||
self.tab_sent.clear();
|
||||
self.stale = true;
|
||||
}
|
||||
|
||||
pub fn hidden(&self, uuid: u128) -> bool {
|
||||
self.data.hidden.contains(&smp_store::uuid_string(uuid))
|
||||
}
|
||||
|
||||
/// Whether the ranking should be built again: the economy changed
|
||||
/// Whether the balances should be read again: the economy changed
|
||||
/// (`revision`) and `min_seconds` passed, or `refresh_seconds` passed.
|
||||
pub fn due(&self, now: i64, revision: u64) -> bool {
|
||||
if !self.config.sidebar {
|
||||
@@ -218,27 +205,17 @@ impl Board {
|
||||
|| since >= self.config.refresh_seconds * 1000
|
||||
}
|
||||
|
||||
/// The ranking from the accounts (UUID, name, balance, any order).
|
||||
pub fn rebuild(&mut self, mut accounts: Vec<(u128, String, i64)>, now: i64, revision: u64) {
|
||||
accounts.sort_by(|a, b| b.2.cmp(&a.2).then_with(|| a.1.cmp(&b.1)));
|
||||
self.rank_of = accounts
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, a)| (a.0, i + 1))
|
||||
.collect();
|
||||
self.ranked = accounts;
|
||||
/// The balances from the accounts (UUID, name, balance).
|
||||
pub fn rebuild(&mut self, accounts: Vec<(u128, String, i64)>, now: i64, revision: u64) {
|
||||
self.balances = accounts.into_iter().map(|(u, _, b)| (u, b)).collect();
|
||||
self.built_at = Some(now);
|
||||
self.built_revision = revision;
|
||||
self.stale = true;
|
||||
}
|
||||
|
||||
/// `uuid`'s sidebar: their own block, the richest from the ranking, the
|
||||
/// address.
|
||||
/// `uuid`'s sidebar: their own block, then the address.
|
||||
pub fn lines(&self, uuid: u128, me: &Me, online: usize) -> Vec<Line> {
|
||||
let balance = self
|
||||
.rank_of
|
||||
.get(&uuid)
|
||||
.map_or(0, |&rank| self.ranked[rank - 1].2);
|
||||
let balance = self.balances.get(&uuid).copied().unwrap_or(0);
|
||||
let mut lines = vec![
|
||||
Line::Name(me.name.clone()),
|
||||
Line::Stat {
|
||||
@@ -254,19 +231,6 @@ impl Board {
|
||||
value: online.to_string(),
|
||||
},
|
||||
];
|
||||
let top = self.config.top.min(MAX_TOP);
|
||||
if top > 0 && !self.ranked.is_empty() {
|
||||
lines.push(Line::Gap);
|
||||
lines.push(Line::Heading);
|
||||
lines.extend(self.ranked.iter().take(top).enumerate().map(
|
||||
|(i, (u, name, balance))| Line::Top {
|
||||
rank: i + 1,
|
||||
name: name.clone(),
|
||||
amount: compact(*balance),
|
||||
me: *u == uuid,
|
||||
},
|
||||
));
|
||||
}
|
||||
if !self.config.footer.is_empty() {
|
||||
lines.push(Line::Gap);
|
||||
lines.push(Line::Footer(self.config.footer.clone()));
|
||||
@@ -297,11 +261,12 @@ impl Board {
|
||||
}
|
||||
let all = std::mem::take(&mut self.stale) || personal;
|
||||
let mut out = Vec::new();
|
||||
let online = on.iter().filter(|u| !self.quiet.contains(u)).count();
|
||||
for &uuid in on {
|
||||
if self.hidden(uuid) || (!all && self.sent.contains_key(&uuid)) {
|
||||
continue;
|
||||
}
|
||||
let lines = self.lines(uuid, &me(uuid), on.len());
|
||||
let lines = self.lines(uuid, &me(uuid), online);
|
||||
if self.sent.get(&uuid) != Some(&lines) {
|
||||
self.sent.insert(uuid, lines.clone());
|
||||
out.push((uuid, lines));
|
||||
@@ -429,7 +394,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_sidebar_shows_the_player_then_the_three_richest_then_the_address() {
|
||||
fn the_sidebar_shows_the_player_then_the_address_and_no_ranking() {
|
||||
let d = dir();
|
||||
let mut b = Board::open(&d, Config::default()).unwrap();
|
||||
assert!(b.due(0, 0));
|
||||
@@ -442,12 +407,6 @@ mod tests {
|
||||
label,
|
||||
value: value.into(),
|
||||
};
|
||||
let top = |rank, name: &str, amount: &str, me| Line::Top {
|
||||
rank,
|
||||
name: name.into(),
|
||||
amount: amount.into(),
|
||||
me,
|
||||
};
|
||||
assert_eq!(
|
||||
b.lines(11, &rocky, 42),
|
||||
vec![
|
||||
@@ -456,27 +415,20 @@ mod tests {
|
||||
stat("Playtime", "2h 05m"),
|
||||
stat("Online", "42"),
|
||||
Line::Gap,
|
||||
Line::Heading,
|
||||
top(1, "P12", "1,200", false),
|
||||
top(2, "P11", "1,100", true),
|
||||
top(3, "P10", "1,000", false),
|
||||
Line::Gap,
|
||||
Line::Footer("play.playhorizon.dev".into()),
|
||||
]
|
||||
);
|
||||
// A player with no account yet; a server with no address and no top.
|
||||
// A player with no account yet; a server with no address.
|
||||
assert_eq!(b.lines(99, &rocky, 1)[1], stat("Balance", "0"));
|
||||
b.config.footer.clear();
|
||||
b.config.top = 0;
|
||||
assert_eq!(b.lines(99, &rocky, 1).len(), 4);
|
||||
let _ = std::fs::remove_dir_all(d);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_old_defaults_move_to_the_new_ones_and_set_ones_stay() {
|
||||
fn the_old_title_moves_to_the_new_one_and_a_set_one_stays() {
|
||||
let mut old = Config {
|
||||
title: "Richest players".into(),
|
||||
top: 10,
|
||||
..Config::default()
|
||||
};
|
||||
assert!(migrate(&mut old));
|
||||
@@ -484,15 +436,21 @@ mod tests {
|
||||
assert!(!migrate(&mut old));
|
||||
let mut own = Config {
|
||||
title: "My server".into(),
|
||||
top: 5,
|
||||
..Config::default()
|
||||
};
|
||||
assert!(!migrate(&mut own));
|
||||
assert_eq!((own.title.as_str(), own.top), ("My server", 5));
|
||||
assert_eq!(own.title, "My server");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sidebars_go_out_on_join_on_a_new_ranking_and_on_the_refresh() {
|
||||
fn an_old_config_with_a_top_still_reads() {
|
||||
let old = serde_json::json!({ "title": "Horizon SMP", "top": 3 });
|
||||
let c: Config = serde_json::from_value(old).unwrap();
|
||||
assert_eq!(c, Config::default());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sidebars_go_out_on_join_on_a_balance_change_and_on_the_refresh() {
|
||||
let d = dir();
|
||||
let mut b = Board::open(&d, Config::default()).unwrap();
|
||||
b.rebuild(accounts(3), 0, 1);
|
||||
@@ -511,11 +469,12 @@ mod tests {
|
||||
assert!(b.due(5_000, 2));
|
||||
b.rebuild(accounts(3), 5_000, 2);
|
||||
assert!(b.updates(5_000, &[1, 2], me).is_empty(), "same lines");
|
||||
// Player 1 gets richer: everyone's top changes.
|
||||
// Player 1 gets richer: only their own sidebar changes.
|
||||
let mut a = accounts(3);
|
||||
a[0].2 = 1_000;
|
||||
b.rebuild(a, 10_000, 3);
|
||||
assert_eq!(b.updates(10_000, &[1, 2], me).len(), 2);
|
||||
let sent = b.updates(10_000, &[1, 2], me);
|
||||
assert_eq!(sent.iter().map(|s| s.0).collect::<Vec<_>>(), vec![1]);
|
||||
// A newcomer gets theirs at once, and only they are looked at.
|
||||
asked.set(0);
|
||||
assert_eq!(b.updates(11_000, &[1, 2, 3], me).len(), 1);
|
||||
@@ -529,6 +488,15 @@ mod tests {
|
||||
let sent = b.updates(60_000, &[1, 2, 3], me);
|
||||
assert_eq!(sent.iter().map(|s| s.0).collect::<Vec<_>>(), vec![2]);
|
||||
assert!(b.due(70_000, 3), "a minute passed");
|
||||
// A vanished player isn't counted, at the next refresh.
|
||||
b.quiet.insert(3);
|
||||
let sent = b.updates(120_000, &[1, 2, 3], me);
|
||||
assert_eq!(sent.len(), 3, "everyone's count changed");
|
||||
let online = Line::Stat {
|
||||
label: "Online",
|
||||
value: "2".into(),
|
||||
};
|
||||
assert!(sent[0].1.contains(&online), "{:?}", sent[0].1);
|
||||
let _ = std::fs::remove_dir_all(d);
|
||||
}
|
||||
|
||||
|
||||
Loaded 100 of 110 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user