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
12 changes: 12 additions & 0 deletions docs/Config-Documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ The config file can be found at `config/EssentialCommands.properties`
| nickname_above_head | false | boolean |
| nickname_max_length | 32 | integer |
| nickname_prefix | {"text":"~","color":"red"} | MinecraftText |
| nicknames_as_command_arg | Never | NicknameCommandArgMode |
| nicknames_in_player_list | true | boolean |
| ops_bypass_teleport_rules | true | boolean |
| persist_back_location | false | boolean |
Expand Down Expand Up @@ -131,6 +132,17 @@ You can use a tellraw generator like [MinecraftJson](https://www.minecraftjson.c

Examples: `"Alexandra"`, `{"text":"Alex","color":"green","bold":true}`

### `NicknameCommandArgMode`

Controls whether player nicknames can be used in place of real usernames in
command arguments.

Valid values:

- `Never` - nicknames are not resolved in any command argument (default)
- `EssentialCommandsOnly` - nicknames resolve and suggest only in Essential Commands
- `Everywhere` - nicknames resolve and suggest in all commands (e.g. `/tp`, `/give`)

### `RespawnCondition`

Valid values:
Expand Down
4 changes: 4 additions & 0 deletions docs/Feature-Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@ Customize player display names.
- `nick_reveal_on_hover` - Show real name on nickname hover - Default: `true`
- `nickname_above_head` - Show nickname above player's head - Default: `false`
- `nicknames_in_player_list` - Show nicknames in tab list - Default: `true`
- `nicknames_as_command_arg` - Whether nicknames can be used as player arguments in commands - Default: `Never`
- `Never` - Nicknames are not resolved in any command argument
- `EssentialCommandsOnly` - Nicknames resolve and suggest only in Essential Commands
- `Everywhere` - Nicknames resolve and suggest in all commands (e.g. `/tp`, `/give`)

## Utility Commands

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.ComponentArgument;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.network.chat.Component;
import net.minecraft.server.permissions.Permission;
import net.minecraft.server.permissions.PermissionLevel;
Expand Down Expand Up @@ -84,7 +83,7 @@ public static void register(
if (CONFIG.ENABLE_TPA) {
registerNode.accept(Commands.literal("tpa")
.requires(ECPerms.require(ECPerms.Registry.tpa, 0))
.then(CommandUtil.targetPlayerArgument()
.then(NicknameTargetResolver.targetPlayerArgument()
.executes(new TeleportAskCommand()))
.build());

Expand All @@ -96,22 +95,22 @@ public static void register(
registerNode.accept(Commands.literal("tpaccept")
.requires(ECPerms.require(ECPerms.Registry.tpaccept, 0))
.executes(new TeleportAcceptCommand()::runDefault)
.then(CommandUtil.targetPlayerArgument()
.then(NicknameTargetResolver.targetPlayerArgument()
.suggests(TeleportResponseSuggestion.STRING_SUGGESTIONS_PROVIDER)
.executes(new TeleportAcceptCommand()))
.build());

registerNode.accept(Commands.literal("tpdeny")
.requires(ECPerms.require(ECPerms.Registry.tpdeny, 0))
.executes(new TeleportDenyCommand()::runDefault)
.then(CommandUtil.targetPlayerArgument()
.then(NicknameTargetResolver.targetPlayerArgument()
.suggests(TeleportResponseSuggestion.STRING_SUGGESTIONS_PROVIDER)
.executes(new TeleportDenyCommand()))
.build());

registerNode.accept(Commands.literal("tpahere")
.requires(ECPerms.require(ECPerms.Registry.tpahere, 0))
.then(CommandUtil.targetPlayerArgument()
.then(NicknameTargetResolver.targetPlayerArgument()
.executes(new TeleportAskHereCommand()))
.build());
}
Expand Down Expand Up @@ -148,7 +147,7 @@ public static void register(

homeTpOtherBuilder
.requires(ECPerms.require(ECPerms.Registry.home_tp_others, 2))
.then(argument("target_player", EntityArgument.player())
.then(CommandUtil.targetPlayerArgument()
.then(argument("home_name", StringArgumentType.word())
.suggests(HomeTeleportOtherCommand.Suggestion.LIST_SUGGESTION_PROVIDER)
.executes(new HomeTeleportOtherCommand())));
Expand Down Expand Up @@ -241,7 +240,7 @@ public static void register(

warpTpOtherBuilder
.requires(ECPerms.require(ECPerms.Registry.home_tp_others, 2))
.then(argument("target_player", EntityArgument.player())
.then(CommandUtil.targetPlayerArgument()
.then(argument("warp_name", StringArgumentType.word())
.suggests(WarpSuggestion.STRING_SUGGESTIONS_PROVIDER)
.executes(new WarpTpCommand()::runOther)));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.fibermc.essentialcommands.access;

public interface EntitySelectorNicknameAccess {
String ec$getPlayerName();
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import com.mojang.brigadier.tree.CommandNode;

import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.commands.arguments.selector.EntitySelector;
import net.minecraft.server.level.ServerPlayer;

Expand All @@ -21,7 +18,7 @@ public final class CommandUtil {
private CommandUtil() {}

public static RequiredArgumentBuilder<CommandSourceStack, EntitySelector> targetPlayerArgument() {
return Commands.argument("target_player", EntityArgument.player());
return NicknameTargetResolver.targetPlayerArgumentNonGreedy();
}

public static String getCommandString(CommandSourceStack source, CommandNode<CommandSourceStack> commandNode) {
Expand All @@ -39,7 +36,7 @@ public static CommandSyntaxException createSimpleException(Message msg) {

public static ServerPlayer getCommandTargetPlayer(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
try {
return EntityArgument.getPlayer(context, "target_player");
return NicknameTargetResolver.getPlayer(context, "target_player");
} catch (IllegalArgumentException e) {
return context.getSource().getPlayer();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import com.mojang.brigadier.suggestion.SuggestionProvider;

import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.network.chat.Component;

import static com.fibermc.essentialcommands.EssentialCommands.CONFIG;
Expand All @@ -36,7 +35,7 @@ public int run(CommandContext<CommandSourceStack> context) throws CommandSyntaxE
}

private static PlayerData getTargetPlayerData(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
return ((ServerPlayerEntityAccess) EntityArgument.getPlayer(context, "target_player")).ec$getPlayerData();
return ((ServerPlayerEntityAccess) NicknameTargetResolver.getPlayer(context, "target_player")).ec$getPlayerData();
}

public int runDefault(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package com.fibermc.essentialcommands.commands;

import java.util.List;
import java.util.concurrent.CompletableFuture;

import com.fibermc.essentialcommands.access.EntitySelectorNicknameAccess;
import com.fibermc.essentialcommands.playerdata.PlayerData;
import com.fibermc.essentialcommands.playerdata.PlayerDataManager;
import com.fibermc.essentialcommands.types.NicknameCommandArgMode;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.commands.arguments.selector.EntitySelector;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;

import static com.fibermc.essentialcommands.EssentialCommands.CONFIG;

/**
* Adds optional nickname fallback on top of Minecraft's normal player
* argument handling. Vanilla resolution is always attempted first.
*/
public final class NicknameTargetResolver {
private NicknameTargetResolver() {}

public static RequiredArgumentBuilder<CommandSourceStack, EntitySelector> targetPlayerArgument() {
return Commands.argument("target_player", EntityArgument.player())
.suggests(NicknameTargetResolver::suggestPlayers);
}

public static RequiredArgumentBuilder<CommandSourceStack, EntitySelector> targetPlayerArgumentNonGreedy() {
return targetPlayerArgument();
}

private static CompletableFuture<Suggestions> suggestPlayers(
CommandContext<CommandSourceStack> context,
SuggestionsBuilder builder
) throws CommandSyntaxException {
if (CONFIG.NICKNAMES_AS_COMMAND_ARG != NicknameCommandArgMode.Never) {
addNicknameSuggestions(builder);
}

return EntityArgument.player().listSuggestions(context, builder);
}

public static void addNicknameSuggestions(SuggestionsBuilder builder) {
if (!PlayerDataManager.exists()) {
return;
}

String remaining = PlayerData.normalizeNickname(builder.getRemaining());

for (PlayerData playerData : PlayerDataManager.getInstance().getAllPlayerData()) {
String normalized = playerData.getNormalizedNickname();
if (normalized == null || !normalized.startsWith(remaining)) {
continue;
}
playerData.getNickname()
.map(Component::getString)
.map(nick -> nick.replaceAll("\\s+", ""))
.filter(nick -> !nick.isBlank())
.ifPresent(builder::suggest);
}
}

public static ServerPlayer getPlayer(
CommandContext<CommandSourceStack> context,
String argumentName
) throws CommandSyntaxException {
try {
return EntityArgument.getPlayer(context, argumentName);
} catch (CommandSyntaxException vanillaFailure) {
if (CONFIG.NICKNAMES_AS_COMMAND_ARG != NicknameCommandArgMode.EssentialCommandsOnly) {
throw vanillaFailure;
}

EntitySelector selector = context.getArgument(argumentName, EntitySelector.class);
String playerName = ((EntitySelectorNicknameAccess) selector).ec$getPlayerName();
ServerPlayer nicknameMatch = resolvePlayerByNickname(playerName);
if (nicknameMatch != null) {
return nicknameMatch;
}

throw vanillaFailure;
}
}

/**
* Finds one online player whose nickname matches the literal command
* player name after normalization (whitespace removal + case folding).
*
* Returns null for no match or an ambiguous match.
*/
public static ServerPlayer resolvePlayerByNickname(String playerName) {
if (
playerName == null
|| playerName.isBlank()
) {
return null;
}

List<PlayerData> matches = PlayerDataManager.getInstance().getByNickname(playerName);
// Ambiguous (>1) or no match -- never choose one arbitrarily.
if (matches.size() != 1) {
return null;
}
return matches.getFirst().getPlayer();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import com.mojang.brigadier.exceptions.CommandSyntaxException;

import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.server.level.ServerPlayer;

public class TeleportAskCommand implements Command<CommandSourceStack> {
Expand All @@ -25,7 +24,7 @@ public TeleportAskCommand() {}
public int run(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
TeleportManager tpMgr = ManagerLocator.getInstance().getTpManager();
ServerPlayer senderPlayer = context.getSource().getPlayerOrException();
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "target_player");
ServerPlayer targetPlayer = NicknameTargetResolver.getPlayer(context, "target_player");
var senderPlayerData = PlayerData.access(senderPlayer);
var targetPlayerData = PlayerData.access(targetPlayer);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import com.mojang.brigadier.exceptions.CommandSyntaxException;

import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.server.level.ServerPlayer;

public class TeleportAskHereCommand implements Command<CommandSourceStack> {
Expand All @@ -23,7 +22,7 @@ public TeleportAskHereCommand() {}
public int run(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
TeleportManager tpMgr = ManagerLocator.getInstance().getTpManager();
ServerPlayer senderPlayer = context.getSource().getPlayerOrException();
ServerPlayer targetPlayer = EntityArgument.getPlayer(context, "target_player");
ServerPlayer targetPlayer = NicknameTargetResolver.getPlayer(context, "target_player");
var senderPlayerData = PlayerData.access(senderPlayer);
var targetPlayerData = PlayerData.access(targetPlayer);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import com.mojang.brigadier.exceptions.CommandSyntaxException;

import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.server.level.ServerPlayer;

public abstract class TeleportResponseCommand implements Command<CommandSourceStack> {
Expand All @@ -22,7 +21,7 @@ public int run(CommandContext<CommandSourceStack> context) throws CommandSyntaxE
return exec(
context,
context.getSource().getPlayer(),
EntityArgument.getPlayer(context, "target_player")
NicknameTargetResolver.getPlayer(context, "target_player")
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import com.mojang.brigadier.exceptions.CommandSyntaxException;

import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.server.level.ServerPlayer;

public class WarpTpCommand implements Command<CommandSourceStack> {
Expand Down Expand Up @@ -61,7 +60,7 @@ private void exec(
}

public int runOther(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
exec(context, EntityArgument.getPlayer(context, "target_player"));
exec(context, NicknameTargetResolver.getPlayer(context, "target_player"));
return 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.fibermc.essentialcommands.EssentialCommands;
import com.fibermc.essentialcommands.ManagerLocator;
import com.fibermc.essentialcommands.playerdata.PlayerDataManager;
import com.fibermc.essentialcommands.types.NicknameCommandArgMode;
import com.fibermc.essentialcommands.types.RespawnCondition;
import com.fibermc.essentialcommands.types.RtpCenter;
import org.jetbrains.annotations.NotNull;
Expand Down Expand Up @@ -85,6 +86,7 @@ public final class EssentialCommandsConfig extends Config<EssentialCommandsConfi
@ConfigOption public final Option<Boolean> OPS_BYPASS_TELEPORT_RULES = new Option<>("ops_bypass_teleport_rules", true, Boolean::parseBoolean);
@ConfigOption public final Option<Boolean> NICKNAMES_IN_PLAYER_LIST = new Option<>("nicknames_in_player_list", true, Boolean::parseBoolean);
@ConfigOption public final Option<Integer> NICKNAME_MAX_LENGTH = new Option<>("nickname_max_length", 32, ConfigUtil::parseInt);
@ConfigOption public final Option<NicknameCommandArgMode> NICKNAMES_AS_COMMAND_ARG = new Option<>("nicknames_as_command_arg", NicknameCommandArgMode.Never, NicknameCommandArgMode::valueOf);
@ConfigOption public final Option<Boolean> NICKNAME_ABOVE_HEAD = new Option<>("nickname_above_head", false, Boolean::parseBoolean);
@ConfigOption public final Option<RtpCenter> RTP_CENTER = new Option<>("rtp_center", RtpCenter.spawn(), RtpCenter::parse, RtpCenter::serialize);
@ConfigOption public final Option<Integer> RTP_RADIUS = new Option<>("rtp_radius", 1000, ConfigUtil::parseInt);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import net.minecraft.network.chat.Style;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.Level;
import com.fibermc.essentialcommands.types.NicknameCommandArgMode;
import com.fibermc.essentialcommands.types.RespawnCondition;
import com.fibermc.essentialcommands.types.RtpCenter;
import dev.jpcode.eccore.config.expression.Expression;
Expand Down Expand Up @@ -64,6 +65,7 @@ public final class EssentialCommandsConfigSnapshot {
public final boolean OPS_BYPASS_TELEPORT_RULES;
public final boolean NICKNAMES_IN_PLAYER_LIST;
public final int NICKNAME_MAX_LENGTH;
public final NicknameCommandArgMode NICKNAMES_AS_COMMAND_ARG;
public final boolean NICKNAME_ABOVE_HEAD;
public final RtpCenter RTP_CENTER;
public final int RTP_RADIUS;
Expand Down Expand Up @@ -143,6 +145,7 @@ private EssentialCommandsConfigSnapshot(EssentialCommandsConfig config) {
this.OPS_BYPASS_TELEPORT_RULES = config.OPS_BYPASS_TELEPORT_RULES.getValue();
this.NICKNAMES_IN_PLAYER_LIST = config.NICKNAMES_IN_PLAYER_LIST.getValue();
this.NICKNAME_MAX_LENGTH = config.NICKNAME_MAX_LENGTH.getValue();
this.NICKNAMES_AS_COMMAND_ARG = config.NICKNAMES_AS_COMMAND_ARG.getValue();
this.NICKNAME_ABOVE_HEAD = config.NICKNAME_ABOVE_HEAD.getValue();
this.RTP_CENTER = config.RTP_CENTER.getValue();
this.RTP_RADIUS = config.RTP_RADIUS.getValue();
Expand Down
Loading