Skip to content

Commit

Permalink
refactor(cmds): events and custom command feedback (fixes #54) (#58)
Browse files Browse the repository at this point in the history
  • Loading branch information
axieum authored Aug 21, 2022
1 parent 4baa059 commit 162f30f
Show file tree
Hide file tree
Showing 6 changed files with 310 additions and 112 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import com.mojang.brigadier.exceptions.CommandSyntaxException;

import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.Text;

import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;

import me.axieum.mcmod.minecord.api.cmds.command.MinecordCommand;

/**
* A collection of callbacks for Minecord issued commands.
*/
Expand All @@ -18,67 +24,126 @@ public final class MinecordCommandEvents
private MinecordCommandEvents() {}

/**
* A collection of uptime command events.
* Called before executing a Minecord command.
*/
public static final Event<BeforeExecute> BEFORE_EXECUTE =
EventFactory.createArrayBacked(BeforeExecute.class, callbacks -> (ctx, event, server) -> {
for (BeforeExecute callback : callbacks) {
if (!callback.onBeforeMinecordCommand(ctx, event, server))
return false;
}
return true;
});

/**
* Called after executing a Minecord command.
*/
public static final class Uptime
public static final Event<AfterExecute> AFTER_EXECUTE =
EventFactory.createArrayBacked(AfterExecute.class, callbacks -> (ctx, event, server) -> {
for (AfterExecute callback : callbacks) {
callback.onMinecordCommand(ctx, event, server);
}
});

@FunctionalInterface
public interface BeforeExecute
{
/**
* Called before executing a Minecord command.
*
* @param context Minecord command context
* @param event JDA slash command event to reply to
* @param server Minecraft server
* @return true if the Minecord command should execute
*/
boolean onBeforeMinecordCommand(
MinecordCommand context,
SlashCommandInteractionEvent event,
@Nullable MinecraftServer server
);
}

@FunctionalInterface
public interface AfterExecute
{
/**
* Called after executing an uptime command.
* Called after executing a Minecord command.
*
* @param context Minecord command context
* @param event JDA slash command event to reply to
* @param server Minecraft server
*/
public static final Event<AfterExecuteUptimeCommand> AFTER_EXECUTE =
EventFactory.createArrayBacked(AfterExecuteUptimeCommand.class, callbacks -> (event, server, embed) -> {
EmbedBuilder builder = embed;
for (AfterExecuteUptimeCommand callback : callbacks)
builder = callback.onAfterExecuteUptime(event, server, embed);
return builder;
void onMinecordCommand(
MinecordCommand context,
SlashCommandInteractionEvent event,
@Nullable MinecraftServer server
);
}

/**
* A collection of built-in Minecord command events.
*/
public static final class Builtin
{
/**
* Called after executing the ticks-per-second (TPS) command.
*/
public static final Event<TPSCommand> TPS =
EventFactory.createArrayBacked(TPSCommand.class, callbacks -> (ctx, event, server, embed) -> {
for (TPSCommand callback : callbacks) {
embed = callback.onTPSCommand(ctx, event, server, embed);
}
return embed;
});

/**
* Called after executing the uptime command.
*/
public static final Event<UptimeCommand> UPTIME =
EventFactory.createArrayBacked(UptimeCommand.class, callbacks -> (ctx, event, srv, embed) -> {
for (UptimeCommand callback : callbacks) {
embed = callback.onUptimeCommand(ctx, event, srv, embed);
}
return embed;
});

@FunctionalInterface
public interface AfterExecuteUptimeCommand
public interface TPSCommand
{
/**
* Called after executing an uptime command.
* Called after executing the ticks-per-second (TPS) command.
*
* @param context Minecord command context
* @param event JDA slash command event to reply to
* @param server Minecraft server, if present
* @param server Minecraft server
* @param embed builder used to build the embed that will be sent to Discord
* @return embed builder used to build the embed that will be sent to Discord
*/
@NotNull EmbedBuilder onAfterExecuteUptime(
SlashCommandInteractionEvent event, @Nullable MinecraftServer server, EmbedBuilder embed
@NotNull EmbedBuilder onTPSCommand(
MinecordCommand context,
SlashCommandInteractionEvent event,
@NotNull MinecraftServer server,
EmbedBuilder embed
);
}
}

/**
* A collection of ticks-per-second (TPS) command events.
*/
public static final class TPS
{
/**
* Called after executing a ticks-per-second (TPS) command.
*/
public static final Event<AfterExecuteTPSCommand> AFTER_EXECUTE =
EventFactory.createArrayBacked(AfterExecuteTPSCommand.class, callbacks -> (event, server, embed) -> {
EmbedBuilder builder = embed;
for (AfterExecuteTPSCommand callback : callbacks)
builder = callback.onAfterExecuteTPS(event, server, embed);
return builder;
});

@FunctionalInterface
public interface AfterExecuteTPSCommand
public interface UptimeCommand
{
/**
* Called after executing a ticks-per-second (TPS) command.
* Called after executing the uptime command.
*
* @param context Minecord command
* @param event JDA slash command event to reply to
* @param server Minecraft server
* @param server Minecraft server, if present
* @param embed builder used to build the embed that will be sent to Discord
* @return embed builder used to build the embed that will be sent to Discord
*/
@NotNull EmbedBuilder onAfterExecuteTPS(
SlashCommandInteractionEvent event, @NotNull MinecraftServer server, EmbedBuilder embed
@NotNull EmbedBuilder onUptimeCommand(
MinecordCommand context,
SlashCommandInteractionEvent event,
@Nullable MinecraftServer server,
EmbedBuilder embed
);
}
}
Expand All @@ -88,64 +153,154 @@ public interface AfterExecuteTPSCommand
*/
public static final class Custom
{
/**
* Called before allowing custom Minecraft-proxy command execution.
*
* <p>NB: If you cancel the execution by returning {@code null},
* you are responsible for replying to the interaction.
*/
public static final Event<AllowExecute> ALLOW_EXECUTE =
EventFactory.createArrayBacked(AllowExecute.class, callbacks -> (ctx, event, server, cmd) -> {
for (AllowExecute callback : callbacks) {
cmd = callback.onAllowCustomCommand(ctx, event, server, cmd);
if (cmd == null) return null;
}
return cmd;
});

/**
* Called before executing a custom Minecraft-proxy command.
*/
public static final Event<BeforeExecuteCustomCommand> BEFORE_EXECUTE =
EventFactory.createArrayBacked(BeforeExecuteCustomCommand.class, callbacks -> (command, event, server) -> {
for (BeforeExecuteCustomCommand callback : callbacks)
if (!callback.onBeforeExecuteCustom(command, event, server))
return false;
return true;
public static final Event<BeforeExecute> BEFORE_EXECUTE =
EventFactory.createArrayBacked(BeforeExecute.class, callbacks -> (ctx, event, server, cmd, source) -> {
for (BeforeExecute callback : callbacks) {
source = callback.onBeforeCustomCommand(ctx, event, server, cmd, source);
}
return source;
});

/**
* Called during execution of a custom Minecraft-proxy command when
* providing feedback to the executor.
*
* <p>NB: This is not called if no command feedback was provided!
*/
public static final Event<Feedback> FEEDBACK =
EventFactory.createArrayBacked(Feedback.class, callbacks -> (ctx, event, server, cmd, msg, s, embed) -> {
for (Feedback callback : callbacks) {
embed = callback.onCustomCommandFeedback(ctx, event, server, cmd, msg, s, embed);
if (embed == null) return null;
}
return embed;
});

/**
* Called after executing a custom Minecraft-proxy command.
*/
public static final Event<AfterExecuteCustomCommand> AFTER_EXECUTE =
EventFactory.createArrayBacked(AfterExecuteCustomCommand.class, callbacks -> (ev, se, co, re, su, em) -> {
EmbedBuilder builder = em;
for (AfterExecuteCustomCommand callback : callbacks)
builder = callback.onAfterExecuteCustom(ev, se, co, re, su, em);
return builder;
public static final Event<AfterExecute> AFTER_EXECUTE =
EventFactory.createArrayBacked(AfterExecute.class, callbacks -> (ctx, event, server, cmd, s, res, exc) -> {
for (AfterExecute callback : callbacks) {
callback.onCustomCommand(ctx, event, server, cmd, s, res, exc);
}
});

@FunctionalInterface
public interface BeforeExecuteCustomCommand
public interface AllowExecute
{
/**
* Called before allowing custom Minecraft-proxy command execution.
*
* <p>NB: If you cancel the execution by returning {@code null},
* you are responsible for replying to the interaction.
*
* @param context Minecord command context
* @param event JDA slash command event
* @param server Minecraft server
* @param command Minecraft command that will be executed (without leading '/')
* @return the command to be executed if the execution should go ahead, or {@code null} to cancel
*/
@Nullable String onAllowCustomCommand(
MinecordCommand context,
SlashCommandInteractionEvent event,
@NotNull MinecraftServer server,
String command
);
}

@FunctionalInterface
public interface BeforeExecute
{
/**
* Called before executing a custom Minecraft-proxy command.
*
* @param context Minecord command context
* @param event JDA slash command event
* @param server Minecraft server
* @param command Minecraft command that will be executed (without leading '/')
* @return true if the execution should go ahead, or false to cancel
* @param source Minecraft command source
* @return Minecraft command source used to execute the command
*/
boolean onBeforeExecuteCustom(String command, SlashCommandInteractionEvent event, MinecraftServer server);
@NotNull ServerCommandSource onBeforeCustomCommand(
MinecordCommand context,
SlashCommandInteractionEvent event,
@NotNull MinecraftServer server,
String command,
@NotNull ServerCommandSource source
);
}

@FunctionalInterface
public interface AfterExecuteCustomCommand
public interface Feedback
{
/**
* Called after executing a custom Minecraft-proxy command.
* Called during execution of a custom Minecraft-proxy command when
* providing feedback to the executor.
*
* <p>NB: This is not called if no command feedback was provided!
*
* @param context Minecord command context
* @param event JDA slash command event to reply to
* @param server Minecraft server
* @param command Minecraft command that was executed (without leading '/')
* @param result Minecraft command execution feedback
* @param text Minecraft command execution feedback
* @param success true if the command was a success
* @param embed builder used to build the embed that will be sent to Discord
* @return embed builder used to build the embed that will be sent to Discord
* @return embed builder used to build the embed that will be sent to Discord, or {@code null} to cancel
*/
@NotNull EmbedBuilder onAfterExecuteCustom(
@Nullable EmbedBuilder onCustomCommandFeedback(
MinecordCommand context,
SlashCommandInteractionEvent event,
MinecraftServer server,
@NotNull MinecraftServer server,
String command,
String result,
Text text,
boolean success,
EmbedBuilder embed
);
}

@FunctionalInterface
public interface AfterExecute
{
/**
* Called after executing a custom Minecraft-proxy command.
*
* @param context Minecord command context
* @param event JDA slash command event to reply to
* @param server Minecraft server
* @param command Minecraft command that was executed (without leading '/')
* @param success true if the command was a success
* @param result Minecraft command execution result
* @param exc Minecraft command syntax exception if present, else {@code null}
*/
void onCustomCommand(
MinecordCommand context,
SlashCommandInteractionEvent event,
@NotNull MinecraftServer server,
String command,
boolean success,
int result,
@Nullable CommandSyntaxException exc
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
import net.dv8tion.jda.api.events.ReadyEvent;
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import org.jetbrains.annotations.Nullable;

import net.minecraft.server.MinecraftServer;

import me.axieum.mcmod.minecord.api.Minecord;
import me.axieum.mcmod.minecord.api.cmds.MinecordCommands;
import me.axieum.mcmod.minecord.api.cmds.event.MinecordCommandEvents;
import me.axieum.mcmod.minecord.api.util.StringTemplate;
import static me.axieum.mcmod.minecord.impl.cmds.MinecordCommandsImpl.LOGGER;
import static me.axieum.mcmod.minecord.impl.cmds.MinecordCommandsImpl.getConfig;
Expand Down Expand Up @@ -44,7 +46,7 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event)
// Attempt to run the command
try {
// Check whether Minecraft is required, and hence whether the server has started
final MinecraftServer server = Minecord.getInstance().getMinecraft().orElse(null);
final @Nullable MinecraftServer server = Minecord.getInstance().getMinecraft().orElse(null);
if (command.requiresMinecraft() && (server == null || server.getTickTime() == 0)) {
LOGGER.warn("@{} used '{}' but the server is not yet ready!", username, raw);
event.getHook().sendMessageEmbeds(
Expand Down Expand Up @@ -74,9 +76,18 @@ public void onSlashCommandInteraction(SlashCommandInteractionEvent event)
}
}

// Fire an event to allow the command execution to be cancelled
if (!MinecordCommandEvents.BEFORE_EXECUTE.invoker().onBeforeMinecordCommand(command, event, server)) {
LOGGER.debug("@{} used '{}' but its execution was externally cancelled!", username, raw);
return;
}

// Attempt to cascade the event to the matched command
LOGGER.info("@{} used '{}'", username, raw);
command.execute(event, server);

// Fire an event for the successful command execution
MinecordCommandEvents.AFTER_EXECUTE.invoker().onMinecordCommand(command, event, server);
} catch (Exception e) {
LOGGER.error("@{} failed to use '{}'", username, raw, e);
event.getHook().sendMessageEmbeds(
Expand Down
Loading

0 comments on commit 162f30f

Please sign in to comment.