diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index ffb6e98..174f9df 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -3,7 +3,7 @@ name: Bug report about: 不具合を報告するIssueを作成します。 title: '' labels: 👮security -assignees: m2en +assignees: acrylic-style --- diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 13d0c1d..3d7efa8 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -3,7 +3,7 @@ name: Feature request about: 機能のリクエストなどを送信します。 title: '' labels: ✨enhancement -assignees: m2en +assignees: acrylic-style --- diff --git a/.github/codeql-config.yml b/.github/codeql-config.yml deleted file mode 100644 index 1095a04..0000000 --- a/.github/codeql-config.yml +++ /dev/null @@ -1,4 +0,0 @@ -paths: - - src -paths-ignore: - - '/**/*.md' diff --git a/.github/labeler.yml b/.github/labeler.yml deleted file mode 100644 index d8d3c03..0000000 --- a/.github/labeler.yml +++ /dev/null @@ -1,2 +0,0 @@ -🔮workflow (gh-actions): - - .github/workflows/*.yml diff --git a/.github/labels.yml b/.github/labels.yml deleted file mode 100644 index efd43c9..0000000 --- a/.github/labels.yml +++ /dev/null @@ -1,52 +0,0 @@ -# AfnwCore2 Label - b5f0aa - -- name: '🔒not compatible' - color: 'd4d4d4' -- name: '📡help wanted' - color: 'f0dc59' -- name: '📡duplicate' - description: 'This issue or pull request already exists' - color: 'f0dc59' -- name: '📡invalid' - description: 'This issue or pull request is invalid' - color: 'f0dc59' -- name: '📡wrong' - description: 'This issue or pull request is wrong' - color: 'f0dc59' -- name: '🏃high' - color: 'fc032c' -- name: '🚶low' - color: 'f7b2bd' -- name: '🐛bug' - color: 'ff0026' -- name: '🐛bug(JE v1.17.x)' - color: 'ff0026' -- name: '🐛bug(Not JE v1.17.x)' - color: 'ff0026' -- name: '👮security' - color: 'ff0026' -- name: '👮security(CVE)' - color: 'ff0026' -- name: '✨design' - color: 'ee9cff' -- name: '✨improvement' - color: 'ee9cff' -- name: '✨enhancement' - color: 'ee9cff' -- name: '✨documentation' - color: 'ee9cff' -- name: '🔮workflow (gh-actions)' - color: 'ee9cff' -- name: '🔮renovate: update' - color: 'ee9cff' -- name: '🔮renovate: security' - color: 'ee9cff' -- name: '💃feature request' - description: '鯖民からの要望' - color: 'f0dc59' -- name: '💃no context' - description: 'No Context, 内容が薄いもう少し詳しいものがほしいものに付与' - color: 'f0dc59' -- name: '💃feature request' - description: 'This issue or pull request is invalid' - color: 'f0dc59' diff --git a/.github/renovate.json b/.github/renovate.json deleted file mode 100644 index b0f6723..0000000 --- a/.github/renovate.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": [ - "github>m2en/renovate-config" - ], - "labels": ["\uD83D\uDD2Erenovate: update"] -} diff --git a/.github/workflows/auto-labeler.yml b/.github/workflows/auto-labeler.yml deleted file mode 100644 index ab1d8f8..0000000 --- a/.github/workflows/auto-labeler.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Auto Labeler -on: - - pull_request_target - -jobs: - auto-label: - permissions: - contents: read - pull-requests: write - runs-on: ubuntu-latest - steps: - - uses: actions/labeler@v4 - with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index d38f469..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Java17 Build - -on: - pull_request: - paths-ignore: - - '**.md' - -jobs: - build: - name: Build with Java17 - - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - - steps: - - uses: actions/checkout@v3 - - - uses: actions/setup-java@v3 - with: - distribution: 'adopt' - java-version: '17' - cache: 'gradle' - - - name: Prepare gradle.properties - run: | - mkdir -p $HOME/.gradle - echo "azisabaNmsUsername=${{ secrets.REPO_USERNAME }}" >> $HOME/.gradle/gradle.properties - echo "azisabaNmsPassword=${{ secrets.REPO_PASSWORD }}" >> $HOME/.gradle/gradle.properties - - run: ./gradlew build diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 5626c1a..0000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: CodeQL - -on: - push: - branches: - - main - pull_request: - branches: - - main - schedule: - - cron: '32 6 * * 5' - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - matrix: - language: [ 'java' ] - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - config-file: ./.github/codeql-config.yml - - - name: Autobuild - uses: github/codeql-action/autobuild@v2 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..50e6bfe --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,16 @@ +name: Deploy +on: + push: + branches: [ main ] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: 'adoptium' + java-version: '21' + - run: ./gradlew shadowJar + - run: 'curl -X PUT --data-binary @build/libs/AfnwCore2-*-all.jar "https://saba8-deploy.azisaba.dev/deploy?secret={{ secrets.DEPLOYMAN_SECRET_SABA8 }}&token={{ secrets.DEPLOYMAN_TOKEN }}&filename=AfnwCore2.jar"' diff --git a/.github/workflows/label-sync.yml b/.github/workflows/label-sync.yml deleted file mode 100644 index 6ca8de9..0000000 --- a/.github/workflows/label-sync.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: label sync - -on: - push: - branches: - - main - paths: - - '.github/labels.yml' - - '.github/workflows/label-sync.yml' - -jobs: - label-sync: - runs-on: ubuntu-latest - steps: - - name: checkout - uses: actions/checkout@v3 - - - name: label sync - if: success() - uses: crazy-max/ghaction-github-labeler@v4 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index 19088fa..96af4c1 100644 --- a/README.md +++ b/README.md @@ -11,18 +11,20 @@ - [Intellij IDEA Ultimate](https://www.jetbrains.com/idea/) - [Java SE Development Kit 17](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) - Java 17 -- Gradle 7.3 -- Paper 1.17.1 (gh 411) +- Gradle 8.9 +- Paper 1.20.2 ## 前提プラグイン AfnwCore2を導入したPaperを起動するには以下のプラグインが `plugins/` に導入されている必要があります。 - [EssentialsX](https://essentialsx.net/downloads.html) +- Votifier +- [ItemStash](https://github.com/AzisabaNetwork/ItemStash) ## 使用API -- [paper-api:1.17.1-R0.1-SNAPSHOT](https://papermc.io/repo/repository/maven-public/) +- [paper-api:1.20.2-R0.1-SNAPSHOT](https://papermc.io/repo/repository/maven-public/) - [JavaDoc](https://papermc.io/repo/repository/maven-public/) - [EssentialsX API](https://repo.essentialsx.net/releases/) @@ -30,7 +32,7 @@ AfnwCore2を導入したPaperを起動するには以下のプラグインが `p ```sh git clone https://github.com/AfnwTeam/AfnwCore2.git -gradle build +gradlew reobfJar ``` なお、Intellij IDEAを使用している場合はすべてGUI上で使用可能です。 diff --git a/build.gradle.kts b/build.gradle.kts index 0f5ba63..b8ee7df 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,48 +1,39 @@ plugins { + id("io.papermc.paperweight.userdev") version "2.0.0-beta.19" java - id("com.github.johnrengelman.shadow") version "8.1.1" + id("com.gradleup.shadow") version "9.3.0" } group = "net.azisaba.afnw" -version = "1.5.0-SNAPSHOT" +version = "2.1.0-SNAPSHOT" repositories { mavenCentral() - maven { - name = "papermc-repo" - url = uri("https://papermc.io/repo/repository/maven-public/") - } - maven { - name = "sonatype" - url = uri("https://oss.sonatype.org/content/groups/public/") - } - maven { - name = "azisaba" - url = uri("https://repo.azisaba.net/repository/maven-public/") - } - maven { - name = "essentialsx" - url = uri("https://repo.essentialsx.net/releases/") - } - if (properties["azisabaNmsUsername"] != null && properties["azisabaNmsPassword"] != null) { - maven { - name = "azisabaNms" - credentials(PasswordCredentials::class) - url = uri("https://repo.azisaba.net/repository/nms/") - } - } - mavenLocal() + maven("https://repo.papermc.io/repository/maven-public/") + maven("https://oss.sonatype.org/content/groups/public/") + maven("https://repo.azisaba.net/repository/maven-public/") + maven("https://repo.essentialsx.net/releases/") + maven("https://mvn.lumine.io/repository/maven-public/") + maven("https://jitpack.io") } dependencies { - implementation("net.blueberrymc:native-util:2.1.0") - compileOnly("io.papermc.paper:paper-api:1.20.2-R0.1-SNAPSHOT") - compileOnly("org.spigotmc:spigot:1.20.2-R0.1-SNAPSHOT") +// implementation("net.blueberrymc:native-util:2.1.0") + implementation("xyz.acrylicstyle.java-util:common:2.1.1") + implementation("xyz.acrylicstyle.java-util:expression:2.1.1") compileOnly("net.azisaba.ballotbox:receiver:1.0.1") - compileOnly("net.essentialsx:EssentialsX:2.19.7") + compileOnly("net.azisaba:ItemStash:1.0.0-SNAPSHOT") + compileOnly("net.essentialsx:EssentialsX:2.20.1") { + exclude("org.spigotmc", "spigot-api") + } + compileOnly("io.lumine:Mythic-Dist:5.4.0") + compileOnly("io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT") + compileOnly("net.azisaba:TAB-BukkitBridge:3.1.0") + compileOnly("org.jetbrains:annotations:26.0.1") + paperweight.paperDevBundle("1.21.11-R0.1-SNAPSHOT") } -java.toolchain.languageVersion.set(JavaLanguageVersion.of(17)) +java.toolchain.languageVersion.set(JavaLanguageVersion.of(21)) tasks { compileJava { @@ -57,4 +48,8 @@ tasks { filteringCharset = "UTF-8" } } + + shadowJar { + relocate("xyz.acrylicstyle.util", "net.azisaba.afnwcore2.lib.xyz.acrylicstyle.util") + } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e411586..ac57dd1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/AfnwCore2.java b/src/main/java/net/azisaba/afnw/afnwcore2/AfnwCore2.java index 429001e..f77cd3c 100644 --- a/src/main/java/net/azisaba/afnw/afnwcore2/AfnwCore2.java +++ b/src/main/java/net/azisaba/afnw/afnwcore2/AfnwCore2.java @@ -1,31 +1,34 @@ package net.azisaba.afnw.afnwcore2; +import java.util.HashSet; import java.util.Objects; -import net.azisaba.afnw.afnwcore2.commands.AfnwCommand; -import net.azisaba.afnw.afnwcore2.commands.BedCommand; -import net.azisaba.afnw.afnwcore2.commands.BonusCommand; -import net.azisaba.afnw.afnwcore2.commands.ConfigReloadCommand; -import net.azisaba.afnw.afnwcore2.commands.EnderchestCommand; -import net.azisaba.afnw.afnwcore2.commands.LobbyCommand; -import net.azisaba.afnw.afnwcore2.commands.MaintenanceCommand; -import net.azisaba.afnw.afnwcore2.commands.RespawnCommand; -import net.azisaba.afnw.afnwcore2.commands.TicketCommand; -import net.azisaba.afnw.afnwcore2.commands.TrashCommand; -import net.azisaba.afnw.afnwcore2.commands.TutorialCommand; -import net.azisaba.afnw.afnwcore2.commands.VoidCommand; -import net.azisaba.afnw.afnwcore2.commands.VoteCommand; +import java.util.Set; +import java.util.UUID; + +import net.azisaba.afnw.afnwcore2.commands.*; import net.azisaba.afnw.afnwcore2.listeners.block.CropsBreakCanceller; import net.azisaba.afnw.afnwcore2.listeners.block.SaplingBreakCanceller; +import net.azisaba.afnw.afnwcore2.listeners.entity.DropShardListener; import net.azisaba.afnw.afnwcore2.listeners.entity.WitherSpawn; import net.azisaba.afnw.afnwcore2.listeners.other.VoteListener; import net.azisaba.afnw.afnwcore2.listeners.player.*; +import net.azisaba.afnw.afnwcore2.util.TheTAB; import net.azisaba.afnw.afnwcore2.util.data.PlayerData; import net.azisaba.afnw.afnwcore2.util.data.PlayerDataSave; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.world.entity.ai.attributes.Attributes; +import net.minecraft.world.entity.ai.attributes.RangedAttribute; import org.bukkit.Bukkit; import org.bukkit.World; +import org.bukkit.craftbukkit.entity.CraftDolphin; import org.bukkit.entity.Dolphin; +import org.bukkit.entity.Player; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; /** * AfnwCore2 のメインクラス @@ -34,6 +37,7 @@ * @see org.bukkit.plugin.java.JavaPlugin */ public class AfnwCore2 extends JavaPlugin { + public final Set pvpEnabled = new HashSet<>(); @Override public void onEnable() { @@ -41,6 +45,9 @@ public void onEnable() { // コンフィグのロード saveDefaultConfig(); + if (getConfig().getBoolean("settings.require-item-stash", false) && Bukkit.getPluginManager().getPlugin("ItemStash") == null) { + throw new RuntimeException("ItemStashプラグインがインストールされていません。settings > require-item-stashをfalseにするか、ItemStashをインストール、またはエラーを確認してください。"); + } getLogger().info("コンフィグ ロード完了"); // プレイヤーデータのロード @@ -58,16 +65,25 @@ public void onEnable() { getLogger().info("Listener 設定中...."); /* プレイヤーリスナー */ pluginEvent.registerEvents(new JoinListener(this, data), this); - pluginEvent.registerEvents(new QuitListener(), this); + pluginEvent.registerEvents(new QuitListener(this), this); pluginEvent.registerEvents(new DeathListener(), this); pluginEvent.registerEvents(new FirstPlayerJoinListener(this, data), this); pluginEvent.registerEvents(new AFKListener(this), this); pluginEvent.registerEvents(new RespawnEnvironment(this), this); - pluginEvent.registerEvents(new SuperAfnwTicketListener(), this); + pluginEvent.registerEvents(new SuperAfnwTicketListener(this), this); + pluginEvent.registerEvents(new VillagerInteractListener(), this); + pluginEvent.registerEvents(new VillagerProtectorListener(), this); + pluginEvent.registerEvents(new BedListener(this), this); + pluginEvent.registerEvents(new EnderDragonDisableListener(), this); + pluginEvent.registerEvents(new PvPListener(this), this); + pluginEvent.registerEvents(new BlessedRandomTeleporterListener(), this); + pluginEvent.registerEvents(new FishingListener(), this); + pluginEvent.registerEvents(new TrashListener(this), this); /* エンティティリスナー */ pluginEvent.registerEvents(new WitherSpawn(this), this); + pluginEvent.registerEvents(new DropShardListener(), this); /* その他 */ - pluginEvent.registerEvents(new VoteListener(), this); + pluginEvent.registerEvents(new VoteListener(this, data), this); getLogger().info("Listener 設定完了"); /* ブロックリスナー */ pluginEvent.registerEvents(new CropsBreakCanceller(), this); @@ -88,9 +104,22 @@ public void onEnable() { Objects.requireNonNull(getCommand("trash")).setExecutor(new TrashCommand(this)); Objects.requireNonNull(getCommand("maintenance")).setExecutor(new MaintenanceCommand()); Objects.requireNonNull(getCommand("bonus")).setExecutor(new BonusCommand(this, data)); + Objects.requireNonNull(getCommand("pvp")).setExecutor(new PvPCommand(this)); + Objects.requireNonNull(getCommand("mmgive")).setExecutor(new MMGiveCommand(this)); + Objects.requireNonNull(getCommand("mmgiveeval")).setExecutor(new MMGiveEvalCommand()); + Objects.requireNonNull(getCommand("safeteleport")).setExecutor(new SafeTeleportCommand()); getLogger().info("コマンド 設定完了"); - if(getConfig().getBoolean("settings.maintenance-mode-toggle", false)) { + Bukkit.getScheduler().runTask(this, () -> { + if (Bukkit.getPluginManager().isPluginEnabled("TAB-BukkitBridge")) { + TheTAB.enable(); + getLogger().info("TABの連携が有効です。"); + } else { + getLogger().info("TABの連携は無効です。"); + } + }); + + if (getConfig().getBoolean("settings.maintenance-mode-toggle", false)) { getServer().setWhitelist(true); getLogger().info("正常に起動しました。(メンテナンスモード)"); return; @@ -99,10 +128,28 @@ public void onEnable() { Bukkit.getScheduler().runTaskTimer(this, () -> { for (World world : Bukkit.getWorlds()) { for (Dolphin entity : world.getEntitiesByClass(Dolphin.class)) { - entity.remove(); + // Prevent DolphinSwimToTreasureGoal from being triggered + ((CraftDolphin) entity).getHandle().goalSelector.removeAllGoals(goal -> goal.getClass().getTypeName().equals("net.minecraft.world.entity.animal.EntityDolphin$a") || + goal.getClass().getTypeName().equals("net.minecraft.world.entity.animal.dolphin.Dolphin$DolphinSwimToTreasureGoal")); } } - }, 1, 1); + for (Player player : Bukkit.getOnlinePlayers()) { + boolean pvp = pvpEnabled.contains(player.getUniqueId()); + player.sendActionBar( + Component.text("⚔ PvP: ") + .append(Component.text(pvp ? "有効" : "無効", pvp ? NamedTextColor.RED : NamedTextColor.GREEN)) + ); + } + }, 10, 10); + + var optionalAttributeReference = Objects.requireNonNull(BuiltInRegistries.ATTRIBUTE.get(Attributes.LUCK.unwrap().left().orElseThrow())); + optionalAttributeReference.ifPresent(attributeReference -> { + var optional = attributeReference.unwrap().right(); + optional.ifPresent(attribute -> { + ((RangedAttribute) attribute).maxValue = Double.MAX_VALUE; + getLogger().info("Luck attribute max value set to Double.MAX_VALUE"); + }); + }); getLogger().info("正常に起動しました。"); } @@ -114,4 +161,9 @@ public void onDisable() { } getLogger().info("正常に終了しました。"); } + + @NotNull + public static Logger getPluginLogger() { + return getPlugin(AfnwCore2.class).getSLF4JLogger(); + } } diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/commands/AfnwCommand.java b/src/main/java/net/azisaba/afnw/afnwcore2/commands/AfnwCommand.java index 6650702..8049789 100644 --- a/src/main/java/net/azisaba/afnw/afnwcore2/commands/AfnwCommand.java +++ b/src/main/java/net/azisaba/afnw/afnwcore2/commands/AfnwCommand.java @@ -5,12 +5,17 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Optional; + import net.azisaba.afnw.afnwcore2.util.data.PlayerData; import net.azisaba.afnw.afnwcore2.util.item.AfnwScaffold; import net.azisaba.afnw.afnwcore2.util.item.AfnwTicket; +import net.azisaba.afnw.afnwcore2.util.item.ItemUtil; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Material; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; @@ -34,36 +39,53 @@ private static boolean isAllowed(Material type) { if (!type.isItem()) { return false; } - switch (type) { - case BEDROCK: - case STRUCTURE_BLOCK: - case STRUCTURE_VOID: - case COMMAND_BLOCK: - case CHAIN_COMMAND_BLOCK: - case COMMAND_BLOCK_MINECART: - case REPEATING_COMMAND_BLOCK: - case BARRIER: - case LIGHT: - case JIGSAW: - case END_PORTAL: - case KNOWLEDGE_BOOK: - case DEBUG_STICK: - case AIR: - case VOID_AIR: - case CAVE_AIR: - case BUNDLE: - return false; - default: - return true; - } + return switch (type) { + case BEDROCK, STRUCTURE_BLOCK, STRUCTURE_VOID, COMMAND_BLOCK, CHAIN_COMMAND_BLOCK, COMMAND_BLOCK_MINECART, + REPEATING_COMMAND_BLOCK, BARRIER, LIGHT, JIGSAW, END_PORTAL, KNOWLEDGE_BOOK, DEBUG_STICK, + TEST_INSTANCE_BLOCK, TEST_BLOCK, + AIR, VOID_AIR, CAVE_AIR -> false; + default -> true; + }; } @Contract("_ -> new") public static @NotNull ItemStack getRandomItem(int amount) { + return getRandomItem(0, amount); + } + + @Contract("_, _ -> new") + public static @NotNull ItemStack getRandomItem(int luck, int amount) { try { SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); List itemList = new ArrayList<>(Arrays.asList(Material.values())); itemList.removeIf(type -> !isAllowed(type)); + if (luck > 0) { + for (int i = 0; i < luck; i++) { + itemList.add(Material.DRIPSTONE_BLOCK); + itemList.add(Material.POINTED_DRIPSTONE); + itemList.add(Material.LAVA_BUCKET); + itemList.add(Material.DIRT); + itemList.add(Material.NETHERITE_BLOCK); + itemList.add(Material.DIAMOND_BLOCK); + itemList.add(Material.GOLD_BLOCK); + itemList.add(Material.ELYTRA); + itemList.add(Material.END_PORTAL_FRAME); + itemList.add(Material.ENDER_PEARL); + itemList.add(Material.ENDER_EYE); + itemList.add(Material.TRIDENT); + itemList.add(Material.ENCHANTED_GOLDEN_APPLE); + itemList.add(Material.NETHER_STAR); + itemList.add(Material.ANCIENT_DEBRIS); + itemList.add(Material.BLAZE_POWDER); + itemList.add(Material.SPAWNER); + itemList.add(Material.NETHERITE_INGOT); + itemList.add(Material.DIAMOND); + itemList.add(Material.GOLD_INGOT); + itemList.add(Material.NETHERITE_UPGRADE_SMITHING_TEMPLATE); + itemList.add(Material.REINFORCED_DEEPSLATE); + Arrays.stream(Material.values()).filter(m -> m.name().endsWith("_SPAWN_EGG")).forEach(itemList::add); + } + } return new ItemStack(itemList.get(random.nextInt(itemList.size() - 1)), amount); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); @@ -111,17 +133,20 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command int itemSize = config.getInt("vote.item-size", 1); int scaffoldSize = config.getInt("vote.scaffold-size", 8); - ItemStack afnwItem = getRandomItem(itemSize); + int luck = (int) Math.ceil(Optional.ofNullable(((Player) sender).getAttribute(Attribute.LUCK)).map(AttributeInstance::getValue).orElse(0.0)); + ItemStack afnwItem = getRandomItem(luck, itemSize); inv.removeItem(AfnwTicket.afnwTicket); - inv.addItem(afnwItem); + for (ItemStack value : inv.addItem(afnwItem).values()) { + ItemUtil.addToStashIfEnabledAsync(plugin, ((Player) sender).getUniqueId(), value); + } for (int i = 0; i < scaffoldSize; i++) { inv.addItem(AfnwScaffold.afnwScaffold); } sender.sendMessage(Component.text("アイテムと交換しました。").color(NamedTextColor.GOLD)); sender.sendMessage(Component.text( - "交換内容: " + afnwItem.getType() + " ×" + afnwItem.getAmount() + ", 足場ブロック ×" + scaffoldSize) + "交換内容: " + afnwItem.getType() + " ×" + itemSize + "、足場ブロック ×" + scaffoldSize) .color(NamedTextColor.GOLD)); return true; } diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/commands/MMGiveCommand.java b/src/main/java/net/azisaba/afnw/afnwcore2/commands/MMGiveCommand.java new file mode 100644 index 0000000..4063b43 --- /dev/null +++ b/src/main/java/net/azisaba/afnw/afnwcore2/commands/MMGiveCommand.java @@ -0,0 +1,89 @@ +package net.azisaba.afnw.afnwcore2.commands; + +import io.lumine.mythic.bukkit.MythicBukkit; +import io.lumine.mythic.core.items.MythicItem; +import net.azisaba.afnw.afnwcore2.util.Expr; +import net.azisaba.afnw.afnwcore2.util.item.ItemUtil; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.function.BiFunction; + +public record MMGiveCommand(@NotNull Plugin plugin) implements TabExecutor { + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + Player player = Bukkit.getPlayerExact(args[0]); + if (player == null) { + sender.sendMessage(Component.text("Player " + args[0] + " is offline (tried to give " + args[1] + " (extra: " + String.join(", ", args) + "))", NamedTextColor.RED)); + return true; + } + String mythicType = args[1]; + int amount = args.length == 2 ? 1 : Integer.parseInt(args[2]); + ItemStack stack = MythicBukkit.inst().getItemManager().getItemStack(mythicType, amount); + if (stack.hasItemMeta()) { + BiFunction modify = new BiFunction<>() { + @NotNull + @Override + public Component apply(Component component, String description) { + if (component instanceof TextComponent text) { + String replaced = Expr.evalAndReplace(player, text.content(), description); + if (replaced != null) { + component = text.content(replaced); + } + } + return component.children(component.children().stream().map(c -> apply(c, description)).toList()); + } + }; + ItemMeta meta = Objects.requireNonNull(stack.getItemMeta()); + if (meta.hasLore()) { + meta.lore(Objects.requireNonNull(meta.lore()).stream().map(line -> modify.apply(line, mythicType + " (lore)")).toList()); + } + if (meta.hasDisplayName()) { + meta.displayName(modify.apply(meta.displayName(), mythicType + " (name)")); + } + stack.setItemMeta(meta); + } + for (ItemStack value : player.getInventory().addItem(stack).values()) { + ItemUtil.addToStashIfEnabledAsync(plugin, player.getUniqueId(), value); + player.sendMessage(Component.text("インベントリがいっぱいのため、Stashに入りました。", NamedTextColor.RED)); + player.sendMessage( + Component.text("/pickupstashで受け取れます。", NamedTextColor.RED) + .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.RUN_COMMAND, "/pickupstash")) + .hoverEvent(HoverEvent.showText(Component.text("クリックでStashの画面を開く"))) + ); + } + return true; + } + + @NotNull + @Override + public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (args.length == 1) { + return Bukkit.getOnlinePlayers().stream().map(Player::getName).filter(s -> s.startsWith(args[0])).toList(); + } + if (args.length == 2) { + return MythicBukkit.inst() + .getItemManager() + .getItems() + .stream() + .map(MythicItem::getInternalName).filter(s -> s.startsWith(args[1])) + .toList(); + } + return Collections.emptyList(); + } +} diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/commands/MMGiveEvalCommand.java b/src/main/java/net/azisaba/afnw/afnwcore2/commands/MMGiveEvalCommand.java new file mode 100644 index 0000000..bedfb27 --- /dev/null +++ b/src/main/java/net/azisaba/afnw/afnwcore2/commands/MMGiveEvalCommand.java @@ -0,0 +1,46 @@ +package net.azisaba.afnw.afnwcore2.commands; + +import net.azisaba.afnw.afnwcore2.AfnwCore2; +import net.azisaba.afnw.afnwcore2.util.Expr; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +public class MMGiveEvalCommand implements TabExecutor { + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (!(sender instanceof Player player)) { + return true; + } + String src = String.join(" ", args); + try { + sender.sendMessage(String.valueOf(Expr.eval(player, src))); + } catch (Exception e) { + sender.sendMessage(Component.text(e.getClass().getName() + ": " + e.getMessage(), NamedTextColor.RED)); + AfnwCore2.getPluginLogger().error("Failed to evaluate script", e); + } + return true; + } + + @Override + public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { + if (!(sender instanceof Player)) { + return Collections.emptyList(); + } + try { + return Expr.getSuggestions(sender, String.join(" ", args)).collect(Collectors.toList()); + } catch (Exception e) { + String message = e.getMessage(); + return Collections.singletonList("§c" + message.substring(0, Math.min(150, message.length()))); + } + } +} diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/commands/PvPCommand.java b/src/main/java/net/azisaba/afnw/afnwcore2/commands/PvPCommand.java new file mode 100644 index 0000000..c63f166 --- /dev/null +++ b/src/main/java/net/azisaba/afnw/afnwcore2/commands/PvPCommand.java @@ -0,0 +1,34 @@ +package net.azisaba.afnw.afnwcore2.commands; + +import net.azisaba.afnw.afnwcore2.AfnwCore2; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; + +public record PvPCommand(@NotNull AfnwCore2 plugin) implements TabExecutor { + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (!(sender instanceof Player player)) { + return true; + } + if (plugin.pvpEnabled.remove(player.getUniqueId())) { + player.sendMessage(Component.text("⚔ PvPを無効にしました。", NamedTextColor.GOLD)); + } else { + plugin.pvpEnabled.add(player.getUniqueId()); + player.sendMessage(Component.text("⚔ PvPを有効にしました。", NamedTextColor.GOLD)); + } + return true; + } + + @Override + public @NotNull List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + return Collections.emptyList(); + } +} diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/commands/SafeTeleportCommand.java b/src/main/java/net/azisaba/afnw/afnwcore2/commands/SafeTeleportCommand.java new file mode 100644 index 0000000..163b16c --- /dev/null +++ b/src/main/java/net/azisaba/afnw/afnwcore2/commands/SafeTeleportCommand.java @@ -0,0 +1,59 @@ +package net.azisaba.afnw.afnwcore2.commands; + +import net.azisaba.afnw.afnwcore2.util.Expr; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabExecutor; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class SafeTeleportCommand implements TabExecutor { + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + Player player = Bukkit.getPlayerExact(args[0]); + if (player == null) { + sender.sendMessage(Component.text("Player was not found", NamedTextColor.RED)); + return true; + } + int x; + try { + x = Integer.parseInt(args[1]); + } catch (NumberFormatException e) { + x = (int) Objects.requireNonNull(Expr.eval(player, args[1])); + } + int y; + try { + y = Integer.parseInt(args[2]); + } catch (NumberFormatException e) { + y = (int) Objects.requireNonNull(Expr.eval(player, args[2])); + } + int z; + try { + z = Integer.parseInt(args[3]); + } catch (NumberFormatException e) { + z = (int) Objects.requireNonNull(Expr.eval(player, args[3])); + } + Location location = new Location(player.getWorld(), x + 0.5, y, z + 0.5); + player.teleport(location); + location.subtract(0, 1, 0).getBlock().setType(Material.HONEYCOMB_BLOCK); + return true; + } + + @Override + public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if (args.length == 1) { + return null; + } + return Collections.emptyList(); + } +} diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/commands/TrashCommand.java b/src/main/java/net/azisaba/afnw/afnwcore2/commands/TrashCommand.java index 31b1fd5..e37e719 100644 --- a/src/main/java/net/azisaba/afnw/afnwcore2/commands/TrashCommand.java +++ b/src/main/java/net/azisaba/afnw/afnwcore2/commands/TrashCommand.java @@ -1,12 +1,11 @@ package net.azisaba.afnw.afnwcore2.commands; +import net.azisaba.afnw.afnwcore2.gui.TrashInventory; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; -import org.bukkit.Bukkit; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; -import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; @@ -29,19 +28,7 @@ public boolean onCommand(@NotNull CommandSender sender, Command command, @NotNul return false; } - FileConfiguration config = plugin.getConfig(); - String trashName = config.getString("trash.name", "ゴミ箱"); - int trashSize = config.getInt("trash.size", 54); - if (trashSize % 9 == 0) { - config.set("trash.size", 54); - } - - trashGUI(p, trashSize, trashName); + p.openInventory(new TrashInventory().getInventory()); return true; } - - public void trashGUI(Player p, int size, String name) { - p.openInventory( - Bukkit.createInventory(null, size, Component.text(name, NamedTextColor.DARK_PURPLE))); - } } diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/commands/VoteCommand.java b/src/main/java/net/azisaba/afnw/afnwcore2/commands/VoteCommand.java index 714f20b..c7e2d49 100644 --- a/src/main/java/net/azisaba/afnw/afnwcore2/commands/VoteCommand.java +++ b/src/main/java/net/azisaba/afnw/afnwcore2/commands/VoteCommand.java @@ -37,8 +37,6 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command ChatColor.GOLD + """ 投票よろしくお願いします! - ・monocraft - https://monocraft.net/servers/xWBVrf1nqB2P0LxlMm2v ・JMS https://minecraft.jp/servers/azisaba.net """ diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/gui/TrashInventory.java b/src/main/java/net/azisaba/afnw/afnwcore2/gui/TrashInventory.java new file mode 100644 index 0000000..c2399af --- /dev/null +++ b/src/main/java/net/azisaba/afnw/afnwcore2/gui/TrashInventory.java @@ -0,0 +1,32 @@ +package net.azisaba.afnw.afnwcore2.gui; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; + +public class TrashInventory implements InventoryHolder { + public static final ItemStack trashItem = new ItemStack(Material.BARRIER); + private final Inventory inventory = Bukkit.createInventory(this, 54, Component.text("ゴミ箱", NamedTextColor.RED)); + + static { + ItemMeta meta = trashItem.getItemMeta(); + meta.displayName(Component.text("ゴミを捨てる", NamedTextColor.RED)); + trashItem.setItemMeta(meta); + } + + public TrashInventory() { + inventory.setItem(53, trashItem); + } + + @NotNull + @Override + public Inventory getInventory() { + return inventory; + } +} diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/listeners/entity/DropShardListener.java b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/entity/DropShardListener.java new file mode 100644 index 0000000..cbe298a --- /dev/null +++ b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/entity/DropShardListener.java @@ -0,0 +1,29 @@ +package net.azisaba.afnw.afnwcore2.listeners.entity; + +import io.lumine.mythic.bukkit.MythicBukkit; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDeathEvent; + +import java.util.Optional; + +public class DropShardListener implements Listener { + @EventHandler + public void onEntityDeath(EntityDeathEvent e) { + if (e.getEntity().getKiller() == null) { + return; + } + Player killer = e.getEntity().getKiller(); + double baseChance = 0.001; // 0.1% + double playerLuck = Optional.ofNullable(killer.getAttribute(Attribute.LUCK)).map(AttributeInstance::getValue).orElse(0.0); + double chance = baseChance + playerLuck * 0.0001; // 0.01% per luck level + // calculate chance + if (Math.random() < chance) { + // drop shard + killer.getInventory().addItem(MythicBukkit.inst().getItemManager().getItemStack("Rare_Lootbox_Shard")); + } + } +} diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/listeners/other/VoteListener.java b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/other/VoteListener.java index 9031549..e9223ef 100644 --- a/src/main/java/net/azisaba/afnw/afnwcore2/listeners/other/VoteListener.java +++ b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/other/VoteListener.java @@ -1,12 +1,105 @@ package net.azisaba.afnw.afnwcore2.listeners.other; import com.vexsoftware.votifier.model.VotifierEvent; +import net.azisaba.afnw.afnwcore2.AfnwCore2; +import net.azisaba.afnw.afnwcore2.util.data.PlayerData; +import net.azisaba.afnw.afnwcore2.util.item.AfnwTicket; +import net.azisaba.afnw.afnwcore2.util.item.ItemUtil; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.OfflinePlayer; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; -public class VoteListener implements Listener { +public record VoteListener(AfnwCore2 plugin, PlayerData playerData) implements Listener { @EventHandler public void onVote(VotifierEvent e) { - // + handleVote(e); + handleVote(e); + } + + public void handleVote(VotifierEvent e) { + FileConfiguration config = plugin.getConfig(); + int ticketSize = config.getInt("vote.send-ticket-size", 1); + int bonusLine = config.getInt("vote.bonus-line", 9); + Player sendTarget = Bukkit.getPlayerExact(e.getVote().getUsername()); + + ItemStack ticketItem = AfnwTicket.afnwTicket.clone(); + ticketItem.setAmount(ticketSize); + + Bukkit.broadcast( + Component.text("[", NamedTextColor.GOLD) + .append(Component.text("Broadcast", NamedTextColor.DARK_RED)) + .append(Component.text("] ", NamedTextColor.GOLD)) + .append(Component.text(e.getVote().getUsername(), NamedTextColor.RED)) + .append(Component.text("が" + e.getVote().getServiceName() + "で投票しました!", NamedTextColor.DARK_GREEN))); + if (sendTarget != null) { + Inventory inv = sendTarget.getInventory(); + + for (ItemStack stack : inv.addItem(ticketItem).values()) { + ItemUtil.addToStashIfEnabledAsync(plugin, sendTarget.getUniqueId(), stack); + } + + FileConfiguration dataFile = playerData.getPlayerData(); + int voteCount = dataFile.getInt((sendTarget.getUniqueId().toString())); + voteCount++; + if (voteCount >= bonusLine) { + for (int i = 0; i < 10; i++) { + for (ItemStack value : inv.addItem(AfnwTicket.afnwTicket).values()) { + ItemUtil.addToStashIfEnabledAsync(plugin, sendTarget.getUniqueId(), value); + } + } + ItemStack netherStar = new ItemStack(Material.NETHER_STAR); + ItemMeta meta = netherStar.getItemMeta(); + meta.displayName(Component.text("投票ボーナス", NamedTextColor.YELLOW)); + netherStar.setItemMeta(meta); + for (ItemStack value : inv.addItem(netherStar).values()) { + ItemUtil.addToStashIfEnabledAsync(plugin, sendTarget.getUniqueId(), value); + } + sendTarget.sendMessage(Component.text("* 投票ボーナスとしてチケット10枚とネザースターを獲得しました。") + .color(NamedTextColor.YELLOW)); + sendTarget.sendMessage( + Component.text("* 投票ボーナスがリセットされました。次回以降の投票から有効です。").color(NamedTextColor.YELLOW)); + plugin.getSLF4JLogger().info(sendTarget.getName() + "が投票ボーナスを獲得しました。"); + voteCount = 0; + } + dataFile.set(sendTarget.getUniqueId().toString(), voteCount); + playerData.savePlayerData(); + + // 成功した趣旨の情報送信 + plugin.getSLF4JLogger().info(sendTarget.getName() + "へのチケット送信に成功しました。配布数:" + ticketSize); + // 通知 + sendTarget.sendMessage(Component.text("チケットを入手しました。/afnwを実行することでアイテムと交換することができます。", + NamedTextColor.LIGHT_PURPLE)); + sendTarget.sendMessage(Component.text("投票ボーナスまで: " + voteCount + "/" + config.getInt("vote.bonus-line", 10), NamedTextColor.YELLOW)); + } else { + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + OfflinePlayer player = Bukkit.getOfflinePlayer(e.getVote().getUsername()); + ItemUtil.addToStashIfEnabled(player.getUniqueId(), ticketItem); + FileConfiguration dataFile = playerData.getPlayerData(); + int voteCount = dataFile.getInt((player.getUniqueId().toString())); + voteCount++; + if (voteCount >= bonusLine) { + for (int i = 0; i < 10; i++) { + ItemUtil.addToStashIfEnabled(player.getUniqueId(), AfnwTicket.afnwTicket); + } + ItemUtil.addToStashIfEnabled(player.getUniqueId(), new ItemStack(Material.NETHER_STAR)); + plugin.getSLF4JLogger().info(player.getName() + " (" + player.getUniqueId() + ")が投票ボーナスを獲得しました。"); + voteCount = 0; + } + dataFile.set(player.getUniqueId().toString(), voteCount); + playerData.savePlayerData(); + + // 成功した趣旨の情報送信 + plugin.getSLF4JLogger().info(player.getName() + " (" + player.getUniqueId() + ")へのチケット送信に成功しました。配布数:" + ticketSize); + }); + } } } diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/BedListener.java b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/BedListener.java new file mode 100644 index 0000000..3c8f6bd --- /dev/null +++ b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/BedListener.java @@ -0,0 +1,41 @@ +package net.azisaba.afnw.afnwcore2.listeners.player; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class BedListener implements Listener { + private final Map clicks = new HashMap<>(); + private final Plugin plugin; + + public BedListener(@NotNull Plugin plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onInteract(PlayerInteractEvent e) { + if (e.getClickedBlock() == null) return; + if (e.getAction() != Action.RIGHT_CLICK_BLOCK) return; + if (e.getClickedBlock().getType().name().contains("_BED")) { + int current = clicks.getOrDefault(e.getPlayer().getUniqueId(), 0); + if (current < 2) { + e.getPlayer().sendMessage(Component.text("あと" + (2 - current) + "回クリックしてください...", NamedTextColor.YELLOW)); + e.setCancelled(true); + clicks.put(e.getPlayer().getUniqueId(), current + 1); + Bukkit.getScheduler().runTaskLater(plugin, () -> clicks.remove(e.getPlayer().getUniqueId()), 20 * 3); + } else { + clicks.remove(e.getPlayer().getUniqueId()); + } + } + } +} diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/BlessedRandomTeleporterListener.java b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/BlessedRandomTeleporterListener.java new file mode 100644 index 0000000..47ab94e --- /dev/null +++ b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/BlessedRandomTeleporterListener.java @@ -0,0 +1,46 @@ +package net.azisaba.afnw.afnwcore2.listeners.player; + +import net.azisaba.afnw.afnwcore2.util.Expr; +import net.azisaba.afnw.afnwcore2.util.item.ItemUtil; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerItemConsumeEvent; + +/** + * Blessed Random Teleporterの実際の動作 + */ +public record BlessedRandomTeleporterListener() implements Listener { + @EventHandler + public void onPlayerItemConsume(PlayerItemConsumeEvent e) { + if (!"Blessed_Random_Teleporter".equals(ItemUtil.getMythicType(e.getItem()))) return; + String worldName = e.getPlayer().getWorld().getName(); + if (!worldName.equals("afnw") && !worldName.equals("afnw_nether") && !worldName.equals("afnw_the_end")) { + e.getPlayer().sendMessage(Component.text("このワールドでは使えません!", NamedTextColor.RED)); + e.setCancelled(true); + return; + } + Location location; + do { + location = new Location(e.getPlayer().getWorld(), Expr.INSTANCE.randomInt(-100000, 100000) + 0.5, 100, Expr.INSTANCE.randomInt(-100000, 100000) + 0.5); + } while (location.distance(e.getPlayer().getWorld().getSpawnLocation()) < 15000); + location.clone().subtract(0, 1, 0).getBlock().setType(Material.HONEYCOMB_BLOCK); + for (Entity entity : e.getPlayer().getNearbyEntities(2, 2, 2)) { + if (entity instanceof Player) { + entity.sendMessage( + Component.text(e.getPlayer().getName() + "の", NamedTextColor.YELLOW) + .append(Component.text("Blessed Random Teleporter", NamedTextColor.GREEN).decorate(TextDecoration.BOLD)) + .append(Component.text("の効果でテレポートされました!", NamedTextColor.YELLOW)) + ); + entity.teleport(location); + } + } + e.getPlayer().teleport(location); + } +} diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/DeathListener.java b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/DeathListener.java index 83cf953..2236b97 100644 --- a/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/DeathListener.java +++ b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/DeathListener.java @@ -2,6 +2,8 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.GameMode; +import org.bukkit.Statistic; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -16,22 +18,37 @@ */ public class DeathListener implements Listener { - /** - * 死んだプレイヤーに失った経験治療を通知する - * - * @param e イベント発火原因のプレイヤー - */ - @EventHandler(priority = EventPriority.HIGH) - public void onDeath(PlayerDeathEvent e) { - Player p = e.getPlayer(); + /** + * 死んだプレイヤーに失った経験治療を通知する + * + * @param e イベント発火原因のプレイヤー + */ + @EventHandler(priority = EventPriority.HIGH) + public void onDeath(PlayerDeathEvent e) { + Player p = e.getPlayer(); - // ドロップした経験値量を通知 - int dropExp = e.getDroppedExp(); - if (dropExp == 0) { - p.sendMessage(Component.text("死亡しました。経験値の消費はありません。", NamedTextColor.RED)); - return; + String inventoryMessage; + if (e.getPlayer().getGameMode() != GameMode.SURVIVAL) { + e.setKeepInventory(true); + e.getDrops().clear(); + inventoryMessage = "サバイバルモード以外のため、インベントリの中身は失いません。"; + } else { + if (e.getPlayer().getStatistic(Statistic.PLAY_ONE_MINUTE) >= 20 * 60 * 60 * 5) { // 5 hours + e.setKeepInventory(false); + inventoryMessage = "プレイ時間が5時間以上のため、インベントリの中身を失いました。"; + } else { + e.setKeepInventory(true); + e.getDrops().clear(); + inventoryMessage = "プレイ時間が5時間未満のため、インベントリの中身は失いません。"; + } + } + + // ドロップした経験値量を通知 + if (e.getDroppedExp() == 0) { + p.sendMessage(Component.text("死亡しました。経験値の消費はありません。" + inventoryMessage, NamedTextColor.RED)); + } else { + p.sendMessage( + Component.text("死亡したため、" + e.getDroppedExp() + " Expを失いました。" + inventoryMessage, NamedTextColor.RED)); + } } - p.sendMessage( - Component.text("死亡したため、" + e.getDroppedExp() + " Expを失いました。", NamedTextColor.RED)); - } } diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/EnderDragonDisableListener.java b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/EnderDragonDisableListener.java new file mode 100644 index 0000000..333a18d --- /dev/null +++ b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/EnderDragonDisableListener.java @@ -0,0 +1,27 @@ +package net.azisaba.afnw.afnwcore2.listeners.player; + +import org.bukkit.Material; +import org.bukkit.entity.EntityType; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntitySpawnEvent; +import org.bukkit.event.player.PlayerInteractEvent; + +public class EnderDragonDisableListener implements Listener { + @EventHandler + public void onPlayerInteract(PlayerInteractEvent e) { + if (e.getItem() == null) return; + Material inHand = e.getItem().getType(); + if ((inHand == Material.ENDER_DRAGON_SPAWN_EGG || inHand == Material.WITHER_SPAWN_EGG) + && !e.getPlayer().getWorld().getName().equals("afnw_the_end")) { + e.setCancelled(true); + } + } + + @EventHandler + public void onEntitySpawn(EntitySpawnEvent e) { + if (e.getEntity().getType() == EntityType.ENDER_DRAGON && !e.getEntity().getWorld().getName().equals("afnw_the_end")) { + e.setCancelled(true); + } + } +} diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/FirstPlayerJoinListener.java b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/FirstPlayerJoinListener.java index b865db1..a19a635 100644 --- a/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/FirstPlayerJoinListener.java +++ b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/FirstPlayerJoinListener.java @@ -54,7 +54,9 @@ public void onJoin(PlayerJoinEvent e) { playerData.savePlayerData(); // テレポートとプレイヤーデータ作成通知 - p.teleport(tutorial.getSpawnLocation()); + Bukkit.getScheduler().runTaskLater(plugin, () -> { + p.teleport(tutorial.getSpawnLocation()); + }, 5); p.sendMessage(Component.text("プレイヤーデータが作成されました。", NamedTextColor.YELLOW)); } } diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/FishingListener.java b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/FishingListener.java new file mode 100644 index 0000000..d448807 --- /dev/null +++ b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/FishingListener.java @@ -0,0 +1,26 @@ +package net.azisaba.afnw.afnwcore2.listeners.player; + +import io.lumine.mythic.bukkit.MythicBukkit; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerFishEvent; + +import java.util.Optional; + +public class FishingListener implements Listener { + @EventHandler + public void on(PlayerFishEvent e) { + if (e.getCaught() != null) { + double baseChance = 0.01; // 1% + double playerLuck = Optional.ofNullable(e.getPlayer().getAttribute(Attribute.LUCK)).map(AttributeInstance::getValue).orElse(0.0); + double chance = baseChance + playerLuck * 0.001; // 0.1% per luck level + // calculate chance + if (Math.random() < chance) { + // drop shard + e.getPlayer().getInventory().addItem(MythicBukkit.inst().getItemManager().getItemStack("Rare_Lootbox_Shard")); + } + } + } +} diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/JoinListener.java b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/JoinListener.java index 555a0fb..b37061c 100644 --- a/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/JoinListener.java +++ b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/JoinListener.java @@ -4,6 +4,7 @@ import java.util.Arrays; import net.azisaba.afnw.afnwcore2.AfnwCore2; import net.azisaba.afnw.afnwcore2.util.data.PlayerData; +import net.azisaba.afnw.afnwcore2.util.item.AfnwTicket; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.title.Title; @@ -36,6 +37,16 @@ public record JoinListener(JavaPlugin plugin, PlayerData playerData) implements public void onJoin(PlayerJoinEvent e) { Player p = e.getPlayer(); + // 初参加の場合 + if (!playerData.getPlayerData().getBoolean("players." + p.getUniqueId() + ".first-join", false)) { + playerData.getPlayerData().set("players." + p.getUniqueId() + ".first-join", true); + e.getPlayer().getInventory().addItem(AfnwTicket.afnwTicket); + e.getPlayer().getInventory().addItem(AfnwTicket.afnwTicket); + e.getPlayer().getInventory().addItem(AfnwTicket.afnwTicket); + e.getPlayer().getInventory().addItem(AfnwTicket.afnwTicket); + Bukkit.getScheduler().runTaskAsynchronously(plugin, playerData::savePlayerData); + } + // プレイヤーがサーバーに参加したらタイトルとログインメッセージを送信する sendPlayerTitle(p); e.joinMessage(Component.text("* " + p.getName() + "がログインしました").color(NamedTextColor.AQUA)); @@ -72,7 +83,7 @@ public void run() { @Deprecated public void sendPlayerTitle(@NonNull Player p) { // タイトルの表示時間の設定 - final Title.Times times = Title.Times.of(Duration.ofMillis(500), Duration.ofMillis(3000), + final Title.Times times = Title.Times.times(Duration.ofMillis(500), Duration.ofMillis(3000), Duration.ofMillis(1000)); // タイトルの設定 diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/PvPListener.java b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/PvPListener.java new file mode 100644 index 0000000..4754053 --- /dev/null +++ b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/PvPListener.java @@ -0,0 +1,25 @@ +package net.azisaba.afnw.afnwcore2.listeners.player; + +import net.azisaba.afnw.afnwcore2.AfnwCore2; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; + +public record PvPListener(AfnwCore2 plugin) implements Listener { + @EventHandler + public void onEntityDamageByEntityEvent(EntityDamageByEntityEvent e) { + if (!(e.getEntity() instanceof Player player)) return; + if (e.getDamager() instanceof Player killer) { + if (!plugin.pvpEnabled.contains(killer.getUniqueId()) || !plugin.pvpEnabled.contains(player.getUniqueId())) { + e.setCancelled(true); + } + } + if (e.getDamager() instanceof Projectile projectile && projectile.getShooter() instanceof Player shooter) { + if (shooter != e.getEntity() && !plugin.pvpEnabled.contains(shooter.getUniqueId()) || !plugin.pvpEnabled.contains(player.getUniqueId())) { + e.setCancelled(true); + } + } + } +} diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/QuitListener.java b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/QuitListener.java index 1654036..0c38be9 100644 --- a/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/QuitListener.java +++ b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/QuitListener.java @@ -1,5 +1,6 @@ package net.azisaba.afnw.afnwcore2.listeners.player; +import net.azisaba.afnw.afnwcore2.AfnwCore2; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.entity.Player; @@ -7,6 +8,7 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerQuitEvent; +import org.jetbrains.annotations.NotNull; /** * ログアウトしたプレイヤーに関するクラス @@ -15,6 +17,11 @@ * @see org.bukkit.event.Listener */ public class QuitListener implements Listener { + private final @NotNull AfnwCore2 plugin; + + public QuitListener(@NotNull AfnwCore2 plugin) { + this.plugin = plugin; + } /** * ログアウト通知を行います。 @@ -27,5 +34,6 @@ public void onQuit(PlayerQuitEvent e) { Player p = e.getPlayer(); e.quitMessage(Component.text("* " + p.getName() + "がログアウトしました。").color(NamedTextColor.AQUA)); + plugin.pvpEnabled.remove(p.getUniqueId()); } } diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/SuperAfnwTicketListener.java b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/SuperAfnwTicketListener.java index 30f00d0..ac82016 100644 --- a/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/SuperAfnwTicketListener.java +++ b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/SuperAfnwTicketListener.java @@ -1,13 +1,36 @@ package net.azisaba.afnw.afnwcore2.listeners.player; +import net.azisaba.afnw.afnwcore2.commands.AfnwCommand; +import net.azisaba.afnw.afnwcore2.util.item.ItemUtil; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Sound; +import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.Plugin; -public class SuperAfnwTicketListener implements Listener { +/** + * Super Afnw Ticketの実際の動作 + */ +public record SuperAfnwTicketListener(Plugin plugin) implements Listener { @EventHandler public void onPlayerInteract(PlayerInteractEvent e) { - if (e.getAction() != Action.LEFT_CLICK_AIR && e.getAction() != Action.LEFT_CLICK_BLOCK) return; + if (e.getAction() != Action.RIGHT_CLICK_AIR && e.getAction() != Action.RIGHT_CLICK_BLOCK) return; + if (e.getPlayer().getGameMode() != GameMode.SURVIVAL) return; + if (!"Super_Afnw_Ticket".equals(ItemUtil.getMythicType(e.getItem()))) return; + e.getItem().setAmount(e.getItem().getAmount() - 1); + for (Player player : Bukkit.getOnlinePlayers()) { + player.playSound(e.getPlayer().getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1000f, 1.5f); + Bukkit.getScheduler().runTaskLater(plugin, () -> player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1000f, 1.75f), 3); + Bukkit.getScheduler().runTaskLater(plugin, () -> player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1000f, 2.0f), 6); + for (ItemStack value : player.getInventory().addItem(AfnwCommand.getRandomItem(50, 1)).values()) { + ItemUtil.addToStashIfEnabledAsync(plugin, player.getUniqueId(), value); + } + player.sendMessage("§d" + e.getPlayer().getName() + "§eが§aSuper Afnw Ticket§eを使用し、アイテムが配布されました!"); + } } } diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/TrashListener.java b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/TrashListener.java new file mode 100644 index 0000000..7498223 --- /dev/null +++ b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/TrashListener.java @@ -0,0 +1,73 @@ +package net.azisaba.afnw.afnwcore2.listeners.player; + +import net.azisaba.afnw.afnwcore2.AfnwCore2; +import net.azisaba.afnw.afnwcore2.gui.TrashInventory; +import net.azisaba.afnw.afnwcore2.util.item.ItemUtil; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public record TrashListener(@NotNull AfnwCore2 plugin) implements Listener { + @EventHandler + public void onInventoryClick(InventoryClickEvent e) { + if (e.getClickedInventory() == null || !(e.getClickedInventory().getHolder() instanceof TrashInventory)) { + return; + } + if (e.getSlot() != 53) { + return; + } + e.getInventory().setItem(53, null); + int count = 0; + List items = new ArrayList<>(); + for (ItemStack item : e.getInventory().getContents()) { + if (item == null || item.getAmount() <= 0) continue; + count += item.getAmount(); + items.add(item.clone()); + item.setAmount(0); + } + e.getWhoClicked().sendMessage(Component.text("ゴミ箱に" + count + "個のアイテムを捨てました。", NamedTextColor.YELLOW)); + plugin.getSLF4JLogger().info("Player {} has trashed {} items:", e.getWhoClicked().getName(), count); + for (ItemStack item : items) { + plugin.getSLF4JLogger().info(" {}", ItemUtil.toString(item)); + e.getWhoClicked().sendMessage(Component.text(" " + ItemUtil.toString(item), NamedTextColor.GRAY)); + } + e.getClickedInventory().clear(); + e.getWhoClicked().closeInventory(); + } + + @EventHandler + public void onInventoryDrag(InventoryDragEvent e) { + if (e.getInventory().getHolder() instanceof TrashInventory) { + e.setCancelled(true); + } + } + + @EventHandler + public void onInventoryClose(InventoryCloseEvent e) { + if (!(e.getInventory().getHolder() instanceof TrashInventory)) { + return; + } + e.getInventory().setItem(53, null); + boolean stash = false; + for (ItemStack item : e.getInventory().getContents()) { + if (item == null || item.getAmount() <= 0) continue; + for (ItemStack value : e.getPlayer().getInventory().addItem(item).values()) { + ItemUtil.addToStashIfEnabledAsync(plugin, e.getPlayer().getUniqueId(), value); + stash = true; + } + } + if (stash) { + e.getPlayer().sendMessage(Component.text("インベントリがいっぱいのため、Stashに保管されました。", NamedTextColor.RED)); + e.getPlayer().sendMessage(Component.text("/pickupstash", NamedTextColor.AQUA).append(Component.text("で回収できます。", NamedTextColor.RED))); + } + } +} diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/VillagerInteractListener.java b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/VillagerInteractListener.java new file mode 100644 index 0000000..a00c174 --- /dev/null +++ b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/VillagerInteractListener.java @@ -0,0 +1,26 @@ +package net.azisaba.afnw.afnwcore2.listeners.player; + +import org.bukkit.entity.Villager; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerInteractAtEntityEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; + +/** + * 製図家に対するクリックを無効化するクラス + */ +public record VillagerInteractListener() implements Listener { + @EventHandler + public void onPlayerInteractAtEntity(PlayerInteractAtEntityEvent e) { + onPlayerInteractEntity(e); + } + + @EventHandler + public void onPlayerInteractEntity(PlayerInteractEntityEvent e) { + if (e.getRightClicked() instanceof Villager villager) { + if (villager.getProfession() == Villager.Profession.CARTOGRAPHER) { + e.setCancelled(true); + } + } + } +} diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/VillagerProtectorListener.java b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/VillagerProtectorListener.java new file mode 100644 index 0000000..92f626d --- /dev/null +++ b/src/main/java/net/azisaba/afnw/afnwcore2/listeners/player/VillagerProtectorListener.java @@ -0,0 +1,36 @@ +package net.azisaba.afnw.afnwcore2.listeners.player; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.GameMode; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.Player; +import org.bukkit.entity.Villager; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; + +public class VillagerProtectorListener implements Listener { + @EventHandler + public void onPlayerAttack(EntityDamageByEntityEvent e) { + if (!(e.getEntity() instanceof Villager)) return; + if (e.getDamager() instanceof Player player) { + if (player.getGameMode() != GameMode.CREATIVE) { + e.setCancelled(true); + } + } + if (e.getDamager() instanceof Arrow arrow && arrow.getShooter() instanceof Player) { + e.setCancelled(true); + } + } + + @EventHandler + public void onBlockBreak(BlockBreakEvent e) { + if (e.getPlayer().getGameMode() == GameMode.CREATIVE) return; + if (!e.getBlock().getLocation().add(0.5, 0, 0.5).getNearbyEntitiesByType(Villager.class, 0.2, 2).isEmpty()) { + e.setCancelled(true); + e.getPlayer().sendMessage(Component.text("このブロックは破壊できません!", NamedTextColor.RED)); + } + } +} diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/util/Expr.java b/src/main/java/net/azisaba/afnw/afnwcore2/util/Expr.java new file mode 100644 index 0000000..792dba6 --- /dev/null +++ b/src/main/java/net/azisaba/afnw/afnwcore2/util/Expr.java @@ -0,0 +1,233 @@ +package net.azisaba.afnw.afnwcore2.util; + +import net.azisaba.afnw.afnwcore2.AfnwCore2; +import org.bukkit.BanEntry; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import xyz.acrylicstyle.util.expression.CompileData; +import xyz.acrylicstyle.util.expression.ExpressionParser; +import xyz.acrylicstyle.util.expression.RuntimeData; +import xyz.acrylicstyle.util.expression.instruction.DummyInstTypeInfo; +import xyz.acrylicstyle.util.expression.instruction.Instruction; +import xyz.acrylicstyle.util.expression.instruction.InstructionSet; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.security.SecureRandom; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +@SuppressWarnings("unused") +public final class Expr { + public static final Expr INSTANCE = new Expr(); + private static final Set> DISALLOWED_RETURN_TYPES = new HashSet<>(Arrays.asList( + void.class, ConsoleCommandSender.class, Class.class, Field.class, Method.class, Constructor.class, + BanEntry.class + )); + private static final SecureRandom SECURE_RANDOM = new SecureRandom(); + private static final Pattern SCRIPT_PATTERN = Pattern.compile("\\$\\{(.+)}"); + + @Nullable + public static String evalAndReplace(Player player, String line, String description) { + AtomicBoolean modified = new AtomicBoolean(false); + Matcher matcher = SCRIPT_PATTERN.matcher(line); + StringBuilder sb = new StringBuilder(); + while (matcher.find()) { + modified.set(true); + try { + matcher.appendReplacement(sb, String.valueOf(Expr.eval(player, matcher.group(1)))); + } catch (Exception e) { + matcher.appendReplacement(sb, ""); + AfnwCore2.getPluginLogger().error("Error evaluating script (description: {})\nLine: {}", description, line, e); + } + } + matcher.appendTail(sb); + return modified.get() ? sb.toString() : null; + } + + @NotNull + public static Stream getSuggestions(Object sender, String src) { + try { + CompileData compileData = + CompileData.builder() + .allowPrivate(true) + .addVariable("player", sender.getClass()) + .addVariable("expr", Expr.class) + .build(); + return getSuggestionsPartial(Stream.of("player", "expr"), compileData, src); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static @NotNull Stream getSuggestionsFull(@NotNull Stream variables, @NotNull CompileData compileData, @NotNull String input) throws Exception { + if (!input.contains(".")) { + return variables; + } + InstructionSet instructionSet = ExpressionParser.compile(input.substring(0, input.lastIndexOf('.')), compileData); + Instruction instruction = instructionSet.lastOrNull(); + if (instruction instanceof DummyInstTypeInfo typeInfo) { + return getTokens(typeInfo.getClazz()).map(s -> input.substring(0, input.lastIndexOf('.') + 1) + s); + } + return Stream.empty(); + } + + public static @NotNull Stream getSuggestionsPartial(@NotNull Stream variables, @NotNull CompileData compileData, @NotNull String input) throws Exception { + if (!input.contains(".")) { + return variables.filter(s -> s.startsWith(input)); + } + String token = input.substring(input.lastIndexOf('.') + 1); + String[] args = input.split(" "); + String last = args[args.length - 1]; + InstructionSet instructionSet = ExpressionParser.compile(input.substring(0, input.lastIndexOf('.')), compileData); + Instruction instruction = instructionSet.lastOrNull(); + if (instruction instanceof DummyInstTypeInfo typeInfo) { + return getTokens(typeInfo.getClazz()) + .filter(s -> s.toLowerCase(Locale.ROOT).startsWith(token.toLowerCase(Locale.ROOT))) + .map(s -> last.substring(0, last.lastIndexOf('.') + 1) + s); + } + return Stream.empty(); + } + + private static Stream getTokens(@NotNull Class type) { + List tokens = new ArrayList<>(); + for (Class clazz : getSupers(type)) { + for (Field field : clazz.getDeclaredFields()) { + tokens.add(field.getName()); + } + for (Method method : clazz.getDeclaredMethods()) { + if (DISALLOWED_RETURN_TYPES.contains(method.getReturnType())) continue; + if (method.getName().length() >= 4 + && method.getName().startsWith("get") + && Character.isUpperCase(method.getName().charAt(3)) + && method.getParameterCount() == 0) { + tokens.add(method.getName().substring(3, 4).toLowerCase(Locale.ROOT) + method.getName().substring(4)); + } + if (method.getParameterCount() == 0) { + tokens.add(method.getName() + "()"); + tokens.add(method.getName()); + } else { + tokens.add(method.getName() + "("); + } + } + } + tokens.add("?as("); + return tokens.stream().distinct(); + } + + private static @NotNull Set> getSupers(@NotNull Class type) { + Set> list = new LinkedHashSet<>(); + list.add(type); + if (type.getSuperclass() != null) { + list.add(type.getSuperclass()); + list.addAll(getSupers(type.getSuperclass())); + } + list.addAll(Arrays.asList(type.getInterfaces())); + for (Class anInterface : type.getInterfaces()) { + list.addAll(getSupers(anInterface)); + } + return list; + } + + public static @Nullable Object eval(@NotNull Player player, @NotNull String src) { + try { + CompileData compileData = + CompileData.builder() + .allowPrivate(true) + .addVariable("player", player.getClass()) + .addVariable("expr", Expr.class) + .build(); + InstructionSet instructionSet = ExpressionParser.compile(src, compileData); + instructionSet.forEach(instruction -> { + if (instruction instanceof DummyInstTypeInfo) { + Class clazz = ((DummyInstTypeInfo) instruction).getClazz(); + if (DISALLOWED_RETURN_TYPES.contains(clazz)) { + throw new RuntimeException("Disallowed return type: " + clazz); + } + } + }); + return instructionSet.execute( + RuntimeData.builder() + .allowPrivate(true) + .addVariable("player", player) + .addVariable("expr", INSTANCE) + .build() + ); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public long round(double d) { + return Math.round(d); + } + + public double round1(double d) { + return round(d, 1); + } + + public double round2(double d) { + return round(d, 2); + } + + public double round3(double d) { + return round(d, 3); + } + + public double round4(double d) { + return round(d, 4); + } + + public double round5(double d) { + return round(d, 5); + } + + public double round(double d, int points) { + return Math.round(d * Math.pow(10, points)) / Math.pow(10, points); + } + + public double randomDouble(double min, double max) { + return Math.random() * (max - min) + min; + } + + public double secureRandomDouble(double min, double max) { + return SECURE_RANDOM.nextDouble() * (max - min) + min; + } + + public double randomDouble(int min, int max) { + return Math.random() * (max - min) + min; + } + + public double secureRandomDouble(int min, int max) { + return SECURE_RANDOM.nextDouble() * (max - min) + min; + } + + public long randomLong(long min, long max) { + return Math.round(randomDouble(min, max)); + } + + public long secureRandomLong(long min, long max) { + return Math.round(secureRandomDouble(min, max)); + } + + public int randomInt(int min, int max) { + return Math.toIntExact(Math.round(randomDouble(min, max))); + } + + public int secureRandomInt(int min, int max) { + return Math.toIntExact(Math.round(secureRandomDouble(min, max))); + } + + @NotNull + @Contract(pure = true) + public static String longToHex(long l) { + return Long.toString(l, 16); + } +} diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/util/TheTAB.java b/src/main/java/net/azisaba/afnw/afnwcore2/util/TheTAB.java new file mode 100644 index 0000000..744d545 --- /dev/null +++ b/src/main/java/net/azisaba/afnw/afnwcore2/util/TheTAB.java @@ -0,0 +1,24 @@ +package net.azisaba.afnw.afnwcore2.util; + +import net.azisaba.afnw.afnwcore2.AfnwCore2; +import net.azisaba.tabbukkitbridge.data.DataKey; +import org.bukkit.entity.Player; + +public class TheTAB { + private static boolean enabled = false; + + public static void enable() { + if (enabled) return; + enabled = true; + try { + DataKey pvpEnabled = new DataKey<>(false); + pvpEnabled.register(p -> true, player -> { + if (player == null) return false; + return AfnwCore2.getPlugin(AfnwCore2.class).pvpEnabled.contains(player.getUniqueId()); + }); + pvpEnabled.getPlaceholders().add("pvp_enabled"); + } catch (Exception | NoClassDefFoundError e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/util/data/PlayerDataSave.java b/src/main/java/net/azisaba/afnw/afnwcore2/util/data/PlayerDataSave.java index 15b4569..ea480e7 100644 --- a/src/main/java/net/azisaba/afnw/afnwcore2/util/data/PlayerDataSave.java +++ b/src/main/java/net/azisaba/afnw/afnwcore2/util/data/PlayerDataSave.java @@ -24,10 +24,8 @@ public PlayerData playerData() { new BukkitRunnable() { @Override public void run() { - Bukkit.getServer().broadcast(Component.text("プレイヤーデータ: セーブ中....", NamedTextColor.YELLOW)); playerData.savePlayerData(); playerData.reloadPlayerData(); - Bukkit.getServer().broadcast(Component.text("プレイヤーデータ: セーブ完了", NamedTextColor.YELLOW)); } }.runTaskTimer(plugin, 0, 20L * setPeriod); return playerData; diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/util/item/AfnwTicket.java b/src/main/java/net/azisaba/afnw/afnwcore2/util/item/AfnwTicket.java index 2dba6ca..e6ed996 100644 --- a/src/main/java/net/azisaba/afnw/afnwcore2/util/item/AfnwTicket.java +++ b/src/main/java/net/azisaba/afnw/afnwcore2/util/item/AfnwTicket.java @@ -2,7 +2,6 @@ import java.util.ArrayList; import java.util.List; -import net.azisaba.afnw.afnwcore2.AfnwCore2; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Material; @@ -12,10 +11,9 @@ /** * AfnwTicketを生成します。 * - * @param plugin メインクラス * @author m2en */ -public record AfnwTicket(AfnwCore2 plugin) { +public record AfnwTicket() { public static ItemStack afnwTicket = new ItemStack(Material.PAPER, 1); diff --git a/src/main/java/net/azisaba/afnw/afnwcore2/util/item/ItemUtil.java b/src/main/java/net/azisaba/afnw/afnwcore2/util/item/ItemUtil.java new file mode 100644 index 0000000..16b5070 --- /dev/null +++ b/src/main/java/net/azisaba/afnw/afnwcore2/util/item/ItemUtil.java @@ -0,0 +1,72 @@ +package net.azisaba.afnw.afnwcore2.util.item; + +import net.azisaba.itemstash.ItemStash; +import net.minecraft.core.component.DataComponents; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.item.component.CustomData; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +public class ItemUtil { + public static void addToStashIfEnabled(@NotNull UUID uuid, @NotNull ItemStack stack) { + if (Bukkit.getPluginManager().isPluginEnabled("ItemStash")) { + ItemStash.getInstance().addItemToStash(uuid, stack); + } + } + + public static void addToStashIfEnabledAsync(@NotNull Plugin plugin, @NotNull UUID uuid, @NotNull ItemStack stack) { + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> addToStashIfEnabled(uuid, stack)); + } + + public static @Nullable CompoundTag getCustomData(@NotNull ItemStack stack) { + CustomData customData = CraftItemStack.asNMSCopy(stack).get(DataComponents.CUSTOM_DATA); + if (customData == null) return null; + return customData.copyTag(); + } + + public static @NotNull String getStringTag(@NotNull ItemStack stack, @NotNull String name) { + CompoundTag tag = getCustomData(stack); + if (tag == null) return ""; + return tag.getString(name).orElse(""); + } + + public static @NotNull CompoundTag getCompoundTag(@NotNull ItemStack stack, @NotNull String name) { + CompoundTag tag = getCustomData(stack); + if (tag == null) return new CompoundTag(); + return tag.getCompound(name).orElseGet(CompoundTag::new); + } + + @Contract("null -> null") + public static @Nullable String getMythicType(@Nullable ItemStack stack) { + if (stack == null) return null; + String s = getCompoundTag(stack, "PublicBukkitValues").getString("mythicmobs:type").orElse(""); + if (s.isBlank()) return null; + return s; + } + + public static @NotNull String toString(@NotNull ItemStack stack) { + List props = new ArrayList<>(); + props.add("[Type: " + stack.getType().name() + "]"); + props.add("[Amount: " + stack.getAmount() + "]"); + ItemMeta meta = stack.getItemMeta(); + if (meta != null) { + if (meta.hasDisplayName()) props.add("[Name: " + meta.getDisplayName() + "]"); + if (meta.hasLore()) props.add("[Lore: " + Objects.requireNonNull(meta.getLore()).size() + " entries]"); + if (meta.hasCustomModelData()) props.add("[CustomModelData: " + meta.getCustomModelData() + "]"); + if (getMythicType(stack) != null) props.add("[MMID: " + getMythicType(stack) + "]"); + if (meta.hasEnchants()) meta.getEnchants().forEach((enchant, level) -> props.add("[Enchant: " + enchant.getKey() + " " + level + "]")); + } + return String.join("", props); + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index ba6064d..511cd58 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -2,51 +2,53 @@ # AfnwCore2に関するいろんな設定を変更できるファイルです。パーミッションの設定はここでは行えません。 # 編集した後はサーバーを再起動するか、プラグインを再リロードするか、コマンド "/config_reload" を実行してください。 # デフォルト値は万が一指定されていないときにAfnwCore2が代入する値です。(World名のConfigではデフォルト値が入ってもワールドが見つからない場合はNullPointerException(例外)としてクラッシュします。) - -vote: # 投票周りの設定 - item-size: 1 +# 投票周りの設定 +vote: # /afnwで交換できるアイテムの数を指定してください。int(数)ではない場合はエラーが発生します。デフォルト: 1 - scaffold-size: 8 + item-size: 1 # /afnwで交換時に一緒に配布される足場の数を指定してください。int(数)ではない場合はエラーが発生します。デフォルト: 8 - send-ticket-size: 1 + scaffold-size: 8 # 投票時に受け取れるチケットの数を指定してください。int(数)ではない場合はエラーが発生します。デフォルト: 1 - bonus-line: 10 + send-ticket-size: 1 # 投票ボーナスの回数を指定します。指定された数以上の回数と一致したらチケット10枚とネザースターが配布されます。int(数)ではない場合はエラーが発生します。デフォルト: 10 + bonus-line: 10 tp: - standby: 10 # TPコマンド(not /tp)を使用した際の待機時間を秒で指定します。int(数)ではない場合はエラーが発生します。デフォルト: 10 - lobby_world_name: "lobby" + standby: 10 # ロビーワールドの名前を指定します。デフォルト:lobby - void_world_name: "afnw" + lobby_world_name: "lobby" # Voidワールドの名前を指定します。デフォルト:afnw - tutorial_world_name: "tutorial" + void_world_name: "afnw" # チュートリアルワールドの名前を指定します。デフォルト:tutorial + tutorial_world_name: "tutorial" afk: - afk_world_name: "afk" # AFKポイントとして利用するワールド名を指定します。デフォルト:afk + afk_world_name: "afk" + # AFKポイントとして利用する座標をそれぞれ指定します。int(数)ではない場合はエラーが発生します。窒息を防ぐため、Y座標のみ一つ足された数値で利用されるので注意してください。 + # デフォルト値は0です。必ず指定することをおすすめします。 afk_point_x: 0 afk_point_y: 0 afk_point_z: 0 - # AFKポイントとして利用する座標をそれぞれ指定します。int(数)ではない場合はエラーが発生します。窒息を防ぐため、Y座標のみ一つ足された数値で利用されるので注意してください。 - # デフォルト値は0です。必ず指定することをおすすめします。 trash: - name: "ゴミ箱" # ゴミ箱の名前を指定します。"春猫"って指定しちゃダメだよ。デフォルト:ゴミ箱 - size: 54 + name: "ゴミ箱" # ゴミ箱のスロット数を指定します。int(数)ではない場合はエラーが発生します。デフォルト: 54 # Spigot(Paper) 、どっちかというとMinecraft自身の仕様で9の倍数である必要があります。9の倍数ではない場合はデフォルト値に変更されます。 + size: 54 settings: - player-save-period: 120 # プレイヤーデータの自動セーブ間隔を秒で指定します。int(数)ではない場合はエラーが発生します。デフォルト: 120 - allow-bedrock-player: ['.Meru92a'] + player-save-period: 120 # Bedrock Blockerを除外するプレイヤーのMCIDを入力します。必ず先頭には "." を入れ、コンマで区切ってください。 - allow-wither-spawn: true + allow-bedrock-player: ['.Meru92a'] # ウィザーのスポーン(召喚)を許可するか, booleanで指定します。 true: 許可します。召喚されてもキャンセルしません。 / false: 許可しません。召喚されてもキャンセルします。 - notification-wither-spawn: true + allow-wither-spawn: true # ウィザーの召喚通知を行うか, booleanで指定します。true: 通知します / false: 通知しません。 - maintenance-mode-toggle: false + notification-wither-spawn: true # メンテナンスモードで起動するか, booleanで指定します。true: 起動します / false: 起動しません。 + maintenance-mode-toggle: false + # ItemStashプラグインが有効ではない場合はクラッシュします。 + require-item-stash: false diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index fed73e4..77e5068 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -6,7 +6,10 @@ prefix: AfnwCore2 load: STARTUP authors: [ merunno, afnwteam ] description: That world, once again. -depend: [ Essentials ] +depend: [ Essentials, Votifier ] +softdepend: + - ItemStash + - TAB-BukkitBridge website: https://github.com/AfnwTeam/AfnwCore2 permissions: @@ -93,3 +96,11 @@ commands: aliases: - mente - debug + pvp: + description: PvPの可否を設定します。 + mmgive: + permission: afnw.command.mmgive + mmgiveeval: + permission: afnw.command.mmgiveeval + safeteleport: + permission: afnw.command.safeteleport