diff --git a/api/src/main/java/net/momirealms/customfishing/api/event/FishingResultEvent.java b/api/src/main/java/net/momirealms/customfishing/api/event/FishingResultEvent.java index bfe1a771..aa533993 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/event/FishingResultEvent.java +++ b/api/src/main/java/net/momirealms/customfishing/api/event/FishingResultEvent.java @@ -25,6 +25,7 @@ import org.bukkit.event.player.PlayerEvent; import org.jetbrains.annotations.NotNull; import java.util.Map; +import java.util.Optional; public class FishingResultEvent extends PlayerEvent implements Cancellable { @@ -73,6 +74,10 @@ public class FishingResultEvent extends PlayerEvent implements Cancellable { return loot; } + public int getAmount() { + return Integer.parseInt(Optional.ofNullable(getArg("{amount}")).orElse("1")); + } + public enum Result { SUCCESS, FAILURE diff --git a/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/ActionTrigger.java b/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/ActionTrigger.java index f73ab0cc..46840904 100644 --- a/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/ActionTrigger.java +++ b/api/src/main/java/net/momirealms/customfishing/api/mechanic/action/ActionTrigger.java @@ -26,5 +26,7 @@ public enum ActionTrigger { CAST, BITE, LAND, - ACTIVATE, TIMER, INTERACT + ACTIVATE, + TIMER, + INTERACT } diff --git a/build.gradle.kts b/build.gradle.kts index 26afabcf..cfb170c2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,7 +7,7 @@ plugins { allprojects { - version = "2.0-beta-0917" + version = "2.0-beta-5" apply() apply(plugin = "java") diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 16816955..c9097bc6 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -44,12 +44,14 @@ dependencies { compileOnly("com.github.Zrips:Jobs:4.17.2") compileOnly("com.github.Archy-X:AureliumSkills:Beta1.3.21") compileOnly("com.github.MilkBowl:VaultAPI:1.7") + compileOnly("org.betonquest:betonquest:2.0.0-SNAPSHOT") // local jars compileOnly(files("libs/AdvancedEnchantments-api.jar")) compileOnly(files("libs/RealisticSeasons-api.jar")) compileOnly(files("libs/CustomCrops-api.jar")) compileOnly(files("libs/mcMMO-api.jar")) + compileOnly(files("libs/ClueScrolls-4.8.7-api.jar")) // api module implementation(project(":api")) diff --git a/plugin/libs/ClueScrolls-4.8.7-api.jar b/plugin/libs/ClueScrolls-4.8.7-api.jar new file mode 100644 index 00000000..783c6ffd Binary files /dev/null and b/plugin/libs/ClueScrolls-4.8.7-api.jar differ diff --git a/plugin/src/main/java/net/momirealms/customfishing/CustomFishingPluginImpl.java b/plugin/src/main/java/net/momirealms/customfishing/CustomFishingPluginImpl.java index d2efbab3..10ca5fc2 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/CustomFishingPluginImpl.java +++ b/plugin/src/main/java/net/momirealms/customfishing/CustomFishingPluginImpl.java @@ -51,6 +51,7 @@ import net.momirealms.customfishing.storage.StorageManagerImpl; import net.momirealms.customfishing.version.VersionManagerImpl; import org.bukkit.Bukkit; import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.event.HandlerList; import org.jetbrains.annotations.NotNull; import java.io.File; @@ -130,6 +131,7 @@ public class CustomFishingPluginImpl extends CustomFishingPlugin { ((TotemManagerImpl) this.totemManager).disable(); this.coolDownManager.disable(); this.commandManager.unload(); + HandlerList.unregisterAll(this); } @Override diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/IntegrationManagerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/compatibility/IntegrationManagerImpl.java index 49ece251..315c7cd4 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/compatibility/IntegrationManagerImpl.java +++ b/plugin/src/main/java/net/momirealms/customfishing/compatibility/IntegrationManagerImpl.java @@ -30,14 +30,18 @@ import net.momirealms.customfishing.compatibility.entity.ItemsAdderEntityImpl; import net.momirealms.customfishing.compatibility.entity.MythicEntityImpl; import net.momirealms.customfishing.compatibility.item.*; import net.momirealms.customfishing.compatibility.level.*; +import net.momirealms.customfishing.compatibility.quest.BetonQuestHook; +import net.momirealms.customfishing.compatibility.quest.ClueScrollsHook; import net.momirealms.customfishing.compatibility.season.CustomCropsSeasonImpl; import net.momirealms.customfishing.compatibility.season.RealisticSeasonsImpl; +import org.bukkit.Bukkit; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Objects; public class IntegrationManagerImpl implements IntegrationManager { @@ -50,7 +54,7 @@ public class IntegrationManagerImpl implements IntegrationManager { this.plugin = plugin; this.levelPluginMap = new HashMap<>(); this.enchantments = new HashMap<>(); - this.init(); + this.load(); } public void disable() { @@ -58,7 +62,7 @@ public class IntegrationManagerImpl implements IntegrationManager { this.levelPluginMap.clear(); } - public void init() { + public void load() { if (plugin.isHookedPluginEnabled("ItemsAdder")) { plugin.getItemManager().registerItemLibrary(new ItemsAdderItemImpl()); plugin.getBlockManager().registerBlockLibrary(new ItemsAdderBlockImpl()); @@ -129,6 +133,16 @@ public class IntegrationManagerImpl implements IntegrationManager { if (plugin.isHookedPluginEnabled("Vault")) { VaultHook.initialize(); } + if (plugin.isHookedPluginEnabled("ClueScrolls")) { + ClueScrollsHook clueScrollsHook = new ClueScrollsHook(); + Bukkit.getPluginManager().registerEvents(clueScrollsHook, plugin); + hookMessage("ClueScrolls"); + } + if (plugin.isHookedPluginEnabled("BetonQuest")) { + if (Objects.requireNonNull(Bukkit.getPluginManager().getPlugin("BetonQuest")).getPluginMeta().getVersion().startsWith("2")) { + BetonQuestHook.register(); + } + } } @Override diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/quest/BetonQuestHook.java b/plugin/src/main/java/net/momirealms/customfishing/compatibility/quest/BetonQuestHook.java new file mode 100644 index 00000000..54b15602 --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customfishing/compatibility/quest/BetonQuestHook.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.compatibility.quest; + +import net.momirealms.customfishing.api.event.FishingResultEvent; +import net.momirealms.customfishing.api.util.LogUtils; +import org.betonquest.betonquest.BetonQuest; +import org.betonquest.betonquest.Instruction; +import org.betonquest.betonquest.VariableNumber; +import org.betonquest.betonquest.api.CountingObjective; +import org.betonquest.betonquest.api.config.quest.QuestPackage; +import org.betonquest.betonquest.api.profiles.OnlineProfile; +import org.betonquest.betonquest.api.profiles.Profile; +import org.betonquest.betonquest.exceptions.InstructionParseException; +import org.betonquest.betonquest.utils.PlayerConverter; +import org.betonquest.betonquest.utils.location.CompoundLocation; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; + +import java.util.Collections; +import java.util.HashSet; + +@SuppressWarnings("DuplicatedCode") +public class BetonQuestHook { + + public static void register() { + BetonQuest.getInstance().registerObjectives("customfishing_id", IDObjective.class); + BetonQuest.getInstance().registerObjectives("customfishing_group", GroupObjective.class); + } + + public static class IDObjective extends CountingObjective implements Listener { + + private final CompoundLocation playerLocation; + private final VariableNumber rangeVar; + private final HashSet loot_ids; + + public IDObjective(Instruction instruction) throws InstructionParseException { + super(instruction, "loot_to_fish"); + loot_ids = new HashSet<>(); + Collections.addAll(loot_ids, instruction.getArray()); + targetAmount = instruction.getVarNum(); + preCheckAmountNotLessThanOne(targetAmount); + final QuestPackage pack = instruction.getPackage(); + final String loc = instruction.getOptional("playerLocation"); + final String range = instruction.getOptional("range"); + if (loc != null && range != null) { + playerLocation = new CompoundLocation(pack, loc); + rangeVar = new VariableNumber(pack, range); + } else { + playerLocation = null; + rangeVar = null; + } + } + + @EventHandler + public void onFish(FishingResultEvent event) { + if (event.getResult() != FishingResultEvent.Result.FAILURE) { + OnlineProfile onlineProfile = PlayerConverter.getID(event.getPlayer()); + if (!containsPlayer(onlineProfile)) { + return; + } + if (isInvalidLocation(event, onlineProfile)) { + return; + } + if (this.loot_ids.contains(event.getLoot().getID()) && this.checkConditions(onlineProfile)) { + getCountingData(onlineProfile).progress(event.getAmount()); + completeIfDoneOrNotify(onlineProfile); + } + } + } + + private boolean isInvalidLocation(FishingResultEvent event, final Profile profile) { + if (playerLocation == null || rangeVar == null) { + return false; + } + + final Location targetLocation; + try { + targetLocation = playerLocation.getLocation(profile); + } catch (final org.betonquest.betonquest.exceptions.QuestRuntimeException e) { + LogUtils.warn(e.getMessage()); + return true; + } + final int range = rangeVar.getInt(profile); + final Location playerLoc = event.getPlayer().getLocation(); + return !playerLoc.getWorld().equals(targetLocation.getWorld()) || targetLocation.distanceSquared(playerLoc) > range * range; + } + + @Override + public void start() { + Bukkit.getPluginManager().registerEvents(this, BetonQuest.getInstance()); + } + + @Override + public void stop() { + HandlerList.unregisterAll(this); + } + } + + public static class GroupObjective extends CountingObjective implements Listener { + + private final CompoundLocation playerLocation; + private final VariableNumber rangeVar; + private final HashSet loot_groups; + + public GroupObjective(Instruction instruction) throws InstructionParseException { + super(instruction, "group_to_fish"); + loot_groups = new HashSet<>(); + Collections.addAll(loot_groups, instruction.getArray()); + targetAmount = instruction.getVarNum(); + preCheckAmountNotLessThanOne(targetAmount); + final QuestPackage pack = instruction.getPackage(); + final String loc = instruction.getOptional("playerLocation"); + final String range = instruction.getOptional("range"); + if (loc != null && range != null) { + playerLocation = new CompoundLocation(pack, loc); + rangeVar = new VariableNumber(pack, range); + } else { + playerLocation = null; + rangeVar = null; + } + } + + @EventHandler + public void onFish(FishingResultEvent event) { + if (event.getResult() != FishingResultEvent.Result.FAILURE) { + OnlineProfile onlineProfile = PlayerConverter.getID(event.getPlayer()); + if (!containsPlayer(onlineProfile)) { + return; + } + if (isInvalidLocation(event, onlineProfile)) { + return; + } + String[] groups = event.getLoot().getLootGroup(); + if (groups != null) + for (String group : groups) { + if (this.loot_groups.contains(group) && this.checkConditions(onlineProfile)) { + getCountingData(onlineProfile).progress(event.getAmount()); + completeIfDoneOrNotify(onlineProfile); + return; + } + } + } + } + + private boolean isInvalidLocation(FishingResultEvent event, final Profile profile) { + if (playerLocation == null || rangeVar == null) { + return false; + } + + final Location targetLocation; + try { + targetLocation = playerLocation.getLocation(profile); + } catch (final org.betonquest.betonquest.exceptions.QuestRuntimeException e) { + LogUtils.warn(e.getMessage()); + return true; + } + final int range = rangeVar.getInt(profile); + final Location playerLoc = event.getPlayer().getLocation(); + return !playerLoc.getWorld().equals(targetLocation.getWorld()) || targetLocation.distanceSquared(playerLoc) > range * range; + } + + @Override + public void start() { + Bukkit.getPluginManager().registerEvents(this, BetonQuest.getInstance()); + } + + @Override + public void stop() { + HandlerList.unregisterAll(this); + } + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/compatibility/quest/ClueScrollsHook.java b/plugin/src/main/java/net/momirealms/customfishing/compatibility/quest/ClueScrollsHook.java new file mode 100644 index 00000000..64694b7a --- /dev/null +++ b/plugin/src/main/java/net/momirealms/customfishing/compatibility/quest/ClueScrollsHook.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) <2022> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.momirealms.customfishing.compatibility.quest; + +import com.electro2560.dev.cluescrolls.api.*; +import net.momirealms.customfishing.api.CustomFishingPlugin; +import net.momirealms.customfishing.api.event.FishingResultEvent; +import net.momirealms.customfishing.api.mechanic.loot.Loot; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +public class ClueScrollsHook implements Listener { + + private final CustomClue idClue; + private final CustomClue groupClue; + + public ClueScrollsHook() { + idClue = ClueScrollsAPI.getInstance().registerCustomClue(CustomFishingPlugin.getInstance(), "loot", new ClueConfigData("id", DataType.STRING)); + groupClue = ClueScrollsAPI.getInstance().registerCustomClue(CustomFishingPlugin.getInstance(), "group", new ClueConfigData("group", DataType.STRING)); + } + + @EventHandler + public void onFish(FishingResultEvent event) { + if (event.isCancelled() || event.getResult() == FishingResultEvent.Result.FAILURE) + return; + + final Player player = event.getPlayer(); + idClue.handle( + player, + event.getAmount(), + new ClueDataPair("loot_id", "any") + ); + Loot loot = event.getLoot(); + if (loot != null) + idClue.handle( + player, + event.getAmount(), + new ClueDataPair("loot_id", loot.getID()) + ); + if (loot != null && loot.getLootGroup() != null) + for (String group : event.getLoot().getLootGroup()) { + groupClue.handle( + player, + event.getAmount(), + new ClueDataPair("loot_group", group) + ); + } + } +} diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/fishing/FishingManagerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/fishing/FishingManagerImpl.java index cf974a9e..143fa0de 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/fishing/FishingManagerImpl.java +++ b/plugin/src/main/java/net/momirealms/customfishing/mechanic/fishing/FishingManagerImpl.java @@ -42,6 +42,7 @@ import net.momirealms.customfishing.api.mechanic.game.GameInstance; import net.momirealms.customfishing.api.mechanic.game.GameSettings; import net.momirealms.customfishing.api.mechanic.game.GamingPlayer; import net.momirealms.customfishing.api.mechanic.loot.Loot; +import net.momirealms.customfishing.api.mechanic.loot.LootType; import net.momirealms.customfishing.api.mechanic.loot.WeightModifier; import net.momirealms.customfishing.api.util.LogUtils; import net.momirealms.customfishing.api.util.WeightUtils; @@ -525,6 +526,14 @@ public class FishingManagerImpl implements Listener, FishingManager { var fishingPreparation = state.getPreparation(); var player = fishingPreparation.getPlayer(); fishingPreparation.insertArg("{size-multiplier}", String.format("%.2f", effect.getSizeMultiplier())); + int amount; + if (loot.getType() == LootType.ITEM) { + amount = (int) effect.getMultipleLootChance(); + amount += Math.random() < (effect.getMultipleLootChance() - amount) ? 2 : 1; + } else { + amount = 1; + } + fishingPreparation.insertArg("{amount}", String.valueOf(amount)); // call event FishingResultEvent fishingResultEvent = new FishingResultEvent( @@ -540,8 +549,7 @@ public class FishingManagerImpl implements Listener, FishingManager { switch (loot.getType()) { case ITEM -> { - int amount = (int) effect.getMultipleLootChance(); - amount += Math.random() < (effect.getMultipleLootChance() - amount) ? 2 : 1; + // build the items for multiple times instead of using setAmount() to make sure that each item is unique if (loot.getID().equals("vanilla")) { ItemStack itemStack = vanillaLootMap.remove(player.getUniqueId()); @@ -560,8 +568,12 @@ public class FishingManagerImpl implements Listener, FishingManager { } return; } - case ENTITY -> plugin.getEntityManager().summonEntity(hook.getLocation(), player.getLocation(), loot); - case BLOCK -> plugin.getBlockManager().summonBlock(player, hook.getLocation(), player.getLocation(), loot); + case ENTITY -> { + plugin.getEntityManager().summonEntity(hook.getLocation(), player.getLocation(), loot); + } + case BLOCK -> { + plugin.getBlockManager().summonBlock(player, hook.getLocation(), player.getLocation(), loot); + } } doSuccessActions(loot, effect, fishingPreparation, player); } diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/totem/TotemManagerImpl.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/totem/TotemManagerImpl.java index f11a92bf..3d8c789f 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/totem/TotemManagerImpl.java +++ b/plugin/src/main/java/net/momirealms/customfishing/mechanic/totem/TotemManagerImpl.java @@ -26,9 +26,9 @@ import net.momirealms.customfishing.api.mechanic.action.ActionTrigger; import net.momirealms.customfishing.api.mechanic.condition.Condition; import net.momirealms.customfishing.api.mechanic.effect.EffectCarrier; import net.momirealms.customfishing.api.scheduler.CancellableTask; +import net.momirealms.customfishing.mechanic.totem.block.TotemBlock; import net.momirealms.customfishing.mechanic.totem.block.property.AxisImpl; import net.momirealms.customfishing.mechanic.totem.block.property.FaceImpl; -import net.momirealms.customfishing.mechanic.totem.block.TotemBlock; import net.momirealms.customfishing.mechanic.totem.block.property.HalfImpl; import net.momirealms.customfishing.mechanic.totem.block.property.TotemBlockProperty; import net.momirealms.customfishing.mechanic.totem.block.type.TypeCondition; diff --git a/plugin/src/main/java/net/momirealms/customfishing/mechanic/totem/TotemModel.java b/plugin/src/main/java/net/momirealms/customfishing/mechanic/totem/TotemModel.java index 3200fb13..83251994 100644 --- a/plugin/src/main/java/net/momirealms/customfishing/mechanic/totem/TotemModel.java +++ b/plugin/src/main/java/net/momirealms/customfishing/mechanic/totem/TotemModel.java @@ -23,7 +23,6 @@ import org.bukkit.Axis; import org.bukkit.Location; import java.io.Serializable; -import java.util.Arrays; import java.util.StringJoiner; public class TotemModel implements Serializable { diff --git a/plugin/src/main/resources/messages/chinese.yml b/plugin/src/main/resources/messages/chinese.yml new file mode 100644 index 00000000..4dbe8af0 --- /dev/null +++ b/plugin/src/main/resources/messages/chinese.yml @@ -0,0 +1,31 @@ +# Don't change this +config-version: '26' + +messages: + prefix: '[CustomFishing] ' + reload: '重载完成. 耗时 {time}ms.' + item-not-exist: '物品不存在.' + give-item: '给予玩家 {player} {amount}x {item}.' + get-item: '获得 {amount}x {item}.' + possible-loots: '可能的战利品: ' + split-char: ', ' + competition-not-exist: '比赛 {id} 不存在.' + no-competition-ongoing: "没有正在进行的比赛." + stop-competition: '停止了当前的比赛.' + end-competition: '结束了当前的比赛.' + no-score: '无比分' + no-player: '无选手' + no-rank: '无排名' + goal-catch-amount: '捕鱼总数' + goal-max-size: '最大尺寸' + goal-total-size: '捕鱼总尺寸' + goal-total-score: '捕鱼总分' + unsafe-modification: "你不能修改一个正在其他子服游玩的玩家钓鱼背包." + never-played: "此玩家从未玩过服务器." + data-not-loaded: "数据未能正常加载. 请尝试切换子服或重进. 如果问题依然存在请联系服务器管理员." + open-market-gui: "为玩家 {player} 打开了市场" + open-fishing-bag: "为玩家 {player} 打开了钓鱼背包" + format-day: '天' + format-hour: '小时' + format-minute: '分' + format-second: '秒' \ No newline at end of file diff --git a/plugin/src/main/resources/messages/english.yml b/plugin/src/main/resources/messages/english.yml index f9bfdd10..7cc1e0f8 100644 --- a/plugin/src/main/resources/messages/english.yml +++ b/plugin/src/main/resources/messages/english.yml @@ -5,7 +5,6 @@ messages: prefix: '[CustomFishing] ' reload: 'Reloaded. Took {time}ms.' item-not-exist: 'Item not found.' - escape: 'The fish slipped off the hook and escaped.' give-item: 'Successfully given player {player} {amount}x {item}.' get-item: 'Successfully got {amount}x {item}.' possible-loots: 'Possible loots here: ' diff --git a/plugin/src/main/resources/messages/hungarian.yml b/plugin/src/main/resources/messages/hungarian.yml index a0c61ac1..65a0752a 100644 --- a/plugin/src/main/resources/messages/hungarian.yml +++ b/plugin/src/main/resources/messages/hungarian.yml @@ -5,7 +5,6 @@ messages: prefix: '[CustomFishing] ' reload: 'Újratöltve. Időtartam: {time}ms.' item-not-exist: 'Tétel nem található.' - escape: 'A hal lecsúszott a horogról és elmenekült.' give-item: 'Sikeresen adva {player} játékosnak {amount}x {item}.' get-item: 'Sikeresen megszerezve {amount}x {item}.' possible-loots: 'Lehetséges kapások itt: ' diff --git a/plugin/src/main/resources/plugin.yml b/plugin/src/main/resources/plugin.yml index 13968d76..032f4bbc 100644 --- a/plugin/src/main/resources/plugin.yml +++ b/plugin/src/main/resources/plugin.yml @@ -27,6 +27,7 @@ softdepend: - BattlePass - ClueScrolls - BetonQuest + - Quests - AdvancedEnchantments - EcoEnchants