diff --git a/.gitignore b/.gitignore index 98e1d962f..c71f1d1d8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,10 +3,11 @@ .classpath bin # Package Files # -*.jar *.war *.ear **/target +**/build +/.gradle /lib /.classpath /.project @@ -17,5 +18,6 @@ bin *.DS_Store +gradle.properties dependency-reduced-pom.xml -deploy_rsa \ No newline at end of file +deploy_rsa diff --git a/gradle.properties b/gradle.properties deleted file mode 100644 index d9c76c6b7..000000000 --- a/gradle.properties +++ /dev/null @@ -1,7 +0,0 @@ -systemProp.http.proxyHost=127.0.0.1 -systemProp.http.proxyPort=7890 - -systemProp.https.proxyHost=127.0.0.1 -systemProp.https.proxyPort=7890 - -org.gradle.java.installations.paths=D:/Java/jdk-21.0.3 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..a4b76b953 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/progress_requirements_example.yml b/progress_requirements_example.yml new file mode 100644 index 000000000..fd12f2d86 --- /dev/null +++ b/progress_requirements_example.yml @@ -0,0 +1,73 @@ +# Progress Requirements 配置示例 +# 这个文件展示了如何在challenges.yml中配置Progress Requirements + +ranks: + novice: + displayName: "新手" + challenges: + mining_challenge: + name: "挖矿达人" + description: "成为一名挖矿专家" + type: "onPlayer" + requiredItems: + - "DIAMOND_PICKAXE:1" + requiredProgress: + - "blocks_mined:100" # 简单格式:需要挖掘100个方块 + - "stone_mined:50" # 需要挖掘50个石头 + reward: + items: + - "DIAMOND:5" + currency: 1000 + xp: 500 + repeatable: true + + farming_expert: + name: "农业专家" + description: "种植大量作物" + type: "onPlayer" + requiredProgress: + - "crops_harvested:200:+:25" # 扩展格式:基础200,每次重复增加25 + - "seeds_planted:100:*:1.2" # 基础100,每次重复乘以1.2 + reward: + items: + - "GOLDEN_HOE:1" + currency: 2000 + xp: 800 + repeatable: true + repeatLimit: 10 + + expert: + displayName: "专家" + requiredChallenges: + - "mining_challenge" + challenges: + monster_slayer: + name: "怪物猎人" + description: "击败大量怪物" + type: "onPlayer" + requiredProgress: + - "monsters_killed:500" + - "boss_kills:5" + - "damage_dealt:10000" + reward: + items: + - "DIAMOND_SWORD:1" + currency: 5000 + xp: 2000 + +# Progress Keys 说明: +# - blocks_mined: 挖掘的方块总数 +# - stone_mined: 挖掘的石头数量 +# - crops_harvested: 收获的作物数量 +# - seeds_planted: 种植的种子数量 +# - monsters_killed: 击杀的怪物数量 +# - boss_kills: 击杀的Boss数量 +# - damage_dealt: 造成的总伤害 +# - distance_traveled: 行走的总距离 +# - items_crafted: 制作的物品数量 +# - fish_caught: 钓到的鱼数量 + +# 操作符说明: +# + : 每次重复增加固定值 +# - : 每次重复减少固定值 +# * : 每次重复乘以倍数 diff --git a/uSkyBlock-Core/build.gradle.kts b/uSkyBlock-Core/build.gradle.kts index 9f0d1b15e..dd20f31f6 100644 --- a/uSkyBlock-Core/build.gradle.kts +++ b/uSkyBlock-Core/build.gradle.kts @@ -17,10 +17,10 @@ dependencies { testImplementation("junit:junit:4.13.2") testImplementation("org.junit.vintage:junit-vintage-engine:5.9.0") testImplementation("org.mockito:mockito-core:5.14.2") - testImplementation("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT") + testImplementation("org.spigotmc:spigot-api:1.21.1-R0.1-SNAPSHOT") testImplementation("com.sk89q.worldedit:worldedit-bukkit:7.2.19") compileOnly("net.milkbowl.vault:VaultUnlockedAPI:2.10") - compileOnly("io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT") + compileOnly("org.spigotmc:spigot-api:1.21.1-R0.1-SNAPSHOT") compileOnly("com.onarandombox.multiversecore:Multiverse-Core:4.3.1") compileOnly("com.onarandombox.multiverseinventories:Multiverse-Inventories:4.2.3") compileOnly("com.sk89q.worldedit:worldedit-bukkit:7.2.19") @@ -36,6 +36,8 @@ dependencies { compileOnly("org.apache.commons:commons-text:1.12.0") compileOnly("org.apache.httpcomponents:httpclient:4.5.14") compileOnly("org.apache.maven:maven-artifact:3.8.6") + + compileOnly(files("libs/SignShop-5.0.0-dev.jar")) } description = "uSkyBlock-Core" diff --git a/uSkyBlock-Core/libs/SignShop-5.0.0-dev.jar b/uSkyBlock-Core/libs/SignShop-5.0.0-dev.jar new file mode 100644 index 000000000..108ae7d22 Binary files /dev/null and b/uSkyBlock-Core/libs/SignShop-5.0.0-dev.jar differ diff --git "a/uSkyBlock-Core/libs/\345\274\200\345\217\221\350\200\205\350\257\267\350\207\252\350\241\214\346\212\212\350\277\231\344\270\252jar\345\234\250\351\241\271\347\233\256\347\273\223\346\236\204\351\207\214\345\241\236\350\277\233\344\276\235\350\265\226" "b/uSkyBlock-Core/libs/\345\274\200\345\217\221\350\200\205\350\257\267\350\207\252\350\241\214\346\212\212\350\277\231\344\270\252jar\345\234\250\351\241\271\347\233\256\347\273\223\346\236\204\351\207\214\345\241\236\350\277\233\344\276\235\350\265\226" new file mode 100644 index 000000000..e69de29bb diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/Settings.java b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/Settings.java index 0a94be517..0a63bc23c 100644 --- a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/Settings.java +++ b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/Settings.java @@ -138,7 +138,7 @@ public static boolean loadPluginConfig(FileConfiguration config) { extras_sendToSpawn = config.getBoolean("options.extras.sendToSpawn"); extras_respawnAtIsland = config.getBoolean("options.extras.respawnAtIsland"); island_useTopTen = config.getBoolean("options.island.useTopTen"); - general_worldName = config.getString("options.general.worldName", "skyworld"); + general_worldName = config.getString("options.general.worldName", "world_acidisland_nether"); island_removeCreaturesByTeleport = config.getBoolean("options.island.removeCreaturesByTeleport"); island_allowIslandLock = config.getBoolean("options.island.allowIslandLock"); island_topTenTimeout = Duration.ofMinutes(config.getLong("options.island.topTenTimeout", 7)); diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/bootstrap/Listeners.java b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/bootstrap/Listeners.java index dbd89dd89..2add8c2d0 100644 --- a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/bootstrap/Listeners.java +++ b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/bootstrap/Listeners.java @@ -9,17 +9,7 @@ import us.talabrek.ultimateskyblock.PluginConfig; import us.talabrek.ultimateskyblock.Settings; import us.talabrek.ultimateskyblock.chat.ChatEvents; -import us.talabrek.ultimateskyblock.event.ExploitEvents; -import us.talabrek.ultimateskyblock.event.GriefEvents; -import us.talabrek.ultimateskyblock.event.InternalEvents; -import us.talabrek.ultimateskyblock.event.ItemDropEvents; -import us.talabrek.ultimateskyblock.event.MenuEvents; -import us.talabrek.ultimateskyblock.event.NetherTerraFormEvents; -import us.talabrek.ultimateskyblock.event.PlayerEvents; -import us.talabrek.ultimateskyblock.event.SpawnEvents; -import us.talabrek.ultimateskyblock.event.ToolMenuEvents; -import us.talabrek.ultimateskyblock.event.WitherTagEvents; -import us.talabrek.ultimateskyblock.event.WorldGuardEvents; +import us.talabrek.ultimateskyblock.event.*; import us.talabrek.ultimateskyblock.gui.GuiListener; import us.talabrek.ultimateskyblock.signs.SignEvents; import us.talabrek.ultimateskyblock.command.InviteHandler; @@ -38,6 +28,7 @@ public class Listeners { private final WitherTagEvents witherTagEvents; private final GriefEvents griefEvents; private final ItemDropEvents itemDropEvents; + private final IslandBorderEvent islandBorderEvent; private final SpawnEvents spawnEvents; private final WorldGuardEvents worldGuardEvents; private final NetherTerraFormEvents netherTerraFormEvents; @@ -58,6 +49,7 @@ public Listeners( @NotNull WitherTagEvents witherTagEvents, @NotNull GriefEvents griefEvents, @NotNull ItemDropEvents itemDropEvents, + @NotNull IslandBorderEvent islandBorderEvent, @NotNull SpawnEvents spawnEvents, @NotNull WorldGuardEvents worldGuardEvents, @NotNull NetherTerraFormEvents netherTerraFormEvents, @@ -76,6 +68,7 @@ public Listeners( this.witherTagEvents = witherTagEvents; this.griefEvents = griefEvents; this.itemDropEvents = itemDropEvents; + this.islandBorderEvent = islandBorderEvent; this.spawnEvents = spawnEvents; this.worldGuardEvents = worldGuardEvents; this.netherTerraFormEvents = netherTerraFormEvents; @@ -99,6 +92,8 @@ public void registerListeners(Plugin plugin) { manager.registerEvents(inviteHandler, plugin); manager.registerEvents(playerDB, plugin); + manager.registerEvents(islandBorderEvent, plugin); + // TODO minoneer 06.02.2025: Move this logic. Either into the appropriate listener, or into submodules if we don't want all features active (e.g., the nether) if (config.getYamlConfig().getBoolean("options.protection.enabled", true)) { manager.registerEvents(griefEvents, plugin); diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/challenge/Challenge.java b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/challenge/Challenge.java index 490c63d6d..b66835c18 100644 --- a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/challenge/Challenge.java +++ b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/challenge/Challenge.java @@ -9,6 +9,7 @@ import org.jetbrains.annotations.NotNull; import us.talabrek.ultimateskyblock.player.PlayerInfo; import us.talabrek.ultimateskyblock.uSkyBlock; +import us.talabrek.ultimateskyblock.util.TranslationUtil; import java.time.Duration; import java.util.ArrayList; @@ -50,6 +51,7 @@ static Type from(String s) { private final List requiredEntities; private final List requiredChallenges; private final double requiredLevel; + private List requiredProgress; private final Rank rank; private final Duration resetDuration; private final ItemStack displayItem; @@ -64,7 +66,7 @@ static Type from(String s) { public Challenge(String name, String displayName, String description, Type type, List requiredItems, @NotNull List requiredBlocks, List requiredEntities, - List requiredChallenges, double requiredLevel, Rank rank, + List requiredChallenges, double requiredLevel, List requiredProgress, Rank rank, Duration resetDuration, ItemStack displayItem, String tool, ItemStack lockedItem, int offset, boolean takeItems, int radius, Reward reward, Reward repeatReward, int repeatLimit) { this.name = name; @@ -75,6 +77,7 @@ public Challenge(String name, String displayName, String description, Type type, this.requiredEntities = requiredEntities; this.requiredChallenges = requiredChallenges; this.requiredLevel = requiredLevel; + this.requiredProgress = requiredProgress; this.rank = rank; this.resetDuration = resetDuration; this.displayItem = displayItem; @@ -138,6 +141,10 @@ public List getRequiredChallenges() { return requiredChallenges; } + public List getRequiredProgress() { + return requiredProgress != null ? requiredProgress : Collections.emptyList(); + } + public Rank getRank() { return rank; } @@ -182,10 +189,24 @@ public ItemStack getDisplayItem(ChallengeCompletion completion, boolean withCurr } Map requiredItemsForChallenge = getRequiredItems(timesCompleted); if (!requiredItemsForChallenge.isEmpty() || !requiredBlocks.isEmpty() - || (requiredEntities != null && !requiredEntities.isEmpty())) { + || (requiredEntities != null && !requiredEntities.isEmpty()) || !getRequiredProgress().isEmpty()) { lores.add(tr("\u00a7eThis challenge requires:")); } List details = new ArrayList<>(); + + // Add progress requirements to details + List progressRequirements = getRequiredProgress(); + if (!progressRequirements.isEmpty()) { + for (ProgressRequirement progressReq : progressRequirements) { + if (wrappedDetails(details).size() >= MAX_DETAILS) { + details.add(tr("\u00a77and more...")); + break; + } + double requiredAmount = progressReq.amountForRepetitions(timesCompleted); + details.add(tr("\u00a7f{0}: {1}", progressReq.key(), requiredAmount)); + } + } + if (!requiredItemsForChallenge.isEmpty()) { for (Map.Entry requiredItem : requiredItemsForChallenge.entrySet()) { if (wrappedDetails(details).size() >= MAX_DETAILS) { @@ -195,8 +216,8 @@ public ItemStack getDisplayItem(ChallengeCompletion completion, boolean withCurr int requiredAmount = requiredItem.getValue(); ItemStack requiredType = requiredItem.getKey(); details.add(requiredAmount > 1 - ? tr("\u00a7f{0}x \u00a77{1}", requiredAmount, ItemStackUtil.getItemName(requiredType)) - : tr("\u00a77{0}", ItemStackUtil.getItemName(requiredType))); + ? tr("\u00a7f{0}x \u00a77{1}", requiredAmount, TranslationUtil.INSTANCE.getItemLocalizedName(requiredType)) + : tr("\u00a77{0}", TranslationUtil.INSTANCE.getItemLocalizedName(requiredType))); } } if (!requiredBlocks.isEmpty() && wrappedDetails(details).size() < MAX_DETAILS) { diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/challenge/ChallengeFactory.java b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/challenge/ChallengeFactory.java index 8d34a5bf3..ad7df53f8 100644 --- a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/challenge/ChallengeFactory.java +++ b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/challenge/ChallengeFactory.java @@ -75,11 +75,12 @@ public static Challenge createChallenge(Rank rank, ConfigurationSection section, repeatReward = reward; } List requiredChallenges = section.getStringList("requiredChallenges"); + List requiredProgress = createProgressRequirements(section.getStringList("requiredProgress")); int offset = section.getInt("offset", 0); int repeatLimit = section.getInt("repeatLimit", 0); return new Challenge(name, displayName, description, type, requiredItems, requiredBlocks, requiredEntities, requiredChallenges, section.getDouble("requiredLevel", 0d), - rank, resetDuration, displayItem, section.getString("tool", null), lockedItem, offset, takeItems, + requiredProgress, rank, resetDuration, displayItem, section.getString("tool", null), lockedItem, offset, takeItems, radius, reward, repeatReward, repeatLimit); } @@ -107,6 +108,59 @@ private static List createEntities(List requiredEntities) { return entities; } + private static List createProgressRequirements(List progressRequirements) { + List requirements = new ArrayList<>(); + for (String progressString : progressRequirements) { + requirements.add(createProgressRequirement(progressString)); + } + return requirements; + } + + private static ProgressRequirement createProgressRequirement(String progressString) { + // Format: "key:amount" or "key:amount:operator:increment" + // Examples: "kills:10", "blocks_mined:100:+:5", "trees_planted:50:*:1.5" + String[] parts = progressString.split(":"); + if (parts.length < 2) { + throw new IllegalArgumentException("Invalid progress requirement format: " + progressString + + ". Expected format: 'key:amount' or 'key:amount:operator:increment'"); + } + + String key = parts[0]; + double amount; + try { + amount = Double.parseDouble(parts[1]); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid amount in progress requirement: " + progressString, e); + } + + if (parts.length == 2) { + // Simple format: just key and amount + return ProgressRequirement.of(key, amount); + } else if (parts.length == 4) { + // Extended format with operator and increment + String operatorStr = parts[2]; + double increment; + try { + increment = Double.parseDouble(parts[3]); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid increment in progress requirement: " + progressString, e); + } + + ItemRequirement.Operator operator = switch (operatorStr) { + case "+" -> ItemRequirement.Operator.ADD; + case "-" -> ItemRequirement.Operator.SUBTRACT; + case "*" -> ItemRequirement.Operator.MULTIPLY; + default -> throw new IllegalArgumentException("Invalid operator in progress requirement: " + operatorStr + + ". Supported operators: +, -, *"); + }; + + return ProgressRequirement.of(key, amount, operator, increment); + } else { + throw new IllegalArgumentException("Invalid progress requirement format: " + progressString + + ". Expected format: 'key:amount' or 'key:amount:operator:increment'"); + } + } + private static Reward createReward(ConfigurationSection section) { if (section == null) { return null; diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/challenge/ChallengeLogic.java b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/challenge/ChallengeLogic.java index 1617393e0..efee2d6b6 100644 --- a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/challenge/ChallengeLogic.java +++ b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/challenge/ChallengeLogic.java @@ -30,6 +30,7 @@ import us.talabrek.ultimateskyblock.player.PerkLogic; import us.talabrek.ultimateskyblock.player.PlayerInfo; import us.talabrek.ultimateskyblock.uSkyBlock; +import us.talabrek.ultimateskyblock.util.TranslationUtil; import java.time.Duration; import java.util.ArrayList; @@ -325,11 +326,26 @@ private boolean tryCompleteOnPlayer(Player player, String challengeName) { if (challenge != null && completion != null) { StringBuilder sb = new StringBuilder(); boolean hasAll = true; + + // Check progress requirements + List requiredProgress = challenge.getRequiredProgress(); + if (!requiredProgress.isEmpty()) { + us.talabrek.ultimateskyblock.progress.Progress progress = us.talabrek.ultimateskyblock.progress.Progress.getProgress(player); + for (ProgressRequirement progressRequirement : requiredProgress) { + double requiredAmount = progressRequirement.amountForRepetitions(completion.getTimesCompletedInCooldown()); + double currentProgress = progress.getProgress(progressRequirement.key()); + if (currentProgress < requiredAmount) { + sb.append(tr(" \u00a74{0}: {1}/{2}", progressRequirement.key(), currentProgress, requiredAmount)); + hasAll = false; + } + } + } + Map requiredItems = challenge.getRequiredItems(completion.getTimesCompletedInCooldown()); for (Map.Entry required : requiredItems.entrySet()) { ItemStack requiredType = required.getKey(); int requiredAmount = required.getValue(); - String name = ItemStackUtil.getItemName(requiredType); + String name = TranslationUtil.INSTANCE.getItemLocalizedName(requiredType); if (!player.getInventory().containsAtLeast(requiredType, requiredAmount)) { sb.append(tr(" \u00a74{0} \u00a7b{1}", (requiredAmount - getCountOf(player.getInventory(), requiredType)), name)); hasAll = false; @@ -599,6 +615,23 @@ public Collection getChallenges(PlayerInfo playerInfo) { return completionLogic.getChallenges(playerInfo).values(); } + public void completeLocationChallengeIfNotDone(Location loc, String challengeName) { + IslandInfo islandInfo = plugin.getIslandInfo(loc); + if (islandInfo == null) { + return; + } + PlayerInfo playerInfo = plugin.getPlayerInfo(islandInfo.getLeaderUniqueId()); + if (completionLogic.checkChallenge(playerInfo, challengeName) == 0) { + completionLogic.completeChallenge(playerInfo, challengeName); + } + } + + public void completeChallengeIfNotDone(PlayerInfo playerInfo, String challengeName) { + if (completionLogic.checkChallenge(playerInfo, challengeName) == 0) { + completionLogic.completeChallenge(playerInfo, challengeName); + } + } + public void completeChallenge(PlayerInfo playerInfo, String challengeName) { completionLogic.completeChallenge(playerInfo, challengeName); } diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/challenge/ProgressRequirement.java b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/challenge/ProgressRequirement.java new file mode 100644 index 000000000..840a2029b --- /dev/null +++ b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/challenge/ProgressRequirement.java @@ -0,0 +1,41 @@ +package us.talabrek.ultimateskyblock.challenge; + +import dk.lockfuglsang.minecraft.util.ItemRequirement; + +/** + * Represents a progress requirement for challenges. + * This requirement checks if the player's island has achieved a certain progress value for a specific key. + */ +public record ProgressRequirement(String key, double amount, ItemRequirement.Operator operator, double increment) { + + /** + * Calculates the required amount for the given number of repetitions. + * @param repetitions The number of challenge repetitions + * @return The required progress amount + */ + public double amountForRepetitions(int repetitions) { + return operator().apply(amount(), increment(), repetitions); + } + + /** + * Creates a simple progress requirement with no operator (fixed amount). + * @param key The progress key to check + * @param amount The required progress amount + * @return A new ProgressRequirement + */ + public static ProgressRequirement of(String key, double amount) { + return new ProgressRequirement(key, amount, ItemRequirement.Operator.NONE, 0.0); + } + + /** + * Creates a progress requirement with an operator and increment for repeatable challenges. + * @param key The progress key to check + * @param amount The base required progress amount + * @param operator The operator to apply for repetitions + * @param increment The increment value for each repetition + * @return A new ProgressRequirement + */ + public static ProgressRequirement of(String key, double amount, ItemRequirement.Operator operator, double increment) { + return new ProgressRequirement(key, amount, operator, increment); + } +} diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/command/island/TrustCommand.java b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/command/island/TrustCommand.java index fd36e3a18..9739be7b7 100644 --- a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/command/island/TrustCommand.java +++ b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/command/island/TrustCommand.java @@ -27,7 +27,7 @@ public TrustCommand(@NotNull uSkyBlock plugin) { @Override protected boolean doExecute(final String alias, final Player player, final PlayerInfo pi, final IslandInfo island, Map data, String... args) { PlayerInfo playerInfo = plugin.getPlayerInfo(player); - boolean isFirstCompletion = playerInfo.checkChallenge("page1finished") == 0; + boolean isFirstCompletion = playerInfo.checkChallenge("beginner") == 0; if(isFirstCompletion){ player.sendMessage(tr("\u00a7c你现在没有权限信任他,请先完成第一页任务")); return true; @@ -53,7 +53,7 @@ protected boolean doExecute(final String alias, final Player player, final Playe } if (alias.equals("trust")) { PlayerInfo pinfo = plugin.getPlayerInfo(offlinePlayer.getUniqueId()); - boolean pii = pinfo.checkChallenge("page1finished") == 0; + boolean pii = pinfo.checkChallenge("beginner") == 0; if(pii){ player.sendMessage(tr("\u00a7c你现在没有权限信任他,请先完成第一页任务")); return true; diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/event/GriefEvents.java b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/event/GriefEvents.java index 84b93d355..1bef18f6d 100644 --- a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/event/GriefEvents.java +++ b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/event/GriefEvents.java @@ -58,7 +58,7 @@ public GriefEvents(@NotNull uSkyBlock plugin) { @EventHandler public void onCreeperExplode(ExplosionPrimeEvent event) { - if (!creeperEnabled || !plugin.getWorldManager().isSkyWorld(event.getEntity().getWorld())) { + if (!creeperEnabled || !plugin.getWorldManager().isSkyAssociatedWorld(event.getEntity().getWorld())) { return; } if (event.getEntity() instanceof Creeper diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/event/IslandBorderEvent.java b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/event/IslandBorderEvent.java new file mode 100644 index 000000000..599119fe1 --- /dev/null +++ b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/event/IslandBorderEvent.java @@ -0,0 +1,463 @@ +package us.talabrek.ultimateskyblock.event; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.Dispenser; +import org.bukkit.block.data.Directional; +import org.bukkit.entity.*; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.*; +import org.bukkit.event.block.BlockIgniteEvent.IgniteCause; +import org.bukkit.event.entity.EntityChangeBlockEvent; +import org.bukkit.event.entity.EntityPickupItemEvent; +import org.bukkit.event.entity.EntitySpawnEvent; +import org.bukkit.event.entity.EntityTargetEvent; +import org.bukkit.event.inventory.InventoryMoveItemEvent; +import org.bukkit.event.inventory.InventoryPickupItemEvent; +import org.bukkit.event.player.PlayerEggThrowEvent; +import org.bukkit.event.player.PlayerPickupArrowEvent; +import org.bukkit.event.vehicle.VehicleMoveEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.wargamer2010.signshop.player.SignShopPlayer; +import us.talabrek.ultimateskyblock.island.IslandInfo; +import us.talabrek.ultimateskyblock.player.PlayerInfo; +import us.talabrek.ultimateskyblock.uSkyBlock; + +import org.wargamer2010.signshop.events.SSCreatedEvent; +import org.wargamer2010.signshop.events.SSPreTransactionEvent; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +import static dk.lockfuglsang.minecraft.po.I18nUtil.tr; +import static us.talabrek.ultimateskyblock.event.ItemDropEvents.*; + +@Singleton +public class IslandBorderEvent implements Listener { + private static uSkyBlock plugin = null; + private Cache origin; + + public static boolean denyCrossBorder(Entity e) { + return e instanceof Item + || e instanceof FallingBlock + || e instanceof Mob + || e instanceof TNTPrimed + || (e instanceof Firework fw && fw.getShooter() == null); + } + @Inject + public IslandBorderEvent(@NotNull uSkyBlock plugin) { + this.plugin = plugin; + this.origin = CacheBuilder.newBuilder() + .expireAfterWrite(10, TimeUnit.MINUTES) + .build(); + + // prevent mobs leave the island + Bukkit.getScheduler().runTaskTimer(plugin, () -> { + List entities = plugin.getWorldManager().getWorld().getEntities(); + entities.addAll(plugin.getWorldManager().getNetherWorld().getEntities()); + for (Entity e : entities) { + if (denyCrossBorder(e)) { + Location last_loc = origin.getIfPresent(e.getUniqueId()); + Location now_loc = e.getLocation(); + if (last_loc == null || isBothTrusted(plugin.getIslandInfo(last_loc), plugin.getIslandInfo(now_loc))) { + origin.put(e.getUniqueId(), e.getLocation()); + continue; + } + e.teleport(last_loc); + origin.put(e.getUniqueId(), last_loc); + Vector vel = e.getVelocity(); + vel.setX(-vel.getX()).setZ(-vel.getZ()); + e.setVelocity(vel); + } + } + }, 10, 1); + } + + @EventHandler + public void onEntitySpawn(EntitySpawnEvent event) { + Entity entity = event.getEntity(); + Location loc = entity.getLocation(); + origin.put(entity.getUniqueId(), loc); + if (entity.getType() == EntityType.ITEM) { + IslandInfo ii = plugin.getIslandInfo(loc); + if (ii == null) { + // plugin.getLogger().info("Item spawned (cancelled) at wild with UUID " + entity.getUniqueId()); + event.setCancelled(true); + } else { + Item item = (Item) entity; + addDropInfo(ii, item.getItemStack()); + // plugin.getLogger().info("Item spawned at " + loc + " with UUID " + entity.getUniqueId()); + } + + } + } + + + @EventHandler (priority = EventPriority.LOWEST, ignoreCancelled = true) + public void on(EntityTargetEvent event) { + if (event.getTarget() instanceof Player player) { + IslandInfo iip = plugin.getIslandInfo(player); + IslandInfo iip_pos = plugin.getIslandInfo(player.getLocation()); + IslandInfo iie = plugin.getIslandInfo(event.getEntity().getLocation()); + if (!isBothTrusted(iie, iip) || !isBothTrusted(iie, iip_pos)) { + event.setCancelled(true); + event.setTarget(null); + } + } + } + + @EventHandler(ignoreCancelled = true) + public void onPlayerThrowEgg(PlayerEggThrowEvent event) { + if (!plugin.getWorldManager().isSkyAssociatedWorld(event.getEgg().getWorld())) { + return; + } + IslandInfo target = plugin.getIslandInfo(event.getEgg().getLocation()); + IslandInfo source = plugin.getIslandInfo(origin.getIfPresent(event.getEgg().getUniqueId())); + if (!isBothTrusted(target, source)) { + event.setHatching(false); + } + } + + // NOTE: droppers put item into a chest do not fire this event. Use InventoryMoveItemEvent + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onBlockDispense(BlockDispenseEvent event){ //发射器发射东西 + Block block = event.getBlock(); + if (!plugin.getWorldManager().isSkyAssociatedWorld(block.getWorld())) { + return; + } + if (!(block.getState() instanceof Dispenser dispenser)) {//发射器 + plugin.getLogger().severe("BlockDispenseEvent: block is not Dispenser! " + event.getBlock()); + return; + } + IslandInfo ii = plugin.getIslandInfo(block.getLocation()); + IslandInfo ii2 = plugin.getIslandInfo(block.getRelative(((Directional)dispenser.getBlockData()).getFacing()).getLocation()); + if (!isBothTrusted(ii, ii2)) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onBlockDispenseArmor(BlockDispenseArmorEvent event){ //发射器穿戴盔甲 + Block block = event.getBlock(); + if (!plugin.getWorldManager().isSkyAssociatedWorld(block.getWorld())) { + return; + } + IslandInfo islandInfo = plugin.getIslandInfo(block.getLocation()); + if (event.getTargetEntity() instanceof Player p) { //对面诗人 + PlayerInfo playerInfo = plugin.getPlayerInfo(p); + IslandInfo islandInfo1 = playerInfo.getIslandInfo(); + if (!isBothTrusted(islandInfo, islandInfo1)) { + event.setCancelled(true); + } + } else { //对面部诗人 + Location ori = origin.getIfPresent(event.getTargetEntity().getUniqueId()); + IslandInfo islandInfo1 = plugin.getIslandInfo(ori); + if (!isBothTrusted(islandInfo, islandInfo1)) { + event.setCancelled(true); + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onInventoryMoveItem(InventoryMoveItemEvent event) { //漏斗 + if (event.getSource().getLocation() == null || event.getDestination().getLocation() == null) { + return; + } + if (!plugin.getWorldManager().isSkyAssociatedWorld(event.getSource().getLocation().getWorld())) { + return; + } + IslandInfo ii = plugin.getIslandInfo(event.getSource().getLocation()); + IslandInfo ii2 = plugin.getIslandInfo(event.getDestination().getLocation()); + event.setCancelled(!isBothTrusted(ii, ii2)); + if (!event.isCancelled()) { + if (event.getDestination().getHolder() instanceof Entity entity) { + ii2 = plugin.getIslandInfo(origin.getIfPresent(entity.getUniqueId())); + event.setCancelled(!isBothTrusted(ii, ii2)); + } else if (event.getSource().getHolder() instanceof Entity entity) { + ii = plugin.getIslandInfo(origin.getIfPresent(entity.getUniqueId())); + event.setCancelled(!isBothTrusted(ii, ii2)); + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onInventoryPickupItem(InventoryPickupItemEvent event) { //漏斗捡起 + Inventory inv = event.getInventory(); + if (!plugin.getWorldManager().isSkyAssociatedWorld(inv.getLocation().getWorld())) { + return; + } + if (inv.getHolder() instanceof Entity entity) { + IslandInfo ii = plugin.getIslandInfo(inv.getLocation()); + IslandInfo ii2 = plugin.getIslandInfo(origin.getIfPresent(entity.getUniqueId())); + if (!isBothTrusted(ii, ii2)) { + event.setCancelled(true); + return; + } + } + IslandInfo ii = plugin.getIslandInfo(inv.getLocation()); + IslandInfo ii2 = plugin.getIslandInfo(origin.getIfPresent(event.getItem().getUniqueId())); + if (isBothTrusted(ii, ii2)) { + clearDropInfo(event.getItem()); + } else { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onVehicleMove(VehicleMoveEvent event) {//矿车 + if (!plugin.getWorldManager().isSkyAssociatedWorld(event.getVehicle().getWorld())) { + return; + } + if (!(event.getVehicle() instanceof Minecart)) { + return; + } + Vehicle v = event.getVehicle(); + if (origin.getIfPresent(v.getUniqueId()) == null) { + origin.put(v.getUniqueId(), v.getLocation()); + return; + } + Location from = event.getFrom(); + Location to = event.getTo(); + IslandInfo ii = plugin.getIslandInfo(from); + IslandInfo ii2 = plugin.getIslandInfo(to); + if (!isBothTrusted(ii, ii2)) { + v.teleport(from); + Vector vel = v.getVelocity(); + vel.setX(-vel.getX()).setZ(-vel.getZ()); + v.setVelocity(vel); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onChestPlace(BlockPlaceEvent event) { + if (!plugin.getWorldManager().isSkyAssociatedWorld(event.getBlock().getWorld())) { + return; + } + Block block = event.getBlock(); + BlockFace[] faces = {BlockFace.NORTH, BlockFace.SOUTH, BlockFace.EAST, BlockFace.WEST}; + + //防止跨岛连箱子 + if (block.getType() == Material.TRAPPED_CHEST || block.getType() == Material.CHEST) { + for (BlockFace face : faces) { + Block relative = block.getRelative(face); + IslandInfo ii = plugin.getIslandInfo(relative.getLocation()); + IslandInfo ii2 = plugin.getIslandInfo(block.getLocation()); + if (relative.getType() == block.getType() + && !Objects.equals( + ii == null ? null : ii.getName(), + ii2 == null ? null : ii2.getName()) + ) { + if (!isBothTrusted(ii, ii2)) { + event.setCancelled(true); + event.getPlayer().sendMessage("cannot build"); + return; + } + } + } + } + + // 防止跨岛传输物品 + if (block.getType() == Material.DISPENSER || block.getType() == Material.DROPPER || block.getType() == Material.HOPPER) { + BlockFace face = ((Directional) block.getBlockData()).getFacing(); + Block relative = block.getRelative(face); + IslandInfo ii = plugin.getIslandInfo(relative.getLocation()); + IslandInfo ii2 = plugin.getIslandInfo(block.getLocation()); + if (!isBothTrusted(ii, ii2)) { + event.setCancelled(true); + event.getPlayer().sendMessage("cannot build"); + } + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onBlockIgnite(BlockIgniteEvent event) { //防止火烧联营 + IgniteCause cause = event.getCause(); + if ((cause == IgniteCause.FLINT_AND_STEEL || cause == IgniteCause.FIREBALL) && event.getPlayer() != null) { + // handle in InteractEvent + return; + } + if (cause == IgniteCause.LIGHTNING || cause == IgniteCause.LAVA) { + // 付钱防火? + return; + } + Entity e = event.getIgnitingEntity(); + if (e == null) { + return; + } + Block b = event.getIgnitingBlock(); + IslandInfo ii = plugin.getIslandInfo(origin.getIfPresent(e.getUniqueId())); + IslandInfo ii2; + if (b == null) { + ii2 = plugin.getIslandInfo(e.getLocation()); + } else { + ii2 = plugin.getIslandInfo(b.getLocation()); + } + event.setCancelled(!isBothTrusted(ii, ii2)); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onBlockSpread(BlockSpreadEvent event) { //防止方块传播 (蘑菇 火势) + IslandInfo ii = plugin.getIslandInfo(event.getBlock().getLocation()); + IslandInfo ii2 = plugin.getIslandInfo(event.getSource().getLocation()); + event.setCancelled(!isBothTrusted(ii, ii2)); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onBlockFertilize(BlockFertilizeEvent event) { //防骨粉种草 + IslandInfo ii = plugin.getIslandInfo(event.getBlock().getLocation()); + event.getBlocks().removeIf(block -> { + IslandInfo ii2 = plugin.getIslandInfo(block.getLocation()); + return !isBothTrusted(ii, ii2); + }); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onBlockBurn(BlockBurnEvent event) { //防火烧联营烧坏 + if (event.getIgnitingBlock() == null) { + return; + } + IslandInfo ii = plugin.getIslandInfo(event.getBlock().getLocation()); + IslandInfo ii2 = plugin.getIslandInfo(event.getIgnitingBlock().getLocation()); + event.setCancelled(!isBothTrusted(ii, ii2)); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onWitherChangeBlock(EntityChangeBlockEvent event) { //凋零破坏 + if (event.getEntity() instanceof Wither && event.getTo() == Material.AIR) { + IslandInfo a = plugin.getIslandInfo(origin.getIfPresent(event.getEntity().getUniqueId())); + IslandInfo b = plugin.getIslandInfo(event.getBlock().getLocation()); + if (!a.equals(b)) { + event.setCancelled(true); + return; + } + } + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onPickupEvent(EntityPickupItemEvent event) { + if (!plugin.getWorldManager().isSkyAssociatedWorld((event.getEntity().getWorld()))) { + return; + } + if (event.getEntity() instanceof Player player) { //对玩家 + if (isForIsland(plugin.getIslandInfo(player), event) || + player.hasPermission("usb.mod.bypassprotection") || + IslandBorderEvent.isBothTrusted(plugin.getIslandInfo(player), plugin.getIslandInfo(origin.getIfPresent(event.getItem().getUniqueId()))) + ) { + clearDropInfo(event.getItem()); + } else { + event.setCancelled(true); + plugin.notifyPlayer(player, tr("You cannot pick up other players' loot when you are a visitor!")); + } + } else { //对实体 + if (isForIsland(plugin.getIslandInfo(origin.getIfPresent(event.getEntity().getUniqueId())), event) || + IslandBorderEvent.isBothTrusted( + plugin.getIslandInfo(origin.getIfPresent(event.getEntity().getUniqueId())), + plugin.getIslandInfo(origin.getIfPresent(event.getItem().getUniqueId())) + ) + ) { + clearDropInfo(event.getItem()); + } else { + event.setCancelled(true); + } + } + } + + @SuppressWarnings("deprecation") + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onPlayerPickupArrow(PlayerPickupArrowEvent event) { + if (!plugin.getWorldManager().isSkyAssociatedWorld(event.getPlayer().getLocation().getWorld())) { + return; + } + if (isBothTrusted(plugin.getIslandInfo(event.getPlayer()), plugin.getIslandInfo(origin.getIfPresent(event.getArrow().getUniqueId())))) { + event.setCancelled(false); + } + } + + public static boolean isBothTrusted(IslandInfo islandInfo, IslandInfo islandInfo1) { + if (islandInfo == null && islandInfo1 == null) return true; //都没有岛 + if (islandInfo == null || islandInfo1 == null) return false; //只有一个岛 + return ( + Objects.equals(islandInfo.getName(), islandInfo1.getName()) || //同一个岛 + (islandInfo.getTrusteeUUIDs().contains(islandInfo1.getLeaderUniqueId())) && (islandInfo1.getTrusteeUUIDs().contains(islandInfo.getLeaderUniqueId())) //完全互信 + ); + } + + public Location getNearestlocationOn(IslandInfo island, Location target) { + if (plugin.getIslandInfo(target).equals(island)) { + return target; + } + Location location = target.clone(); //所在位置 + IslandInfo island2 = plugin.getIslandInfo(target); //对面岛 + Location islandlocation = island.getIslandLocation(); //这边岛位置 + Location island2location = island2.getIslandLocation(); //对面岛位置 + + // 只用 X 和 Z,忽略 Y + double ax = islandlocation.getX(), az = islandlocation.getZ(); + double bx = island2location.getX(), bz = island2location.getZ(); + + // 中点 M + double mx = (ax + bx) / 2.0; + double mz = (az + bz) / 2.0; + + if (islandlocation.getX() == island2location.getX()) { + return new Location(location.getWorld(), location.getX(), location.getBlockY(), mz); + } else if (islandlocation.getZ() == island2location.getZ()) { + return new Location(location.getWorld(), mx, location.getBlockY(), location.getZ()); + } else return location;//??? + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onSSPreTransactionEvent(SSPreTransactionEvent event) { + if(event.isCancelled() || !event.canBeCancelled()) + return; + if (!plugin.getWorldManager().isSkyAssociatedWorld(event.getPlayer().getWorld())) { + return; + } + SignShopPlayer ssPlayer = event.getPlayer(); + PlayerInfo playerInfo = plugin.getPlayerInfo(ssPlayer.getPlayer()); + IslandInfo islandInfo = plugin.getIslandInfo(playerInfo); + Location location = event.getSign().getLocation(); + IslandInfo shopIslandInfo = plugin.getIslandInfo(location); + + if (islandInfo == null) return; + if (shopIslandInfo == null) return; + + if(!Objects.equals(shopIslandInfo.getName(), islandInfo.getName())) { + ssPlayer.sendMessage(tr("You don't have permission to access this shop")); + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGH) + public void onSSBuildEvent(SSCreatedEvent event) { + if(event.isCancelled() || !event.canBeCancelled()) + return; + if (!plugin.getWorldManager().isSkyAssociatedWorld(event.getPlayer().getWorld())) { + return; + } + SignShopPlayer ssPlayer = event.getPlayer(); + PlayerInfo playerInfo = plugin.getPlayerInfo(ssPlayer.getPlayer()); + IslandInfo islandInfo = plugin.getIslandInfo(playerInfo); + Location location = event.getSign().getLocation(); + IslandInfo shopIslandInfo = plugin.getIslandInfo(location); + + if (islandInfo == null) return; + if (shopIslandInfo == null) return; + + if(!Objects.equals(shopIslandInfo.getName(), islandInfo.getName())) { + ssPlayer.sendMessage(tr("You don't have permission to build this shop")); + event.setCancelled(true); + } + } +} diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/event/ItemDropEvents.java b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/event/ItemDropEvents.java index 47d4d1845..24a594151 100644 --- a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/event/ItemDropEvents.java +++ b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/event/ItemDropEvents.java @@ -9,11 +9,11 @@ import org.bukkit.event.Listener; import org.bukkit.event.entity.EntityPickupItemEvent; import org.bukkit.event.entity.PlayerDeathEvent; -import org.bukkit.event.inventory.InventoryPickupItemEvent; import org.bukkit.event.player.PlayerDropItemEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; +import us.talabrek.ultimateskyblock.island.IslandInfo; import us.talabrek.ultimateskyblock.uSkyBlock; import java.util.ArrayList; @@ -26,7 +26,7 @@ */ @Singleton public class ItemDropEvents implements Listener { - private final uSkyBlock plugin; + private static uSkyBlock plugin; private final boolean visitorsCanDrop; @Inject @@ -39,7 +39,7 @@ public ItemDropEvents(@NotNull uSkyBlock plugin) { @SuppressWarnings("unused") public void onDropEvent(PlayerDropItemEvent event) { Player player = event.getPlayer(); - if (!plugin.getWorldManager().isSkyWorld(player.getWorld())) { + if (!plugin.getWorldManager().isSkyAssociatedWorld(player.getWorld())) { return; } if (!visitorsCanDrop && !plugin.playerIsOnIsland(player) && !plugin.playerIsInSpawn(player)) { @@ -72,14 +72,30 @@ public void onDeathEvent(PlayerDeathEvent event) { } } - private void addDropInfo(Player player, ItemStack stack) { + static void addDropInfo(IslandInfo islandInfo, ItemStack stack) { + ItemMeta meta = stack.getItemMeta(); + if (meta != null) { + List lore = meta.getLore(); + if (lore == null) { + lore = new ArrayList<>(); + } + String ownerTag = tr("Owner: {0}", islandInfo.getName()); + if (!lore.contains(ownerTag)) { + lore.add(ownerTag); + } + meta.setLore(lore); + stack.setItemMeta(meta); + } + } + + static void addDropInfo(Player player, ItemStack stack) { ItemMeta meta = stack.getItemMeta(); if (meta != null) { List lore = meta.getLore(); if (lore == null) { lore = new ArrayList<>(); } - String ownerTag = tr("Owner: {0}", player.getName()); + String ownerTag = tr("Owner: {0}", plugin.getIslandInfo(player).getName()); if (!lore.contains(ownerTag)) { lore.add(ownerTag); } @@ -88,7 +104,7 @@ private void addDropInfo(Player player, ItemStack stack) { } } - private void clearDropInfo(Item item) { + static void clearDropInfo(Item item) { ItemStack stack = item.getItemStack(); ItemMeta meta = stack.getItemMeta(); if (meta != null) { @@ -102,44 +118,15 @@ private void clearDropInfo(Item item) { } } - @EventHandler(priority = EventPriority.HIGHEST) - @SuppressWarnings("unused") - public void onPickupInventoryEvent(InventoryPickupItemEvent event) { - if (!plugin.getWorldManager().isSkyWorld(event.getItem().getWorld())) { - return; - } - // I.e. hoppers... - clearDropInfo(event.getItem()); - } - - @EventHandler(priority = EventPriority.HIGHEST) - @SuppressWarnings("unused") - public void onPickupEvent(EntityPickupItemEvent event) { - if (!(event.getEntity() instanceof Player)) { - return; - } - Player player = (Player) event.getEntity(); - if (event.isCancelled() || !plugin.getWorldManager().isSkyWorld(player.getWorld()) || !plugin.getWorldManager().isSkyNether(player.getWorld() )) { - clearDropInfo(event.getItem()); - return; - } - if (wasDroppedBy(player, event) || player.hasPermission("usb.mod.bypassprotection") || plugin.playerIsOnIsland(player) || plugin.playerIsInSpawn(player)) { - clearDropInfo(event.getItem()); - return; // Allowed - } - // You are on another's island, and the stuff dropped weren't yours. - event.setCancelled(true); - plugin.notifyPlayer(player, tr("You cannot pick up other players' loot when you are a visitor!")); - } - - private boolean wasDroppedBy(Player player, EntityPickupItemEvent event) { + public static boolean isForIsland(IslandInfo islandInfo, EntityPickupItemEvent event) { ItemStack itemStack = event.getItem().getItemStack(); ItemMeta meta = itemStack.getItemMeta(); if (meta != null) { List lore = meta.getLore(); if (lore != null && !lore.isEmpty()) { String lastLine = lore.get(lore.size() - 1); - return lastLine.equalsIgnoreCase(tr("Owner: {0}", player.getName())); + String islandName = lastLine.replaceFirst(tr("Owner: {0}", ""), ""); + return islandName.equalsIgnoreCase(islandInfo.getName()); } } return false; diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/event/PlayerEvents.java b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/event/PlayerEvents.java index 78576d4e9..ae2cb7254 100644 --- a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/event/PlayerEvents.java +++ b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/event/PlayerEvents.java @@ -1,35 +1,30 @@ package us.talabrek.ultimateskyblock.event; -import com.destroystokyo.paper.event.player.PlayerTeleportEndGatewayEvent; import com.google.common.util.concurrent.RateLimiter; import com.google.inject.Inject; import com.google.inject.Singleton; -import dk.lockfuglsang.minecraft.util.ItemStackUtil; -import org.bukkit.ChatColor; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.World; -import org.bukkit.block.Block; +import net.kyori.adventure.text.BlockNBTComponent; +import org.bukkit.*; +import org.bukkit.block.*; import org.bukkit.block.data.BlockData; import org.bukkit.block.data.Levelled; +import org.bukkit.block.data.type.Leaves; +import org.bukkit.block.data.type.Vault; +import org.bukkit.command.Command; import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.entity.EnderPearl; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.Player; -import org.bukkit.entity.Projectile; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.*; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; -import org.bukkit.event.block.Action; -import org.bukkit.event.block.BlockBreakEvent; -import org.bukkit.event.block.BlockExplodeEvent; -import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.block.*; import org.bukkit.event.entity.*; import org.bukkit.event.player.*; -import org.bukkit.event.world.PortalCreateEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; +import org.bukkit.inventory.meta.BlockDataMeta; import org.bukkit.projectiles.ProjectileSource; +import org.bukkit.scheduler.BukkitRunnable; import org.jetbrains.annotations.NotNull; import us.talabrek.ultimateskyblock.Settings; import us.talabrek.ultimateskyblock.api.async.Callback; @@ -41,21 +36,160 @@ import us.talabrek.ultimateskyblock.player.PlayerInfo; import us.talabrek.ultimateskyblock.uSkyBlock; import us.talabrek.ultimateskyblock.util.LocationUtil; -import us.talabrek.ultimateskyblock.util.LogUtil; +import us.talabrek.ultimateskyblock.util.TranslationUtil; +import us.talabrek.ultimateskyblock.world.AcidBiomeProvider; import us.talabrek.ultimateskyblock.world.WorldManager; import java.time.Duration; import java.time.Instant; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Random; -import java.util.Set; -import java.util.UUID; -import java.util.logging.Level; +import java.util.*; import static dk.lockfuglsang.minecraft.po.I18nUtil.tr; +import static org.bukkit.Bukkit.getServer; + +class SuspiciousConversion implements Runnable { + private final uSkyBlock plugin; + private final Location location; + private static final Random random = new Random(); + public static final Set toConvert = new HashSet(); + + public static final List> RUINS_LOOT = List.of( + // 心碎/危机/麦捆/爱心/挚友/狼嚎/烈焰纹样陶片 + // 向导/牧民/雇主/塑造盔甲纹饰 + // Relic唱片 + List.of(Material.HEARTBREAK_POTTERY_SHERD, Material.DANGER_POTTERY_SHERD, Material.SHEAF_POTTERY_SHERD, + Material.HEART_POTTERY_SHERD, Material.FRIEND_POTTERY_SHERD, Material.HOWL_POTTERY_SHERD, + Material.BURN_POTTERY_SHERD, Material.WAYFINDER_ARMOR_TRIM_SMITHING_TEMPLATE, + Material.RAISER_ARMOR_TRIM_SMITHING_TEMPLATE, Material.HOST_ARMOR_TRIM_SMITHING_TEMPLATE, + Material.SHAPER_ARMOR_TRIM_SMITHING_TEMPLATE, Material.MUSIC_DISC_RELIC), + // 绿宝石, 红砖, 煤炭 + List.of(Material.EMERALD, Material.BRICK, Material.COAL), + // 枯萎的灌木, 小麦, 甜菜种子, 小麦种子 + List.of(Material.DEAD_BUSH, Material.WHEAT, Material.BEETROOT_SEEDS, Material.WHEAT_SEEDS) + ); + + public static final List> MOSS_LOOT = List.of( + // 荒野盔甲纹饰/钻石马铠/钻石 + List.of(Material.WILD_ARMOR_TRIM_SMITHING_TEMPLATE, Material.DIAMOND_HORSE_ARMOR, Material.DIAMOND), + // 绿宝石, 金锭, 铁锭 + List.of(Material.EMERALD, Material.GOLD_INGOT, Material.IRON_INGOT), + // 皮革, 骨头, 腐肉, 竹子 + List.of(Material.LEATHER, Material.BONE, Material.ROTTEN_FLESH, Material.BAMBOO) + ); + + public static final List> SAND_LOOT = List.of( + //弓箭/采矿/珍宝/头颅/举臂/佳酿纹样陶片 + //钻石 + //沙丘盔甲纹饰 + List.of(Material.ARCHER_POTTERY_SHERD, Material.MINER_POTTERY_SHERD, Material.PRIZE_POTTERY_SHERD, + Material.SKULL_POTTERY_SHERD, Material.ARMS_UP_POTTERY_SHERD, Material.BREWER_POTTERY_SHERD, + Material.DIAMOND, Material.DUNE_ARMOR_TRIM_SMITHING_TEMPLATE), + //绿宝石,金锭,金苹果 + List.of(Material.EMERALD, Material.GOLD_INGOT, Material.GOLDEN_APPLE), + //沙子,骨头,腐肉,蜘蛛眼 + List.of(Material.SAND, Material.BONE, Material.ROTTEN_FLESH, Material.SPIDER_EYE) + ); + + public static final List> OCEAN_SAND_LOOT = List.of( + //海草+沙子掉落表 + //垂钓/树荫/嗅探纹样陶片 + //嗅探兽蛋 + //海岸盔甲纹饰 + //钻石 + List.of(Material.ANGLER_POTTERY_SHERD, Material.SHELTER_POTTERY_SHERD, Material.SNORT_POTTERY_SHERD, + Material.SNIFFER_EGG, Material.COAST_ARMOR_TRIM_SMITHING_TEMPLATE, Material.DIAMOND), + //绿宝石,青金石,金苹果 + List.of(Material.EMERALD, Material.GOLDEN_APPLE, Material.LAPIS_LAZULI), + //南瓜,胡萝卜,马铃薯,羽毛 + List.of(Material.PUMPKIN, Material.CARROT, Material.POTATO, Material.FEATHER) + ); + + public static final List> OCEAN_GRAVEL_LOOT = List.of( + //海草+沙砾掉落表 + //利刃/探险/悲恸/富饶纹样陶片 + //海岸盔甲纹饰 + //钻石 + List.of(Material.BLADE_POTTERY_SHERD,Material.EXPLORER_POTTERY_SHERD,Material.MOURNER_POTTERY_SHERD, + Material.PLENTY_POTTERY_SHERD, Material.COAST_ARMOR_TRIM_SMITHING_TEMPLATE, Material.DIAMOND), + //绿宝石,青金石,金苹果 + List.of(Material.EMERALD, Material.GOLDEN_APPLE, Material.LAPIS_LAZULI), + //南瓜,胡萝卜,马铃薯,羽毛 + List.of(Material.PUMPKIN, Material.CARROT, Material.POTATO, Material.FEATHER) + ); + + public static final List> TRIAL_LOOT = List.of( + //涂蜡的铜块掉落表 + //涡流/旋风/刮削纹样陶片 + //试炼钥匙 + //Creator(八音盒)唱片 + //绿宝石块 + List.of(Material.FLOW_POTTERY_SHERD, Material.GUSTER_POTTERY_SHERD, Material.SCRAPE_POTTERY_SHERD, + Material.EMERALD_BLOCK, Material.TRIAL_KEY, Material.MUSIC_DISC_CREATOR_MUSIC_BOX), + //绿宝石,金胡萝卜,金苹果 + List.of(Material.EMERALD, Material.GOLDEN_APPLE, Material.GOLDEN_CARROT), + //铁锭,箭,面包,木棍 + List.of(Material.IRON_INGOT, Material.ARROW, Material.BREAD, Material.STICK) + ); + + static public boolean isArchaeologyFeature(Material material) { + return switch (material) { + case MUD_BRICKS, MOSSY_COBBLESTONE, SANDSTONE, SEAGRASS, WAXED_COPPER_BLOCK -> true; + default -> false; + }; + } + + static private Material pickLoot(List> lootList) { + // 12% first, 24% second, 64% third + Double chance = random.nextDouble(); + int index = chance < 0.12 ? 0 : (chance < 0.36 ? 1 : 2); + List list = lootList.get(index); + return list.get(random.nextInt(list.size())); + } + + public SuspiciousConversion(uSkyBlock plugin, Location location) { + this.plugin = plugin; + this.location = location; + } + + @Override + public void run() { + toConvert.remove(location); + if (!location.isWorldLoaded()) { + return; + } + Block block = location.getBlock(); + Material self_type = block.getType(); + Block up = block.getRelative(BlockFace.UP); + Material up_type = up.getType(); + + if (self_type != Material.SAND && self_type != Material.GRAVEL) { + return; + } + if (!isArchaeologyFeature(up_type)) { + return; + } + + String blocktype = self_type == Material.SAND ? "suspicious_sand" : "suspicious_gravel"; + String loottype = pickLoot(switch (up_type) { + case MUD_BRICKS -> RUINS_LOOT; + case MOSSY_COBBLESTONE -> MOSS_LOOT; + case SANDSTONE -> SAND_LOOT; + case SEAGRASS -> self_type == Material.SAND ? OCEAN_SAND_LOOT : OCEAN_GRAVEL_LOOT; + case WAXED_COPPER_BLOCK -> TRIAL_LOOT; + default -> RUINS_LOOT; + }).toString().toLowerCase(); + + String data_str = String.format("minecraft:%s{item:{id:\"minecraft:%s\",count:1}}", + blocktype,loottype); + + String command_str = String.format("execute in %s run setblock %d %d %d %s replace", + block.getWorld().getName(), block.getX(), block.getY(), block.getZ(), data_str); + + block.getWorld().playEffect(block.getLocation(), Effect.OXIDISED_COPPER_SCRAPE, 0); + plugin.getLogger().info("Converting suspicious block: " + data_str); + getServer().dispatchCommand(getServer().getConsoleSender(), command_str); + } +} @Singleton public class PlayerEvents implements Listener { @@ -93,6 +227,53 @@ public PlayerEvents(@NotNull uSkyBlock plugin) { blockLimitsEnabled = config.getBoolean("options.island.block-limits.enabled", false); } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onLootEvent(EntityDeathEvent event) { + if (!plugin.getWorldManager().isSkyAssociatedWorld(event.getEntity().getWorld())) { + return; + } + LivingEntity mob = event.getEntity(); + // Piglins drop an extra Pigstep CD if killed by a skeleton + if (mob.getType() == org.bukkit.entity.EntityType.PIGLIN) { + Entity killer = event.getDamageSource().getCausingEntity(); + if (killer instanceof Skeleton) { + mob.getWorld().dropItemNaturally(mob.getLocation(), new ItemStack(Material.MUSIC_DISC_PIGSTEP)); + } + return; + } + + + // warden drop an extra ward template + if (mob.getType() == org.bukkit.entity.EntityType.WARDEN) { + mob.getWorld().dropItemNaturally(mob.getLocation(), new ItemStack(Material.WARD_ARMOR_TRIM_SMITHING_TEMPLATE)); + return; + } + + // piglin brute: + // base: drop 1 gold ingot + 0.2% upgrade template + // +1 gold ingots and 0.2% chance if killed by a player + // extra +1 and 0.2% chance for each level of looting + if (mob.getType() == org.bukkit.entity.EntityType.PIGLIN_BRUTE) { + ItemStack goldIngot = new ItemStack(Material.GOLD_INGOT); + int lootingLevel = 0; + var killer = event.getEntity().getKiller(); + if (killer != null && killer.getType() == EntityType.PLAYER) { + // If killed by a player, increase the drop count and chance + lootingLevel = 1 + killer.getInventory().getItemInMainHand().getEnchantmentLevel(Enchantment.LOOTING); + } + goldIngot.setAmount(1 + lootingLevel); + mob.getWorld().dropItemNaturally(mob.getLocation(), goldIngot); + + double chance = 0.001 + (0.002 * lootingLevel); + if (Math.random() < chance) { + mob.getWorld().dropItemNaturally(mob.getLocation(), new ItemStack(Material.NETHERITE_UPGRADE_SMITHING_TEMPLATE)); + } + return; + } + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) public void onPlayerFoodChange(final FoodLevelChangeEvent event) { if (event.getEntity() instanceof Player player) { @@ -143,6 +324,74 @@ public void onClickOnObsidian(final PlayerInteractEvent event) { } } + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onBuryBrick(final PlayerInteractEvent event) { + Player player = event.getPlayer(); + ItemStack item = event.getItem(); + Block block = event.getClickedBlock(); + if (item != null && event.getAction() == Action.RIGHT_CLICK_BLOCK + && item.getType() == Material.BRICK + && block != null + && (block.getType() == Material.SAND || block.getType()==Material.GRAVEL)) { + Block up = block.getRelative(BlockFace.UP); + if (!SuspiciousConversion.isArchaeologyFeature(up.getType())) { + return; + } + Location loc = block.getLocation(); + if (SuspiciousConversion.toConvert.contains(block.getLocation())) { + // Hint the player: failed + block.getWorld().playEffect(block.getLocation(), Effect.SMOKE, event.getBlockFace()); + } else { + // Hint the player: success + block.getWorld().playEffect(block.getLocation(), Effect.COPPER_WAX_ON, 0); + if (player.getGameMode() != GameMode.CREATIVE) { + // Remove the brick from the player's inventory + item.setAmount(item.getAmount() - 1); + if (item.getAmount() <= 0) { + // If the item is depleted, remove it from the player's inventory + PlayerInventory inventory = player.getInventory(); + inventory.remove(item); + } + } + SuspiciousConversion.toConvert.add(loc); + Bukkit.getScheduler().runTaskLater(plugin, new SuspiciousConversion(plugin, loc), 6000L); // 5 minutes later + } + } + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onRefreshVaults(final PlayerInteractEvent event) { + Player player = event.getPlayer(); + ItemStack item = event.getItem(); + Block block = event.getClickedBlock(); + if (item != null && event.getAction() == Action.RIGHT_CLICK_BLOCK + && item.getType() == Material.GOLD_INGOT + && block != null + && block.getType() == Material.VAULT) { + if (block.getBlockData() instanceof Vault vault) { + if (!vault.isOminous()) { + if (vault.getTrialSpawnerState() == Vault.State.INACTIVE) { + // Reset this vault's blockdata to the default state + Location loc = block.getLocation(); + BlockData BD = block.getBlockData().clone(); + block.breakNaturally(); + player.getWorld().setBlockData(loc, BD); + if (player.getGameMode() != GameMode.CREATIVE) { + item.setAmount(item.getAmount() - 1); + if (item.getAmount() <= 0) { + // If the item is depleted, remove it from the player's inventory + PlayerInventory inventory = player.getInventory(); + inventory.remove(item); + } + } + player.sendMessage(tr("\u00a7eVault refreshed!")); + } + } else { + player.sendMessage(tr("\u00a74You cannot refresh an ominous vault!")); + } + } + } + } /** * Tests for more than one obsidian close by. */ @@ -299,7 +548,7 @@ public void onPlayerRespawn(PlayerRespawnEvent event) { @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) public void onTeleport(PlayerTeleportEvent event) { - if (event.getTo() == null || !plugin.getWorldManager().isSkyWorld(event.getTo().getWorld())) { + if (event.getTo() == null || !plugin.getWorldManager().isSkyAssociatedWorld(event.getTo().getWorld())) { return; } final Player player = event.getPlayer(); @@ -322,28 +571,41 @@ public void onTeleport(PlayerTeleportEvent event) { private RateLimiter rateLimiter = RateLimiter.create(1); @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) - public void onPlayerPortal(EntityPortalEnterEvent event) { - if (event.getEntityType() == EntityType.PLAYER) { - Player player = (Player) event.getEntity(); - PlayerInfo playerInfo = plugin.getPlayerInfo(player); - boolean isFirstCompletion = playerInfo.checkChallenge("page1finished") == 0; - if (player.isOp()) return; + public void onEntityPortal(EntityPortalEvent event) { + IslandInfo islandInfo = plugin.getIslandInfo(event.getFrom()); + if (islandInfo != null) { + PlayerInfo playerInfo = plugin.getPlayerInfo(islandInfo.getLeader()); + boolean isFirstCompletion = playerInfo.checkChallenge("beginner") == 0; if (isFirstCompletion){ event.setCancelled(true); - if (rateLimiter.tryAcquire()) { - player.sendMessage("\u00a7c地狱门已被禁用"); - } } } - else { - IslandInfo islandInfo = plugin.getIslandInfo(event.getLocation()); - if (islandInfo != null) { - PlayerInfo playerInfo = plugin.getPlayerInfo(islandInfo.getLeader()); - boolean isFirstCompletion = playerInfo.checkChallenge("page1finished") == 0; - if (isFirstCompletion){ - event.setCancelled(true); - } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onPlayerPortal(PlayerPortalEvent event) { + Player player = event.getPlayer(); + PlayerInfo playerInfo = plugin.getPlayerInfo(player); + boolean isFirstCompletion = playerInfo.checkChallenge("beginner") == 0; + if (player.isOp()) return; + if (isFirstCompletion){ + event.setCancelled(true); + if (rateLimiter.tryAcquire()) { + player.sendMessage("\u00a7c地狱门已被禁用"); } + } else { + plugin.getChallengeLogic().completeChallengeIfNotDone(playerInfo, "enter_nether"); + } + } + + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onPlayerMove(PlayerMoveEvent event) { + Player player = event.getPlayer(); + PlayerInfo playerInfo = plugin.getPlayerInfo(player); + IslandInfo ii = plugin.getIslandInfo(event.getTo()); + if (ii != null && !ii.getMemberUUIDs().contains(player.getUniqueId())) { // player is visitor + plugin.getChallengeLogic().completeChallengeIfNotDone(playerInfo, "visit_1"); + plugin.getChallengeLogic().completeLocationChallengeIfNotDone(event.getTo(), "visited_1"); } } @@ -383,7 +645,7 @@ public void onBlockPlaceEvent(BlockPlaceEvent event) { final String key = "usb.block-limits"; if (!PatienceTester.isRunning(player, key)) { PatienceTester.startRunning(player, key); - player.sendMessage(tr("\u00a74{0} is limited. \u00a7eScanning your island to see if you are allowed to place more, please be patient", ItemStackUtil.getItemName(new ItemStack(type)))); + player.sendMessage(tr("\u00a74{0} is limited. \u00a7eScanning your island to see if you are allowed to place more, please be patient", TranslationUtil.INSTANCE.getItemLocalizedName(new ItemStack(type)))); plugin.fireAsyncEvent(new IslandInfoEvent(player, islandInfo.getIslandLocation(), new Callback<>() { @Override public void run() { @@ -397,9 +659,9 @@ public void run() { if (canPlace == BlockLimitLogic.CanPlace.NO) { event.setCancelled(true); if (type == Material.HOPPER) - player.sendMessage(tr("\u00a74You''ve hit the {0} limit!\u00a7e You can''t have more of that type on your island!\u00a79 Max: {1,number}", ItemStackUtil.getItemName(new ItemStack(type)), (plugin.getBlockLimitLogic().getLimit(type)+islandInfo.getHopperLimit()))); + player.sendMessage(tr("\u00a74You''ve hit the {0} limit!\u00a7e You can''t have more of that type on your island!\u00a79 Max: {1,number}", TranslationUtil.INSTANCE.getItemLocalizedName(new ItemStack(type)), (plugin.getBlockLimitLogic().getLimit(type)+islandInfo.getHopperLimit()))); else - player.sendMessage(tr("\u00a74You''ve hit the {0} limit!\u00a7e You can''t have more of that type on your island!\u00a79 Max: {1,number}", ItemStackUtil.getItemName(new ItemStack(type)), plugin.getBlockLimitLogic().getLimit(type))); + player.sendMessage(tr("\u00a74You''ve hit the {0} limit!\u00a7e You can''t have more of that type on your island!\u00a79 Max: {1,number}", TranslationUtil.INSTANCE.getItemLocalizedName(new ItemStack(type)), plugin.getBlockLimitLogic().getLimit(type))); return; } plugin.getBlockLimitLogic().incBlockCount(islandInfo.getIslandLocation(), type); @@ -440,4 +702,271 @@ public void onBlockExplodeUnknown(BlockExplodeEvent event) { } } } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onFrostIce(EntityBlockFormEvent event){ + if (!plugin.getWorldManager().isSkyWorld(event.getBlock().getWorld())) { + return; + } + Location loc = event.getBlock().getLocation(); + loc.setX(loc.getBlockX() & ~3); + loc.setY(62); + loc.setZ(loc.getBlockZ() & ~3); + if (frostIceRecord.containsKey(loc)) { + frostIceRecord.get(loc).cancel(); + } + FrostIceRecord record = new FrostIceRecord(plugin, loc); + record.runTaskTimer(uSkyBlock.getInstance(), 24000, 24000); + frostIceRecord.put(loc, record); + } + + HashMap frostIceRecord = new HashMap<>(); + + public static class FrostIceRecord extends BukkitRunnable { + + private final Location loc; + private final uSkyBlock plugin; + public FrostIceRecord(uSkyBlock plugin, Location loc) { + this.loc = loc; this.plugin = plugin; + } + + @Override + public void run() { + Biome biome = loc.getWorld().getBiome(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); + int i; + for (i = 0; i < 5; i++) { + if (biome.equals(AcidBiomeProvider.tempToBiome[i])) { + break; + } + } + i--; + if (i <= 0) { + this.cancel(); + i = 0; + } + for (int y = -64; y < 319; y += 4) { + loc.getWorld().setBiome(loc.getBlockX(), y, loc.getBlockZ(), AcidBiomeProvider.tempToBiome[i]); + } + if (i == 0) { + plugin.getChallengeLogic().completeLocationChallengeIfNotDone(loc, "biome_nether"); + } + } + } + + @EventHandler (priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlaceCoral(BlockPlaceEvent event){ + if (plugin.getWorldManager().isSkyWorld(event.getPlayer().getWorld())) { + Material material = event.getBlock().getType(); + Location location = event.getBlock().getLocation(); + if (Tag.CORAL_BLOCKS.getValues().contains(material)) { + checkCoral(location, event.getPlayer()); + } + } + } + + @EventHandler (priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onBreakCoral(BlockBreakEvent event){ + if (plugin.getWorldManager().isSkyWorld(event.getPlayer().getWorld())) { + Material material = event.getBlock().getType(); + Location location = event.getBlock().getLocation(); + if (Tag.CORAL_BLOCKS.getValues().contains(material)) { + checkCoral(location, event.getPlayer()); + } + } + } + + private void checkCoral(Location loc, Player p) { + int ox = loc.getBlockX() >> 2; + int oz = loc.getBlockZ() >> 2; + HashSet record = new HashSet<>(); + record.add(loc); + int temperature = AcidBiomeProvider.getTemperature(ox, oz); + int need; + if (temperature == 1) need = 32; + else if (temperature == 2) need = 16; + else if (temperature == 3) need = 8; + else return; + + doCheckCoral(loc, ox, oz, record, need); + int ret = record.size(); + int gain = 0; + if (ret >= 32) gain = 3; + else if (ret >= 16) gain = 2; + else if (ret >= 8) gain = 1; + temperature += gain; + if (temperature >= 4) temperature = 4; + if (loc.getWorld().getBiome(ox << 2, 62, oz << 2) != AcidBiomeProvider.tempToBiome[temperature]) { + for (int y = 0; y < 256; y += 4) { + loc.getWorld().setBiome(ox << 2, y, oz << 2, AcidBiomeProvider.tempToBiome[temperature]); + } + p.sendMessage("Temperature changed!"); + } + if (temperature == 4) { + plugin.getChallengeLogic().completeLocationChallengeIfNotDone(loc, "biome_warm"); + } + } + + private static void doCheckCoral(Location loc, int ox, int oz, HashSet record, int stopvalue) { + int x = loc.getBlockX(); + int y = loc.getBlockY(); + int z = loc.getBlockZ(); + for (int i = x - 1; i <= x + 1; i ++) { + for (int j = y - 1; j <= y + 1; j ++) { + for (int k = z - 1; k <= z + 1; k ++) { + if (j < -64 || j > 319) continue; + if (i >> 2 != ox || k >> 2 != oz) continue; + Location temp = loc.clone(); + temp.setX(i); + temp.setY(j); + temp.setZ(k); + if (record.contains(temp)) continue; + if (!Tag.CORAL_BLOCKS.getValues().contains(temp.getBlock().getType())) continue; + record.add(temp); + if (record.size() >= stopvalue) return; + doCheckCoral(temp, ox, oz, record, stopvalue); + if (record.size() >= stopvalue) return; + } + } + } + } + + @EventHandler (priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onPlaceInNether(BlockPlaceEvent event){ + if (plugin.getWorldManager().isSkyNether(event.getPlayer().getWorld())) { + Material material = event.getBlock().getType(); + // 地狱放置4*2*4方块 改变群系 (绯红森林/诡异森林 上层菌岩 下层地狱岩) + // 玄武岩/黑石->玄武岩三角洲 绯红菌岩->绯红森林 诡异菌岩->诡异森林 灵魂土->灵魂沙峡谷 地狱岩->下界荒地 + if (material != Material.BASALT && material != Material.CRIMSON_NYLIUM && material != Material.WARPED_NYLIUM && material != Material.SOUL_SOIL && material != Material.NETHERRACK && material != Material.BLACKSTONE) { + return; + } + Location location = event.getBlock().getLocation(); + int x = location.getBlockX() & ~3; + int y = location.getBlockY(); + if (y <= 2) { + return; + } + int z = location.getBlockZ() & ~3; + World world = event.getBlock().getWorld(); + + // 检查4x4内暴露在空气中的方块 + Material record = null; + for (int i = x; i < x + 4; i ++) { + for (int j = z; j < z + 4; j ++) { + int k = y; + if (world.getBlockAt(i, k, j).getType() == Material.AIR) { + do k--; + while(world.getBlockAt(i, k, j).getType() == Material.AIR); + } else { + do k++; + while(world.getBlockAt(i, k, j).getType() != Material.AIR); + k--; + } + if (k <= 2) { + return; + } + // 检查最上层 + if (record == null) { + record = world.getBlockAt(i, k, j).getType(); + if (record == Material.BLACKSTONE) { + record = Material.BASALT; + } + } else { + Material current = world.getBlockAt(i, k, j).getType(); + if (current != record && (current != Material.BLACKSTONE || record != Material.BASALT)) { + return; + } + } + // 检查下一层 + Material current = world.getBlockAt(i, k - 1, j).getType(); + if ( + ((current == Material.BLACKSTONE || current == Material.BASALT) && record == Material.BASALT) || + (current == Material.NETHERRACK && record == Material.CRIMSON_NYLIUM) || + (current == Material.NETHERRACK && record == Material.WARPED_NYLIUM) || + (current == Material.NETHERRACK && record == Material.NETHERRACK) || + (current == Material.SOUL_SOIL && record == Material.SOUL_SOIL) + ) continue; + else { + return; + } + } + } + //检查完 + Biome biome; + if (record == Material.BASALT) biome = Biome.BASALT_DELTAS; + else if (record == Material.CRIMSON_NYLIUM) biome = Biome.CRIMSON_FOREST; + else if (record == Material.WARPED_NYLIUM) biome = Biome.WARPED_FOREST; + else if (record == Material.SOUL_SOIL) biome = Biome.SOUL_SAND_VALLEY; + else biome = Biome.NETHER_WASTES; + plugin.getChallengeLogic().completeLocationChallengeIfNotDone(location, "biome_nether"); + for (int k = y - 4; k < y + 12; k += 4) { + if (k < 0 || k > 255) continue; + world.setBiome(x, k, z, biome); + } + Player player = event.getPlayer(); + player.sendMessage("changed biome to" + biome.name()); + } + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onEntityChangeBlock(EntityChangeBlockEvent event) { + if (!plugin.getWorldManager().isSkyWorld(event.getBlock().getWorld())) { + return; + } + Block block = event.getBlock(); + Material toType = event.getTo(); + Material fromType = block.getType(); + Random random = new Random(); + if (fromType == Material.AIR){ + if (toType == Material.ANVIL || toType == Material.CHIPPED_ANVIL || toType == Material.DAMAGED_ANVIL){ + // 铁砧下落砸方块更改 + Block change = block.getRelative(BlockFace.DOWN); + Material changeType = change.getType(); + BlockData changed = change.getBlockData(); + if (changed instanceof Leaves){ + change.setType(Material.AIR); + plugin.getChallengeLogic().completeLocationChallengeIfNotDone(block.getLocation(), "anvilleaves"); + return; + } + if (changeType == Material.STONE_BRICKS){ + change.setType(Material.CRACKED_STONE_BRICKS); + return; + } + if (changeType == Material.COBBLESTONE){ + if (random.nextInt(50) == 0) { + change.setType(Material.GRAVEL); + } + return; + } + if (changeType == Material.STONE){ + if (random.nextInt(10) == 0) { + change.setType(Material.DEEPSLATE); + } + return; + } + if (changeType == Material.MAGMA_BLOCK){ + if (random.nextInt(10) == 0) { + change.setType(Material.AIR); + if (random.nextInt(2) == 0) { + change.getWorld().dropItemNaturally(change.getLocation(), new ItemStack(Material.BLAZE_POWDER)); + } + } + return; + } + if (changeType == Material.SOUL_SOIL){ + if (random.nextInt(200) == 0) { + change.getWorld().dropItemNaturally(change.getLocation(), new ItemStack(Material.NETHER_WART)); + } + return; + } + if (changeType == Material.WARPED_NYLIUM){ + change.setType(Material.CRIMSON_NYLIUM); + return; + } + if (changeType == Material.CRIMSON_NYLIUM){ + change.setType(Material.WARPED_NYLIUM); + return; + } + } + } + } } diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/event/ProgressEvents.java b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/event/ProgressEvents.java new file mode 100644 index 000000000..88afadf5b --- /dev/null +++ b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/event/ProgressEvents.java @@ -0,0 +1,109 @@ +package us.talabrek.ultimateskyblock.event; + +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import us.talabrek.ultimateskyblock.api.event.IslandLeaderChangedEvent; +import us.talabrek.ultimateskyblock.island.IslandInfo; +import us.talabrek.ultimateskyblock.player.PlayerInfo; +import us.talabrek.ultimateskyblock.progress.Progress; +import us.talabrek.ultimateskyblock.uSkyBlock; + +import java.util.UUID; + +public class ProgressEvents implements Listener { + + /** + * Handles the event when the leader of an island changes. + *

+ * When the leader of an island changes, this method updates the progress for the new leader. + * This ensures that the new leader has the correct progress data. + * + * @param event The island leader changed event. + */ + @EventHandler + public void onLeaderChangeEvent(IslandLeaderChangedEvent event) { + // Use UUID-based API for better offline player support + UUID originalLeaderUUID = event.getOriginalLeaderInfo().getUniqueId(); + Progress.migrateProgress(originalLeaderUUID); + } + + /** + * Handles player logout to clear progress cache and prevent memory leaks. + * @param event The player quit event. + */ + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + Progress.clearCache(event.getPlayer()); + } + + /** + * Handles the event when a player uses a command related to visiting an island. + *

+ * This method listens for the {@link PlayerCommandPreprocessEvent} and checks if the player + * has executed a command starting with "/island warp" or "/is warp". If the command is valid + * (target player exists, has an island, and has an active warp), it updates both the visitor's + * "visit" progress and the visited player's "visited_by" progress. + * + * @param event The player command preprocess event. + */ + @EventHandler + public void onVisitEvent(PlayerCommandPreprocessEvent event) { + String message = event.getMessage(); + if(message.startsWith("/island warp") || message.startsWith("/is warp")) { + // Extract the target player name from the command + String[] args = message.split(" "); + if (args.length >= 3) { // "/island warp " or "/is warp " + String targetPlayerName = args[2]; + + // Get plugin instance + uSkyBlock plugin = uSkyBlock.getInstance(); + Player visitor = event.getPlayer(); + + // Check if visitor has permission to warp + if (!visitor.hasPermission("usb.island.warp")) { + return; + } + + // Check if visitor's island is generating + PlayerInfo visitorInfo = plugin.getPlayerInfo(visitor); + if (visitorInfo.isIslandGenerating()) { + return; + } + + // Get target player info + PlayerInfo targetPlayerInfo = plugin.getPlayerInfo(targetPlayerName); + if (targetPlayerInfo == null || !targetPlayerInfo.getHasIsland()) { + return; // Target player doesn't exist or doesn't have an island + } + + // Get target island info + IslandInfo targetIsland = plugin.getIslandInfo(targetPlayerInfo); + if (targetIsland == null || (!targetIsland.hasWarp() && !targetIsland.isTrusted(visitor))) { + return; // Target doesn't have an active warp and visitor is not trusted + } + + // Check if target island is generating + if (targetPlayerInfo.isIslandGenerating()) { + return; + } + + // Check if visitor is banned from the island + if (targetIsland.isBanned(visitor)) { + return; + } + + // If we reach here, the warp command is valid + // Add to visitor's visit progress using UUID-based API + Progress.getProgress(visitor.getUniqueId()).addToProgress("visit", 1.0); + + // Add to target player's visited_by progress using UUID-based API + // This works regardless of whether the target player is online or offline + UUID targetLeaderUUID = targetIsland.getLeaderUniqueId(); + Progress.getProgress(targetLeaderUUID).addToProgress("visited_by", 1.0); + } + } + } +} diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/event/SpawnEvents.java b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/event/SpawnEvents.java index a4e1360c6..53889891a 100644 --- a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/event/SpawnEvents.java +++ b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/event/SpawnEvents.java @@ -2,19 +2,14 @@ import com.google.inject.Inject; import com.google.inject.Singleton; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.Registry; -import org.bukkit.World; +import org.bukkit.*; import org.bukkit.block.Biome; +import org.bukkit.block.Block; import org.bukkit.entity.*; -import org.bukkit.event.Cancellable; -import org.bukkit.event.Event; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; +import org.bukkit.event.*; import org.bukkit.event.block.Action; import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.entity.EntityPickupItemEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.SpawnEggMeta; @@ -22,15 +17,14 @@ import org.jetbrains.annotations.Nullable; import us.talabrek.ultimateskyblock.api.IslandInfo; import us.talabrek.ultimateskyblock.handler.WorldGuardHandler; -import us.talabrek.ultimateskyblock.player.PlayerInfo; import us.talabrek.ultimateskyblock.uSkyBlock; -import us.talabrek.ultimateskyblock.util.LogUtil; +import us.talabrek.ultimateskyblock.util.LocationUtil; -import java.util.*; -import java.util.logging.Level; +import java.util.Collection; +import java.util.Objects; +import java.util.Set; import static dk.lockfuglsang.minecraft.po.I18nUtil.tr; -import static us.talabrek.ultimateskyblock.util.LogUtil.log; /** * Responsible for controlling spawns on uSkyBlock islands. @@ -39,7 +33,6 @@ public class SpawnEvents implements Listener { private static final Set RIGHT_CLICKS = Set.of(Action.RIGHT_CLICK_AIR, Action.RIGHT_CLICK_BLOCK); - private HashMap newIsland = new HashMap<>(); private final uSkyBlock plugin; private boolean phantomsInOverworld; @@ -52,10 +45,25 @@ public SpawnEvents(@NotNull uSkyBlock plugin) { phantomsInNether = plugin.getConfig().getBoolean("options.spawning.phantoms.nether", false); } + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onPiglinConvert(EntityPickupItemEvent event) { + if (!plugin.getWorldManager().isSkyAssociatedWorld((event.getEntity().getWorld()))) { + return; + } + if (event.getEntity() instanceof Piglin piglin && piglin.isAdult()) { //对玩家 + if (event.getItem().getItemStack().getType() == Material.GOLDEN_AXE) { + // Spawn a Piglin Brute instead of a Piglin + Location location = piglin.getLocation(); + piglin.remove(); + location.getWorld().spawnEntity(location, EntityType.PIGLIN_BRUTE); + } + } + } + @EventHandler public void onSpawnEggEvent(PlayerInteractEvent event) { Player player = event.getPlayer(); - if (event.useItemInHand() == Event.Result.DENY || !plugin.getWorldManager().isSkyWorld(player.getWorld())) { + if (event.useItemInHand() == Event.Result.DENY || !plugin.getWorldManager().isSkyAssociatedWorld(player.getWorld())) { return; // Bail out, we don't care } if (player.hasPermission("usb.mod.bypassprotection") || player.isOp()) { @@ -75,12 +83,34 @@ public void onSpawnEggEvent(PlayerInteractEvent event) { event.setUseItemInHand(Event.Result.DENY); event.setUseInteractedBlock(Event.Result.DENY); } + + Block block = event.getClickedBlock(); + // If used on a trial spawner, only allow certain entities to be spawned + if (block != null && block.getType() == Material.TRIAL_SPAWNER) { + EntityType entityType = getSpawnEggType(item); + if (!allowTrialSpawner(entityType)) { + plugin.notifyPlayer(player, tr("\u00a7cYou cannot spawn this with a trial spawner.")); + event.setUseItemInHand(Event.Result.DENY); + event.setUseInteractedBlock(Event.Result.DENY); + } + } + } + } + + private boolean allowTrialSpawner(EntityType entityType) { + if (entityType == null) { + return false; } + return switch (entityType) { + case ZOMBIE, HUSK, SLIME, SPIDER, CAVE_SPIDER, SKELETON, STRAY, BOGGED, BREEZE -> true; + default -> false; + }; } private static @Nullable EntityType getSpawnEggType(@NotNull ItemStack itemStack) { if (itemStack.getItemMeta() instanceof SpawnEggMeta spawnEggMeta) { - EntitySnapshot spawnedEntity = spawnEggMeta.getSpawnedEntity(); + // getSpawnedEntity is broken + EntitySnapshot spawnedEntity = null; // spawnEggMeta.getSpawnedEntity(); if (spawnedEntity != null) { return spawnedEntity.getEntityType(); } else { @@ -94,12 +124,6 @@ public void onSpawnEggEvent(PlayerInteractEvent event) { } } - private int fastpos(int pos){ - pos+=64; - return (pos<0)?((pos+1)/128):(pos/128); - } - - @EventHandler(ignoreCancelled = true) public void onCreatureSpawn(CreatureSpawnEvent event) { if (event == null || !plugin.getWorldManager().isSkyAssociatedWorld(event.getLocation().getWorld())) { @@ -108,121 +132,24 @@ public void onCreatureSpawn(CreatureSpawnEvent event) { if (event.getSpawnReason().equals(CreatureSpawnEvent.SpawnReason.SPAWNER_EGG)) { return; // Allow it, the above method would have blocked it if it should be blocked. } - if (event.getEntity() instanceof Phantom) { - String island = fastpos(event.getLocation().getBlockX()) + "," + fastpos(event.getLocation().getBlockZ()); - - if(newIsland.get(island) == null){ - IslandInfo is = plugin.getIslandInfo(event.getLocation()); - if(is != null){ - PlayerInfo pi = plugin.getPlayerInfo(is.getLeader()); - if (pi != null && pi.checkChallenge("page1finished")==0){ - // newbie protection - event.setCancelled(true); - pi = null; is = null; - newIsland.put(island, true); - log(Level.INFO, "Add inf Phantom Protection: "+island+" : protect"); - return; - } - pi = null; is = null; - newIsland.put(island, false); - log(Level.INFO, "Add inf Phantom Protection: "+island+" : Not-protect"); - } - }else{ - if (newIsland.get(island) == true){ - event.setCancelled(true); - return; - } - } - } - if (event.getEntity() instanceof WaterMob) { - Random r = new Random(); - if (r.nextInt(20) != 0){ - event.setCancelled(true); - return; - } - } - if (event.getEntity() instanceof ArmorStand){ - return; - } checkLimits(event, event.getEntity().getType(), event.getLocation()); if (event.getEntity() instanceof WaterMob) { Location loc = event.getLocation(); - if(doPrismarineRoof(loc)) + if (isDeepOceanBiome(loc) && isPrismarineRoof(loc)) { + loc.getWorld().spawnEntity(loc, EntityType.GUARDIAN); event.setCancelled(true); - } - if (!event.isCancelled() && event.getEntity() instanceof Enderman) { - Location loc = event.getLocation(); - - if(isPurpurFloor(loc)) { - if (isEndBiome(loc)) { - Random r = new Random(); - if (r.nextInt(10) == 0) { - loc.getWorld().spawnEntity(loc, EntityType.SHULKER); - event.setCancelled(true); - } - } else { - loc.getWorld().spawnEntity(loc, EntityType.SHULKER); - event.setCancelled(true); - } } } } - private boolean isPurpurFloor(Location loc){ - List purpurBlocks = Arrays.asList(Material.PURPUR_BLOCK, Material.PURPUR_PILLAR, Material.PURPUR_SLAB); - loc.setY(loc.getY()-1); - boolean ret=purpurBlocks.contains(loc.getBlock().getType()); - loc.setY(loc.getY()+1); - return ret; - } - - private boolean doPrismarineRoof(Location loc) { - List prismarineBlocks = Arrays.asList(Material.PRISMARINE, Material.PRISMARINE_BRICKS, Material.DARK_PRISMARINE); - Location tloc = loc.clone(); - if(tloc.getBlockY()<47 || tloc.getBlockY()>64) - return false; - while(tloc.getBlockY()<=70){ - if (tloc.getBlock().getType() == Material.WATER){ - tloc.add(0,1,0); - }else{ - if(prismarineBlocks.contains(tloc.getBlock().getType())){ - Random r = new Random(); - if (r.nextInt(5) == 0){ - if(r.nextInt(1000) == 0){ - if(r.nextInt(1000)==0){ - Drowned drowned= (Drowned) loc.getWorld().spawnEntity(loc, EntityType.DROWNED); - drowned.getEquipment().setItemInMainHand(new ItemStack(Material.TRIDENT)); - LogUtil.log(Level.INFO, java.time.Clock.systemUTC().instant()+" "+plugin.getIslandInfo(loc).getLeader()+" RANDOM TRIDENT"); - } - else{ - Drowned drowned= (Drowned) loc.getWorld().spawnEntity(loc, EntityType.DROWNED); - if(drowned.getEquipment().getItemInMainHand().equals(new ItemStack(Material.TRIDENT))){ - LogUtil.log(Level.INFO,java.time.Clock.systemUTC().instant()+" "+plugin.getIslandInfo(loc).getLeader()+" DROWNED TRIDENT"); - } - else{ - LogUtil.log(Level.INFO,java.time.Clock.systemUTC().instant()+" "+plugin.getIslandInfo(loc).getLeader()+" DROWNED NO TRIDENT"); - } - } - } - else{ - loc.getWorld().spawnEntity(loc, EntityType.GUARDIAN); - } - } - return true; - }else if(tloc.getBlock().getType() == Material.SEA_LANTERN){ - Random r = new Random(); - if (r.nextInt(50) == 0) - loc.getWorld().spawnEntity(loc, EntityType.ELDER_GUARDIAN); - return true; - } - return false; - } - } - return false; + private boolean isPrismarineRoof(Location loc) { + Collection prismarineBlocks = Set.of(Material.PRISMARINE, Material.PRISMARINE_BRICKS, Material.DARK_PRISMARINE); + return prismarineBlocks.contains(LocationUtil.findRoofBlock(loc).getType()); } - private boolean isEndBiome(Location loc) { - return loc.getWorld().getBiome(loc.getBlockX(), loc.getBlockZ())==Biome.THE_END; + private boolean isDeepOceanBiome(Location loc) { + Collection deepOceans = Set.of(Biome.DEEP_OCEAN, Biome.DEEP_COLD_OCEAN, Biome.DEEP_FROZEN_OCEAN, Biome.DEEP_LUKEWARM_OCEAN); + return deepOceans.contains(loc.getWorld().getBiome(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ())); } private void checkLimits(Cancellable event, EntityType entityType, Location location) { @@ -248,7 +175,7 @@ private void checkLimits(Cancellable event, EntityType entityType, Location loca @EventHandler(ignoreCancelled = true) public void onPhantomSpawn(CreatureSpawnEvent event) { if (!(event.getEntity() instanceof Phantom) || - event.getSpawnReason() != CreatureSpawnEvent.SpawnReason.NATURAL) { + event.getSpawnReason() != CreatureSpawnEvent.SpawnReason.NATURAL) { return; } @@ -264,6 +191,7 @@ public void onPhantomSpawn(CreatureSpawnEvent event) { /** * Changes the setting that allows Phantoms to spawn in the overworld. Used for testing purposes. + * * @param state True/enabled means spawning is allowed, false disallowed. */ void setPhantomsInOverworld(boolean state) { @@ -272,6 +200,7 @@ void setPhantomsInOverworld(boolean state) { /** * Changes the setting that allows Phantoms to spawn in the nether. Used for testing purposes. + * * @param state True/enabled means spawning is allowed, false disallowed. */ void setPhantomsInNether(boolean state) { diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/island/IslandGenerator.java b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/island/IslandGenerator.java index 2adb6503f..b07d5e917 100644 --- a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/island/IslandGenerator.java +++ b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/island/IslandGenerator.java @@ -61,7 +61,7 @@ public IslandGenerator( directorySchematics.mkdir(); } copySchematicsFromJar(); - netherSchematic = getSchematicFile(config.getYamlConfig().getString("nether.schematicName", "uSkyBlockNether")); + netherSchematic = getSchematicFile(config.getYamlConfig().getString("nether.schematicName", "acidIslandNether")); schemFiles = loadSchematics(config); if (schemFiles == null) { @@ -98,8 +98,8 @@ public boolean createIsland(@NotNull PlayerPerk playerPerk, @NotNull Location ne next.setYaw(0); next.setPitch(0); next.setY((double) Settings.island_height); - File schemFile = getSchematicFile(cSchem != null ? cSchem : "default"); - File netherFile = getSchematicFile(cSchem != null ? cSchem + "Nether" : "uSkyBlockNether"); + File schemFile = getSchematicFile(cSchem != null ? cSchem : "acidIsland"); + File netherFile = getSchematicFile(cSchem != null ? cSchem + "Nether" : "acidIslandNether"); if (netherFile == null) { netherFile = netherSchematic; } diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/progress/Progress.java b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/progress/Progress.java new file mode 100644 index 000000000..dc8a6f5fc --- /dev/null +++ b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/progress/Progress.java @@ -0,0 +1,347 @@ +package us.talabrek.ultimateskyblock.progress; + +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import us.talabrek.ultimateskyblock.event.ProgressEvents; +import us.talabrek.ultimateskyblock.island.IslandInfo; +import us.talabrek.ultimateskyblock.player.PlayerInfo; +import us.talabrek.ultimateskyblock.uSkyBlock; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; +import java.util.UUID; +import java.util.logging.Logger; + +/** + * An abstraction for storing player progress in YAML files. + * This provides a way to track game progress and support content creation. + *

This mechanism is currently not thread-safe. + */ +public class Progress { + + private static uSkyBlock plugin; + private static Logger logger; + private static File progressDir; + // Global cache for player progress (UUID-based for internal consistency) + private static final Map uuidCache = new HashMap<>(); + // Legacy cache for backwards compatibility + private static final Map playerCache = new HashMap<>(); + + /** + * Initialize the Progress tracking mechanism. + * This is called by the uSkyBlock plugin on startup. + * @param plugin The uSkyBlock plugin instance. + */ + public static void init(uSkyBlock plugin) { + Progress.plugin = plugin; + Progress.logger = plugin.getLogger(); + Progress.progressDir = new File(plugin.getDataFolder(), "progress"); + if (!progressDir.exists()) { + if (!progressDir.mkdirs()) { + logger.severe("Failed to create progress directory: " + progressDir.getAbsolutePath()); + } + } + plugin.getServer().getPluginManager().registerEvents(new ProgressEvents(), plugin); + } + + /** + * Retrieves the progress for a player by UUID. If the player's progress is not already + * cached, it will be created and stored in the cache. Note that if the player is + * not the leader of their party, the leader's progress will be returned instead. + * @param playerUUID The UUID of the player to retrieve progress for. + * @return The cached progress for the player. + */ + public static Progress getProgress(UUID playerUUID) { + PlayerInfo playerInfo = plugin.getPlayerInfo(playerUUID); + IslandInfo islandInfo = plugin.getIslandInfo(playerInfo); + UUID leaderUUID = islandInfo.getLeaderUniqueId(); // Use getLeaderUniqueId() directly + if (!playerUUID.equals(leaderUUID)) { + playerUUID = leaderUUID; + } + return forceGetProgressUUID(playerUUID); + } + + /** + * Retrieves the progress for a player. If the player's progress is not already + * cached, it will be created and stored in the cache. Note that if the player is + * not the leader of their party, the leader's progress will be returned instead. + * @param player The player to retrieve progress for. + * @return The cached progress for the player. + */ + public static Progress getProgress(Player player) { + return getProgress(player.getUniqueId()); + } + + /** + * Retrieves the progress for a player by UUID. If the player's progress is not already + * cached, it will be created and stored in the cache. Note that this method + * will return a new progress object even if the player is not the leader of + * their party. If you want to get the progress for the leader of the player's + * party, use {@link #getProgress(UUID)}. + * @param playerUUID The UUID of the player to retrieve progress for. + * @return The cached progress for the player. + */ + public static Progress forceGetProgressUUID(UUID playerUUID) { + if (uuidCache.containsKey(playerUUID)) { + return uuidCache.get(playerUUID); + } else { + Progress progress = new Progress(playerUUID); + uuidCache.put(playerUUID, progress); + return progress; + } + } + + /** + * Retrieves the progress for a player. If the player's progress is not already + * cached, it will be created and stored in the cache. Note that this method + * will return a new progress object even if the player is not the leader of + * their party. If you want to get the progress for the leader of the player's + * party, use {@link #getProgress(Player)}. + * @param player The player to retrieve progress for. + * @return The cached progress for the player. + */ + public static Progress forceGetProgressPlayer(Player player) { + UUID playerUUID = player.getUniqueId(); + Progress progress = forceGetProgressUUID(playerUUID); + + // Update legacy cache for backwards compatibility + playerCache.put(player, progress); + + return progress; + } + + /** + * Migrates the player's progress to their island leader's progress by UUID. + *

+ * This method retrieves the player's island leader and migrates each progress entry + * from the player's progress to the leader's progress. The leader's progress for each key + * is incremented by the player's current progress for that key. + *

+ * This is useful in scenarios where a player joins an island led by another player, and + * they assume leadership of the island, migrating island progress to their progress file. + * + * @param playerUUID The UUID of the player whose progress will be migrated to the leader's progress. + */ + public static void migrateProgress(UUID playerUUID) { + PlayerInfo playerInfo = plugin.getPlayerInfo(playerUUID); + IslandInfo island = plugin.getIslandInfo(playerInfo); + UUID leaderUUID = island.getLeaderUniqueId(); // Use getLeaderUniqueId() directly + Progress leaderProgress = forceGetProgressUUID(leaderUUID); + Progress progress = forceGetProgressUUID(playerUUID); + if (leaderProgress.equals(progress)) { + logger.warning("Tried to migrate progress for player " + playerUUID + " but they are already the leader."); + return; + } + progress.progress.forEach(leaderProgress::setProgress); + progress.resetProgress(); // Clear the original player's progress after migration + logger.info("Migrated progress for player " + playerUUID); + } + + /** + * Migrates the player's progress to their island leader's progress. + *

+ * This method retrieves the player's island leader and migrates each progress entry + * from the player's progress to the leader's progress. The leader's progress for each key + * is incremented by the player's current progress for that key. + *

+ * This is useful in scenarios where a player joins an island led by another player, and + * they assume leadership of the island, migrating island progress to their progress file. + * + * @param player The player whose progress will be migrated to the leader's progress. + */ + public static void migrateProgress(Player player) { + migrateProgress(player.getUniqueId()); + } + + /** + * Clears a player from both caches. This should be called when a player logs out + * to prevent memory leaks. + * @param player The player to remove from cache. + */ + public static void clearCache(Player player) { + UUID playerUUID = player.getUniqueId(); + Progress progress = uuidCache.get(playerUUID); + if (progress != null) { + progress.flushCache(); // Ensure data is saved before removing from cache + } + uuidCache.remove(playerUUID); + playerCache.remove(player); + } + + /** + * Clears a player from cache by UUID. This should be called when a player logs out + * to prevent memory leaks. + * @param playerUUID The UUID of the player to remove from cache. + */ + public static void clearCache(UUID playerUUID) { + Progress progress = uuidCache.get(playerUUID); + if (progress != null) { + progress.flushCache(); // Ensure data is saved before removing from cache + } + uuidCache.remove(playerUUID); + // Also remove from legacy cache if present + playerCache.entrySet().removeIf(entry -> entry.getKey().getUniqueId().equals(playerUUID)); + } + + public final UUID playerUUID; + @Deprecated // Use playerUUID instead + public final Player player; + private final Map progress = new TreeMap<>(); + private final File progressFile; + private YamlConfiguration progressConfig; + + /** + * Creates a new Progress object for the given player UUID. + * It is generally not advised to create a new Progress object directly. + * Use Progress.getProgress(UUID) instead to ensure proper caching and retrieval. + */ + public Progress(UUID playerUUID) { + this.playerUUID = playerUUID; + this.player = plugin.getServer().getPlayer(playerUUID); // May be null if offline + this.progressFile = new File(progressDir, playerUUID + ".yml"); + this.progressConfig = YamlConfiguration.loadConfiguration(progressFile); + fetchCache(); + } + + /** + * It is generally not advised to create a new Progress object directly. + * Use Progress.getProgress(Player) instead to ensure proper caching and retrieval. + * @deprecated Use Progress(UUID) constructor instead for better offline player support. + */ + @Deprecated + public Progress(Player player) { + this(player.getUniqueId()); + } + + /** + * Loads the player's progress from the YAML file into the progress cache. + *

+ * IMPORTANT: If inconsistency may happen, + * this method and flushCache() should be called to ensure the cache is up-to-date. + */ + public void fetchCache() { + progress.clear(); + if (progressFile.exists()) { + progressConfig = YamlConfiguration.loadConfiguration(progressFile); + for (String key : progressConfig.getKeys(false)) { + if (key.startsWith("progress_")) { + String progressKey = key.substring(9); // Remove "progress_" prefix + double value = progressConfig.getDouble(key, 0.0); + progress.put(progressKey, value); + } + } + } + } + + /** + * Saves the progress cache to the player's YAML file, then + * fetch any possible external modifications. + *

+ * IMPORTANT: This should be called whenever inconsistency may happen. + */ + public void flushCache() { + try { + // Ensure the progress directory exists + if (!progressDir.exists()) { + if (!progressDir.mkdirs()) { + logger.severe("Failed to create progress directory: " + progressDir.getAbsolutePath()); + return; + } + } + + // Clear existing progress entries + for (String key : progressConfig.getKeys(false)) { + if (key.startsWith("progress_")) { + progressConfig.set(key, null); + } + } + + // Save current progress + for (Map.Entry entry : progress.entrySet()) { + progressConfig.set("progress_" + entry.getKey(), entry.getValue()); + } + + progressConfig.save(progressFile); + + // Re-fetch to ensure consistency + fetchCache(); + } catch (IOException e) { + String playerName = player != null ? player.getName() : playerUUID.toString(); + logger.severe("Failed to save progress for player " + playerName + ": " + e.getMessage()); + } + } + + /** + * Sets the progress for the given key to the given value. + *

+ * The key should be a unique identifier for the progress being tracked (e.g. "Challenge_1" or "Islands_Visited"). + * The value should be a double indicating the player's progress. + *

+ * This will update the player's YAML file as well as the cache. + * @param key The key for the progress to track. + * @param value The value of the progress. + */ + public void setProgress(String key, double value) { + progress.put(key, value); + try { + progressConfig.set("progress_" + key, value); + progressConfig.save(progressFile); + } catch (IOException e) { + String playerName = player != null ? player.getName() : playerUUID.toString(); + logger.severe("Failed to save progress for player " + playerName + ", key " + key + ": " + e.getMessage()); + } + } + + /** + * Returns the progress for the given key. + *

+ * If the key does not exist, returns 0. + * @param key The key for the progress to retrieve. + * @return The progress for the given key. + */ + public double getProgress(String key) { + return progress.getOrDefault(key, 0.0); + } + + /** + * Adds the given value to the progress for the given key and returns the new value. + *

+ * If the key does not exist, it will be created with the given value. + * @param key The key for the progress to modify. + * @param value The value to add to the progress. + * @return The new value of the progress. + */ + @SuppressWarnings("UnusedReturnValue") + public double addToProgress(String key, double value) { + double currentValue = getProgress(key); + double newValue = currentValue + value; + setProgress(key, newValue); + return newValue; + } + + /** + * Resets all progress for the player. + *

+ * This method clears the progress cache and removes all progress-related entries + * from the player's YAML file. It effectively resets the player's + * tracked progress to an initial state. + */ + public void resetProgress() { + progress.clear(); + try { + // Clear all progress entries from the config + for (String key : progressConfig.getKeys(false)) { + if (key.startsWith("progress_")) { + progressConfig.set(key, null); + } + } + progressConfig.save(progressFile); + } catch (IOException e) { + String playerName = player != null ? player.getName() : playerUUID.toString(); + logger.severe("Failed to reset progress for player " + playerName + ": " + e.getMessage()); + } + } +} diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/signs/SignEvents.java b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/signs/SignEvents.java index d870faa37..3aecada52 100644 --- a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/signs/SignEvents.java +++ b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/signs/SignEvents.java @@ -56,6 +56,7 @@ public void onPlayerHitSign(PlayerInteractEvent e) { logic.updateSign(e.getClickedBlock().getLocation()); } else { logic.signClicked(e.getPlayer(), e.getClickedBlock().getLocation()); + e.setCancelled(true); // TODO: cancel only if the sign is a SkyBlock sign } } diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/uSkyBlock.java b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/uSkyBlock.java index fa32a3c11..3daabf31d 100644 --- a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/uSkyBlock.java +++ b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/uSkyBlock.java @@ -68,6 +68,8 @@ import us.talabrek.ultimateskyblock.player.PlayerNotifier; import us.talabrek.ultimateskyblock.player.PlayerPerk; import us.talabrek.ultimateskyblock.player.TeleportLogic; +import us.talabrek.ultimateskyblock.progress.Progress; +import us.talabrek.ultimateskyblock.util.*; import us.talabrek.ultimateskyblock.util.IslandUtil; import us.talabrek.ultimateskyblock.util.LocationUtil; import us.talabrek.ultimateskyblock.util.Scheduler; @@ -642,10 +644,14 @@ private void startup() { WorldManager.skyBlockWorld = null; // Force a re-import or what-ever... WorldManager.skyBlockNetherWorld = null; + TranslationUtil.INSTANCE.loadItemLang(this); + Injector injector = Guice.createInjector(new SkyblockModule(this)); this.skyBlock = injector.getInstance(SkyblockApp.class); injector.injectMembers(this); this.skyBlock.startup(this); + + Progress.init(this); } private void delayedEnable() { diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/util/TranslationUtil.kt b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/util/TranslationUtil.kt new file mode 100644 index 000000000..353f8fd8b --- /dev/null +++ b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/util/TranslationUtil.kt @@ -0,0 +1,123 @@ +package us.talabrek.ultimateskyblock.util + +import com.google.gson.Gson +import com.google.gson.JsonArray +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import com.google.gson.reflect.TypeToken +import org.bukkit.Bukkit +import org.bukkit.inventory.ItemStack +import org.bukkit.plugin.java.JavaPlugin +import java.io.File +import java.lang.reflect.Type +import java.net.URL +import java.nio.file.Files +import java.nio.file.StandardCopyOption +import java.util.* +import java.util.regex.Matcher +import java.util.regex.Pattern + +object TranslationUtil { + var dictionary = HashMap() + + fun getItemLocalizedName(itemStack: ItemStack): String { + val name: String = itemStack.type.name.lowercase(Locale.getDefault()) + if (dictionary.contains("item.minecraft.$name")) { + return dictionary.get("item.minecraft.$name").toString() + } + if (dictionary.contains("block.minecraft.$name")) { + return dictionary.get("block.minecraft.$name").toString() + } + return name + } + + fun loadItemLang(plugin: JavaPlugin): Boolean { + val folder: File = File(plugin.getDataFolder(), "i18n/items") + if (!folder.exists()) { + fetchLanguageAssets(plugin) + } + + for (file in folder.listFiles()!!) { + var tr: String? = null + try { + tr = file.readText() + } catch (e: java.lang.Exception) { + e.printStackTrace() + plugin.logger.severe("TranslationUtil: error in loading " + file.name) + return false + } + + val type: Type = object: TypeToken>() {}.type + dictionary = Gson().fromJson(JsonParser().parse(tr).asJsonObject, type) + + } + plugin.logger.info("TranslationUtil: Loaded " + dictionary.toList().size + " item translations. ") + return true + } + + private fun fetchLanguageAssets(plugin: JavaPlugin) { + try { + val parser = JsonParser() + val version = Bukkit.getBukkitVersion().split("-".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0] + val versionsManifest: JsonArray = parser.parse( + URL("https://launchermeta.mojang.com/mc/game/version_manifest.json").readText() + ).asJsonObject.getAsJsonArray("versions") // like a array with: {"id": "1.21.1", "type": "release", "url": "https://piston-meta.mojang.com/v1/packages/c4c37a8abcbab7fb31031acfb19b9a3a34efb1ec/1.21.1.json", "time": "2025-07-17T06:36:54+00:00", "releaseTime": "2024-08-08T12:24:45+00:00"} + var versionUrl: URL? = null + versionsManifest.forEach { + if (it is JsonObject && it.has("id") && it.get("id").asString.equals(version)) { + versionUrl = URL(it.get("url").asString) + } + } + if (versionUrl == null) { + plugin.logger.severe("TranslationUtil: Unable to find version information: $version") + throw Exception() + } + // versionUrl like: https://piston-meta.mojang.com/v1/packages/c4c37a8abcbab7fb31031acfb19b9a3a34efb1ec/1.21.1.json + plugin.logger.info("TranslationUtil: Fetching metadata of $version...") + val assetsURL = parser.parse(versionUrl!!.readText()).asJsonObject.getAsJsonObject("assetIndex").get("url").asString // "assetIndex": {"id": "26", "sha1": "7eb8873392fc365779dbfea6e2c28fca30a6c6cd", "size": 490976, "totalSize": 432035882, "url": "https://piston-meta.mojang.com/v1/packages/7eb8873392fc365779dbfea6e2c28fca30a6c6cd/26.json"} + if (assetsURL == null) { // https://piston-meta.mojang.com/v1/packages/7eb8873392fc365779dbfea6e2c28fca30a6c6cd/26.json + plugin.logger.severe("TranslationUtil: Unable to get assets information.") + throw Exception() + } + plugin.logger.info("TranslationUtil: Fetching assets information...") // like array of "minecraft/lang/zh_cn.json": {"hash": "6a829093df0c8f7d3942e26655290334925a9b23", "size": 475421} + val objects = parser.parse(URL(assetsURL).readText()).asJsonObject.getAsJsonObject("objects") + + val folder = File(plugin.dataFolder, "i18n/items") + if (!folder.exists()) { + folder.mkdirs() + } + val pattern: Pattern = Pattern.compile("minecraft/lang/([^\\.]+)\\.json") // minecraft/lang/zh_cn.json -> zh_cn + + objects.entrySet().forEach { (key) -> + val match: Matcher = pattern.matcher(key) + if (match.matches() && match.groupCount() == 1) { + val lang = match.group(1) + + if (lang != "zh_cn") return@forEach + + val langJsonObject = objects.getAsJsonObject("minecraft/lang/$lang.json") // minecraft/lang/zh_cn.json + if (langJsonObject == null) { + plugin.logger.severe("TranslationUtil: Failed to fetch: $lang, internal error.") + return@forEach + } + plugin.logger.info("TranslationUtil: Fetching $lang...") + var langJsonDownloadUrl: String? = null + try { + val hash: String = langJsonObject.get("hash").asString + langJsonDownloadUrl = "https://resources.download.minecraft.net/" + hash.substring(0, 2) + "/" + hash // 6a/6a829093df0c8f7d3942e26655290334925a9b23 + val json = URL(langJsonDownloadUrl) + val file = File(folder, "$lang.json") + Files.copy(json.openStream(), file.toPath(), StandardCopyOption.REPLACE_EXISTING) + } catch (e: Exception) { + plugin.logger.severe("Cannot load $lang from mojang, url=$langJsonDownloadUrl") + e.printStackTrace() + return + } + } + } + } catch (e: Exception) { + plugin.logger.warning("TranslationUtil: Error when get item translation files from mojang. Please check your config or add these files manually.") + plugin.server.pluginManager.disablePlugin(plugin) + } + } +} diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/uuid/NullPlayer.java b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/uuid/NullPlayer.java index 85c24c490..b83fc740d 100644 --- a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/uuid/NullPlayer.java +++ b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/uuid/NullPlayer.java @@ -1,6 +1,5 @@ package us.talabrek.ultimateskyblock.uuid; -import io.papermc.paper.persistence.PersistentDataContainerView; import org.bukkit.BanEntry; import org.bukkit.Location; import org.bukkit.Material; @@ -28,11 +27,6 @@ public boolean isOnline() { return false; } - @Override - public boolean isConnected() { - return false; - } - @Override public String getName() { return PlayerDB.UNKNOWN_PLAYER_NAME; @@ -43,8 +37,9 @@ public UUID getUniqueId() { return PlayerDB.UNKNOWN_PLAYER_UUID; } + @NotNull @Override - public com.destroystokyo.paper.profile.PlayerProfile getPlayerProfile() { + public PlayerProfile getPlayerProfile() { return null; } @@ -106,16 +101,6 @@ public Location getBedSpawnLocation() { return null; } - @Override - public long getLastLogin() { - return 0; - } - - @Override - public long getLastSeen() { - return 0; - } - @Nullable @Override public Location getRespawnLocation() { @@ -224,11 +209,6 @@ public Location getLocation() { return null; } - @Override - public PersistentDataContainerView getPersistentDataContainer() { - return null; - } - @Override public Map serialize() { return null; diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/world/AcidBiomeProvider.java b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/world/AcidBiomeProvider.java new file mode 100644 index 000000000..b25558ba5 --- /dev/null +++ b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/world/AcidBiomeProvider.java @@ -0,0 +1,38 @@ +package us.talabrek.ultimateskyblock.world; + +import org.bukkit.block.Biome; +import org.bukkit.generator.BiomeProvider; +import org.bukkit.generator.WorldInfo; +import org.bukkit.util.noise.SimplexNoiseGenerator; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; + +public class AcidBiomeProvider extends BiomeProvider { + + public final static SimplexNoiseGenerator noise = new SimplexNoiseGenerator(0); + + public static final Biome[] tempToBiome = {Biome.FROZEN_OCEAN, Biome.COLD_OCEAN, Biome.OCEAN, Biome.LUKEWARM_OCEAN, Biome.WARM_OCEAN}; + + public static int getTemperature(int x, int z) { + double res = noise.noise(0.09 * x, 0.09 * z); + int ret; + if (res <= -0.5) ret = 1; + else if (res <= 0) ret = 2; + else if (res <= 0.6) ret = 3; + else if (res < 1) ret = 4; + else ret = 2; + return ret; + } + + @Override + public @NotNull Biome getBiome(@NotNull WorldInfo worldInfo, int x, int y, int z) { + return tempToBiome[getTemperature(x, z)]; + } + + @Override + public @NotNull List getBiomes(@NotNull WorldInfo worldInfo) { + return Arrays.stream(tempToBiome).toList(); + } +} diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/world/AcidIslandChunkGenerator.java b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/world/AcidIslandChunkGenerator.java new file mode 100644 index 000000000..56da9a11f --- /dev/null +++ b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/world/AcidIslandChunkGenerator.java @@ -0,0 +1,54 @@ +package us.talabrek.ultimateskyblock.world; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.block.data.BlockData; +import org.bukkit.generator.BiomeProvider; +import org.bukkit.generator.BlockPopulator; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.generator.WorldInfo; +import org.jetbrains.annotations.NotNull; +import us.talabrek.ultimateskyblock.Settings; + +import java.util.List; +import java.util.Random; + +public class AcidIslandChunkGenerator extends ChunkGenerator { + + private final boolean old = false; + + @Override + public void generateSurface(@NotNull WorldInfo worldInfo, @NotNull Random random, int chunkX, int chunkZ, @NotNull ChunkData chunkData) { + if (old) { + chunkData.setRegion(0, 1, 0, 16, Settings.island_height - 1, 16, Bukkit.createBlockData("acidwater:acid_block")); + } else { + chunkData.setRegion(0, -63, 0, 16, Settings.island_height - 1, 16, Bukkit.createBlockData("acidwater:acid_block")); + } + } + + @Override + public void generateBedrock(@NotNull WorldInfo worldInfo, @NotNull Random random, int chunkX, int chunkZ, @NotNull ChunkData chunkData) { + if (old) { + chunkData.setRegion(0, 0, 0, 16, 1, 16, Material.BARRIER); + } else { + chunkData.setRegion(0, -64, 0, 16, -63, 16, Material.BARRIER); + } + } + + @Override + public @NotNull List getDefaultPopulators(@NotNull World world) { + return List.of(); + } + + @Override + public BiomeProvider getDefaultBiomeProvider(@NotNull WorldInfo worldInfo) { + return new AcidBiomeProvider(); + } + + @Override + public Location getFixedSpawnLocation(@NotNull World world, @NotNull Random random) { + return new Location(world, 0, Settings.island_height, 0); + } +} diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/world/AcidNetherChunkGenerator.java b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/world/AcidNetherChunkGenerator.java new file mode 100644 index 000000000..3d8b4cfef --- /dev/null +++ b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/world/AcidNetherChunkGenerator.java @@ -0,0 +1,62 @@ +package us.talabrek.ultimateskyblock.world; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.generator.BiomeProvider; +import org.bukkit.generator.BlockPopulator; +import org.bukkit.generator.ChunkGenerator; +import org.bukkit.generator.WorldInfo; +import org.jetbrains.annotations.NotNull; +import us.talabrek.ultimateskyblock.Settings; + +import java.util.List; +import java.util.Random; + +public class AcidNetherChunkGenerator extends ChunkGenerator { + + @Override + public void generateNoise(@NotNull WorldInfo worldInfo, @NotNull Random random, int chunkX, int chunkZ, @NotNull ChunkData chunkData) { + Random r = new Random(); + for (int i = 0; i < 4; i ++) { + int x = r.nextInt(16); + int z = r.nextInt(16); + for (int j = 0; j < 30; j ++) { + if (r.nextInt(2) != 0) continue; + int tx = x + (int)(r.nextGaussian() * 2); + int tz = z + (int)(r.nextGaussian() * 2); + if (i % 2 == 0) { + chunkData.setBlock(tx, 128, tz, Material.BROWN_MUSHROOM); + } else { + chunkData.setBlock(tx, 128, tz, Material.RED_MUSHROOM); + } + } + } + } + + @Override + public void generateSurface(@NotNull WorldInfo worldInfo, @NotNull Random random, int chunkX, int chunkZ, @NotNull ChunkData chunkData) { + chunkData.setRegion(0, 1, 0, 16, 32, 16, Material.LAVA); + } + + @Override + public void generateBedrock(@NotNull WorldInfo worldInfo, @NotNull Random random, int chunkX, int chunkZ, @NotNull ChunkData chunkData) { + chunkData.setRegion(0, 0, 0, 16, 1, 16, Material.BEDROCK); + chunkData.setRegion(0, 127, 0, 16, 128, 16, Material.BEDROCK); + } + + @Override + public @NotNull List getDefaultPopulators(@NotNull World world) { + return List.of(); + } + + @Override + public BiomeProvider getDefaultBiomeProvider(@NotNull WorldInfo worldInfo) { + return new SingleBiomeProvider(Settings.general_defaultNetherBiome); + } + + @Override + public Location getFixedSpawnLocation(@NotNull World world, @NotNull Random random) { + return new Location(world, 0, Settings.nether_height, 0); + } +} diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/world/SkyBlockChunkGenerator.java b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/world/SkyBlockChunkGenerator.java deleted file mode 100644 index f0ede4c3c..000000000 --- a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/world/SkyBlockChunkGenerator.java +++ /dev/null @@ -1,34 +0,0 @@ -package us.talabrek.ultimateskyblock.world; - -import org.bukkit.Location; -import org.bukkit.World; -import org.bukkit.generator.BiomeProvider; -import org.bukkit.generator.BlockPopulator; -import org.bukkit.generator.ChunkGenerator; -import org.bukkit.generator.WorldInfo; -import org.jetbrains.annotations.NotNull; -import us.talabrek.ultimateskyblock.Settings; - -import java.util.List; -import java.util.Random; - -/** - * A chunk generator that generates an empty world with a single biome. - */ -public class SkyBlockChunkGenerator extends ChunkGenerator { - - @NotNull - public List getDefaultPopulators(@NotNull World world) { - return List.of(); - } - - @Override - public BiomeProvider getDefaultBiomeProvider(@NotNull WorldInfo worldInfo) { - return new SingleBiomeProvider(Settings.general_defaultBiome); - } - - @Override - public Location getFixedSpawnLocation(@NotNull World world, @NotNull Random random) { - return new Location(world, 0.5d, Settings.island_height, 0.5d); - } -} diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/world/SkyBlockNetherChunkGenerator.java b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/world/SkyBlockNetherChunkGenerator.java deleted file mode 100644 index c6b826ded..000000000 --- a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/world/SkyBlockNetherChunkGenerator.java +++ /dev/null @@ -1,93 +0,0 @@ -package us.talabrek.ultimateskyblock.world; - -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.World; -import org.bukkit.generator.BiomeProvider; -import org.bukkit.generator.BlockPopulator; -import org.bukkit.generator.ChunkGenerator; -import org.bukkit.generator.WorldInfo; -import org.jetbrains.annotations.NotNull; -import us.talabrek.ultimateskyblock.Settings; - -import java.util.List; -import java.util.Random; - -public class SkyBlockNetherChunkGenerator extends ChunkGenerator { - - // The nether height limits intentionally differ from World.getMinHeight() and World.getMaxHeight() to reflect - // vanilla nether limits. - private static final int MIN_HEIGHT = 0; - private static final int MAX_HEIGHT = 128; - - @Override - public void generateNoise(@NotNull WorldInfo worldInfo, @NotNull Random random, int chunkX, int chunkZ, @NotNull ChunkData chunkData) { - // Generate lava sea - chunkData.setRegion(0, MIN_HEIGHT, 0, 16, Settings.nether_lava_level + 1, 16, Material.LAVA); - - // Generate netherrack ceiling - - // First layer with holes - int y = MAX_HEIGHT - 8; - for (int x = 0; x < 16; x++) { - for (int z = 0; z < 16; z++) { - if (random.nextDouble() >= 0.20) { // 20% air - chunkData.setBlock(x, y, z, Material.NETHERRACK); - } - } - } - - // Solid block above - chunkData.setRegion(0, MAX_HEIGHT - 7, 0, 16, MAX_HEIGHT, 16, Material.NETHERRACK); - } - - @Override - public void generateBedrock(@NotNull WorldInfo worldInfo, @NotNull Random random, int chunkX, int chunkZ, @NotNull ChunkData chunkData) { - // Solid bedrock floor - chunkData.setRegion(0, MIN_HEIGHT, 0, 16, MIN_HEIGHT + 1, 16, Material.BEDROCK); - - // Bedrock floor with holes in it - for (int yOffset = 1; yOffset < 6; yOffset++) { - double yThreshold = 0.10 * yOffset; // 90% - 50% bedrock - int y = MIN_HEIGHT + yOffset; - for (int x = 0; x < 16; x++) { - for (int z = 0; z < 16; z++) { - if (random.nextDouble() >= yThreshold) { - chunkData.setBlock(x, y, z, Material.BEDROCK); - } - } - } - } - - // Bedrock ceiling with holes in it - for (int yOffset = 1; yOffset < 6; yOffset++) { - double yThreshold = 0.20 * (yOffset); // 20% - 100% bedrock - int y = MAX_HEIGHT - yOffset - 1; - for (int x = 0; x < 16; x++) { - for (int z = 0; z < 16; z++) { - if (random.nextDouble() >= yThreshold) { - chunkData.setBlock(x, y, z, Material.BEDROCK); - } - } - } - } - - // Solid bedrock ceiling - chunkData.setRegion(0, MAX_HEIGHT - 1, 0, 16, MAX_HEIGHT, 16, Material.BEDROCK); - } - - @Override - public @NotNull List getDefaultPopulators(@NotNull World world) { - return List.of(); - } - - @Override - public BiomeProvider getDefaultBiomeProvider(@NotNull WorldInfo worldInfo) { - return new SingleBiomeProvider(Settings.general_defaultNetherBiome); - } - - @Override - public Location getFixedSpawnLocation(@NotNull World world, @NotNull Random random) { - return new Location(world, 0, Settings.nether_height, 0); - } -} diff --git a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/world/WorldManager.java b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/world/WorldManager.java index c9e61dca2..88f5500dc 100644 --- a/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/world/WorldManager.java +++ b/uSkyBlock-Core/src/main/java/us/talabrek/ultimateskyblock/world/WorldManager.java @@ -161,7 +161,7 @@ private void createSpawn(@NotNull Location spawnLocation) { private ChunkGenerator getOverworldGenerator() { try { String clazz = config.getYamlConfig().getString("options.advanced.chunk-generator", - "us.talabrek.ultimateskyblock.world.SkyBlockChunkGenerator"); + "us.talabrek.ultimateskyblock.world.AcidIslandChunkGenerator"); Object generator = Class.forName(clazz).getDeclaredConstructor().newInstance(); if (generator instanceof ChunkGenerator) { return (ChunkGenerator) generator; @@ -172,7 +172,7 @@ private ChunkGenerator getOverworldGenerator() { NoSuchMethodException ex) { logger.log(Level.WARNING, "Unable to instantiate overworld chunk-generator: " + ex); } - return new SkyBlockChunkGenerator(); + return new AcidIslandChunkGenerator(); } /** @@ -184,7 +184,7 @@ private ChunkGenerator getOverworldGenerator() { private ChunkGenerator getNetherGenerator() { try { String clazz = config.getYamlConfig().getString("nether.chunk-generator", - "us.talabrek.ultimateskyblock.world.SkyBlockNetherChunkGenerator"); + "us.talabrek.ultimateskyblock.world.AcidNetherChunkGenerator"); Object generator = Class.forName(clazz).getDeclaredConstructor().newInstance(); if (generator instanceof ChunkGenerator) { return (ChunkGenerator) generator; @@ -195,7 +195,7 @@ private ChunkGenerator getNetherGenerator() { NoSuchMethodException ex) { logger.log(Level.WARNING, "Unable to instantiate nether chunk-generator: " + ex); } - return new SkyBlockNetherChunkGenerator(); + return new AcidNetherChunkGenerator(); } /** diff --git a/uSkyBlock-Core/src/main/resources/challenges.yml b/uSkyBlock-Core/src/main/resources/challenges.yml index 3c0d13931..af588b3e6 100644 --- a/uSkyBlock-Core/src/main/resources/challenges.yml +++ b/uSkyBlock-Core/src/main/resources/challenges.yml @@ -1,209 +1,169 @@ # ====================================================================================================================== # -# Here follows the format and explanation for the challenge group and challenges setup. The single commented (#) are the -# most used settings while the double commented (##) are optional if you like to use those settings. +# 以下是挑战组和挑战设置的格式说明。单行注释(#)表示最常用的设置,双行注释(##)表示可选设置。 # -# Item type format -# Item types are defined as in the minecraft /give command, i.e. with their minecraft key and possible components in -# square brackets. For example, 'minecraft:diamond_sword[damage=42]'. Refer to the Minecraft wiki for more information: -# https://minecraft.wiki/w/Data_component_format. You can also use the command '/usb iteminfo' to get the information. +# 物品类型格式: +# 物品类型按照Minecraft的/give命令格式定义,即使用Minecraft键名和方括号内的可选组件。 +# 例如: 'minecraft:diamond_sword[damage=42]'。参考Minecraft维基获取更多信息: +# https://minecraft.wiki/w/Data_component_format。也可以使用命令'/usb iteminfo'获取物品信息。 # -# This item type is supplemented with additional information depending on where it is used: +# 根据使用场景不同,物品类型会补充额外信息: # -# display-item: -# An item to be displayed in a GUI. It only defines the item type as above, without any additional information. -# For example: 'cobblestone', 'minecraft:stone', 'diamond_sword[damage=42]'. +# 展示物品(display-item): <类型> +# 用于在GUI中显示的物品。仅定义物品类型,不包含额外信息。 +# 例如: 'cobblestone', 'minecraft:stone', 'diamond_sword[damage=42]'。 # -# item-requirement: :[;+] -# An item requirement for a challenge. The amount is the number of items required. The optional + is the number -# of items to add to the required amount for each repeat of the challenge. For example, 'cobblestone:64;+16' would -# require 64 cobblestone for the first completion and 80 cobblestone for the second completion. Other options are -# -, *, and / for subtraction, multiplication, and division, respectively. For example, 'cobblestone:64;*2' would -# require 64 cobblestone for the first completion, 128 cobblestone for the second completion, and 256 cobblestone for -# the third completion. +# 物品需求(item-requirement): <类型>:<数量>[;+<增量>] +# 挑战所需的物品。数量是需要的物品数。可选的+<数量>表示每次重复挑战时需要增加的物品数。 +# 例如: 'cobblestone:64;+16'表示第一次完成需要64圆石,第二次需要80圆石。 +# 其他操作符包括-、*、/,分别表示减法、乘法和除法。 +# 例如: 'cobblestone:64;*2'表示第一次64,第二次128,第三次256。 # -# item-reward: [{p=}]: -# An item reward for a challenge. The amount is the number of items to give. The optional {p=} is the -# probability of the item being given. For example, 'cobblestone:64' would give 64 cobblestone every time the challenge -# is completed, while '{p=0.1}cobblestone:64' would give 64 cobblestone 10% of the time. +# 物品奖励(item-reward): [{p=<概率>}]<类型>:<数量> +# 挑战完成的物品奖励。数量是给予的物品数。可选的{p=<概率>}表示给予该物品的概率。 +# 例如: 'cobblestone:64'每次完成都会给予64圆石,而'{p=0.1}cobblestone:64'只有10%概率给予。 # # ====================================================================================================================== -# All challenges are defined in the ranks section. Each rank is a tier of challenges that players can complete. +# 所有挑战都在ranks部分定义。每个rank代表玩家可以完成的一个挑战层级。 # # ranks: -# # [text] name of the challenge Rank. -# TierX: -# # [text] The name of the challenge rank that shows when you do /challenges (supports capitals and color codes). -# name: '&aCustom Challenges rank name' -# # [display-item] The item to be displayed in the challenge menu for completed challenges. -# displayItem: 'cyan_terracotta' -# # [integer] The time in hours before required items reset to default (this overwrites the main reset time) -# resetInHours: 20 -# # These requirements controls when a challenge group will be available to a player. -# requires: -# # [integer] The number of tasks per rank that can be left uncompleted to advance to the next rank. For example, -# if you have 4 challenges with a rankLeeway of 1, a player would only need to complete 3 to advance to -# the next rank. A rankLeeway of 0 would require them all. -# rankLeeway: 8 -# # [List[text]] Challenges that have to be completed before this group will available to a player. -# challenges: -# - a challenge name +# # [文本] 挑战等级的名称 +# TierX: +# # [文本] 执行/challenges时显示的挑战等级名称(支持大写和颜色代码) +# name: '&a自定义挑战等级名称' +# # [展示物品] 在挑战菜单中用于表示已完成挑战的物品 +# displayItem: '青色陶瓦' +# # [整数] 物品需求重置为默认值的小时数(覆盖主重置时间) +# resetInHours: 20 +# # 这些要求控制挑战组何时对玩家可用 +# requires: +# # [整数] 允许未完成任务数的容差值。例如如果有4个挑战且rankLeeway为1, +# # 玩家只需完成3个即可进入下一等级。设为0则需要全部完成。 +# rankLeeway: 8 +# # [文本列表] 必须先完成这些挑战才能解锁本组挑战 +# challenges: +# - 挑战名称 # ====================================================================================================================== # challenges: -# # [text] The name of the challenge. All challenge names should be lower-case. +# # [文本] 挑战名称。所有挑战名称应为小写 # defaultchallenge: -# # [text] The name of the challenge that shows in /challenges (this supports capitals and color codes). -# name: '&a Default Challenge' -# # [text] The descriptions players see when they do /challenges +# # [文本] 在/challenges中显示的挑战名称(支持大写和颜色代码) +# name: '&a默认挑战' +# # [文本] 玩家执行/challenges <挑战名称>时看到的描述 # description: -# # [onIsland/onPlayer/islandLevel] This defines whether the required blocks/items should be in the player's -# # inventory or on their island. When using onIsland, the player must within 10 blocks from the required blocks -# # on his island. When using islandLevel, the 'requiredItems' field should be the island level required. The -# # player must use /island level first to update their level. +# # [onIsland/onPlayer/islandLevel] 定义所需方块/物品应在玩家物品栏中还是岛上。 +# # 使用onIsland时,玩家必须距离岛上所需方块10格范围内。 +# # 使用islandLevel时,'requiredItems'字段应为所需的岛屿等级。 +# # 玩家必须先使用/island level更新等级。 # type: onPlayer # ## type: islandLevel # ## type: onIsland -# # [integer] Overrides the default radius of 10 blocks when using onIsland. +# # [整数] 使用onIsland时覆盖默认的10格半径 # ## radius: 20 -# # List[item-requirement] The items required to complete the challenge. +# # [物品需求列表] 完成挑战所需的物品 # requiredItems: # - stone:64;+16 # - cobblestone:64;+16 -# # List[block-requirement] The blocks required to complete the challenge. +# # [方块需求列表] 完成挑战所需的方块 # requiredBlocks: # - stone:64 # - cobblestone:64 -# # [true/false] If the challenge can be repeated or not. +# # [true/false] 挑战是否可重复完成 # ## repeatable: true -# # [integer] The maximum number of times the challenge can be completed. Overrides the default repeatLimit. -# # A value of 0 means unlimited repeats. +# # [整数] 挑战可完成的最大次数(覆盖默认repeatLimit)。0表示无限次。 # ## repeatLimit: 5 -# # [display-item] The item to be displayed in the challenge menu for completed challenges. +# # [展示物品] 在挑战菜单中用于表示已完成挑战的物品 # displayItem: cobblestone -# # [integer] The time in hours before required items reset to default (overwrites the main and rank defaults). +# # [整数] 物品需求重置为默认值的小时数(覆盖主设置和等级默认值) # ## resetInHours: 4 -# # [true/false] Take required items on completing a challenge. +# # [true/false] 完成挑战时是否收取所需物品 # ## takeItems: true -# # The rewards players get for completing the challenge +# # 玩家完成挑战获得的奖励 # reward: -# # [text] Description of the reward. -# text: 'Mossy cobblestone and an iron pickaxe with unbreaking 1' -# # [List[item-requirement]] A list of items given to the player for completing the challenge. +# # [文本] 奖励描述 +# text: '苔石和带有耐久1的铁镐' +# # [物品需求列表] 完成挑战给予玩家的物品 # items: # - mossy_cobblestone:16 # - iron_pickaxe[enchantments={levels:{unbreaking:1}}]:1 -# # [permission node] A permission granted for completion. Multiple permissions are space-separated. +# # [权限节点] 完成时授予的权限(多个权限用空格分隔) # ## permission: 'test.permission' -# # [integer] How much currency to give for completion. (requires an economy plugin) +# # [整数] 给予的货币数量(需要经济插件) # ## currency: 0 -# # [integer] How much xp to give to the player for completion. +# # [整数] 给予玩家的经验值 # ## xp: 0 -# # [List[Text]] Executes the given command upon completion. Prepend with "op" or "console" to run the commands -# as OP or from the Console. Examples: -# # Possible command arguments are: -# # {player} - The name of the player -# # {playerName} - The display name of the player -# # {challenge} - The name of the challenge -# # {position} - The position of the player -# # {party} - Execute the command once for each member of the party (substituting the name) +# # [文本列表] 完成时执行的命令。添加"op"或"console"前缀表示以OP或控制台身份执行。 +# # 可用参数: +# # {player} - 玩家名称 +# # {playerName} - 玩家显示名称 +# # {challenge} - 挑战名称 +# # {position} - 玩家位置 +# # {party} - 为队伍每个成员执行一次命令(替换名称) # ## commands: -# ## - 'op: me are the GOD of things' -# ## - 'console: give {party} aweseomestuff 32' -# # reward section to reward the player for completing a repeated challenge (any time after the first). The -# # structure is identical to the 'reward' section. +# ## - 'op: 我是万物的主宰' +# ## - 'console: give {party} 超棒物品 32' +# # 重复完成挑战时的奖励部分(第一次之后的任何完成)。结构与'reward'相同。 # repeatReward: -# text: 'Mossy cobblestone' +# text: '苔石' # items: # - mossy_cobblestone:16 # # ====================================================================================================================== - -# [true/false] Enable the use of the challenges command. allowChallenges: true - -# [island/player] Whether challenges are tracked per player, or per island challengeSharing: island - -# [true/false] If true, first time challenge completions are broadcast to the whole server. broadcastCompletion: true - -# [text] The color/formatting of the broadcast text when showing first time completions. broadcastText: '&6' - -# [true/false] If true, challenges in higher level ranks require challenges in lower level ranks to be completed. requirePreviousRank: true - -# [integer] The number of tasks per rank that can be left uncompleted to advance to the next rank. For example, if you have 4 easy challenges -# with a rankLeeway of 1, a player would only need to complete 3 to advance to the next rank. -# A rankLeeway of 0 would require them all. rankLeeway: 12 - -# [integer] The time in hours before required items reset to default. (only if not specified in the challenges below) defaultResetInHours: 20 - -# [integer] The default radius in blocks when using onIsland. Can be overridden in the challenges below. radius: 10 - -# [integer] The maximum number of times a challenge can be completed by default. 0 means unlimited. Can be overridden in the challenge definition below. repeatLimit: 0 - -# [color code] The color to use for uncompleted challenges in the list. challengeColor: '&e' - -# [color code] The color to use for completed challenges in the list. (non-repeatable) finishedColor: '&2' - -# [color code] The color to use for completed challenges in the list. (repeatable) repeatableColor: '&a' - -# [true/false] If true, enables vault to handle currency rewards. enableEconomyPlugin: true - -# [true/false] If false, challenges are not reset on island creation (or restart) resetChallengesOnCreate: true - -# Material to show for locked challenges (i.e. STAINED_GLASS_PANE:14 for a red glass-pane, or 160:14) -lockedDisplayItem: red_stained_glass_pane - -# Material to show for onIsland challenges when locked +lockedDisplayItem: gray_stained_glass_pane ISLAND: lockedDisplayItem: blue_stained_glass_pane - -# Material to show for islandLevel challenges when locked ISLAND_LEVEL: lockedDisplayItem: black_stained_glass_pane - -# Whether to show the name of locked challenges showLockedChallengeName: true - -# When creating your own challenges you will have -# to uncomment the section below or the default challenges -# will be re-added on every server restart. When altering -# the default challenges this should be fine to leave. -# merge-ignore: -# - 'ranks' -# -# =============================================== -# An explanation to setup your own challenges -# can be found at the bottom of this file. -# =============================================== ranks: Tier1: - name: '&7Novice' - displayItem: cyan_terracotta + name: '&7新手民工' + displayItem: iron_axe resetInHours: 20 challenges: + start: + name: '&6开始咯' + description: 蒲公英x2。 + type: onPlayer + requiredItems: + - 'dandelion:2' + displayItem: dandelion + resetInHours: 6 + reward: + text: 水和岩浆 + items: + - ice:2 + - lava_bucket:1 + currency: 20 + xp: 20 + repeatReward: + text: '' cobblestonegenerator: - name: '&7Cobble Stone Generator' - description: Mine from a cobblestone generator. + name: '&7石头!' + description: 造个刷石机 这还要我教吗 + requiredChallenges: + - start type: onPlayer requiredItems: - - cobblestone:64;+2 + - cobblestone:64 displayItem: cobblestone - lockedDisplayItem: gray_stained_glass_pane resetInHours: 12 reward: - text: 3 leather, 20% chance to get a book + text: 皮革x3 items: - leather:3 - '{p=0.2}book:1' @@ -212,159 +172,122 @@ ranks: commands: - op:effect give {player} regeneration repeatReward: - text: 1 leather, 10% chance to get a book + text: 皮革x1 items: - leather:1 - '{p=0.1}book:1' currency: 5 xp: 5 - applecollector: - name: '&6Apple Collector' - description: Collect apples from trees. + toolmaker: + name: '&3工欲善其事...' + description: 准备全套石头工具 + requiredChallenges: + - start type: onPlayer requiredItems: - - apple:2;+1 - displayItem: apple - lockedDisplayItem: brown_stained_glass_pane - resetInHours: 6 + - stone_shovel:1;+0 + - stone_pickaxe:1;+0 + - stone_axe:1;+0 + - stone_hoe:1;+0 + displayItem: stone_axe + resetInHours: 12 reward: - text: 1 of each sapling, (4 dark oak) - items: - - oak_sapling:1 - - spruce_sapling:1 - - birch_sapling:1 - - jungle_sapling:1 - - acacia_sapling:1 - - dark_oak_sapling:4 - - '{p=0.2}jungle_sapling:3' - currency: 20 - xp: 20 - repeatReward: - text: 1 of each sapling (4 dark oak) + text: 红石矿x4,铁矿x4,猪x1,各种树苗x1 items: + - redstone_ore:4 + - iron_ore:4 + - pig_spawn_egg:1 - oak_sapling:1 - spruce_sapling:1 - birch_sapling:1 - - jungle_sapling:1 + - jungle_sapling:4 - acacia_sapling:1 - dark_oak_sapling:4 - - '{p=0.1}jungle_sapling:3' - currency: 10 - xp: 10 - sugarplanter: - name: '&9Sugar Planter' - type: onIsland - displayItem: sugar_cane - lockedDisplayItem: brown_stained_glass_pane - requiredBlocks: - - sugar_cane:4 - reward: - text: 4 dirt - items: - - dirt:4 - currency: 20 - xp: 20 - sugarfarmer: - name: '&6Sugar Farmer' - description: Harvest sugarcane from a farm. - type: onPlayer - requiredItems: - - sugar_cane:64;+16 - offset: -1 - displayItem: sugar_cane - lockedDisplayItem: brown_stained_glass_pane - reward: - text: 8 dirt - items: - - dirt:8 - - '{p=0.1}bone:1' - currency: 20 - xp: 20 + currency: 30 + xp: 30 repeatReward: - text: 4 dirt + text: 红石矿x4 items: - - dirt:4 - - '{p=0.05}dirt:4' - currency: 10 - xp: 10 - melonfarmer: - name: '&6Melon Farmer' - description: Harvest slices of melon from a farm. + - redstone_ore:4 + - '{p=0.1}iron_ore:1' + currency: 5 + xp: 5 + woodpractice: + name: '&3木工实习' + description: 收集所有类型的原木。 type: onPlayer + requiredChallenges: + - start requiredItems: - - melon_slice:128;+8 - displayItem: melon - lockedDisplayItem: brown_stained_glass_pane + - oak_log:2;+2 + - spruce_log:2;+2 + - birch_log:2;+2 + - jungle_log:2;+2 + - acacia_log:2;+2 + - dark_oak_log:2;+2 + - stripped_oak_log:2;+2 + - stripped_spruce_log:2;+2 + - stripped_birch_log:2;+2 + - stripped_jungle_log:2;+2 + - stripped_acacia_log:2;+2 + - stripped_dark_oak_log:2;+2 + displayItem: oak_log reward: - text: 8 dirt + text: 4 redstone ore, 4 iron, cocoa and a mule items: - - dirt:8 - currency: 20 - xp: 20 + - cocoa_beans:1 + - redstone_ore:4 + - iron_ore:4 + - mule_spawn_egg:1 + currency: 30 + xp: 30 repeatReward: - text: 4 dirt + text: 1 redstone ore, 1 iron ore items: - - dirt:4 - currency: 10 - xp: 10 - cactusfarmer: - name: '&6Cactus Farmer' - description: Harvest cacti from a farm. + - redstone_ore:1 + - iron_ore:1 + - '{p=0.05}gold_ore:1' + currency: 15 + xp: 15 + stonepractice: + name: '&3碳酸钙 吗' + description: 烧制石头。 type: onPlayer + requiredChallenges: + - start requiredItems: - - cactus:64;+16 - displayItem: cactus - lockedDisplayItem: brown_stained_glass_pane + - stone:64 + displayItem: stone reward: - text: 8 sand, 20% chance to get a bone - items: - - sand:8 - - '{p=0.2}bone:1' - - '{p=0.1}wooden_hoe:1' - currency: 20 - xp: 20 - repeatReward: - text: 4 sand + text: 平滑石头x64 items: - - sand:4 - - '{p=0.1}bone:1' + - smooth_stone:64 + - '{p=0.2}redstone:4' currency: 10 xp: 10 - pumpkinfarmer: - name: '&6Pumpkin Farmer' - description: Harvest pumpkins from a farm. - type: onPlayer - requiredItems: - - pumpkin:64;+4 - displayItem: pumpkin - lockedDisplayItem: brown_stained_glass_pane - reward: - text: 8 dirt - items: - - dirt:8 - - '{p=0.3}jack_o_lantern:1' - currency: 20 - xp: 20 + commands: + - op:effect give {player} regeneration repeatReward: - text: 4 dirt + text: 平滑石头x64 items: - - dirt:4 - - '{p=0.05}jack_o_lantern:1' - currency: 10 - xp: 10 + - smooth_stone:64 + - '{p=0.2}redstone:1' + currency: 5 + xp: 5 stonebrickmaker: - name: '&aStone Brick Maker' - description: Make 64 Stone Bricks. + name: '&a工业进阶' + description: 制作64个石砖。 type: onPlayer + requiredChallenges: + - start requiredItems: - stone_bricks:64;+8 - stone_brick_slab:30;+6 - chiseled_stone_bricks:30;+6 - stone_brick_stairs:16;+4 displayItem: stone_bricks - lockedDisplayItem: gray_stained_glass_pane reward: - text: 4 redstone ore, 4 iron ore, 1 chicken + text: 4个红石矿石,4个铁矿石,1只鸡 items: - redstone_ore:4 - iron_ore:4 @@ -372,246 +295,276 @@ ranks: currency: 30 xp: 30 repeatReward: - text: 1 redstone ore, 1 iron ore + text: 1个红石矿石,1个铁矿石 items: - redstone_ore:1 - iron_ore:1 currency: 15 xp: 15 - novicebuilder: - name: '&7Novice Builder' - description: Reach island level 20. - type: islandLevel - requiredLevel: 20 + coalboss: + name: '&7煤老板上班' + description: 木炭x64。 displayItem: coal_ore + requiredChallenges: + - start + requiredItems: + - charcoal:64;+0 reward: - text: 8 dirt, 8 sand, 5 emeralds + text: 煤炭x64 items: - - dirt:8 - - sand:8 - - emerald:5 - - diamond:1 + - coal:64 currency: 20 xp: 20 commands: - op:effect give {party} regeneration - adeptbuilder: - name: '&aAdept Builder' - description: Reach island level 50. - type: islandLevel - requiredLevel: 50 - displayItem: iron_ore - offset: -1 + repeatReward: + text: 煤炭x64 + items: + - coal:64 + currency: 20 + xp: 20 + commands: + - op:effect give {party} regeneration + workerfinished: + name: '&a完成新手民工任务' + description: 完成新手民工任务。 + requiredChallenges: + - start + - cobblestonegenerator + - toolmaker + - woodpractice + - stonepractice + - stonebrickmaker + - coalboss + displayItem: crafting_table reward: - text: 10 obsidian, 2 diamonds, 5 emeralds + text: 2个钻石,5个绿宝石 items: - - obsidian:10 - emerald:5 - diamond:2 currency: 100 xp: 100 - expertbuilder: - name: '&eExpert Builder' - description: Reach island level 100. - type: islandLevel - requiredLevel: 100 - displayItem: gold_ore - offset: -1 - reward: - text: 16 dirt, 16 sand, 3 diamonds, 5 emeralds - items: - - dirt:16 - - sand:16 - - emerald:5 - - diamond:3 - currency: 250 - xp: 250 - masterbuilder: - name: '&cMaster Builder' - description: Reach island level 250. - type: islandLevel - requiredLevel: 250 - displayItem: diamond_ore - offset: -1 - reward: - text: 32 dirt, 32 sand, 4 diamonds, 5 emeralds - items: - - dirt:32 - - sand:32 - - emerald:5 - - diamond:4 - currency: 500 - xp: 500 - skylord: - name: '&4Sky Lord' - description: Reach island level 500. - type: islandLevel - requiredLevel: 500 - displayItem: emerald_ore - offset: -1 - reward: - text: 64 dirt, 64 sand, 5 diamond, 5 emeralds - items: - - dirt:64 - - sand:64 - - diamond:5 - - emerald:5 - currency: 1000 - xp: 1000 Tier2: - name: '&aAdept' - displayItem: lime_terracotta + name: '&a新手农民' + displayItem: wheat resetInHours: 20 requires: - - # means disabled in effect - rankLeeway: 99 challenges: - cobblestonegenerator - - novicebuilder challenges: - lumberjack: - name: '&3Lumberjack' - description: Collect all types of wood logs. + farmerpack: + name: '&e农场主礼包' + description: 开始你的农场主生涯吧。 type: onPlayer - requiredItems: - - oak_log:16;+2 - - spruce_log:16;+2 - - birch_log:16;+2 - - jungle_log:16;+2 - - acacia_log:16;+2 - - dark_oak_log:16;+2 - displayItem: oak_log - lockedDisplayItem: cyan_stained_glass_pane + displayItem: wheat reward: - text: 4 redstone ore, 4 iron ore, 1 wolf + text: 农场主礼包x1 items: - - redstone_ore:4 - - iron_ore:4 - - wolf_spawn_egg:1 + - cactus:1 + - pumpkin:1 + - sugar_cane:1 + - melon_seeds:1 + - beetroot_seeds:1 + - brown_mushroom:1 + - red_mushroom:1 + - carrot:1 + - potato:1 currency: 30 xp: 30 repeatReward: - text: 1 redstone ore, 1 iron ore + text: '' items: - - redstone_ore:1 - - iron_ore:1 - currency: 15 - xp: 15 - shroompicker: - name: '&6Shroom Picker' - description: Collect red and brown mushrooms. + currency: 0 + xp: 0 + cactusfarmer: + name: '&6仙人掌君' + description: 开始生产仙人掌 type: onPlayer + requiredChallenges: + - farmerpack requiredItems: - - brown_mushroom:64;+4 - - red_mushroom:64;+4 - displayItem: red_mushroom + - cactus:64;+16 + displayItem: cactus lockedDisplayItem: brown_stained_glass_pane reward: - text: 8 mycelium, 4 podzol + text: 8个沙子, 20%几率获得骨头 items: - - mycelium:8 - - podzol:4 - currency: 30 - xp: 30 + - sand:8 + - '{p=0.2}bone:1' + - '{p=0.1}wooden_hoe:1' + currency: 20 + xp: 20 repeatReward: - text: 4 mycelium + text: 4个沙子 items: - - mycelium:4 - - '{p=0.02}mycelium:4' - - '{p=0.02}podzol:2' - currency: 15 - xp: 15 - potatofarmer: - name: '&6Potato Farmer' - description: Harvest potato's from a farm. + - sand:4 + - '{p=0.1}bone:1' + currency: 10 + xp: 10 + pumpkinfarmer: + name: '&6吓人的南瓜' + description: 种植南瓜 type: onPlayer + requiredChallenges: + - farmerpack requiredItems: - - potato:64;+16 - displayItem: potato + - pumpkin:64;+4 + displayItem: pumpkin lockedDisplayItem: brown_stained_glass_pane reward: - text: 1 carrot, 4 dirt + text: 8个泥土 items: - - carrot:1 - - dirt:4 - - '{p=0.10}beetroot_seeds:1' - - '{p=0.05}red_sand:1' - - '{p=0.01}diamond:1' - currency: 50 - xp: 50 + - dirt:8 + - '{p=0.3}jack_o_lantern:1' + currency: 20 + xp: 20 repeatReward: - text: 4 dirt and a baked potato + text: 4个泥土 items: - dirt:4 - - baked_potato:1 - - '{p=0.10}beetroot_seeds:1' - - '{p=0.05}red_sand:1' - - '{p=0.01}diamond:1' - currency: 25 - xp: 25 - carrotfarmer: - name: '&6Carrot Farmer' - description: Harvest carrot's from a farm. + - '{p=0.05}jack_o_lantern:1' + currency: 10 + xp: 10 + sugarfarmer: + name: '&6来点甘蔗' + description: 从农场收获甘蔗并做成糖和纸。 type: onPlayer + requiredChallenges: + - farmerpack requiredItems: - - carrot:64;+16 + - paper:32;+4 + - sugar:32;+4 displayItem: carrot lockedDisplayItem: brown_stained_glass_pane reward: - text: a pig with saddle and a potato + text: 1只猪 items: - - saddle:1 - - potato:1 - pig_spawn_egg:1 - '{p=0.05}red_sand:1' - '{p=0.01}diamond:1' currency: 50 xp: 50 repeatReward: - text: 1 golden carrot + text: 1个金胡萝卜 items: - golden_carrot:1 - '{p=0.05}sand:1' - '{p=0.01}diamond:1' currency: 25 xp: 25 - wheatfarmer: - name: '&6Wheat Farmer' - description: Harvest wheat from a farm. + shroompicker: + name: '&6发配云南' + description: 收集64个红蘑菇和棕蘑菇. type: onPlayer + requiredChallenges: + - farmerpack requiredItems: - - wheat:64;+16 - displayItem: wheat + - brown_mushroom:64;+4 + - red_mushroom:64;+4 + displayItem: red_mushroom lockedDisplayItem: brown_stained_glass_pane reward: - text: 8 dirt + text: 8 菌丝, 4 灰化土 items: - - dirt:8 - currency: 20 - xp: 20 + - mycelium:8 + - podzol:4 + currency: 30 + xp: 30 repeatReward: - text: 4 dirt + text: 4 菌丝 items: - - dirt:4 - - '{p=0.2}bone:1' - currency: 10 - xp: 10 - monsterfarm: - name: '&5Monster Farm' - description: Build a mob farm and collect mob loot. + - mycelium:4 + - '{p=0.02}mycelium:4' + - '{p=0.02}podzol:2' + currency: 15 + xp: 15 + melonfarmer: + name: '&6夏天来块西瓜吧' + description: 种植西瓜 + type: onPlayer + requiredChallenges: + - farmerpack + requiredItems: + - melon_slice:128;+8 + displayItem: melon + lockedDisplayItem: brown_stained_glass_pane + reward: + text: 8个泥土 + items: + - dirt:8 + currency: 20 + xp: 20 + repeatReward: + text: 4个泥土 + items: + - dirt:4 + currency: 10 + xp: 10 + beetrootfarmer: + name: '&6甜菜根' + description: 种植甜菜根 + type: onPlayer + requiredChallenges: + - farmerpack + requiredItems: + - beetroot:64;+16 + displayItem: beetroot + lockedDisplayItem: brown_stained_glass_pane + reward: + text: 8个泥土 + items: + - dirt:8 + currency: 20 + xp: 20 + repeatReward: + text: 4个泥土 + items: + - dirt:4 + - '{p=0.2}bone:1' + currency: 10 + xp: 10 + farmerfinished: + name: '&a完成新手农场主任务' + description: 完成新手农场主任务。 + requiredChallenges: + - start + - cobblestonegenerator + - farmerpack + - cactusfarmer + - pumpkinfarmer + - sugarfarmer + - shroompicker + - melonfarmer + - beetrootfarmer + displayItem: crafting_table + reward: + text: 2个钻石,5个绿宝石 + items: + - emerald:5 + - diamond:2 + currency: 100 + xp: 100 + Tier3: + name: '&e新手怪物猎人' + displayItem: iron_sword + resetInHours: 20 + requires: + challenges: + - start + - cobblestonegenerator + challenges: + monsterfarm: + name: '&5怪物猎人' + description: 开始猎杀怪物 type: onPlayer requiredItems: - - rotten_flesh:64;+4 - - string:32;+2 - - arrow:32;+2 + - rotten_flesh:32;+4 - bone:32;+2 - - gunpowder:16;+1 - - spider_eye:5 + - gunpowder:32;+1 displayItem: rotten_flesh lockedDisplayItem: purple_stained_glass_pane reward: - text: 4 redstone ore, 4 iron ore and 1 flint + text: 4个红石矿, 4个铁矿, 1个燧石 items: - redstone_ore:4 - iron_ore:4 @@ -621,215 +574,182 @@ ranks: currency: 30 xp: 30 repeatReward: - text: 1 redstone ore, 1 iron ore, 1 flint + text: 1个红石矿, 1个铁矿, 1个燧石 items: - redstone_ore:1 - - iron_ore:4 + - iron_ore:1 - flint:1 - '{p=0.05}potato:1' - '{p=0.05}carrot:1' currency: 15 xp: 15 - homeowner: - name: '&9Home Owner' - description: Build a house with furnishings. - type: onIsland - requiredChallenges: - - stonebrickmaker - requiredBlocks: - - red_bed:1 - - crafting_table:1 - - glass:1 - - oak_door:1 - - furnace:1 - - bookshelf:1 - - torch:1 - lockedDisplayItem: blue_stained_glass_pane - displayItem: oak_door - reward: - text: 4 redstone ore, 5 inksac, 4 iron ore and some seeds + witchfarm: + name: '&5女巫猎人' + description: 猎杀女巫 + type: onPlayer + requiredItems: + - glass_bottle:5;+5 + - glowstone_dust:5;+5 + - redstone:5;+5 + displayItem: glowstone_dust + reward: + text: 4个沙子, 4个铁矿 items: - - redstone_ore:4 + - sand:4 - iron_ore:4 - - ink_sac:5 - - beetroot_seeds:1 - currency: 40 - xp: 40 - netherportal: - name: '&9Nether Portal' - description: Build a nether portal on your island. - type: onIsland - requiredChallenges: - - adeptbuilder - - homeowner - requiredBlocks: - - obsidian:10 - - nether_portal:1 - displayItem: obsidian - lockedDisplayItem: blue_stained_glass_pane - reward: - text: 1 iron pickaxe, 1 iron shovel + - '{p=0.10}potato:1' + - '{p=0.10}carrot:1' + currency: 30 + xp: 30 + repeatReward: + text: 2个沙子, 2个铁矿 items: - - iron_shovel:1 - - iron_pickaxe:1 - currency: 40 - xp: 40 - nethermining: - name: '&7Nether Mining' - description: Mine from your Nether island. + - sand:4 + - iron_ore:2 + - '{p=0.05}potato:1' + - '{p=0.05}carrot:1' + currency: 15 + xp: 15 + slimefarm: + name: '&5史莱姆农场' + description: 收集史莱姆球 type: onPlayer - displayItem: netherrack - lockedDisplayItem: gray_stained_glass_pane - offset: -1 requiredItems: - - netherrack:64;+2 - - soul_sand:16;+2 - - gravel:16;+2 - - quartz:32;+2 - - glowstone:16;+2 - - coarse_dirt:4;+2 + - slime_ball:64;+4 + displayItem: slime_ball + lockedDisplayItem: purple_stained_glass_pane reward: - text: 1 ghast tear, 1 magic bow + text: 4个红石矿,1个绿宝石 items: - - ghast_tear:1 - - 'bow[enchantments={levels:{infinity:1,unbreaking:3}}]:1' - currency: 40 - xp: 40 + - redstone_ore:4 + - emerald:1 + currency: 70 + xp: 70 repeatReward: - text: a blaze-rod and a chance of ghast tear + text: 8个红石 items: - - blaze_rod:1 - - '{p=0.10}ghast_tear:1' - currency: 20 - xp: 20 - Tier3: - name: '&eExpert' - displayItem: yellow_terracotta - resetInHours: 20 - requires: - challenges: - - adeptbuilder - challenges: - toolmaker: - name: '&3Tool Maker' - description: Make all stone tools + - redstone:8 + currency: 35 + xp: 35 + woolwhere: + name: '&5无中生有' + description: 收集羊毛 type: onPlayer - requiredChallenges: - - lumberjack + displayItem: white_wool + lockedDisplayItem: purple_stained_glass_pane requiredItems: - - stone_shovel:1 - - stone_pickaxe:1 - - stone_axe:1 - - stone_hoe:1 - displayItem: stone_axe - lockedDisplayItem: cyan_stained_glass_pane + - white_wool:5;+5 reward: - text: 4 redstone ore, 4 iron, 1 pig + text: 2个钻石,2只羊 items: - - redstone_ore:4 - - iron_ore:4 - - pig_spawn_egg:1 - currency: 30 - xp: 30 - sawmill: - name: '&3Saw Mill' - description: Deliver a collection of processed wood + - diamond:2 + - sheep_spawn_egg:2 + currency: 70 + xp: 70 + repeatReward: + text: 一个绿宝石 + items: + - emerald:1 + currency: 10 + xp: 10 + goldwhere: + name: '&5土法炼金' + description: 想办法制作3个金锭 type: onPlayer - offset: -1 - requiredChallenges: - - toolmaker + displayItem: gold_ingot + lockedDisplayItem: purple_stained_glass_pane requiredItems: - - stripped_oak_log:8;+2 - - stripped_spruce_log:8;+2 - - stripped_birch_log:8;+2 - - stripped_jungle_log:8;+2 - - stripped_acacia_log:8;+2 - - stripped_dark_oak_log:8;+2 - displayItem: iron_axe - lockedDisplayItem: cyan_stained_glass_pane + - gold_ingot:3 reward: - text: 4 redstone ore, 4 iron, cocoa and a mule + text: 4个钻石 items: - - cocoa_beans:1 - - redstone_ore:4 - - iron_ore:4 - - mule_spawn_egg:1 - currency: 30 - xp: 30 + - diamond:4 + currency: 70 + xp: 70 repeatReward: - text: 1 redstone ore, 1 iron ore - items: - - redstone_ore:1 - - iron_ore:1 - - '{p=0.05}gold_ore:1' - currency: 15 - xp: 15 - torchmaker: - name: '&3Torch Maker' - description: Make 128 torches. + text: 10 coin + currency: 10 + xp: 10 + itemfarm: + name: '&5神射手' + description: 获得一些箭 type: onPlayer - requiredChallenges: - - lumberjack requiredItems: - - torch:128;+32 - displayItem: torch - lockedDisplayItem: cyan_stained_glass_pane + - arrow:128;+32 + displayItem: arrow + lockedDisplayItem: purple_stained_glass_pane reward: - text: 4 redstone ore, 4 iron ore + text: 4个红石矿, 4个铁矿, 1个燧石 items: - redstone_ore:4 - iron_ore:4 + - flint:1 + - '{p=0.10}potato:1' + - '{p=0.10}carrot:1' currency: 30 xp: 30 repeatReward: - text: 1 redstone ore, 1 iron ore + text: 2个沙子 items: - - redstone_ore:1 - - iron_ore:1 + - sand:2 + - '{p=0.05}potato:1' + - '{p=0.05}carrot:1' currency: 15 xp: 15 - expertfarmer: - name: '&6Expert Farmer' - description: Harvest many different farming resources. + killenderman1: + name: '&5杀点小黑 之一' + description: 收集末影珍珠. type: onPlayer - displayItem: iron_hoe - lockedDisplayItem: brown_stained_glass_pane - requiredChallenges: - - applecollector - - melonfarmer - - pumpkinfarmer - - wheatfarmer - - carrotfarmer - - potatofarmer - - cactusfarmer - - sugarfarmer - - shroompicker requiredItems: - - melon_slice:256;+2 - - sugar_cane:128;+2 - - wheat:128;+2 - - potato:128;+2 - - carrot:128;+2 - - pumpkin:128;+2 - - cactus:128;+2 - - beetroot:128;+2 + - ender_pearl:10;+4 + displayItem: ender_pearl + lockedDisplayItem: purple_stained_glass_pane reward: - text: a bucket, cocoa and a cow + text: 5个金矿 1个绿宝石 items: - - bucket:1 - - cocoa_beans:1 - - cow_spawn_egg:1 - currency: 50 - xp: 50 + - gold_ore:5 + - emerald:1 + currency: 70 + xp: 70 repeatReward: - text: a cow + text: 1个金矿 items: - - cow_spawn_egg:1 - currency: 25 - xp: 25 + - gold_ore:1 + currency: 35 + xp: 35 + mhfinished: + name: '&a完成新手怪物猎人任务' + description: 完成新手怪物猎人任务。 + requiredChallenges: + - start + - cobblestonegenerator + - monsterfarm + - witchfarm + - slimefarm + - woolwhere + - goldwhere + - killenderman1 + - itemfarm + displayItem: crafting_table + reward: + text: 2个钻石,5个绿宝石 + items: + - emerald:5 + - diamond:2 + currency: 100 + xp: 100 + Tier4: + name: '&e新手牧场主' + displayItem: lead + resetInHours: 20 + requires: + challenges: + - start + - cobblestonegenerator + challenges: fisherman: - name: '&5Fisherman' - description: Catch different types of fish. + name: '&1小鱼干' + description: 钓鱼走起. type: onPlayer requiredItems: - cod:5;+1 @@ -837,20 +757,20 @@ ranks: - pufferfish:3;+1 - tropical_fish:1;+0 - ink_sac:5;+2 + repeatable: true displayItem: cod - lockedDisplayItem: purple_stained_glass_pane + takeItems: true reward: - text: 10 lapis blocks, 20 prismarine, kelp, deep-ocean + text: 一些海产 好好利用哦 items: - lapis_block:10 - prismarine:20 - kelp:1 - '{p=0.20}prismarine_crystals:5' - permission: usb.biome.deep_ocean currency: 50 xp: 50 repeatReward: - text: 2 lapis, 5 prismarine, kelp and a chance at prismarine crystals + text: 一些海产 好好利用哦 items: - lapis_lazuli:2 - prismarine:5 @@ -858,154 +778,420 @@ ranks: - '{p=0.20}prismarine_crystals:5' currency: 25 xp: 25 - woolcollector: - name: '&5Wool Collector' - description: Collect every color of wool. + grasslover: + name: '&e穷到吃草' + description: 爱吃 type: onPlayer - displayItem: white_wool - lockedDisplayItem: purple_stained_glass_pane - requiredChallenges: - - monsterfarmer - requiredItems: - - white_wool:2;+4 - - orange_wool:2;+4 - - magenta_wool:2;+4 - - light_blue_wool:2;+4 - - yellow_wool:2;+4 - - lime_wool:2;+4 - - pink_wool:2;+4 - - gray_wool:2;+4 - - light_gray_wool:2;+4 - - cyan_wool:2;+4 - - purple_wool:2;+4 - - blue_wool:2;+4 - - brown_wool:2;+4 - - green_wool:2;+4 - - red_wool:2;+4 - - black_wool:2;+4 - reward: - text: 2 diamonds, sheep, emerald, flowers + requiredItems: + - short_grass:32;+16 + - grass_block:4;+1 + - seagrass:32;+16 + displayItem: grass_block + lockedDisplayItem: yellow_stained_glass_pane + reward: + text: 4红石矿,5粘土,2牛 items: - - diamond:2 - - emerald:1 - - sheep_spawn_egg:1 - - rose_bush:1 - - peony:1 - permission: usb.biome.flower_forest - currency: 70 - xp: 70 + - redstone_ore:4 + - clay:5 + - cow_spawn_egg:2 + currency: 80 + xp: 80 repeatReward: - text: emerald, sheep, flowers + text: 铁矿,黏土 items: - - emerald:1 - - sheep_spawn_egg:1 - - rose_bush:1 - - peony:1 - currency: 35 - xp: 35 - maestro: - name: '&5Maestro' - description: Make a jukebox and collect all music discs. - type: onPlayer - requiredItems: - - music_disc_13:1 - - music_disc_cat:1 - - music_disc_blocks:1 - - music_disc_chirp:1 - - music_disc_far:1 - - music_disc_mall:1 - - music_disc_mellohi:1 - - music_disc_stal:1 - - music_disc_strad:1 - - music_disc_ward:1 - - music_disc_11:1 - - music_disc_wait:1 - - jukebox:1 - displayItem: jukebox - lockedDisplayItem: purple_stained_glass_pane + - iron_ore:1 + - clay:5 + - bucket:1 + - '{p=0.4}clay:3' + - '{p=0.3}iron_ore:2' + currency: 40 + xp: 40 + killcow: + name: '&e大屠杀时刻' + description: 通过牧场养殖 获得一定量的皮革. + type: onPlayer + requiredItems: + - leather:96;+32 + displayItem: leather + lockedDisplayItem: yellow_stained_glass_pane reward: - text: 3 diamonds, 1 gold block, 10 emeralds + text: 2只猪 1个鞍 items: - - diamond:3 - - emerald:10 - - gold_block:1 - currency: 70 - xp: 70 + - saddle:1 + - pig_spawn_egg:2 + currency: 80 + xp: 80 repeatReward: - text: 2 gold ore, 1 diamond + text: 1个鞍 items: - - gold_ore:2 - - diamond:1 - - '{p=0.2}diamond:1' - currency: 35 - xp: 35 - ironfarm: - name: '&9Iron Farm' - description: Build an iron-farm. - type: onIsland - displayItem: iron_block - lockedDisplayItem: blue_stained_glass_pane - radius: 50 - requiredChallenges: - - monsterfarm - - applecollector - requiredBlocks: - - oak_door:30 - requiredEntities: - - Villager:10 - - IRON_GOLEM:1 - reward: - text: 2 gold blocks, 1 diamond block - items: - - gold_block:2 - - diamond_block:1 - currency: 500 - xp: 400 - netherfortress: - name: '&9Nether Fortress' - description: Build a netherfortress. - type: onIsland - radius: 50 - requiredBlocks: - - nether_bricks:512 - - nether_brick_slab:64 - - nether_brick_fence:64 - - nether_brick_stairs:64 - - soul_sand:32 - displayItem: nether_brick_fence - lockedDisplayItem: blue_stained_glass_pane - reward: - text: a wither-skull and a blaze rod and a chance of diamonds - items: - - wither_skeleton_skull:1 - - blaze_rod:1 - - '{p=0.05}diamond:3' - - '{p=0.10}diamond:2' - - '{p=0.15}diamond:1' + - saddle:1 + - '{p=0.3}iron_ore:2' currency: 40 xp: 40 - # =============================================== - Tier4: - name: '&cMaster' - displayItem: orange_terracotta - resetInHours: 20 - requires: - - # disabled - rankLeeway: 99 - challenges: - - expertbuilder - challenges: - glassmaker: - name: '&3Glassmaker' - description: Collect every color of stained glass. + horsingaround: + name: '&1准备养马' + description: 给马准备点干草,栓绳,胡萝卜钓竿,剪刀. type: onPlayer - displayItem: white_stained_glass - lockedDisplayItem: cyan_stained_glass_pane requiredItems: - - white_stained_glass:8;+2 - - orange_stained_glass:8;+2 - - magenta_stained_glass:8;+2 + - wheat:32;+4 + - lead:8;+2 + - carrot_on_a_stick:1 + - shears:1 + repeatable: true + displayItem: wheat + takeItems: true + reward: + text: 1 匹小马驹, 1 个铁马铠, 5% 的机会获得一个钻石马铠 + items: + - horse_spawn_egg:1 + - iron_horse_armor:1 + - '{p=0.05}diamond_horse_armor:1' + currency: 50 + xp: 50 + repeatReward: + text: 1 个红石矿, 1 绿宝石 + items: + - redstone_ore:1 + - emerald:1 + currency: 25 + xp: 25 + fisherydevelopment: + name: '&1发展渔业' + description: 小池塘 大世界. + type: onPlayer + requiredItems: + - lily_pad:16;+8 + - fishing_rod:1 + - kelp:64;+16 + repeatable: true + displayItem: fishing_rod + takeItems: true + reward: + text: 1 个青金石矿, 1 个萤石 + items: + - lapis_ore:1 + - glowstone:1 + currency: 20 + xp: 20 + repeatReward: + text: 1 个青金石矿, 2 个萤石粉 + items: + - lapis_ore:1 + - glowstone_dust:2 + currency: 20 + xp: 20 + tacticalfishing: + name: '&1战术性钓鱼' + description: 我该干什么. + type: onPlayer + requiredItems: + - cod_bucket:1 + - salmon_bucket:1 + - tropical_fish_bucket:1 + - pufferfish_bucket:1 + repeatable: true + displayItem: cod_bucket + takeItems: true + reward: + text: 1 个钻石矿, 1 个萤石 + items: + - diamond_ore:1 + - glowstone:1 + currency: 20 + xp: 20 + repeatReward: + text: 1 个青金石块, 2 个萤石粉 + items: + - lapis_block:1 + - glowstone_dust:2 + currency: 20 + xp: 20 + beautifulegg: + name: '&1鸡你太美' + description: 鸡蛋 + type: onPlayer + requiredItems: + - egg:64;+32 + repeatable: true + displayItem: egg + takeItems: true + reward: + text: 1 个金胡萝卜, 1 个CD, 4 个绿宝石 + items: + - golden_carrot:1 + - music_disc_cat:1 + - emerald:4 + currency: 60 + xp: 60 + repeatReward: + text: 20% 的机会获得一个CD, 1 个绿宝石 + items: + - emerald:1 + - '{p=0.2}music_disc_wait:1' + currency: 30 + xp: 30 + rancherfinished: + name: '&a完成新手牧场主任务' + description: 完成新手牧场主任务。 + requiredChallenges: + - start + - cobblestonegenerator + - fisherman + - grasslover + - killcow + - horsingaround + - fisherydevelopment + - tacticalfishing + - beautifulegg + displayItem: crafting_table + reward: + text: 2个钻石,5个绿宝石 + items: + - emerald:5 + - diamond:2 + currency: 100 + xp: 100 + Tier5: + name: '&e新手建筑师' + displayItem: smooth_stone + resetInHours: 20 + requires: + challenges: + - start + - cobblestonegenerator + challenges: + builderstart: + name: '&7开始铺地' + description: 达到10级. + type: islandLevel + requiredLevel: 20 + displayItem: coal_ore + reward: + text: 8个泥土 8个沙子 5个绿宝石 1个钻石 + items: + - dirt:8 + - sand:8 + - emerald:5 + - diamond:1 + currency: 20 + xp: 20 + commands: + - op:effect give {party} regeneration + builder1: + name: '&7铺地新手' + description: 达到20级. + type: islandLevel + requiredLevel: 20 + displayItem: coal_ore + reward: + text: 8个泥土 8个沙子 5个绿宝石 1个钻石 + items: + - dirt:8 + - sand:8 + - emerald:5 + - diamond:1 + currency: 20 + xp: 20 + commands: + - op:effect give {party} regeneration + builder2: + name: '&a铺地菜鸡' + description: 达到50级. + type: islandLevel + requiredLevel: 50 + displayItem: iron_ore + reward: + text: 2个钻石 5个绿宝石 + items: + - emerald:5 + - diamond:2 + currency: 100 + xp: 100 + builder3: + name: '&e入门铺地员' + description: 达到100级. + type: islandLevel + requiredLevel: 100 + displayItem: gold_ore + reward: + text: 16个泥土 16个沙子 5个绿宝石 3个钻石 + items: + - dirt:16 + - sand:16 + - emerald:5 + - diamond:3 + currency: 250 + xp: 250 + builder4: + name: '&e苦逼铺地员' + description: 达到150级. + type: islandLevel + requiredLevel: 100 + displayItem: gold_ore + reward: + text: 16个泥土 16个沙子 5个绿宝石 3个钻石 + items: + - dirt:16 + - sand:16 + - emerald:5 + - diamond:3 + currency: 250 + xp: 250 + builder5: + name: '&e200级!' + description: 达到200级. + type: islandLevel + requiredLevel: 100 + displayItem: gold_ore + reward: + text: 16个泥土 16个沙子 5个绿宝石 3个钻石 + items: + - dirt:16 + - sand:16 + - emerald:5 + - diamond:3 + currency: 250 + xp: 250 + builderfinished: + name: '&a完成新手建筑师任务' + description: 完成新手建筑师任务。 + requiredChallenges: + - start + - cobblestonegenerator + - builderstart + - builder1 + - builder2 + - builder3 + - builder4 + - builder5 + displayItem: crafting_table + reward: + text: 2个钻石,5个绿宝石 + items: + - emerald:5 + - diamond:2 + currency: 100 + xp: 100 + page1finished: + name: '&a完成新手任务' + description: 完成新手任务,解锁地狱,解锁trust功能,解锁新内容。 + requiredChallenges: + - start + - cobblestonegenerator + - workerfinished + - farmerfinished + - mhfinished + - rancherfinished + - builderfinished + displayItem: crafting_table + reward: + text: 30个钻石,50个绿宝石,前往下界 + items: + - emerald:50 + - diamond:30 + - obsidian:10 + currency: 100 + xp: 100 + Tier6: + name: '&e入门民工' + displayItem: iron_axe + resetInHours: 20 + requires: + challenges: + - page1finished + challenges: + netherportal: + name: '&4想要更深入些' + description: 建造一座地狱门并点燃它. + type: onIsland + requiredItems: + - obsidian:10 + repeatable: false + displayItem: obsidian + takeItems: false + reward: + text: 1 个铁镐, 1 铁锹 + items: + - iron_pickaxe:1 + - iron_shovel:1 + currency: 40 + xp: 40 + mobfarm1: + name: '&5造一个主世界刷怪塔!' + description: 更快!更高!更大! + type: onPlayer + requiredChallenges: + - netherportal + requiredItems: + - rotten_flesh:128;+64 + - bone:128;+64 + - gunpowder:128;+64 + - spider_eye:128;+64 + displayItem: rotten_flesh + lockedDisplayItem: purple_stained_glass_pane + reward: + text: 4个红石块, 4个铁块, 4个金块 + items: + - redstone_block:4 + - iron_block:4 + - gold_block:4 + - '{p=0.10}potato:1' + - '{p=0.10}carrot:1' + currency: 30 + xp: 30 + repeatReward: + text: 1个红石块, 1个铁块 + items: + - redstone_block:1 + - iron_block:1 + - '{p=0.05}potato:1' + - '{p=0.05}carrot:1' + currency: 15 + xp: 15 + mobfarm2: + name: '&5造一个地狱刷怪塔!' + description: 更快!更高!更大! + type: onPlayer + requiredChallenges: + - netherportal + requiredItems: + - blaze_rod:128;+64 + - gold_ingot:128;+64 + - rotten_flesh:128;+64 + - ghast_tear:8;+4 + displayItem: blaze_powder + lockedDisplayItem: purple_stained_glass_pane + reward: + text: 4个红石块, 4个铁块, 4个金块 + items: + - redstone_block:4 + - iron_block:4 + - gold_block:4 + - '{p=0.10}potato:1' + - '{p=0.10}carrot:1' + currency: 30 + xp: 30 + repeatReward: + text: 1个红石块, 1个铁块 + items: + - redstone_block:1 + - iron_block:1 + - '{p=0.05}potato:1' + - '{p=0.05}carrot:1' + currency: 15 + xp: 15 + glassmaker: + name: '&3玻璃心' + description: 获得所有品种玻璃. + type: onPlayer + requiredChallenges: + - netherportal + displayItem: white_stained_glass + lockedDisplayItem: cyan_stained_glass_pane + requiredItems: + - white_stained_glass:8;+2 + - orange_stained_glass:8;+2 + - magenta_stained_glass:8;+2 - light_blue_stained_glass:8;+2 - yellow_stained_glass:8;+2 - lime_stained_glass:8;+2 @@ -1020,7 +1206,7 @@ ranks: - red_stained_glass:8;+2 - black_stained_glass:8;+2 reward: - text: 2 diamonds, 2 disks, 1 emeralds + text: 2个钻石 2个唱片 1个绿宝石 items: - diamond:2 - music_disc_ward:1 @@ -1028,11 +1214,10 @@ ranks: - emerald:1 - sunflower:1 - lilac:1 - permission: usb.biome.flower_forest currency: 70 xp: 70 repeatReward: - text: 30% chance on 1 or 2 disks, 1 emeralds + text: 1个绿宝石 items: - '{p=0.3}music_disc_ward:1' - '{p=0.3}music_disc_11:1' @@ -1041,63 +1226,925 @@ ranks: - lilac:1 currency: 35 xp: 35 - carpenter: - name: '&3Carpenter' - description: Collect all types of wood items. - type: onPlayer - requiredItems: - - jungle_stairs:16;+8 - - spruce_door:1;+1 - - dark_oak_fence_gate:2;+2 - - acacia_fence:30;+15 - - ladder:30;+15 - - oak_trapdoor:4;+2 - - oak_pressure_plate:2;+1 + duplicarpet: + name: '&3我爱地毯' + description: 获得所有品种地毯。 + type: onPlayer requiredChallenges: - - toolmaker - - lumberjack:2 - displayItem: crafting_table + - netherportal + displayItem: white_carpet lockedDisplayItem: cyan_stained_glass_pane + requiredItems: + - white_carpet:8;+2 + - orange_carpet:8;+2 + - magenta_carpet:8;+2 + - light_blue_carpet:8;+2 + - yellow_carpet:8;+2 + - lime_carpet:8;+2 + - pink_carpet:8;+2 + - gray_carpet:8;+2 + - light_gray_carpet:8;+2 + - cyan_carpet:8;+2 + - purple_carpet:8;+2 + - blue_carpet:8;+2 + - brown_carpet:8;+2 + - green_carpet:8;+2 + - red_carpet:8;+2 + - black_carpet:8;+2 + reward: + text: 2个钻石 2个唱片 1个管珊瑚扇 + items: + - diamond:2 + - music_disc_creator:1 + - music_disc_13:1 + - tube_coral_fan:1 + - sunflower:1 + - lilac:1 + currency: 70 + xp: 70 + repeatReward: + text: 1个绿宝石 1个管珊瑚扇 + items: + - '{p=0.3}music_disc_creator:1' + - '{p=0.3}music_disc_13:1' + - emerald:1 + - sunflower:1 + - lilac:1 + - tube_coral_fan:1 + currency: 35 + xp: 35 + netherrack: + name: '&7地狱矿工' + description: 收集地狱岩 tips:挖地狱岩有概率产生新的 + type: onPlayer + requiredChallenges: + - netherportal + requiredItems: + - netherrack:64;+32 + - soul_sand:64;+32 + - glowstone:32;+32 + - gravel:32;+16 + - quartz:32;+16 + displayItem: netherrack + resetInHours: 12 + reward: + text: 1 把弓, 32 只箭,1 个可可豆 + items: + - bow:1 + - arrow:32 + - cocoa_beans:1 + currency: 40 + xp: 40 + commands: + - op:effect give {player} regeneration + repeatReward: + text: 64 只箭 + items: + - arrow:64 + currency: 20 + xp: 20 + hellfire: + name: '&4地狱火' + description: 火炎焱. + requiredChallenges: + - netherportal + type: onPlayer + requiredItems: + - blaze_rod:16;+8 + - water_bucket:32;+16 + repeatable: true + displayItem: blaze_rod + takeItems: true + reward: + text: 1 个漏斗, 2 个雪块 + items: + - hopper:1 + - snow_block:2 + currency: 20 + xp: 20 + repeatReward: + text: 1 个雪块 + items: + - snow_block:1 + currency: 20 + xp: 20 + adeptworkerfinished: + name: '&a完成入门民工任务' + description: 完成入门民工任务。 + requiredChallenges: + - mobfarm1 + - mobfarm2 + - glassmaker + - duplicarpet + - netherrack + - hellfire + displayItem: crafting_table + reward: + text: 2个钻石,5个绿宝石 + items: + - emerald:5 + - diamond:2 + currency: 100 + xp: 100# ====================================================================================================================== +# +# 以下是挑战组和挑战设置的格式说明。单行注释(#)表示最常用的设置,双行注释(##)表示可选设置。 +# +# 物品类型格式: +# 物品类型按照Minecraft的/give命令格式定义,即使用Minecraft键名和方括号内的可选组件。 +# 例如: 'minecraft:diamond_sword[damage=42]'。参考Minecraft维基获取更多信息: +# https://minecraft.wiki/w/Data_component_format。也可以使用命令'/usb iteminfo'获取物品信息。 +# +# 根据使用场景不同,物品类型会补充额外信息: +# +# 展示物品(display-item): <类型> +# 用于在GUI中显示的物品。仅定义物品类型,不包含额外信息。 +# 例如: 'cobblestone', 'minecraft:stone', 'diamond_sword[damage=42]'。 +# +# 物品需求(item-requirement): <类型>:<数量>[;+<增量>] +# 挑战所需的物品。数量是需要的物品数。可选的+<数量>表示每次重复挑战时需要增加的物品数。 +# 例如: 'cobblestone:64;+16'表示第一次完成需要64圆石,第二次需要80圆石。 +# 其他操作符包括-、*、/,分别表示减法、乘法和除法。 +# 例如: 'cobblestone:64;*2'表示第一次64,第二次128,第三次256。 +# +# 物品奖励(item-reward): [{p=<概率>}]<类型>:<数量> +# 挑战完成的物品奖励。数量是给予的物品数。可选的{p=<概率>}表示给予该物品的概率。 +# 例如: 'cobblestone:64'每次完成都会给予64圆石,而'{p=0.1}cobblestone:64'只有10%概率给予。 +# +# ====================================================================================================================== +# 所有挑战都在ranks部分定义。每个rank代表玩家可以完成的一个挑战层级。 +# +# ranks: +# # [文本] 挑战等级的名称 +# TierX: +# # [文本] 执行/challenges时显示的挑战等级名称(支持大写和颜色代码) +# name: '&a自定义挑战等级名称' +# # [展示物品] 在挑战菜单中用于表示已完成挑战的物品 +# displayItem: '青色陶瓦' +# # [整数] 物品需求重置为默认值的小时数(覆盖主重置时间) +# resetInHours: 20 +# # 这些要求控制挑战组何时对玩家可用 +# requires: +# # [整数] 允许未完成任务数的容差值。例如如果有4个挑战且rankLeeway为1, +# # 玩家只需完成3个即可进入下一等级。设为0则需要全部完成。 +# rankLeeway: 8 +# # [文本列表] 必须先完成这些挑战才能解锁本组挑战 +# challenges: +# - 挑战名称 +# ====================================================================================================================== +# challenges: +# # [文本] 挑战名称。所有挑战名称应为小写 +# defaultchallenge: +# # [文本] 在/challenges中显示的挑战名称(支持大写和颜色代码) +# name: '&a默认挑战' +# # [文本] 玩家执行/challenges <挑战名称>时看到的描述 +# description: +# # [onIsland/onPlayer/islandLevel] 定义所需方块/物品应在玩家物品栏中还是岛上。 +# # 使用onIsland时,玩家必须距离岛上所需方块10格范围内。 +# # 使用islandLevel时,'requiredItems'字段应为所需的岛屿等级。 +# # 玩家必须先使用/island level更新等级。 +# type: onPlayer +# ## type: islandLevel +# ## type: onIsland +# # [整数] 使用onIsland时覆盖默认的10格半径 +# ## radius: 20 +# # [物品需求列表] 完成挑战所需的物品 +# requiredItems: +# - stone:64;+16 +# - cobblestone:64;+16 +# # [方块需求列表] 完成挑战所需的方块 +# requiredBlocks: +# - stone:64 +# - cobblestone:64 +# # [true/false] 挑战是否可重复完成 +# ## repeatable: true +# # [整数] 挑战可完成的最大次数(覆盖默认repeatLimit)。0表示无限次。 +# ## repeatLimit: 5 +# # [展示物品] 在挑战菜单中用于表示已完成挑战的物品 +# displayItem: cobblestone +# # [整数] 物品需求重置为默认值的小时数(覆盖主设置和等级默认值) +# ## resetInHours: 4 +# # [true/false] 完成挑战时是否收取所需物品 +# ## takeItems: true +# # 玩家完成挑战获得的奖励 +# reward: +# # [文本] 奖励描述 +# text: '苔石和带有耐久1的铁镐' +# # [物品需求列表] 完成挑战给予玩家的物品 +# items: +# - mossy_cobblestone:16 +# - iron_pickaxe[enchantments={levels:{unbreaking:1}}]:1 +# # [权限节点] 完成时授予的权限(多个权限用空格分隔) +# ## permission: 'test.permission' +# # [整数] 给予的货币数量(需要经济插件) +# ## currency: 0 +# # [整数] 给予玩家的经验值 +# ## xp: 0 +# # [文本列表] 完成时执行的命令。添加"op"或"console"前缀表示以OP或控制台身份执行。 +# # 可用参数: +# # {player} - 玩家名称 +# # {playerName} - 玩家显示名称 +# # {challenge} - 挑战名称 +# # {position} - 玩家位置 +# # {party} - 为队伍每个成员执行一次命令(替换名称) +# ## commands: +# ## - 'op: 我是万物的主宰' +# ## - 'console: give {party} 超棒物品 32' +# # 重复完成挑战时的奖励部分(第一次之后的任何完成)。结构与'reward'相同。 +# repeatReward: +# text: '苔石' +# items: +# - mossy_cobblestone:16 +# +# ====================================================================================================================== +allowChallenges: true +challengeSharing: island +broadcastCompletion: true +broadcastText: '&6' +requirePreviousRank: true +rankLeeway: 12 +defaultResetInHours: 20 +radius: 10 +repeatLimit: 0 +challengeColor: '&e' +finishedColor: '&2' +repeatableColor: '&a' +enableEconomyPlugin: true +resetChallengesOnCreate: true +lockedDisplayItem: gray_stained_glass_pane +ISLAND: + lockedDisplayItem: blue_stained_glass_pane +ISLAND_LEVEL: + lockedDisplayItem: black_stained_glass_pane +showLockedChallengeName: true +ranks: + Tier1: + name: '&7新手民工' + displayItem: iron_axe + resetInHours: 20 + challenges: + start: + name: '&6开始咯' + description: 蒲公英x2。 + type: onPlayer + requiredItems: + - 'dandelion:2' + displayItem: dandelion + resetInHours: 6 + reward: + text: 水和岩浆 + items: + - ice:2 + - lava_bucket:1 + currency: 20 + xp: 20 + repeatReward: + text: '' + cobblestonegenerator: + name: '&7石头!' + description: 造个刷石机 这还要我教吗 + requiredChallenges: + - start + type: onPlayer + requiredItems: + - cobblestone:64 + displayItem: cobblestone + resetInHours: 12 + reward: + text: 皮革x3 + items: + - leather:3 + - '{p=0.2}book:1' + currency: 10 + xp: 10 + commands: + - op:effect give {player} regeneration + repeatReward: + text: 皮革x1 + items: + - leather:1 + - '{p=0.1}book:1' + currency: 5 + xp: 5 + toolmaker: + name: '&3工欲善其事...' + description: 准备全套石头工具 + requiredChallenges: + - start + type: onPlayer + requiredItems: + - stone_shovel:1;+0 + - stone_pickaxe:1;+0 + - stone_axe:1;+0 + - stone_hoe:1;+0 + displayItem: stone_axe + resetInHours: 12 + reward: + text: 红石矿x4,铁矿x4,猪x1,各种树苗x1 + items: + - redstone_ore:4 + - iron_ore:4 + - pig_spawn_egg:1 + - oak_sapling:1 + - spruce_sapling:1 + - birch_sapling:1 + - jungle_sapling:4 + - acacia_sapling:1 + - dark_oak_sapling:4 + currency: 30 + xp: 30 + repeatReward: + text: 红石矿x4 + items: + - redstone_ore:4 + - '{p=0.1}iron_ore:1' + currency: 5 + xp: 5 + woodpractice: + name: '&3木工实习' + description: 收集所有类型的原木。 + type: onPlayer + requiredChallenges: + - start + requiredItems: + - oak_log:2;+2 + - spruce_log:2;+2 + - birch_log:2;+2 + - jungle_log:2;+2 + - acacia_log:2;+2 + - dark_oak_log:2;+2 + - stripped_oak_log:2;+2 + - stripped_spruce_log:2;+2 + - stripped_birch_log:2;+2 + - stripped_jungle_log:2;+2 + - stripped_acacia_log:2;+2 + - stripped_dark_oak_log:2;+2 + displayItem: oak_log + reward: + text: 4 redstone ore, 4 iron, cocoa and a mule + items: + - cocoa_beans:1 + - redstone_ore:4 + - iron_ore:4 + - mule_spawn_egg:1 + currency: 30 + xp: 30 + repeatReward: + text: 1 redstone ore, 1 iron ore + items: + - redstone_ore:1 + - iron_ore:1 + - '{p=0.05}gold_ore:1' + currency: 15 + xp: 15 + stonepractice: + name: '&3碳酸钙 吗' + description: 烧制石头。 + type: onPlayer + requiredChallenges: + - start + requiredItems: + - stone:64 + displayItem: stone + reward: + text: 平滑石头x64 + items: + - smooth_stone:64 + - '{p=0.2}redstone:4' + currency: 10 + xp: 10 + commands: + - op:effect give {player} regeneration + repeatReward: + text: 平滑石头x64 + items: + - smooth_stone:64 + - '{p=0.2}redstone:1' + currency: 5 + xp: 5 + stonebrickmaker: + name: '&a工业进阶' + description: 制作64个石砖。 + type: onPlayer + requiredChallenges: + - start + requiredItems: + - stone_bricks:64;+8 + - stone_brick_slab:30;+6 + - chiseled_stone_bricks:30;+6 + - stone_brick_stairs:16;+4 + displayItem: stone_bricks + reward: + text: 4个红石矿石,4个铁矿石,1只鸡 + items: + - redstone_ore:4 + - iron_ore:4 + - chicken_spawn_egg:1 + currency: 30 + xp: 30 + repeatReward: + text: 1个红石矿石,1个铁矿石 + items: + - redstone_ore:1 + - iron_ore:1 + currency: 15 + xp: 15 + coalboss: + name: '&7煤老板上班' + description: 木炭x64。 + displayItem: coal_ore + requiredChallenges: + - start + requiredItems: + - charcoal:64;+0 + reward: + text: 煤炭x64 + items: + - coal:64 + currency: 20 + xp: 20 + commands: + - op:effect give {party} regeneration + repeatReward: + text: 煤炭x64 + items: + - coal:64 + currency: 20 + xp: 20 + commands: + - op:effect give {party} regeneration + workerfinished: + name: '&a完成新手民工任务' + description: 完成新手民工任务。 + requiredChallenges: + - start + - cobblestonegenerator + - toolmaker + - woodpractice + - stonepractice + - stonebrickmaker + - coalboss + displayItem: crafting_table + reward: + text: 2个钻石,5个绿宝石 + items: + - emerald:5 + - diamond:2 + currency: 100 + xp: 100 + Tier2: + name: '&a新手农民' + displayItem: wheat + resetInHours: 20 + requires: + challenges: + - cobblestonegenerator + challenges: + farmerpack: + name: '&e农场主礼包' + description: 开始你的农场主生涯吧。 + type: onPlayer + displayItem: wheat + reward: + text: 农场主礼包x1 + items: + - cactus:1 + - pumpkin:1 + - sugar_cane:1 + - melon_seeds:1 + - beetroot_seeds:1 + - brown_mushroom:1 + - red_mushroom:1 + - carrot:1 + - potato:1 + currency: 30 + xp: 30 + repeatReward: + text: '' + items: + currency: 0 + xp: 0 + cactusfarmer: + name: '&6仙人掌君' + description: 开始生产仙人掌 + type: onPlayer + requiredChallenges: + - farmerpack + requiredItems: + - cactus:64;+16 + displayItem: cactus + lockedDisplayItem: brown_stained_glass_pane + reward: + text: 8个沙子, 20%几率获得骨头 + items: + - sand:8 + - '{p=0.2}bone:1' + - '{p=0.1}wooden_hoe:1' + currency: 20 + xp: 20 + repeatReward: + text: 4个沙子 + items: + - sand:4 + - '{p=0.1}bone:1' + currency: 10 + xp: 10 + pumpkinfarmer: + name: '&6吓人的南瓜' + description: 种植南瓜 + type: onPlayer + requiredChallenges: + - farmerpack + requiredItems: + - pumpkin:64;+4 + displayItem: pumpkin + lockedDisplayItem: brown_stained_glass_pane + reward: + text: 8个泥土 + items: + - dirt:8 + - '{p=0.3}jack_o_lantern:1' + currency: 20 + xp: 20 + repeatReward: + text: 4个泥土 + items: + - dirt:4 + - '{p=0.05}jack_o_lantern:1' + currency: 10 + xp: 10 + sugarfarmer: + name: '&6来点甘蔗' + description: 从农场收获甘蔗并做成糖和纸。 + type: onPlayer + requiredChallenges: + - farmerpack + requiredItems: + - paper:32;+4 + - sugar:32;+4 + displayItem: carrot + lockedDisplayItem: brown_stained_glass_pane + reward: + text: 1只猪 + items: + - pig_spawn_egg:1 + - '{p=0.05}red_sand:1' + - '{p=0.01}diamond:1' + currency: 50 + xp: 50 + repeatReward: + text: 1个金胡萝卜 + items: + - golden_carrot:1 + - '{p=0.05}sand:1' + - '{p=0.01}diamond:1' + currency: 25 + xp: 25 + shroompicker: + name: '&6发配云南' + description: 收集64个红蘑菇和棕蘑菇. + type: onPlayer + requiredChallenges: + - farmerpack + requiredItems: + - brown_mushroom:64;+4 + - red_mushroom:64;+4 + displayItem: red_mushroom + lockedDisplayItem: brown_stained_glass_pane + reward: + text: 8 菌丝, 4 灰化土 + items: + - mycelium:8 + - podzol:4 + currency: 30 + xp: 30 + repeatReward: + text: 4 菌丝 + items: + - mycelium:4 + - '{p=0.02}mycelium:4' + - '{p=0.02}podzol:2' + currency: 15 + xp: 15 + melonfarmer: + name: '&6夏天来块西瓜吧' + description: 种植西瓜 + type: onPlayer + requiredChallenges: + - farmerpack + requiredItems: + - melon_slice:128;+8 + displayItem: melon + lockedDisplayItem: brown_stained_glass_pane + reward: + text: 8个泥土 + items: + - dirt:8 + currency: 20 + xp: 20 + repeatReward: + text: 4个泥土 + items: + - dirt:4 + currency: 10 + xp: 10 + beetrootfarmer: + name: '&6甜菜根' + description: 种植甜菜根 + type: onPlayer + requiredChallenges: + - farmerpack + requiredItems: + - beetroot:64;+16 + displayItem: beetroot + lockedDisplayItem: brown_stained_glass_pane + reward: + text: 8个泥土 + items: + - dirt:8 + currency: 20 + xp: 20 + repeatReward: + text: 4个泥土 + items: + - dirt:4 + - '{p=0.2}bone:1' + currency: 10 + xp: 10 + farmerfinished: + name: '&a完成新手农场主任务' + description: 完成新手农场主任务。 + requiredChallenges: + - start + - cobblestonegenerator + - farmerpack + - cactusfarmer + - pumpkinfarmer + - sugarfarmer + - shroompicker + - melonfarmer + - beetrootfarmer + displayItem: crafting_table + reward: + text: 2个钻石,5个绿宝石 + items: + - emerald:5 + - diamond:2 + currency: 100 + xp: 100 + Tier3: + name: '&e新手怪物猎人' + displayItem: iron_sword + resetInHours: 20 + requires: + challenges: + - start + - cobblestonegenerator + challenges: + monsterfarm: + name: '&5怪物猎人' + description: 开始猎杀怪物 + type: onPlayer + requiredItems: + - rotten_flesh:32;+4 + - bone:32;+2 + - gunpowder:32;+1 + displayItem: rotten_flesh + lockedDisplayItem: purple_stained_glass_pane + reward: + text: 4个红石矿, 4个铁矿, 1个燧石 + items: + - redstone_ore:4 + - iron_ore:4 + - flint:1 + - '{p=0.10}potato:1' + - '{p=0.10}carrot:1' + currency: 30 + xp: 30 + repeatReward: + text: 1个红石矿, 1个铁矿, 1个燧石 + items: + - redstone_ore:1 + - iron_ore:1 + - flint:1 + - '{p=0.05}potato:1' + - '{p=0.05}carrot:1' + currency: 15 + xp: 15 + witchfarm: + name: '&5女巫猎人' + description: 猎杀女巫 + type: onPlayer + requiredItems: + - glass_bottle:5;+5 + - glowstone_dust:5;+5 + - redstone:5;+5 + displayItem: glowstone_dust + reward: + text: 4个沙子, 4个铁矿 + items: + - sand:4 + - iron_ore:4 + - '{p=0.10}potato:1' + - '{p=0.10}carrot:1' + currency: 30 + xp: 30 + repeatReward: + text: 2个沙子, 2个铁矿 + items: + - sand:4 + - iron_ore:2 + - '{p=0.05}potato:1' + - '{p=0.05}carrot:1' + currency: 15 + xp: 15 + slimefarm: + name: '&5史莱姆农场' + description: 收集史莱姆球 + type: onPlayer + requiredItems: + - slime_ball:64;+4 + displayItem: slime_ball + lockedDisplayItem: purple_stained_glass_pane + reward: + text: 4个红石矿,1个绿宝石 + items: + - redstone_ore:4 + - emerald:1 + currency: 70 + xp: 70 + repeatReward: + text: 8个红石 + items: + - redstone:8 + currency: 35 + xp: 35 + woolwhere: + name: '&5无中生有' + description: 收集羊毛 + type: onPlayer + displayItem: white_wool + lockedDisplayItem: purple_stained_glass_pane + requiredItems: + - white_wool:5;+5 + reward: + text: 2个钻石,2只羊 + items: + - diamond:2 + - sheep_spawn_egg:2 + currency: 70 + xp: 70 + repeatReward: + text: 一个烤羊肉 + items: + - cooked_mutton:1 + currency: 10 + xp: 10 + goldwhere: + name: '&5土法炼金' + description: 想办法制作3个金锭 + type: onPlayer + displayItem: gold_ingot + lockedDisplayItem: purple_stained_glass_pane + requiredItems: + - gold_ingot:3 + reward: + text: 4个钻石 + items: + - diamond:4 + currency: 70 + xp: 70 + repeatReward: + text: 10 coin + currency: 10 + xp: 10 + itemfarm: + name: '&5神射手' + description: 获得一些箭 + type: onPlayer + requiredItems: + - arrow:128;+32 + displayItem: arrow + lockedDisplayItem: purple_stained_glass_pane + reward: + text: 4个红石矿, 4个铁矿, 1个燧石 + items: + - redstone_ore:4 + - iron_ore:4 + - flint:1 + - '{p=0.10}potato:1' + - '{p=0.10}carrot:1' + currency: 30 + xp: 30 + repeatReward: + text: 2个沙子 + items: + - sand:2 + - '{p=0.05}potato:1' + - '{p=0.05}carrot:1' + currency: 15 + xp: 15 + killenderman1: + name: '&5杀点小黑 之一' + description: 收集末影珍珠. + type: onPlayer + requiredItems: + - ender_pearl:10;+4 + displayItem: ender_pearl + lockedDisplayItem: purple_stained_glass_pane + reward: + text: 5个金矿 1个绿宝石 + items: + - gold_ore:5 + - emerald:1 + currency: 70 + xp: 70 + repeatReward: + text: 1个金矿 + items: + - gold_ore:1 + currency: 35 + xp: 35 + mhfinished: + name: '&a完成新手怪物猎人任务' + description: 完成新手怪物猎人任务。 + requiredChallenges: + - start + - cobblestonegenerator + - monsterfarm + - witchfarm + - slimefarm + - woolwhere + - goldwhere + - killenderman1 + - itemfarm + displayItem: crafting_table + reward: + text: 2个钻石,5个绿宝石 + items: + - emerald:5 + - diamond:2 + currency: 100 + xp: 100 + Tier4: + name: '&e新手牧场主' + displayItem: lead + resetInHours: 20 + requires: + challenges: + - start + - cobblestonegenerator + challenges: + fisherman: + name: '&1小鱼干' + description: 钓鱼走起. + type: onPlayer + requiredItems: + - cod:5;+1 + - salmon:5;+1 + - pufferfish:3;+1 + - tropical_fish:1;+0 + - ink_sac:5;+2 + repeatable: true + displayItem: cod + takeItems: true reward: - text: a parrot and a jukebox with some redstone and iron + text: 一些海产 好好利用哦 items: - - parrot_spawn_egg:1 - - jukebox:1 - - redstone_ore:4 - - iron_ore:4 - currency: 30 - xp: 30 + - lapis_block:10 + - prismarine:20 + - kelp:1 + - '{p=0.20}prismarine_crystals:5' + currency: 50 + xp: 50 repeatReward: - text: redstone, iron and a slim chance at a parrot + text: 一些海产 好好利用哦 items: - - redstone_ore:1 - - iron_ore:1 - - '{p=0.25}jukebox:1' - - '{p=0.05}parrot_spawn_egg:1' - currency: 15 - xp: 15 - cookielover: - name: '&eCookie Lover' - description: Make cookies and a bucket of milk. + - lapis_lazuli:2 + - prismarine:5 + - kelp:1 + - '{p=0.20}prismarine_crystals:5' + currency: 25 + xp: 25 + grasslover: + name: '&e穷到吃草' + description: 爱吃 type: onPlayer - requiredChallenges: - - expertfarmer requiredItems: - - cookie:128;+4 - - milk_bucket:1 - displayItem: cookie + - short_grass:32;+16 + - grass_block:4;+1 + - seagrass:32;+16 + displayItem: grass_block lockedDisplayItem: yellow_stained_glass_pane reward: - text: 4 redstone ore, clay a bucket and a diamond + text: 4红石矿,5粘土,2牛 items: - redstone_ore:4 - clay:5 - - diamond:1 - - bucket:1 + - cow_spawn_egg:2 currency: 80 xp: 80 repeatReward: - text: iron ore and some clay + text: 铁矿,黏土 items: - iron_ore:1 - clay:5 @@ -1106,689 +2153,784 @@ ranks: - '{p=0.3}iron_ore:2' currency: 40 xp: 40 - deepseafisherman: - name: '&5Deep Sea Fishing' - description: Farm the deep sea - type: onPlayer - requiredItems: - - cod:10;+5 - - salmon:10;+5 - - pufferfish:5;+3 - - tropical_fish:3;+2 - - prismarine_crystals:16;+8 - - prismarine_shard:16;+8 - - dried_kelp_block:64;+32 - - nautilus_shell:1 - displayItem: heart_of_the_sea - lockedDisplayItem: purple_stained_glass_pane + killcow: + name: '&e大屠杀时刻' + description: 通过牧场养殖 获得一定量的皮革. + type: onPlayer + requiredItems: + - leather:96;+32 + displayItem: leather + lockedDisplayItem: yellow_stained_glass_pane reward: - text: heart-of-the-sea, nautilus shell and a turtle + text: 2只猪 1个鞍 items: - - nautilus_shell:1 - - heart_of_the_sea:1 - - turtle_spawn_egg:1 - - '{p=0.1}nautilus_shell:1' - permission: usb.biome.deep_ocean - currency: 50 - xp: 50 + - saddle:1 + - pig_spawn_egg:2 + currency: 80 + xp: 80 repeatReward: - text: nautilus shell, turtle and a chance of a heart + text: 1个鞍 items: - - nautilus_shell:1 - - turtle_spawn_egg:1 - - '{p=0.05}heart_of_the_sea:1' - - '{p=0.1}nautilus_shell:1' - currency: 25 - xp: 25 + - saddle:1 + - '{p=0.3}iron_ore:2' + currency: 40 + xp: 40 horsingaround: - name: '&6Horsing Around' - description: Get hay bales for the horses. + name: '&1准备养马' + description: 给马准备点干草,栓绳,胡萝卜钓竿,剪刀. type: onPlayer requiredItems: - - hay_block:32;+4 + - wheat:32;+4 - lead:8;+2 - carrot_on_a_stick:1 - shears:1 - requirecChallenges: - - wheatfarmer - displayItem: hay_block - lockedDisplayItem: brown_stained_glass_pane + repeatable: true + displayItem: wheat + takeItems: true reward: - text: 1 horse, 1 iron horse armor, 5% chance on diamond horse armor + text: 1 匹小马驹, 1 个铁马铠, 5% 的机会获得一个钻石马铠 items: - - iron_horse_armor:1 - horse_spawn_egg:1 + - iron_horse_armor:1 - '{p=0.05}diamond_horse_armor:1' currency: 50 xp: 50 repeatReward: - text: 1 redstone ore, 1 emerald + text: 1 个红石矿, 1 绿宝石 items: - - redstone:1 + - redstone_ore:1 - emerald:1 currency: 25 xp: 25 - slimefarmer: - name: '&5Slime Farmer' - description: Collect slimeballs from slimes. + fisherydevelopment: + name: '&1发展渔业' + description: 小池塘 大世界. type: onPlayer requiredItems: - - slime_ball:64;+4 - displayItem: slime_ball - lockedDisplayItem: purple_stained_glass_pane + - lily_pad:16;+8 + - fishing_rod:1 + - kelp:64;+16 + repeatable: true + displayItem: fishing_rod + takeItems: true reward: - text: 1 diamond, 1 emeralds, swampland + text: 1 个青金石矿, 1 个萤石 items: - - diamond:1 - - emerald:1 - permission: usb.biome.swamp - currency: 70 - xp: 70 + - lapis_ore:1 + - glowstone:1 + currency: 20 + xp: 20 repeatReward: - text: 1 redstone ore, 1 emeralds + text: 1 个青金石矿, 2 个萤石粉 items: - - redstone_ore:1 - - emerald:1 - currency: 35 - xp: 35 - animalfarm: - name: '&9Animal Farm' - description: Create an animal farm. - type: onIsland - displayItem: oak_fence - radius: 40 - requiredChallenges: - - woolcollector - - potatofarmer - - carrotfarmer - requiredEntities: - - Cow:8 - - Pig:8 - - Chicken:16 - - Sheep:{"Color":"WHITE"} - - Sheep:{"Color":"ORANGE"} - - Sheep:{"Color":"MAGENTA"} - - Sheep:{"Color":"LIGHT_BLUE"} - - Sheep:{"Color":"YELLOW"} - - Sheep:{"Color":"LIME"} - - Sheep:{"Color":"PINK"} - - Sheep:{"Color":"GRAY"} - - Sheep:{"Color":"LIGHT_GRAY"} - - Sheep:{"Color":"CYAN"} - - Sheep:{"Color":"PURPLE"} - - Sheep:{"Color":"BLUE"} - - Sheep:{"Color":"BROWN"} - - Sheep:{"Color":"GREEN"} - - Sheep:{"Color":"RED"} - - Sheep:{"Color":"BLACK"} - reward: - text: 1 horse, 1 iron horse armor, 5% chance horse armor + - lapis_ore:1 + - glowstone_dust:2 + currency: 20 + xp: 20 + tacticalfishing: + name: '&1战术性钓鱼' + description: 我该干什么. + type: onPlayer + requiredItems: + - cod_bucket:1 + - salmon_bucket:1 + - tropical_fish_bucket:1 + - pufferfish_bucket:1 + repeatable: true + displayItem: cod_bucket + takeItems: true + reward: + text: 1 个钻石矿, 1 个萤石, 一只鸡, 两只海龟 items: - - iron_horse_armor:1 - - horse_spawn_egg:1 - - '{p=0.05}iron_horse_armor:1' - - '{p=0.05}golden_horse_armor:1' - - '{p=0.05}diamond_horse_armor:1' - currency: 50 - xp: 50 + - turtle_spawn_egg:2 + - chicken_spawn_egg:1 + - diamond_ore:1 + - glowstone:1 + currency: 20 + xp: 20 + repeatReward: + text: 1 个青金石块, 2 个萤石粉 + items: + - lapis_block:1 + - glowstone_dust:2 + currency: 20 + xp: 20 + beautifulegg: + name: '&1鸡你太美' + description: 鸡蛋 + type: onPlayer + requiredItems: + - egg:64;+32 + repeatable: true + displayItem: egg + takeItems: true + reward: + text: 1 个金胡萝卜, 1 个CD, 4 个绿宝石 + items: + - golden_carrot:1 + - music_disc_cat:1 + - emerald:4 + currency: 60 + xp: 60 repeatReward: - text: 1 redstone ore, 1 emerald, 5% chance horse armor + text: 20% 的机会获得一个CD, 1 个绿宝石 items: - - redstone:1 - emerald:1 - - '{p=0.05}iron_horse_armor:1' - - '{p=0.05}golden_horse_armor:1' - - '{p=0.05}diamond_horse_armor:1' - currency: 25 - xp: 25 - witherhunter: - name: '&5Wither Hunter' - description: Collect some Wither Skeleton skulls. + - '{p=0.2}music_disc_wait:1' + currency: 30 + xp: 30 + rancherfinished: + name: '&a完成新手牧场主任务' + description: 完成新手牧场主任务。 + requiredChallenges: + - start + - cobblestonegenerator + - fisherman + - grasslover + - killcow + - horsingaround + - fisherydevelopment + - tacticalfishing + - beautifulegg + displayItem: crafting_table + reward: + text: 2个钻石,5个绿宝石 + items: + - emerald:5 + - diamond:2 + currency: 100 + xp: 100 + Tier5: + name: '&e新手建筑师' + displayItem: smooth_stone + resetInHours: 20 + requires: + challenges: + - start + - cobblestonegenerator + challenges: + builderstart: + name: '&7开始铺地' + description: 达到10级. + type: islandLevel + requiredLevel: 20 + displayItem: coal_ore + reward: + text: 8个泥土 8个沙子 5个绿宝石 1个钻石 + items: + - dirt:8 + - sand:8 + - emerald:5 + - diamond:1 + currency: 20 + xp: 20 + commands: + - op:effect give {party} regeneration + builder1: + name: '&7铺地新手' + description: 达到20级. + type: islandLevel + requiredLevel: 20 + displayItem: coal_ore + reward: + text: 8个泥土 8个沙子 5个绿宝石 1个钻石 + items: + - dirt:8 + - sand:8 + - emerald:5 + - diamond:1 + currency: 20 + xp: 20 + commands: + - op:effect give {party} regeneration + builder2: + name: '&a铺地菜鸡' + description: 达到50级. + type: islandLevel + requiredLevel: 50 + displayItem: iron_ore + reward: + text: 2个钻石 5个绿宝石 + items: + - emerald:5 + - diamond:2 + currency: 100 + xp: 100 + builder3: + name: '&e入门铺地员' + description: 达到100级. + type: islandLevel + requiredLevel: 100 + displayItem: gold_ore + reward: + text: 16个泥土 16个沙子 5个绿宝石 3个钻石 + items: + - dirt:16 + - sand:16 + - emerald:5 + - diamond:3 + currency: 250 + xp: 250 + builder4: + name: '&e苦逼铺地员' + description: 达到150级. + type: islandLevel + requiredLevel: 100 + displayItem: gold_ore + reward: + text: 16个泥土 16个沙子 5个绿宝石 3个钻石 + items: + - dirt:16 + - sand:16 + - emerald:5 + - diamond:3 + currency: 250 + xp: 250 + builder5: + name: '&e200级!' + description: 达到200级. + type: islandLevel + requiredLevel: 100 + displayItem: gold_ore + reward: + text: 16个泥土 16个沙子 5个绿宝石 3个钻石 + items: + - dirt:16 + - sand:16 + - emerald:5 + - diamond:3 + currency: 250 + xp: 250 + builderfinished: + name: '&a完成新手建筑师任务' + description: 完成新手建筑师任务。 + requiredChallenges: + - start + - cobblestonegenerator + - builderstart + - builder1 + - builder2 + - builder3 + - builder4 + - builder5 + displayItem: crafting_table + reward: + text: 2个钻石,5个绿宝石 + items: + - emerald:5 + - diamond:2 + currency: 100 + xp: 100 + page1finished: + name: '&a完成新手任务' + description: 完成新手任务,解锁地狱,解锁trust功能,解锁新内容。 + requiredChallenges: + - start + - cobblestonegenerator + - workerfinished + - farmerfinished + - mhfinished + - rancherfinished + - builderfinished + displayItem: crafting_table + reward: + text: 30个钻石,50个绿宝石,前往下界 + items: + - emerald:50 + - diamond:30 + - obsidian:10 + currency: 100 + xp: 100 + Tier6: + name: '&e入门民工' + displayItem: iron_axe + resetInHours: 20 + requires: + challenges: + - page1finished + challenges: + netherportal: + name: '&4想要更深入些' + description: 建造一座地狱门并点燃它. + type: onIsland + requiredItems: + - obsidian:10 + repeatable: false + displayItem: obsidian + takeItems: false + reward: + text: 1 个铁镐, 1 铁锹 + items: + - iron_pickaxe:1 + - iron_shovel:1 + currency: 40 + xp: 40 + mobfarm1: + name: '&5造一个主世界刷怪塔!' + description: 更快!更高!更大! type: onPlayer requiredChallenges: - - netherfortress + - netherportal requiredItems: - - wither_skeleton_skull:10;+1 - displayItem: wither_skeleton_skull + - rotten_flesh:128;+64 + - bone:128;+64 + - gunpowder:128;+64 + - spider_eye:128;+64 + displayItem: rotten_flesh lockedDisplayItem: purple_stained_glass_pane - offset: -1 reward: - text: 5 diamonds, 5% chance of nether star + text: 4个红石块, 4个铁块, 4个金块 items: - - diamond:5 - - '{p=0.05}nether_star:1' - currency: 400 - xp: 400 + - redstone_block:4 + - iron_block:4 + - gold_block:4 + - '{p=0.10}potato:1' + - '{p=0.10}carrot:1' + currency: 30 + xp: 30 repeatReward: - text: 2 gold ore, 5% chance of nether star + text: 1个红石块, 1个铁块 items: - - gold_ore:2 - - '{p=0.05}nether_star:1' - currency: 20 - xp: 20 - pearlcollector: - name: '&5Pearl Collector' - description: Collect enderpearls from endermen. + - redstone_block:1 + - iron_block:1 + - '{p=0.05}potato:1' + - '{p=0.05}carrot:1' + currency: 15 + xp: 15 + mobfarm2: + name: '&5造一个地狱刷怪塔!' + description: 更快!更高!更大! type: onPlayer + requiredChallenges: + - netherportal requiredItems: - - ender_pearl:10;+4 - displayItem: ender_pearl + - blaze_rod:128;+64 + - gold_ingot:128;+64 + - rotten_flesh:128;+64 + - ghast_tear:8;+4 + displayItem: blaze_powder lockedDisplayItem: purple_stained_glass_pane reward: - text: 5 gold ore, 1 blaze rod, 1 emerald + text: 4个红石块, 4个铁块, 4个金块 + items: + - redstone_block:4 + - iron_block:4 + - gold_block:4 + - '{p=0.10}potato:1' + - '{p=0.10}carrot:1' + currency: 30 + xp: 30 + repeatReward: + text: 1个红石块, 1个铁块 + items: + - redstone_block:1 + - iron_block:1 + - '{p=0.05}potato:1' + - '{p=0.05}carrot:1' + currency: 15 + xp: 15 + glassmaker: + name: '&3玻璃心' + description: 获得所有品种玻璃. + type: onPlayer + requiredChallenges: + - netherportal + displayItem: white_stained_glass + lockedDisplayItem: cyan_stained_glass_pane + requiredItems: + - white_stained_glass:8;+2 + - orange_stained_glass:8;+2 + - magenta_stained_glass:8;+2 + - light_blue_stained_glass:8;+2 + - yellow_stained_glass:8;+2 + - lime_stained_glass:8;+2 + - pink_stained_glass:8;+2 + - gray_stained_glass:8;+2 + - light_gray_stained_glass:8;+2 + - cyan_stained_glass:8;+2 + - purple_stained_glass:8;+2 + - blue_stained_glass:8;+2 + - brown_stained_glass:8;+2 + - green_stained_glass:8;+2 + - red_stained_glass:8;+2 + - black_stained_glass:8;+2 + reward: + text: 2个钻石 2个唱片 1个绿宝石 items: - - gold_ore:5 - - blaze_rod:1 + - diamond:2 + - music_disc_ward:1 + - music_disc_11:1 - emerald:1 + - sunflower:1 + - lilac:1 currency: 70 xp: 70 repeatReward: - text: 1 gold ore, 1 blaze rod + text: 1个绿宝石 items: - - gold_ore:1 - - blaze_rod:1 + - '{p=0.3}music_disc_ward:1' + - '{p=0.3}music_disc_11:1' + - emerald:1 + - sunflower:1 + - lilac:1 currency: 35 xp: 35 - - # =============================================== - Tier5: - name: '&4Sky Lord' - displayItem: red_terracotta - resetInHours: 48 - requires: - challenges: - - masterbuilder - challenges: - technician: - name: '&3Technician' - description: Collect some of every type of redstone equipment. - type: onPlayer - requiredItems: - - redstone:64;+16 - - redstone_torch:32;+4 - - repeater:5;+1 - - comparator:3;+1 - - piston:2;+1 - - sticky_piston:2;+1 - - lever:1;+1 - - stone_button:1;+1 - - stone_pressure_plate:1;+1 - - hopper:1;+1 - - dispenser:1;+1 - - dropper:1;+1 - - daylight_detector:1;+1 - displayItem: redstone + duplicarpet: + name: '&3我爱地毯' + description: 获得所有品种地毯。 + type: onPlayer + requiredChallenges: + - netherportal + displayItem: white_carpet lockedDisplayItem: cyan_stained_glass_pane + requiredItems: + - white_carpet:8;+2 + - orange_carpet:8;+2 + - magenta_carpet:8;+2 + - light_blue_carpet:8;+2 + - yellow_carpet:8;+2 + - lime_carpet:8;+2 + - pink_carpet:8;+2 + - gray_carpet:8;+2 + - light_gray_carpet:8;+2 + - cyan_carpet:8;+2 + - purple_carpet:8;+2 + - blue_carpet:8;+2 + - brown_carpet:8;+2 + - green_carpet:8;+2 + - red_carpet:8;+2 + - black_carpet:8;+2 reward: - text: some snow, ice and packed ice + text: 2个钻石 2个唱片 1个管珊瑚扇 items: - - snow_block:4 - - ice:8 - - packed_ice:8 - - redstone_block:64 + - diamond:2 + - music_disc_creator:1 + - music_disc_13:1 + - tube_coral_fan:1 + - sunflower:1 + - lilac:1 currency: 70 xp: 70 repeatReward: - text: a stack of redstone blocks and some ice + text: 1个绿宝石 1个管珊瑚扇 items: - - redstone_block:64 - - ice:4 - - packed_ice:1 + - '{p=0.3}music_disc_creator:1' + - '{p=0.3}music_disc_13:1' + - emerald:1 + - sunflower:1 + - lilac:1 + - tube_coral_fan:1 currency: 35 xp: 35 - emeraldcollector: - name: '&5Emerald Collector' - description: Collect emeralds. + netherrack: + name: '&7地狱矿工' + description: 收集地狱岩 tips:挖地狱岩有概率产生新的 type: onPlayer + requiredChallenges: + - netherportal requiredItems: - - emerald:60;+10 - displayItem: emerald - lockedDisplayItem: purple_stained_glass_pane + - netherrack:64;+32 + - soul_sand:64;+32 + - glowstone:32;+32 + - gravel:32;+16 + - quartz:32;+16 + displayItem: netherrack + resetInHours: 12 reward: - text: a full set of diamond armor + text: 1 把弓, 32 只箭,1 个可可豆 items: - - diamond_helmet:1 - - diamond_chestplate:1 - - diamond_leggings:1 - - diamond_boots:1 - currency: 70 - xp: 70 + - bow:1 + - arrow:32 + - cocoa_beans:1 + currency: 40 + xp: 40 + commands: + - op:effect give {player} regeneration repeatReward: - text: full diamond armor + text: 64 只箭 items: - - diamond_helmet:1 - - diamond_chestplate:1 - - diamond_leggings:1 - - diamond_boots:1 - currency: 35 - xp: 35 - topchef: - name: '&eTop Chef' - description: Collect every kind of edible food. - type: onPlayer - requiredItems: - - baked_potato:1 - - bread:1 - - cake:1 - - cooked_chicken:1 - - cooked_cod:1 - - cooked_salmon:1 - - tropical_fish:1 - - cooked_porkchop:1 - - cookie:1 - - golden_apple:1 - - golden_carrot:1 - - mushroom_stew:1 - - pumpkin_pie:1 - - cooked_beef:1 - - melon:1 - - carrot:1 + - arrow:64 + currency: 20 + xp: 20 + hellfire: + name: '&4地狱火' + description: 火炎焱. requiredChallenges: - - expertfarmer - - fisherman - - cookielover - displayItem: carrot - lockedDisplayItem: yellow_stained_glass_pane + - netherportal + type: onPlayer + requiredItems: + - blaze_rod:16;+8 + - water_bucket:32;+16 + repeatable: true + displayItem: blaze_rod + takeItems: true reward: - text: 2 diamond, 1 mooshroom + text: 1 个漏斗, 2 个雪块, 两个苔藓块 items: - - diamond:2 - - mooshroom_spawn_egg:1 - currency: 80 - xp: 80 + - hopper:1 + - snow_block:2 + - moss_block:2 + currency: 20 + xp: 20 repeatReward: - text: 1 diamond, 1 mooshroom + text: 1 个雪块 items: - - diamond:1 - - mooshroom_spawn_egg:1 - currency: 40 - xp: 40 - tajmahal: - name: '&9Taj Mahal' - description: Build a temple of quartz - type: onIsland - radius: 30 - displayItem: quartz_block - lockedDisplayItem: blue_stained_glass_pane - requiredChallenges: - - nethermining - requiredBlocks: - - quartz_block:512 - - chiseled_quartz_block:64 - - quartz_pillar:128 - - quartz_stairs:64 - - quartz_slab:192 - reward: - text: 3 end-portal frames, red sand - items: - - end_portal_frame:3 - - red_sand:64 - currency: 1000 - xp: 500 - greatpyramid: - name: '&9Great Pyramid' - description: Build a pyramid of sandstone - type: onIsland - radius: 30 - displayItem: sandstone - lockedDisplayItem: blue_stained_glass_pane - requiredBlocks: - - sandstone:512 - - chiseled_sandstone:64 - - smooth_sandstone:128 - - sandstone_stairs:64 - - sandstone_slab:192 - - red_sandstone:16 - reward: - text: 3 end-portal frames, 3 diamonds, bow - items: - - end_portal_frame:3 - - diamond:3 - - 'bow[enchantments={levels:{infinity:1,power:5,unbreaking:3}}]:1' - currency: 1000 - xp: 500 - poseidonshalls: - name: '&9Poseidon''s Halls' - description: Build the halls of Poseidon - type: onIsland - radius: 50 - displayItem: prismarine - lockedDisplayItem: blue_stained_glass_pane - requiredChallenges: - - deepseafisherman - requiredBlocks: - - prismarine:512 - - prismarine_bricks:128 - - dark_prismarine:64 - - sea_lantern:32 - - conduit:1 - - flower_pot:16 - - vine:128 - - poppy:10 - - blue_orchid:10 - - allium:10 - - azure_bluet:10 - - red_tulip:10 - - orange_tulip:10 - - white_tulip:10 - - pink_tulip:10 - - oxeye_daisy:10 - - sunflower:10 - - lilac:10 - - rose_bush:10 - - peony:10 - reward: - text: 3 end-portal frames, 5 diamonds - items: - - end_portal_frame:3 - - diamond:5 - currency: 1000 - xp: 500 - beaconator: - name: '&9Beaconator' - description: Build a very expensive beacon. - type: onIsland - radius: 15 - displayItem: beacon - lockedDisplayItem: blue_stained_glass_pane - requiredChallenges: - - netherfortress - - ironfarm - - maestro - - witherhunter - requiredBlocks: - - beacon:1 - - diamond_block:1 - - emerald_block:8 - - gold_block:25 - - iron_block:49 - reward: - items: - - end_portal_frame:3 - - 'diamond_sword[enchantments={levels:{fire_aspect:1,sharpness:5,unbreaking:3}}]:1' - text: some end-portal-pieces and a magic diamond sword - currency: 4000 - xp: 1000 - endportal: - name: '&9End Portal' - description: Build and activate an end-portal. - type: onIsland - radius: 15 - displayItem: end_portal_frame - lockedDisplayItem: blue_stained_glass_pane + - snow_block:1 + currency: 20 + xp: 20 + adeptworkerfinished: + name: '&a完成入门民工任务' + description: 完成入门民工任务。 requiredChallenges: - - beaconator - - tajmahal - - poseidonshalls - - greatpyramid - requiredBlocks: - - end_portal_frame:12 - - end_portal:9 - reward: - text: 5 diamonds and some nice boots - items: - - diamond:5 - - 'diamond_boots[enchantments={levels:{blast_protection:4,feather_falling:4,protection:4}}]:1' - currency: 4000 - xp: 1000 - - # =============================================== - Tier6: - name: '&aWorld Foods' - displayItem: light_gray_terracotta + - mobfarm1 + - mobfarm2 + - glassmaker + - duplicarpet + - netherrack + - hellfire + displayItem: crafting_table + reward: + text: 2个钻石,5个绿宝石 + items: + - emerald:5 + - diamond:2 + currency: 100 + xp: 100 + Tier7: + name: '&a入门农民' + displayItem: wheat resetInHours: 20 requires: challenges: - - masterbuilder - - topchef + - netherportal challenges: - fishandchips: - name: '&eFish & Chips' - description: Create the famous English Fish & Chips. + hellfarm: + name: '&4地狱农场' + description: 勤劳致富 地狱也能发财. type: onPlayer requiredItems: - - cooked_cod:32;+8 - - baked_potato:32;+8 - displayItem: cooked_cod - lockedDisplayItem: yellow_stained_glass_pane + - coal:64;+16 + - nether_wart:64;+16 + - red_mushroom:64;+16 + repeatable: true + displayItem: coal + takeItems: true reward: - text: 16 baked potatos, 1 disk, 4 emerald + text: 64 个红砖, 3 个黏土 items: - - baked_potato:16 - - music_disc_13:1 - - emerald:4 - currency: 60 - xp: 60 - commands: - - 'console: msg {player} &9https://en.wikipedia.org/wiki/Fish_and_chips - &2Click the link for more info' + - brick:64 + - clay:3 + currency: 20 + xp: 20 repeatReward: - text: 30% chance on 1 disk, 1 emerald + text: 32 个红砖, 2 个黏土 items: - - emerald:1 - - '{p=0.3}music_disc_13:1' - currency: 30 - xp: 30 - commands: - - 'console: msg {player} &9https://en.wikipedia.org/wiki/Fish_and_chips - &2Click the link for more info' - smorrebrod: - name: '&eSmørrebrød' - description: Create the famous Danish smørrebrød. - type: onPlayer - requiredItems: - - bread:16;+4 - - cooked_salmon:8;+4 - - tropical_fish:2;+1 - - cooked_porkchop:8;+4 - - cooked_beef:8;+4 - displayItem: cooked_cod - lockedDisplayItem: yellow_stained_glass_pane + - brick:32 + - clay:2 + currency: 20 + xp: 20 + wheatfarmer: + name: '&6该来点小麦了' + description: 种植小麦; + type: onPlayer + requiredItems: + - wheat:64;+16 + displayItem: wheat + lockedDisplayItem: brown_stained_glass_pane reward: - text: 1 rod, 2 name tags, 2 ocelots + text: 8个泥土 items: - - fishing_rod:1 - - name_tag:2 - - ocelot_spawn_egg:2 - currency: 60 - xp: 60 - commands: - - 'console: msg {player} &9https://en.wikipedia.org/wiki/Danish_cuisine - &2Click the link for more info' + - dirt:8 + currency: 20 + xp: 20 repeatReward: - text: 1 emerald + text: 4个泥土 items: - - emerald:1 - currency: 30 - xp: 30 - commands: - - 'console: msg {player} &9https://en.wikipedia.org/wiki/Danish_cuisine - &2Click the link for more info' - hutspot: - name: '&eHutspot' - description: Create the famous Dutch hutspot. + - dirt:4 + - '{p=0.2}bone:1' + currency: 10 + xp: 10 + carrotfarmer: + name: '&6萝卜萝卜' + description: 种植萝卜 type: onPlayer requiredItems: - - potato:64;+8 - - carrot:64;+8 - lockedDisplayItem: yellow_stained_glass_pane - displayItem: golden_carrot + - carrot:64;+16 + displayItem: carrot + lockedDisplayItem: brown_stained_glass_pane reward: - text: 1 golden carrot, 1 disk, 4 emerald + text: 猪和鞍 items: - - golden_carrot:1 - - music_disc_cat:1 - - emerald:4 - currency: 60 - xp: 60 - commands: - - 'console: msg {player} &9https://en.wikipedia.org/wiki/Hutspot &2Click - the link for more info' + - saddle:1 + - pig_spawn_egg:1 + - '{p=0.05}red_sand:1' + - '{p=0.01}diamond:1' + currency: 50 + xp: 50 repeatReward: - text: 30% chance on 1 disk, 1 emerald + text: 一个金胡萝卜 items: - - emerald:1 - - '{p=0.3}music_disc_cat:1' - currency: 30 - xp: 30 - commands: - - 'console: msg {player} &9https://en.wikipedia.org/wiki/Hutspot &2Click - the link for more info' - apfelstrudel: - name: '&eApfelstrudele' - description: Create the famous German apfelstrudel. - type: onPlayer - requiredItems: - - apple:8;+5 - - wheat:16;+5 - - sugar:16;+5 - - milk_bucket:1 - displayItem: golden_apple - lockedDisplayItem: yellow_stained_glass_pane + - golden_carrot:1 + - '{p=0.05}sand:1' + - '{p=0.01}diamond:1' + currency: 25 + xp: 25 + potatofarmer: + name: '&6土豆服务器' + description: 收获土豆. + type: onPlayer + requiredItems: + - potato:64;+16 + displayItem: potato + lockedDisplayItem: brown_stained_glass_pane reward: - text: 1 bucket, 1 disk, 4 emerald + text: 4个泥土 items: - - bucket:1 - - emerald:4 - - music_disc_blocks:1 - currency: 60 - xp: 60 - commands: - - 'console: msg {player} &9https://en.wikipedia.org/wiki/Apple_strudel &2Click - the link for more info' + - dirt:4 + - '{p=0.10}beetroot_seeds:1' + - '{p=0.05}red_sand:1' + - '{p=0.01}diamond:1' + currency: 50 + xp: 50 repeatReward: - text: 1 bucket, 30% chance on 1 disk, 1 emerald + text: 4个泥土 items: - - bucket:1 - - emerald:1 - - '{p=0.3}music_disc_blocks:1' - currency: 30 - xp: 30 - commands: - - 'console: msg {player} &9https://en.wikipedia.org/wiki/Apple_strudel &2Click - the link for more info' - brownies: - name: '&eBrownies' - description: Create the famous American brownies. - type: onPlayer - requiredItems: - - wheat:16;+5 - - sugar:16;+5 - - egg:16;+5 - - ink_sac:16;+5 - - milk_bucket:1 - displayItem: dark_oak_slab - lockedDisplayItem: yellow_stained_glass_pane + - dirt:4 + - '{p=0.10}beetroot_seeds:1' + - '{p=0.05}red_sand:1' + - '{p=0.01}diamond:1' + currency: 25 + xp: 25 + woolcollector: + name: '&6剪羊毛的时间到了' + description: 收获羊毛. + type: onPlayer + requiredItems: + - white_wool:64;+2 + displayItem: white_wool + lockedDisplayItem: brown_stained_glass_pane reward: - text: 1 bucket, 1 disk, 4 emerald + text: 4个泥土, 5只羊 items: - - bucket:1 - - emerald:4 - - music_disc_far:1 - currency: 60 - xp: 60 - commands: - - 'console: msg {player} &9https://en.wikipedia.org/wiki/Chocolate_brownie - &2Click the link for more info' + - dirt:4 + - sheep_spawn_egg:5 + - '{p=0.10}beetroot_seeds:1' + - '{p=0.05}red_sand:1' + - '{p=0.01}diamond:1' + currency: 50 + xp: 50 repeatReward: - text: 1 bucket, 30% chance on 1 disk, 1 emerald + text: 4个泥土 items: - - bucket:1 - - emerald:1 - - '{p=0.3}music_disc_far:1' - currency: 30 - xp: 30 - commands: - - 'console: msg {player} &9https://en.wikipedia.org/wiki/Chocolate_brownie - &2Click the link for more info' - pastafunghi: - name: '&ePasta Funghi' - description: Create the famous Italian Pasta Funghi. - type: onPlayer - requiredItems: - - wheat:32;+8 - - egg:32;+8 - - brown_mushroom:16;+4 - - red_mushroom:16;+4 - - milk_bucket:1 - displayItem: red_mushroom_block - lockedDisplayItem: yellow_stained_glass_pane + - dirt:4 + - '{p=0.10}beetroot_seeds:1' + - '{p=0.05}red_sand:1' + - '{p=0.01}diamond:1' + currency: 25 + xp: 25 + delicioussoup: + name: '&1美味的汤' + description: 用甜菜 蘑菇 兔子做出美味的汤. + type: onPlayer + requiredItems: + - beetroot:96;+16 + - carrot:8;+5 + - brown_mushroom:16;+16 + - red_mushroom:8;+5 + - rabbit_foot:8;+5 + repeatable: true + displayItem: rabbit_foot reward: - text: 2 rabbits, 1 bucket + text: 1 个桶, 1 个CD机, 1 绿宝石矿 items: - bucket:1 - - rabbit_spawn_egg:2 + - jukebox:1 + - emerald_ore:1 currency: 60 xp: 60 - commands: - - console:msg {player} &9https://en.wikipedia.org/wiki/Pasta &2Click the - link for more info repeatReward: - text: 30% chance on 1 disk, 1 emerald + text: 1 个桶, 40% 的机会获得一个CD机, 1 个绿宝石, 5% 的机会获得一个潜影壳 items: + - bucket:1 - emerald:1 - - '{p=0.3}music_disc_13:1' + - '{p=0.4}jukebox:1' + - '{p=0.05}shulker_shell:1' currency: 30 xp: 30 - commands: - - console:msg {player} &9https://en.wikipedia.org/wiki/Pasta &2Click the - link for more info - chocolate: - name: '&eBelgian chocolate' - description: Create the famous belgian chocolate. + turtle: + name: '&1绿帽子' + description: 养海龟. type: onPlayer requiredItems: - - sugar:16;+5 - - ink_sac:16;+5 - - milk_bucket:1 - displayItem: pig_spawn_egg - lockedDisplayItem: yellow_stained_glass_pane + - turtle_helmet:1 + repeatable: true + displayItem: turtle_helmet reward: - text: 1 bucket, 1 disk, 4 emeralds + text: 1 绿宝石矿 items: - - bucket:1 - - emerald:4 - - music_disc_mall:1 + - emerald_ore:1 currency: 60 xp: 60 - commands: - - 'console: msg {player} &9https://en.wikipedia.org/wiki/Belgian_chocolate - &2Click the link for more info' repeatReward: - text: 1 bucket, 30% chance on 1 disk, 1 emerald + text: 1 个绿宝石, 10% 的机会获得一个潜影壳 items: - bucket:1 - emerald:1 - - '{p=0.3}music_disc_mall:1' + - '{p=0.4}jukebox:1' + - '{p=0.1}shulker_shell:1' currency: 30 xp: 30 - commands: - - 'console: msg {player} &9https://en.wikipedia.org/wiki/Belgian_chocolate - &2Click the link for more info' - baker: - name: '&ePâtisserie' - description: Bake cakes, pumpkin pies, and cookies. + adeptfarmerfinished: + name: '&a完成入门农民任务' + description: 完成入门农民任务。 + requiredChallenges: + - hellfarm + - wheatfarmer + - potatofarmer + - carrotfarmer + - woolcollector + - delicioussoup + - turtle + displayItem: crafting_table + reward: + text: 2个钻石,5个绿宝石 + items: + - emerald:5 + - diamond:2 + currency: 100 + xp: 100 + Tier8: + name: '&a入门怪物猎人' + displayItem: golden_sword + resetInHours: 20 + requires: + challenges: + - netherportal + challenges: + witherhunter: + name: '&4啊,来战吧!' + description: 宰杀地狱怪物. type: onPlayer requiredItems: - - cake:5;+1 - - pumpkin_pie:5;+1 - - cookie:128;+4 - displayItem: cake - lockedDisplayItem: yellow_stained_glass_pane + - blaze_rod:10;+2 + - ender_pearl:1;+2 + - ghast_tear:25;+10 + - nether_wart:64;+64 + - quartz:2;+2 + repeatable: true + displayItem: ender_pearl + takeItems: true reward: - text: 5 gold ore, 1 diamond + text: 1 个钻石 1 个甜菜种子 items: - - gold_ore:5 - diamond:1 - currency: 80 - xp: 80 - repeatReward: - text: 2 iron ore, 1 gold ore - items: - - iron_ore:2 - - gold_ore:1 + - beetroot_seeds:1 currency: 40 xp: 40 - -# DO NOT CHANGE THE VERSION! You will break the conversion and unexpected things will happen! -version: 107 + repeatReward: + text: 2 个金矿 + items: + - gold_ore:2 + currency: 20 + xp: 20 +version: 108 diff --git a/uSkyBlock-Core/src/main/resources/config.yml b/uSkyBlock-Core/src/main/resources/config.yml index 8f3ee1020..b9932a3f3 100644 --- a/uSkyBlock-Core/src/main/resources/config.yml +++ b/uSkyBlock-Core/src/main/resources/config.yml @@ -26,14 +26,14 @@ options: maxSpam: 2000 # [string] The name of the skyblock world, will be automatically generated if it doesn't exist. - worldName: skyworld + worldName: world_acidisland # [integer] Area around 0,0 where islands will not be created to protect spawn. spawnSize: 64 island: # [integer] The y-coordinate (height) where islands are spawned. - height: 150 + height: 63 # [integer] The number of blocks between islands. distance: 128 @@ -44,7 +44,7 @@ options: # [filename] The schematic to use for island generation. # Put your schematic in the 'uSkyBlock/schematics' folder, you don't need to add the '.schematic' part below. - schematicName: default + schematicName: acidIsland # [true/false] If true, remove all hostile mobs when a player teleports back to their island. removeCreaturesByTeleport: false @@ -377,11 +377,11 @@ worldguard: exit-message: true nether: enabled: true - height: 75 + height: 35 lava_level: 7 activate-at: level: 100 - schematicName: uSkyBlockNether + schematicName: acidIslandNether terraform-enabled: true # The distance to search for valid terra-form location. diff --git a/uSkyBlock-Core/src/main/resources/plugin.yml b/uSkyBlock-Core/src/main/resources/plugin.yml index 58712da9c..27a80f6ff 100644 --- a/uSkyBlock-Core/src/main/resources/plugin.yml +++ b/uSkyBlock-Core/src/main/resources/plugin.yml @@ -17,7 +17,9 @@ softdepend: - FastAsyncWorldEdit # placeholders - MVdWPlaceholderAPI -api-version: 1.21.4 + # signshop + - SignShop +api-version: 1.21.1 libraries: - com.google.code.gson:gson:2.10.1 - com.google.inject:guice:7.0.0 diff --git a/uSkyBlock-Core/src/main/resources/schematics/acidIsland.schem b/uSkyBlock-Core/src/main/resources/schematics/acidIsland.schem new file mode 100644 index 000000000..e0b72e993 Binary files /dev/null and b/uSkyBlock-Core/src/main/resources/schematics/acidIsland.schem differ diff --git a/uSkyBlock-Core/src/main/resources/schematics/acidIslandNether.schem b/uSkyBlock-Core/src/main/resources/schematics/acidIslandNether.schem new file mode 100644 index 000000000..525dd7636 Binary files /dev/null and b/uSkyBlock-Core/src/main/resources/schematics/acidIslandNether.schem differ diff --git a/uSkyBlock-Core/src/main/resources/schematics/default.schematic b/uSkyBlock-Core/src/main/resources/schematics/default.schematic deleted file mode 100644 index f8f18ee5e..000000000 Binary files a/uSkyBlock-Core/src/main/resources/schematics/default.schematic and /dev/null differ diff --git a/uSkyBlock-Core/src/main/resources/schematics/uSkyBlockNether.schem b/uSkyBlock-Core/src/main/resources/schematics/uSkyBlockNether.schem deleted file mode 100644 index 3c2b2c0e6..000000000 Binary files a/uSkyBlock-Core/src/main/resources/schematics/uSkyBlockNether.schem and /dev/null differ diff --git a/uSkyBlock-Plugin/src/main/resources/version.json b/uSkyBlock-Plugin/src/main/resources/version.json index 28fc1049e..671295622 100644 --- a/uSkyBlock-Plugin/src/main/resources/version.json +++ b/uSkyBlock-Plugin/src/main/resources/version.json @@ -1 +1 @@ -{"version": "cong", "minecraft-version": "1.21.4-R0.1-SNAPSHOT"} +{"version": "cong", "minecraft-version": "1.21.1-R0.1-SNAPSHOT"}