Release 0.8.3

# Conflicts:
#	pom.xml
This commit is contained in:
BONNe 2020-07-10 01:00:03 +03:00
commit 88027805e6
19 changed files with 402 additions and 108 deletions

View File

@ -7,14 +7,12 @@ Add-on for BentoBox to provide challenges for any BentoBox GameMode.
## Where to find
Currently Challenges Addon is in **Beta stage**, so it may or may not contain bugs... a lot of bugs. Also it means, that some features are not working or implemented.
Latest official **Beta Release is 0.8.1**, and you can download it from [Release tab](https://github.com/BentoBoxWorld/Challenges/releases)
But it will work with BentoBox 1.6.x and BentoBox 1.7.x.
Latest official **Beta Release is 0.8.3**, and you can download it from [Release tab](https://github.com/BentoBoxWorld/Challenges/releases)
But it will work with BentoBox 1.14.
Latest development builds will be based on **Minecraft 1.14.4** and **BentoBox 1.8.0**.
Latest development builds will be based on **Minecraft 1.16.1** and **BentoBox 1.14.0**.
**Nightly builds** are available in [Jenkins Server](https://ci.codemc.org/job/BentoBoxWorld/job/Challenges/lastStableBuild/).
Be aware that 0.8.0 stores data differently than it is in 0.7.5 and below. It will be necessary to migrate data via command `/[gamemode_admin] challenges migrate`.
If you like this addon but something is missing or is not working as you want, you can always submit an [Issue request](https://github.com/BentoBoxWorld/Challenges/issues) or get a support in Discord [BentoBox ![icon](https://avatars2.githubusercontent.com/u/41555324?s=15&v=4)](https://discord.bentobox.world)
## Translations
@ -38,7 +36,7 @@ There exist also Web Library, where users can download public challenges. It is
## Compatibility
- [x] BentoBox - 1.6.x and 1.7.x versions
- [x] BentoBox - 1.14 versions
- [x] BSkyBlock
- [x] AcidIsland
- [x] SkyGrid
@ -46,4 +44,4 @@ There exist also Web Library, where users can download public challenges. It is
## Information
More information can be found in [Wiki Pages](https://github.com/BentoBoxWorld/Challenges/wiki).
More information can be found in [Wiki Pages](https://docs.bentobox.world/addons/Challenges/).

View File

@ -35,14 +35,14 @@
<java.version>1.8</java.version>
<powermock.version>2.0.2</powermock.version>
<!-- More visible way how to change dependency versions -->
<spigot.version>1.14.4-R0.1-SNAPSHOT</spigot.version>
<bentobox.version>1.7.0</bentobox.version>
<spigot.version>1.15.2-R0.1-SNAPSHOT</spigot.version>
<bentobox.version>1.14.0</bentobox.version>
<level.version>1.6.0</level.version>
<vault.version>1.7</vault.version>
<!-- Revision variable removes warning about dynamic version -->
<revision>${build.version}-SNAPSHOT</revision>
<!-- This allows to change between versions and snapshots. -->
<build.version>0.8.2</build.version>
<build.version>0.8.3</build.version>
<build.number>-LOCAL</build.number>
</properties>

View File

@ -179,6 +179,9 @@ public class ChallengesAddon extends Addon {
CHALLENGES_ISLAND_PROTECTION.addGameModeAddon(gameModeAddon);
this.registerPlaceholders(gameModeAddon);
// TODO: this is old placeholders. Remove when backward compatibility ends.
this.registerPlaceholdersOld(gameModeAddon);
}
});
@ -283,7 +286,14 @@ public class ChallengesAddon extends Addon {
if (this.hooked) {
this.challengesManager.save();
}
}
/**
* This method saves addon settings into file.
*/
public void saveSettings()
{
if (this.settings != null)
{
new Config<>(this, Settings.class).saveConfigObject(this.settings);
@ -320,6 +330,93 @@ public class ChallengesAddon extends Addon {
* @param gameModeAddon GameMode addon where placeholders must be hooked in.
*/
private void registerPlaceholders(GameModeAddon gameModeAddon)
{
final String addonName = this.getDescription().getName().toLowerCase();
final World world = gameModeAddon.getOverWorld();
// Number of completions for all challenges placeholder
this.getPlugin().getPlaceholdersManager().registerPlaceholder(gameModeAddon,
addonName + "_total_completion_count",
user -> String.valueOf(this.challengesManager.getTotalChallengeCompletionCount(user, world)));
// Completed challenge count placeholder
this.getPlugin().getPlaceholdersManager().registerPlaceholder(gameModeAddon,
addonName + "_completed_count",
user -> String.valueOf(this.challengesManager.getCompletedChallengeCount(user, world)));
// Uncompleted challenge count placeholder
this.getPlugin().getPlaceholdersManager().registerPlaceholder(gameModeAddon,
addonName + "_uncompleted_count",
user -> String.valueOf(this.challengesManager.getChallengeCount(world) -
this.challengesManager.getCompletedChallengeCount(user, world)));
// Completed challenge level count placeholder
this.getPlugin().getPlaceholdersManager().registerPlaceholder(gameModeAddon,
addonName + "_completed_level_count",
user -> String.valueOf(this.challengesManager.getCompletedLevelCount(user, world)));
// Uncompleted challenge level count placeholder
this.getPlugin().getPlaceholdersManager().registerPlaceholder(gameModeAddon,
addonName + "_uncompleted_level_count",
user -> String.valueOf(this.challengesManager.getLevelCount(world) -
this.challengesManager.getCompletedLevelCount(user, world)));
// Unlocked challenge level count placeholder
this.getPlugin().getPlaceholdersManager().registerPlaceholder(gameModeAddon,
addonName + "_unlocked_level_count",
user -> String.valueOf(this.challengesManager.getLevelCount(world) -
this.challengesManager.getUnlockedLevelCount(user, world)));
// Locked challenge level count placeholder
this.getPlugin().getPlaceholdersManager().registerPlaceholder(gameModeAddon,
addonName + "_locked_level_count",
user -> String.valueOf(this.challengesManager.getLevelCount(world) -
this.challengesManager.getUnlockedLevelCount(user, world)));
// Latest challenge level name placeholder
this.getPlugin().getPlaceholdersManager().registerPlaceholder(gameModeAddon,
addonName + "_latest_level_name",
user -> {
ChallengeLevel level = this.challengesManager.getLatestUnlockedLevel(user, world);
return level != null ? level.getFriendlyName() : "";
});
// Latest challenge level id placeholder
this.getPlugin().getPlaceholdersManager().registerPlaceholder(gameModeAddon,
addonName + "_latest_level_id",
user -> {
ChallengeLevel level = this.challengesManager.getLatestUnlockedLevel(user, world);
return level != null ? level.getUniqueId() : "";
});
// Completed challenge count in latest level
this.getPlugin().getPlaceholdersManager().registerPlaceholder(gameModeAddon,
addonName + "_latest_level_completed_count",
user -> {
ChallengeLevel level = this.challengesManager.getLatestUnlockedLevel(user, world);
return String.valueOf(level != null ?
this.challengesManager.getLevelCompletedChallengeCount(user, world, level) : 0);
});
// Uncompleted challenge count in latest level
this.getPlugin().getPlaceholdersManager().registerPlaceholder(gameModeAddon,
addonName + "_latest_level_uncompleted_count",
user -> {
ChallengeLevel level = this.challengesManager.getLatestUnlockedLevel(user, world);
return String.valueOf(level != null ?
level.getChallenges().size() - this.challengesManager.getLevelCompletedChallengeCount(user, world, level) : 0);
});
}
/**
* This method registers placeholders into GameMode addon.
* @param gameModeAddon GameMode addon where placeholders must be hooked in.
* @since 0.8.1
* @deprecated remove after 0.9.0
*/
@Deprecated
private void registerPlaceholdersOld(GameModeAddon gameModeAddon)
{
final String gameMode = gameModeAddon.getDescription().getName().toLowerCase();
final World world = gameModeAddon.getOverWorld();

View File

@ -126,7 +126,8 @@ public class ChallengesImportManager
return false;
}
this.addon.getChallengesManager().save();
this.addon.getChallengesManager().saveChallenges();
this.addon.getChallengesManager().saveLevels();
if (removeAtEnd)
{
@ -203,7 +204,8 @@ public class ChallengesImportManager
return false;
}
this.addon.getChallengesManager().save();
this.addon.getChallengesManager().saveChallenges();
this.addon.getChallengesManager().saveLevels();
return true;
}

View File

@ -206,11 +206,15 @@ public class ChallengesManager
}
this.playerCacheData.clear();
loadAndValidate();
this.loadAndValidate();
}
private void loadAndValidate() {
/**
* This method loads and validates all challenges and levels.
*/
private void loadAndValidate()
{
this.challengeDatabase.loadObjects().forEach(this::loadChallenge);
this.levelDatabase.loadObjects().forEach(this::loadLevel);
// this validate challenge levels
@ -233,7 +237,7 @@ public class ChallengesManager
//this.levelDatabase = new Database<>(addon, ChallengeLevel.class);
//this.playersDatabase = new Database<>(addon, ChallengesPlayerData.class);
loadAndValidate();
this.loadAndValidate();
}
@ -263,6 +267,18 @@ public class ChallengesManager
User user,
boolean silent)
{
// This may happen if database somehow failed to load challenge and return
// null as input.
if (challenge == null)
{
if (!silent)
{
user.sendMessage("load-error", "[value]", "NULL");
}
return false;
}
if (this.challengeCacheData.containsKey(challenge.getUniqueId()))
{
if (!overwrite)
@ -324,6 +340,18 @@ public class ChallengesManager
User user,
boolean silent)
{
// This may happen if database somehow failed to load challenge and return
// null as input.
if (level == null)
{
if (!silent)
{
user.sendMessage("load-error", "[value]", "NULL");
}
return false;
}
if (!this.isValidLevel(level))
{
if (user != null)
@ -402,12 +430,15 @@ public class ChallengesManager
*/
public void removeFromCache(UUID playerID)
{
if (!this.settings.isStoreAsIslandData() && this.playerCacheData.containsKey(playerID.toString()))
{
// save before remove
this.savePlayerData(playerID.toString());
this.playerCacheData.remove(playerID.toString());
}
// Remove due possible issues with saving... (#246)
// if (!this.settings.isStoreAsIslandData() && this.playerCacheData.containsKey(playerID.toString()))
// {
// // save before remove
// this.savePlayerData(playerID.toString());
// this.playerCacheData.remove(playerID.toString());
// }
this.savePlayerData(playerID.toString());
// TODO: It would be necessary to remove also data, if they stores islands.
// Unfortunately, I do not know all worlds. Checking everything would be bad. Probably, I could
@ -474,11 +505,8 @@ public class ChallengesManager
{
if (!this.challengeCacheData.containsKey(uniqueID))
{
if (this.challengeDatabase.objectExists(uniqueID))
{
this.loadChallenge(this.challengeDatabase.loadObject(uniqueID));
}
else
if (!this.challengeDatabase.objectExists(uniqueID) ||
!this.loadChallenge(this.challengeDatabase.loadObject(uniqueID), false, null, true))
{
this.addon.logError("Cannot find " + uniqueID + " challenge for " + level.getUniqueId());
return false;
@ -524,7 +552,7 @@ public class ChallengesManager
{
// Create the player data
ChallengesPlayerData pd = new ChallengesPlayerData(uniqueID);
this.playersDatabase.saveObject(pd);
this.playersDatabase.saveObjectAsync(pd);
// Add to cache
this.playerCacheData.put(uniqueID, pd);
}
@ -671,7 +699,7 @@ public class ChallengesManager
challengesID.forEach(challenge ->
level.getChallenges().add(addonName + challenge.substring(world.getName().length())));
this.levelDatabase.saveObject(level);
this.levelDatabase.saveObjectAsync(level);
this.levelCacheData.put(level.getUniqueId(), level);
updated = true;
@ -715,7 +743,7 @@ public class ChallengesManager
updated = true;
this.challengeDatabase.saveObject(challenge);
this.challengeDatabase.saveObjectAsync(challenge);
this.challengeCacheData.put(challenge.getUniqueId(), challenge);
}
@ -758,7 +786,7 @@ public class ChallengesManager
// This save should not involve any upgrades in other parts.
this.challengeDatabase.saveObject(challenge);
this.challengeDatabase.saveObjectAsync(challenge);
this.challengeCacheData.put(challenge.getUniqueId(), challenge);
}
}
@ -809,7 +837,7 @@ public class ChallengesManager
}
});
this.playersDatabase.saveObject(playerData);
this.playersDatabase.saveObjectAsync(playerData);
});
}
@ -824,8 +852,11 @@ public class ChallengesManager
*/
public void save()
{
this.saveChallenges();
this.saveLevels();
// Challenges and Levels are saved on modifications only to avoid issues with
// NULL's in data after interrupting server while in saving stage.
// this.saveChallenges();
// this.saveLevels();
this.savePlayersData();
}
@ -833,9 +864,9 @@ public class ChallengesManager
/**
* This method saves all challenges to database.
*/
private void saveChallenges()
public void saveChallenges()
{
this.challengeCacheData.values().forEach(this.challengeDatabase::saveObject);
this.challengeCacheData.values().forEach(this::saveChallenge);
}
@ -845,16 +876,16 @@ public class ChallengesManager
*/
public void saveChallenge(Challenge challenge)
{
this.challengeDatabase.saveObject(challenge);
this.challengeDatabase.saveObjectAsync(challenge);
}
/**
* This method saves all levels to database.
*/
private void saveLevels()
public void saveLevels()
{
this.levelCacheData.values().forEach(this.levelDatabase::saveObject);
this.levelCacheData.values().forEach(this::saveLevel);
}
@ -864,7 +895,7 @@ public class ChallengesManager
*/
public void saveLevel(ChallengeLevel level)
{
this.levelDatabase.saveObject(level);
this.levelDatabase.saveObjectAsync(level);
}
@ -873,7 +904,7 @@ public class ChallengesManager
*/
private void savePlayersData()
{
this.playerCacheData.values().forEach(this.playersDatabase::saveObject);
this.playerCacheData.values().forEach(this.playersDatabase::saveObjectAsync);
}
@ -902,7 +933,7 @@ public class ChallengesManager
}
}
this.playersDatabase.saveObject(cachedData);
this.playersDatabase.saveObjectAsync(cachedData);
}
}
@ -1053,7 +1084,20 @@ public class ChallengesManager
private void resetAllChallenges(@NonNull String storageDataID, @NonNull String gameMode)
{
this.addPlayerData(storageDataID);
this.playerCacheData.get(storageDataID).reset(gameMode);
if (this.playerCacheData.containsKey(storageDataID))
{
// There may be a rare situation when player data cannot be loaded. Just avoid
// error.
this.playerCacheData.get(storageDataID).reset(gameMode);
}
else
{
// If object cannot be loaded remove it completely.
this.playersDatabase.deleteID(storageDataID);
this.addon.logError("Database object was not loaded. It is removed completely. Object Id: " + storageDataID);
}
// Save
this.savePlayerData(storageDataID);
}
@ -1079,6 +1123,7 @@ public class ChallengesManager
// The first level is always unlocked and previous for it is null.
ChallengeLevel previousLevel = null;
int doneChallengeCount = 0;
boolean previousUnlocked = true;
// For each challenge level, check how many the storageDataID has done
for (ChallengeLevel level : challengeLevelList)
@ -1088,20 +1133,25 @@ public class ChallengesManager
// remove waiver amount to get count of challenges that still necessary to do.
int challengesToDo = previousLevel == null ? 0 :
(previousLevel.getChallenges().size() - doneChallengeCount - level.getWaiverAmount());
(previousLevel.getChallenges().size() - doneChallengeCount - previousLevel.getWaiverAmount());
// As level already contains unique ids of challenges, just iterate through them.
doneChallengeCount = (int) level.getChallenges().stream().filter(playerData::isChallengeDone).count();
// Mark if level is unlocked
boolean unlocked = previousUnlocked && challengesToDo <= 0;
result.add(new LevelStatus(
level,
previousLevel,
challengesToDo,
level.getChallenges().size() == doneChallengeCount,
challengesToDo <= 0));
unlocked));
previousLevel = level;
previousUnlocked = unlocked;
}
return result;
}
@ -1132,7 +1182,7 @@ public class ChallengesManager
ChallengeLevel previousLevel = levelIndex < 1 ? null : challengeLevelList.get(levelIndex - 1);
int challengesToDo = previousLevel == null ? 0 :
(previousLevel.getChallenges().size() - level.getWaiverAmount()) -
(previousLevel.getChallenges().size() - previousLevel.getWaiverAmount()) -
(int) previousLevel.getChallenges().stream().filter(playerData::isChallengeDone).count();
// As level already contains unique ids of challenges, just iterate through them.
@ -1362,7 +1412,7 @@ public class ChallengesManager
* @param world - World where challenges must be reset.
* @param adminID - admin iD
*/
public void resetAllChallenges(UUID userID, World world, UUID adminID)
public void resetAllChallenges(@NonNull UUID userID, World world, @Nullable UUID adminID)
{
String storageID = this.getDataUniqueID(userID, Util.getWorld(world));
@ -1526,12 +1576,12 @@ public class ChallengesManager
LevelStatus lastStatus = null;
for (Iterator<LevelStatus> statusIterator = this.getAllChallengeLevelStatus(user, world).iterator();
statusIterator.hasNext() && (lastStatus == null || !lastStatus.isUnlocked());)
statusIterator.hasNext() && (lastStatus == null || lastStatus.isUnlocked());)
{
lastStatus = statusIterator.next();
}
return lastStatus != null ? lastStatus.getLevel() : null;
return lastStatus != null ? lastStatus.getPreviousLevel() : null;
}
@ -1752,6 +1802,21 @@ public class ChallengesManager
}
/**
* This method returns completed challenge count in given level.
* @param user User which should be checked
* @param world World where challenges are operating
* @param level Level which challenges must be checked.
* @return Number of completed challenges in given level.
*/
public long getLevelCompletedChallengeCount(User user, World world, ChallengeLevel level)
{
return this.getLevelChallenges(level).stream().
filter(challenge -> this.getChallengeTimes(user, world, challenge) > 0).
count();
}
// ---------------------------------------------------------------------
// Section: Level related methods
// ---------------------------------------------------------------------

View File

@ -1,7 +1,7 @@
package world.bentobox.challenges.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -34,14 +34,14 @@ public class Settings implements ConfigObject
@ConfigComment("Allows to define common challenges command that will open User GUI")
@ConfigComment("with all GameMode selection or Challenges from user world.")
@ConfigComment("This will not affect /{gamemode_user} challenges command.")
@ConfigEntry(path = "commands.user", needsReset = true)
@ConfigEntry(path = "commands.user", needsRestart = true)
private String userCommand = "challenges c";
@ConfigComment("")
@ConfigComment("Allows to define common challenges command that will open Admin GUI")
@ConfigComment("with all GameMode selection.")
@ConfigComment("This will not affect /{gamemode_admin} challenges command.")
@ConfigEntry(path = "commands.admin", needsReset = true)
@ConfigEntry(path = "commands.admin", needsRestart = true)
private String adminCommand = "challengesadmin chadmin";
@ConfigComment("")
@ -49,7 +49,7 @@ public class Settings implements ConfigObject
@ConfigComment("all GameModes. For admins it will open selection with all GameModes")
@ConfigComment("(unless there is one), but for users it will open GUI that corresponds")
@ConfigComment("to their world (unless it is specified other way in Admin GUI).")
@ConfigEntry(path = "commands.single-gui", needsReset = true)
@ConfigEntry(path = "commands.single-gui", needsRestart = true)
private boolean useCommonGUI = false;
@ConfigComment("")
@ -130,7 +130,7 @@ public class Settings implements ConfigObject
@ConfigComment("Requirement and reward items, blocks and entities that are defined in challenge and can be customized under 'challenges.gui.item-description.*'")
@ConfigEntry(path = "gui-settings.challenge-lore")
@Adapter(ChallengeLoreAdapter.class)
private List<ChallengeLore> challengeLoreMessage = new ArrayList<>();
private List<ChallengeLore> challengeLoreMessage = Arrays.asList(ChallengeLore.values());
@ConfigComment("")
@ConfigComment("This string allows to change element order in Level description. Each letter represents")
@ -149,7 +149,7 @@ public class Settings implements ConfigObject
@ConfigComment("Reward items that are defined in challenge level and can be customized under 'challenges.gui.item-description.*'")
@ConfigEntry(path = "gui-settings.level-lore")
@Adapter(LevelLoreAdapter.class)
private List<LevelLore> levelLoreMessage = new ArrayList<>();
private List<LevelLore> levelLoreMessage = Arrays.asList(LevelLore.values());
@ConfigComment("")
@ConfigComment("This indicate if challenges data will be stored per island (true) or per player (false).")

View File

@ -19,6 +19,8 @@ import com.google.gson.annotations.Expose;
import com.google.gson.annotations.JsonAdapter;
import world.bentobox.bentobox.database.objects.DataObject;
import world.bentobox.bentobox.database.objects.Table;
import world.bentobox.challenges.database.object.adapters.EntityCompatibilityAdapter;
import world.bentobox.challenges.database.object.adapters.RequirementsAdapter;
import world.bentobox.challenges.database.object.requirements.Requirements;
@ -28,6 +30,7 @@ import world.bentobox.challenges.database.object.requirements.Requirements;
* @author tastybento
*
*/
@Table(name = "Challenge")
public class Challenge implements DataObject
{
/**
@ -156,6 +159,7 @@ public class Challenge implements DataObject
@Deprecated
@Expose
@JsonAdapter(EntityCompatibilityAdapter.class)
private Map<EntityType, Integer> requiredEntities = new EnumMap<>(EntityType.class);
@Deprecated

View File

@ -14,6 +14,7 @@ import com.google.gson.annotations.Expose;
import world.bentobox.bentobox.api.configuration.ConfigComment;
import world.bentobox.bentobox.database.objects.DataObject;
import world.bentobox.bentobox.database.objects.Table;
import world.bentobox.challenges.ChallengesManager;
/**
@ -21,6 +22,7 @@ import world.bentobox.challenges.ChallengesManager;
* @author tastybento
*
*/
@Table(name = "ChallengeLevel")
public class ChallengeLevel implements DataObject, Comparable<ChallengeLevel>
{
/**

View File

@ -14,6 +14,7 @@ import com.google.gson.annotations.Expose;
import world.bentobox.bentobox.api.logs.LogEntry;
import world.bentobox.bentobox.database.objects.DataObject;
import world.bentobox.bentobox.database.objects.Table;
import world.bentobox.bentobox.database.objects.adapters.Adapter;
import world.bentobox.bentobox.database.objects.adapters.LogEntryListAdapter;
@ -23,6 +24,7 @@ import world.bentobox.bentobox.database.objects.adapters.LogEntryListAdapter;
* @author tastybento
*
*/
@Table(name = "ChallengesPlayerData")
public class ChallengesPlayerData implements DataObject
{
/**

View File

@ -0,0 +1,92 @@
//
// Created by BONNe
// Copyright - 2020
//
package world.bentobox.challenges.database.object.adapters;
import com.google.gson.*;
import org.bukkit.entity.EntityType;
import java.lang.reflect.Type;
import java.util.EnumMap;
import java.util.Map;
import world.bentobox.bentobox.BentoBox;
/**
* This is compatibility class for dealing with Mojang renamed entities.
* Created for update 1.16.
*/
public class EntityCompatibilityAdapter implements
JsonSerializer<Map<EntityType, Integer>>, JsonDeserializer<Map<EntityType, Integer>>
{
/**
* This method serializes input map as String Key and Integer value.
* @param src EnumMap that contains EntityType as key and Integer as value.
* @return serialized JsonElement.
*/
@Override
public JsonElement serialize(Map<EntityType, Integer> src, Type typeOfSrc, JsonSerializationContext context)
{
JsonObject jsonArray = new JsonObject();
src.forEach((entity, number) ->
{
jsonArray.addProperty(entity.name(), number);
});
return jsonArray;
}
/**
* This method deserializes json object that stores Entity Name and amount as integer.
* @param json Json element that must be parsed.
* @return EnumMap that contains EntityType as key and Integer as value.
* @throws JsonParseException
*/
@Override
public Map<EntityType, Integer> deserialize(JsonElement json,
Type typeOfT,
JsonDeserializationContext context)
throws JsonParseException
{
Map<EntityType, Integer> map = new EnumMap<>(EntityType.class);
for (Map.Entry<String, JsonElement> entrySet : json.getAsJsonObject().entrySet())
{
try
{
EntityType entityType = EntityType.valueOf(entrySet.getKey());
map.put(entityType, entrySet.getValue().getAsInt());
}
catch (IllegalArgumentException e)
{
if (entrySet.getKey().equals("PIG_ZOMBIE"))
{
// Hacky way how to get new entity name.
map.put(EntityType.valueOf("ZOMBIFIED_PIGLIN"),
entrySet.getValue().getAsInt());
}
else if (entrySet.getKey().equals("ZOMBIFIED_PIGLIN"))
{
// Hacky way how to get new entity name.
map.put(EntityType.valueOf("PIG_ZOMBIE"),
entrySet.getValue().getAsInt());
}
else
{
// No replacement for new entities in older server.
BentoBox.getInstance().logWarning("[ChallengesAddon] Entity with name `" +
entrySet.getKey() + "` does not exist in your Minecraft server version." +
" It will be skipped!");
}
}
}
return map;
}
}

View File

@ -16,6 +16,9 @@ import org.bukkit.Material;
import org.bukkit.entity.EntityType;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.JsonAdapter;
import world.bentobox.challenges.database.object.adapters.EntityCompatibilityAdapter;
/**
@ -193,6 +196,7 @@ public class IslandRequirements extends Requirements
* Map that contains which entities and how many is necessary around player to complete challenge.
*/
@Expose
@JsonAdapter(EntityCompatibilityAdapter.class)
private Map<EntityType, Integer> requiredEntities = new EnumMap<>(EntityType.class);
/**

View File

@ -124,6 +124,10 @@ public class EditSettingsGUI extends CommonGUI
// Return Button
panelBuilder.item(44, this.returnButton);
// Save Settings every time this GUI is created. It will avoid issues with
// Overwritten setting after server stop.
this.addon.saveSettings();
panelBuilder.build();
}

View File

@ -7,6 +7,7 @@ import java.util.List;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.scheduler.BukkitTask;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.builders.PanelBuilder;
@ -118,7 +119,7 @@ public class ListLibraryGUI extends CommonGUI
}
panelBuilder.item(4, this.createDownloadNow());
panelBuilder.item(44, this.returnButton);
panelBuilder.item(44, this.createReturnButton());
panelBuilder.build();
}
@ -153,10 +154,16 @@ public class ListLibraryGUI extends CommonGUI
}
else
{
this.addon.getWebManager().requestCatalogGitHubData(false);
this.addon.getWebManager().requestCatalogGitHubData(this.clearCache);
// Fix multiclick issue.
if (this.updateTask != null)
{
this.updateTask.cancel();
}
// add some delay to rebuilding gui.
this.addon.getPlugin().getServer().getScheduler().runTaskLaterAsynchronously(
this.updateTask = this.addon.getPlugin().getServer().getScheduler().runTaskLater(
this.addon.getPlugin(),
this::build,
100L);
@ -169,6 +176,33 @@ public class ListLibraryGUI extends CommonGUI
}
/**
* This creates return button, that allows to exist or return to parent gui,
* @return PanelItem for return button.
*/
private PanelItem createReturnButton()
{
return new PanelItemBuilder().
name(this.user.getTranslation("challenges.gui.buttons.return")).
icon(Material.OAK_DOOR).
clickHandler((panel, user1, clickType, i) -> {
if (this.updateTask != null)
{
this.updateTask.cancel();
}
if (this.parentGUI == null)
{
this.user.closeInventory();
return true;
}
this.parentGUI.build();
return true;
}).build();
}
/**
* This method creates button for given library entry.
* @param libraryEntry LibraryEntry which button must be created.
@ -199,10 +233,20 @@ public class ListLibraryGUI extends CommonGUI
if (this.parentGUI != null)
{
if (this.updateTask != null)
{
this.updateTask.cancel();
}
this.parentGUI.build();
}
else
{
if (this.updateTask != null)
{
this.updateTask.cancel();
}
this.user.closeInventory();
}
}
@ -252,6 +296,11 @@ public class ListLibraryGUI extends CommonGUI
*/
private boolean clearCache;
/**
* Stores update task that is triggered.
*/
private BukkitTask updateTask = null;
/**
* This variable will protect against spam-click.
*/

View File

@ -114,9 +114,6 @@ public class GuiUtils
switch (entity)
{
case PIG_ZOMBIE:
itemStack = new ItemStack(Material.ZOMBIE_PIGMAN_SPAWN_EGG);
break;
case ENDER_DRAGON:
itemStack = new ItemStack(Material.DRAGON_EGG);
break;
@ -153,6 +150,12 @@ public class GuiUtils
break;
}
if (entity.name().equals("PIG_ZOMBIE"))
{
// If pig zombie exist, then pigman spawn egg exists too.
itemStack = new ItemStack(Material.getMaterial("ZOMBIE_PIGMAN_SPAWN_EGG"));
}
itemStack.setAmount(amount);
return itemStack;

View File

@ -111,7 +111,15 @@ public enum HeadLib
PILLAGER("1ac9d5aa-46ef-4d71-b077-4564382c0a43", "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNGFlZTZiYjM3Y2JmYzkyYjBkODZkYjVhZGE0NzkwYzY0ZmY0NDY4ZDY4Yjg0OTQyZmRlMDQ0MDVlOGVmNTMzMyJ9fX0="),
RAVAGER("def81bd7-85e5-4644-b1b2-e7521e53bba8", "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMWNiOWYxMzlmOTQ4OWQ4NmU0MTBhMDZkOGNiYzY3MGM4MDI4MTM3NTA4ZTNlNGJlZjYxMmZlMzJlZGQ2MDE5MyJ9fX0="),
TRADER_LLAMA("47dbdab5-105f-42bc-9580-c61cee9231f3", "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzA4N2E1NTZkNGZmYTk1ZWNkMjg0NGYzNTBkYzQzZTI1NGU1ZDUzNWZhNTk2ZjU0MGQ3ZTc3ZmE2N2RmNDY5NiJ9fX0="),
WANDERING_TRADER("943947ea-3e1a-4fdc-85e5-f538379f05e9", "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNWYxMzc5YTgyMjkwZDdhYmUxZWZhYWJiYzcwNzEwZmYyZWMwMmRkMzRhZGUzODZiYzAwYzkzMGM0NjFjZjkzMiJ9fX0=");
WANDERING_TRADER("943947ea-3e1a-4fdc-85e5-f538379f05e9", "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNWYxMzc5YTgyMjkwZDdhYmUxZWZhYWJiYzcwNzEwZmYyZWMwMmRkMzRhZGUzODZiYzAwYzkzMGM0NjFjZjkzMiJ9fX0="),
// Since 1.15
BEE("77342662-8870-445a-869f-f0aef1406b3d", "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTlhYzE2ZjI5NmI0NjFkMDVlYTA3ODVkNDc3MDMzZTUyNzM1OGI0ZjMwYzI2NmFhMDJmMDIwMTU3ZmZjYTczNiJ9fX0="),
// Since 1.16
PIGLIN("7b3f9b15-325b-4d6e-a184-0455e233a1cc", "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2NlZDlkODAxYWE2ZjgzZjhlNDlmOTBkOWE4Yjg1YjdmOGZkYTU4M2Q4NWY3MmNmZmI2OTg2NzI1Nzg5ZjYzNiJ9fX0="),
ZOMBIFIED_PIGLIN("4f013cfb-84f8-4d80-8529-25127f6c70ee", "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvN2VhYmFlY2M1ZmFlNWE4YTQ5Yzg4NjNmZjQ4MzFhYWEyODQxOThmMWEyMzk4ODkwYzc2NWUwYThkZTE4ZGE4YyJ9fX0="),
STRIDER("d1c2fba9-6633-4625-9cda-8528fae6fe09", "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMThhOWFkZjc4MGVjN2RkNDYyNWM5YzA3NzkwNTJlNmExNWE0NTE4NjY2MjM1MTFlNGM4MmU5NjU1NzE0YjNjMSJ9fX0="),
HOGLIN("8196c240-e96a-4434-b630-6b191ceeb480", "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOWJiOWJjMGYwMWRiZDc2MmEwOGQ5ZTc3YzA4MDY5ZWQ3Yzk1MzY0YWEzMGNhMTA3MjIwODU2MWI3MzBlOGQ3NSJ9fX0="),
ZOGLIN("d6f4e7ce-dc71-4c81-97dc-df0d15d39a68", "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNWZhMGFkYTM0MTFmYmE4Yjg4NTgzZDg2NGIyNTI2MDZlOTNkZmRmNjQ3NjkwZDNjZjRjMDE3YjYzYmFiMTJiMCJ9fX0=");
// ---------------------------------------------------------------------

View File

@ -1,6 +1,7 @@
name: Challenges
main: world.bentobox.challenges.ChallengesAddon
version: ${version}${build.number}
api-version: 1.14
repository: 'BentoBoxWorld/Challenges'
metrics: true
@ -18,52 +19,12 @@ permissions:
description: Let the player use the '/challenges' command
default: true
bskyblock.challenges:
'[gamemode].challenges':
description: Let the player use the '/island challenges' command
default: true
bskyblock.challenges.multiple:
'[gamemode].challenges.multiple':
description: Let the player complete challenge multiple times
default: true
bskyblock.admin.challenges:
'[gamemode].admin.challenges':
description: Access challenge admin commands
default: op
acidisland.challenges:
description: Let the player use the '/ai challenges' command
default: true
acidisland.challenges.multiple:
description: Let the player complete challenge multiple times
default: true
acidisland.admin.challenges:
description: Access challenge admin commands
default: op
caveblock.challenges:
description: Let the player use the '/cave challenges' command
default: true
caveblock.challenges.multiple:
description: Let the player complete challenge multiple times
default: true
caveblock.admin.challenges:
description: Access challenge admin commands
default: op
skygrid.challenges:
description: Let the player use the '/skygrid challenges' command
default: true
skygrid.challenges.multiple:
description: Let the player complete challenge multiple times
default: true
skygrid.admin.challenges:
description: Access challenge admin commands
default: op
aoneblock.challenges:
description: Let the player use the '/aoneblock challenges' command
default: true
aoneblock.challenges.multiple:
description: Let the player complete challenge multiple times
default: true
aoneblock.admin.challenges:
description: Access challenge admin commands
default: op
default: op

View File

@ -1196,7 +1196,8 @@
},
"rewardText": "Some handy mending books and redstone ore",
"rewardItems": [
"is:\n ==: org.bukkit.inventory.ItemStack\n v: 1976\n type: ENCHANTED_BOOK\n amount: 2\n meta:\n ==: ItemMeta\n meta-type: ENCHANTED\n stored-enchants:\n MENDING: 1\n",
"is:\n ==: org.bukkit.inventory.ItemStack\n v: 1976\n type: ENCHANTED_BOOK\n amount: 1\n meta:\n ==: ItemMeta\n meta-type: ENCHANTED\n stored-enchants:\n MENDING: 1\n",
"is:\n ==: org.bukkit.inventory.ItemStack\n v: 1976\n type: ENCHANTED_BOOK\n amount: 1\n meta:\n ==: ItemMeta\n meta-type: ENCHANTED\n stored-enchants:\n MENDING: 1\n",
"is:\n ==: org.bukkit.inventory.ItemStack\n v: 1976\n type: REDSTONE_ORE\n amount: 16\n"
],
"rewardExperience": 50,
@ -1282,7 +1283,8 @@
},
"rewardText": "Some mending books and lapis ore",
"rewardItems": [
"is:\n ==: org.bukkit.inventory.ItemStack\n v: 1976\n type: ENCHANTED_BOOK\n amount: 2\n meta:\n ==: ItemMeta\n meta-type: ENCHANTED\n stored-enchants:\n MENDING: 1\n",
"is:\n ==: org.bukkit.inventory.ItemStack\n v: 1976\n type: ENCHANTED_BOOK\n amount: 1\n meta:\n ==: ItemMeta\n meta-type: ENCHANTED\n stored-enchants:\n MENDING: 1\n",
"is:\n ==: org.bukkit.inventory.ItemStack\n v: 1976\n type: ENCHANTED_BOOK\n amount: 1\n meta:\n ==: ItemMeta\n meta-type: ENCHANTED\n stored-enchants:\n MENDING: 1\n",
"is:\n ==: org.bukkit.inventory.ItemStack\n v: 1976\n type: LAPIS_ORE\n amount: 16\n"
],
"rewardExperience": 50,

View File

@ -118,6 +118,7 @@ public class ChallengesManagerTest {
BentoBox plugin = mock(BentoBox.class);
Whitebox.setInternalState(BentoBox.class, "instance", plugin);
when(addon.getPlugin()).thenReturn(plugin);
User.setPlugin(plugin);
// IWM
when(plugin.getIWM()).thenReturn(iwm);

View File

@ -167,6 +167,7 @@ public class TryToCompleteTest {
PowerMockito.mockStatic(Util.class);
when(Util.getWorld(any())).thenReturn(world);
when(Util.prettifyText(anyString())).thenCallRealMethod();
when(Util.stripSpaceAfterColorCodes(anyString())).thenCallRealMethod();
// Island World Manager
IslandWorldManager iwm = mock(IslandWorldManager.class);
@ -252,7 +253,6 @@ public class TryToCompleteTest {
// ItemFactory
ItemFactory itemFactory = mock(ItemFactory.class);
when(Bukkit.getItemFactory()).thenReturn(itemFactory);
}
/**