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 net.kyori.adventure.text.Component; 047import net.kyori.adventure.text.minimessage.tag.Tag; 048import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; 049import org.checkerframework.checker.nullness.qual.NonNull; 050 051import java.io.File; 052import java.io.FileInputStream; 053import java.io.FileOutputStream; 054import java.io.IOException; 055import java.util.Collection; 056import java.util.Collections; 057import java.util.LinkedList; 058import java.util.List; 059import java.util.Set; 060import java.util.stream.Collectors; 061import java.util.zip.ZipEntry; 062import java.util.zip.ZipInputStream; 063import java.util.zip.ZipOutputStream; 064 065@CommandDeclaration(command = "template", 066 permission = "plots.admin", 067 usage = "/plot template [import | export] <world> <template>", 068 category = CommandCategory.ADMINISTRATION) 069public class Template extends SubCommand { 070 071 private final PlotAreaManager plotAreaManager; 072 private final YamlConfiguration worldConfiguration; 073 private final File worldFile; 074 private final SetupUtils setupUtils; 075 private final WorldUtil worldUtil; 076 077 @Inject 078 public Template( 079 final @NonNull PlotAreaManager plotAreaManager, 080 @WorldConfig final @NonNull YamlConfiguration worldConfiguration, 081 @WorldFile final @NonNull File worldFile, 082 final @NonNull SetupUtils setupUtils, 083 final @NonNull WorldUtil worldUtil 084 ) { 085 this.plotAreaManager = plotAreaManager; 086 this.worldConfiguration = worldConfiguration; 087 this.worldFile = worldFile; 088 this.setupUtils = setupUtils; 089 this.worldUtil = worldUtil; 090 } 091 092 public static boolean extractAllFiles(String world, String template) { 093 try { 094 File folder = 095 FileUtils.getFile(PlotSquared.platform().getDirectory(), Settings.Paths.TEMPLATES); 096 if (!folder.exists()) { 097 return false; 098 } 099 File output = PlotSquared.platform().getDirectory(); 100 if (!output.exists()) { 101 output.mkdirs(); 102 } 103 File input = new File(folder + File.separator + template + ".template"); 104 try (ZipInputStream zis = new ZipInputStream(new FileInputStream(input))) { 105 ZipEntry ze = zis.getNextEntry(); 106 byte[] buffer = new byte[2048]; 107 while (ze != null) { 108 if (!ze.isDirectory()) { 109 String name = ze.getName().replace('\\', File.separatorChar) 110 .replace('/', File.separatorChar); 111 File newFile = new File( 112 (output + File.separator + name).replaceAll("__TEMP_DIR__", world)); 113 File parent = newFile.getParentFile(); 114 if (parent != null) { 115 parent.mkdirs(); 116 } 117 try (FileOutputStream fos = new FileOutputStream(newFile)) { 118 int len; 119 while ((len = zis.read(buffer)) > 0) { 120 fos.write(buffer, 0, len); 121 } 122 } 123 } 124 ze = zis.getNextEntry(); 125 } 126 zis.closeEntry(); 127 } 128 return true; 129 } catch (IOException e) { 130 e.printStackTrace(); 131 return false; 132 } 133 } 134 135 public static byte[] getBytes(PlotArea plotArea) { 136 ConfigurationSection section = PlotSquared 137 .get() 138 .getWorldConfiguration() 139 .getConfigurationSection("worlds." + plotArea.getWorldName()); 140 YamlConfiguration config = new YamlConfiguration(); 141 String generator = PlotSquared.platform().setupUtils().getGenerator(plotArea); 142 if (generator != null) { 143 config.set("generator.plugin", generator); 144 } 145 for (String key : section.getKeys(true)) { 146 config.set(key, section.get(key)); 147 } 148 return config.saveToString().getBytes(); 149 } 150 151 public static void zipAll(String world, Set<FileBytes> files) throws IOException { 152 File output = FileUtils.getFile(PlotSquared.platform().getDirectory(), Settings.Paths.TEMPLATES); 153 output.mkdirs(); 154 try (FileOutputStream fos = new FileOutputStream( 155 output + File.separator + world + ".template"); 156 ZipOutputStream zos = new ZipOutputStream(fos)) { 157 158 for (FileBytes file : files) { 159 ZipEntry ze = new ZipEntry(file.path()); 160 zos.putNextEntry(ze); 161 zos.write(file.data()); 162 } 163 zos.closeEntry(); 164 } 165 } 166 167 @Override 168 public boolean onCommand(final PlotPlayer<?> player, String[] args) { 169 if (args.length != 2 && args.length != 3) { 170 if (args.length == 1) { 171 if (args[0].equalsIgnoreCase("export")) { 172 player.sendMessage( 173 TranslatableCaption.of("commandconfig.command_syntax"), 174 TagResolver.resolver("value", Tag.inserting(Component.text("/plot template export <world>"))) 175 ); 176 return true; 177 } else if (args[0].equalsIgnoreCase("import")) { 178 player.sendMessage( 179 TranslatableCaption.of("commandconfig.command_syntax"), 180 TagResolver.resolver( 181 "value", 182 Tag.inserting(Component.text("/plot template import <world> <template>")) 183 ) 184 ); 185 return true; 186 } 187 } 188 sendUsage(player); 189 return true; 190 } 191 final String world = args[1]; 192 switch (args[0].toLowerCase()) { 193 case "import" -> { 194 if (args.length != 3) { 195 player.sendMessage( 196 TranslatableCaption.of("commandconfig.command_syntax"), 197 TagResolver.resolver( 198 "value", 199 Tag.inserting(Component.text("/plot template import <world> <template>")) 200 ) 201 ); 202 return false; 203 } 204 if (this.plotAreaManager.hasPlotArea(world)) { 205 player.sendMessage( 206 TranslatableCaption.of("setup.setup_world_taken"), 207 TagResolver.resolver("value", Tag.inserting(Component.text(world))) 208 ); 209 return false; 210 } 211 boolean result = extractAllFiles(world, args[2]); 212 if (!result) { 213 player.sendMessage( 214 TranslatableCaption.of("template.invalid_template"), 215 TagResolver.resolver("value", Tag.inserting(Component.text(args[2]))) 216 ); 217 return false; 218 } 219 File worldFile = FileUtils.getFile( 220 PlotSquared.platform().getDirectory(), 221 Settings.Paths.TEMPLATES + File.separator + "tmp-data.yml" 222 ); 223 YamlConfiguration worldConfig = YamlConfiguration.loadConfiguration(worldFile); 224 this.worldConfiguration.set("worlds." + world, worldConfig.get("")); 225 try { 226 this.worldConfiguration.save(this.worldFile); 227 this.worldConfiguration.load(this.worldFile); 228 } catch (InvalidConfigurationException | IOException e) { 229 e.printStackTrace(); 230 } 231 String manager = 232 worldConfig.getString("generator.plugin", PlotSquared.platform().pluginName()); 233 String generator = worldConfig.getString("generator.init", manager); 234 PlotAreaBuilder builder = PlotAreaBuilder.newBuilder() 235 .plotAreaType(ConfigurationUtil.getType(worldConfig)) 236 .terrainType(ConfigurationUtil.getTerrain(worldConfig)) 237 .plotManager(manager) 238 .generatorName(generator) 239 .settingsNodesWrapper(new SettingsNodesWrapper(new ConfigurationNode[0], null)) 240 .worldName(world); 241 242 this.setupUtils.setupWorld(builder); 243 TaskManager.runTask(() -> { 244 player.teleport(this.worldUtil.getSpawn(world), TeleportCause.COMMAND_TEMPLATE); 245 player.sendMessage(TranslatableCaption.of("setup.setup_finished")); 246 }); 247 return true; 248 } 249 case "export" -> { 250 if (args.length != 2) { 251 player.sendMessage( 252 TranslatableCaption.of("commandconfig.command_syntax"), 253 TagResolver.resolver("value", Tag.inserting(Component.text("/plot template export <world>"))) 254 ); 255 return false; 256 } 257 final PlotArea area = this.plotAreaManager.getPlotAreaByString(world); 258 if (area == null) { 259 player.sendMessage( 260 TranslatableCaption.of("errors.not_valid_plot_world"), 261 TagResolver.resolver("value", Tag.inserting(Component.text(args[1]))) 262 ); 263 return false; 264 } 265 final PlotManager manager = area.getPlotManager(); 266 TaskManager.runTaskAsync(() -> { 267 try { 268 manager.exportTemplate(); 269 } catch (Exception e) { // Must recover from any exception thrown a third party template manager 270 e.printStackTrace(); 271 player.sendMessage( 272 TranslatableCaption.of("template.template_failed"), 273 TagResolver.resolver("value", Tag.inserting(Component.text(e.getMessage()))) 274 ); 275 return; 276 } 277 player.sendMessage(TranslatableCaption.of("setup.setup_finished")); 278 }); 279 return true; 280 } 281 default -> sendUsage(player); 282 } 283 return false; 284 } 285 286 @Override 287 public Collection<Command> tab(final PlotPlayer<?> player, final String[] args, final boolean space) { 288 if (args.length == 1) { 289 final List<String> completions = new LinkedList<>(); 290 if (player.hasPermission(Permission.PERMISSION_TEMPLATE_EXPORT)) { 291 completions.add("export"); 292 } 293 if (player.hasPermission(Permission.PERMISSION_TEMPLATE_IMPORT)) { 294 completions.add("import"); 295 } 296 final List<Command> commands = completions.stream().filter(completion -> completion 297 .toLowerCase() 298 .startsWith(args[0].toLowerCase())) 299 .map(completion -> new Command( 300 null, 301 true, 302 completion, 303 "", 304 RequiredType.NONE, 305 CommandCategory.ADMINISTRATION 306 ) { 307 }).collect(Collectors.toCollection(LinkedList::new)); 308 if (player.hasPermission(Permission.PERMISSION_TEMPLATE) && args[0].length() > 0) { 309 commands.addAll(TabCompletions.completePlayers(player, args[0], Collections.emptyList())); 310 } 311 return commands; 312 } 313 return TabCompletions.completePlayers(player, String.join(",", args).trim(), Collections.emptyList()); 314 } 315 316}