diff --git a/src/main/java/gg/auroramc/quests/AuroraQuests.java b/src/main/java/gg/auroramc/quests/AuroraQuests.java index b8ca302..1e3b473 100644 --- a/src/main/java/gg/auroramc/quests/AuroraQuests.java +++ b/src/main/java/gg/auroramc/quests/AuroraQuests.java @@ -9,12 +9,15 @@ import gg.auroramc.quests.hooks.HookManager; import gg.auroramc.quests.listener.*; import gg.auroramc.quests.placeholder.QuestPlaceholderHandler; +import io.papermc.paper.threadedregions.scheduler.ScheduledTask; import lombok.Getter; import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; import org.quartz.SchedulerException; import org.quartz.impl.StdSchedulerFactory; +import java.util.concurrent.TimeUnit; + @Getter public class AuroraQuests extends JavaPlugin { @Getter @@ -28,6 +31,7 @@ public static AuroraLogger logger() { private ConfigManager configManager; private CommandManager commandManager; private QuestManager questManager; + private ScheduledTask unlockTask; @Override public void onLoad() { @@ -70,27 +74,35 @@ public void onEnable() { questManager.reload(); Bukkit.getPluginManager().registerEvents(new PlayerListener(this), this); + reloadUnlockTask(); } public void reload() { configManager.reload(); commandManager.reload(); questManager.reload(); + reloadUnlockTask(); Bukkit.getOnlinePlayers().forEach(player -> { - questManager.rollQuestsIfNecessary(player); + questManager.tryUnlockQuestPools(player); questManager.tryStartGlobalQuests(player); + questManager.rollQuestsIfNecessary(player); }); } @Override public void onDisable() { commandManager.unregisterCommands(); + try { l.info("Shutting down scheduler..."); StdSchedulerFactory.getDefaultScheduler().shutdown(true); } catch (SchedulerException e) { l.severe("Failed to shutdown scheduler: " + e.getMessage()); } + + if (unlockTask != null && !unlockTask.isCancelled()) { + unlockTask.cancel(); + } } private void registerListeners() { @@ -115,4 +127,23 @@ private void registerListeners() { pm.registerEvents(new SmeltingListener(), this); pm.registerEvents(new TamingListener(), this); } + + private void reloadUnlockTask() { + var cf = configManager.getConfig().getUnlockTask(); + + if (!cf.getEnabled()) { + if (unlockTask != null && !unlockTask.isCancelled()) { + unlockTask.cancel(); + unlockTask = null; + } + return; + } + + unlockTask = Bukkit.getAsyncScheduler().runAtFixedRate(this, (task) -> { + Bukkit.getOnlinePlayers().forEach(player -> { + questManager.tryUnlockQuestPools(player); + questManager.tryStartGlobalQuests(player); + }); + }, cf.getInterval(), cf.getInterval(), TimeUnit.SECONDS); + } } \ No newline at end of file diff --git a/src/main/java/gg/auroramc/quests/api/data/QuestData.java b/src/main/java/gg/auroramc/quests/api/data/QuestData.java index d43a898..7276862 100644 --- a/src/main/java/gg/auroramc/quests/api/data/QuestData.java +++ b/src/main/java/gg/auroramc/quests/api/data/QuestData.java @@ -18,11 +18,21 @@ public class QuestData extends UserDataHolder { private final Map> completedQuests = Maps.newConcurrentMap(); private final Map completedCount = Maps.newConcurrentMap(); private final Map> questUnlocks = Maps.newConcurrentMap(); + private final Set poolUnlocks = Sets.newConcurrentHashSet(); public PoolRollData getPoolRollData(String poolId) { return rolledQuests.get(poolId); } + public void unlockPool(String poolId) { + poolUnlocks.add(poolId); + dirty.set(true); + } + + public boolean isPoolUnlocked(String poolId) { + return poolUnlocks.contains(poolId); + } + public void setRolledQuests(String poolId, List quests) { rolledQuests.put(poolId, new PoolRollData(System.currentTimeMillis(), quests)); completedQuests.computeIfAbsent(poolId, k -> Sets.newConcurrentHashSet()).clear(); @@ -104,7 +114,7 @@ public void serializeInto(ConfigurationSection data) { for (var questEntry : poolEntry.getValue().entrySet()) { var questSection = poolSection.createSection(questEntry.getKey()); for (var taskEntry : questEntry.getValue().entrySet()) { - if(taskEntry.getValue() > 0) { + if (taskEntry.getValue() > 0) { questSection.set(taskEntry.getKey(), taskEntry.getValue()); } } @@ -118,6 +128,9 @@ public void serializeInto(ConfigurationSection data) { } } + // Pool unlocks + data.set("pool_unlocks", poolUnlocks.stream().toList()); + // Completed quests for (var poolEntry : completedQuests.entrySet()) { for (var questEntry : poolEntry.getValue()) { @@ -174,5 +187,7 @@ public void initFrom(@Nullable ConfigurationSection data) { completedCount.put(key, completedCountSection.getLong(key)); } } + + poolUnlocks.addAll(data.getStringList("pool_unlocks")); } } diff --git a/src/main/java/gg/auroramc/quests/api/quest/Quest.java b/src/main/java/gg/auroramc/quests/api/quest/Quest.java index 5f266a3..94690cd 100644 --- a/src/main/java/gg/auroramc/quests/api/quest/Quest.java +++ b/src/main/java/gg/auroramc/quests/api/quest/Quest.java @@ -57,7 +57,9 @@ public String getId() { public void progress(Player player, String taskType, double count, Map params) { if (!taskTypes.contains(taskType)) return; if (isCompleted(player)) return; - if (!canStart(player)) return; + if (!holder.isUnlocked(player)) return; + if (!isUnlocked(player)) return; + for (var task : tasks.values()) { if (task.getTaskType().equals(taskType)) { task.progress(player, count, params); @@ -111,16 +113,15 @@ private boolean hasStartRequirements() { public boolean isUnlocked(Player player) { var data = AuroraAPI.getUserManager().getUser(player).getData(QuestData.class); - return config.getStartRequirements() == null || data.isQuestStartUnlocked(holder.getId(), getId()); + return data.isQuestStartUnlocked(holder.getId(), getId()) || !hasStartRequirements(); } public void tryStart(Player player) { if (!holder.isGlobal()) return; - if (!hasStartRequirements()) return; + if (isUnlocked(player)) return; var data = AuroraAPI.getUserManager().getUser(player).getData(QuestData.class); - if (data.isQuestStartUnlocked(holder.getId(), getId())) return; - if (canStart(player) && config.getStartRequirements() != null) { + if (config.getStartRequirements() != null && canStart(player)) { data.setQuestStartUnlock(holder.getId(), getId()); var msg = AuroraQuests.getInstance().getConfigManager().getMessageConfig().getGlobalQuestUnlocked(); Chat.sendMessage(player, msg, Placeholder.of("{quest}", config.getName()), Placeholder.of("{pool}", holder.getConfig().getName())); diff --git a/src/main/java/gg/auroramc/quests/api/quest/QuestManager.java b/src/main/java/gg/auroramc/quests/api/quest/QuestManager.java index 731c125..ea82242 100644 --- a/src/main/java/gg/auroramc/quests/api/quest/QuestManager.java +++ b/src/main/java/gg/auroramc/quests/api/quest/QuestManager.java @@ -79,6 +79,7 @@ private void actuallyProgress(Player player, String taskType, double amount, Map synchronized (getPlayerLock(player)) { for (var pool : pools.values()) { if (!pool.hasTaskType(taskType)) continue; + if (!pool.isUnlocked(player)) continue; for (var quest : pool.getNotCompletedPlayerQuests(player)) { quest.progress(player, taskType, amount, params); } @@ -113,4 +114,10 @@ public void tryStartGlobalQuests(Player player) { } } } + + public void tryUnlockQuestPools(Player player) { + for (var pool : pools.values()) { + pool.tryUnlock(player); + } + } } diff --git a/src/main/java/gg/auroramc/quests/api/quest/QuestPool.java b/src/main/java/gg/auroramc/quests/api/quest/QuestPool.java index da33c71..3ac55b6 100644 --- a/src/main/java/gg/auroramc/quests/api/quest/QuestPool.java +++ b/src/main/java/gg/auroramc/quests/api/quest/QuestPool.java @@ -3,6 +3,7 @@ import com.google.common.collect.Maps; import com.google.common.collect.Sets; import gg.auroramc.aurora.api.AuroraAPI; +import gg.auroramc.aurora.api.item.TypeId; import gg.auroramc.aurora.api.levels.MatcherManager; import gg.auroramc.aurora.api.message.Chat; import gg.auroramc.aurora.api.message.Placeholder; @@ -54,6 +55,55 @@ public QuestPool(PoolConfig config, RewardFactory rewardFactory) { AuroraQuests.logger().debug("Loaded difficulties for pool " + config.getId() + ": " + String.join(", ", config.getDifficulties().keySet())); } + private boolean canUnlock(Player player) { + var data = AuroraAPI.getUserManager().getUser(player).getData(QuestData.class); + + if (config.getUnlockRequirements() == null) return true; + + if (config.getUnlockRequirements().getQuests() != null) { + for (var questId : config.getUnlockRequirements().getQuests()) { + var typeId = TypeId.fromString(questId); + var pool = typeId.namespace().equals("minecraft") ? getId() : typeId.namespace(); + if (!data.hasCompletedQuest(pool, typeId.id())) { + return false; + } + } + } + + if (config.getUnlockRequirements().getPermissions() != null) { + for (var perm : config.getUnlockRequirements().getPermissions()) { + if (!player.hasPermission(perm)) { + return false; + } + } + } + + return true; + } + + private boolean hasStartRequirements() { + return config.getUnlockRequirements() != null && ((config.getUnlockRequirements().getQuests() != null && !config.getUnlockRequirements().getQuests().isEmpty()) || + (config.getUnlockRequirements().getPermissions() != null && !config.getUnlockRequirements().getPermissions().isEmpty())); + } + + public boolean isUnlocked(Player player) { + var data = AuroraAPI.getUserManager().getUser(player).getData(QuestData.class); + return !hasStartRequirements() || data.isPoolUnlocked(getId()); + } + + public void tryUnlock(Player player) { + if (isUnlocked(player)) return; + var data = getQuestData(player); + + if (canUnlock(player)) { + data.unlockPool(getId()); + var msg = AuroraQuests.getInstance().getConfigManager().getMessageConfig().getPoolUnlocked(); + Chat.sendMessage(player, msg, Placeholder.of("{pool}", config.getName())); + tryStartGlobalQuests(player); + reRollQuests(player, false); + } + } + public boolean hasTaskType(String taskType) { return taskTypes.contains(taskType); } @@ -177,6 +227,7 @@ public Collection getQuests() { public void tryStartGlobalQuests(Player player) { if (!isGlobal()) return; + if (!isUnlocked(player)) return; for (var quest : quests.values()) { quest.tryStart(player); } @@ -186,6 +237,7 @@ public boolean rollIfNecessary(Player player, boolean sendNotification) { AuroraQuests.logger().debug("Checking if player " + player.getName() + " needs to reroll quests for pool " + config.getId()); if (!isTimedRandom()) return false; if (!questRoller.isValid()) return false; + if (!isUnlocked(player)) return false; AuroraQuests.logger().debug("Pool is timed random and quest roller is valid"); try { diff --git a/src/main/java/gg/auroramc/quests/config/Config.java b/src/main/java/gg/auroramc/quests/config/Config.java index 8bd7c8d..a848512 100644 --- a/src/main/java/gg/auroramc/quests/config/Config.java +++ b/src/main/java/gg/auroramc/quests/config/Config.java @@ -24,6 +24,7 @@ public class Config extends AuroraConfig { private LevelUpMessage questCompleteMessage; private CommandAliasConfig commandAliases; private List sortOrder; + private UnlockTaskConfig unlockTask = new UnlockTaskConfig(); @IgnoreField private Map sortOderMap; @@ -55,6 +56,12 @@ public static final class DisplayComponent { private String line; } + @Getter + public static final class UnlockTaskConfig { + private Boolean enabled = false; + private Integer interval = 5; + } + @Getter public static final class LevelUpMessage { private Boolean enabled; diff --git a/src/main/java/gg/auroramc/quests/config/MessageConfig.java b/src/main/java/gg/auroramc/quests/config/MessageConfig.java index bd1e8f8..b15c969 100644 --- a/src/main/java/gg/auroramc/quests/config/MessageConfig.java +++ b/src/main/java/gg/auroramc/quests/config/MessageConfig.java @@ -26,6 +26,7 @@ public class MessageConfig extends AuroraConfig { private String reRolledSource = "&aQuests for {player} for pool {pool} have been re-rolled!"; private String globalQuestUnlocked = "&aYou have unlocked the {quest} quest in {pool}!"; private String poolNotFound = "&cThere isn't any quest line with this id: {pool}!"; + private String poolUnlocked = "&aYou have unlocked a new quest pool: {pool}!"; public MessageConfig(AuroraQuests plugin, String language) { super(getFile(plugin, language)); diff --git a/src/main/java/gg/auroramc/quests/config/quest/PoolConfig.java b/src/main/java/gg/auroramc/quests/config/quest/PoolConfig.java index 06b3622..08fb8dc 100644 --- a/src/main/java/gg/auroramc/quests/config/quest/PoolConfig.java +++ b/src/main/java/gg/auroramc/quests/config/quest/PoolConfig.java @@ -23,6 +23,7 @@ public class PoolConfig extends AuroraConfig { private PoolMenuItem menuItem; private PoolMenu menu; private Leveling leveling; + private StartRequirementConfig unlockRequirements; @Setter @IgnoreField @@ -37,6 +38,7 @@ public static class PoolMenuItem { private Boolean showInMainMenu; private Integer page; private ItemConfig item; + private List lockedLore; } @Getter diff --git a/src/main/java/gg/auroramc/quests/hooks/luckperms/LuckPermsHook.java b/src/main/java/gg/auroramc/quests/hooks/luckperms/LuckPermsHook.java index d9c4d76..0d022db 100644 --- a/src/main/java/gg/auroramc/quests/hooks/luckperms/LuckPermsHook.java +++ b/src/main/java/gg/auroramc/quests/hooks/luckperms/LuckPermsHook.java @@ -13,7 +13,7 @@ public class LuckPermsHook implements Hook { public void hook(AuroraQuests plugin) { plugin.getQuestManager().getRewardFactory() .registerRewardType(NamespacedId.fromDefault("permission"), PermissionReward.class); - + plugin.getQuestManager().getRewardAutoCorrector() .registerCorrector(NamespacedId.fromDefault("permission"), new PermissionCorrector()); @@ -21,7 +21,10 @@ public void hook(AuroraQuests plugin) { lp.getEventBus().subscribe(UserDataRecalculateEvent.class, (event) -> { var player = Bukkit.getPlayer(event.getUser().getUniqueId()); - if (player != null) plugin.getQuestManager().tryStartGlobalQuests(player); + if (player != null) { + plugin.getQuestManager().tryUnlockQuestPools(player); + plugin.getQuestManager().tryStartGlobalQuests(player); + } }); AuroraQuests.logger().info("Hooked into LuckPerms for permission rewards and for permission start requirements"); diff --git a/src/main/java/gg/auroramc/quests/listener/PlayerListener.java b/src/main/java/gg/auroramc/quests/listener/PlayerListener.java index c73e24a..fe2667f 100644 --- a/src/main/java/gg/auroramc/quests/listener/PlayerListener.java +++ b/src/main/java/gg/auroramc/quests/listener/PlayerListener.java @@ -41,7 +41,9 @@ public void onPlayerQuit(PlayerQuitEvent event) { } private void roll(Player player) { + plugin.getQuestManager().tryUnlockQuestPools(player); plugin.getQuestManager().tryStartGlobalQuests(player); + var pools = plugin.getQuestManager().rollQuestsIfNecessary(player); if (pools.isEmpty()) return; diff --git a/src/main/java/gg/auroramc/quests/menu/MainMenu.java b/src/main/java/gg/auroramc/quests/menu/MainMenu.java index 4c62c92..8dafa23 100644 --- a/src/main/java/gg/auroramc/quests/menu/MainMenu.java +++ b/src/main/java/gg/auroramc/quests/menu/MainMenu.java @@ -49,7 +49,9 @@ private AuroraMenu createMenu() { }); var pools = AuroraQuests.getInstance().getQuestManager().getQuestPools(); - var maybeInt = pools.stream().filter(pool -> pool.getConfig().getMenuItem().getShowInMainMenu()) + var maybeInt = pools.stream() + .filter(pool -> pool.getConfig().getMenuItem().getShowInMainMenu()) + .filter(pool -> pool.isUnlocked(player) || pool.getConfig().getUnlockRequirements().isAlwaysShowInMenu()) .mapToInt(pool -> pool.getConfig().getMenuItem().getPage()).max(); if (maybeInt.isPresent()) { @@ -61,6 +63,7 @@ private AuroraMenu createMenu() { for (var pool : pools) { + if (!pool.isUnlocked(player) && !pool.getConfig().getUnlockRequirements().isAlwaysShowInMenu()) continue; var mi = pool.getConfig().getMenuItem(); if (!mi.getShowInMainMenu()) continue; if (mi.getPage() != page) continue; @@ -85,12 +88,21 @@ private AuroraMenu createMenu() { AuroraAPI.formatNumber(Math.max(Bukkit.getOnlinePlayers().size(), AuroraAPI.getLeaderboards().getBoardSize(boardName))))); } + var lore = new ArrayList<>(mi.getItem().getLore()); + + if (!pool.isUnlocked(player)) { + lore.addAll(mi.getLockedLore()); + } + menu.addItem(ItemBuilder.of(mi.getItem()) + .setLore(lore) .placeholder(Placeholder.of("{name}", pool.getConfig().getName())) .placeholder(Placeholder.of("{total_completed}", AuroraAPI.formatNumber(pool.getCompletedQuestCount(player)))) .placeholder(placeholders) .build(player), (e) -> { - new PoolMenu(player, pool).open(); + if (pool.isUnlocked(player)) { + new PoolMenu(player, pool).open(); + } }); } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index df22461..7918e55 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -5,6 +5,12 @@ debug: false language: en prevent-creative-mode: true +# Timer to try to unlock global quests and quest pools if for some reason the event driven method doesn't work +unlock-task: + enabled: false + # Interval in seconds + interval: 5 + command-aliases: quests: [ "quests", "quest" ] diff --git a/src/main/resources/messages_en.yml b/src/main/resources/messages_en.yml index 5a157b4..af73383 100644 --- a/src/main/resources/messages_en.yml +++ b/src/main/resources/messages_en.yml @@ -14,4 +14,5 @@ menu-opened: "&aOpened quest menu for {player}" re-rolled-target: "&aYour quests for {pool} have been re-rolled!" re-rolled-source: "&aQuests for {player} for pool {pool} have been re-rolled!" global-quest-unlocked: "&aYou have unlocked the {quest} quest in {pool}!" -pool-not-found: "&cThere isn't any quest line with this id: {pool}!" \ No newline at end of file +pool-not-found: "&cThere isn't any quest line with this id: {pool}!" +pool-unlocked: "&aYou have unlocked a new quest pool: {pool}!" \ No newline at end of file diff --git a/src/main/resources/quest_pools/daily/config.yml b/src/main/resources/quest_pools/daily/config.yml index a5629ac..19553ac 100644 --- a/src/main/resources/quest_pools/daily/config.yml +++ b/src/main/resources/quest_pools/daily/config.yml @@ -14,6 +14,9 @@ reset-frequency: 0 0 0 * * ? menu-item: show-in-main-menu: true page: 1 + locked-lore: + - "" + - "&cThis quest pool is locked!" item: material: WRITABLE_BOOK slot: 10 @@ -88,4 +91,4 @@ leveling: my-command-reward: type: command command: "[console] say %player_name% has reached level 1!" - display: "&8+&aConsole message" + display: "&8+&aConsole message" \ No newline at end of file diff --git a/src/main/resources/quest_pools/global/config.yml b/src/main/resources/quest_pools/global/config.yml index c363a92..04d80b2 100644 --- a/src/main/resources/quest_pools/global/config.yml +++ b/src/main/resources/quest_pools/global/config.yml @@ -11,6 +11,9 @@ difficulties: menu-item: show-in-main-menu: true page: 1 + locked-lore: + - "" + - "&cThis quest pool is locked!" item: material: WRITABLE_BOOK slot: 11 diff --git a/src/main/resources/quest_pools/weekly/config.yml b/src/main/resources/quest_pools/weekly/config.yml index 5f32a67..d8ea55f 100644 --- a/src/main/resources/quest_pools/weekly/config.yml +++ b/src/main/resources/quest_pools/weekly/config.yml @@ -14,6 +14,9 @@ reset-frequency: 0 0 0 ? * MON menu-item: show-in-main-menu: true page: 1 + locked-lore: + - "" + - "&cThis quest pool is locked!" item: material: WRITABLE_BOOK slot: 12