c2e4e91b1b
## **Current API** The current world generation API is very old and limited when you want to make more complex world generation. Resulting in some hard to fix bugs such as that you cannot modify blocks outside the chunk in the BlockPopulator (which should and was per the docs possible), or strange behavior such as SPIGOT-5880. ## **New API** With the new API, the generation is more separate in multiple methods and is more in line with Vanilla chunk generation. The new API is designed to as future proof as possible. If for example a new generation step is added it can easily also be added as a step in API by simply creating the method for it. On the other side if a generation step gets removed, the method can easily be called after another, which is the case with surface and bedrock. The new API and changes are also fully backwards compatible with old chunk generators. ### **Changes in the new api** **Extra generation steps:** Noise, surface, bedrock and caves are added as steps. With those generation steps three extra methods for Vanilla generation are also added. Those new methods provide the ChunkData instead of returning one. The reason for this is, that the ChunkData is now backed by a ChunkAccess. With this, each step has the information of the step before and the Vanilla information (if chosen by setting a 'should' method to true). The old method is deprecated. **New class BiomeProvider** The BiomeProvider acts as Biome source and wrapper for the NMS class WorldChunkManager. With this the underlying Vanilla ChunkGeneration knows which Biome to use for the structure and decoration generation. (Fixes: SPIGOT-5880). Although the List of Biomes which is required in BiomeProvider, is currently not much in use in Vanilla, I decided to add it to future proof the API when it may be required in later versions of Minecraft. The BiomeProvider is also separated from the ChunkGenerator for plugins which only want to change the biome map, such as single Biome worlds or if some biomes should be more present than others. **Deprecated isParallelCapable** Mojang has and is pushing to a more multi threaded chunk generation. This should also be the case for custom chunk generators. This is why the new API only supports multi threaded generation. This does not affect the old API, which is still checking this. **Base height method added** This method was added to also bring the Minecraft generator and Bukkit generator more in line. With this it is possible to return the max height of a location (before decorations). This is useful to let most structures know were to place them. This fixes SPIGOT-5567. (This fixes not all structures placement, desert pyramids for example are still way up at y-level 64, This however is more a vanilla bug and should be fixed at Mojangs end). **WorldInfo Class** The World object was swapped for a WorldInfo object. This is because many methods of the World object won't work during world generation and would mostly likely result in a deadlock. It contains any information a plugin should need to identify the world. **BlockPopulator Changes** Instead of directly manipulating a chunk, changes are now made to a new class LimitedRegion, this class provides methods to populated the chunk and its surrounding area. The wrapping is done so that the population can be moved into the place where Minecraft generates decorations. Where there is no chunk to access yet. By moving it into this place the generation is now async and the surrounding area of the chunk can also be used. For common methods between the World and LimitedRegion a RegionAccessor was added. By: DerFrZocker <derrieple@gmail.com>
2180 lines
83 KiB
Java
2180 lines
83 KiB
Java
package org.bukkit.craftbukkit;
|
|
|
|
import com.google.common.base.Charsets;
|
|
import com.google.common.base.Function;
|
|
import com.google.common.base.Preconditions;
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.google.common.collect.ImmutableSet;
|
|
import com.google.common.collect.Iterators;
|
|
import com.google.common.collect.Lists;
|
|
import com.google.common.collect.MapMaker;
|
|
import com.mojang.authlib.GameProfile;
|
|
import com.mojang.brigadier.StringReader;
|
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
|
import com.mojang.brigadier.tree.CommandNode;
|
|
import com.mojang.brigadier.tree.LiteralCommandNode;
|
|
import com.mojang.serialization.DynamicOps;
|
|
import com.mojang.serialization.Lifecycle;
|
|
import io.netty.buffer.ByteBuf;
|
|
import io.netty.buffer.ByteBufOutputStream;
|
|
import io.netty.buffer.Unpooled;
|
|
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
|
|
import java.awt.image.BufferedImage;
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.IOException;
|
|
import java.io.InputStreamReader;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Base64;
|
|
import java.util.Collections;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.LinkedHashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.Optional;
|
|
import java.util.Properties;
|
|
import java.util.Random;
|
|
import java.util.Set;
|
|
import java.util.UUID;
|
|
import java.util.function.Consumer;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
import javax.imageio.ImageIO;
|
|
import jline.console.ConsoleReader;
|
|
import net.minecraft.advancements.Advancement;
|
|
import net.minecraft.commands.CommandDispatcher;
|
|
import net.minecraft.commands.CommandListenerWrapper;
|
|
import net.minecraft.commands.arguments.ArgumentEntity;
|
|
import net.minecraft.core.BlockPosition;
|
|
import net.minecraft.core.IRegistry;
|
|
import net.minecraft.core.RegistryMaterials;
|
|
import net.minecraft.nbt.DynamicOpsNBT;
|
|
import net.minecraft.nbt.NBTBase;
|
|
import net.minecraft.resources.MinecraftKey;
|
|
import net.minecraft.resources.RegistryReadOps;
|
|
import net.minecraft.resources.ResourceKey;
|
|
import net.minecraft.server.MinecraftServer;
|
|
import net.minecraft.server.ServerCommand;
|
|
import net.minecraft.server.bossevents.BossBattleCustom;
|
|
import net.minecraft.server.commands.CommandReload;
|
|
import net.minecraft.server.dedicated.DedicatedPlayerList;
|
|
import net.minecraft.server.dedicated.DedicatedServer;
|
|
import net.minecraft.server.dedicated.DedicatedServerProperties;
|
|
import net.minecraft.server.dedicated.DedicatedServerSettings;
|
|
import net.minecraft.server.level.EntityPlayer;
|
|
import net.minecraft.server.level.TicketType;
|
|
import net.minecraft.server.level.WorldServer;
|
|
import net.minecraft.server.players.JsonListEntry;
|
|
import net.minecraft.server.players.PlayerList;
|
|
import net.minecraft.tags.Tags;
|
|
import net.minecraft.tags.TagsBlock;
|
|
import net.minecraft.tags.TagsFluid;
|
|
import net.minecraft.tags.TagsItem;
|
|
import net.minecraft.util.datafix.DataConverterRegistry;
|
|
import net.minecraft.world.EnumDifficulty;
|
|
import net.minecraft.world.effect.MobEffects;
|
|
import net.minecraft.world.entity.ai.village.VillageSiege;
|
|
import net.minecraft.world.entity.npc.MobSpawnerCat;
|
|
import net.minecraft.world.entity.npc.MobSpawnerTrader;
|
|
import net.minecraft.world.entity.player.EntityHuman;
|
|
import net.minecraft.world.inventory.Container;
|
|
import net.minecraft.world.inventory.ContainerWorkbench;
|
|
import net.minecraft.world.inventory.InventoryCraftResult;
|
|
import net.minecraft.world.inventory.InventoryCrafting;
|
|
import net.minecraft.world.item.Item;
|
|
import net.minecraft.world.item.ItemWorldMap;
|
|
import net.minecraft.world.item.crafting.IRecipe;
|
|
import net.minecraft.world.item.crafting.RecipeCrafting;
|
|
import net.minecraft.world.item.crafting.RecipeRepair;
|
|
import net.minecraft.world.item.crafting.Recipes;
|
|
import net.minecraft.world.item.enchantment.Enchantments;
|
|
import net.minecraft.world.level.EnumGamemode;
|
|
import net.minecraft.world.level.GameRules;
|
|
import net.minecraft.world.level.MobSpawner;
|
|
import net.minecraft.world.level.WorldSettings;
|
|
import net.minecraft.world.level.biome.BiomeManager;
|
|
import net.minecraft.world.level.biome.WorldChunkManager;
|
|
import net.minecraft.world.level.block.Block;
|
|
import net.minecraft.world.level.dimension.DimensionManager;
|
|
import net.minecraft.world.level.dimension.WorldDimension;
|
|
import net.minecraft.world.level.levelgen.ChunkGeneratorAbstract;
|
|
import net.minecraft.world.level.levelgen.GeneratorSettings;
|
|
import net.minecraft.world.level.levelgen.MobSpawnerPatrol;
|
|
import net.minecraft.world.level.levelgen.MobSpawnerPhantom;
|
|
import net.minecraft.world.level.material.FluidType;
|
|
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
|
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
|
import net.minecraft.world.level.storage.Convertable;
|
|
import net.minecraft.world.level.storage.WorldDataServer;
|
|
import net.minecraft.world.level.storage.WorldNBTStorage;
|
|
import net.minecraft.world.level.storage.loot.LootTableRegistry;
|
|
import net.minecraft.world.phys.Vec3D;
|
|
import org.apache.commons.lang.Validate;
|
|
import org.bukkit.BanList;
|
|
import org.bukkit.Bukkit;
|
|
import org.bukkit.ChatColor;
|
|
import org.bukkit.GameMode;
|
|
import org.bukkit.Keyed;
|
|
import org.bukkit.Location;
|
|
import org.bukkit.NamespacedKey;
|
|
import org.bukkit.OfflinePlayer;
|
|
import org.bukkit.Server;
|
|
import org.bukkit.StructureType;
|
|
import org.bukkit.UnsafeValues;
|
|
import org.bukkit.Warning.WarningState;
|
|
import org.bukkit.World;
|
|
import org.bukkit.World.Environment;
|
|
import org.bukkit.WorldCreator;
|
|
import org.bukkit.block.data.BlockData;
|
|
import org.bukkit.boss.BarColor;
|
|
import org.bukkit.boss.BarFlag;
|
|
import org.bukkit.boss.BarStyle;
|
|
import org.bukkit.boss.BossBar;
|
|
import org.bukkit.boss.KeyedBossBar;
|
|
import org.bukkit.command.Command;
|
|
import org.bukkit.command.CommandException;
|
|
import org.bukkit.command.CommandSender;
|
|
import org.bukkit.command.ConsoleCommandSender;
|
|
import org.bukkit.command.PluginCommand;
|
|
import org.bukkit.command.SimpleCommandMap;
|
|
import org.bukkit.configuration.ConfigurationSection;
|
|
import org.bukkit.configuration.file.YamlConfiguration;
|
|
import org.bukkit.configuration.serialization.ConfigurationSerialization;
|
|
import org.bukkit.conversations.Conversable;
|
|
import org.bukkit.craftbukkit.block.data.CraftBlockData;
|
|
import org.bukkit.craftbukkit.boss.CraftBossBar;
|
|
import org.bukkit.craftbukkit.boss.CraftKeyedBossbar;
|
|
import org.bukkit.craftbukkit.command.BukkitCommandWrapper;
|
|
import org.bukkit.craftbukkit.command.CraftCommandMap;
|
|
import org.bukkit.craftbukkit.command.VanillaCommandWrapper;
|
|
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
|
import org.bukkit.craftbukkit.event.CraftEventFactory;
|
|
import org.bukkit.craftbukkit.generator.CraftWorldInfo;
|
|
import org.bukkit.craftbukkit.generator.CustomWorldChunkManager;
|
|
import org.bukkit.craftbukkit.generator.OldCraftChunkData;
|
|
import org.bukkit.craftbukkit.help.SimpleHelpMap;
|
|
import org.bukkit.craftbukkit.inventory.CraftBlastingRecipe;
|
|
import org.bukkit.craftbukkit.inventory.CraftCampfireRecipe;
|
|
import org.bukkit.craftbukkit.inventory.CraftFurnaceRecipe;
|
|
import org.bukkit.craftbukkit.inventory.CraftItemFactory;
|
|
import org.bukkit.craftbukkit.inventory.CraftItemStack;
|
|
import org.bukkit.craftbukkit.inventory.CraftMerchantCustom;
|
|
import org.bukkit.craftbukkit.inventory.CraftRecipe;
|
|
import org.bukkit.craftbukkit.inventory.CraftShapedRecipe;
|
|
import org.bukkit.craftbukkit.inventory.CraftShapelessRecipe;
|
|
import org.bukkit.craftbukkit.inventory.CraftSmithingRecipe;
|
|
import org.bukkit.craftbukkit.inventory.CraftSmokingRecipe;
|
|
import org.bukkit.craftbukkit.inventory.CraftStonecuttingRecipe;
|
|
import org.bukkit.craftbukkit.inventory.RecipeIterator;
|
|
import org.bukkit.craftbukkit.inventory.util.CraftInventoryCreator;
|
|
import org.bukkit.craftbukkit.map.CraftMapView;
|
|
import org.bukkit.craftbukkit.metadata.EntityMetadataStore;
|
|
import org.bukkit.craftbukkit.metadata.PlayerMetadataStore;
|
|
import org.bukkit.craftbukkit.metadata.WorldMetadataStore;
|
|
import org.bukkit.craftbukkit.potion.CraftPotionBrewer;
|
|
import org.bukkit.craftbukkit.scheduler.CraftScheduler;
|
|
import org.bukkit.craftbukkit.scoreboard.CraftScoreboardManager;
|
|
import org.bukkit.craftbukkit.tag.CraftBlockTag;
|
|
import org.bukkit.craftbukkit.tag.CraftFluidTag;
|
|
import org.bukkit.craftbukkit.tag.CraftItemTag;
|
|
import org.bukkit.craftbukkit.util.CraftChatMessage;
|
|
import org.bukkit.craftbukkit.util.CraftIconCache;
|
|
import org.bukkit.craftbukkit.util.CraftMagicNumbers;
|
|
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
|
|
import org.bukkit.craftbukkit.util.DatFileFilter;
|
|
import org.bukkit.craftbukkit.util.Versioning;
|
|
import org.bukkit.craftbukkit.util.permissions.CraftDefaultPermissions;
|
|
import org.bukkit.entity.Entity;
|
|
import org.bukkit.entity.Player;
|
|
import org.bukkit.event.inventory.InventoryType;
|
|
import org.bukkit.event.player.PlayerChatTabCompleteEvent;
|
|
import org.bukkit.event.server.BroadcastMessageEvent;
|
|
import org.bukkit.event.server.ServerLoadEvent;
|
|
import org.bukkit.event.server.TabCompleteEvent;
|
|
import org.bukkit.event.world.WorldLoadEvent;
|
|
import org.bukkit.event.world.WorldUnloadEvent;
|
|
import org.bukkit.generator.BiomeProvider;
|
|
import org.bukkit.generator.ChunkGenerator;
|
|
import org.bukkit.generator.WorldInfo;
|
|
import org.bukkit.help.HelpMap;
|
|
import org.bukkit.inventory.BlastingRecipe;
|
|
import org.bukkit.inventory.CampfireRecipe;
|
|
import org.bukkit.inventory.ComplexRecipe;
|
|
import org.bukkit.inventory.FurnaceRecipe;
|
|
import org.bukkit.inventory.Inventory;
|
|
import org.bukkit.inventory.InventoryHolder;
|
|
import org.bukkit.inventory.InventoryView;
|
|
import org.bukkit.inventory.ItemStack;
|
|
import org.bukkit.inventory.Merchant;
|
|
import org.bukkit.inventory.Recipe;
|
|
import org.bukkit.inventory.ShapedRecipe;
|
|
import org.bukkit.inventory.ShapelessRecipe;
|
|
import org.bukkit.inventory.SmithingRecipe;
|
|
import org.bukkit.inventory.SmokingRecipe;
|
|
import org.bukkit.inventory.StonecuttingRecipe;
|
|
import org.bukkit.loot.LootTable;
|
|
import org.bukkit.map.MapView;
|
|
import org.bukkit.permissions.Permissible;
|
|
import org.bukkit.permissions.Permission;
|
|
import org.bukkit.plugin.Plugin;
|
|
import org.bukkit.plugin.PluginLoadOrder;
|
|
import org.bukkit.plugin.PluginManager;
|
|
import org.bukkit.plugin.ServicesManager;
|
|
import org.bukkit.plugin.SimplePluginManager;
|
|
import org.bukkit.plugin.SimpleServicesManager;
|
|
import org.bukkit.plugin.java.JavaPluginLoader;
|
|
import org.bukkit.plugin.messaging.Messenger;
|
|
import org.bukkit.plugin.messaging.StandardMessenger;
|
|
import org.bukkit.potion.Potion;
|
|
import org.bukkit.potion.PotionEffectType;
|
|
import org.bukkit.scheduler.BukkitWorker;
|
|
import org.bukkit.util.StringUtil;
|
|
import org.bukkit.util.permissions.DefaultPermissions;
|
|
import org.yaml.snakeyaml.Yaml;
|
|
import org.yaml.snakeyaml.constructor.SafeConstructor;
|
|
import org.yaml.snakeyaml.error.MarkedYAMLException;
|
|
|
|
public final class CraftServer implements Server {
|
|
private final String serverName = "CraftBukkit";
|
|
private final String serverVersion;
|
|
private final String bukkitVersion = Versioning.getBukkitVersion();
|
|
private final Logger logger = Logger.getLogger("Minecraft");
|
|
private final ServicesManager servicesManager = new SimpleServicesManager();
|
|
private final CraftScheduler scheduler = new CraftScheduler();
|
|
private final CraftCommandMap commandMap = new CraftCommandMap(this);
|
|
private final SimpleHelpMap helpMap = new SimpleHelpMap(this);
|
|
private final StandardMessenger messenger = new StandardMessenger();
|
|
private final SimplePluginManager pluginManager = new SimplePluginManager(this, commandMap);
|
|
protected final DedicatedServer console;
|
|
protected final DedicatedPlayerList playerList;
|
|
private final Map<String, World> worlds = new LinkedHashMap<String, World>();
|
|
private YamlConfiguration configuration;
|
|
private YamlConfiguration commandsConfiguration;
|
|
private final Yaml yaml = new Yaml(new SafeConstructor());
|
|
private final Map<UUID, OfflinePlayer> offlinePlayers = new MapMaker().weakValues().makeMap();
|
|
private final EntityMetadataStore entityMetadata = new EntityMetadataStore();
|
|
private final PlayerMetadataStore playerMetadata = new PlayerMetadataStore();
|
|
private final WorldMetadataStore worldMetadata = new WorldMetadataStore();
|
|
private int monsterSpawn = -1;
|
|
private int animalSpawn = -1;
|
|
private int waterAnimalSpawn = -1;
|
|
private int waterAmbientSpawn = -1;
|
|
private int ambientSpawn = -1;
|
|
private File container;
|
|
private WarningState warningState = WarningState.DEFAULT;
|
|
public String minimumAPI;
|
|
public CraftScoreboardManager scoreboardManager;
|
|
public boolean playerCommandState;
|
|
private boolean printSaveWarning;
|
|
private CraftIconCache icon;
|
|
private boolean overrideAllCommandBlockCommands = false;
|
|
public boolean ignoreVanillaPermissions = false;
|
|
private final List<CraftPlayer> playerView;
|
|
public int reloadCount;
|
|
|
|
static {
|
|
ConfigurationSerialization.registerClass(CraftOfflinePlayer.class);
|
|
CraftItemFactory.instance();
|
|
}
|
|
|
|
public CraftServer(DedicatedServer console, PlayerList playerList) {
|
|
this.console = console;
|
|
this.playerList = (DedicatedPlayerList) playerList;
|
|
this.playerView = Collections.unmodifiableList(Lists.transform(playerList.players, new Function<EntityPlayer, CraftPlayer>() {
|
|
@Override
|
|
public CraftPlayer apply(EntityPlayer player) {
|
|
return player.getBukkitEntity();
|
|
}
|
|
}));
|
|
this.serverVersion = CraftServer.class.getPackage().getImplementationVersion();
|
|
|
|
Bukkit.setServer(this);
|
|
|
|
// Register all the Enchantments and PotionTypes now so we can stop new registration immediately after
|
|
Enchantments.SHARPNESS.getClass();
|
|
org.bukkit.enchantments.Enchantment.stopAcceptingRegistrations();
|
|
|
|
Potion.setPotionBrewer(new CraftPotionBrewer());
|
|
MobEffects.BLINDNESS.getClass();
|
|
PotionEffectType.stopAcceptingRegistrations();
|
|
// Ugly hack :(
|
|
|
|
if (!Main.useConsole) {
|
|
getLogger().info("Console input is disabled due to --noconsole command argument");
|
|
}
|
|
|
|
configuration = YamlConfiguration.loadConfiguration(getConfigFile());
|
|
configuration.options().copyDefaults(true);
|
|
configuration.setDefaults(YamlConfiguration.loadConfiguration(new InputStreamReader(getClass().getClassLoader().getResourceAsStream("configurations/bukkit.yml"), Charsets.UTF_8)));
|
|
ConfigurationSection legacyAlias = null;
|
|
if (!configuration.isString("aliases")) {
|
|
legacyAlias = configuration.getConfigurationSection("aliases");
|
|
configuration.set("aliases", "now-in-commands.yml");
|
|
}
|
|
saveConfig();
|
|
if (getCommandsConfigFile().isFile()) {
|
|
legacyAlias = null;
|
|
}
|
|
commandsConfiguration = YamlConfiguration.loadConfiguration(getCommandsConfigFile());
|
|
commandsConfiguration.options().copyDefaults(true);
|
|
commandsConfiguration.setDefaults(YamlConfiguration.loadConfiguration(new InputStreamReader(getClass().getClassLoader().getResourceAsStream("configurations/commands.yml"), Charsets.UTF_8)));
|
|
saveCommandsConfig();
|
|
|
|
// Migrate aliases from old file and add previously implicit $1- to pass all arguments
|
|
if (legacyAlias != null) {
|
|
ConfigurationSection aliases = commandsConfiguration.createSection("aliases");
|
|
for (String key : legacyAlias.getKeys(false)) {
|
|
ArrayList<String> commands = new ArrayList<String>();
|
|
|
|
if (legacyAlias.isList(key)) {
|
|
for (String command : legacyAlias.getStringList(key)) {
|
|
commands.add(command + " $1-");
|
|
}
|
|
} else {
|
|
commands.add(legacyAlias.getString(key) + " $1-");
|
|
}
|
|
|
|
aliases.set(key, commands);
|
|
}
|
|
}
|
|
|
|
saveCommandsConfig();
|
|
overrideAllCommandBlockCommands = commandsConfiguration.getStringList("command-block-overrides").contains("*");
|
|
ignoreVanillaPermissions = commandsConfiguration.getBoolean("ignore-vanilla-permissions");
|
|
pluginManager.useTimings(configuration.getBoolean("settings.plugin-profiling"));
|
|
monsterSpawn = configuration.getInt("spawn-limits.monsters");
|
|
animalSpawn = configuration.getInt("spawn-limits.animals");
|
|
waterAnimalSpawn = configuration.getInt("spawn-limits.water-animals");
|
|
waterAmbientSpawn = configuration.getInt("spawn-limits.water-ambient");
|
|
ambientSpawn = configuration.getInt("spawn-limits.ambient");
|
|
console.autosavePeriod = configuration.getInt("ticks-per.autosave");
|
|
warningState = WarningState.value(configuration.getString("settings.deprecated-verbose"));
|
|
TicketType.PLUGIN.timeout = configuration.getInt("chunk-gc.period-in-ticks");
|
|
minimumAPI = configuration.getString("settings.minimum-api");
|
|
loadIcon();
|
|
}
|
|
|
|
public boolean getCommandBlockOverride(String command) {
|
|
return overrideAllCommandBlockCommands || commandsConfiguration.getStringList("command-block-overrides").contains(command);
|
|
}
|
|
|
|
private File getConfigFile() {
|
|
return (File) console.options.valueOf("bukkit-settings");
|
|
}
|
|
|
|
private File getCommandsConfigFile() {
|
|
return (File) console.options.valueOf("commands-settings");
|
|
}
|
|
|
|
private void saveConfig() {
|
|
try {
|
|
configuration.save(getConfigFile());
|
|
} catch (IOException ex) {
|
|
Logger.getLogger(CraftServer.class.getName()).log(Level.SEVERE, "Could not save " + getConfigFile(), ex);
|
|
}
|
|
}
|
|
|
|
private void saveCommandsConfig() {
|
|
try {
|
|
commandsConfiguration.save(getCommandsConfigFile());
|
|
} catch (IOException ex) {
|
|
Logger.getLogger(CraftServer.class.getName()).log(Level.SEVERE, "Could not save " + getCommandsConfigFile(), ex);
|
|
}
|
|
}
|
|
|
|
public void loadPlugins() {
|
|
pluginManager.registerInterface(JavaPluginLoader.class);
|
|
|
|
File pluginFolder = (File) console.options.valueOf("plugins");
|
|
|
|
if (pluginFolder.exists()) {
|
|
Plugin[] plugins = pluginManager.loadPlugins(pluginFolder);
|
|
for (Plugin plugin : plugins) {
|
|
try {
|
|
String message = String.format("Loading %s", plugin.getDescription().getFullName());
|
|
plugin.getLogger().info(message);
|
|
plugin.onLoad();
|
|
} catch (Throwable ex) {
|
|
Logger.getLogger(CraftServer.class.getName()).log(Level.SEVERE, ex.getMessage() + " initializing " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex);
|
|
}
|
|
}
|
|
} else {
|
|
pluginFolder.mkdir();
|
|
}
|
|
}
|
|
|
|
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) {
|
|
commandMap.setFallbackCommands();
|
|
setVanillaCommands();
|
|
commandMap.registerServerAliases();
|
|
DefaultPermissions.registerCorePermissions();
|
|
CraftDefaultPermissions.registerCorePermissions();
|
|
loadCustomPermissions();
|
|
helpMap.initializeCommands();
|
|
syncCommands();
|
|
}
|
|
}
|
|
|
|
public void disablePlugins() {
|
|
pluginManager.disablePlugins();
|
|
}
|
|
|
|
private void setVanillaCommands() {
|
|
CommandDispatcher dispatcher = console.vanillaCommandDispatcher;
|
|
|
|
// Build a list of all Vanilla commands and create wrappers
|
|
for (CommandNode<CommandListenerWrapper> cmd : dispatcher.a().getRoot().getChildren()) {
|
|
commandMap.register("minecraft", new VanillaCommandWrapper(dispatcher, cmd));
|
|
}
|
|
}
|
|
|
|
public void syncCommands() {
|
|
// Clear existing commands
|
|
CommandDispatcher dispatcher = console.resources.commands = new CommandDispatcher();
|
|
|
|
// Register all commands, vanilla ones will be using the old dispatcher references
|
|
for (Map.Entry<String, Command> entry : commandMap.getKnownCommands().entrySet()) {
|
|
String label = entry.getKey();
|
|
Command command = entry.getValue();
|
|
|
|
if (command instanceof VanillaCommandWrapper) {
|
|
LiteralCommandNode<CommandListenerWrapper> node = (LiteralCommandNode<CommandListenerWrapper>) ((VanillaCommandWrapper) command).vanillaCommand;
|
|
if (!node.getLiteral().equals(label)) {
|
|
LiteralCommandNode<CommandListenerWrapper> clone = new LiteralCommandNode(label, node.getCommand(), node.getRequirement(), node.getRedirect(), node.getRedirectModifier(), node.isFork());
|
|
|
|
for (CommandNode<CommandListenerWrapper> child : node.getChildren()) {
|
|
clone.addChild(child);
|
|
}
|
|
node = clone;
|
|
}
|
|
|
|
dispatcher.a().getRoot().addChild(node);
|
|
} else {
|
|
new BukkitCommandWrapper(this, entry.getValue()).register(dispatcher.a(), label);
|
|
}
|
|
}
|
|
|
|
// Refresh commands
|
|
for (EntityPlayer player : getHandle().players) {
|
|
dispatcher.a(player);
|
|
}
|
|
}
|
|
|
|
private void enablePlugin(Plugin plugin) {
|
|
try {
|
|
List<Permission> perms = plugin.getDescription().getPermissions();
|
|
|
|
for (Permission perm : perms) {
|
|
try {
|
|
pluginManager.addPermission(perm, false);
|
|
} catch (IllegalArgumentException ex) {
|
|
getLogger().log(Level.WARNING, "Plugin " + plugin.getDescription().getFullName() + " tried to register permission '" + perm.getName() + "' but it's already registered", ex);
|
|
}
|
|
}
|
|
pluginManager.dirtyPermissibles();
|
|
|
|
pluginManager.enablePlugin(plugin);
|
|
} catch (Throwable ex) {
|
|
Logger.getLogger(CraftServer.class.getName()).log(Level.SEVERE, ex.getMessage() + " loading " + plugin.getDescription().getFullName() + " (Is it up to date?)", ex);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String getName() {
|
|
return serverName;
|
|
}
|
|
|
|
@Override
|
|
public String getVersion() {
|
|
return serverVersion + " (MC: " + console.getVersion() + ")";
|
|
}
|
|
|
|
@Override
|
|
public String getBukkitVersion() {
|
|
return bukkitVersion;
|
|
}
|
|
|
|
@Override
|
|
public List<CraftPlayer> getOnlinePlayers() {
|
|
return this.playerView;
|
|
}
|
|
|
|
@Override
|
|
@Deprecated
|
|
public Player getPlayer(final String name) {
|
|
Validate.notNull(name, "Name cannot be null");
|
|
|
|
Player found = getPlayerExact(name);
|
|
// Try for an exact match first.
|
|
if (found != null) {
|
|
return found;
|
|
}
|
|
|
|
String lowerName = name.toLowerCase(java.util.Locale.ENGLISH);
|
|
int delta = Integer.MAX_VALUE;
|
|
for (Player player : getOnlinePlayers()) {
|
|
if (player.getName().toLowerCase(java.util.Locale.ENGLISH).startsWith(lowerName)) {
|
|
int curDelta = Math.abs(player.getName().length() - lowerName.length());
|
|
if (curDelta < delta) {
|
|
found = player;
|
|
delta = curDelta;
|
|
}
|
|
if (curDelta == 0) break;
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
|
|
@Override
|
|
@Deprecated
|
|
public Player getPlayerExact(String name) {
|
|
Validate.notNull(name, "Name cannot be null");
|
|
|
|
EntityPlayer player = playerList.getPlayer(name);
|
|
return (player != null) ? player.getBukkitEntity() : null;
|
|
}
|
|
|
|
@Override
|
|
public Player getPlayer(UUID id) {
|
|
EntityPlayer player = playerList.getPlayer(id);
|
|
|
|
if (player != null) {
|
|
return player.getBukkitEntity();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public int broadcastMessage(String message) {
|
|
return broadcast(message, BROADCAST_CHANNEL_USERS);
|
|
}
|
|
|
|
@Override
|
|
@Deprecated
|
|
public List<Player> matchPlayer(String partialName) {
|
|
Validate.notNull(partialName, "PartialName cannot be null");
|
|
|
|
List<Player> matchedPlayers = new ArrayList<Player>();
|
|
|
|
for (Player iterPlayer : this.getOnlinePlayers()) {
|
|
String iterPlayerName = iterPlayer.getName();
|
|
|
|
if (partialName.equalsIgnoreCase(iterPlayerName)) {
|
|
// Exact match
|
|
matchedPlayers.clear();
|
|
matchedPlayers.add(iterPlayer);
|
|
break;
|
|
}
|
|
if (iterPlayerName.toLowerCase(java.util.Locale.ENGLISH).contains(partialName.toLowerCase(java.util.Locale.ENGLISH))) {
|
|
// Partial match
|
|
matchedPlayers.add(iterPlayer);
|
|
}
|
|
}
|
|
|
|
return matchedPlayers;
|
|
}
|
|
|
|
@Override
|
|
public int getMaxPlayers() {
|
|
return playerList.getMaxPlayers();
|
|
}
|
|
|
|
// NOTE: These are dependent on the corresponding call in MinecraftServer
|
|
// so if that changes this will need to as well
|
|
@Override
|
|
public int getPort() {
|
|
return this.getServer().getPort();
|
|
}
|
|
|
|
@Override
|
|
public int getViewDistance() {
|
|
return this.getProperties().viewDistance;
|
|
}
|
|
|
|
@Override
|
|
public String getIp() {
|
|
return this.getServer().getServerIp();
|
|
}
|
|
|
|
@Override
|
|
public String getWorldType() {
|
|
return this.getProperties().properties.getProperty("level-type");
|
|
}
|
|
|
|
@Override
|
|
public boolean getGenerateStructures() {
|
|
return this.getProperties().a(this.getServer().getCustomRegistry()).shouldGenerateMapFeatures();
|
|
}
|
|
|
|
@Override
|
|
public int getMaxWorldSize() {
|
|
return this.getProperties().maxWorldSize;
|
|
}
|
|
|
|
@Override
|
|
public boolean getAllowEnd() {
|
|
return this.configuration.getBoolean("settings.allow-end");
|
|
}
|
|
|
|
@Override
|
|
public boolean getAllowNether() {
|
|
return this.getServer().getAllowNether();
|
|
}
|
|
|
|
public boolean getWarnOnOverload() {
|
|
return this.configuration.getBoolean("settings.warn-on-overload");
|
|
}
|
|
|
|
public boolean getQueryPlugins() {
|
|
return this.configuration.getBoolean("settings.query-plugins");
|
|
}
|
|
|
|
@Override
|
|
public boolean hasWhitelist() {
|
|
return this.getProperties().whiteList.get();
|
|
}
|
|
|
|
// NOTE: Temporary calls through to server.properies until its replaced
|
|
private DedicatedServerProperties getProperties() {
|
|
return this.console.getDedicatedServerProperties();
|
|
}
|
|
// End Temporary calls
|
|
|
|
@Override
|
|
public String getUpdateFolder() {
|
|
return this.configuration.getString("settings.update-folder", "update");
|
|
}
|
|
|
|
@Override
|
|
public File getUpdateFolderFile() {
|
|
return new File((File) console.options.valueOf("plugins"), this.configuration.getString("settings.update-folder", "update"));
|
|
}
|
|
|
|
@Override
|
|
public long getConnectionThrottle() {
|
|
return this.configuration.getInt("settings.connection-throttle");
|
|
}
|
|
|
|
@Override
|
|
public int getTicksPerAnimalSpawns() {
|
|
return this.configuration.getInt("ticks-per.animal-spawns");
|
|
}
|
|
|
|
@Override
|
|
public int getTicksPerMonsterSpawns() {
|
|
return this.configuration.getInt("ticks-per.monster-spawns");
|
|
}
|
|
|
|
@Override
|
|
public int getTicksPerWaterSpawns() {
|
|
return this.configuration.getInt("ticks-per.water-spawns");
|
|
}
|
|
|
|
@Override
|
|
public int getTicksPerWaterAmbientSpawns() {
|
|
return this.configuration.getInt("ticks-per.water-ambient-spawns");
|
|
}
|
|
|
|
@Override
|
|
public int getTicksPerAmbientSpawns() {
|
|
return this.configuration.getInt("ticks-per.ambient-spawns");
|
|
}
|
|
|
|
@Override
|
|
public PluginManager getPluginManager() {
|
|
return pluginManager;
|
|
}
|
|
|
|
@Override
|
|
public CraftScheduler getScheduler() {
|
|
return scheduler;
|
|
}
|
|
|
|
@Override
|
|
public ServicesManager getServicesManager() {
|
|
return servicesManager;
|
|
}
|
|
|
|
@Override
|
|
public List<World> getWorlds() {
|
|
return new ArrayList<World>(worlds.values());
|
|
}
|
|
|
|
public DedicatedPlayerList getHandle() {
|
|
return playerList;
|
|
}
|
|
|
|
// NOTE: Should only be called from DedicatedServer.ah()
|
|
public boolean dispatchServerCommand(CommandSender sender, ServerCommand serverCommand) {
|
|
if (sender instanceof Conversable) {
|
|
Conversable conversable = (Conversable) sender;
|
|
|
|
if (conversable.isConversing()) {
|
|
conversable.acceptConversationInput(serverCommand.msg);
|
|
return true;
|
|
}
|
|
}
|
|
try {
|
|
this.playerCommandState = true;
|
|
return dispatchCommand(sender, serverCommand.msg);
|
|
} catch (Exception ex) {
|
|
getLogger().log(Level.WARNING, "Unexpected exception while parsing console command \"" + serverCommand.msg + '"', ex);
|
|
return false;
|
|
} finally {
|
|
this.playerCommandState = false;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean dispatchCommand(CommandSender sender, String commandLine) {
|
|
Validate.notNull(sender, "Sender cannot be null");
|
|
Validate.notNull(commandLine, "CommandLine cannot be null");
|
|
|
|
if (commandMap.dispatch(sender, commandLine)) {
|
|
return true;
|
|
}
|
|
|
|
if (sender instanceof Player) {
|
|
sender.sendMessage("Unknown command. Type \"/help\" for help.");
|
|
} else {
|
|
sender.sendMessage("Unknown command. Type \"help\" for help.");
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public void reload() {
|
|
reloadCount++;
|
|
configuration = YamlConfiguration.loadConfiguration(getConfigFile());
|
|
commandsConfiguration = YamlConfiguration.loadConfiguration(getCommandsConfigFile());
|
|
|
|
console.settings = new DedicatedServerSettings(console.options);
|
|
DedicatedServerProperties config = console.settings.getProperties();
|
|
|
|
console.setPVP(config.pvp);
|
|
console.setAllowFlight(config.allowFlight);
|
|
console.setMotd(config.motd);
|
|
monsterSpawn = configuration.getInt("spawn-limits.monsters");
|
|
animalSpawn = configuration.getInt("spawn-limits.animals");
|
|
waterAnimalSpawn = configuration.getInt("spawn-limits.water-animals");
|
|
waterAmbientSpawn = configuration.getInt("spawn-limits.water-ambient");
|
|
ambientSpawn = configuration.getInt("spawn-limits.ambient");
|
|
warningState = WarningState.value(configuration.getString("settings.deprecated-verbose"));
|
|
TicketType.PLUGIN.timeout = configuration.getInt("chunk-gc.period-in-ticks");
|
|
minimumAPI = configuration.getString("settings.minimum-api");
|
|
printSaveWarning = false;
|
|
console.autosavePeriod = configuration.getInt("ticks-per.autosave");
|
|
loadIcon();
|
|
|
|
try {
|
|
playerList.getIPBans().load();
|
|
} catch (IOException ex) {
|
|
logger.log(Level.WARNING, "Failed to load banned-ips.json, " + ex.getMessage());
|
|
}
|
|
try {
|
|
playerList.getProfileBans().load();
|
|
} catch (IOException ex) {
|
|
logger.log(Level.WARNING, "Failed to load banned-players.json, " + ex.getMessage());
|
|
}
|
|
|
|
for (WorldServer world : console.getWorlds()) {
|
|
world.serverLevelData.setDifficulty(config.difficulty);
|
|
world.setSpawnFlags(config.spawnMonsters, config.spawnAnimals);
|
|
if (this.getTicksPerAnimalSpawns() < 0) {
|
|
world.ticksPerAnimalSpawns = 400;
|
|
} else {
|
|
world.ticksPerAnimalSpawns = this.getTicksPerAnimalSpawns();
|
|
}
|
|
|
|
if (this.getTicksPerMonsterSpawns() < 0) {
|
|
world.ticksPerMonsterSpawns = 1;
|
|
} else {
|
|
world.ticksPerMonsterSpawns = this.getTicksPerMonsterSpawns();
|
|
}
|
|
|
|
if (this.getTicksPerWaterSpawns() < 0) {
|
|
world.ticksPerWaterSpawns = 1;
|
|
} else {
|
|
world.ticksPerWaterSpawns = this.getTicksPerWaterSpawns();
|
|
}
|
|
|
|
if (this.getTicksPerWaterAmbientSpawns() < 0) {
|
|
world.ticksPerWaterAmbientSpawns = 1;
|
|
} else {
|
|
world.ticksPerWaterAmbientSpawns = this.getTicksPerWaterAmbientSpawns();
|
|
}
|
|
|
|
if (this.getTicksPerAmbientSpawns() < 0) {
|
|
world.ticksPerAmbientSpawns = 1;
|
|
} else {
|
|
world.ticksPerAmbientSpawns = this.getTicksPerAmbientSpawns();
|
|
}
|
|
}
|
|
|
|
pluginManager.clearPlugins();
|
|
commandMap.clearCommands();
|
|
reloadData();
|
|
overrideAllCommandBlockCommands = commandsConfiguration.getStringList("command-block-overrides").contains("*");
|
|
ignoreVanillaPermissions = commandsConfiguration.getBoolean("ignore-vanilla-permissions");
|
|
|
|
int pollCount = 0;
|
|
|
|
// Wait for at most 2.5 seconds for plugins to close their threads
|
|
while (pollCount < 50 && getScheduler().getActiveWorkers().size() > 0) {
|
|
try {
|
|
Thread.sleep(50);
|
|
} catch (InterruptedException e) {}
|
|
pollCount++;
|
|
}
|
|
|
|
List<BukkitWorker> overdueWorkers = getScheduler().getActiveWorkers();
|
|
for (BukkitWorker worker : overdueWorkers) {
|
|
Plugin plugin = worker.getOwner();
|
|
String author = "<NoAuthorGiven>";
|
|
if (plugin.getDescription().getAuthors().size() > 0) {
|
|
author = plugin.getDescription().getAuthors().get(0);
|
|
}
|
|
getLogger().log(Level.SEVERE, String.format(
|
|
"Nag author: '%s' of '%s' about the following: %s",
|
|
author,
|
|
plugin.getDescription().getName(),
|
|
"This plugin is not properly shutting down its async tasks when it is being reloaded. This may cause conflicts with the newly loaded version of the plugin"
|
|
));
|
|
}
|
|
loadPlugins();
|
|
enablePlugins(PluginLoadOrder.STARTUP);
|
|
enablePlugins(PluginLoadOrder.POSTWORLD);
|
|
getPluginManager().callEvent(new ServerLoadEvent(ServerLoadEvent.LoadType.RELOAD));
|
|
}
|
|
|
|
@Override
|
|
public void reloadData() {
|
|
CommandReload.reload(console);
|
|
}
|
|
|
|
private void loadIcon() {
|
|
icon = new CraftIconCache(null);
|
|
try {
|
|
final File file = new File(new File("."), "server-icon.png");
|
|
if (file.isFile()) {
|
|
icon = loadServerIcon0(file);
|
|
}
|
|
} catch (Exception ex) {
|
|
getLogger().log(Level.WARNING, "Couldn't load server icon", ex);
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings({ "unchecked", "finally" })
|
|
private void loadCustomPermissions() {
|
|
File file = new File(configuration.getString("settings.permissions-file"));
|
|
FileInputStream stream;
|
|
|
|
try {
|
|
stream = new FileInputStream(file);
|
|
} catch (FileNotFoundException ex) {
|
|
try {
|
|
file.createNewFile();
|
|
} finally {
|
|
return;
|
|
}
|
|
}
|
|
|
|
Map<String, Map<String, Object>> perms;
|
|
|
|
try {
|
|
perms = (Map<String, Map<String, Object>>) yaml.load(stream);
|
|
} catch (MarkedYAMLException ex) {
|
|
getLogger().log(Level.WARNING, "Server permissions file " + file + " is not valid YAML: " + ex.toString());
|
|
return;
|
|
} catch (Throwable ex) {
|
|
getLogger().log(Level.WARNING, "Server permissions file " + file + " is not valid YAML.", ex);
|
|
return;
|
|
} finally {
|
|
try {
|
|
stream.close();
|
|
} catch (IOException ex) {}
|
|
}
|
|
|
|
if (perms == null) {
|
|
getLogger().log(Level.INFO, "Server permissions file " + file + " is empty, ignoring it");
|
|
return;
|
|
}
|
|
|
|
List<Permission> permsList = Permission.loadPermissions(perms, "Permission node '%s' in " + file + " is invalid", Permission.DEFAULT_PERMISSION);
|
|
|
|
for (Permission perm : permsList) {
|
|
try {
|
|
pluginManager.addPermission(perm);
|
|
} catch (IllegalArgumentException ex) {
|
|
getLogger().log(Level.SEVERE, "Permission in " + file + " was already defined", ex);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "CraftServer{" + "serverName=" + serverName + ",serverVersion=" + serverVersion + ",minecraftVersion=" + console.getVersion() + '}';
|
|
}
|
|
|
|
public World createWorld(String name, World.Environment environment) {
|
|
return WorldCreator.name(name).environment(environment).createWorld();
|
|
}
|
|
|
|
public World createWorld(String name, World.Environment environment, long seed) {
|
|
return WorldCreator.name(name).environment(environment).seed(seed).createWorld();
|
|
}
|
|
|
|
public World createWorld(String name, Environment environment, ChunkGenerator generator) {
|
|
return WorldCreator.name(name).environment(environment).generator(generator).createWorld();
|
|
}
|
|
|
|
public World createWorld(String name, Environment environment, long seed, ChunkGenerator generator) {
|
|
return WorldCreator.name(name).environment(environment).seed(seed).generator(generator).createWorld();
|
|
}
|
|
|
|
@Override
|
|
public World createWorld(WorldCreator creator) {
|
|
Preconditions.checkState(!console.levels.isEmpty(), "Cannot create additional worlds on STARTUP");
|
|
Validate.notNull(creator, "Creator may not be null");
|
|
|
|
String name = creator.name();
|
|
ChunkGenerator generator = creator.generator();
|
|
BiomeProvider biomeProvider = creator.biomeProvider();
|
|
File folder = new File(getWorldContainer(), name);
|
|
World world = getWorld(name);
|
|
|
|
if (world != null) {
|
|
return world;
|
|
}
|
|
|
|
if ((folder.exists()) && (!folder.isDirectory())) {
|
|
throw new IllegalArgumentException("File exists with the name '" + name + "' and isn't a folder");
|
|
}
|
|
|
|
if (generator == null) {
|
|
generator = getGenerator(name);
|
|
}
|
|
|
|
if (biomeProvider == null) {
|
|
biomeProvider = getBiomeProvider(name);
|
|
}
|
|
|
|
ResourceKey<WorldDimension> actualDimension;
|
|
switch (creator.environment()) {
|
|
case NORMAL:
|
|
actualDimension = WorldDimension.OVERWORLD;
|
|
break;
|
|
case NETHER:
|
|
actualDimension = WorldDimension.NETHER;
|
|
break;
|
|
case THE_END:
|
|
actualDimension = WorldDimension.END;
|
|
break;
|
|
default:
|
|
throw new IllegalArgumentException("Illegal dimension");
|
|
}
|
|
|
|
Convertable.ConversionSession worldSession;
|
|
try {
|
|
worldSession = Convertable.a(getWorldContainer().toPath()).c(name, actualDimension);
|
|
} catch (IOException ex) {
|
|
throw new RuntimeException(ex);
|
|
}
|
|
MinecraftServer.convertWorld(worldSession); // Run conversion now
|
|
|
|
boolean hardcore = creator.hardcore();
|
|
|
|
RegistryReadOps<NBTBase> registryreadops = RegistryReadOps.a((DynamicOps) DynamicOpsNBT.INSTANCE, console.resources.i(), console.registryHolder);
|
|
WorldDataServer worlddata = (WorldDataServer) worldSession.a((DynamicOps) registryreadops, console.datapackconfiguration);
|
|
|
|
WorldSettings worldSettings;
|
|
// See MinecraftServer.a(String, String, long, WorldType, JsonElement)
|
|
if (worlddata == null) {
|
|
Properties properties = new Properties();
|
|
properties.put("generator-settings", Objects.toString(creator.generatorSettings()));
|
|
properties.put("level-seed", Objects.toString(creator.seed()));
|
|
properties.put("generate-structures", Objects.toString(creator.generateStructures()));
|
|
properties.put("level-type", Objects.toString(creator.type().getName()));
|
|
|
|
GeneratorSettings generatorsettings = GeneratorSettings.a(console.getCustomRegistry(), properties);
|
|
worldSettings = new WorldSettings(name, EnumGamemode.getById(getDefaultGameMode().getValue()), hardcore, EnumDifficulty.EASY, false, new GameRules(), console.datapackconfiguration);
|
|
worlddata = new WorldDataServer(worldSettings, generatorsettings, Lifecycle.stable());
|
|
}
|
|
worlddata.checkName(name);
|
|
worlddata.a(console.getServerModName(), console.getModded().isPresent());
|
|
|
|
if (console.options.has("forceUpgrade")) {
|
|
net.minecraft.server.Main.convertWorld(worldSession, DataConverterRegistry.a(), console.options.has("eraseCache"), () -> {
|
|
return true;
|
|
}, worlddata.getGeneratorSettings().d().d().stream().map((entry) -> {
|
|
return ResourceKey.a(IRegistry.DIMENSION_TYPE_REGISTRY, ((ResourceKey) entry.getKey()).a());
|
|
}).collect(ImmutableSet.toImmutableSet()));
|
|
}
|
|
|
|
long j = BiomeManager.a(creator.seed());
|
|
List<MobSpawner> list = ImmutableList.of(new MobSpawnerPhantom(), new MobSpawnerPatrol(), new MobSpawnerCat(), new VillageSiege(), new MobSpawnerTrader(worlddata));
|
|
RegistryMaterials<WorldDimension> registrymaterials = worlddata.getGeneratorSettings().d();
|
|
WorldDimension worlddimension = (WorldDimension) registrymaterials.a(actualDimension);
|
|
DimensionManager dimensionmanager;
|
|
net.minecraft.world.level.chunk.ChunkGenerator chunkgenerator;
|
|
|
|
if (worlddimension == null) {
|
|
dimensionmanager = (DimensionManager) console.registryHolder.d(IRegistry.DIMENSION_TYPE_REGISTRY).d(DimensionManager.OVERWORLD_LOCATION);
|
|
chunkgenerator = GeneratorSettings.a(console.registryHolder.d(IRegistry.BIOME_REGISTRY), console.registryHolder.d(IRegistry.NOISE_GENERATOR_SETTINGS_REGISTRY), (new Random()).nextLong());
|
|
} else {
|
|
dimensionmanager = worlddimension.b();
|
|
chunkgenerator = worlddimension.c();
|
|
}
|
|
|
|
WorldInfo worldInfo = new CraftWorldInfo(worlddata, worldSession, creator.environment(), dimensionmanager);
|
|
if (biomeProvider == null && generator != null) {
|
|
biomeProvider = generator.getDefaultBiomeProvider(worldInfo);
|
|
}
|
|
|
|
if (biomeProvider != null) {
|
|
WorldChunkManager worldChunkManager = new CustomWorldChunkManager(worldInfo, biomeProvider, console.registryHolder.b(IRegistry.BIOME_REGISTRY));
|
|
if (chunkgenerator instanceof ChunkGeneratorAbstract) {
|
|
chunkgenerator = new ChunkGeneratorAbstract(worldChunkManager, chunkgenerator.strongholdSeed, ((ChunkGeneratorAbstract) chunkgenerator).settings);
|
|
}
|
|
}
|
|
|
|
ResourceKey<net.minecraft.world.level.World> worldKey;
|
|
String levelName = this.getServer().getDedicatedServerProperties().levelName;
|
|
if (name.equals(levelName + "_nether")) {
|
|
worldKey = net.minecraft.world.level.World.NETHER;
|
|
} else if (name.equals(levelName + "_the_end")) {
|
|
worldKey = net.minecraft.world.level.World.END;
|
|
} else {
|
|
worldKey = ResourceKey.a(IRegistry.DIMENSION_REGISTRY, new MinecraftKey(name.toLowerCase(java.util.Locale.ENGLISH)));
|
|
}
|
|
|
|
WorldServer internal = (WorldServer) new WorldServer(console, console.executor, worldSession, worlddata, worldKey, dimensionmanager, getServer().progressListenerFactory.create(11),
|
|
chunkgenerator, worlddata.getGeneratorSettings().isDebugWorld(), j, creator.environment() == Environment.NORMAL ? list : ImmutableList.of(), true, creator.environment(), generator, biomeProvider);
|
|
|
|
if (!(worlds.containsKey(name.toLowerCase(java.util.Locale.ENGLISH)))) {
|
|
return null;
|
|
}
|
|
|
|
console.initWorld(internal, worlddata, worlddata, worlddata.getGeneratorSettings());
|
|
|
|
internal.setSpawnFlags(true, true);
|
|
console.levels.put(internal.getDimensionKey(), internal);
|
|
|
|
getServer().loadSpawn(internal.getChunkProvider().chunkMap.progressListener, internal);
|
|
internal.entityManager.tick(); // SPIGOT-6526: Load pending entities so they are available to the API
|
|
|
|
pluginManager.callEvent(new WorldLoadEvent(internal.getWorld()));
|
|
return internal.getWorld();
|
|
}
|
|
|
|
@Override
|
|
public boolean unloadWorld(String name, boolean save) {
|
|
return unloadWorld(getWorld(name), save);
|
|
}
|
|
|
|
@Override
|
|
public boolean unloadWorld(World world, boolean save) {
|
|
if (world == null) {
|
|
return false;
|
|
}
|
|
|
|
WorldServer handle = ((CraftWorld) world).getHandle();
|
|
|
|
if (!(console.levels.containsKey(handle.getDimensionKey()))) {
|
|
return false;
|
|
}
|
|
|
|
if (handle.getDimensionKey() == net.minecraft.world.level.World.OVERWORLD) {
|
|
return false;
|
|
}
|
|
|
|
if (handle.getPlayers().size() > 0) {
|
|
return false;
|
|
}
|
|
|
|
WorldUnloadEvent e = new WorldUnloadEvent(handle.getWorld());
|
|
pluginManager.callEvent(e);
|
|
|
|
if (e.isCancelled()) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
if (save) {
|
|
handle.save(null, true, true);
|
|
}
|
|
|
|
handle.getChunkProvider().close(save);
|
|
handle.convertable.close();
|
|
} catch (Exception ex) {
|
|
getLogger().log(Level.SEVERE, null, ex);
|
|
}
|
|
|
|
worlds.remove(world.getName().toLowerCase(java.util.Locale.ENGLISH));
|
|
console.levels.remove(handle.getDimensionKey());
|
|
return true;
|
|
}
|
|
|
|
public DedicatedServer getServer() {
|
|
return console;
|
|
}
|
|
|
|
@Override
|
|
public World getWorld(String name) {
|
|
Validate.notNull(name, "Name cannot be null");
|
|
|
|
return worlds.get(name.toLowerCase(java.util.Locale.ENGLISH));
|
|
}
|
|
|
|
@Override
|
|
public World getWorld(UUID uid) {
|
|
for (World world : worlds.values()) {
|
|
if (world.getUID().equals(uid)) {
|
|
return world;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public void addWorld(World world) {
|
|
// Check if a World already exists with the UID.
|
|
if (getWorld(world.getUID()) != null) {
|
|
System.out.println("World " + world.getName() + " is a duplicate of another world and has been prevented from loading. Please delete the uid.dat file from " + world.getName() + "'s world directory if you want to be able to load the duplicate world.");
|
|
return;
|
|
}
|
|
worlds.put(world.getName().toLowerCase(java.util.Locale.ENGLISH), world);
|
|
}
|
|
|
|
@Override
|
|
public Logger getLogger() {
|
|
return logger;
|
|
}
|
|
|
|
public ConsoleReader getReader() {
|
|
return console.reader;
|
|
}
|
|
|
|
@Override
|
|
public PluginCommand getPluginCommand(String name) {
|
|
Command command = commandMap.getCommand(name);
|
|
|
|
if (command instanceof PluginCommand) {
|
|
return (PluginCommand) command;
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void savePlayers() {
|
|
checkSaveState();
|
|
playerList.savePlayers();
|
|
}
|
|
|
|
@Override
|
|
public boolean addRecipe(Recipe recipe) {
|
|
CraftRecipe toAdd;
|
|
if (recipe instanceof CraftRecipe) {
|
|
toAdd = (CraftRecipe) recipe;
|
|
} else {
|
|
if (recipe instanceof ShapedRecipe) {
|
|
toAdd = CraftShapedRecipe.fromBukkitRecipe((ShapedRecipe) recipe);
|
|
} else if (recipe instanceof ShapelessRecipe) {
|
|
toAdd = CraftShapelessRecipe.fromBukkitRecipe((ShapelessRecipe) recipe);
|
|
} else if (recipe instanceof FurnaceRecipe) {
|
|
toAdd = CraftFurnaceRecipe.fromBukkitRecipe((FurnaceRecipe) recipe);
|
|
} else if (recipe instanceof BlastingRecipe) {
|
|
toAdd = CraftBlastingRecipe.fromBukkitRecipe((BlastingRecipe) recipe);
|
|
} else if (recipe instanceof CampfireRecipe) {
|
|
toAdd = CraftCampfireRecipe.fromBukkitRecipe((CampfireRecipe) recipe);
|
|
} else if (recipe instanceof SmokingRecipe) {
|
|
toAdd = CraftSmokingRecipe.fromBukkitRecipe((SmokingRecipe) recipe);
|
|
} else if (recipe instanceof StonecuttingRecipe) {
|
|
toAdd = CraftStonecuttingRecipe.fromBukkitRecipe((StonecuttingRecipe) recipe);
|
|
} else if (recipe instanceof SmithingRecipe) {
|
|
toAdd = CraftSmithingRecipe.fromBukkitRecipe((SmithingRecipe) recipe);
|
|
} else if (recipe instanceof ComplexRecipe) {
|
|
throw new UnsupportedOperationException("Cannot add custom complex recipe");
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
toAdd.addToCraftingManager();
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public List<Recipe> getRecipesFor(ItemStack result) {
|
|
Validate.notNull(result, "Result cannot be null");
|
|
|
|
List<Recipe> results = new ArrayList<Recipe>();
|
|
Iterator<Recipe> iter = recipeIterator();
|
|
while (iter.hasNext()) {
|
|
Recipe recipe = iter.next();
|
|
ItemStack stack = recipe.getResult();
|
|
if (stack.getType() != result.getType()) {
|
|
continue;
|
|
}
|
|
if (result.getDurability() == -1 || result.getDurability() == stack.getDurability()) {
|
|
results.add(recipe);
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
@Override
|
|
public Recipe getRecipe(NamespacedKey recipeKey) {
|
|
Preconditions.checkArgument(recipeKey != null, "recipeKey == null");
|
|
|
|
return getServer().getCraftingManager().getRecipe(CraftNamespacedKey.toMinecraft(recipeKey)).map(IRecipe::toBukkitRecipe).orElse(null);
|
|
}
|
|
|
|
@Override
|
|
public Recipe getCraftingRecipe(ItemStack[] craftingMatrix, World world) {
|
|
// Create a players Crafting Inventory
|
|
Container container = new Container(null, -1) {
|
|
@Override
|
|
public InventoryView getBukkitView() {
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public boolean canUse(EntityHuman entityhuman) {
|
|
return false;
|
|
}
|
|
};
|
|
InventoryCrafting inventoryCrafting = new InventoryCrafting(container, 3, 3);
|
|
|
|
return getNMSRecipe(craftingMatrix, inventoryCrafting, (CraftWorld) world).map(IRecipe::toBukkitRecipe).orElse(null);
|
|
}
|
|
|
|
@Override
|
|
public ItemStack craftItem(ItemStack[] craftingMatrix, World world, Player player) {
|
|
Preconditions.checkArgument(world != null, "world must not be null");
|
|
Preconditions.checkArgument(player != null, "player must not be null");
|
|
|
|
CraftWorld craftWorld = (CraftWorld) world;
|
|
CraftPlayer craftPlayer = (CraftPlayer) player;
|
|
|
|
// Create a players Crafting Inventory and get the recipe
|
|
ContainerWorkbench container = new ContainerWorkbench(-1, craftPlayer.getHandle().getInventory());
|
|
InventoryCrafting inventoryCrafting = container.craftSlots;
|
|
InventoryCraftResult craftResult = container.resultSlots;
|
|
|
|
Optional<RecipeCrafting> recipe = getNMSRecipe(craftingMatrix, inventoryCrafting, craftWorld);
|
|
|
|
// Generate the resulting ItemStack from the Crafting Matrix
|
|
net.minecraft.world.item.ItemStack itemstack = net.minecraft.world.item.ItemStack.EMPTY;
|
|
|
|
if (recipe.isPresent()) {
|
|
RecipeCrafting recipeCrafting = recipe.get();
|
|
if (craftResult.setRecipeUsed(craftWorld.getHandle(), craftPlayer.getHandle(), recipeCrafting)) {
|
|
itemstack = recipeCrafting.a(inventoryCrafting);
|
|
}
|
|
}
|
|
|
|
// Call Bukkit event to check for matrix/result changes.
|
|
net.minecraft.world.item.ItemStack result = CraftEventFactory.callPreCraftEvent(inventoryCrafting, craftResult, itemstack, container.getBukkitView(), recipe.orElse(null) instanceof RecipeRepair);
|
|
|
|
// Set the resulting matrix items
|
|
for (int i = 0; i < craftingMatrix.length; i++) {
|
|
Item remaining = inventoryCrafting.getContents().get(i).getItem().getCraftingRemainingItem();
|
|
craftingMatrix[i] = (remaining != null) ? CraftItemStack.asBukkitCopy(remaining.createItemStack()) : null;
|
|
}
|
|
|
|
return CraftItemStack.asBukkitCopy(result);
|
|
}
|
|
|
|
private Optional<RecipeCrafting> getNMSRecipe(ItemStack[] craftingMatrix, InventoryCrafting inventoryCrafting, CraftWorld world) {
|
|
Preconditions.checkArgument(craftingMatrix != null, "craftingMatrix must not be null");
|
|
Preconditions.checkArgument(craftingMatrix.length == 9, "craftingMatrix must be an array of length 9");
|
|
Preconditions.checkArgument(world != null, "world must not be null");
|
|
|
|
for (int i = 0; i < craftingMatrix.length; i++) {
|
|
inventoryCrafting.setItem(i, CraftItemStack.asNMSCopy(craftingMatrix[i]));
|
|
}
|
|
|
|
return getServer().getCraftingManager().craft(Recipes.CRAFTING, inventoryCrafting, world.getHandle());
|
|
}
|
|
|
|
@Override
|
|
public Iterator<Recipe> recipeIterator() {
|
|
return new RecipeIterator();
|
|
}
|
|
|
|
@Override
|
|
public void clearRecipes() {
|
|
console.getCraftingManager().clearRecipes();
|
|
}
|
|
|
|
@Override
|
|
public void resetRecipes() {
|
|
reloadData(); // Not ideal but hard to reload a subset of a resource pack
|
|
}
|
|
|
|
@Override
|
|
public boolean removeRecipe(NamespacedKey recipeKey) {
|
|
Preconditions.checkArgument(recipeKey != null, "recipeKey == null");
|
|
|
|
MinecraftKey mcKey = CraftNamespacedKey.toMinecraft(recipeKey);
|
|
for (Object2ObjectLinkedOpenHashMap<MinecraftKey, IRecipe<?>> recipes : getServer().getCraftingManager().recipes.values()) {
|
|
if (recipes.remove(mcKey) != null) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public Map<String, String[]> getCommandAliases() {
|
|
ConfigurationSection section = commandsConfiguration.getConfigurationSection("aliases");
|
|
Map<String, String[]> result = new LinkedHashMap<String, String[]>();
|
|
|
|
if (section != null) {
|
|
for (String key : section.getKeys(false)) {
|
|
List<String> commands;
|
|
|
|
if (section.isList(key)) {
|
|
commands = section.getStringList(key);
|
|
} else {
|
|
commands = ImmutableList.of(section.getString(key));
|
|
}
|
|
|
|
result.put(key, commands.toArray(new String[commands.size()]));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public void removeBukkitSpawnRadius() {
|
|
configuration.set("settings.spawn-radius", null);
|
|
saveConfig();
|
|
}
|
|
|
|
public int getBukkitSpawnRadius() {
|
|
return configuration.getInt("settings.spawn-radius", -1);
|
|
}
|
|
|
|
@Override
|
|
public String getShutdownMessage() {
|
|
return configuration.getString("settings.shutdown-message");
|
|
}
|
|
|
|
@Override
|
|
public int getSpawnRadius() {
|
|
return this.getServer().getSpawnProtection();
|
|
}
|
|
|
|
@Override
|
|
public void setSpawnRadius(int value) {
|
|
configuration.set("settings.spawn-radius", value);
|
|
saveConfig();
|
|
}
|
|
|
|
@Override
|
|
public boolean getOnlineMode() {
|
|
return console.getOnlineMode();
|
|
}
|
|
|
|
@Override
|
|
public boolean getAllowFlight() {
|
|
return console.getAllowFlight();
|
|
}
|
|
|
|
@Override
|
|
public boolean isHardcore() {
|
|
return console.isHardcore();
|
|
}
|
|
|
|
public ChunkGenerator getGenerator(String world) {
|
|
ConfigurationSection section = configuration.getConfigurationSection("worlds");
|
|
ChunkGenerator result = null;
|
|
|
|
if (section != null) {
|
|
section = section.getConfigurationSection(world);
|
|
|
|
if (section != null) {
|
|
String name = section.getString("generator");
|
|
|
|
if ((name != null) && (!name.equals(""))) {
|
|
String[] split = name.split(":", 2);
|
|
String id = (split.length > 1) ? split[1] : null;
|
|
Plugin plugin = pluginManager.getPlugin(split[0]);
|
|
|
|
if (plugin == null) {
|
|
getLogger().severe("Could not set generator for default world '" + world + "': Plugin '" + split[0] + "' does not exist");
|
|
} else if (!plugin.isEnabled()) {
|
|
getLogger().severe("Could not set generator for default world '" + world + "': Plugin '" + plugin.getDescription().getFullName() + "' is not enabled yet (is it load:STARTUP?)");
|
|
} else {
|
|
try {
|
|
result = plugin.getDefaultWorldGenerator(world, id);
|
|
if (result == null) {
|
|
getLogger().severe("Could not set generator for default world '" + world + "': Plugin '" + plugin.getDescription().getFullName() + "' lacks a default world generator");
|
|
}
|
|
} catch (Throwable t) {
|
|
plugin.getLogger().log(Level.SEVERE, "Could not set generator for default world '" + world + "': Plugin '" + plugin.getDescription().getFullName(), t);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public BiomeProvider getBiomeProvider(String world) {
|
|
ConfigurationSection section = configuration.getConfigurationSection("worlds");
|
|
BiomeProvider result = null;
|
|
|
|
if (section != null) {
|
|
section = section.getConfigurationSection(world);
|
|
|
|
if (section != null) {
|
|
String name = section.getString("biome-provider");
|
|
|
|
if ((name != null) && (!name.equals(""))) {
|
|
String[] split = name.split(":", 2);
|
|
String id = (split.length > 1) ? split[1] : null;
|
|
Plugin plugin = pluginManager.getPlugin(split[0]);
|
|
|
|
if (plugin == null) {
|
|
getLogger().severe("Could not set biome provider for default world '" + world + "': Plugin '" + split[0] + "' does not exist");
|
|
} else if (!plugin.isEnabled()) {
|
|
getLogger().severe("Could not set biome provider for default world '" + world + "': Plugin '" + plugin.getDescription().getFullName() + "' is not enabled yet (is it load:STARTUP?)");
|
|
} else {
|
|
try {
|
|
result = plugin.getDefaultBiomeProvider(world, id);
|
|
if (result == null) {
|
|
getLogger().severe("Could not set biome provider for default world '" + world + "': Plugin '" + plugin.getDescription().getFullName() + "' lacks a default world biome provider");
|
|
}
|
|
} catch (Throwable t) {
|
|
plugin.getLogger().log(Level.SEVERE, "Could not set biome provider for default world '" + world + "': Plugin '" + plugin.getDescription().getFullName(), t);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
@Deprecated
|
|
public CraftMapView getMap(int id) {
|
|
WorldMap worldmap = console.getWorldServer(net.minecraft.world.level.World.OVERWORLD).a("map_" + id);
|
|
if (worldmap == null) {
|
|
return null;
|
|
}
|
|
return worldmap.mapView;
|
|
}
|
|
|
|
@Override
|
|
public CraftMapView createMap(World world) {
|
|
Validate.notNull(world, "World cannot be null");
|
|
|
|
net.minecraft.world.level.World minecraftWorld = ((CraftWorld) world).getHandle();
|
|
// creates a new map at world spawn with the scale of 3, with out tracking position and unlimited tracking
|
|
int newId = ItemWorldMap.createNewSavedData(minecraftWorld, minecraftWorld.getWorldData().a(), minecraftWorld.getWorldData().c(), 3, false, false, minecraftWorld.getDimensionKey());
|
|
return minecraftWorld.a(ItemWorldMap.a(newId)).mapView;
|
|
}
|
|
|
|
@Override
|
|
public ItemStack createExplorerMap(World world, Location location, StructureType structureType) {
|
|
return this.createExplorerMap(world, location, structureType, 100, true);
|
|
}
|
|
|
|
@Override
|
|
public ItemStack createExplorerMap(World world, Location location, StructureType structureType, int radius, boolean findUnexplored) {
|
|
Validate.notNull(world, "World cannot be null");
|
|
Validate.notNull(structureType, "StructureType cannot be null");
|
|
Validate.notNull(structureType.getMapIcon(), "Cannot create explorer maps for StructureType " + structureType.getName());
|
|
|
|
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
|
Location structureLocation = world.locateNearestStructure(location, structureType, radius, findUnexplored);
|
|
BlockPosition structurePosition = new BlockPosition(structureLocation.getBlockX(), structureLocation.getBlockY(), structureLocation.getBlockZ());
|
|
|
|
// Create map with trackPlayer = true, unlimitedTracking = true
|
|
net.minecraft.world.item.ItemStack stack = ItemWorldMap.createFilledMapView(worldServer, structurePosition.getX(), structurePosition.getZ(), MapView.Scale.NORMAL.getValue(), true, true);
|
|
ItemWorldMap.applySepiaFilter(worldServer, stack);
|
|
// "+" map ID taken from EntityVillager
|
|
ItemWorldMap.getSavedMap(stack, worldServer).decorateMap(stack, structurePosition, "+", MapIcon.Type.a(structureType.getMapIcon().getValue()));
|
|
|
|
return CraftItemStack.asBukkitCopy(stack);
|
|
}
|
|
|
|
@Override
|
|
public void shutdown() {
|
|
console.safeShutdown(false);
|
|
}
|
|
|
|
@Override
|
|
public int broadcast(String message, String permission) {
|
|
Set<CommandSender> recipients = new HashSet<>();
|
|
for (Permissible permissible : getPluginManager().getPermissionSubscriptions(permission)) {
|
|
if (permissible instanceof CommandSender && permissible.hasPermission(permission)) {
|
|
recipients.add((CommandSender) permissible);
|
|
}
|
|
}
|
|
|
|
BroadcastMessageEvent broadcastMessageEvent = new BroadcastMessageEvent(!Bukkit.isPrimaryThread(), message, recipients);
|
|
getPluginManager().callEvent(broadcastMessageEvent);
|
|
|
|
if (broadcastMessageEvent.isCancelled()) {
|
|
return 0;
|
|
}
|
|
|
|
message = broadcastMessageEvent.getMessage();
|
|
|
|
for (CommandSender recipient : recipients) {
|
|
recipient.sendMessage(message);
|
|
}
|
|
|
|
return recipients.size();
|
|
}
|
|
|
|
@Override
|
|
@Deprecated
|
|
public OfflinePlayer getOfflinePlayer(String name) {
|
|
Validate.notNull(name, "Name cannot be null");
|
|
Validate.notEmpty(name, "Name cannot be empty");
|
|
|
|
OfflinePlayer result = getPlayerExact(name);
|
|
if (result == null) {
|
|
// This is potentially blocking :(
|
|
GameProfile profile = console.getUserCache().getProfile(name).orElse(null);
|
|
if (profile == null) {
|
|
// Make an OfflinePlayer using an offline mode UUID since the name has no profile
|
|
result = getOfflinePlayer(new GameProfile(UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(Charsets.UTF_8)), name));
|
|
} else {
|
|
// Use the GameProfile even when we get a UUID so we ensure we still have a name
|
|
result = getOfflinePlayer(profile);
|
|
}
|
|
} else {
|
|
offlinePlayers.remove(result.getUniqueId());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public OfflinePlayer getOfflinePlayer(UUID id) {
|
|
Validate.notNull(id, "UUID cannot be null");
|
|
|
|
OfflinePlayer result = getPlayer(id);
|
|
if (result == null) {
|
|
result = offlinePlayers.get(id);
|
|
if (result == null) {
|
|
result = new CraftOfflinePlayer(this, new GameProfile(id, null));
|
|
offlinePlayers.put(id, result);
|
|
}
|
|
} else {
|
|
offlinePlayers.remove(id);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public OfflinePlayer getOfflinePlayer(GameProfile profile) {
|
|
OfflinePlayer player = new CraftOfflinePlayer(this, profile);
|
|
offlinePlayers.put(profile.getId(), player);
|
|
return player;
|
|
}
|
|
|
|
@Override
|
|
@SuppressWarnings("unchecked")
|
|
public Set<String> getIPBans() {
|
|
return new HashSet<String>(Arrays.asList(playerList.getIPBans().getEntries()));
|
|
}
|
|
|
|
@Override
|
|
public void banIP(String address) {
|
|
Validate.notNull(address, "Address cannot be null.");
|
|
|
|
this.getBanList(org.bukkit.BanList.Type.IP).addBan(address, null, null, null);
|
|
}
|
|
|
|
@Override
|
|
public void unbanIP(String address) {
|
|
Validate.notNull(address, "Address cannot be null.");
|
|
|
|
this.getBanList(org.bukkit.BanList.Type.IP).pardon(address);
|
|
}
|
|
|
|
@Override
|
|
public Set<OfflinePlayer> getBannedPlayers() {
|
|
Set<OfflinePlayer> result = new HashSet<OfflinePlayer>();
|
|
|
|
for (JsonListEntry entry : playerList.getProfileBans().getValues()) {
|
|
result.add(getOfflinePlayer((GameProfile) entry.getKey()));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public BanList getBanList(BanList.Type type) {
|
|
Validate.notNull(type, "Type cannot be null");
|
|
|
|
switch (type) {
|
|
case IP:
|
|
return new CraftIpBanList(playerList.getIPBans());
|
|
case NAME:
|
|
default:
|
|
return new CraftProfileBanList(playerList.getProfileBans());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setWhitelist(boolean value) {
|
|
playerList.setHasWhitelist(value);
|
|
console.setHasWhitelist(value);
|
|
}
|
|
|
|
@Override
|
|
public boolean isWhitelistEnforced() {
|
|
return console.isEnforceWhitelist();
|
|
}
|
|
|
|
@Override
|
|
public void setWhitelistEnforced(boolean value) {
|
|
console.setEnforceWhitelist(value);
|
|
}
|
|
|
|
@Override
|
|
public Set<OfflinePlayer> getWhitelistedPlayers() {
|
|
Set<OfflinePlayer> result = new LinkedHashSet<OfflinePlayer>();
|
|
|
|
for (JsonListEntry entry : playerList.getWhitelist().getValues()) {
|
|
result.add(getOfflinePlayer((GameProfile) entry.getKey()));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public Set<OfflinePlayer> getOperators() {
|
|
Set<OfflinePlayer> result = new HashSet<OfflinePlayer>();
|
|
|
|
for (JsonListEntry entry : playerList.getOPs().getValues()) {
|
|
result.add(getOfflinePlayer((GameProfile) entry.getKey()));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public void reloadWhitelist() {
|
|
playerList.reloadWhitelist();
|
|
}
|
|
|
|
@Override
|
|
public GameMode getDefaultGameMode() {
|
|
return GameMode.getByValue(console.getWorldServer(net.minecraft.world.level.World.OVERWORLD).serverLevelData.getGameType().getId());
|
|
}
|
|
|
|
@Override
|
|
public void setDefaultGameMode(GameMode mode) {
|
|
Validate.notNull(mode, "Mode cannot be null");
|
|
|
|
for (World world : getWorlds()) {
|
|
((CraftWorld) world).getHandle().serverLevelData.setGameType(EnumGamemode.getById(mode.getValue()));
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public ConsoleCommandSender getConsoleSender() {
|
|
return console.console;
|
|
}
|
|
|
|
public EntityMetadataStore getEntityMetadata() {
|
|
return entityMetadata;
|
|
}
|
|
|
|
public PlayerMetadataStore getPlayerMetadata() {
|
|
return playerMetadata;
|
|
}
|
|
|
|
public WorldMetadataStore getWorldMetadata() {
|
|
return worldMetadata;
|
|
}
|
|
|
|
@Override
|
|
public File getWorldContainer() {
|
|
return this.getServer().storageSource.a(net.minecraft.world.level.World.OVERWORLD).getParentFile();
|
|
}
|
|
|
|
@Override
|
|
public OfflinePlayer[] getOfflinePlayers() {
|
|
WorldNBTStorage storage = console.playerDataStorage;
|
|
String[] files = storage.getPlayerDir().list(new DatFileFilter());
|
|
Set<OfflinePlayer> players = new HashSet<OfflinePlayer>();
|
|
|
|
for (String file : files) {
|
|
try {
|
|
players.add(getOfflinePlayer(UUID.fromString(file.substring(0, file.length() - 4))));
|
|
} catch (IllegalArgumentException ex) {
|
|
// Who knows what is in this directory, just ignore invalid files
|
|
}
|
|
}
|
|
|
|
players.addAll(getOnlinePlayers());
|
|
|
|
return players.toArray(new OfflinePlayer[players.size()]);
|
|
}
|
|
|
|
@Override
|
|
public Messenger getMessenger() {
|
|
return messenger;
|
|
}
|
|
|
|
@Override
|
|
public void sendPluginMessage(Plugin source, String channel, byte[] message) {
|
|
StandardMessenger.validatePluginMessage(getMessenger(), source, channel, message);
|
|
|
|
for (Player player : getOnlinePlayers()) {
|
|
player.sendPluginMessage(source, channel, message);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Set<String> getListeningPluginChannels() {
|
|
Set<String> result = new HashSet<String>();
|
|
|
|
for (Player player : getOnlinePlayers()) {
|
|
result.addAll(player.getListeningPluginChannels());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public Inventory createInventory(InventoryHolder owner, InventoryType type) {
|
|
Validate.isTrue(type.isCreatable(), "Cannot open an inventory of type ", type);
|
|
return CraftInventoryCreator.INSTANCE.createInventory(owner, type);
|
|
}
|
|
|
|
@Override
|
|
public Inventory createInventory(InventoryHolder owner, InventoryType type, String title) {
|
|
Validate.isTrue(type.isCreatable(), "Cannot open an inventory of type ", type);
|
|
return CraftInventoryCreator.INSTANCE.createInventory(owner, type, title);
|
|
}
|
|
|
|
@Override
|
|
public Inventory createInventory(InventoryHolder owner, int size) throws IllegalArgumentException {
|
|
Validate.isTrue(9 <= size && size <= 54 && size % 9 == 0, "Size for custom inventory must be a multiple of 9 between 9 and 54 slots (got " + size + ")");
|
|
return CraftInventoryCreator.INSTANCE.createInventory(owner, size);
|
|
}
|
|
|
|
@Override
|
|
public Inventory createInventory(InventoryHolder owner, int size, String title) throws IllegalArgumentException {
|
|
Validate.isTrue(9 <= size && size <= 54 && size % 9 == 0, "Size for custom inventory must be a multiple of 9 between 9 and 54 slots (got " + size + ")");
|
|
return CraftInventoryCreator.INSTANCE.createInventory(owner, size, title);
|
|
}
|
|
|
|
@Override
|
|
public Merchant createMerchant(String title) {
|
|
return new CraftMerchantCustom(title == null ? InventoryType.MERCHANT.getDefaultTitle() : title);
|
|
}
|
|
|
|
@Override
|
|
public HelpMap getHelpMap() {
|
|
return helpMap;
|
|
}
|
|
|
|
public SimpleCommandMap getCommandMap() {
|
|
return commandMap;
|
|
}
|
|
|
|
@Override
|
|
public int getMonsterSpawnLimit() {
|
|
return monsterSpawn;
|
|
}
|
|
|
|
@Override
|
|
public int getAnimalSpawnLimit() {
|
|
return animalSpawn;
|
|
}
|
|
|
|
@Override
|
|
public int getWaterAnimalSpawnLimit() {
|
|
return waterAnimalSpawn;
|
|
}
|
|
|
|
@Override
|
|
public int getWaterAmbientSpawnLimit() {
|
|
return waterAmbientSpawn;
|
|
}
|
|
|
|
@Override
|
|
public int getAmbientSpawnLimit() {
|
|
return ambientSpawn;
|
|
}
|
|
|
|
@Override
|
|
public boolean isPrimaryThread() {
|
|
return Thread.currentThread().equals(console.serverThread) || console.hasStopped(); // All bets are off if we have shut down (e.g. due to watchdog)
|
|
}
|
|
|
|
@Override
|
|
public String getMotd() {
|
|
return console.getMotd();
|
|
}
|
|
|
|
@Override
|
|
public WarningState getWarningState() {
|
|
return warningState;
|
|
}
|
|
|
|
public List<String> tabComplete(CommandSender sender, String message, WorldServer world, Vec3D pos, boolean forceCommand) {
|
|
if (!(sender instanceof Player)) {
|
|
return ImmutableList.of();
|
|
}
|
|
|
|
List<String> offers;
|
|
Player player = (Player) sender;
|
|
if (message.startsWith("/") || forceCommand) {
|
|
offers = tabCompleteCommand(player, message, world, pos);
|
|
} else {
|
|
offers = tabCompleteChat(player, message);
|
|
}
|
|
|
|
TabCompleteEvent tabEvent = new TabCompleteEvent(player, message, offers);
|
|
getPluginManager().callEvent(tabEvent);
|
|
|
|
return tabEvent.isCancelled() ? Collections.EMPTY_LIST : tabEvent.getCompletions();
|
|
}
|
|
|
|
public List<String> tabCompleteCommand(Player player, String message, WorldServer world, Vec3D pos) {
|
|
List<String> completions = null;
|
|
try {
|
|
if (message.startsWith("/")) {
|
|
// Trim leading '/' if present (won't always be present in command blocks)
|
|
message = message.substring(1);
|
|
}
|
|
if (pos == null) {
|
|
completions = getCommandMap().tabComplete(player, message);
|
|
} else {
|
|
completions = getCommandMap().tabComplete(player, message, new Location(world.getWorld(), pos.x, pos.y, pos.z));
|
|
}
|
|
} catch (CommandException ex) {
|
|
player.sendMessage(ChatColor.RED + "An internal error occurred while attempting to tab-complete this command");
|
|
getLogger().log(Level.SEVERE, "Exception when " + player.getName() + " attempted to tab complete " + message, ex);
|
|
}
|
|
|
|
return completions == null ? ImmutableList.<String>of() : completions;
|
|
}
|
|
|
|
public List<String> tabCompleteChat(Player player, String message) {
|
|
List<String> completions = new ArrayList<String>();
|
|
PlayerChatTabCompleteEvent event = new PlayerChatTabCompleteEvent(player, message, completions);
|
|
String token = event.getLastToken();
|
|
for (Player p : getOnlinePlayers()) {
|
|
if (player.canSee(p) && StringUtil.startsWithIgnoreCase(p.getName(), token)) {
|
|
completions.add(p.getName());
|
|
}
|
|
}
|
|
pluginManager.callEvent(event);
|
|
|
|
Iterator<?> it = completions.iterator();
|
|
while (it.hasNext()) {
|
|
Object current = it.next();
|
|
if (!(current instanceof String)) {
|
|
// Sanity
|
|
it.remove();
|
|
}
|
|
}
|
|
Collections.sort(completions, String.CASE_INSENSITIVE_ORDER);
|
|
return completions;
|
|
}
|
|
|
|
@Override
|
|
public CraftItemFactory getItemFactory() {
|
|
return CraftItemFactory.instance();
|
|
}
|
|
|
|
@Override
|
|
public CraftScoreboardManager getScoreboardManager() {
|
|
return scoreboardManager;
|
|
}
|
|
|
|
public void checkSaveState() {
|
|
if (this.playerCommandState || this.printSaveWarning || this.console.autosavePeriod <= 0) {
|
|
return;
|
|
}
|
|
this.printSaveWarning = true;
|
|
getLogger().log(Level.WARNING, "A manual (plugin-induced) save has been detected while server is configured to auto-save. This may affect performance.", warningState == WarningState.ON ? new Throwable() : null);
|
|
}
|
|
|
|
@Override
|
|
public CraftIconCache getServerIcon() {
|
|
return icon;
|
|
}
|
|
|
|
@Override
|
|
public CraftIconCache loadServerIcon(File file) throws Exception {
|
|
Validate.notNull(file, "File cannot be null");
|
|
if (!file.isFile()) {
|
|
throw new IllegalArgumentException(file + " is not a file");
|
|
}
|
|
return loadServerIcon0(file);
|
|
}
|
|
|
|
static CraftIconCache loadServerIcon0(File file) throws Exception {
|
|
return loadServerIcon0(ImageIO.read(file));
|
|
}
|
|
|
|
@Override
|
|
public CraftIconCache loadServerIcon(BufferedImage image) throws Exception {
|
|
Validate.notNull(image, "Image cannot be null");
|
|
return loadServerIcon0(image);
|
|
}
|
|
|
|
static CraftIconCache loadServerIcon0(BufferedImage image) throws Exception {
|
|
ByteBuf bytebuf = Unpooled.buffer();
|
|
|
|
Validate.isTrue(image.getWidth() == 64, "Must be 64 pixels wide");
|
|
Validate.isTrue(image.getHeight() == 64, "Must be 64 pixels high");
|
|
ImageIO.write(image, "PNG", new ByteBufOutputStream(bytebuf));
|
|
ByteBuffer bytebuffer = Base64.getEncoder().encode(bytebuf.nioBuffer());
|
|
|
|
return new CraftIconCache("data:image/png;base64," + StandardCharsets.UTF_8.decode(bytebuffer));
|
|
}
|
|
|
|
@Override
|
|
public void setIdleTimeout(int threshold) {
|
|
console.setIdleTimeout(threshold);
|
|
}
|
|
|
|
@Override
|
|
public int getIdleTimeout() {
|
|
return console.getIdleTimeout();
|
|
}
|
|
|
|
@Override
|
|
public ChunkGenerator.ChunkData createChunkData(World world) {
|
|
Validate.notNull(world, "World cannot be null");
|
|
return new OldCraftChunkData(world);
|
|
}
|
|
|
|
@Override
|
|
public BossBar createBossBar(String title, BarColor color, BarStyle style, BarFlag... flags) {
|
|
return new CraftBossBar(title, color, style, flags);
|
|
}
|
|
|
|
@Override
|
|
public KeyedBossBar createBossBar(NamespacedKey key, String title, BarColor barColor, BarStyle barStyle, BarFlag... barFlags) {
|
|
Preconditions.checkArgument(key != null, "key");
|
|
|
|
BossBattleCustom bossBattleCustom = getServer().getBossBattleCustomData().register(CraftNamespacedKey.toMinecraft(key), CraftChatMessage.fromString(title, true)[0]);
|
|
CraftKeyedBossbar craftKeyedBossbar = new CraftKeyedBossbar(bossBattleCustom);
|
|
craftKeyedBossbar.setColor(barColor);
|
|
craftKeyedBossbar.setStyle(barStyle);
|
|
for (BarFlag flag : barFlags) {
|
|
craftKeyedBossbar.addFlag(flag);
|
|
}
|
|
|
|
return craftKeyedBossbar;
|
|
}
|
|
|
|
@Override
|
|
public Iterator<KeyedBossBar> getBossBars() {
|
|
return Iterators.unmodifiableIterator(Iterators.transform(getServer().getBossBattleCustomData().getBattles().iterator(), new Function<BossBattleCustom, org.bukkit.boss.KeyedBossBar>() {
|
|
@Override
|
|
public org.bukkit.boss.KeyedBossBar apply(BossBattleCustom bossBattleCustom) {
|
|
return bossBattleCustom.getBukkitEntity();
|
|
}
|
|
}));
|
|
}
|
|
|
|
@Override
|
|
public KeyedBossBar getBossBar(NamespacedKey key) {
|
|
Preconditions.checkArgument(key != null, "key");
|
|
net.minecraft.server.bossevents.BossBattleCustom bossBattleCustom = getServer().getBossBattleCustomData().a(CraftNamespacedKey.toMinecraft(key));
|
|
|
|
return (bossBattleCustom == null) ? null : bossBattleCustom.getBukkitEntity();
|
|
}
|
|
|
|
@Override
|
|
public boolean removeBossBar(NamespacedKey key) {
|
|
Preconditions.checkArgument(key != null, "key");
|
|
net.minecraft.server.bossevents.BossBattleCustomData bossBattleCustomData = getServer().getBossBattleCustomData();
|
|
net.minecraft.server.bossevents.BossBattleCustom bossBattleCustom = bossBattleCustomData.a(CraftNamespacedKey.toMinecraft(key));
|
|
|
|
if (bossBattleCustom != null) {
|
|
bossBattleCustomData.remove(bossBattleCustom);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public Entity getEntity(UUID uuid) {
|
|
Validate.notNull(uuid, "UUID cannot be null");
|
|
|
|
for (WorldServer world : getServer().getWorlds()) {
|
|
net.minecraft.world.entity.Entity entity = world.getEntity(uuid);
|
|
if (entity != null) {
|
|
return entity.getBukkitEntity();
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public org.bukkit.advancement.Advancement getAdvancement(NamespacedKey key) {
|
|
Preconditions.checkArgument(key != null, "key");
|
|
|
|
Advancement advancement = console.getAdvancementData().a(CraftNamespacedKey.toMinecraft(key));
|
|
return (advancement == null) ? null : advancement.bukkit;
|
|
}
|
|
|
|
@Override
|
|
public Iterator<org.bukkit.advancement.Advancement> advancementIterator() {
|
|
return Iterators.unmodifiableIterator(Iterators.transform(console.getAdvancementData().getAdvancements().iterator(), new Function<Advancement, org.bukkit.advancement.Advancement>() {
|
|
@Override
|
|
public org.bukkit.advancement.Advancement apply(Advancement advancement) {
|
|
return advancement.bukkit;
|
|
}
|
|
}));
|
|
}
|
|
|
|
@Override
|
|
public BlockData createBlockData(org.bukkit.Material material) {
|
|
Validate.isTrue(material != null, "Must provide material");
|
|
|
|
return createBlockData(material, (String) null);
|
|
}
|
|
|
|
@Override
|
|
public BlockData createBlockData(org.bukkit.Material material, Consumer<BlockData> consumer) {
|
|
BlockData data = createBlockData(material);
|
|
|
|
if (consumer != null) {
|
|
consumer.accept(data);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
@Override
|
|
public BlockData createBlockData(String data) throws IllegalArgumentException {
|
|
Validate.isTrue(data != null, "Must provide data");
|
|
|
|
return createBlockData(null, data);
|
|
}
|
|
|
|
@Override
|
|
public BlockData createBlockData(org.bukkit.Material material, String data) {
|
|
Validate.isTrue(material != null || data != null, "Must provide one of material or data");
|
|
|
|
return CraftBlockData.newData(material, data);
|
|
}
|
|
|
|
@Override
|
|
@SuppressWarnings("unchecked")
|
|
public <T extends Keyed> org.bukkit.Tag<T> getTag(String registry, NamespacedKey tag, Class<T> clazz) {
|
|
MinecraftKey key = CraftNamespacedKey.toMinecraft(tag);
|
|
|
|
switch (registry) {
|
|
case org.bukkit.Tag.REGISTRY_BLOCKS:
|
|
Preconditions.checkArgument(clazz == org.bukkit.Material.class, "Block namespace must have material type");
|
|
|
|
return (org.bukkit.Tag<T>) new CraftBlockTag(TagsBlock.a(), key);
|
|
case org.bukkit.Tag.REGISTRY_ITEMS:
|
|
Preconditions.checkArgument(clazz == org.bukkit.Material.class, "Item namespace must have material type");
|
|
|
|
return (org.bukkit.Tag<T>) new CraftItemTag(TagsItem.a(), key);
|
|
case org.bukkit.Tag.REGISTRY_FLUIDS:
|
|
Preconditions.checkArgument(clazz == org.bukkit.Fluid.class, "Fluid namespace must have fluid type");
|
|
|
|
return (org.bukkit.Tag<T>) new CraftFluidTag(TagsFluid.a(), key);
|
|
default:
|
|
throw new IllegalArgumentException();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
@SuppressWarnings("unchecked")
|
|
public <T extends Keyed> Iterable<org.bukkit.Tag<T>> getTags(String registry, Class<T> clazz) {
|
|
switch (registry) {
|
|
case org.bukkit.Tag.REGISTRY_BLOCKS:
|
|
Preconditions.checkArgument(clazz == org.bukkit.Material.class, "Block namespace must have material type");
|
|
|
|
Tags<Block> blockTags = TagsBlock.a();
|
|
return blockTags.a().keySet().stream().map(key -> (org.bukkit.Tag<T>) new CraftBlockTag(blockTags, key)).collect(ImmutableList.toImmutableList());
|
|
case org.bukkit.Tag.REGISTRY_ITEMS:
|
|
Preconditions.checkArgument(clazz == org.bukkit.Material.class, "Item namespace must have material type");
|
|
|
|
Tags<Item> itemTags = TagsItem.a();
|
|
return itemTags.a().keySet().stream().map(key -> (org.bukkit.Tag<T>) new CraftItemTag(itemTags, key)).collect(ImmutableList.toImmutableList());
|
|
case org.bukkit.Tag.REGISTRY_FLUIDS:
|
|
Preconditions.checkArgument(clazz == org.bukkit.Material.class, "Fluid namespace must have fluid type");
|
|
|
|
Tags<FluidType> fluidTags = TagsFluid.a();
|
|
return fluidTags.a().keySet().stream().map(key -> (org.bukkit.Tag<T>) new CraftFluidTag(fluidTags, key)).collect(ImmutableList.toImmutableList());
|
|
default:
|
|
throw new IllegalArgumentException();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public LootTable getLootTable(NamespacedKey key) {
|
|
Validate.notNull(key, "NamespacedKey cannot be null");
|
|
|
|
LootTableRegistry registry = getServer().getLootTableRegistry();
|
|
return new CraftLootTable(key, registry.getLootTable(CraftNamespacedKey.toMinecraft(key)));
|
|
}
|
|
|
|
@Override
|
|
public List<Entity> selectEntities(CommandSender sender, String selector) {
|
|
Preconditions.checkArgument(selector != null, "Selector cannot be null");
|
|
Preconditions.checkArgument(sender != null, "Sender cannot be null");
|
|
|
|
ArgumentEntity arg = ArgumentEntity.multipleEntities();
|
|
List<? extends net.minecraft.world.entity.Entity> nms;
|
|
|
|
try {
|
|
StringReader reader = new StringReader(selector);
|
|
nms = arg.parse(reader, true).getEntities(VanillaCommandWrapper.getListener(sender));
|
|
Preconditions.checkArgument(!reader.canRead(), "Spurious trailing data in selector: " + selector);
|
|
} catch (CommandSyntaxException ex) {
|
|
throw new IllegalArgumentException("Could not parse selector: " + selector, ex);
|
|
}
|
|
|
|
return new ArrayList<>(Lists.transform(nms, (entity) -> entity.getBukkitEntity()));
|
|
}
|
|
|
|
@Deprecated
|
|
@Override
|
|
public UnsafeValues getUnsafe() {
|
|
return CraftMagicNumbers.INSTANCE;
|
|
}
|
|
}
|