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}