Skip to content

Commit 9487260

Browse files
Ascacosclaude
andcommitted
Add /nightvision toggle and keep portable workstation menus open (1.3.0)
- New /nightvision (/nv) toggle command: permanent night vision effect for players with essentials.nightvision; essentials.nightvision.others allows toggling for other players. Fabric-only addition, noted in the parity manifest. - Portable workstation menus (anvil, cartography table, grindstone, loom, smithing table, stonecutter, workbench) closed on the next tick because vanilla's stillValid check looked for the matching block at the player's position. Menus now keep a real level anchor for sounds/crafting updates but override stillValid to always pass. - mod_version bumped to 1.3.0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYfnDNjsMCprfh1CWcGd7V
1 parent e0ed379 commit 9487260

6 files changed

Lines changed: 117 additions & 22 deletions

File tree

fabric/core/src/main/java/net/essentialsx/fabric/CommandRegistrar.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
/**
1010
* Registers every core command with its upstream aliases (generated from the locked
11-
* EssentialsX {@code plugin.yml}; see parity/commands.yml). 153 commands.
11+
* EssentialsX {@code plugin.yml}; see parity/commands.yml). 153 upstream commands plus /nightvision.
1212
*/
1313
public final class CommandRegistrar {
1414
private CommandRegistrar() {
@@ -101,6 +101,7 @@ public static void registerAll(final Essentials ess) {
101101
reg(r, new Commandmute(), "emute", "silence", "esilence", "unmute", "eunmute");
102102
reg(r, new Commandnear(), "enear", "nearby", "enearby");
103103
reg(r, new Commandnick(), "enick", "nickname", "enickname");
104+
reg(r, new Commandnightvision(), "nv", "enightvision", "env"); // Fabric-only addition
104105
reg(r, new Commandnuke(), "enuke");
105106
reg(r, new Commandpay(), "epay");
106107
reg(r, new Commandpayconfirmtoggle(), "epayconfirmtoggle", "payconfirmoff", "epayconfirmoff", "payconfirmon", "epayconfirmon", "payconfirm", "epayconfirm");
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package net.essentialsx.fabric.commands;
2+
3+
import net.essentialsx.fabric.command.CommandSource;
4+
import net.essentialsx.fabric.command.EssentialsToggleCommand;
5+
import net.essentialsx.fabric.user.User;
6+
import net.essentialsx.fabric.utils.CommonPlaceholders;
7+
import net.minecraft.server.MinecraftServer;
8+
import net.minecraft.server.level.ServerPlayer;
9+
import net.minecraft.world.effect.MobEffectInstance;
10+
import net.minecraft.world.effect.MobEffects;
11+
12+
/**
13+
* Toggles a permanent night vision effect (Fabric-only addition, not an upstream command).
14+
* The effect is a vanilla infinite-duration status effect, so it survives relogs and death
15+
* only as vanilla would, and can be cleared with /nightvision off or vanilla /effect clear.
16+
*/
17+
public class Commandnightvision extends EssentialsToggleCommand {
18+
public Commandnightvision() {
19+
super("nightvision", "essentials.nightvision.others");
20+
}
21+
22+
@Override
23+
protected void run(final MinecraftServer server, final CommandSource sender, final String commandLabel, final String[] args) throws Exception {
24+
toggleOtherPlayers(server, sender, args);
25+
}
26+
27+
@Override
28+
protected void run(final MinecraftServer server, final User user, final String commandLabel, final String[] args) throws Exception {
29+
handleToggleWithArgs(server, user, args);
30+
}
31+
32+
private static boolean hasPermanentNightVision(final ServerPlayer player) {
33+
final MobEffectInstance effect = player.getEffect(MobEffects.NIGHT_VISION);
34+
return effect != null && effect.isInfiniteDuration();
35+
}
36+
37+
@Override
38+
protected void togglePlayer(final CommandSource sender, final User user, Boolean enabled) {
39+
final ServerPlayer player = user.getBase();
40+
if (enabled == null) {
41+
enabled = !hasPermanentNightVision(player);
42+
}
43+
if (enabled) {
44+
// Replace any potion-based (finite) night vision with the permanent one; no particles, keep the HUD icon.
45+
player.removeEffect(MobEffects.NIGHT_VISION);
46+
player.addEffect(new MobEffectInstance(MobEffects.NIGHT_VISION, MobEffectInstance.INFINITE_DURATION, 0, false, false, true));
47+
} else {
48+
player.removeEffect(MobEffects.NIGHT_VISION);
49+
}
50+
user.sendTl("nightVision", CommonPlaceholders.enableDisable(user.getSource(), enabled), user.getDisplayName());
51+
if (!sender.isPlayer() || !sender.getPlayer().equals(player)) {
52+
sender.sendTl("nightVision", CommonPlaceholders.enableDisable(user.getSource(), enabled), user.getDisplayName());
53+
}
54+
}
55+
}

fabric/core/src/main/java/net/essentialsx/fabric/items/Workstations.java

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
import net.minecraft.world.item.ItemStack;
2323

2424
/**
25-
* Portable vanilla screen handlers (Section 11.3). Menus use an unrestricted
26-
* {@link ContainerLevelAccess} so they stay valid without a nearby block.
25+
* Portable vanilla screen handlers (Section 11.3). Menus are anchored at the player's position
26+
* but skip vanilla's "matching block nearby" validity check so they stay open without a block.
2727
*/
2828
public final class Workstations {
2929
private Workstations() {
@@ -52,44 +52,74 @@ private interface MenuFactory {
5252
AbstractContainerMenu create(int id, Inventory inventory);
5353
}
5454

55-
/** Menu wrapper that never closes because the player is "too far" from a block. */
56-
private static final class AlwaysValid {
57-
static ContainerLevelAccess of(final ServerPlayer player) {
58-
return new ContainerLevelAccess() {
59-
@Override
60-
public <T> java.util.Optional<T> evaluate(final java.util.function.BiFunction<net.minecraft.world.level.Level, net.minecraft.core.BlockPos, T> function) {
61-
return java.util.Optional.ofNullable(function.apply(player.level(), player.blockPosition()));
62-
}
63-
};
64-
}
65-
}
55+
/*
56+
* Each portable menu keeps a real ContainerLevelAccess at the player's position so vanilla can
57+
* still run its block-side effects (crafting result updates, anvil/stonecutter/loom sounds,
58+
* grindstone XP), but overrides stillValid: vanilla's check requires the matching block to be
59+
* at that position and there is none, which would close the screen on the next tick.
60+
*/
6661

6762
public static void openAnvil(final ServerPlayer player) {
68-
open(player, Component.translatable("container.repair"), (id, inv) -> new AnvilMenu(id, inv, AlwaysValid.of(player)));
63+
open(player, Component.translatable("container.repair"), (id, inv) -> new AnvilMenu(id, inv, access(player)) {
64+
@Override
65+
public boolean stillValid(final Player p) {
66+
return true;
67+
}
68+
});
6969
}
7070

7171
public static void openCartography(final ServerPlayer player) {
72-
open(player, Component.translatable("container.cartography_table"), (id, inv) -> new CartographyTableMenu(id, inv, AlwaysValid.of(player)));
72+
open(player, Component.translatable("container.cartography_table"), (id, inv) -> new CartographyTableMenu(id, inv, access(player)) {
73+
@Override
74+
public boolean stillValid(final Player p) {
75+
return true;
76+
}
77+
});
7378
}
7479

7580
public static void openGrindstone(final ServerPlayer player) {
76-
open(player, Component.translatable("container.grindstone_title"), (id, inv) -> new GrindstoneMenu(id, inv, AlwaysValid.of(player)));
81+
open(player, Component.translatable("container.grindstone_title"), (id, inv) -> new GrindstoneMenu(id, inv, access(player)) {
82+
@Override
83+
public boolean stillValid(final Player p) {
84+
return true;
85+
}
86+
});
7787
}
7888

7989
public static void openLoom(final ServerPlayer player) {
80-
open(player, Component.translatable("container.loom"), (id, inv) -> new LoomMenu(id, inv, AlwaysValid.of(player)));
90+
open(player, Component.translatable("container.loom"), (id, inv) -> new LoomMenu(id, inv, access(player)) {
91+
@Override
92+
public boolean stillValid(final Player p) {
93+
return true;
94+
}
95+
});
8196
}
8297

8398
public static void openSmithing(final ServerPlayer player) {
84-
open(player, Component.translatable("container.upgrade"), (id, inv) -> new SmithingMenu(id, inv, AlwaysValid.of(player)));
99+
open(player, Component.translatable("container.upgrade"), (id, inv) -> new SmithingMenu(id, inv, access(player)) {
100+
@Override
101+
public boolean stillValid(final Player p) {
102+
return true;
103+
}
104+
});
85105
}
86106

87107
public static void openStonecutter(final ServerPlayer player) {
88-
open(player, Component.translatable("container.stonecutter"), (id, inv) -> new StonecutterMenu(id, inv, AlwaysValid.of(player)));
108+
open(player, Component.translatable("container.stonecutter"), (id, inv) -> new StonecutterMenu(id, inv, access(player)) {
109+
@Override
110+
public boolean stillValid(final Player p) {
111+
return true;
112+
}
113+
});
89114
}
90115

91116
public static void openWorkbench(final ServerPlayer player) {
92-
open(player, Component.translatable("container.crafting"), (id, inv) -> new CraftingMenu(id, inv, AlwaysValid.of(player)));
117+
open(player, Component.translatable("container.crafting"), (id, inv) -> new CraftingMenu(id, inv, access(player)) {
118+
@Override
119+
public boolean stillValid(final Player p) {
120+
return true;
121+
}
122+
});
93123
}
94124

95125
/**

fabric/core/src/main/resources/messages.properties

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,11 @@ nearCommandUsage4Description=Lists all players within the given radius of the sp
835835
nearbyPlayers=<primary>Players nearby\:<reset> {0}
836836
nearbyPlayersList={0}<white>(<secondary>{1}m<white>)
837837
negativeBalanceError=<dark_red>User is not allowed to have a negative balance.
838+
nightVision=<primary>Set night vision<secondary> {0} <primary>for {1}<primary>.
839+
nightvisionCommandDescription=Toggles permanent night vision.
840+
nightvisionCommandUsage=/<command> [player] [on|off]
841+
nightvisionCommandUsage1=/<command> [player]
842+
nightvisionCommandUsage1Description=Toggles night vision for yourself or another player if specified
838843
nickChanged=<primary>Nickname changed.
839844
nickCommandDescription=Change your nickname or that of another player.
840845
nickCommandUsage=/<command> [player] <nickname|off>

fabric/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ fabric_api_version=0.116.15+1.21.1
99
loom_version=1.17.20
1010

1111
# Mod
12-
mod_version=1.2.1
12+
mod_version=1.3.0
1313
maven_group=net.essentialsx.fabric
1414

1515
# Integrations

fabric/parity/commands.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,10 @@ commands:
244244
nick:
245245
aliases: [enick, nickname, enickname]
246246
class: net.essentialsx.fabric.commands.Commandnick
247+
nightvision:
248+
aliases: [nv, enightvision, env]
249+
class: net.essentialsx.fabric.commands.Commandnightvision
250+
note: Fabric-only addition (no upstream equivalent); permission essentials.nightvision
247251
nuke:
248252
aliases: [enuke]
249253
class: net.essentialsx.fabric.commands.Commandnuke

0 commit comments

Comments
 (0)