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
8 changes: 8 additions & 0 deletions changeset.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
Features:

- Add `/tptoggle [on/off/off-silent]` (permission `essentialcommands.tptoggle`)
to control whether other players can send you teleport requests. Backed by
the new `allowTeleportRequests` player profile option.
- `off` auto-denies requests and tells you someone tried.
- `off-silent` auto-denies requests without notifying you.

Fixes:

- Fix OPs being unable to set styled nicknames for other players when
Expand Down
1 change: 1 addition & 0 deletions docs/Feature-Guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Essential Commands provides a request-based teleportation system that allows any
| Accept teleport request | `/tpaccept <player>` | `essentialcommands.tpaccept` |
| Deny teleport request | `/tpdeny <player>` | `essentialcommands.tpdeny` |
| Cancel teleport request | `/tpcancel <player>` | Same as original teleport |
| Toggle receiving teleport requests | `/tptoggle [on/off/off-silent]` | `essentialcommands.tptoggle` |

**Related Config Options:**
- `teleport_request_duration` - How long teleport requests remain valid (seconds) - Default: `60`
Expand Down
1 change: 1 addition & 0 deletions docs/List-of-Commands-&-Permissions.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Grant access to all subcommands using wildcards, like so:
| /tpahere \<player> | `essentialcommands.tpahere` | Request that a player teleports to you. |
| /tpaccept \<player> | `essentialcommands.tpaccept` | Accept player's teleport request. |
| /tpdeny \<player> | `essentialcommands.tpdeny` | Deny player's teleport request. |
| /tptoggle [on/off/off-silent] | `essentialcommands.tptoggle` | Toggle whether you accept teleport requests. See [Player Profiles](Player-Profiles.md). |
| /home set \<home_name> | `essentialcommands.home.set` | Set a personal home location. |
| /home tp \<home_name> | `essentialcommands.home.tp` | Teleport to your home. |
| /home delete \<home_name> | `essentialcommands.home.delete` | Delete your home. |
Expand Down
2 changes: 2 additions & 0 deletions docs/Permissions-Quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ teleport to `warp`s, and to use `/rtp`:
/lp group default permission set essentialcommands.tpahere true
/lp group default permission set essentialcommands.tpaccept true
/lp group default permission set essentialcommands.tpdeny true
/lp group default permission set essentialcommands.tptoggle true

/lp group default permission set essentialcommands.home.{set,tp,delete} true
/lp group default permission set essentialcommands.warp.tp true
Expand Down Expand Up @@ -76,6 +77,7 @@ this:
"essentialcommands.tpahere": "allow",
"essentialcommands.tpaccept": "allow",
"essentialcommands.tpdeny": "allow",
"essentialcommands.tptoggle": "allow",
"essentialcommands.home.set": "allow",
"essentialcommands.home.tp": "allow",
"essentialcommands.home.delete": "allow",
Expand Down
19 changes: 19 additions & 0 deletions docs/Player-Profiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,22 @@ Essential Commands are presented:
- `/essentialcommands profile set formattingDefault <value>`
- `/essentialcommands profile set formattingAccent <value>`
- `/essentialcommands profile set printTeleportCoordinates <true/false>`

## Teleport Requests

`allowTeleportRequests` controls whether other players can send you teleport
requests (`/tpa` and `/tpahere`). It defaults to `on`.

- `on`: requests are delivered as normal.
- `off`: requests are auto-denied. You get a short notice in chat that someone
tried, and the sender is told you saw it.
- `off-silent`: requests are auto-denied, and nothing appears in your chat. The
sender is told you did not see it. Use this to stop request spam.

Set it with `/essentialcommands profile set allowTeleportRequests <on/off/off-silent>`,
or with the `/tptoggle` shorthand:

- `/tptoggle`: switch between `on` and `off` (switches to `on` from `off-silent`)
- `/tptoggle on`, `/tptoggle off`, `/tptoggle off-silent`: set it directly

Requests that are already pending when you turn this off are not affected.
3 changes: 2 additions & 1 deletion src/main/java/com/fibermc/essentialcommands/ECPerms.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public static final class Registry {
public static final Identifier tpahere = of("tpahere");
public static final Identifier tpaccept = of("tpaccept");
public static final Identifier tpdeny = of("tpdeny");
public static final Identifier tptoggle = of("tptoggle");
public static final Identifier home_set = of("home.set");
public static final Identifier home_tp = of("home.tp");
public static final Identifier home_tp_others = of("home_tp_others");
Expand Down Expand Up @@ -89,7 +90,7 @@ public static final class Registry {
public static final Identifier admin_lastpos = of("admin.lastpos");

public static final class Group {
public static final Identifier[] tpa_group = {tpa, tpahere, tpaccept, tpdeny};
public static final Identifier[] tpa_group = {tpa, tpahere, tpaccept, tpdeny, tptoggle};
public static final Identifier[] home_group = {home_set, home_tp, home_delete};
public static final Identifier[] warp_group = {warp_set, warp_tp, warp_delete};
public static final Identifier[] spawn_group = {spawn_tp, spawn_set};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.fibermc.essentialcommands.commands.suggestions.WarpSuggestion;
import com.fibermc.essentialcommands.commands.utility.*;
import com.fibermc.essentialcommands.text.ECText;
import com.fibermc.essentialcommands.types.AllowTeleportRequests;
import com.fibermc.essentialcommands.types.NamedMinecraftLocation;
import com.fibermc.essentialcommands.util.EssentialsConvertor;
import com.fibermc.essentialcommands.util.EssentialsXParser;
Expand Down Expand Up @@ -113,6 +114,17 @@ public static void register(
.then(NicknameTargetResolver.targetPlayerArgument()
.executes(new TeleportAskHereCommand()))
.build());

registerNode.accept(Commands.literal("tptoggle")
.requires(ECPerms.require(ECPerms.Registry.tptoggle, 0))
.executes(TeleportToggleCommand::toggle)
.then(Commands.literal("on")
.executes(TeleportToggleCommand.setTo(AllowTeleportRequests.ON)))
.then(Commands.literal("off")
.executes(TeleportToggleCommand.setTo(AllowTeleportRequests.OFF)))
.then(Commands.literal("off-silent")
.executes(TeleportToggleCommand.setTo(AllowTeleportRequests.OFF_SILENT)))
.build());
}

if (CONFIG.ENABLE_HOME) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@ public static LiteralCommandNode<CommandSourceStack> buildNode() {
var name = entry.getKey();
var option = entry.getValue();

var valueArgument = argument("value", option.argumentType());
if (option.suggestionProvider() != null) {
valueArgument.suggests(option.suggestionProvider());
}
set.then(Commands.literal(name)
.then(argument("value", option.argumentType()).executes((context) -> {
.then(valueArgument.executes((context) -> {
var player = context.getSource().getPlayerOrException();
var profile = ((ServerPlayerEntityAccess) player).ec$getProfile();
option.profileSetter().setValue(context, "value", profile);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ public int run(CommandContext<CommandSourceStack> context) throws CommandSyntaxE
}
}

if (TeleportToggleCommand.denyIfDisallowed(senderPlayer, targetPlayer)) {
return 0;
}

//inform target player of tp request via chat
var targetPlayerEcText = ECText.access(targetPlayer);
var targetPlayerProfile = PlayerProfile.access(targetPlayer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ public int run(CommandContext<CommandSourceStack> context) throws CommandSyntaxE
}
}

if (TeleportToggleCommand.denyIfDisallowed(senderPlayer, targetPlayer)) {
return 0;
}

//inform target player of tp request via chat
var targetPlayerEcText = ECText.access(targetPlayer);
targetPlayerData.sendMessage(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.fibermc.essentialcommands.commands;

import com.fibermc.essentialcommands.playerdata.PlayerData;
import com.fibermc.essentialcommands.playerdata.PlayerProfile;
import com.fibermc.essentialcommands.text.TextFormatType;
import com.fibermc.essentialcommands.types.AllowTeleportRequests;

import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;

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

public final class TeleportToggleCommand {
private TeleportToggleCommand() {}

public static int toggle(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
var profile = PlayerProfile.accessFromContextOrThrow(context);
var newValue = profile.getAllowTeleportRequests() == AllowTeleportRequests.ON
? AllowTeleportRequests.OFF
: AllowTeleportRequests.ON;
return set(context, newValue);
}

public static Command<CommandSourceStack> setTo(AllowTeleportRequests value) {
return (context) -> set(context, value);
}

private static int set(CommandContext<CommandSourceStack> context, AllowTeleportRequests value)
throws CommandSyntaxException
{
var player = context.getSource().getPlayerOrException();
var profile = PlayerProfile.access(player);
profile.setAllowTeleportRequests(value);
profile.setDirty();
profile.save(context.getSource().getServer().registryAccess());

PlayerData.access(player).sendCommandFeedback("cmd.tptoggle.feedback." + value.getId());
return Command.SINGLE_SUCCESS;
}

/**
* Auto-denies a tpa/tpahere request if the target does not allow teleport requests, notifying the sender (and,
* unless silent, the target).
*
* @return true if the request was denied and should not be sent.
*/
static boolean denyIfDisallowed(ServerPlayer senderPlayer, ServerPlayer targetPlayer) {
var targetPlayerProfile = PlayerProfile.access(targetPlayer);
var senderPlayerProfile = PlayerProfile.access(senderPlayer);
var targetPlayerText = targetPlayer.getDisplayName().copy()
.withStyle(senderPlayerProfile.getStyle(TextFormatType.Accent));

switch (targetPlayerProfile.getAllowTeleportRequests()) {
case ON -> {
return false;
}
case OFF -> {
PlayerData.access(targetPlayer).sendMessage(
"cmd.tpask.receive.disallowed",
senderPlayer.getDisplayName().copy().withStyle(targetPlayerProfile.getStyle(TextFormatType.Accent)));
PlayerData.access(senderPlayer).sendCommandError("cmd.tpask.error.disallowed", targetPlayerText);
return true;
}
case OFF_SILENT -> {
PlayerData.access(senderPlayer).sendCommandError("cmd.tpask.error.disallowed_silent", targetPlayerText);
return true;
}
}
throw new IllegalStateException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
import java.io.File;
import java.io.IOException;
import java.util.AbstractMap.SimpleEntry;
import java.util.Arrays;
import java.util.Map;
import java.util.Optional;

import com.fibermc.essentialcommands.EssentialCommands;
import com.fibermc.essentialcommands.access.ServerPlayerEntityAccess;
import com.fibermc.essentialcommands.commands.CommandUtil;
import com.fibermc.essentialcommands.text.ECText;
import com.fibermc.essentialcommands.types.AllowTeleportRequests;
import com.fibermc.essentialcommands.types.IStyleProvider;
import com.fibermc.essentialcommands.types.ProfileOption;
import org.jetbrains.annotations.NotNull;
Expand All @@ -19,9 +23,11 @@
import com.mojang.brigadier.exceptions.CommandSyntaxException;

import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.core.HolderLookup;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.Style;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.saveddata.SavedData;
Expand All @@ -46,6 +52,7 @@ private static final class ProfileOptions {
private Optional<Style> formattingAccent = Optional.empty();
private Optional<Style> formattingError = Optional.empty();
private Optional<Boolean> printTeleportCoordinates = Optional.empty();
private Optional<AllowTeleportRequests> allowTeleportRequests = Optional.empty();
}

public static final Map<String, ProfileOption<?>> OPTIONS = Map.ofEntries(
Expand All @@ -56,6 +63,24 @@ private static final class ProfileOptions {
false,
(context, name, profile) -> profile.profileOptions.printTeleportCoordinates = Optional.of(BoolArgumentType.getBool(context, name)),
(profile) -> profile.profileOptions.printTeleportCoordinates)),
new SimpleEntry<>(
StorageKey.ALLOW_TELEPORT_REQUESTS,
new ProfileOption<>(
StringArgumentType.word(),
AllowTeleportRequests.ON.getId(),
(context, name, profile) -> {
var id = StringArgumentType.getString(context, name);
var value = AllowTeleportRequests.fromId(id);
if (value.isEmpty()) {
throw CommandUtil.createSimpleException(
ECText.access(profile.getPlayer()).getText("cmd.profile.error.invalid_value", Component.literal(id)));
}
profile.profileOptions.allowTeleportRequests = value;
},
(profile) -> profile.profileOptions.allowTeleportRequests.map(AllowTeleportRequests::getId),
(context, builder) -> SharedSuggestionProvider.suggest(
Arrays.stream(AllowTeleportRequests.values()).map(AllowTeleportRequests::getId),
builder))),
new SimpleEntry<>(
StorageKey.FORMATTING_DEAULT,
new ProfileOption<>(
Expand Down Expand Up @@ -83,6 +108,14 @@ public Optional<Boolean> shouldPrintTeleportCoordinates() {
return profileOptions.printTeleportCoordinates;
}

public AllowTeleportRequests getAllowTeleportRequests() {
return profileOptions.allowTeleportRequests.orElse(AllowTeleportRequests.ON);
}

public void setAllowTeleportRequests(AllowTeleportRequests allowTeleportRequests) {
profileOptions.allowTeleportRequests = Optional.of(allowTeleportRequests);
}

public @Nullable Style getFormattingDefault() {
return profileOptions.formattingDefault.orElse(null);
}
Expand All @@ -100,6 +133,7 @@ private static final class StorageKey {
static final String FORMATTING_ACCENT = "formattingAccent";
static final String FORMATTING_ERROR = "formattingError";
static final String PRINT_TELEPORT_COORDINATES = "printTeleportCoordinates";
static final String ALLOW_TELEPORT_REQUESTS = "allowTeleportRequests";
}

public void fromNbt(CompoundTag tag) {
Expand All @@ -117,6 +151,9 @@ public void fromNbt(CompoundTag tag) {
.flatMap(s -> Optional.ofNullable(ConfigUtil.parseStyle(s)));

this.profileOptions.printTeleportCoordinates = dataTag.getBoolean(StorageKey.PRINT_TELEPORT_COORDINATES);

this.profileOptions.allowTeleportRequests = dataTag.getString(StorageKey.ALLOW_TELEPORT_REQUESTS)
.flatMap(AllowTeleportRequests::fromId);
}

public CompoundTag writeNbt(CompoundTag tag, HolderLookup.Provider wrapperLookup) {
Expand All @@ -132,6 +169,9 @@ public CompoundTag writeNbt(CompoundTag tag, HolderLookup.Provider wrapperLookup
this.profileOptions.printTeleportCoordinates.ifPresent(
printTeleportCoordinates -> tag.putBoolean(StorageKey.PRINT_TELEPORT_COORDINATES, printTeleportCoordinates));

this.profileOptions.allowTeleportRequests.ifPresent(
allowTeleportRequests -> tag.putString(StorageKey.ALLOW_TELEPORT_REQUESTS, allowTeleportRequests.getId()));

return tag;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.fibermc.essentialcommands.types;

import java.util.Arrays;
import java.util.Optional;

public enum AllowTeleportRequests {
// Requests are delivered as normal.
ON("on"),
// Requests are auto-denied, but the receiver is notified that one was attempted.
OFF("off"),
// Requests are auto-denied, and nothing is sent to the receiver (spam prevention).
OFF_SILENT("off-silent");

private final String id;

AllowTeleportRequests(String id) {
this.id = id;
}

public String getId() {
return id;
}

public static Optional<AllowTeleportRequests> fromId(String id) {
return Arrays.stream(values())
.filter(value -> value.id.equals(id))
.findFirst();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,26 @@

import java.util.Optional;

import org.jetbrains.annotations.Nullable;

import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.suggestion.SuggestionProvider;

import net.minecraft.commands.CommandSourceStack;

public record ProfileOption<T>(
ArgumentType<T> argumentType,
T defaultValue,
ProfileOptionFromContextSetter<T> profileSetter,
ProfileOptionGetter<Optional<T>> profileGetter) {}
ProfileOptionGetter<Optional<T>> profileGetter,
@Nullable SuggestionProvider<CommandSourceStack> suggestionProvider)
{
public ProfileOption(
ArgumentType<T> argumentType,
T defaultValue,
ProfileOptionFromContextSetter<T> profileSetter,
ProfileOptionGetter<Optional<T>> profileGetter)
{
this(argumentType, defaultValue, profileSetter, profileGetter, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import com.fibermc.essentialcommands.playerdata.PlayerProfile;

import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;

import net.minecraft.commands.CommandSourceStack;

@FunctionalInterface
public interface ProfileOptionFromContextSetter<T> {
void setValue(CommandContext<CommandSourceStack> context, String name, PlayerProfile profile);
void setValue(CommandContext<CommandSourceStack> context, String name, PlayerProfile profile)
throws CommandSyntaxException;
}
Loading
Loading