Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Written for Consumers: what a mod building against Voidworks can use, or will se

## Unreleased

- An item a Density tag names shows its Density in its tooltip, right under its name, with the motes one yields fed to a Void Displacer, from `densityMotes` in `voidworks-server.toml` ("Density 4: 64 motes"), or its Density alone before a world has loaded the config. Another mod's storage blocks in the `c:` tags show it too; an item of Density 0 shows nothing. (#106)
- A Void Dragon's rider watches it start down into the Well after they jump, sitting on its back as before, and sees the void only half a second after their eyes go below the Well's rim into the shaft (`VoidDragon.VOID_DELAY_TICKS`), the dragon diving on meanwhile; before, the screen went black as they jumped, while the dragon still hovered over the Well. They are unseen from the jump on, now from the jump's own tick. `VoidDragon.inTheVoid()` says whether a presence has taken its rider into the shaft, and its rider stands at its feet only from then; `carrying()` and `DragonRiding.inTheDive` still hold from the jump. (#84)
- A Void Dragon is seen again wherever it is after its presence's chunk unloaded: that presence, never saved, was left out of the world but still counted as the dragon's, so no new one showed, at the Well it was called to or anywhere else, and it came back at its old place with the chunk. A presence where nothing ticks is now taken away, and a new one shows where its dragon is. (#79)
- A grown Void Dragon with nothing to do away from its owner's Wells, such as one just grown from a Dragonling, dives to the nearest of them and rests there, so it can be ridden and called. Before, one with no trip to make stayed where it grew up for good. (#79)
Expand Down
4 changes: 3 additions & 1 deletion docs/spec/void-energy.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,9 @@ it, and it leaves no Voidstone: the renewable and abundant blocks above all (cob
deepslate, dirt, sand, gravel, netherrack, blackstone, basalt, end stone, obsidian, snow, ice), and
everything made from them, so a generator never feeds a harvest. Density rises steeply (shape: about
4^(density − 1), so 1, 4, 16, 64). Density tags and Families are separate data and need not agree:
amethyst, quartz and glowstone climb the gem and dust Families but stay Density 0, being renewable.
amethyst, quartz and glowstone climb the gem and dust Families but stay Density 0, being renewable. An
item a Density tag names shows its Density in its tooltip, with the motes one yields in a Displacer
once the server's numbers are known; one of Density 0 shows nothing.

| Density | Motes | Blocks, raw and refined alike |
|---|---|---|
Expand Down
12 changes: 8 additions & 4 deletions src/main/java/io/github/_5thlayer/voidworks/Voidworks.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import io.github._5thlayer.voidworks.butterfly.VoidworksButterflies;
import io.github._5thlayer.voidworks.command.VoidworksCommands;
import io.github._5thlayer.voidworks.displacer.DensityTooltip;
import io.github._5thlayer.voidworks.displacer.Displacement;
import io.github._5thlayer.voidworks.displacer.ZoneUpgrade;
import io.github._5thlayer.voidworks.dormant.Dormancy;
Expand All @@ -29,10 +30,12 @@

/**
* The Module's entry point. It registers the Void Pressure data map (and checks the defaults it
* ships), Voidstone and the Void Displacers with their displacement table and server
* config, the Dormant machines' cost map, conduction through Voidstone networks, the Families, the Unmaking Chamber's breakdown map, the
* Void Siphon, the dragon eggs, the Dragonling they hatch and the Void Dragon it grows into, which carries motes between Void Wells, the {@code /voidworks} commands and the game tests, which exist only when game tests
* are enabled.
* ships), Voidstone and the Void Displacers with their displacement table and server config, the
* Density line in a Density item's tooltip, the Dormant machines' cost map, conduction through
* Voidstone networks, the Families, the Unmaking Chamber's breakdown map, the Void Siphon, the
* dragon eggs, the Dragonling they hatch and the Void Dragon it grows into, which carries motes
* between Void Wells, the {@code /voidworks} commands and the game tests, which exist only when
* game tests are enabled.
*/
@Mod(Voidworks.MOD_ID)
public final class Voidworks {
Expand All @@ -50,6 +53,7 @@ public Voidworks(IEventBus modBus, ModContainer container) {
VoidworksEntities.register(modBus);
modBus.addListener(WorksTabContents::add);
Displacement.register(modBus);
DensityTooltip.register();
Dormancy.register(modBus);
DiveColour.register(modBus);
EggPrice.register(modBus);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: 2026 5thlayer
// SPDX-License-Identifier: MIT

package io.github._5thlayer.voidworks.displacer;

import java.util.OptionalInt;

import io.github._5thlayer.voidworks.VoidworksConfig;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.event.entity.player.ItemTooltipEvent;
import net.neoforged.neoforge.transfer.item.ItemResource;

/**
* The line under the name of an item a Density tag names: its Density and, once the server's config
* is known, the motes one yields fed to a Void Displacer. An item of Density 0 shows nothing.
*/
public final class DensityTooltip {

private DensityTooltip() {
}

/** Adds the line to every item tooltip built: a client's, and the game tests'. */
public static void register() {
NeoForge.EVENT_BUS.addListener(DensityTooltip::onItemTooltip);
}

private static void onItemTooltip(ItemTooltipEvent event) {
int density = Density.of(ItemResource.of(event.getItemStack()));
if (density > 0) {
// Known on a client only once a server has sent its config.
var motes = VoidworksConfig.loaded()
? OptionalInt.of(VoidworksConfig.motes(density))
: OptionalInt.empty();
event.getToolTip().add(1, line(density, motes));
}
}

/**
* @param motes what one yields fed to a Displacer, or empty when that is not known
* @return the line an item of {@code density}, above 0, shows
*/
static Component line(int density, OptionalInt motes) {
MutableComponent line;
if (motes.isEmpty()) {
line = Component.translatable("voidworks.density.tooltip", density);
} else {
String key = motes.getAsInt() == 1 ? "voidworks.density.tooltip.mote" : "voidworks.density.tooltip.motes";
line = Component.translatable(key, density, motes.getAsInt());
}
return line.withStyle(ChatFormatting.GRAY);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: 2026 5thlayer
// SPDX-License-Identifier: MIT

package io.github._5thlayer.voidworks.gametest;

import java.util.List;

import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.contents.TranslatableContents;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.GameType;

/**
* A Density item's tooltip, built on a real server with the server's config: the line under its
* name, with the motes a Displacer yields from it, and no line for a block of Density 0.
*/
final class DensityTooltipTests {

private DensityTooltipTests() {
}

static void register(VoidworksGameTests.Registrar tests) {
tests.test("density_tooltip_shows_a_diamond_blocks_density_and_motes", 1,
helper -> showsUnderTheName(helper, Items.DIAMOND_BLOCK, "voidworks.density.tooltip.motes", 4, 64));
tests.test("density_tooltip_shows_a_copper_blocks_density_and_motes", 1,
helper -> showsUnderTheName(helper, Items.COPPER_BLOCK, "voidworks.density.tooltip.mote", 1, 1));
tests.test("density_tooltip_shows_nothing_for_cobblestone", 1, DensityTooltipTests::showsNothingForDensityZero);
}

private static void showsUnderTheName(GameTestHelper helper, Item item, String key, int density, int motes) {
var lines = tooltip(helper, item);
if (lines.size() < 2 || !(lines.get(1).getContents() instanceof TranslatableContents line) || !key.equals(line.getKey())
|| !List.of(density, motes).equals(List.of(line.getArgs()))) {
helper.fail(item + " should show Density " + density + " and " + motes + " motes right under its name, showed " + lines);
return;
}
helper.succeed();
}

private static void showsNothingForDensityZero(GameTestHelper helper) {
var lines = tooltip(helper, Items.COBBLESTONE);
if (lines.stream().anyMatch(c -> c.getContents() instanceof TranslatableContents line && line.getKey().startsWith("voidworks.density."))) {
helper.fail("cobblestone, of Density 0, showed a Density line: " + lines);
return;
}
helper.succeed();
}

private static List<Component> tooltip(GameTestHelper helper, Item item) {
return new ItemStack(item).getTooltipLines(Item.TooltipContext.of(helper.getLevel()), helper.makeMockPlayer(GameType.SURVIVAL),
TooltipFlag.NORMAL);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ private static void registerTests(RegisterGameTestsEvent event) {
CommandTests.register(tests);
ConductionTests.register(tests);
CreativeTabTests.register(tests);
DensityTooltipTests.register(tests);
DisplacerTests.register(tests);
DormantTests.register(tests);
DragonlingTests.register(tests);
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/assets/voidworks/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@
"item.voidworks.draconic_void_siphon": "Draconic Void Siphon",
"item.voidworks.resonant_void_siphon": "Resonant Void Siphon",
"item.voidworks.void_siphon": "Void Siphon",
"voidworks.density.tooltip": "Density %s",
"voidworks.density.tooltip.mote": "Density %s: %s mote",
"voidworks.density.tooltip.motes": "Density %s: %s motes",
"voidworks.displacer.upgrade.cancel": "Upgrading: %s of %s void energy paid. Sneak-use again to cancel",
"voidworks.displacer.upgrade.cancelled": "Zone upgrade cancelled",
"voidworks.displacer.upgrade.committed": "Upgrading: Zone %s → %s once %s void energy is soaked",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: 2026 5thlayer
// SPDX-License-Identifier: MIT

package io.github._5thlayer.voidworks.displacer;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.List;
import java.util.OptionalInt;

import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.contents.TranslatableContents;
import org.junit.jupiter.api.Test;

/** The Density line: the Density alone until the motes are known, then with them, one said as one. */
class DensityTooltipTest {

@Test
void itShowsTheDensityAloneUntilTheMotesAreKnown() {
assertLine("voidworks.density.tooltip", List.of(4), DensityTooltip.line(4, OptionalInt.empty()));
}

@Test
void itShowsTheMotesOnceKnown() {
assertLine("voidworks.density.tooltip.motes", List.of(4, 64), DensityTooltip.line(4, OptionalInt.of(64)));
}

@Test
void itSaysOneMoteAsOne() {
assertLine("voidworks.density.tooltip.mote", List.of(1, 1), DensityTooltip.line(1, OptionalInt.of(1)));
}

private static void assertLine(String key, List<Object> args, Component line) {
var contents = (TranslatableContents) line.getContents();
assertEquals(key, contents.getKey());
assertEquals(args, List.of(contents.getArgs()));
}
}
Loading