Use bukkit command dispatcher (#132)
This commit is contained in:
parent
ddbb4265f2
commit
9ede1cf416
|
@ -1,5 +1,6 @@
|
||||||
package io.izzel.arclight.common.bridge.server;
|
package io.izzel.arclight.common.bridge.server;
|
||||||
|
|
||||||
|
import net.minecraft.command.Commands;
|
||||||
import org.bukkit.command.ConsoleCommandSender;
|
import org.bukkit.command.ConsoleCommandSender;
|
||||||
import org.bukkit.command.RemoteConsoleCommandSender;
|
import org.bukkit.command.RemoteConsoleCommandSender;
|
||||||
import org.bukkit.craftbukkit.v.CraftServer;
|
import org.bukkit.craftbukkit.v.CraftServer;
|
||||||
|
@ -21,4 +22,6 @@ public interface MinecraftServerBridge {
|
||||||
void bridge$drainQueuedTasks();
|
void bridge$drainQueuedTasks();
|
||||||
|
|
||||||
boolean bridge$hasStopped();
|
boolean bridge$hasStopped();
|
||||||
|
|
||||||
|
Commands bridge$getVanillaCommands();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package io.izzel.arclight.common.mixin.bukkit;
|
||||||
import io.izzel.arclight.common.bridge.bukkit.CraftServerBridge;
|
import io.izzel.arclight.common.bridge.bukkit.CraftServerBridge;
|
||||||
import io.izzel.arclight.common.bridge.world.WorldBridge;
|
import io.izzel.arclight.common.bridge.world.WorldBridge;
|
||||||
import jline.console.ConsoleReader;
|
import jline.console.ConsoleReader;
|
||||||
import net.minecraft.command.Commands;
|
|
||||||
import net.minecraft.server.dedicated.DedicatedPlayerList;
|
import net.minecraft.server.dedicated.DedicatedPlayerList;
|
||||||
import net.minecraft.server.dedicated.DedicatedServer;
|
import net.minecraft.server.dedicated.DedicatedServer;
|
||||||
import net.minecraft.server.management.PlayerList;
|
import net.minecraft.server.management.PlayerList;
|
||||||
|
@ -11,17 +10,12 @@ import net.minecraft.world.server.ServerWorld;
|
||||||
import net.minecraftforge.common.MinecraftForge;
|
import net.minecraftforge.common.MinecraftForge;
|
||||||
import net.minecraftforge.event.world.WorldEvent;
|
import net.minecraftforge.event.world.WorldEvent;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.command.Command;
|
|
||||||
import org.bukkit.craftbukkit.v.CraftServer;
|
import org.bukkit.craftbukkit.v.CraftServer;
|
||||||
import org.bukkit.craftbukkit.v.CraftWorld;
|
import org.bukkit.craftbukkit.v.CraftWorld;
|
||||||
import org.bukkit.craftbukkit.v.command.BukkitCommandWrapper;
|
|
||||||
import org.bukkit.craftbukkit.v.command.CraftCommandMap;
|
import org.bukkit.craftbukkit.v.command.CraftCommandMap;
|
||||||
import org.bukkit.craftbukkit.v.help.SimpleHelpMap;
|
import org.bukkit.craftbukkit.v.help.SimpleHelpMap;
|
||||||
import org.bukkit.craftbukkit.v.util.permissions.CraftDefaultPermissions;
|
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
import org.bukkit.plugin.PluginLoadOrder;
|
|
||||||
import org.bukkit.plugin.SimplePluginManager;
|
import org.bukkit.plugin.SimplePluginManager;
|
||||||
import org.bukkit.util.permissions.DefaultPermissions;
|
|
||||||
import org.spongepowered.asm.mixin.Final;
|
import org.spongepowered.asm.mixin.Final;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.Mutable;
|
import org.spongepowered.asm.mixin.Mutable;
|
||||||
|
@ -81,50 +75,6 @@ public abstract class CraftServerMixin implements CraftServerBridge {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @author IzzelAliz
|
|
||||||
* @reason
|
|
||||||
*/
|
|
||||||
@Overwrite(remap = false)
|
|
||||||
public void enablePlugins(PluginLoadOrder type) {
|
|
||||||
if (type == PluginLoadOrder.STARTUP) {
|
|
||||||
helpMap.clear();
|
|
||||||
helpMap.initializeGeneralTopics();
|
|
||||||
}
|
|
||||||
|
|
||||||
Plugin[] plugins = pluginManager.getPlugins();
|
|
||||||
|
|
||||||
for (Plugin plugin : plugins) {
|
|
||||||
if ((!plugin.isEnabled()) && (plugin.getDescription().getLoad() == type)) {
|
|
||||||
enablePlugin(plugin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == PluginLoadOrder.POSTWORLD) {
|
|
||||||
this.commandMap.setFallbackCommands();
|
|
||||||
this.commandMap.registerServerAliases();
|
|
||||||
DefaultPermissions.registerCorePermissions();
|
|
||||||
CraftDefaultPermissions.registerCorePermissions();
|
|
||||||
this.loadCustomPermissions();
|
|
||||||
this.helpMap.initializeCommands();
|
|
||||||
this.syncCommands();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author IzzelAliz
|
|
||||||
* @reason
|
|
||||||
*/
|
|
||||||
@Overwrite(remap = false)
|
|
||||||
public void syncCommands() {
|
|
||||||
Commands dispatcher = this.console.getCommandManager();
|
|
||||||
for (Map.Entry<String, Command> entry : this.commandMap.getKnownCommands().entrySet()) {
|
|
||||||
String label = entry.getKey();
|
|
||||||
Command command = entry.getValue();
|
|
||||||
new BukkitCommandWrapper((CraftServer) (Object) this, command).register(dispatcher.getDispatcher(), label);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bridge$setPlayerList(PlayerList playerList) {
|
public void bridge$setPlayerList(PlayerList playerList) {
|
||||||
this.playerList = (DedicatedPlayerList) playerList;
|
this.playerList = (DedicatedPlayerList) playerList;
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
package io.izzel.arclight.common.mixin.core.advancements;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.CommandDispatcher;
|
||||||
|
import io.izzel.arclight.common.bridge.server.MinecraftServerBridge;
|
||||||
|
import net.minecraft.advancements.FunctionManager;
|
||||||
|
import net.minecraft.command.CommandSource;
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
@Mixin(FunctionManager.class)
|
||||||
|
public class FunctionManagerMixin {
|
||||||
|
|
||||||
|
@Shadow @Final private MinecraftServer server;
|
||||||
|
|
||||||
|
@Inject(method = "getCommandDispatcher", cancellable = true, at = @At("HEAD"))
|
||||||
|
private void arclight$useVanillaDispatcher(CallbackInfoReturnable<CommandDispatcher<CommandSource>> cir) {
|
||||||
|
cir.setReturnValue(((MinecraftServerBridge) this.server).bridge$getVanillaCommands().getDispatcher());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,29 +1,66 @@
|
||||||
package io.izzel.arclight.common.mixin.core.command;
|
package io.izzel.arclight.common.mixin.core.command;
|
||||||
|
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.mojang.brigadier.CommandDispatcher;
|
||||||
import com.mojang.brigadier.tree.CommandNode;
|
import com.mojang.brigadier.tree.CommandNode;
|
||||||
import com.mojang.brigadier.tree.RootCommandNode;
|
import com.mojang.brigadier.tree.RootCommandNode;
|
||||||
import io.izzel.arclight.common.bridge.command.CommandNodeBridge;
|
import io.izzel.arclight.common.bridge.command.CommandNodeBridge;
|
||||||
import io.izzel.arclight.common.bridge.entity.player.ServerPlayerEntityBridge;
|
import io.izzel.arclight.common.bridge.entity.player.ServerPlayerEntityBridge;
|
||||||
|
import io.izzel.arclight.common.bridge.server.MinecraftServerBridge;
|
||||||
import net.minecraft.command.CommandSource;
|
import net.minecraft.command.CommandSource;
|
||||||
import net.minecraft.command.Commands;
|
import net.minecraft.command.Commands;
|
||||||
import net.minecraft.command.ISuggestionProvider;
|
import net.minecraft.command.ISuggestionProvider;
|
||||||
import net.minecraft.entity.player.ServerPlayerEntity;
|
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||||
|
import net.minecraft.network.play.server.SCommandListPacket;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.event.player.PlayerCommandSendEvent;
|
import org.bukkit.event.player.PlayerCommandSendEvent;
|
||||||
|
import org.spigotmc.SpigotConfig;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.Overwrite;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|
||||||
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
|
|
||||||
|
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@Mixin(Commands.class)
|
@Mixin(Commands.class)
|
||||||
public class CommandsMixin {
|
public abstract class CommandsMixin {
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
@Shadow public abstract int handleCommand(CommandSource source, String command);
|
||||||
|
@Shadow @Final private CommandDispatcher<CommandSource> dispatcher;
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
@Shadow
|
||||||
|
protected abstract void commandSourceNodesToSuggestionNodes(CommandNode<CommandSource> rootCommandSource, CommandNode<ISuggestionProvider> rootSuggestion, CommandSource source, Map<CommandNode<CommandSource>, CommandNode<ISuggestionProvider>> commandNodeToSuggestionNode);
|
||||||
|
|
||||||
|
public void arclight$constructor() {
|
||||||
|
this.dispatcher = new CommandDispatcher<>();
|
||||||
|
this.dispatcher.setConsumer((context, b, i) -> context.getSource().onCommandComplete(context, b, i));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int a(CommandSource source, String command, String label) {
|
||||||
|
return this.handleCommand(source, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author IzzelAliz
|
||||||
|
* @reason
|
||||||
|
*/
|
||||||
|
@Overwrite
|
||||||
|
public void send(ServerPlayerEntity player) {
|
||||||
|
if (SpigotConfig.tabComplete < 0) return;
|
||||||
|
Map<CommandNode<CommandSource>, CommandNode<ISuggestionProvider>> map = Maps.newIdentityHashMap();
|
||||||
|
|
||||||
|
RootCommandNode<ISuggestionProvider> vanillaRoot = new RootCommandNode<>();
|
||||||
|
Commands vanillaCommands = ((MinecraftServerBridge) player.server).bridge$getVanillaCommands();
|
||||||
|
map.put(vanillaCommands.getDispatcher().getRoot(), vanillaRoot);
|
||||||
|
this.commandSourceNodesToSuggestionNodes(vanillaCommands.getDispatcher().getRoot(), vanillaRoot, player.getCommandSource(), map);
|
||||||
|
|
||||||
|
RootCommandNode<ISuggestionProvider> node = new RootCommandNode<>();
|
||||||
|
map.put(this.dispatcher.getRoot(), node);
|
||||||
|
this.commandSourceNodesToSuggestionNodes(this.dispatcher.getRoot(), node, player.getCommandSource(), map);
|
||||||
|
|
||||||
@Inject(method = "send", locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "INVOKE", shift = At.Shift.AFTER, target = "Lnet/minecraft/command/Commands;commandSourceNodesToSuggestionNodes(Lcom/mojang/brigadier/tree/CommandNode;Lcom/mojang/brigadier/tree/CommandNode;Lnet/minecraft/command/CommandSource;Ljava/util/Map;)V"))
|
|
||||||
private void arclight$playerCommandSend(ServerPlayerEntity player, CallbackInfo ci, Map<CommandNode<CommandSource>, CommandNode<ISuggestionProvider>> map , RootCommandNode<ISuggestionProvider> node) {
|
|
||||||
LinkedHashSet<String> set = new LinkedHashSet<>();
|
LinkedHashSet<String> set = new LinkedHashSet<>();
|
||||||
for (CommandNode<ISuggestionProvider> child : node.getChildren()) {
|
for (CommandNode<ISuggestionProvider> child : node.getChildren()) {
|
||||||
set.add(child.getName());
|
set.add(child.getName());
|
||||||
|
@ -35,5 +72,6 @@ public class CommandsMixin {
|
||||||
((CommandNodeBridge) node).bridge$removeCommand(s);
|
((CommandNodeBridge) node).bridge$removeCommand(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
player.connection.sendPacket(new SCommandListPacket(node));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1057,7 +1057,7 @@ public abstract class ServerPlayNetHandlerMixin implements ServerPlayNetHandlerB
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
minecraftServer.getCommandManager().handleCommand(((CraftPlayer) event.getPlayer()).getHandle().getCommandSource(), event.getMessage());
|
this.server.dispatchCommand(event.getPlayer(), event.getMessage().substring(1));
|
||||||
} catch (CommandException ex) {
|
} catch (CommandException ex) {
|
||||||
player.sendMessage(ChatColor.RED + "An internal error occurred while attempting to perform this command");
|
player.sendMessage(ChatColor.RED + "An internal error occurred while attempting to perform this command");
|
||||||
java.util.logging.Logger.getLogger(ServerPlayNetHandler.class.getName()).log(Level.SEVERE, null, ex);
|
java.util.logging.Logger.getLogger(ServerPlayNetHandler.class.getName()).log(Level.SEVERE, null, ex);
|
||||||
|
|
|
@ -135,6 +135,7 @@ public abstract class MinecraftServerMixin extends RecursiveEventLoop<TickDelaye
|
||||||
public RemoteConsoleCommandSender remoteConsole;
|
public RemoteConsoleCommandSender remoteConsole;
|
||||||
public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<>();
|
public java.util.Queue<Runnable> processQueue = new java.util.concurrent.ConcurrentLinkedQueue<>();
|
||||||
public int autosavePeriod;
|
public int autosavePeriod;
|
||||||
|
public Commands vanillaCommandDispatcher;
|
||||||
private boolean hasStopped = false;
|
private boolean hasStopped = false;
|
||||||
private final Object stopLock = new Object();
|
private final Object stopLock = new Object();
|
||||||
|
|
||||||
|
@ -165,6 +166,7 @@ public abstract class MinecraftServerMixin extends RecursiveEventLoop<TickDelaye
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
this.datapackconfiguration = ArclightCaptures.getDatapackConfig();
|
this.datapackconfiguration = ArclightCaptures.getDatapackConfig();
|
||||||
|
this.vanillaCommandDispatcher = dataRegistries.getCommandManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -508,6 +510,11 @@ public abstract class MinecraftServerMixin extends RecursiveEventLoop<TickDelaye
|
||||||
return getBukkitSender(wrapper);
|
return getBukkitSender(wrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Commands bridge$getVanillaCommands() {
|
||||||
|
return this.vanillaCommandDispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isDebugging() {
|
public boolean isDebugging() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import net.minecraft.command.CommandSource;
|
||||||
import net.minecraft.command.Commands;
|
import net.minecraft.command.Commands;
|
||||||
import net.minecraft.network.rcon.RConConsoleSource;
|
import net.minecraft.network.rcon.RConConsoleSource;
|
||||||
import net.minecraft.server.dedicated.DedicatedServer;
|
import net.minecraft.server.dedicated.DedicatedServer;
|
||||||
|
import net.minecraft.server.dedicated.PendingCommand;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.craftbukkit.v.CraftServer;
|
import org.bukkit.craftbukkit.v.CraftServer;
|
||||||
import org.bukkit.craftbukkit.v.command.CraftRemoteConsoleCommandSender;
|
import org.bukkit.craftbukkit.v.command.CraftRemoteConsoleCommandSender;
|
||||||
|
@ -48,10 +49,13 @@ public abstract class DedicatedServerMixin extends MinecraftServerMixin {
|
||||||
|
|
||||||
@Redirect(method = "executePendingCommands", at = @At(value = "INVOKE", target = "Lnet/minecraft/command/Commands;handleCommand(Lnet/minecraft/command/CommandSource;Ljava/lang/String;)I"))
|
@Redirect(method = "executePendingCommands", at = @At(value = "INVOKE", target = "Lnet/minecraft/command/Commands;handleCommand(Lnet/minecraft/command/CommandSource;Ljava/lang/String;)I"))
|
||||||
private int arclight$serverCommandEvent(Commands commands, CommandSource source, String command) {
|
private int arclight$serverCommandEvent(Commands commands, CommandSource source, String command) {
|
||||||
|
if (command.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
ServerCommandEvent event = new ServerCommandEvent(console, command);
|
ServerCommandEvent event = new ServerCommandEvent(console, command);
|
||||||
Bukkit.getPluginManager().callEvent(event);
|
Bukkit.getPluginManager().callEvent(event);
|
||||||
if (!event.isCancelled()) {
|
if (!event.isCancelled()) {
|
||||||
return commands.handleCommand(source, event.getCommand());
|
server.dispatchCommand(console, event.getCommand());
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -69,12 +73,7 @@ public abstract class DedicatedServerMixin extends MinecraftServerMixin {
|
||||||
if (event.isCancelled()) {
|
if (event.isCancelled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ServerCommandEvent event2 = new ServerCommandEvent(console, event.getCommand());
|
this.server.dispatchServerCommand(remoteConsole, new PendingCommand(event.getCommand(), this.rconConsoleSource.getCommandSource()));
|
||||||
Bukkit.getPluginManager().callEvent(event2);
|
|
||||||
if (event2.isCancelled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.getCommandManager().handleCommand(this.rconConsoleSource.getCommandSource(), event2.getCommand());
|
|
||||||
});
|
});
|
||||||
return this.rconConsoleSource.getLogContents();
|
return this.rconConsoleSource.getLogContents();
|
||||||
}
|
}
|
||||||
|
|
|
@ -233,12 +233,12 @@ public abstract class ServerWorldMixin extends WorldMixin implements ServerWorld
|
||||||
|
|
||||||
@Inject(method = "save", at = @At("RETURN"))
|
@Inject(method = "save", at = @At("RETURN"))
|
||||||
private void arclight$saveLevelDat(IProgressUpdate progress, boolean flush, boolean skipSave, CallbackInfo ci) {
|
private void arclight$saveLevelDat(IProgressUpdate progress, boolean flush, boolean skipSave, CallbackInfo ci) {
|
||||||
if (this.field_241103_E_ instanceof DerivedWorldInfo) {
|
if (this.field_241103_E_ instanceof ServerWorldInfo) {
|
||||||
return;
|
ServerWorldInfo worldInfo = (ServerWorldInfo) this.field_241103_E_;
|
||||||
|
worldInfo.setWorldBorderSerializer(this.getWorldBorder().getSerializer());
|
||||||
|
worldInfo.setCustomBossEventData(this.shadow$getServer().getCustomBossEvents().write());
|
||||||
|
this.convertable.saveLevel(this.shadow$getServer().field_240767_f_, worldInfo, this.shadow$getServer().getPlayerList().getHostPlayerData());
|
||||||
}
|
}
|
||||||
this.$$worldDataServer.setWorldBorderSerializer(this.getWorldBorder().getSerializer());
|
|
||||||
this.$$worldDataServer.setCustomBossEventData(this.shadow$getServer().getCustomBossEvents().write());
|
|
||||||
this.convertable.saveLevel(this.shadow$getServer().field_240767_f_, this.$$worldDataServer, this.shadow$getServer().getPlayerList().getHostPlayerData());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Inject(method = "onChunkUnloading", at = @At("HEAD"))
|
@Inject(method = "onChunkUnloading", at = @At("HEAD"))
|
||||||
|
|
|
@ -132,6 +132,7 @@ public class ArclightMixinPlugin implements IMixinConfigPlugin {
|
||||||
.add("net.minecraft.network.play.client.CCloseWindowPacket")
|
.add("net.minecraft.network.play.client.CCloseWindowPacket")
|
||||||
.add("net.minecraft.world.dimension.DimensionType")
|
.add("net.minecraft.world.dimension.DimensionType")
|
||||||
.add("net.minecraft.util.text.Color")
|
.add("net.minecraft.util.text.Color")
|
||||||
|
.add("net.minecraft.command.Commands")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
"mixinPriority": 500,
|
"mixinPriority": 500,
|
||||||
"mixins": [
|
"mixins": [
|
||||||
"advancements.AdvancementMixin",
|
"advancements.AdvancementMixin",
|
||||||
|
"advancements.FunctionManagerMixin",
|
||||||
"advancements.PlayerAdvancementsMixin",
|
"advancements.PlayerAdvancementsMixin",
|
||||||
"block.AbstractBlock_AbstractBlockStateMixin",
|
"block.AbstractBlock_AbstractBlockStateMixin",
|
||||||
"block.AbstractBlockMixin",
|
"block.AbstractBlockMixin",
|
||||||
|
|
Loading…
Reference in New Issue
Block a user