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.components; 020 021import com.google.inject.Inject; 022import com.plotsquared.core.PlotSquared; 023import com.plotsquared.core.backup.BackupManager; 024import com.plotsquared.core.command.MainCommand; 025import com.plotsquared.core.configuration.caption.TranslatableCaption; 026import com.plotsquared.core.configuration.file.YamlConfiguration; 027import com.plotsquared.core.configuration.serialization.ConfigurationSerialization; 028import com.plotsquared.core.generator.ClassicPlotManagerComponent; 029import com.plotsquared.core.permissions.Permission; 030import com.plotsquared.core.player.PlotPlayer; 031import com.plotsquared.core.plot.Plot; 032import com.plotsquared.core.plot.PlotInventory; 033import com.plotsquared.core.plot.PlotItemStack; 034import com.plotsquared.core.queue.QueueCoordinator; 035import com.plotsquared.core.util.EconHandler; 036import com.plotsquared.core.util.InventoryUtil; 037import com.plotsquared.core.util.PatternUtil; 038import com.sk89q.worldedit.function.pattern.Pattern; 039import com.sk89q.worldedit.world.item.ItemTypes; 040import net.kyori.adventure.text.minimessage.MiniMessage; 041import net.kyori.adventure.text.minimessage.Template; 042import org.apache.logging.log4j.LogManager; 043import org.apache.logging.log4j.Logger; 044import org.checkerframework.checker.nullness.qual.NonNull; 045import org.checkerframework.checker.nullness.qual.Nullable; 046 047import java.io.File; 048import java.io.IOException; 049import java.nio.file.Files; 050import java.nio.file.Path; 051import java.nio.file.Paths; 052import java.util.ArrayList; 053import java.util.Collections; 054import java.util.List; 055import java.util.Map; 056import java.util.Objects; 057import java.util.stream.Collectors; 058 059public class ComponentPresetManager { 060 061 private static final MiniMessage MINI_MESSAGE = MiniMessage.builder().build(); 062 private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + ComponentPresetManager.class.getSimpleName()); 063 064 private final List<ComponentPreset> presets; 065 private final EconHandler econHandler; 066 private final InventoryUtil inventoryUtil; 067 private File componentsFile; 068 069 @SuppressWarnings("unchecked") 070 @Inject 071 public ComponentPresetManager(final @NonNull EconHandler econHandler, final @NonNull InventoryUtil inventoryUtil) throws 072 IOException { 073 this.econHandler = econHandler; 074 this.inventoryUtil = inventoryUtil; 075 final File oldLocation = new File(Objects.requireNonNull(PlotSquared.platform()).getDirectory(), "components.yml"); 076 final File folder = new File(Objects.requireNonNull(PlotSquared.platform()).getDirectory(), "config"); 077 if (!folder.exists() && !folder.mkdirs()) { 078 LOGGER.error("Failed to create the /plugins/PlotSquared/config folder. Please create it manually"); 079 } 080 if (oldLocation.exists()) { 081 Path oldLoc = Paths.get(PlotSquared.platform().getDirectory() + "/components.yml"); 082 Path newLoc = Paths.get(PlotSquared.platform().getDirectory() + "/config" + "/components.yml"); 083 Files.move(oldLoc, newLoc); 084 } 085 try { 086 this.componentsFile = new File(folder, "components.yml"); 087 if (!this.componentsFile.exists() && !this.componentsFile.createNewFile()) { 088 LOGGER.error("Could not create the components.yml file. Please create 'components.yml' manually."); 089 } 090 } catch (IOException e) { 091 e.printStackTrace(); 092 } 093 094 ConfigurationSerialization.registerClass(ComponentPreset.class, "ComponentPreset"); 095 096 final YamlConfiguration yamlConfiguration = YamlConfiguration.loadConfiguration(this.componentsFile); 097 098 if (yamlConfiguration.contains("title")) { 099 yamlConfiguration.set("title", "#Now in /lang/messages_%.json, preset.title"); 100 try { 101 yamlConfiguration.save(this.componentsFile); 102 } catch (IOException e) { 103 LOGGER.error("Failed to save default values to components.yml", e); 104 } 105 } 106 107 if (yamlConfiguration.contains("presets")) { 108 this.presets = yamlConfiguration 109 .getMapList("presets") 110 .stream() 111 .map(o -> (Map<String, Object>) o) 112 .map(ComponentPreset::deserialize) 113 .collect(Collectors.toList()); 114 } else { 115 final List<ComponentPreset> defaultPreset = Collections.singletonList( 116 new ComponentPreset( 117 ClassicPlotManagerComponent.FLOOR, 118 "##wool", 119 0, 120 "", 121 "<rainbow:2>Disco Floor</rainbow>", 122 List.of("<gold>Spice up your plot floor</gold>"), 123 ItemTypes.YELLOW_WOOL 124 )); 125 yamlConfiguration.set("presets", defaultPreset.stream().map(ComponentPreset::serialize).collect(Collectors.toList())); 126 try { 127 yamlConfiguration.save(this.componentsFile); 128 } catch (final IOException e) { 129 LOGGER.error("Failed to save default values to components.yml", e); 130 } 131 this.presets = defaultPreset; 132 } 133 134 MainCommand.getInstance().register(new ComponentCommand(this)); 135 } 136 137 /** 138 * Build the component inventory for a player. This also checks 139 * if the player is in a compatible plot, and sends appropriate 140 * error messages if not 141 * 142 * @param player player 143 * @return Build inventory, if it could be created 144 */ 145 public @Nullable PlotInventory buildInventory(final PlotPlayer<?> player) { 146 final Plot plot = player.getCurrentPlot(); 147 148 if (plot == null) { 149 player.sendMessage(TranslatableCaption.of("errors.not_in_plot")); 150 return null; 151 } else if (!plot.hasOwner()) { 152 player.sendMessage(TranslatableCaption.of("info.plot_unowned")); 153 return null; 154 } else if (!plot.isOwner(player.getUUID()) && !plot.getTrusted().contains(player.getUUID()) && !player.hasPermission( 155 Permission.PERMISSION_ADMIN_COMPONENTS_OTHER 156 )) { 157 player.sendMessage(TranslatableCaption.of("permission.no_plot_perms")); 158 return null; 159 } else if (plot.getVolume() > Integer.MAX_VALUE) { 160 player.sendMessage(TranslatableCaption.of("schematics.schematic_too_large")); 161 return null; 162 } 163 164 final List<ComponentPreset> allowedPresets = new ArrayList<>(this.presets.size()); 165 for (final ComponentPreset componentPreset : this.presets) { 166 if (!componentPreset.getPermission().isEmpty() && !player.hasPermission( 167 componentPreset.getPermission() 168 )) { 169 continue; 170 } 171 allowedPresets.add(componentPreset); 172 } 173 if (allowedPresets.isEmpty()) { 174 player.sendMessage(TranslatableCaption.of("preset.empty")); 175 return null; 176 } 177 final int size = (int) Math.ceil((double) allowedPresets.size() / 9.0D); 178 final PlotInventory plotInventory = new PlotInventory(this.inventoryUtil, player, size, 179 TranslatableCaption.of("preset.title").getComponent(player)) { 180 @Override 181 public boolean onClick(final int index) { 182 if (!getPlayer().getCurrentPlot().equals(plot)) { 183 return false; 184 } 185 186 if (index < 0 || index >= allowedPresets.size()) { 187 return false; 188 } 189 190 final ComponentPreset componentPreset = allowedPresets.get(index); 191 if (componentPreset == null) { 192 return false; 193 } 194 195 if (plot.getRunning() > 0) { 196 getPlayer().sendMessage(TranslatableCaption.of("errors.wait_for_timer")); 197 return false; 198 } 199 200 final Pattern pattern = PatternUtil.parse(null, componentPreset.getPattern(), false); 201 if (pattern == null) { 202 getPlayer().sendMessage(TranslatableCaption.of("preset.preset_invalid")); 203 return false; 204 } 205 206 if (componentPreset.getCost() > 0.0D) { 207 if (!econHandler.isEnabled(plot.getArea())) { 208 getPlayer().sendMessage( 209 TranslatableCaption.of("preset.economy_disabled"), 210 Template.of("preset", componentPreset.getDisplayName())); 211 return false; 212 } 213 if (econHandler.getMoney(getPlayer()) < componentPreset.getCost()) { 214 getPlayer().sendMessage(TranslatableCaption.of("preset.preset_cannot_afford")); 215 return false; 216 } else { 217 econHandler.withdrawMoney(getPlayer(), componentPreset.getCost()); 218 getPlayer().sendMessage( 219 TranslatableCaption.of("economy.removed_balance"), 220 Template.of("money", econHandler.format(componentPreset.getCost())) 221 ); 222 } 223 } 224 225 BackupManager.backup(getPlayer(), plot, () -> { 226 plot.addRunning(); 227 QueueCoordinator queue = plot.getArea().getQueue(); 228 queue.setCompleteTask(plot::removeRunning); 229 for (Plot current : plot.getConnectedPlots()) { 230 current.getPlotModificationManager().setComponent( 231 componentPreset.getComponent().name(), 232 pattern, 233 player, 234 queue 235 ); 236 } 237 queue.enqueue(); 238 getPlayer().sendMessage(TranslatableCaption.of("working.generating_component")); 239 }); 240 return false; 241 } 242 }; 243 244 245 for (int i = 0; i < allowedPresets.size(); i++) { 246 final ComponentPreset preset = allowedPresets.get(i); 247 final List<String> lore = new ArrayList<>(); 248 if (preset.getCost() > 0) { 249 if (!this.econHandler.isEnabled(plot.getArea())) { 250 lore.add(MINI_MESSAGE.serialize(MINI_MESSAGE.parse( 251 TranslatableCaption.of("preset.preset_lore_economy_disabled").getComponent(player)))); 252 } else { 253 lore.add(MINI_MESSAGE.serialize(MINI_MESSAGE.parse( 254 TranslatableCaption.of("preset.preset_lore_cost").getComponent(player), 255 Template.of("cost", String.format("%.2f", preset.getCost())) 256 ))); 257 } 258 } 259 lore.add(MINI_MESSAGE.serialize(MINI_MESSAGE.parse( 260 TranslatableCaption.of("preset.preset_lore_component").getComponent(player), 261 Template.of("component", preset.getComponent().name().toLowerCase()), 262 Template.of("prefix", TranslatableCaption.of("core.prefix").getComponent(player)) 263 ))); 264 lore.removeIf(String::isEmpty); 265 lore.addAll(preset.getDescription()); 266 plotInventory.setItem( 267 i, 268 new PlotItemStack( 269 preset.getIcon().getId().replace("minecraft:", ""), 270 1, 271 preset.getDisplayName(), 272 lore.toArray(new String[0]) 273 ) 274 ); 275 } 276 277 return plotInventory; 278 } 279 280}