001/* 002 * PlotSquared, a land and world management plugin for Minecraft. 003 * Copyright (C) IntellectualSites <https://intellectualsites.com> 004 * Copyright (C) IntellectualSites team and contributors 005 * 006 * This program is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * This program is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with this program. If not, see <https://www.gnu.org/licenses/>. 018 */ 019package com.plotsquared.core.command; 020 021import com.google.inject.Inject; 022import com.plotsquared.core.PlotSquared; 023import com.plotsquared.core.configuration.ConfigurationNode; 024import com.plotsquared.core.configuration.ConfigurationSection; 025import com.plotsquared.core.configuration.ConfigurationUtil; 026import com.plotsquared.core.configuration.InvalidConfigurationException; 027import com.plotsquared.core.configuration.Settings; 028import com.plotsquared.core.configuration.caption.TranslatableCaption; 029import com.plotsquared.core.configuration.file.YamlConfiguration; 030import com.plotsquared.core.events.TeleportCause; 031import com.plotsquared.core.inject.annotations.WorldConfig; 032import com.plotsquared.core.inject.annotations.WorldFile; 033import com.plotsquared.core.permissions.Permission; 034import com.plotsquared.core.player.PlotPlayer; 035import com.plotsquared.core.plot.PlotArea; 036import com.plotsquared.core.plot.PlotManager; 037import com.plotsquared.core.plot.world.PlotAreaManager; 038import com.plotsquared.core.setup.PlotAreaBuilder; 039import com.plotsquared.core.setup.SettingsNodesWrapper; 040import com.plotsquared.core.util.FileBytes; 041import com.plotsquared.core.util.FileUtils; 042import com.plotsquared.core.util.SetupUtils; 043import com.plotsquared.core.util.TabCompletions; 044import com.plotsquared.core.util.WorldUtil; 045import com.plotsquared.core.util.task.TaskManager; 046import org.checkerframework.checker.nullness.qual.NonNull; 047 048import java.io.File; 049import java.io.FileInputStream; 050import java.io.FileOutputStream; 051import java.io.IOException; 052import java.util.Collection; 053import java.util.Collections; 054import java.util.LinkedList; 055import java.util.List; 056import java.util.Set; 057import java.util.stream.Collectors; 058import java.util.zip.ZipEntry; 059import java.util.zip.ZipInputStream; 060import java.util.zip.ZipOutputStream; 061 062@CommandDeclaration(command = "template", 063 permission = "plots.admin", 064 usage = "/plot template [import | export] <world> <template>", 065 category = CommandCategory.ADMINISTRATION) 066public class Template extends SubCommand { 067 068 private final PlotAreaManager plotAreaManager; 069 private final YamlConfiguration worldConfiguration; 070 private final File worldFile; 071 private final SetupUtils setupUtils; 072 private final WorldUtil worldUtil; 073 074 @Inject 075 public Template( 076 final @NonNull PlotAreaManager plotAreaManager, 077 @WorldConfig final @NonNull YamlConfiguration worldConfiguration, 078 @WorldFile final @NonNull File worldFile, 079 final @NonNull SetupUtils setupUtils, 080 final @NonNull WorldUtil worldUtil 081 ) { 082 this.plotAreaManager = plotAreaManager; 083 this.worldConfiguration = worldConfiguration; 084 this.worldFile = worldFile; 085 this.setupUtils = setupUtils; 086 this.worldUtil = worldUtil; 087 } 088 089 public static boolean extractAllFiles(String world, String template) { 090 try { 091 File folder = 092 FileUtils.getFile(PlotSquared.platform().getDirectory(), Settings.Paths.TEMPLATES); 093 if (!folder.exists()) { 094 return false; 095 } 096 File output = PlotSquared.platform().getDirectory(); 097 if (!output.exists()) { 098 output.mkdirs(); 099 } 100 File input = new File(folder + File.separator + template + ".template"); 101 try (ZipInputStream zis = new ZipInputStream(new FileInputStream(input))) { 102 ZipEntry ze = zis.getNextEntry(); 103 byte[] buffer = new byte[2048]; 104 while (ze != null) { 105 if (!ze.isDirectory()) { 106 String name = ze.getName().replace('\\', File.separatorChar) 107 .replace('/', File.separatorChar); 108 File newFile = new File( 109 (output + File.separator + name).replaceAll("__TEMP_DIR__", world)); 110 File parent = newFile.getParentFile(); 111 if (parent != null) { 112 parent.mkdirs(); 113 } 114 try (FileOutputStream fos = new FileOutputStream(newFile)) { 115 int len; 116 while ((len = zis.read(buffer)) > 0) { 117 fos.write(buffer, 0, len); 118 } 119 } 120 } 121 ze = zis.getNextEntry(); 122 } 123 zis.closeEntry(); 124 } 125 return true; 126 } catch (IOException e) { 127 e.printStackTrace(); 128 return false; 129 } 130 } 131 132 public static byte[] getBytes(PlotArea plotArea) { 133 ConfigurationSection section = PlotSquared 134 .get() 135 .getWorldConfiguration() 136 .getConfigurationSection("worlds." + plotArea.getWorldName()); 137 YamlConfiguration config = new YamlConfiguration(); 138 String generator = PlotSquared.platform().setupUtils().getGenerator(plotArea); 139 if (generator != null) { 140 config.set("generator.plugin", generator); 141 } 142 for (String key : section.getKeys(true)) { 143 config.set(key, section.get(key)); 144 } 145 return config.saveToString().getBytes(); 146 } 147 148 public static void zipAll(String world, Set<FileBytes> files) throws IOException { 149 File output = FileUtils.getFile(PlotSquared.platform().getDirectory(), Settings.Paths.TEMPLATES); 150 output.mkdirs(); 151 try (FileOutputStream fos = new FileOutputStream( 152 output + File.separator + world + ".template"); 153 ZipOutputStream zos = new ZipOutputStream(fos)) { 154 155 for (FileBytes file : files) { 156 ZipEntry ze = new ZipEntry(file.path); 157 zos.putNextEntry(ze); 158 zos.write(file.data); 159 } 160 zos.closeEntry(); 161 } 162 } 163 164 @Override 165 public boolean onCommand(final PlotPlayer<?> player, String[] args) { 166 if (args.length != 2 && args.length != 3) { 167 if (args.length == 1) { 168 if (args[0].equalsIgnoreCase("export")) { 169 player.sendMessage( 170 TranslatableCaption.of("commandconfig.command_syntax"), 171 net.kyori.adventure.text.minimessage.Template.of("value", "/plot template export <world>") 172 ); 173 return true; 174 } else if (args[0].equalsIgnoreCase("import")) { 175 player.sendMessage( 176 TranslatableCaption.of("commandconfig.command_syntax"), 177 net.kyori.adventure.text.minimessage.Template.of("value", "/plot template import <world> <template>") 178 ); 179 return true; 180 } 181 } 182 sendUsage(player); 183 return true; 184 } 185 final String world = args[1]; 186 switch (args[0].toLowerCase()) { 187 case "import" -> { 188 if (args.length != 3) { 189 player.sendMessage( 190 TranslatableCaption.of("commandconfig.command_syntax"), 191 net.kyori.adventure.text.minimessage.Template.of("value", "/plot template import <world> <template>") 192 ); 193 return false; 194 } 195 if (this.plotAreaManager.hasPlotArea(world)) { 196 player.sendMessage( 197 TranslatableCaption.of("setup.setup_world_taken"), 198 net.kyori.adventure.text.minimessage.Template.of("value", world) 199 ); 200 return false; 201 } 202 boolean result = extractAllFiles(world, args[2]); 203 if (!result) { 204 player.sendMessage( 205 TranslatableCaption.of("template.invalid_template"), 206 net.kyori.adventure.text.minimessage.Template.of("value", args[2]) 207 ); 208 return false; 209 } 210 File worldFile = FileUtils.getFile( 211 PlotSquared.platform().getDirectory(), 212 Settings.Paths.TEMPLATES + File.separator + "tmp-data.yml" 213 ); 214 YamlConfiguration worldConfig = YamlConfiguration.loadConfiguration(worldFile); 215 this.worldConfiguration.set("worlds." + world, worldConfig.get("")); 216 try { 217 this.worldConfiguration.save(this.worldFile); 218 this.worldConfiguration.load(this.worldFile); 219 } catch (InvalidConfigurationException | IOException e) { 220 e.printStackTrace(); 221 } 222 String manager = 223 worldConfig.getString("generator.plugin", PlotSquared.platform().pluginName()); 224 String generator = worldConfig.getString("generator.init", manager); 225 PlotAreaBuilder builder = PlotAreaBuilder.newBuilder() 226 .plotAreaType(ConfigurationUtil.getType(worldConfig)) 227 .terrainType(ConfigurationUtil.getTerrain(worldConfig)) 228 .plotManager(manager) 229 .generatorName(generator) 230 .settingsNodesWrapper(new SettingsNodesWrapper(new ConfigurationNode[0], null)) 231 .worldName(world); 232 233 this.setupUtils.setupWorld(builder); 234 TaskManager.runTask(() -> { 235 player.teleport(this.worldUtil.getSpawn(world), TeleportCause.COMMAND_TEMPLATE); 236 player.sendMessage(TranslatableCaption.of("setup.setup_finished")); 237 }); 238 return true; 239 } 240 case "export" -> { 241 if (args.length != 2) { 242 player.sendMessage( 243 TranslatableCaption.of("commandconfig.command_syntax"), 244 net.kyori.adventure.text.minimessage.Template.of("value", "/plot template export <world>") 245 ); 246 return false; 247 } 248 final PlotArea area = this.plotAreaManager.getPlotAreaByString(world); 249 if (area == null) { 250 player.sendMessage( 251 TranslatableCaption.of("errors.not_valid_plot_world"), 252 net.kyori.adventure.text.minimessage.Template.of("value", args[1]) 253 ); 254 return false; 255 } 256 final PlotManager manager = area.getPlotManager(); 257 TaskManager.runTaskAsync(() -> { 258 try { 259 manager.exportTemplate(); 260 } catch (Exception e) { // Must recover from any exception thrown a third party template manager 261 e.printStackTrace(); 262 player.sendMessage( 263 TranslatableCaption.of("template.template_failed"), 264 net.kyori.adventure.text.minimessage.Template.of("value", e.getMessage()) 265 ); 266 return; 267 } 268 player.sendMessage(TranslatableCaption.of("setup.setup_finished")); 269 }); 270 return true; 271 } 272 default -> sendUsage(player); 273 } 274 return false; 275 } 276 277 @Override 278 public Collection<Command> tab(final PlotPlayer<?> player, final String[] args, final boolean space) { 279 if (args.length == 1) { 280 final List<String> completions = new LinkedList<>(); 281 if (player.hasPermission(Permission.PERMISSION_TEMPLATE_EXPORT)) { 282 completions.add("export"); 283 } 284 if (player.hasPermission(Permission.PERMISSION_TEMPLATE_IMPORT)) { 285 completions.add("import"); 286 } 287 final List<Command> commands = completions.stream().filter(completion -> completion 288 .toLowerCase() 289 .startsWith(args[0].toLowerCase())) 290 .map(completion -> new Command( 291 null, 292 true, 293 completion, 294 "", 295 RequiredType.NONE, 296 CommandCategory.ADMINISTRATION 297 ) { 298 }).collect(Collectors.toCollection(LinkedList::new)); 299 if (player.hasPermission(Permission.PERMISSION_TEMPLATE) && args[0].length() > 0) { 300 commands.addAll(TabCompletions.completePlayers(player, args[0], Collections.emptyList())); 301 } 302 return commands; 303 } 304 return TabCompletions.completePlayers(player, String.join(",", args).trim(), Collections.emptyList()); 305 } 306 307}