package net.minecraft.advancements; import com.google.common.collect.Maps; import com.google.common.io.Files; import java.io.File; import java.nio.charset.StandardCharsets; import java.util.ArrayDeque; import java.util.Map; import javax.annotation.Nullable; import net.minecraft.command.FunctionObject; import net.minecraft.command.ICommandManager; import net.minecraft.command.ICommandSender; import net.minecraft.server.MinecraftServer; import net.minecraft.util.ITickable; import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class FunctionManager implements ITickable { private static final Logger LOGGER = LogManager.getLogger(); private final File functionDir; private final MinecraftServer server; private final Map functions = Maps.newHashMap(); private String currentGameLoopFunctionId = "-"; private FunctionObject gameLoopFunction; private final ArrayDeque commandQueue = new ArrayDeque(); private boolean isExecuting = false; private final ICommandSender gameLoopFunctionSender = new ICommandSender() { /** * Get the name of this object. For players this returns their username */ public String getName() { return FunctionManager.this.currentGameLoopFunctionId; } /** * Returns {@code true} if the CommandSender is allowed to execute the command, {@code false} if not */ public boolean canUseCommand(int permLevel, String commandName) { return permLevel <= 2; } /** * Get the world, if available. {@code null} is not allowed! If you are not an entity in the world, * return the overworld */ public World getEntityWorld() { return FunctionManager.this.server.worlds[0]; } /** * Get the Minecraft server instance */ public MinecraftServer getServer() { return FunctionManager.this.server; } }; public FunctionManager(@Nullable File functionDirIn, MinecraftServer serverIn) { this.functionDir = functionDirIn; this.server = serverIn; this.reload(); } @Nullable public FunctionObject getFunction(ResourceLocation id) { return this.functions.get(id); } public ICommandManager getCommandManager() { return this.server.getCommandManager(); } public int getMaxCommandChainLength() { return this.server.worlds[0].getGameRules().getInt("maxCommandChainLength"); } public Map getFunctions() { return this.functions; } /** * Like the old updateEntity(), except more generic. */ public void update() { String s = this.server.worlds[0].getGameRules().getString("gameLoopFunction"); if (!s.equals(this.currentGameLoopFunctionId)) { this.currentGameLoopFunctionId = s; this.gameLoopFunction = this.getFunction(new ResourceLocation(s)); } if (this.gameLoopFunction != null) { this.execute(this.gameLoopFunction, this.gameLoopFunctionSender); } } public int execute(FunctionObject function, ICommandSender sender) { int i = this.getMaxCommandChainLength(); if (this.isExecuting) { if (this.commandQueue.size() < i) { this.commandQueue.addFirst(new FunctionManager.QueuedCommand(this, sender, new FunctionObject.FunctionEntry(function))); } return 0; } else { int l; try { this.isExecuting = true; int j = 0; FunctionObject.Entry[] afunctionobject$entry = function.getEntries(); for (int k = afunctionobject$entry.length - 1; k >= 0; --k) { this.commandQueue.push(new FunctionManager.QueuedCommand(this, sender, afunctionobject$entry[k])); } while (true) { if (this.commandQueue.isEmpty()) { l = j; return l; } (this.commandQueue.removeFirst()).execute(this.commandQueue, i); ++j; if (j >= i) { break; } } l = j; } finally { this.commandQueue.clear(); this.isExecuting = false; } return l; } } public void reload() { this.functions.clear(); this.gameLoopFunction = null; this.currentGameLoopFunctionId = "-"; this.loadFunctions(); } private void loadFunctions() { if (this.functionDir != null) { this.functionDir.mkdirs(); for (File file1 : FileUtils.listFiles(this.functionDir, new String[] {"mcfunction"}, true)) { String s = FilenameUtils.removeExtension(this.functionDir.toURI().relativize(file1.toURI()).toString()); String[] astring = s.split("/", 2); if (astring.length == 2) { ResourceLocation resourcelocation = new ResourceLocation(astring[0], astring[1]); try { this.functions.put(resourcelocation, FunctionObject.create(this, Files.readLines(file1, StandardCharsets.UTF_8))); } catch (Throwable throwable) { LOGGER.error("Couldn't read custom function " + resourcelocation + " from " + file1, throwable); } } } if (!this.functions.isEmpty()) { LOGGER.info("Loaded " + this.functions.size() + " custom command functions"); } } } public static class QueuedCommand { private final FunctionManager functionManager; private final ICommandSender sender; private final FunctionObject.Entry entry; public QueuedCommand(FunctionManager functionManagerIn, ICommandSender senderIn, FunctionObject.Entry entryIn) { this.functionManager = functionManagerIn; this.sender = senderIn; this.entry = entryIn; } public void execute(ArrayDeque commandQueue, int maxCommandChainLength) { this.entry.execute(this.functionManager, this.sender, commandQueue, maxCommandChainLength); } public String toString() { return this.entry.toString(); } } }