An annotation-based command framework for Bukkit

Related tags

Spring Boot blade
Overview

Blade

Blade is an easy-to-use command framework based on annotations. It currently only supports Bukkit, but it can be easily extended to more platforms. To use Blade, you simply have to include it as a dependency and shade it into your final jar.

If you make any changes or improvements to the project, please consider making a pull request to merge your changes back into the upstream project. This project is in its early stages, if you find any issues please open an issue.

This project follows Semantic Versioning.

Using Blade

Maven

<repositories>
    <repository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
    </repository>
</repositories>

<dependencies>
    <dependency>
        <groupId>com.github.vaperion</groupId>
        <artifactId>blade</artifactId>
        <version>1.2.1</version>
        <scope>compile</scope>
    </dependency>
</dependencies>

Gradle

allprojects {
    repositories {
        maven { url 'https://jitpack.io' }
    }
}

dependencies {
    implementation 'com.github.vaperion:blade:1.2.1'
}

Example code

Initializing Blade:

import me.vaperion.blade.Blade;
import me.vaperion.blade.command.bindings.impl.BukkitBindings;
import me.vaperion.blade.command.container.impl.BukkitCommandContainer;
import me.vaperion.blade.completer.impl.ProtocolLibTabCompleter;
import org.bukkit.plugin.java.JavaPlugin;

public class ExamplePlugin extends JavaPlugin {

    @Override
    public void onEnable() {
        Blade.of()
                .fallbackPrefix("fallbackPrefix")
                .containerCreator(BukkitCommandContainer.CREATOR)
                .binding(new BukkitBindings())
                .build()
                .register(ExampleCommand.class);
    }
}

Overriding bukkit commands:

Blade.of()
        ...
        .overrideCommands(true)
        ...;

Setting a custom tab completer:

Blade.of()
        ...
        .tabCompleter(new ProtocolLibTabCompleter(this))
        ...;

Registering a type provider without Bindings:

Blade.of()
        ...
        .bind(Example.class, new BladeProvider<Example>() {...})
        ...;

Example commands:

import me.vaperion.blade.command.annotation.*;
import org.bukkit.entity.Player;

public class ExampleCommand {
    
    @Command(value = {"ban", "go away"}, async = true, quoted = false, description = "Ban a player")
    @Permission(value = "blade.command.ban", message = "You are not allowed to execute this command.")
    public static void ban(@Sender Player sender,
                           @Flag(value = 's', description = "Silently ban the player") boolean silent,
                           @Name("target") Player target,
                           @Name("reason") @Combined String reason) {
        sender.sendMessage("Silent: " + silent);
        sender.sendMessage("Target: " + target.getName());
        sender.sendMessage("Reason: " + reason);
    }
    
    @Command("test")
    public static void test(@Sender Player sender,
                            @Range(min = 18) int age,
                            @Optional("100") @Range(max = 100000) double balance) {
        sender.sendMessage("Age: " + age);
        sender.sendMessage("Balance: " + balance);
    }
}

Example custom tab completer with Netty:

import me.vaperion.blade.command.service.BladeCommandService;
import me.vaperion.blade.completer.TabCompleter;
import net.minecraft.server.v1_7_R4.PacketPlayInTabComplete;
import net.minecraft.server.v1_7_R4.PacketPlayOutTabComplete;
import net.minecraft.util.io.netty.channel.ChannelDuplexHandler;
import net.minecraft.util.io.netty.channel.ChannelHandlerContext;
import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.v1_7_R4.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;

public class CustomTabCompleter implements TabCompleter, Listener {

    private BladeCommandService commandService;

    public CustomTabCompleter(JavaPlugin plugin) {
        Bukkit.getServer().getPluginManager().registerEvents(this, plugin);
    }

    @Override
    public void init(@NotNull BladeCommandService bladeCommandService) {
        this.commandService = bladeCommandService;
    }

    @EventHandler
    public void onJoin(PlayerJoinEvent event) {
        Player player = event.getPlayer();
        ((CraftPlayer) player).getHandle().playerConnection.networkManager.m.pipeline()
                .addBefore("packet_handler", "blade_completer", new ChannelDuplexHandler() {
                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        if (msg instanceof PacketPlayInTabComplete) {
                            String commandLine = ((PacketPlayInTabComplete) msg).c();
                            if (commandLine.startsWith("/")) {
                                commandLine = commandLine.substring(1);

                                List<String> suggestions = commandService.getCommandCompleter().suggest(commandLine, () -> new BukkitSender(player), (cmd) -> hasPermission(player, cmd));
                                if (suggestions != null) {
                                    ctx.writeAndFlush(new PacketPlayOutTabComplete(suggestions.toArray(new String[0])));
                                    return;
                                }
                            }
                        }

                        super.channelRead(ctx, msg);
                    }
                });
    }
}
Comments
  • Ghost Commands still being registered

    Ghost Commands still being registered

    Commands in a deleted class (ghost class) will still appear in the command help and act as normal commands but are non-existent, only fix I've made is completely refactoring the package the command is in but that's a shitty fix.

    opened by Escapies 3
  • Blade not able to autocomplete?

    Blade not able to autocomplete?

    Hey we have created custom providers and even for non custom providers the command will show up in red (Signinaling the player not having permission) Player's can execute the command and there is no @Permission on the command but it shows up as red and refuses to autocomplete for non op players

    opened by Nopock 2
  • Command Flags Issue

    Command Flags Issue

    So I have this ban command:

        @Command(value = {"ban"}, async = true, description = "Ban Command.")
        @Permission(value = "core.staff")
        public static void onBanCommand(@Sender CommandSender sender, @Name("target") Player target, @Name("reason") @Optional @Combined String reason, @Name("-s") @Flag(value = 's', description = "Silent Ban") boolean silent) {
               //Do Ban Stuff
            }
    

    But the issue is for some reason the flag (-s) is blue and is the first thing in the usage image

    Why is it like that and is it possible to change it?

    opened by GamerRealm 2
  • Console Command

    Console Command

    How would I make a command work for console and for a player?

    For example a simple broadcast command like this:

        @Command(value = {"broadcast"}, description = "Broadcast command.")
        @Permission(value = "core.broadcast")
        public static void onBroadcastCommand(@Sender Player player, @Combined String message) {
            Bukkit.broadcastMessage(CC.translate("&7[&4Broadcast&7] ") + CC.WHITE + message);
        }
        
    

    How would I make it so that a player can use it and the console can use it?

    opened by GamerRealm 2
  • Invisible command parameters

    Invisible command parameters

    I want to create a command with an invisible sender parameter.

    @Command({"is home", "is tp"})
    public void home(@Sender Player player, @Sender Island playerIsland) {
      player.teleport(playerIsland.getSpawnLocation());
      player.sendMessage("Teleported to the island home!");
    }
    

    Then i would create a binding for playerIsland which checks if player has an island etc And I want to make playerIsland parameter not visible in commands usage Is it possible with current blade API?

    opened by Memexurer 2
  • @Optional Issue

    @Optional Issue

    Hey so am trying to use @Optional from the example in the README.md and for some reason it doesn't work for me. Am using the latest version. (1.2.10).

    This is the code of the command:

        @Command({"balance", "bal"})
        public static void balance(@Sender Player sender,
                                   @Optional Player target) {
            if (target == null) {
                sender.sendMessage("Your balance is: $100");
            } else {
                sender.sendMessage(target.getName() + "'s balance is: $100");
            }
        }
    

    This is in the Main class

            Blade.of()
                    .fallbackPrefix("fallbackPrefix")
                    .containerCreator(BukkitCommandContainer.CREATOR)
                    .binding(new BukkitBindings())
                    .build()
                    .register(BalanceCommand.class);
    

    Issue: image

    opened by GamerRealm 2
  • ProtocolLib support issue.

    ProtocolLib support issue.

    If ProtocolLib plugin is present on the server every time I run a command I'm getting this exception: I'm using ProtocolLib v4.8.0 on a 1.16.5 paper server

    [19:48:52 WARN]: An exception was thrown while attempting to tab complete 'sectortp SPAWN02' for player pliki
    [19:48:52 WARN]: FieldAccessException: No field with type [Ljava.lang.String; exists in class PacketPlayOutTabComplete.
    [19:48:52 WARN]:        at com.comphenix.protocol.reflect.StructureModifier.writeInternal(StructureModifier.java:365)
    [19:48:52 WARN]:        at com.comphenix.protocol.reflect.StructureModifier.write(StructureModifier.java:345)
    [19:48:52 WARN]:        at me.vaperion.blade.bukkit.platform.ProtocolLibTabCompleter.onPacketReceiving(ProtocolLibTabCompleter.java:47)
    [19:48:52 WARN]:        at com.comphenix.protocol.injector.SortedPacketListenerList.invokeReceivingListener(SortedPacketListenerList.java:114)
    [19:48:52 WARN]:        at com.comphenix.protocol.injector.SortedPacketListenerList.invokePacketRecieving(SortedPacketListenerList.java:67)
    [19:48:52 WARN]:        at com.comphenix.protocol.injector.PacketFilterManager.handlePacket(PacketFilterManager.java:537)
    [19:48:52 WARN]:        at com.comphenix.protocol.injector.PacketFilterManager.invokePacketRecieving(PacketFilterManager.java:509)
    [19:48:52 WARN]:        at com.comphenix.protocol.injector.netty.ProtocolInjector.packetReceived(ProtocolInjector.java:348)
    [19:48:52 WARN]:        at com.comphenix.protocol.injector.netty.ProtocolInjector.onPacketReceiving(ProtocolInjector.java:313)
    [19:48:52 WARN]:        at com.comphenix.protocol.injector.netty.ChannelInjector.decode(ChannelInjector.java:594)
    [19:48:52 WARN]:        at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:501)
    [19:48:52 WARN]:        at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:440)
    [19:48:52 WARN]:        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
    [19:48:52 WARN]:        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    [19:48:52 WARN]:        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    [19:48:52 WARN]:        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    [19:48:52 WARN]:        at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324)
    [19:48:52 WARN]:        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:296)
    [19:48:52 WARN]:        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    [19:48:52 WARN]:        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    [19:48:52 WARN]:        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    [19:48:52 WARN]:        at com.comphenix.protocol.injector.netty.ChannelInjector$2.channelRead(ChannelInjector.java:290)
    [19:48:52 WARN]:        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    [19:48:52 WARN]:        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    [19:48:52 WARN]:        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    [19:48:52 WARN]:        at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324)
    [19:48:52 WARN]:        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:296)
    [19:48:52 WARN]:        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    [19:48:52 WARN]:        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    [19:48:52 WARN]:        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    [19:48:52 WARN]:        at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286)
    [19:48:52 WARN]:        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    [19:48:52 WARN]:        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    [19:48:52 WARN]:        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    [19:48:52 WARN]:        at io.netty.handler.flush.FlushConsolidationHandler.channelRead(FlushConsolidationHandler.java:152)
    [19:48:52 WARN]:        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    [19:48:52 WARN]:        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    [19:48:52 WARN]:        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    [19:48:52 WARN]:        at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
    [19:48:52 WARN]:        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    [19:48:52 WARN]:        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    [19:48:52 WARN]:        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
    [19:48:52 WARN]:        at io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:792)
    [19:48:52 WARN]:        at io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:475)
    [19:48:52 WARN]:        at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:378)
    [19:48:52 WARN]:        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
    [19:48:52 WARN]:        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
    [19:48:52 WARN]:        at java.base/java.lang.Thread.run(Thread.java:831)
    

    https://github.com/vaperion/blade/blob/210cc4edcebb2ce57ee1fd1de646758b37a43cf7/bukkit/src/main/java/me/vaperion/blade/bukkit/platform/ProtocolLibTabCompleter.java#L47

    opened by sadcenter 1
  • Unregister command suggestion

    Unregister command suggestion

    Hey so the unregister command system is really helpful but I was wondering if it would be possible to like have the unregister system unregister a command by its value? Like for example I have this command:

        @Command(value = {"day", "daytime"}, description = "Day Command.")
        public static void onDayCommand(@Sender Player player) {
    
            player.sendMessage("Time set to Day!");
        }
    

    And I could do like blade.unregisterValueCommand("day"); which would then unregister that command and its aliases. This would be very helpful as all my command classes contain multiple commands and I want to have the ability to unregister just 1 command instead of all the commands in that class.

    opened by GamerRealm 1
  • Higher version support?

    Higher version support?

    Does blade work with higher mc versions like 1.16? Asking this because I face many issues while using it on 1.16. Also If it doesn't, is it planned to be? Btw sorry for all the issues I've been making!

    opened by GamerRealm 1
  • Option to hide blade console warning

    Option to hide blade console warning

    Is there an option to hide the warning when a command takes a few milliseconds to execute?

    [Blade] Command 'command' took 431 milliseconds to execute!
    
    opened by GamerRealm 1
  • Failed to grab commandMap from the plugin manager.

    Failed to grab commandMap from the plugin manager.

    [21:05:30 WARN]: Failed to grab commandMap from the plugin manager. [21:05:30 WARN]: java.lang.NoSuchFieldException: modifiers [21:05:30 WARN]: at java.base/java.lang.Class.getDeclaredField(Class.java:2569) [21:05:30 WARN]: at me.vaperion.blade.command.container.impl.BukkitCommandContainer.(BukkitCommandContainer.java:46) [21:05:30 WARN]: at net.typecode.easyhc.guilds.GuildPlugin.onEnable(GuildPlugin.java:20) [21:05:30 WARN]: at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:263) [21:05:30 WARN]: at org.bukkit.plugin.java.JavaPluginLoader.enablePlugin(JavaPluginLoader.java:370) [21:05:30 WARN]: at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:500) [21:05:30 WARN]: at org.bukkit.craftbukkit.v1_16_R3.CraftServer.enablePlugin(CraftServer.java:518) [21:05:30 WARN]: at org.bukkit.craftbukkit.v1_16_R3.CraftServer.enablePlugins(CraftServer.java:432) [21:05:30 WARN]: at net.minecraft.server.v1_16_R3.MinecraftServer.loadWorld(MinecraftServer.java:596) [21:05:30 WARN]: at net.minecraft.server.v1_16_R3.DedicatedServer.init(DedicatedServer.java:309) [21:05:30 WARN]: at net.minecraft.server.v1_16_R3.MinecraftServer.w(MinecraftServer.java:1074) [21:05:30 WARN]: at net.minecraft.server.v1_16_R3.MinecraftServer.lambda$a$0(MinecraftServer.java:293) [21:05:30 WARN]: at java.base/java.lang.Thread.run(Thread.java:832)

    code main: @Getter public class GuildPlugin extends JavaPlugin {

    public static GuildPlugin INSTANCE = null;
    
    private GuildCache guildCache;
    
    @Override
    public void onEnable() {
        INSTANCE = this;
        
        this.guildCache = new GuildCache();
        Blade.of()
                .fallbackPrefix("guilds")
                .containerCreator(BukkitCommandContainer.CREATOR)
                .binding(new BukkitBindings())
                .build()
                .register(GuildCommand.class);
    }
    

    }

    Command class:

    @RequiredArgsConstructor public class GuildCommand {

    private final GuildCache guildCache;
    
    
    @Command(value = {"g", "gildia", "gildie", "guild"})
    public void help(@Sender CommandSender sender) {
        GuildConstants.GUILD_COMMANDS
                .forEach(s ->
                        sender.sendMessage(TextUtil.colored(s)));
    
    }
    

    }

    where is the problem?

    opened by megagigakox 1
Releases(3.0.1)
Owner
null
Team 5468's 2022 FRC robot code. This code is written in Java and is based off of WPILib's Java control system and utilizes a command based system

FRC 2022 Team 5468's 2022 FRC robot code. This code is written in Java and is based off of WPILib's Java control system and utilizes a command based s

null 4 Oct 4, 2022
Xerath - 🔪 AOP development framework implemented through *Annotation + ASM + Gradle Transform API* for Android🤖

简体中文 | English | Xerath Xerath 是一个通过 [自定义注解]+ASM + Gradle Transform API 实现的一套功能强大,方便开发,并且能够有效减少重复代码的Android Aop 框架。 旨在编译器进行全局性的修改,来完成一些诸如方法耗时统计,异常收集,拦

Pumpkin 325 Nov 22, 2022
A Mixin framework for Spigot/Bukkit that allows you to hook custom event anywhere

A Mixin framework for Spigot/Bukkit that allows you to hook custom event anywhere. Start coding your advanced plugins today!

DragonCommissions 14 Nov 30, 2022
This is a Bukkit-Event based AntiCheat created cause of a challenge.

XAC - XAntiCheat This is a Bukkit-Event based AntiCheat made out of a challenge with a friend of mine. Its not recommandable to use and also a bit exp

ytendx 3 Apr 6, 2022
💡极致性能的企业级Java服务器框架,RPC,游戏服务器框架,web应用服务器框架。(Extreme fast enterprise Java server framework, can be RPC, game server framework, web server framework.)

?? 为性能而生的万能服务器框架 ?? Ⅰ. zfoo简介 ?? 性能炸裂,天生异步,Actor设计思想,无锁化设计,基于Spring的MVC式用法的万能RPC框架 极致序列化,原生集成的目前二进制序列化和反序列化速度最快的 zfoo protocol 作为网络通讯协议 高可拓展性,单台服务器部署,

null 1k Jan 1, 2023
Spring JPA Many To Many example with Hibernate and Spring Boot CRUD Rest API - ManyToMany annotation

Spring JPA Many To Many example with Hibernate and Spring Boot CRUD Rest API - ManyToMany annotation

null 17 Dec 28, 2022
GodType is a very simple Bukkit plugin to allow the console (or a player) to chat as a defined name.

GodType GodType is a very simple Bukkit plugin to allow the console (or a player) to chat as a defined name. Config A config.yml file will be created

null 1 Dec 24, 2021
r/place replica in Minecraft (Bukkit/Spigot plugin)

MinePlace offers a completely new gaming experience in Minecraft. As a parody of r/place, a world-renowned reddit community project, this project offe

Northernside 4 Aug 19, 2022
A bukkit/spigot plugin to add custom music discs using the Simple Voice Chat API.

Custom Discs 1.19 A spigot/bukkit fork of henkelmax's Audio Player. Play custom music discs using the Simple Voice Chat API. (The voice chat mod is re

null 21 Dec 22, 2022
An evil RMI server that can launch an arbitrary command. May be useful for CVE-2021-44228

evil-rmi-server An evil RMI server that can launch an arbitrary command. May be useful for CVE-2021-44228 in a local privesc scenario Build ./gradlew

Adam Bertrand 12 Nov 9, 2022
PipelinR is a lightweight command processing pipeline ❍ ⇢ ❍ ⇢ ❍ for your Java awesome app.

PipelinR PipelinR is a lightweight command processing pipeline ❍ ⇢ ❍ ⇢ ❍ for your awesome Java app. PipelinR has been battle-proven on production, as

Eduards Sizovs 288 Jan 8, 2023
Java controlled pub command execution

OPIUM - Operate pub interface under machine. This package allows operating pub command with Java object only and return console context. Perquisites C

Project Will Pub 1 Jan 23, 2022
A command line utility

CUD A command line utility Create a command line utility that will compute the price for a pizza order. The list of pizzas will be provided as a text

jk 1 Feb 4, 2022
Server grouping /hub and /lobby command

BungeeHub Server grouping /hub and /lobby command This plugin only needs to be installed in BungeeCord/Waterfall. There is currently no development pl

Bing's Plugins 2 Dec 12, 2022
A mod that adds a /btellraw command which provides several enhancement over vanilla's tellraw.

Better Tellraw A mod that adds a /btellraw command which provides several enhancement over vanilla's tellraw. This mod only affects the server-side en

LX862 3 Jun 8, 2022
A plugin that open the GC command execution interface for third-party clients

gc-opencommand-plugin 中文 | English 一个为第三方客户端开放GC命令执行接口的插件 服务端安装 在 Release 下载 jar 放入 plugins 文件夹即可 控制台连接 首次启动时,会在 plugins 目录下生成一个 opencommand-plugin 目录

筱傑 222 Jan 1, 2023
A command-line tool to generate different types of noise as images.

noisegen A command-line tool to generate different types of noise as images. Usage Run one of the releases, either the JAR using java -jar noisegen-0.

Tommy Ettinger 6 Jul 21, 2022
A Minecraft Plugin For 1.8.8 - 1.19.2 For Giving A Player A Custom Item On Join Which Executes A Command On Right Click

PixelPlayz Custom Item A Minecraft Plugin For 1.8.8 - 1.19.2 For Giving A Player A Custom Item On Join Which Executes A Command On Right Click Install

null 2 Sep 9, 2022