Implements Template reading.

Add template loading via Admin Panel.
Improve LibraryPanel so it could find json and yml files.
This commit is contained in:
BONNe 2021-09-18 21:37:30 +03:00
parent 535cde825e
commit 29565538c3
12 changed files with 1521 additions and 675 deletions

View File

@ -314,6 +314,10 @@ public class ChallengesAddon extends Addon {
this.saveResource("panels/main_panel.yml", false);
this.saveResource("panels/multiple_panel.yml",false);
this.saveResource("panels/gamemode_panel.yml",false);
// Save template
this.saveResource("template.yml",false);
this.saveResource("default.json",false);
}

View File

@ -97,7 +97,8 @@ public class DefaultsCommand extends CompositeCommand
@Override
public boolean execute(User user, String label, List<String> args)
{
return DefaultsCommand.this.addon.getImportManager().loadDefaultChallenges(user, this.getWorld());
DefaultsCommand.this.addon.getImportManager().loadDownloadedChallenges(user, this.getWorld(), "default");
return true;
}
}
@ -135,10 +136,12 @@ public class DefaultsCommand extends CompositeCommand
@Override
public boolean execute(User user, String label, List<String> args)
{
return DefaultsCommand.this.addon.getImportManager().generateDefaultChallengeFile(
DefaultsCommand.this.addon.getImportManager().generateDatabaseFile(
user,
this.getWorld(),
!args.isEmpty() && args.get(0).equalsIgnoreCase("overwrite"));
"defaults");
return true;
}

View File

@ -1,19 +1,7 @@
package world.bentobox.challenges.managers;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;
import java.util.*;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
@ -24,6 +12,7 @@ import org.bukkit.entity.EntityType;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.api.addons.GameModeAddon;
import world.bentobox.bentobox.api.logs.LogEntry;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.bentobox.database.Database;
@ -591,29 +580,45 @@ public class ChallengesManager
/**
* This method removes all challenges addon data from Database.
* @param complete Remove also user data.
* @param name Name of the GameMode.
*/
public void wipeDatabase(boolean complete)
public void wipeDatabase(boolean complete, String name)
{
this.wipeLevels();
this.wipeChallenges();
this.wipeLevels(name);
this.wipeChallenges(name);
if (complete)
{
this.wipePlayers();
this.wipePlayers(name);
}
}
/**
* This method removes all challenges addon data from Database.
* @param name Name of the GameMode.
*/
public void wipeDatabase(String name)
{
this.wipeDatabase(false, name);
}
/**
* This method collects all data from levels database and removes them.
* Also clears levels cache data.
*/
private void wipeLevels()
private void wipeLevels(String gamemode)
{
List<ChallengeLevel> levelList = this.levelDatabase.loadObjects();
levelList.forEach(level -> this.levelDatabase.deleteID(level.getUniqueId()));
this.levelCacheData.clear();
levelList.stream().
filter(level -> level.getUniqueId().startsWith(gamemode.toLowerCase()) ||
level.getUniqueId().startsWith(gamemode)).
forEach(level -> {
this.levelDatabase.deleteID(level.getUniqueId());
this.levelCacheData.remove(level.getUniqueId());
});
}
@ -621,12 +626,17 @@ public class ChallengesManager
* This method collects all data from challenges database and removes them.
* Also clears challenges cache data.
*/
private void wipeChallenges()
private void wipeChallenges(String gamemode)
{
List<Challenge> challengeList = this.challengeDatabase.loadObjects();
challengeList.forEach(challenge -> this.challengeDatabase.deleteID(challenge.getUniqueId()));
this.challengeCacheData.clear();
challengeList.stream().
filter(challenge -> challenge.getUniqueId().startsWith(gamemode.toLowerCase()) ||
challenge.getUniqueId().startsWith(gamemode)).
forEach(challenge -> {
this.challengeDatabase.deleteID(challenge.getUniqueId());
this.challengeCacheData.remove(challenge.getUniqueId());
});
}
@ -634,12 +644,15 @@ public class ChallengesManager
* This method collects all data from players database and removes them.
* Also clears players cache data.
*/
public void wipePlayers()
public void wipePlayers(String gamemode)
{
List<ChallengesPlayerData> playerDataList = this.playersDatabase.loadObjects();
playerDataList.forEach(playerData -> this.playersDatabase.deleteID(playerData.getUniqueId()));
this.playerCacheData.clear();
List<ChallengesPlayerData> playerDataList = this.playersDatabase.loadObjects();
playerDataList.forEach(playerData -> {
playerData.reset(gamemode);
this.playersDatabase.saveObjectAsync(playerData);
});
}

View File

@ -102,6 +102,15 @@ public abstract class CommonPanel
protected abstract void build();
/**
* This method reopens given panel.
* @param panel Panel that must be reopened.
*/
public static void reopen(CommonPanel panel)
{
panel.build();
}
// ---------------------------------------------------------------------
// Section: Common methods

View File

@ -1,6 +1,7 @@
package world.bentobox.challenges.panel.admin;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
@ -104,11 +105,11 @@ public class AdminPanel extends CommonPanel
panelBuilder.item(31, this.createButton(Button.DELETE_LEVEL));
// Import Challenges
panelBuilder.item(15, this.createButton(Button.IMPORT_CHALLENGES));
panelBuilder.item(24, this.createButton(Button.LIBRARY));
// Not added as I do not think admins should use it. It still will be able via command.
// panelBuilder.item(33, this.createButton(Button.DEFAULT_EXPORT_CHALLENGES));
panelBuilder.item(14, this.createButton(Button.IMPORT_TEMPLATE));
panelBuilder.item(15, this.createButton(Button.IMPORT_DATABASE));
panelBuilder.item(33, this.createButton(Button.LIBRARY));
// Export Challenges
panelBuilder.item(24, this.createButton(Button.EXPORT_CHALLENGES));
// Edit Addon Settings
panelBuilder.item(16, this.createButton(Button.EDIT_SETTINGS));
@ -326,10 +327,21 @@ public class AdminPanel extends CommonPanel
description.add("");
description.add(this.user.getTranslationOrNothing(Constants.TIPS + "click-to-open"));
}
case IMPORT_CHALLENGES -> {
icon = new ItemStack(Material.HOPPER);
case IMPORT_DATABASE -> {
icon = new ItemStack(Material.BOOKSHELF);
clickHandler = (panel, user, clickType, slot) -> {
// TODO: Importing GUI.
LibraryPanel.open(this, LibraryPanel.Library.DATABASE);
return true;
};
glow = true;
description.add("");
description.add(this.user.getTranslationOrNothing(Constants.TIPS + "click-to-open"));
}
case IMPORT_TEMPLATE -> {
icon = new ItemStack(Material.BOOKSHELF);
clickHandler = (panel, user, clickType, slot) -> {
LibraryPanel.open(this, LibraryPanel.Library.TEMPLATE);
return true;
};
glow = false;
@ -340,7 +352,36 @@ public class AdminPanel extends CommonPanel
case EXPORT_CHALLENGES -> {
icon = new ItemStack(Material.HOPPER);
clickHandler = (panel, user, clickType, slot) -> {
// TODO: Exporting GUI.
// This consumer process file exporting after user input is returned.
Consumer<String> fileNameConsumer = value -> {
if (value != null)
{
this.addon.getImportManager().generateDatabaseFile(this.user,
this.world,
Utils.sanitizeInput(value));
}
this.build();
};
// This function checks if file can be created.
Function<String, Boolean> validationFunction = fileName ->
{
String sanitizedName = Utils.sanitizeInput(fileName);
return !new File(this.addon.getDataFolder(),
sanitizedName.endsWith(".json") ? sanitizedName : sanitizedName + ".json").exists();
};
// Call a conversation API to get input string.
ConversationUtils.createIDStringInput(fileNameConsumer,
validationFunction,
this.user,
this.user.getTranslation(Constants.CONVERSATIONS + "exported-file-name"),
this.user.getTranslation(Constants.CONVERSATIONS + "database-export-completed",
Constants.WORLD, world.getName()),
Constants.CONVERSATIONS + "file-name-exist");
return true;
};
glow = false;
@ -361,7 +402,7 @@ public class AdminPanel extends CommonPanel
clickHandler = (panel, user, clickType, slot) -> {
if (WebManager.isEnabled())
{
ListLibraryPanel.open(this);
LibraryPanel.open(this, LibraryPanel.Library.WEB);
}
return true;
@ -385,7 +426,8 @@ public class AdminPanel extends CommonPanel
Consumer<Boolean> consumer = value -> {
if (value)
{
this.addon.getChallengesManager().wipeDatabase(this.wipeAll);
this.addon.getChallengesManager().wipeDatabase(this.wipeAll,
Utils.getGameMode(this.world));
}
this.build();
@ -423,7 +465,8 @@ public class AdminPanel extends CommonPanel
Consumer<Boolean> consumer = value -> {
if (value)
{
this.addon.getChallengesManager().wipeDatabase(this.wipeAll);
this.addon.getChallengesManager().wipeDatabase(this.wipeAll,
Utils.getGameMode(this.world));
}
this.build();
@ -454,7 +497,7 @@ public class AdminPanel extends CommonPanel
Consumer<Boolean> consumer = value -> {
if (value)
{
this.addon.getChallengesManager().wipePlayers();
this.addon.getChallengesManager().wipePlayers(Utils.getGameMode(this.world));
}
this.build();
@ -512,7 +555,8 @@ public class AdminPanel extends CommonPanel
DELETE_CHALLENGE,
DELETE_LEVEL,
EDIT_SETTINGS,
IMPORT_CHALLENGES,
IMPORT_DATABASE,
IMPORT_TEMPLATE,
EXPORT_CHALLENGES,
/**
* Allows to remove whole database

View File

@ -0,0 +1,439 @@
package world.bentobox.challenges.panel.admin;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.scheduler.BukkitTask;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.PanelListener;
import world.bentobox.bentobox.api.panels.builders.PanelBuilder;
import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.challenges.panel.CommonPagedPanel;
import world.bentobox.challenges.panel.CommonPanel;
import world.bentobox.challenges.panel.ConversationUtils;
import world.bentobox.challenges.utils.Constants;
import world.bentobox.challenges.utils.GuiUtils;
import world.bentobox.challenges.utils.Utils;
import world.bentobox.challenges.web.object.LibraryEntry;
/**
* This class contains all necessary elements to create GUI that lists all challenges.
* It allows to edit them or remove, depending on given input mode.
*/
public class LibraryPanel extends CommonPagedPanel
{
// ---------------------------------------------------------------------
// Section: Constructor
// ---------------------------------------------------------------------
/**
* @param parentGUI ParentGUI object.
*/
private LibraryPanel(CommonPanel parentGUI, Library mode)
{
super(parentGUI);
this.mode = mode;
this.libraryEntries = switch (mode)
{
case WEB -> this.addon.getWebManager().getLibraryEntries();
case DATABASE -> this.generateDatabaseEntries();
case TEMPLATE -> this.generateTemplateEntries();
};
}
/**
* This static method allows to easier open Library GUI.
* @param parentGui ParentGUI object.
* @param mode Library view mode.
*/
public static void open(CommonPanel parentGui, Library mode)
{
new LibraryPanel(parentGui, mode).build();
}
// ---------------------------------------------------------------------
// Section: Data Collectors
// ---------------------------------------------------------------------
/**
* This method generates list of database file entries.
*
* @return List of entries for database files.
*/
private List<LibraryEntry> generateDatabaseEntries()
{
File localeDir = this.addon.getDataFolder();
File[] files = localeDir.listFiles(pathname ->
pathname.getName().endsWith(".json") && pathname.isFile());
if (files == null || files.length == 0)
{
// No
return Collections.emptyList();
}
return Arrays.stream(files).
map(file -> LibraryEntry.fromTemplate(
file.getName().substring(0, file.getName().length() - 5),
Material.PAPER)).
collect(Collectors.toList());
}
/**
* This method generates list of template file entries.
*
* @return List of entries for template files.
*/
private List<LibraryEntry> generateTemplateEntries()
{
File localeDir = this.addon.getDataFolder();
File[] files = localeDir.listFiles(pathname ->
pathname.getName().endsWith(".yml") &&
pathname.isFile() &&
!pathname.getName().equals("config.yml"));
if (files == null || files.length == 0)
{
// No
return Collections.emptyList();
}
return Arrays.stream(files).
map(file -> LibraryEntry.fromTemplate(
file.getName().substring(0, file.getName().length() - 4),
Material.PAPER)).
collect(Collectors.toList());
}
// ---------------------------------------------------------------------
// Section: Methods
// ---------------------------------------------------------------------
/**
* {@inheritDoc}
*/
@Override
protected void build()
{
if (this.libraryEntries.isEmpty())
{
Utils.sendMessage(this.user, this.user.getTranslation(
Constants.ERRORS + "no-library-entries"));
return;
}
// No point to display. Single element.
if (this.libraryEntries.size() == 1 && !this.mode.equals(Library.WEB))
{
this.generateConfirmationInput(this.libraryEntries.get(0));
return;
}
PanelBuilder panelBuilder = new PanelBuilder().user(this.user).name(
this.user.getTranslation(Constants.TITLE + "library"));
GuiUtils.fillBorder(panelBuilder);
this.populateElements(panelBuilder,
this.libraryEntries,
o -> this.createEntryIcon((LibraryEntry) o));
if (this.mode == Library.WEB)
{
panelBuilder.item(4, this.createDownloadNow());
}
panelBuilder.item(44, this.returnButton);
panelBuilder.listener(new DownloadCanceller());
panelBuilder.build();
}
/**
* This creates download now button, that can skip waiting for automatic request.
* @return PanelItem button that allows to manually download libraries.
*/
private PanelItem createDownloadNow()
{
final String reference = Constants.BUTTON + "download.";
final List<String> description = new ArrayList<>(3);
description.add(this.user.getTranslation(reference + "description"));
description.add(this.user.getTranslation(reference +
(this.clearCache ? "enabled" : "disabled")));
description.add("");
description.add(this.user.getTranslation(Constants.TIPS + "left-click-to-download"));
description.add(this.user.getTranslation(Constants.TIPS + "right-click-to-toggle"));
PanelItemBuilder itemBuilder = new PanelItemBuilder().
name(this.user.getTranslation(reference + "name")).
description(description).
icon(Material.HOPPER).
glow(this.clearCache);
itemBuilder.clickHandler((panel, user1, clickType, slot) ->
{
if (clickType.isRightClick())
{
this.clearCache = !this.clearCache;
panel.getInventory().setItem(slot, this.createDownloadNow().getItem());
}
else
{
this.addon.getWebManager().requestCatalogGitHubData(this.clearCache);
// Fix multiclick issue.
if (this.updateTask != null)
{
this.updateTask.cancel();
}
// add some delay to rebuilding gui.
this.updateTask = this.addon.getPlugin().getServer().getScheduler().runTaskLater(
this.addon.getPlugin(),
this::build,
100L);
}
return true;
});
return itemBuilder.build();
}
/**
* This method creates button for given library entry.
* @param libraryEntry LibraryEntry which button must be created.
* @return Entry button.
*/
private PanelItem createEntryIcon(LibraryEntry libraryEntry)
{
PanelItemBuilder itemBuilder = new PanelItemBuilder().
name(ChatColor.translateAlternateColorCodes('&', libraryEntry.name())).
description(this.generateEntryDescription(libraryEntry)).
description("").
description(this.user.getTranslation(Constants.TIPS + "click-to-install")).
icon(libraryEntry.icon()).
glow(false);
itemBuilder.clickHandler((panel, user1, clickType, i) -> {
this.generateConfirmationInput(libraryEntry);
return true;
});
return itemBuilder.build();
}
/**
* This method generates consumer and calls ConversationAPI for confirmation that processes file downloading,
* importing and gui opening or closing.
*
* @param libraryEntry Entry that must be processed.
*/
private void generateConfirmationInput(LibraryEntry libraryEntry)
{
Consumer<Boolean> consumer = value ->
{
if (value)
{
switch (this.mode)
{
case TEMPLATE -> {
this.addon.getImportManager().importFile(this.user,
this.world,
libraryEntry.name());
CommonPanel.reopen(this.parentPanel != null ? this.parentPanel : this);
}
case DATABASE -> {
this.addon.getImportManager().importDatabaseFile(this.user,
this.world,
libraryEntry.name());
CommonPanel.reopen(this.parentPanel != null ? this.parentPanel : this);
}
case WEB -> {
if (!this.blockedForDownland)
{
this.blockedForDownland = true;
Utils.sendMessage(this.user, this.user.getTranslation(
Constants.MESSAGES + "start-downloading"));
// Run download task after 5 ticks.
this.updateTask = this.addon.getPlugin().getServer().getScheduler().
runTaskLaterAsynchronously(
this.addon.getPlugin(),
() -> this.addon.getWebManager().requestEntryGitHubData(this.user,
this.world,
libraryEntry),
5L);
}
CommonPanel.reopen(this.parentPanel != null ? this.parentPanel : this);
}
}
}
if (this.mode.equals(Library.WEB) || this.libraryEntries.size() > 1)
{
this.build();
}
};
ConversationUtils.createConfirmation(
consumer,
this.user,
this.user.getTranslation(Constants.CONVERSATIONS + "confirm-data-replacement",
Constants.GAMEMODE, Utils.getGameMode(this.world)),
this.user.getTranslation(Constants.CONVERSATIONS + "new-challenges-imported",
Constants.GAMEMODE, Utils.getGameMode(this.world)));
}
/**
* This method generated description for LibraryEntry object.
* @param entry LibraryEntry object which description must be generated.
* @return List of strings that will be placed in ItemStack lore message.
*/
private List<String> generateEntryDescription(LibraryEntry entry)
{
final String reference = Constants.DESCRIPTIONS + "library.";
List<String> description = new ArrayList<>();
description.add(this.user.getTranslation(reference + "author",
"[author]", entry.author()));
description.add(entry.description());
description.add(this.user.getTranslation(reference + "gamemode",
"[gamemode]", entry.gameMode()));
description.add(this.user.getTranslation(reference + "lang",
"[lang]", entry.language()));
description.add(this.user.getTranslation(reference + "version",
"[version]", entry.version()));
return description;
}
/**
* This class allows changing icon for Generator Tier
*/
private class DownloadCanceller implements PanelListener
{
/**
* On inventory click.
*
* @param user the user
* @param event the event
*/
@Override
public void onInventoryClick(User user, InventoryClickEvent event)
{
// do nothing
}
/**
* On inventory close.
*
* @param event the event
*/
@Override
public void onInventoryClose(InventoryCloseEvent event)
{
if (LibraryPanel.this.updateTask != null)
{
LibraryPanel.this.updateTask.cancel();
}
}
/**
* Setup current listener.
*/
@Override
public void setup()
{
// Do nothing
}
}
/**
* Enum that holds different view modes for current panel.
*/
public enum Library
{
/**
* Mode for templates available in main folder.
*/
TEMPLATE,
/**
* Mode for database files available in main folder.
*/
DATABASE,
/**
* Mode for web library.
*/
WEB
}
// ---------------------------------------------------------------------
// Section: Instance Variables
// ---------------------------------------------------------------------
/**
* Indicates if download now button should trigger cache clearing.
*/
private boolean clearCache;
/**
* Stores update task that is triggered.
*/
private BukkitTask updateTask = null;
/**
* This variable will protect against spam-click.
*/
private boolean blockedForDownland;
/**
* Stores active library that must be searched.
*/
private final Library mode;
/**
* List of library elements.
*/
private final List<LibraryEntry> libraryEntries;
}

View File

@ -1,306 +0,0 @@
package world.bentobox.challenges.panel.admin;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.scheduler.BukkitTask;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.PanelListener;
import world.bentobox.bentobox.api.panels.builders.PanelBuilder;
import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.challenges.ChallengesAddon;
import world.bentobox.challenges.panel.CommonPagedPanel;
import world.bentobox.challenges.panel.CommonPanel;
import world.bentobox.challenges.utils.Constants;
import world.bentobox.challenges.utils.GuiUtils;
import world.bentobox.challenges.utils.Utils;
import world.bentobox.challenges.web.object.LibraryEntry;
/**
* This class contains all necessary elements to create GUI that lists all challenges.
* It allows to edit them or remove, depending on given input mode.
*/
public class ListLibraryPanel extends CommonPagedPanel
{
// ---------------------------------------------------------------------
// Section: Constructor
// ---------------------------------------------------------------------
/**
* @param parentGUI ParentGUI object.
*/
private ListLibraryPanel(CommonPanel parentGUI)
{
super(parentGUI);
}
/**
* @param addon Addon where panel operates.
* @param world World from which panel was created.
* @param user User who created panel.
* @param topLabel Command top label which creates panel (f.e. island or ai)
* @param permissionPrefix Command permission prefix (f.e. bskyblock.)
*/
private ListLibraryPanel(ChallengesAddon addon,
World world,
User user,
String topLabel,
String permissionPrefix)
{
super(addon, user, world, topLabel, permissionPrefix);
}
/**
* Open the Challenges Admin GUI.
*
* @param addon the addon
* @param world the world
* @param user the user
* @param topLabel the top label
* @param permissionPrefix the permission prefix
*/
public static void open(ChallengesAddon addon,
World world,
User user,
String topLabel,
String permissionPrefix)
{
new ListLibraryPanel(addon, world, user, topLabel, permissionPrefix).build();
}
/**
* This static method allows to easier open Library GUI.
* @param parentGui ParentGUI object.
*/
public static void open(CommonPanel parentGui)
{
new ListLibraryPanel(parentGui).build();
}
// ---------------------------------------------------------------------
// Section: Methods
// ---------------------------------------------------------------------
/**
* {@inheritDoc}
*/
@Override
protected void build()
{
PanelBuilder panelBuilder = new PanelBuilder().user(this.user).name(
this.user.getTranslation(Constants.TITLE + "library"));
GuiUtils.fillBorder(panelBuilder);
this.populateElements(panelBuilder,
this.addon.getWebManager().getLibraryEntries(),
o -> this.createEntryIcon((LibraryEntry) o));
panelBuilder.item(4, this.createDownloadNow());
panelBuilder.item(44, this.returnButton);
panelBuilder.listener(new DownloadCanceller());
panelBuilder.build();
}
/**
* This creates download now button, that can skip waiting for automatic request.
* @return PanelItem button that allows to manually download libraries.
*/
private PanelItem createDownloadNow()
{
final String reference = Constants.BUTTON + "download.";
final List<String> description = new ArrayList<>(3);
description.add(this.user.getTranslation(reference + "description"));
description.add(this.user.getTranslation(reference +
(this.clearCache ? "enabled" : "disabled")));
description.add("");
description.add(this.user.getTranslation(Constants.TIPS + "left-click-to-download"));
description.add(this.user.getTranslation(Constants.TIPS + "right-click-to-toggle"));
PanelItemBuilder itemBuilder = new PanelItemBuilder().
name(this.user.getTranslation(reference + "name")).
description(description).
icon(Material.HOPPER).
glow(this.clearCache);
itemBuilder.clickHandler((panel, user1, clickType, slot) ->
{
if (clickType.isRightClick())
{
this.clearCache = !this.clearCache;
panel.getInventory().setItem(slot, this.createDownloadNow().getItem());
}
else
{
this.addon.getWebManager().requestCatalogGitHubData(this.clearCache);
// Fix multiclick issue.
if (this.updateTask != null)
{
this.updateTask.cancel();
}
// add some delay to rebuilding gui.
this.updateTask = this.addon.getPlugin().getServer().getScheduler().runTaskLater(
this.addon.getPlugin(),
this::build,
100L);
}
return true;
});
return itemBuilder.build();
}
/**
* This method creates button for given library entry.
* @param libraryEntry LibraryEntry which button must be created.
* @return Entry button.
*/
private PanelItem createEntryIcon(LibraryEntry libraryEntry)
{
PanelItemBuilder itemBuilder = new PanelItemBuilder().
name(ChatColor.translateAlternateColorCodes('&', libraryEntry.getName())).
description(this.generateEntryDescription(libraryEntry)).
description("").
description(this.user.getTranslation(Constants.TIPS + "click-to-install")).
icon(libraryEntry.getIcon()).
glow(false);
itemBuilder.clickHandler((panel, user1, clickType, i) -> {
if (!this.blockedForDownland)
{
this.blockedForDownland = true;
Utils.sendMessage(this.user,
this.user.getTranslation(Constants.CONVERSATIONS + "start-downloading"));
// Run download task after 5 ticks.
this.addon.getPlugin().getServer().getScheduler().
runTaskLaterAsynchronously(
this.addon.getPlugin(),
() -> this.addon.getWebManager().requestEntryGitHubData(this.user, this.world, libraryEntry),
5L);
this.build();
}
return true;
});
return itemBuilder.build();
}
/**
* This method generated description for LibraryEntry object.
* @param entry LibraryEntry object which description must be generated.
* @return List of strings that will be placed in ItemStack lore message.
*/
private List<String> generateEntryDescription(LibraryEntry entry)
{
final String reference = Constants.DESCRIPTIONS + "library.";
List<String> description = new ArrayList<>();
description.add(this.user.getTranslation(reference + "author",
"[author]", entry.getAuthor()));
description.add(entry.getDescription());
description.add(this.user.getTranslation(reference + "gamemode",
"[gamemode]", entry.getForGameMode()));
description.add(this.user.getTranslation(reference + "lang",
"[lang]", entry.getLanguage()));
description.add(this.user.getTranslation(reference + "version",
"[version]", entry.getVersion()));
return description;
}
/**
* This class allows changing icon for Generator Tier
*/
private class DownloadCanceller implements PanelListener
{
/**
* On inventory click.
*
* @param user the user
* @param event the event
*/
@Override
public void onInventoryClick(User user, InventoryClickEvent event)
{
// do nothing
}
/**
* On inventory close.
*
* @param event the event
*/
@Override
public void onInventoryClose(InventoryCloseEvent event)
{
if (ListLibraryPanel.this.updateTask != null)
{
ListLibraryPanel.this.updateTask.cancel();
}
}
/**
* Setup current listener.
*/
@Override
public void setup()
{
// Do nothing
}
}
// ---------------------------------------------------------------------
// Section: Instance Variables
// ---------------------------------------------------------------------
/**
* Indicates if download now button should trigger cache clearing.
*/
private boolean clearCache;
/**
* Stores update task that is triggered.
*/
private BukkitTask updateTask = null;
/**
* This variable will protect against spam-click.
*/
private boolean blockedForDownland;
}

View File

@ -115,7 +115,7 @@ public class WebManager
JsonObject catalog = new JsonParser().parse(catalogContent).getAsJsonObject();
catalog.getAsJsonArray("challenges").forEach(gamemode ->
this.library.add(new LibraryEntry(gamemode.getAsJsonObject())));
this.library.add(LibraryEntry.fromJson(gamemode.getAsJsonObject())));
}
});
}
@ -142,7 +142,7 @@ public class WebManager
try
{
challengeLibrary = gitHubWebAPI.getRepository("BentoBoxWorld", "weblink").
getContent("challenges/library/" + entry.getRepository() + ".json").
getContent("challenges/library/" + entry.repository() + ".json").
getContent().
replaceAll("\\n", "");
}
@ -192,7 +192,7 @@ public class WebManager
public List<LibraryEntry> getLibraryEntries()
{
List<LibraryEntry> entries = new ArrayList<>(this.library);
entries.sort(Comparator.comparingInt(LibraryEntry::getSlot));
entries.sort(Comparator.comparingInt(LibraryEntry::slot));
return entries;
}
@ -216,15 +216,15 @@ public class WebManager
/**
* Challenges Addon variable.
*/
private ChallengesAddon addon;
private final ChallengesAddon addon;
/**
* BentoBox plugin variable.
*/
private BentoBox plugin;
private final BentoBox plugin;
/**
* This list contains all entries that were downloaded from GitHub.
*/
private List<LibraryEntry> library;
private final List<LibraryEntry> library;
}

View File

@ -3,185 +3,47 @@ package world.bentobox.challenges.web.object;
import org.bukkit.Material;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.JsonObject;
/**
* This objects allows to load each Challenges Catalog library entry.
* The type Library entry.
* @param name Name of the library entry
* @param icon Icon of the library entry
* @param description Description of the library entry
* @param repository Link to the repository
* @param language Language of the entry
* @param slot order of the entry
* @param gameMode Made primary for gamemode
* @param author Author of the entry
* @param version version of the challenges.
*/
public class LibraryEntry
public record LibraryEntry(String name, Material icon, String description, String repository,
String language, int slot, String gameMode, String author, String version)
{
/**
* Default constructor.
* @param object Json Object that must be translated to LibraryEntry.
*/
public LibraryEntry(@NonNull JsonObject object)
public static LibraryEntry fromJson(@NonNull JsonObject object)
{
this.name = object.get("name").getAsString();
Material material = Material.matchMaterial(object.get("icon").getAsString());
this.icon = (material != null) ? material : Material.PAPER;
this.description = object.get("description").getAsString();
this.repository = object.get("repository").getAsString();
this.language = object.get("language").getAsString();
this.slot = object.get("slot").getAsInt();
this.forGameMode = object.get("for").getAsString();
this.author = object.get("author").getAsString();
this.version = object.get("version").getAsString();
return new LibraryEntry(object.get("name").getAsString(),
(material != null) ? material : Material.PAPER,
object.get("description").getAsString(),
object.get("repository").getAsString(),
object.get("language").getAsString(),
object.get("slot").getAsInt(),
object.get("for").getAsString(),
object.get("author").getAsString(),
object.get("version").getAsString());
}
/**
* This method returns the name value.
* @return the value of name.
*/
@NonNull
public String getName()
public static LibraryEntry fromTemplate(String name, Material icon)
{
return name;
return new LibraryEntry(name, icon, "", "", "", 0, "", "", "");
}
/**
* This method returns the icon value.
* @return the value of icon.
*/
@NonNull
public Material getIcon()
{
return icon;
}
/**
* This method returns the description value.
* @return the value of description.
*/
@NonNull
public String getDescription()
{
return description;
}
/**
* This method returns the repository value.
* @return the value of repository.
*/
@NonNull
public String getRepository()
{
return repository;
}
/**
* This method returns the language value.
* @return the value of language.
*/
@NonNull
public String getLanguage()
{
return language;
}
/**
* This method returns the slot value.
* @return the value of slot.
*/
@NonNull
public int getSlot()
{
return slot;
}
/**
* This method returns the forGameMode value.
* @return the value of forGameMode.
*/
@NonNull
public String getForGameMode()
{
return forGameMode;
}
/**
* This method returns the author value.
* @return the value of author.
*/
@NonNull
public String getAuthor()
{
return author;
}
/**
* This method returns the version value.
* @return the value of version.
*/
@NonNull
public String getVersion()
{
return version;
}
// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
/**
* Name of entry object
*/
private @NonNull String name;
/**
* Defaults to {@link Material#PAPER}.
*/
private @NonNull Material icon;
/**
* Description of entry object.
*/
private @NonNull String description;
/**
* File name in challenges library.
*/
private @NonNull String repository;
/**
* Language of content.
*/
private @Nullable String language;
/**
* Desired slot number.
*/
private int slot;
/**
* Main GameMode for which challenges were created.
*/
private @Nullable String forGameMode;
/**
* Author (-s) who created current configuration.
*/
private @Nullable String author;
/**
* Version of Challenges Addon, for which challenges were created.
*/
private @Nullable String version;
}

View File

@ -189,14 +189,21 @@ challenges:
description: |-
&7 Opens a public
&7 challenges library.
import_challenges:
name: "&f&l Import Challenges"
import_database:
name: "&f&l Import Database"
description: |-
&7 Allows to import challenges.
&7 Allows to import exported
&7 challenges database.
import_template:
name: "&f&l Import Template"
description: |-
&7 Allows to import template
&7 file with challenges.
export_challenges:
name: "&f&l Export Challenges"
description: |-
&7 Allows to export challenges.
&7 Allows to export database
&7 to a local file.
properties:
name: "&f&l Properties"
description: |-
@ -1046,6 +1053,16 @@ challenges:
start-downloading: "&a Starting to download and import Challenges Library."
# Message that appears when writing multiline text.
written-text: "&a Input Text:"
# Message that appears after importing library data into database.
confirm-data-replacement: "&e Please confirm that you want to replace your current challenges with new one."
# Message that appears after successful data importing
new-challenges-imported: "&a Success, new Challenges for [gamemode] were imported."
# Message that appears after admin clicks on database exporting button.
exported-file-name: "&e Please enter a file name for the exported database file. (write 'cancel' to exit)"
# Message that appears after successful database exporting to file.
database-export-completed: "&a Success, the database export for [world] is completed. File [file] generated."
# Message that appears if input file name is already taken.
file-name-exist: "&c File with name '[id]' exists. Cannot overwrite."
titles:
# Title and subtitle may contain variables in [] that will be replaced with a proper message from the challenge object.
# [friendlyName] will be replaced with challenge friendly name.
@ -1131,6 +1148,7 @@ challenges:
no-multiple-permission: "&c You do not have permission to complete this challenge multiple times at once."
invalid-level: "&c Level [level] contains invalid data. It will not be loaded from database!"
invalid-challenge: "&c Challenge [challenge] contains invalid data. It will not be loaded from database!"
no-library-entries: "&c Cannot find any library entries. Nothing to show."
protection:
flags:
CHALLENGES_ISLAND_PROTECTION:

View File

@ -352,7 +352,7 @@ public class ChallengesManagerTest {
}
/**
* Test method for {@link ChallengesManager#wipeDatabase(boolean)}.
* Test method for {@link ChallengesManager#wipeDatabase(boolean, String)}.
* @throws InterruptedException
*/
@Test
@ -376,19 +376,19 @@ public class ChallengesManagerTest {
assertTrue(checkPd.exists());
// Wipe it
cm.wipeDatabase(false);
cm.wipeDatabase(false, "");
// Verify
assertFalse(check.exists());
assertFalse(checkLv.exists());
assertTrue(checkPd.exists());
cm.wipeDatabase(true);
cm.wipeDatabase(true, "");
assertFalse(checkPd.exists());
}
/**
* Test method for {@link ChallengesManager#wipePlayers()}.
* Test method for {@link ChallengesManager#wipePlayers(String)}.
* @throws InterruptedException
*/
@Test
@ -399,7 +399,7 @@ public class ChallengesManagerTest {
File plData = new File(database, "ChallengesPlayerData");
File checkLv = new File(plData, playerID.toString() + ".json");
assertTrue(checkLv.exists());
cm.wipePlayers();
cm.wipePlayers("");
assertFalse(checkLv.exists());
}