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