feat: add command tags (#443)

Co-authored-by: TreemanKing <67459602+TreemanKing@users.noreply.github.com>
This commit is contained in:
Sekwah 2024-08-31 03:47:36 +02:00
parent ab92ff1046
commit 833399e8d0
22 changed files with 335 additions and 96 deletions

View File

@ -13,53 +13,56 @@ concurrency:
jobs:
pre-commit-check:
permissions:
pull-requests: write
if: github.event.action != 'labeled' || github.event.label.name == 'pre-commit ci run'
name: Run pre-commit checks
runs-on: ubuntu-20.04
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- run: gh pr edit ${{ github.event.number }} --remove-label 'pre-commit ci run'
if: github.event.action == 'labeled' && github.event.label.name == 'pre-commit ci run'
env:
GH_TOKEN: ${{ github.token }}
- uses: dorny/paths-filter@v2
id: filter
with:
list-files: shell
filters: |
addedOrModified:
- added|modified: '**'
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- run: gh pr edit ${{ github.event.number }} --remove-label 'pre-commit ci run'
if: github.event.action == 'labeled' && github.event.label.name == 'pre-commit ci run'
env:
GH_TOKEN: ${{ github.token }}
- uses: dorny/paths-filter@v2
id: filter
with:
list-files: shell
filters: |
addedOrModified:
- added|modified: '**'
# run only if changed files were detected
- name: Run against changes
uses: pre-commit/action@v3.0.1
if: steps.filter.outputs.addedOrModified == 'true'
with:
extra_args: --files ${{ steps.filter.outputs.addedOrModified_files }}
- name: Run against changes
uses: pre-commit/action@v3.0.1
if: steps.filter.outputs.addedOrModified == 'true'
with:
extra_args: --files ${{ steps.filter.outputs.addedOrModified_files }}
# run if no changed files were detected (e.g. workflow_dispatch on master branch)
- name: Run against all files
uses: pre-commit/action@v3.0.1
if: steps.filter.outputs.addedOrModified != 'true'
with:
extra_args: --all-files
- uses: pre-commit-ci/lite-action@v1.0.2
if: always()
with:
msg: apply code formatting
- name: Run against all files
uses: pre-commit/action@v3.0.1
if: steps.filter.outputs.addedOrModified != 'true'
with:
extra_args: --all-files
- name: Commit pre-commit changes
if: always() && github.event.action != 'pull_request' && github.event.action != 'labeled'
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git add .
git commit -m "chore: pre-commit changes" || echo "No changes to commit"
git push ${{ github.head_ref }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Commit pre-commit changes
if: always()
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git add .
git commit -m "chore: pre-commit changes [skip ci]" || echo "No changes to commit"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Push pre-commit changes
if: always() && github.event.action != 'pull_request' && github.event.action != 'labeled' && github.event.action != 'synchronize'
run: |
git push ${{ github.head_ref }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: pre-commit-ci/lite-action@v1.0.2
if: always()

View File

@ -128,6 +128,7 @@ public class AdvancedPortalsCore {
this.tagRegistry.registerTag(new CooldownTag());
this.tagRegistry.registerTag(new TriggerBlockTag());
this.tagRegistry.registerTag(new PermissionTag());
this.tagRegistry.registerTag(new CommandTag());
}
/**

View File

@ -31,6 +31,7 @@ public class CoreListeners {
public void playerJoin(PlayerContainer player) {
this.playerDataServices.setJoinCooldown(player);
this.playerDataServices.getPlayerData(player).setInPortal(true);
}
public void teleportEvent(PlayerContainer player) {
@ -173,6 +174,7 @@ public class CoreListeners {
public void worldChange(PlayerContainer player) {
this.playerDataServices.setJoinCooldown(player);
this.playerDataServices.getPlayerData(player).setInPortal(true);
}
public boolean preventEntityCombust(EntityContainer entity) {

View File

@ -75,8 +75,9 @@ public abstract class CreateTaggedSubCommand implements SubCommand {
String baseString = endsWithSplit
? argData
: argData.substring(
0,
argData.lastIndexOf(multiTagSplit) + 1);
0,
argData.lastIndexOf(multiTagSplit)
+ 1);
tagSuggestions =
tagSuggestions

View File

@ -48,12 +48,7 @@ public class CreatePortalSubCommand extends CreateTaggedSubCommand {
// Find the tag with the "name" NAME
DataTag nameTag =
portalTags.stream()
.filter(tag -> {
this.infoLogger.info("Tag: " + tag.NAME);
this.infoLogger.info(
"Equals: " + tag.NAME.equals(NameTag.TAG_NAME));
return tag.NAME.equals(NameTag.TAG_NAME);
})
.filter(tag -> tag.NAME.equals(NameTag.TAG_NAME))
.findFirst()
.orElse(null);

View File

@ -1,6 +1,7 @@
package com.sekwah.advancedportals.core.connector.containers;
import com.sekwah.advancedportals.core.serializeddata.BlockLocation;
import com.sekwah.advancedportals.core.tags.activation.CommandTag;
import java.util.UUID;
/**
@ -37,4 +38,6 @@ public interface PlayerContainer extends EntityContainer {
boolean sendPacket(String channel, byte[] bytes);
void playSound(String sound, float volume, float pitch);
ServerContainer getServer();
}

View File

@ -1,5 +1,6 @@
package com.sekwah.advancedportals.core.connector.containers;
import com.sekwah.advancedportals.core.tags.activation.CommandTag;
import java.util.List;
import java.util.UUID;
@ -13,4 +14,7 @@ public interface ServerContainer {
List<String> getTriggerBlocks();
PlayerContainer[] getPlayers();
void dispatchCommand(UUID uuid, String command,
CommandTag.CommandLevel commandLevel);
}

View File

@ -13,6 +13,7 @@ import com.sekwah.advancedportals.core.tags.activation.TriggerBlockTag;
import com.sekwah.advancedportals.core.util.Lang;
import com.sekwah.advancedportals.core.warphandler.ActivationData;
import com.sekwah.advancedportals.core.warphandler.Tag;
import java.util.*;
/**
@ -104,28 +105,27 @@ public class AdvancedPortal implements TagTarget {
}*/
/**
* @param player The player on the server attempting to use an advanced
* portal
* @param player The player on the server attempting to use an advanced
* portal
* @param moveActivated if the portal was activated by a move event (won't
* trigger knockback)
* trigger knockback)
* @return
*/
public boolean activate(PlayerContainer player, boolean moveActivated) {
var playerData = playerDataServices.getPlayerData(player);
if (playerData.isInPortal())
return false;
playerData.setInPortal(true);
if (playerData.hasJoinCooldown()) {
var cooldown =
(int) Math.ceil(playerData.getJoinCooldownLeft() / 1000D);
(int) Math.ceil(playerData.getJoinCooldownLeft() / 1000D);
player.sendMessage(Lang.translateInsertVariables(
"portal.cooldown.join", cooldown,
Lang.translate(cooldown == 1 ? "time.second"
: "time.seconds")));
"portal.cooldown.join", cooldown,
Lang.translate(cooldown == 1 ? "time.second"
: "time.seconds")));
if (configRepository.playFailSound()) {
var rand = new Random();
player.playSound("block.portal.travel", 0.05f,
rand.nextFloat() * 0.4F + 0.8F);
rand.nextFloat() * 0.4F + 0.8F);
}
return false;
}
@ -139,36 +139,33 @@ public class AdvancedPortal implements TagTarget {
for (DataTag portalTag : portalTags) {
Tag.Activation activationHandler =
tagRegistry.getActivationHandler(portalTag.NAME);
if (activationHandler != null) {
if (!activationHandler.preActivated(
this, player, data,
this.getArgValues(portalTag.NAME))) {
return false;
}
tagRegistry.getActivationHandler(portalTag.NAME);
if (activationHandler != null && !activationHandler.preActivated(
this, player, data,
this.getArgValues(portalTag.NAME))) {
return false;
}
}
for (DataTag portalTag : portalTags) {
Tag.Activation activationHandler =
tagRegistry.getActivationHandler(portalTag.NAME);
if (activationHandler != null) {
if (!activationHandler.activated(
this, player, data,
this.getArgValues(portalTag.NAME))) {
return false;
}
tagRegistry.getActivationHandler(portalTag.NAME);
if (activationHandler != null && !activationHandler.activated(
this, player, data,
this.getArgValues(portalTag.NAME))) {
return false;
}
}
for (DataTag portalTag : portalTags) {
Tag.Activation activationHandler =
tagRegistry.getActivationHandler(portalTag.NAME);
tagRegistry.getActivationHandler(portalTag.NAME);
if (activationHandler != null) {
activationHandler.postActivated(
this, player, data, this.getArgValues(portalTag.NAME));
this, player, data, this.getArgValues(portalTag.NAME));
}
}
if (data.hasActivated()) {
playerData.setNetherPortalCooldown(1000);
playerData.setInPortal(true);
return true;
}
return false;
@ -192,12 +189,12 @@ public class AdvancedPortal implements TagTarget {
double playerZ = loc.getPosZ();
return Objects.equals(loc.getWorldName(), this.minLoc.getWorldName())
&& playerX >= this.minLoc.getPosX() - additionalArea
&& playerX < this.maxLoc.getPosX() + 1 + additionalArea
&& playerY >= this.minLoc.getPosY() - additionalArea
&& playerY < this.maxLoc.getPosY() + 1 + additionalArea
&& playerZ >= this.minLoc.getPosZ() - additionalArea
&& playerZ < this.maxLoc.getPosZ() + 1 + additionalArea;
&& playerX >= this.minLoc.getPosX() - additionalArea
&& playerX < this.maxLoc.getPosX() + 1 + additionalArea
&& playerY >= this.minLoc.getPosY() - additionalArea
&& playerY < this.maxLoc.getPosY() + 1 + additionalArea
&& playerZ >= this.minLoc.getPosZ() - additionalArea
&& playerZ < this.maxLoc.getPosZ() + 1 + additionalArea;
}
public void setArgValues(DataTag portalTag) {

View File

@ -1,6 +1,7 @@
package com.sekwah.advancedportals.core.repository;
import com.sekwah.advancedportals.core.serializeddata.DataStorage;
import com.sekwah.advancedportals.core.serializeddata.config.CommandPortalConfig;
public interface ConfigRepository {
boolean getUseOnlySpecialAxe();
@ -32,4 +33,6 @@ public interface ConfigRepository {
boolean playFailSound();
void storeConfig();
CommandPortalConfig getCommandPortals();
}

View File

@ -3,6 +3,7 @@ package com.sekwah.advancedportals.core.repository.impl;
import com.google.inject.Singleton;
import com.sekwah.advancedportals.core.repository.ConfigRepository;
import com.sekwah.advancedportals.core.serializeddata.DataStorage;
import com.sekwah.advancedportals.core.serializeddata.config.CommandPortalConfig;
import com.sekwah.advancedportals.core.serializeddata.config.Config;
import java.util.HashMap;
@ -95,6 +96,11 @@ public class ConfigRepositoryImpl implements ConfigRepository {
return this.config.playFailSound;
}
@Override
public CommandPortalConfig getCommandPortals() {
return this.config.commandPortals;
}
@Override
public void storeConfig() {
this.dataStorage.storeFile(this.config, "config.yaml");

View File

@ -127,8 +127,8 @@ public class ReflectiveConstructor<T> extends Constructor {
}
} catch (Exception e) {
infoLogger.warning("Failed to set field " + field.getName()
+ " in " + currentClass.getName() + ": "
+ e.getMessage());
+ " in " + currentClass.getName()
+ ": " + e.getMessage());
infoLogger.error(e);
throw new RuntimeException("Failed to set field "
+ field.getName() + " in "

View File

@ -0,0 +1,8 @@
package com.sekwah.advancedportals.core.serializeddata.config;
public class CommandPortalConfig {
public final boolean op = true;
public final boolean console = true;
public final boolean permsWildcard = true;
}

View File

@ -33,4 +33,6 @@ public class Config {
public double throwbackStrength = 0.7;
public boolean playFailSound = true;
public CommandPortalConfig commandPortals = new CommandPortalConfig();
}

View File

@ -95,7 +95,8 @@ public class PortalServices {
}
}
var playerData = playerDataServices.getPlayerData(player);
if (!notInPortal) {
if (!playerData.isInPortal() && !notInPortal) {
playerData.setInPortal(true);
var strength = configRepository.getThrowbackStrength();
PlayerUtils.throwPlayerBack(player, strength);
}

View File

@ -0,0 +1,146 @@
package com.sekwah.advancedportals.core.tags.activation;
import com.google.inject.Inject;
import com.sekwah.advancedportals.core.connector.containers.PlayerContainer;
import com.sekwah.advancedportals.core.registry.TagTarget;
import com.sekwah.advancedportals.core.repository.ConfigRepository;
import com.sekwah.advancedportals.core.util.Lang;
import com.sekwah.advancedportals.core.warphandler.ActivationData;
import com.sekwah.advancedportals.core.warphandler.Tag;
import javax.annotation.Nullable;
public class CommandTag implements Tag.Activation, Tag.Split, Tag.Creation {
@Inject
ConfigRepository configRepository;
public static String TAG_NAME = "command";
private final TagType[] tagTypes = new TagType[] {TagType.PORTAL};
@Override
public TagType[] getTagTypes() {
return tagTypes;
}
@Override
public String getName() {
return TAG_NAME;
}
@Nullable
@Override
public String[] getAliases() {
return null;
}
@Override
public String description() {
return Lang.translate("tag.command.description");
}
@Nullable
@Override
public String splitString() {
return ",";
}
@Override
public boolean preActivated(TagTarget target, PlayerContainer player,
ActivationData activeData, String[] argData) {
return true;
}
// TODO: Check if its worth autocompleting an existing command in the
// command tag by
// grabbing all commands in the server
// TODO: Add a warning in console if op/* command is used and tell them to
// use console instead
@Override
public void postActivated(TagTarget target, PlayerContainer player,
ActivationData activationData, String[] argData) {
for (String command : argData) {
char executionCommand = command.charAt(0);
String formattedCommand =
command.replaceAll("@player", player.getName());
switch (executionCommand) {
case '!':
player.getServer().dispatchCommand(
player.getUUID(), formattedCommand.substring(1),
CommandLevel.OP);
break;
case '#':
player.getServer().dispatchCommand(
player.getUUID(), formattedCommand.substring(1),
CommandLevel.CONSOLE);
break;
case '^':
player.getServer().dispatchCommand(
player.getUUID(), formattedCommand.substring(1),
CommandLevel.PERMISSION_WILDCARD);
break;
default:
player.getServer().dispatchCommand(player.getUUID(),
formattedCommand,
CommandLevel.PLAYER);
break;
}
}
}
@Override
public boolean activated(TagTarget target, PlayerContainer player,
ActivationData activationData, String[] argData) {
// Will trigger in the post activation stage to make sure the command triggers after any teleportation
activationData.setWarpStatus(ActivationData.WarpedStatus.ACTIVATED);
return true;
}
@Override
public boolean created(TagTarget target, PlayerContainer player,
String[] argData) {
if (argData != null) {
for (String command : argData) {
char executionCommand = command.charAt(0);
return switch (executionCommand) {
case '!' -> {
if (!player.hasPermission("advancedportals.createportal.commandlevel.op")) {
player.sendMessage(Lang.translateInsertVariables("tag.command.nopermission", "OP"));
yield false;
}
yield true;
}
case '#' -> {
if (!player.hasPermission("advancedportals.createportal.commandlevel.console")) {
player.sendMessage(Lang.translateInsertVariables("tag.command.nopermission","Console"));
yield false;
}
yield true;
}
case '^' -> {
if (!player.hasPermission("advancedportals.createportal.commandlevel.permswild")) {
player.sendMessage(Lang.translateInsertVariables("tag.command.nopermission", "*"));
yield false;
}
yield true;
}
default -> true;
};
}
}
return false;
}
@Override
public void destroyed(TagTarget target, PlayerContainer player, String[] argData) {
// Needs created but not destroyed
}
public enum CommandLevel{
OP,
PERMISSION_WILDCARD,
CONSOLE,
PLAYER
}
}

View File

@ -19,10 +19,10 @@ import javax.annotation.Nullable;
public class CooldownTag implements Tag.Activation, Tag.Creation {
@Inject
transient PlayerDataServices playerDataServices;
PlayerDataServices playerDataServices;
@Inject
transient ConfigRepository configRepository;
ConfigRepository configRepository;
@Inject
private InfoLogger infoLogger;

View File

@ -13,10 +13,10 @@ import javax.inject.Inject;
public class PermissionTag implements Tag.Activation {
@Inject
transient PlayerDataServices playerDataServices;
PlayerDataServices playerDataServices;
@Inject
transient ConfigRepository configRepository;
ConfigRepository configRepository;
@Inject
private InfoLogger infoLogger;

View File

@ -51,13 +51,15 @@ public class FriendlyDataOutput {
public void writeUtf(String text, int maxLength) {
if (text.length() > maxLength) {
throw new EncoderException("String too big (was " + text.length()
+ " characters, max " + maxLength + ")");
+ " characters, max " + maxLength
+ ")");
} else {
byte[] abyte = text.getBytes(StandardCharsets.UTF_8);
int i = getMaxEncodedUtfLength(maxLength);
if (abyte.length > i) {
throw new EncoderException("String too big (was " + abyte.length
+ " bytes encoded, max " + i + ")");
+ " bytes encoded, max " + i
+ ")");
} else {
this.writeVarInt(abyte.length);
this.writeBytes(abyte);

View File

@ -25,10 +25,8 @@ public class ActivationData {
}
public void setWarpStatus(WarpedStatus warped) {
if (this.warpStatus == WarpedStatus.WARPED) {
return;
} else if (this.warpStatus == WarpedStatus.ACTIVATED
&& warped != WarpedStatus.WARPED) {
if (this.warpStatus == WarpedStatus.WARPED || (this.warpStatus == WarpedStatus.ACTIVATED
&& warped != WarpedStatus.WARPED)) {
return;
}
this.warpStatus = warped;

View File

@ -102,8 +102,8 @@ command.destination.show.unsupported= Destination markers are not supported on t
command.destination.teleport.help=Teleports to specified destination.
command.destination.teleport.detailedhelp=Teleports to destination given by the name of the destination.
command.destination.teleport.error=There was an error teleporting to your destination (@destiname).
command.destination.teleport.success=You have teleported to destination (@destiname).
command.destination.teleport.error= There was an error teleporting to your destination (@destiname).
command.destination.teleport.success= You have teleported to destination (@destiname).
command.destination.reload.help=Reloads the destination data from the repository.
command.destination.reload.detailedhelp=This command will reload all destination data from the repository, updating the cache with the latest data.
@ -172,6 +172,9 @@ items.interact.right=Right Click
tag.permission.description=Sets the permission of a portal
tag.desti.description=Sets the destination of the portal
tag.name.error.nospaces= The name cannot contain spaces.
tag.triggerblock.description=Sets the trigger block/s of the portal. Comma seperated or multi tag.
tag.triggerblock.description=Sets the trigger block/s of the portal. Comma separated or multi tag.
tag.command.description=Sets a command on the post activation of the portal. Comma separated or multi tag.
tag.cooldown.fail= The cooldown must be a number.
tag.command.nopermission= You do not have permission to create a command at %1$s command level
tag.command.notenabled= Command portals of %1$s level are not enabled on this server.

View File

@ -3,8 +3,10 @@ package com.sekwah.advancedportals.spigot.connector.container;
import com.google.inject.Inject;
import com.sekwah.advancedportals.core.AdvancedPortalsCore;
import com.sekwah.advancedportals.core.connector.containers.PlayerContainer;
import com.sekwah.advancedportals.core.connector.containers.ServerContainer;
import com.sekwah.advancedportals.core.serializeddata.BlockLocation;
import com.sekwah.advancedportals.core.serializeddata.PlayerLocation;
import com.sekwah.advancedportals.core.tags.activation.CommandTag;
import com.sekwah.advancedportals.spigot.AdvancedPortalsPlugin;
import com.sekwah.advancedportals.spigot.reflection.MinecraftCustomPayload;
import java.util.Arrays;
@ -12,9 +14,11 @@ import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.permissions.PermissionAttachment;
/**
* Just a temporary container for whenever advanced portals needs to get data
@ -112,4 +116,9 @@ public class SpigotPlayerContainer
public void playSound(String sound, float volume, float pitch) {
this.player.playSound(this.player.getLocation(), sound, volume, pitch);
}
@Override
public ServerContainer getServer() {
return new SpigotServerContainer(this.player.getServer());
}
}

View File

@ -3,12 +3,15 @@ package com.sekwah.advancedportals.spigot.connector.container;
import com.sekwah.advancedportals.core.connector.containers.PlayerContainer;
import com.sekwah.advancedportals.core.connector.containers.ServerContainer;
import com.sekwah.advancedportals.core.connector.containers.WorldContainer;
import com.sekwah.advancedportals.core.tags.activation.CommandTag;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.permissions.PermissionAttachment;
public class SpigotServerContainer implements ServerContainer {
private final Server server;
@ -72,4 +75,56 @@ public class SpigotServerContainer implements ServerContainer {
default -> false;
};
}
@Override
public void dispatchCommand(UUID uuid, String command, CommandTag.CommandLevel commandLevel) {
Player player = server.getPlayer(uuid);
switch (commandLevel) {
case CONSOLE:
server.dispatchCommand(server.getConsoleSender(), command);
break;
case PLAYER:
server.dispatchCommand(player, command);
break;
case OP, PERMISSION_WILDCARD:
executeCommandWithPermission(player, server, command,
commandLevel);
break;
}
}
// Execute commands with elevated permissions method
private void executeCommandWithPermission(
Player player, Server server, String command,
CommandTag.CommandLevel commandLevel) {
switch (commandLevel) {
case PERMISSION_WILDCARD:
if (player.hasPermission("*")) {
server.dispatchCommand(player, command);
return;
}
PermissionAttachment permissionAttachment =
player.addAttachment(
server.getPluginManager().getPlugin("AdvancedPortals"));
try {
permissionAttachment.setPermission("*", true);
server.dispatchCommand(player, command);
} finally {
player.removeAttachment(permissionAttachment);
}
break;
case OP:
if (player.isOp()) {
server.dispatchCommand(player, command);
return;
}
try {
player.setOp(true);
server.dispatchCommand(player, command);
} finally {
player.setOp(false);
}
break;
}
}
}