diff --git a/.github/workflows/comment_on_pr.yaml b/.github/workflows/comment_on_pr.yaml index 15969462c6..cfb901730e 100644 --- a/.github/workflows/comment_on_pr.yaml +++ b/.github/workflows/comment_on_pr.yaml @@ -50,13 +50,6 @@ jobs: echo "Head SHA: $HEAD_SHA" echo "HEAD_SHA=$HEAD_SHA" >> "$GITHUB_ENV" - - name: Delete old comments - uses: izhangzhihao/delete-comment@master - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - delete_user_name: github-actions[bot] - issue_number: ${{ env.PR_NUMBER }} - - name: Update Comment env: JOB_PATH: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ env.PREVIOUS_JOB_ID }}" diff --git a/README.md b/README.md index e21077faab..2c3637f809 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ Current services and exposed ports are: | proxy | 25565 | TCP, Minecraft | | | paper | | | | | pvp | | | | +| gamma | | | | | mariadb | 3306 | TCP, Database | | | postgres | 5432 | TCP, Database | | | rabbitmq | 5672 | TCP, AMQP | | diff --git a/ansible/build.gradle.kts b/ansible/build.gradle.kts index 966e570799..6e724b60fd 100644 --- a/ansible/build.gradle.kts +++ b/ansible/build.gradle.kts @@ -1,4 +1,5 @@ val paperPlugin by configurations.creating +val gammaPlugin by configurations.creating val pvpPlugin by configurations.creating val proxyPlugin by configurations.creating @@ -29,6 +30,31 @@ dependencies { paperPlugin(project(path = ":plugins:simpleadminhacks-paper")) paperPlugin(project(path = ":plugins:heliodor-paper")) + gammaPlugin(project(path = ":plugins:banstick-paper", configuration = "shadow")) + gammaPlugin(project(path = ":plugins:bastion-paper")) + gammaPlugin(project(path = ":plugins:castlegates-paper")) + gammaPlugin(project(path = ":plugins:citadel-paper")) + gammaPlugin(project(path = ":plugins:civchat2-paper")) + gammaPlugin(project(path = ":plugins:civduties-paper")) + gammaPlugin(project(path = ":plugins:civmodcore-paper", configuration = "shadow")) + gammaPlugin(project(path = ":plugins:finale-paper")) + gammaPlugin(project(path = ":plugins:combattagplus-paper")) + gammaPlugin(project(path = ":plugins:donum-paper")) + gammaPlugin(project(path = ":plugins:essenceglue-paper")) + gammaPlugin(project(path = ":plugins:exilepearl-paper")) + gammaPlugin(project(path = ":plugins:factorymod-paper")) + gammaPlugin(project(path = ":plugins:hiddenore-paper")) + gammaPlugin(project(path = ":plugins:itemexchange-paper")) + gammaPlugin(project(path = ":plugins:jukealert-paper", configuration = "shadow")) + gammaPlugin(project(path = ":plugins:namecolors-paper")) + gammaPlugin(project(path = ":plugins:namelayer-paper")) + gammaPlugin(project(path = ":plugins:railswitch-paper")) + gammaPlugin(project(path = ":plugins:randomspawn-paper")) + gammaPlugin(project(path = ":plugins:realisticbiomes2-paper")) + gammaPlugin(project(path = ":plugins:simpleadminhacks-paper")) + gammaPlugin(project(path = ":plugins:heliodor-paper")) + gammaPlugin(project(path = ":plugins:kirabukkitgateway-paper", configuration = "shadow")) + pvpPlugin(project(path = ":plugins:banstick-paper", configuration = "shadow")) pvpPlugin(project(path = ":plugins:civduties-paper")) pvpPlugin(project(path = ":plugins:civmodcore-paper", configuration = "shadow")) @@ -40,6 +66,10 @@ dependencies { pvpPlugin(project(path = ":plugins:namelayer-paper")) pvpPlugin(project(path = ":plugins:civchat2-paper")) pvpPlugin(project(path = ":plugins:namecolors-paper")) + + proxyPlugin(project(path = ":plugins:civproxy-velocity", configuration = "shadow")) + proxyPlugin(project(path = ":plugins:announcements-velocity", configuration = "shadow")) + proxyPlugin(project(path = ":plugins:kiragateway-velocity", configuration = "shadow")) } val copyPaperPlugins = tasks.register("copyPaperPlugins") { @@ -66,6 +96,18 @@ val copyPvpPlugins = tasks.register("copyPvpPlugins") { into("$projectDir/build/pvp-plugins") } +val copyGammaPlugins = tasks.register("copyGammaPlugins") { + dependsOn(gammaPlugin) + + doFirst { + project.delete(files("$projectDir/build/gamma-plugins")) + } + + from("$projectDir/src/gamma-plugins") + from(gammaPlugin.resolvedConfiguration.resolvedArtifacts.map { it.file }) + into("$projectDir/build/gamma-plugins") +} + val copyProxyPlugins = tasks.register("copyProxyPlugins") { dependsOn(proxyPlugin) @@ -81,6 +123,7 @@ val copyProxyPlugins = tasks.register("copyProxyPlugins") { // TODO: Is build the right name? tasks.register("build") { dependsOn(copyPaperPlugins) + dependsOn(copyGammaPlugins) dependsOn(copyPvpPlugins) dependsOn(copyProxyPlugins) } diff --git a/ansible/deactivated_plugins/realisticbiomes-paper/build.gradle.kts b/ansible/deactivated_plugins/realisticbiomes-paper/build.gradle.kts index 34acfb202e..e3a5b57bb2 100644 --- a/ansible/deactivated_plugins/realisticbiomes-paper/build.gradle.kts +++ b/ansible/deactivated_plugins/realisticbiomes-paper/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - id("io.papermc.paperweight.userdev") - id("xyz.jpenilla.run-paper") + alias(libs.plugins.paper.userdev) + alias(libs.plugins.runpaper) } version = "3.2.3" diff --git a/ansible/deactivated_plugins/realisticbiomes-paper/src/main/java/com/untamedears/realisticbiomes/commands/Menu.java b/ansible/deactivated_plugins/realisticbiomes-paper/src/main/java/com/untamedears/realisticbiomes/commands/Menu.java index e70ce244bd..0924ba8aa5 100644 --- a/ansible/deactivated_plugins/realisticbiomes-paper/src/main/java/com/untamedears/realisticbiomes/commands/Menu.java +++ b/ansible/deactivated_plugins/realisticbiomes-paper/src/main/java/com/untamedears/realisticbiomes/commands/Menu.java @@ -7,6 +7,8 @@ import co.aikar.commands.annotation.Optional; import co.aikar.commands.annotation.Syntax; import com.untamedears.realisticbiomes.utils.RealisticBiomesGUI; +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; import org.bukkit.ChatColor; import org.bukkit.block.Biome; import org.bukkit.entity.Player; @@ -31,8 +33,8 @@ public void onCommand(Player p, @Optional String biome) { return; } String concat = String.join(" ", biome); - for (Biome b : Biome.values()) { - if (b.toString().equals(concat)) { + for (Biome b : RegistryAccess.registryAccess().getRegistry(RegistryKey.BIOME)) { + if (b.getKey().value().equals(concat)) { RealisticBiomesGUI gui = new RealisticBiomesGUI(p); gui.showRBOverview(b); return; diff --git a/ansible/deactivated_plugins/realisticbiomes-paper/src/main/java/com/untamedears/realisticbiomes/commands/RBCommandManager.java b/ansible/deactivated_plugins/realisticbiomes-paper/src/main/java/com/untamedears/realisticbiomes/commands/RBCommandManager.java index 4e5a8ac189..e2e76d2b88 100644 --- a/ansible/deactivated_plugins/realisticbiomes-paper/src/main/java/com/untamedears/realisticbiomes/commands/RBCommandManager.java +++ b/ansible/deactivated_plugins/realisticbiomes-paper/src/main/java/com/untamedears/realisticbiomes/commands/RBCommandManager.java @@ -3,6 +3,9 @@ import co.aikar.commands.BukkitCommandCompletionContext; import co.aikar.commands.CommandCompletions; import java.util.Arrays; +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; +import org.bukkit.NamespacedKey; import org.bukkit.block.Biome; import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.NotNull; @@ -23,6 +26,6 @@ public void registerCommands() { @Override public void registerCompletions(@NotNull CommandCompletions completions) { super.registerCompletions(completions); - completions.registerCompletion("RB_Biomes", (context) -> Arrays.stream(Biome.values()).map(Biome::name).toList()); + completions.registerCompletion("RB_Biomes", (context) -> RegistryAccess.registryAccess().getRegistry(RegistryKey.BIOME).stream().map(Biome::getKey).map(NamespacedKey::value).toList()); } } diff --git a/ansible/deactivated_plugins/realisticbiomes-paper/src/main/java/com/untamedears/realisticbiomes/growth/FruitGrower.java b/ansible/deactivated_plugins/realisticbiomes-paper/src/main/java/com/untamedears/realisticbiomes/growth/FruitGrower.java index fb8e4c541e..60c9e5c722 100644 --- a/ansible/deactivated_plugins/realisticbiomes-paper/src/main/java/com/untamedears/realisticbiomes/growth/FruitGrower.java +++ b/ansible/deactivated_plugins/realisticbiomes-paper/src/main/java/com/untamedears/realisticbiomes/growth/FruitGrower.java @@ -13,7 +13,7 @@ public class FruitGrower extends IArtificialGrower { - private List validSoil = Arrays.asList(Material.DIRT, Material.GRASS_BLOCK, Material.FARMLAND); + private List validSoil = Arrays.asList(Material.DIRT, Material.GRASS_BLOCK, Material.FARMLAND, Material.MOSS_BLOCK, Material.COARSE_DIRT, Material.PODZOL, Material.PALE_MOSS_BLOCK); private Material attachedStem; private Material nonAttachedStem; private Material fruitMaterial; diff --git a/ansible/deactivated_plugins/realisticbiomes-paper/src/main/java/com/untamedears/realisticbiomes/utils/RealisticBiomesGUI.java b/ansible/deactivated_plugins/realisticbiomes-paper/src/main/java/com/untamedears/realisticbiomes/utils/RealisticBiomesGUI.java index 60b1abf130..fdf67b2a8c 100644 --- a/ansible/deactivated_plugins/realisticbiomes-paper/src/main/java/com/untamedears/realisticbiomes/utils/RealisticBiomesGUI.java +++ b/ansible/deactivated_plugins/realisticbiomes-paper/src/main/java/com/untamedears/realisticbiomes/utils/RealisticBiomesGUI.java @@ -10,7 +10,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.block.Biome; diff --git a/ansible/files/paper-config/bukkit.yml b/ansible/files/paper-config/bukkit.yml index 139e9fba86..facbdcabbd 100644 --- a/ansible/files/paper-config/bukkit.yml +++ b/ansible/files/paper-config/bukkit.yml @@ -1,5 +1,5 @@ settings: - allow-end: true + allow-end: false warn-on-overload: true permissions-file: permissions.yml update-folder: update @@ -11,11 +11,11 @@ settings: minimum-api: none spawn-limits: axolotls: 1 - monsters: 16 - animals: 8 - water-animals: 6 - water-ambient: 4 - water-underground-creature: 4 + monsters: 12 + animals: 7 + water-animals: 5 + water-ambient: 3 + water-underground-creature: 3 ambient: 0 chunk-gc: period-in-ticks: 400 diff --git a/ansible/files/paper-config/config/gale-global.yml b/ansible/files/paper-config/config/gale-global.yml new file mode 100644 index 0000000000..2f71cec0a2 --- /dev/null +++ b/ansible/files/paper-config/config/gale-global.yml @@ -0,0 +1,46 @@ +# This is the global configuration file for Gale. +# As you can see, there's a lot to configure. Some options may impact gameplay, so use +# with caution, and make sure you know what each option does before configuring. +# +# If you need help with the configuration or have any questions related to Gale, +# join us in our Discord, or check the GitHub Wiki pages. +# +# The world configuration options are inside +# their respective world folder. The files are named gale-world.yml +# +# Wiki: https://github.com/GaleMC/Gale/wiki +# Discord: https://discord.gg/gwezNT8c24 + +_version: 1 +gameplay-mechanics: + enable-book-writing: true +log-to-console: + chat: + empty-message-warning: false + expired-message-warning: false + not-secure-marker: true + ignored-advancements: true + invalid-legacy-text-component: true + invalid-statistics: true + legacy-material-initialization: false + null-id-disconnections: true + player-login-locations: true + plugin-library-loader: + downloads: true + library-loaded: true + start-load-libraries-for-plugin: true + set-block-in-far-chunk: true + unrecognized-recipes: false +misc: + ignore-null-legacy-structure-data: false + keepalive: + send-multiple: false + last-tick-time-in-tps-command: + add-oversleep: false + enabled: false + premium-account-slow-login-timeout: -1 + verify-chat-order: true +small-optimizations: + reduced-intervals: + increase-time-statistics: 20 + update-entity-line-of-sight: 4 diff --git a/ansible/files/paper-config/config/gale-world-defaults.yml b/ansible/files/paper-config/config/gale-world-defaults.yml new file mode 100644 index 0000000000..7aa6558890 --- /dev/null +++ b/ansible/files/paper-config/config/gale-world-defaults.yml @@ -0,0 +1,53 @@ +# This is the world defaults configuration file for Gale. +# As you can see, there's a lot to configure. Some options may impact gameplay, so use +# with caution, and make sure you know what each option does before configuring. +# +# If you need help with the configuration or have any questions related to Gale, +# join us in our Discord, or check the GitHub Wiki pages. +# +# Configuration options here apply to all worlds, unless you specify overrides inside +# the world-specific config file inside each world folder. +# +# Wiki: https://github.com/GaleMC/Gale/wiki +# Discord: https://discord.gg/gwezNT8c24 + +_version: 1 +gameplay-mechanics: + arrow-movement-resets-despawn-counter: false + entities-can-random-stroll-into-non-ticking-chunks: true + entity-wake-up-duration-ratio-standard-deviation: 0.2 + fixes: + broadcast-crit-animations-as-the-entity-being-critted: false + mc-121706: false + mc-238526: false + hide-flames-on-entities-with-fire-resistance: false + try-respawn-ender-dragon-after-end-crystal-place: true +small-optimizations: + load-chunks: + to-activate-climbing-entities: false + to-spawn-phantoms: false + max-projectile-chunk-loads: + per-projectile: + max: 10 + remove-from-world-after-reach-limit: false + reset-movement-after-reach-limit: false + per-tick: 10 + reduced-intervals: + acquire-poi-for-stuck-entity: 60 + check-nearby-item: + hopper: + interval: 1 + minecart: + interval: 1 + temporary-immunity: + check-for-minecart-near-item-interval: 20 + check-for-minecart-near-item-while-active: false + check-for-minecart-near-item-while-inactive: true + duration: 100 + max-item-horizontal-distance: 24.0 + max-item-vertical-distance: 4.0 + nearby-item-max-age: 1200 + check-stuck-in-wall: 10 + villager-item-repickup: 100 + save-fireworks: true + use-optimized-sheep-offspring-color: true diff --git a/ansible/files/paper-config/config/leaf-global.yml b/ansible/files/paper-config/config/leaf-global.yml new file mode 100644 index 0000000000..f4a1bb9eaf --- /dev/null +++ b/ansible/files/paper-config/config/leaf-global.yml @@ -0,0 +1,392 @@ + +# Leaf Config +# GitHub Repo: https://github.com/Winds-Studio/Leaf +# Discord: https://discord.com/invite/gfgAwdSEuM +config-version: '3.0' + +########### +# ASYNC # +########### +async: + # **Experimental feature** + # Enables parallel world ticking to improve performance on multi-core systems. + parallel-world-ticking: + enabled: false + threads: 8 + log-container-creation-stacktraces: false + disable-hard-throw: false + async-unsafe-read-handling: BUFFERED + # ** Experimental Feature ** + # Make entity tracking asynchronously, can improve performance significantly, + # especially in some massive entities in small area situations. + async-entity-tracker: + enabled: true + threads: 0 + # Make PlayerData saving asynchronously. + async-playerdata-save: + enabled: false + async-pathfinding: + enabled: true + max-threads: 12 + keepalive: 60 + queue-size: 0 + # The policy to use when the queue is full and a new task is submitted. + # FLUSH_ALL: All pending tasks will be run on server thread. + # CALLER_RUNS: Newly submitted task will be run on server thread. + # DISCARD: Newly submitted task will be dropped directly. + reject-policy: DISCARD + # Whether or not asynchronous mob spawning should be enabled. + # On servers with many entities, this can improve performance by up to 15%. You must have + # paper's per-player-mob-spawns setting set to true for this to work. + # One quick note - this does not actually spawn mobs async (that would be very unsafe). + # This just offloads some expensive calculations that are required for mob spawning. + async-mob-spawning: + enabled: true + # Whether or not asynchronous locator should be enabled. + # This offloads structure locating to other threads. + # Only for locate command, dolphin treasure finding and eye of ender currently. + async-locator: + enabled: false + threads: 0 + keepalive: 60 + # Makes chunk packet preparation and sending asynchronous to improve server performance. + # This can significantly reduce main thread load when many players are loading chunks. + async-chunk-send: + enabled: true + +########## +# PERF # +########## +performance: + # Use the new Virtual Thread introduced in JDK 21 for User Authenticator. + use-virtual-thread-for-user-authenticator: true + # Use the new Virtual Thread introduced in JDK 21 for profile lookup executor. + use-virtual-thread-for-profile-executor: true + # Use the new Virtual Thread introduced in JDK 21 for Async Chat Executor. + use-virtual-thread-for-async-chat-executor: true + # Use the new Virtual Thread introduced in JDK 21 for CraftAsyncScheduler. + use-virtual-thread-for-async-scheduler: true + create-snapshot-on-retrieving-blockstate: true + # Skip mob spawning for chunks with repeated failures exceeding `min-failed`. + # Randomly skip 1-`spawn-chance`% of these chunks from spawning attempts. + # Failure counter does not increment when spawn limits are reached. + throttle-mob-spawning: + enabled: false + monster: + min-failed: 8 + spawn-chance: 25.0 + creature: + min-failed: 8 + spawn-chance: 25.0 + ambient: + min-failed: 8 + spawn-chance: 25.0 + axolotls: + min-failed: 8 + spawn-chance: 25.0 + underground_water_creature: + min-failed: 8 + spawn-chance: 25.0 + water_creature: + min-failed: 8 + spawn-chance: 25.0 + water_ambient: + min-failed: 8 + spawn-chance: 25.0 + misc: + min-failed: 8 + spawn-chance: 25.0 + throttle-hopper-when-full: + # Throttles the hopper if target container is full. + enabled: false + # How many ticks to throttle when the Hopper is throttled. + skip-ticks: 8 + skip-map-item-data-updates-if-map-does-not-have-craftmaprenderer: true + datapack: + # Skip selecting inactive entities when using execute command. + # Will improve performance on servers with massive datapack functions. + skip-inactive-entity-for-execute-command: false + skip-ai-for-non-aware-mob: true + reduce-packets: + reduce-entity-move-packets: false + optimized-powered-rails: false + optimize-waypoint: false + optimize-random-tick: true + # Whether to optimize player movement processing by skipping unnecessary edge checks and avoiding redundant view distance updates. + optimize-player-movement: true + optimize-no-action-time: + disable-light-check: false + # Whether to only tick / update items in main hand and offhand instead of the entire inventory. + only-tick-items-in-hand: false + optimize-mob-despawn: true + optimize-block-entities: true + cache-biome: + enabled: false + mob-spawning: false + advancements: false + # May cause the inconsistent order of future compose tasks. + faster-structure-gen-future-sequencing: true + # Use faster random generator? + # Requires a JVM that supports RandomGenerator. + # Some JREs don't support this. + faster-random-generator: + enabled: false + # Which random generator will be used? + # See https://openjdk.org/jeps/356 + random-generator: Xoroshiro128PlusPlus + # Enable faster random generator for world generation. + # WARNING: This will affect world generation!!! + enable-for-worldgen: false + # Warn if you are not using legacy random source for slime chunk generation. + warn-for-slime-chunk: true + # Use legacy random source for slime chunk generation, + # to follow vanilla behavior. + use-legacy-random-for-slime-chunk: false + # Use direct random implementation instead of delegating to Java's RandomGenerator. + # This may improve performance but potentially changes RNG behavior. + use-direct-implementation: false + fast-biome-manager-seed-obfuscation: + # Replace vanilla SHA-256 seed obfuscation in BiomeManager with XXHash. + enabled: false + # Seed obfuscation key for XXHash. + seed-obfuscation-key: -2185297566474745118 + # Whether to cache expensive CraftEntityType#minecraftToBukkit call. + enable-cached-minecraft-to-bukkit-entitytype-convert: true + # Optimizes entity brains when + # they're far away from the player + dab: + enabled: true + # After enabling this, non-aquatic entities in the water will not be affected by DAB. + # This could fix entities suffocate in the water. + dont-enable-if-in-water: true + # This value determines how far away an entity has to be + # from the player to start being effected by DEAR. + start-distance: 10 + # This value defines how often in ticks, the furthest entity + # will get their pathfinders and behaviors ticked. 20 = 1s + max-tick-freq: 35 + # This value defines how much distance modifies an entity's + # tick frequency. freq = (distanceToPlayer^2) / (2^value)", + # If you want further away entities to tick less often, use 7. + # If you want further away entities to tick more often, try 9. + activation-dist-mod: 6 + # A list of entities to ignore for activation + blacklisted-entities: + - blaze + - cave_spider + - ghast + - goat + - hoglin + - husk + - piglin_brute + - silverfish + - stray + - sniffer + - villager + - witch + - wither_skeleton + - zombified_piglin + dont-save-entity: + # Disable save primed tnt on chunk unloads. + # Useful for redstone/technical servers, can prevent machines from being exploded by TNT, + # when player disconnected caused by Internet issue. + dont-save-primed-tnt: false + dont-save-falling-block: false + check-survival-before-growth: + # Check if a cactus can survive before growing. + cactus-check-survival: false + +########### +# FIXES # +########### +fixes: + fix-mc298464: false + # Don't let player join server if the server is full. + # If enable this, you should use 'purpur.joinfullserver' permission instead of + # PlayerLoginEvent#allow to let player join full server. + dont-place-player-if-server-full: false + +############## +# GAMEPLAY # +############## +gameplay-mechanisms: + use-spigot-item-merging-mechanism: false + # This section contains settings for mob spawner blocks. + spawner-settings: + # Enable custom spawner settings. Set to true to enable all features below. + enabled: false + # Various checks that can be enabled or disabled for spawner blocks. + checks: + # Check if there is the required light level to spawn the mob + light-level-check: false + # Check if there are the max amount of nearby mobs to spawn the mob + spawner-max-nearby-check: true + # Check if any players are in a radius to spawn the mob + check-for-nearby-players: true + # Check if there are physical blocks obstructing the spawn location, or if custom spawn rules (isValidPosition) fail due to block conditions. + spawner-block-checks: false + # Checks if there is water around that prevents spawning + water-prevent-spawn-check: false + # Ignore mob-specific spawn rules, like animals needing grass or specific biomes/blocks (does not affect light level or physical obstruction checks). + ignore-spawn-rules: false + # Minimum delay (in ticks) between spawner spawns. Higher values slow down spawners. + min-spawn-delay: 200 + # Maximum delay (in ticks) between spawner spawns. Higher values slow down spawners. + max-spawn-delay: 800 + # Enable to make only player pushable + only-player-pushable: false + knockback: + # Make snowball can knockback players. + snowball-knockback-players: false + # Make egg can knockback players. + egg-knockback-players: false + # Make players can knockback zombie. + can-player-knockback-zombie: true + flush-location-while-knockback-player: false + old-blast-protection-explosion-knockback: false + # Controls whether specified component information would be sent to clients. + # It may break resource packs and mods that rely on the information. + # Also, it can avoid some frequent client animations. + # Attention: This is not same as Paper's item-obfuscation, we only hide specified component information from player's inventory. + hide-item-component: + # Which type of components will be hidden from clients. + # It needs a component type list, incorrect things will not work. + hidden-types: [] + # If enabled, specified item component information from player's inventory will be hided. + enabled: false + death-item-drop-knockback: + # If true, items will drop randomly around the player on death. + drop-around: true + # Base speed for horizontal velocity when randomly dropping items. + horizontal-force: 0.5 + # Upward motion for randomly dropped items. + vertical-force: 0.2 + allow-tripwire-dupe: false + player: + # The max distance of UseItem for players. + # Set to -1 to disable max-distance-check. + # NOTE: if set to -1 to disable the check, + # players are able to use some packet modules of hack clients, + # and NoCom Exploit!! + max-use-item-distance: 1.0000001 + inventory-overflow-event: + # The event called when used plugin to Inventory#addItem + # into player's inventory, and the inventory is full. + # This is not recommended to use, please re-design to use the + # returned map of Inventory#addItem method as soon as possible! + enabled: false + # The full class name of the listener which listens to this inventory overflow event. + listener-class: com.example.package.PlayerInventoryOverflowEvent + afk-command: + # The AFK command based on Minecraft built-in idle-timeout mechanism + # Rest of AFK settings are in the Purpur config + enabled: false + +############# +# NETWORK # +############# +network: + protocol-support: + jade-protocol: false + appleskin-protocol: false + appleskin-protocol-sync-tick-interval: 20 + asteorbar-protocol: false + chatimage-protocol: false + xaero-map-protocol: false + xaero-map-server-id: -1381689667 + syncmatica-protocol: false + syncmatica-quota: false + syncmatica-quota-limit: 40000000 + do-a-barrel-roll-protocol: false + do-a-barrel-roll-allow-thrusting: false + do-a-barrel-roll-force-enabled: false + do-a-barrel-roll-force-installed: false + do-a-barrel-roll-installed-timeout: 0 + # WARNING: This option is NOT compatible with ProtocolLib and may cause + # issues with other plugins that modify packet handling. + # + # Optimizes non-flush packet sending by using Netty's lazyExecute method to avoid + # expensive thread wakeup calls when scheduling packet operations. + # + # Requires server restart to take effect. + OptimizeNonFlushPacketSending: false + # Whether or not enable chat message signature, + # disable will prevent players to report chat messages. + # And also disables the popup when joining a server without + # 'secure chat', such as offline-mode servers. + chat-message-signature: true + # Async switch connection state. + async-switch-state: false + +########## +# MISC # +########## +misc: + message: + # Unknown command message, using MiniMessage format, set to "default" to use vanilla message, + # placeholder: + # , show message of the command exception. + # , shows detail of the command exception. + unknown-command: default + rebrand: + server-mod-name: Leaf + server-gui-name: Leaf Console + sentry: + # Sentry DSN for improved error logging, leave blank to disable, + # Obtain from https://sentry.io/ + dsn: '' + # Logs with a level higher than or equal to this level will be recorded. + log-level: WARN + # Only log with a Throwable will be recorded after enabling this. + only-log-thrown: true + # Once you enable secure seed, all ores and structures are generated with 1024-bit seed + # instead of using 64-bit seed in vanilla, made seed cracker become impossible. + secure-seed: + enabled: false + # Remove Vanilla username check, + # allowing all characters as username. + remove-vanilla-username-check: false + # Enable player enter backend server through proxy + # without backend server enabling its bungee mode. + remove-spigot-check-bungee-config: false + # Enable to prevent console spam. + remove-change-non-editable-sign-warning: false + # When enabled, prevents plugins from programmatically changing player operator status. + # This helps maintain server security by blocking unauthorized op modifications. + # Server administrators can still manually manage ops through console/commands. + op-system-protection: + prevent-op-changes: false + # This section contains lag compensation features, + # which could ensure basic playing experience during a lag. + lag-compensation: + enabled: false + enable-for-water: false + enable-for-lava: false + including-5s-in-get-tps: true + # Connection message, using MiniMessage format, set to "default" to use vanilla join message. + # available placeholders: + # %player_name% - player name + # %player_displayname% - player display name + connection-message: + join: + enabled: true + # Join message of player + message: default + quit: + enabled: true + # Quit message of player + message: default + cache: + profile-lookup: + # Cache profile data lookups (skins, textures, etc.) to reduce API calls to Mojang. + enabled: false + # The timeout for profile lookup cache. Unit: Minutes. + timeout: 1440 + # Maximum number of profiles to cache. Higher values use more memory (not that much). + max-size: 8192 + # !!! WARNING !!! + # Disabling this is NOT recommended and can lead to severe server instability. + # Only disable this if you are an advanced user or debugging a specific issue + # and understand the risks involved. + async-catcher: + enabled: true diff --git a/ansible/files/paper-config/config/paper-world-defaults.yml b/ansible/files/paper-config/config/paper-world-defaults.yml index 268e001366..2a1c26750d 100644 --- a/ansible/files/paper-config/config/paper-world-defaults.yml +++ b/ansible/files/paper-config/config/paper-world-defaults.yml @@ -66,6 +66,7 @@ chunks: flush-regions-on-save: false max-auto-save-chunks-per-tick: 6 prevent-moving-into-unloaded-chunks: true + random-tick-scale: 10 collisions: allow-player-cramming-damage: false allow-vehicle-collisions: true @@ -134,6 +135,16 @@ entities: hatch-time: default spawning: all-chunks-are-slime-chunks: false + despawn-time: + llama_spit: 1200 + snowball: 1200 + fireball: 1200 + dragon_fireball: 1200 + small_fireball: 1200 + arrow: 3000 + shulker_bullet: 3000 + wither_skull: 3000 + trident: 3000 alt-item-despawn-rate: enabled: true items: @@ -261,12 +272,12 @@ entities: maximum: default minimum: default tracking-range-y: + enabled: true animal: default - display: default - enabled: false + display: 64 misc: default - monster: default - other: default + monster: 64 + other: 64 player: default environment: disable-explosion-knockback: false @@ -338,7 +349,7 @@ misc: disable-sprint-interruption-on-attack: false light-queue-size: 20 max-leash-distance: 10.0 - redstone-implementation: EIGENCRAFT + redstone-implementation: ALTERNATE_CURRENT shield-blocking-delay: 5 show-sign-click-command-failure-msgs-to-player: false update-pathfinding-on-block-update: false @@ -355,13 +366,13 @@ tick-rates: villager: validatenearbypoi: -1 container-update: 3 - dry-farmland: 1 - grass-spread: 2 + dry-farmland: 3 + grass-spread: 4 mob-spawner: 2 sensor: villager: secondarypoisensor: 40 - wet-farmland: 1 + wet-farmland: 3 unsupported-settings: disable-world-ticking-when-empty: false fix-invulnerable-end-crystal-exploit: true diff --git a/ansible/files/paper-config/libti.so b/ansible/files/paper-config/libti.so index e0d353146b..793a67278f 100755 Binary files a/ansible/files/paper-config/libti.so and b/ansible/files/paper-config/libti.so differ diff --git a/ansible/files/paper-config/ops.json b/ansible/files/paper-config/ops.json index 58de817a83..5c7609e026 100644 --- a/ansible/files/paper-config/ops.json +++ b/ansible/files/paper-config/ops.json @@ -10,5 +10,11 @@ "name": "banyough", "level": 3, "bypassesPlayerLimit": true + }, + { + "uuid": "7d4af441-a721-4e4c-9848-4e92a679f182", + "name": "HaKr_", + "level": 4, + "bypassesPlayerLimit": true } ] diff --git a/ansible/files/paper-config/plugins/Citadel/config.yml b/ansible/files/paper-config/plugins/Citadel/config.yml index c98c1495ac..12a1c35881 100644 --- a/ansible/files/paper-config/plugins/Citadel/config.yml +++ b/ansible/files/paper-config/plugins/Citadel/config.yml @@ -333,6 +333,9 @@ non_reinforceables: - DEAD_TUBE_CORAL_WALL_FAN - SCAFFOLDING - COBWEB + - GLOW_LICHEN + - SCULK_VEIN + - RESIN_CLUMP # Determines whether hanging entities (such as Item Frames) can be protected by their host block hangers_inherit_reinforcement: true @@ -349,6 +352,7 @@ world-border-buffers: shape: CIRCLE #Radius from border center you want the no reinforcement zone to kick in starting_radius: 9900 + decay: true #Center of the border center: x: 0.0 @@ -358,6 +362,7 @@ world-border-buffers: shape: CIRCLE #Radius from border center you want the no reinforcement zone to kick in starting_radius: 9900 + decay: true #Center of the border center: x: 0.0 diff --git a/ansible/files/paper-config/plugins/CivChat2/config.yml b/ansible/files/paper-config/plugins/CivChat2/config.yml index d4eb52dfe9..5caf3280e2 100644 --- a/ansible/files/paper-config/plugins/CivChat2/config.yml +++ b/ansible/files/paper-config/plugins/CivChat2/config.yml @@ -22,7 +22,12 @@ chat: color: GRAY globalGroup: '!' joinGlobalGroupByDefault: true + playtimeStars: true info: + ##FilterRelayGroup: Namelayer to send filtered messages from ! and local to for moderation - set to null to disable + filterRelayGroup: "Mods" + #muteTimeSeconds: How long to automute someone for sending a message with a banned word - 0 to disable + muteTimeSeconds: 3600 #debug: lots of debugging logging will show up in the console def=false debug: false #groups: if NameLayer groups is enabled def=true diff --git a/ansible/files/paper-config/plugins/CraftEnhance/server-recipes.yml b/ansible/files/paper-config/plugins/CraftEnhance/server-recipes.yml index 4e1f6cf48c..e6c744046d 100644 --- a/ansible/files/paper-config/plugins/CraftEnhance/server-recipes.yml +++ b/ansible/files/paper-config/plugins/CraftEnhance/server-recipes.yml @@ -2,6 +2,7 @@ disabled: - minecraft:netherite_scrap - minecraft:netherite_scrap_from_blasting - minecraft:netherite_ingot + - minecraft:lodestone - minecraft:netherite_upgrade_smithing_template - minecraft:sentry_armor_trim_smithing_template - minecraft:vex_armor_trim_smithing_template diff --git a/ansible/files/paper-config/plugins/ExilePearl/config.yml b/ansible/files/paper-config/plugins/ExilePearl/config.yml index ce1d254367..a074afe422 100644 --- a/ansible/files/paper-config/plugins/ExilePearl/config.yml +++ b/ansible/files/paper-config/plugins/ExilePearl/config.yml @@ -1,74 +1,74 @@ # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * # # STORAGE SETTINGS # # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * # -# +# # storage.type # - What type of storage to use -# Options: +# Options: # 0: File storage (default) # 1: MySQL storage # 2: RAM storage (dev use only) -# +# # storage.mysql.x # - Connection options for a MySQL database -# +# # storage.mysql.migrate_pp -# - When true, the MySQL storage will attempt to migrate PrisonPearl data +# - When true, the MySQL storage will attempt to migrate PrisonPearl data # when it connects for the first time. -# -# +# +# # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * # # GENERAL SETTINGS # # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * # -# +# # general.suicide_time_seconds -# - The timeout for the /suicide command before the player +# - The timeout for the /suicide command before the player # actually dies # -# general.max_pearled_alts +# general.max_pearled_alts # - Once a player has this many accounts pearled, any further BanStick alts will be prevented from logging in # -# general.max_pearled_message +# general.max_pearled_message # - Message shown to altbanned accounts when they attempt to login # # general.can_elytra_with_pearls # - Will prevent players opening an elytra with a pearl in their inventory, also disables picking up a pearl item # while they are gliding. -# +# # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * # # PEARL SETTINGS # # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * # # # pearls.autofree_worldborder -# - When true, pearls that are outside world-border will be freed during a +# - When true, pearls that are outside world-border will be freed during a # decay operation # # pearls.free_by_throwing # - When true, pearls can be freed by throwing them -# +# # pearls.free_teleport # - When true, freeing a prison pearled player teleports them to the freer -# +# # pearls.hotbar_needed -# - When true, players will only be pearled if the killer has a pearl +# - When true, players will only be pearled if the killer has a pearl # in their hotbar -# +# # pearls.decay_interval_min # - How often the decay task runs in minutes -# +# # pearls.decay_amount # - The raw health amount that is removed with each decay operation -# +# # pearls.max_value # - The maximum health value. # - This will determine the displayed health percent value -# +# # pearls.start_value # - The starting health value of pearls when they are created -# +# # pearls.TYPE.repair_materials # - The materials used to repair pearls -# - The repair value is how many health points are repaired for each item +# - The repair value is how many health points are repaired for each item # # pearls.bastion_harm_amount # - The amount of damage to deal to players when they are inside a @@ -91,34 +91,34 @@ # # pearls.cost_multiplier_days # - Time frame after which the cost for repairing pearls is increased, set to 0 to disable -# +# # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * # # HELP ITEM SETTINGS # # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * # -# +# # help_item.enabled # - When true, a help item is generated for pearled players on respawn -# +# # help_item.item_name # - The name given to the help item -# +# # help_item.item_text # - The help item text -# -# +# +# # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * # # DAMAGE LOG # # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * # -# +# # damage_log.enabled # - When true, the damage log task is enabled to track who is awarded pearls # If this is disabled, a pearl will only be awarded by a direct kill. # # damage_log.algorithm # - The algorithm type to use when awarding pearls -# Options: +# Options: # 0: Most Recent - Use most recent damager to award the pearl -# 1: Most Damage - Use highest tracked damager to award the pearl# +# 1: Most Damage - Use highest tracked damager to award the pearl# # # damage_log.tick_interval # - The interval in ticks for how often the damage tracking task runs @@ -133,7 +133,7 @@ # - The maximum damage amount to track for a given damager. # # damage_log.potion_damge -# - The base damage to apply for splash potions. This only applies to +# - The base damage to apply for splash potions. This only applies to # harming, poison, and weakness splash potions. # An extended or upgraded potion has a 2x multiplier of this value. # The potion intensity (0 - 1.0) is also used as a multiplier, so a player @@ -143,20 +143,20 @@ # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * # # EXILE RULES # # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * # -# +# # rules.pearl_radius # - The radius around their pearl that an exiled player can't enter -# - Set this value to 0 to disable the radius -# +# - Set this value to 0 to disable the radius +# # rules.ignite # - When false, exiled players can't set things on fire -# +# # rules.use_bed # - When false, exiled players can't use beds -# +# # rules.spawn_reset # - When true, resets the bed of an exiled player if killed by another player -# +# # rules.use_bucket # - When false, exiled players can't empty buckets or move them to other inventories # @@ -168,74 +168,74 @@ # # rules.fill_cauldron # - When false, exiled players can't fill cauldrons from water buckets -# +# # rules.use_potions # - When false, exiled players can't use potions -# +# # rules.throw_pearl # - When false, exiled players can't throw ender pearls -# +# # rules.pvp # - When false, exiled players can't harm other players -# +# # rules.kill_pets # - When false, exiled players can't harm pet mobs -# +# # rules.kill_mobs # - When false, exiled players can't harm protected mobs -# +# # rules.enchant # - When false, exiled players can't enchant items -# +# # rules.brew # - When false, exiled players can't brew potions # # rules.drink_brews # - When false, exiled players can't drink brewery brews -# +# # rules.suicide # - When true, exiled players can use the /suicide command -# +# # rules.mine_blocks # - When false, exiled players can't break any blocks -# +# # rules.collect_xp # - When false, exiled players can't collect xp orbs -# +# # rules.use_anvil # - When false, exiled players can't use anvils -# +# # rules.place_tnt # - When false, exiled players can't place TNT -# +# # rules.chat_local # - When false, exiled players can't chat locally. # - Requires CivChat2 plugin -# +# # rules.create_bastion # - When false, exiled players can't create bastions # - Requires Bastion plugin -# +# # rules.damage_bastion # - When false, exiled players can't damage bastions # - Requires Bastion plugin -# +# # rules.enter_bastion -# - When false, exiled players can't enter bastion fields that they +# - When false, exiled players can't enter bastion fields that they # aren't a member of # - Requires Bastion plugin -# +# # rules.damage_reinforcement # - When false, exiled players can't damage reinforced blocks # - Requires Citadel plugin -# +# # rules.place_snitch # - When false, exiled players can't place jukeboxes or noteblocks # - Requires JukeAlert plugin -# +# # rules.protected_mobs # - Mobs that are protected when rules.kill_mobs is set to false -# +# # rules.disallowed_worlds # - Worlds that pearls are not allowed to be stored in # @@ -277,25 +277,25 @@ pearls: repair_materials: PRISON: mana: - material: ENDER_EYE + material: ENDER_EYE name: Player Essence lore: - - Activity reward used to fuel pearls + - Activity reward used to fuel pearls repair: 12 #24 health is consumed per day, so two essence fuelds one pearl for a day # EXILE: -# mana: -# material: ENDER_EYE +# mana: +# material: ENDER_EYE # name: Player Essence # lore: -# - Activity reward used to fuel pearls +# - Activity reward used to fuel pearls # repair: 144 #768 health is consumed per day, so 144 allows a player to fill 1.5 exile pearls of equal streak (768*1.5/8) bastion_harm_amount: 2.0 upgrade_materials: -# mana: -# material: ENDER_EYE +# mana: +# material: ENDER_EYE # name: Player Essence # lore: -# - Activity reward used to fuel pearls +# - Activity reward used to fuel pearls # repair: 149 default_pearl_type: PRISON allow_pearl_stealing: true @@ -319,7 +319,7 @@ damage_log: max_amount: 30.0 potion_damge: 6.0 rules: - pearl_radius: 0 + pearl_radius: 1000 ignite: false # Can exiles start a fire use_bed: true # Can exiles use a bed spawn_reset: true # reset exile player bed when killed by a player diff --git a/ansible/files/paper-config/plugins/FactoryMod/config.yml b/ansible/files/paper-config/plugins/FactoryMod/config.yml index 641082987f..20eb4661ab 100644 --- a/ansible/files/paper-config/plugins/FactoryMod/config.yml +++ b/ansible/files/paper-config/plugins/FactoryMod/config.yml @@ -359,6 +359,7 @@ factories: - charcoal_from_dark_oak_log - charcoal_from_cherry_log - charcoal_from_mangrove_log + - charcoal_from_pale_oak_log - charcoal_from_crimson_stem - charcoal_from_warped_stem - charcoal_from_coal @@ -1225,6 +1226,12 @@ factories: - make_mangrove_hanging_signs - make_mangrove_trapdoors - make_mangrove_doors + - make_pale_oak_fences + - make_pale_oak_fence_gates + - make_pale_oak_signs + - make_pale_oak_hanging_signs + - make_pale_oak_trapdoors + - make_pale_oak_doors - make_crimson_fences - make_crimson_fence_gates - make_crimson_signs @@ -1289,6 +1296,7 @@ factories: amount: 16 recipes: - make_saddle + - make_harness - make_lead - make_bio_component - make_husbandry_kit @@ -3294,6 +3302,22 @@ recipes: ==: org.bukkit.inventory.ItemStack type: CHARCOAL amount: 128 + charcoal_from_pale_oak_log: + production_time: 4s + name: Make Charcoal from Pale Oak Logs + type: PRODUCTION + input: + log: + v: 3839 + ==: org.bukkit.inventory.ItemStack + type: PALE_OAK_LOG + amount: 64 + output: + charcoal: + v: 3839 + ==: org.bukkit.inventory.ItemStack + type: CHARCOAL + amount: 128 charcoal_from_crimson_stem: production_time: 4s name: Make Charcoal from Crimson Stems @@ -7790,6 +7814,107 @@ recipes: ==: org.bukkit.inventory.ItemStack type: MANGROVE_FENCE_GATE amount: 128 + make_pale_oak_fences: + production_time: 4s + name: Make Fences + type: PRODUCTION + input: + PALE_OAK_LOG: + v: 3839 + ==: org.bukkit.inventory.ItemStack + type: PALE_OAK_LOG + amount: 16 + output: + fence: + v: 3839 + ==: org.bukkit.inventory.ItemStack + type: PALE_OAK_FENCE + amount: 128 + make_pale_oak_signs: + production_time: 4s + name: Make Signs + type: PRODUCTION + input: + PALE_OAK_LOG: + v: 3839 + ==: org.bukkit.inventory.ItemStack + type: PALE_OAK_LOG + amount: 24 + output: + sign: + v: 3839 + ==: org.bukkit.inventory.ItemStack + type: PALE_OAK_SIGN + amount: 128 + make_pale_oak_hanging_signs: + production_time: 4s + name: Make Spruce Hanging Signs + type: PRODUCTION + input: + chest: + v: 3839 + ==: org.bukkit.inventory.ItemStack + type: PALE_OAK_LOG + amount: 24 + iron_ingot: + v: 3839 + ==: org.bukkit.inventory.ItemStack + type: IRON_INGOT + amount: 4 + output: + sign: + v: 3839 + ==: org.bukkit.inventory.ItemStack + type: PALE_OAK_HANGING_SIGN + amount: 128 + make_pale_oak_trapdoors: + production_time: 4s + name: Make Trap Doors + type: PRODUCTION + input: + PALE_OAK_LOG: + v: 3839 + ==: org.bukkit.inventory.ItemStack + type: PALE_OAK_LOG + amount: 32 + output: + trapdoor: + v: 3839 + ==: org.bukkit.inventory.ItemStack + type: PALE_OAK_TRAPDOOR + amount: 128 + make_pale_oak_doors: + production_time: 4s + name: Make Doors + type: PRODUCTION + input: + PALE_OAK_LOG: + v: 3839 + ==: org.bukkit.inventory.ItemStack + type: PALE_OAK_LOG + amount: 24 + output: + door: + v: 3839 + ==: org.bukkit.inventory.ItemStack + type: PALE_OAK_DOOR + amount: 128 + make_pale_oak_fence_gates: + production_time: 4s + name: Make Fence Gates + type: PRODUCTION + input: + PALE_OAK_LOG: + v: 3839 + ==: org.bukkit.inventory.ItemStack + type: PALE_OAK_LOG + amount: 16 + output: + door: + v: 3839 + ==: org.bukkit.inventory.ItemStack + type: PALE_OAK_FENCE_GATE + amount: 128 make_crimson_fences: production_time: 4s name: Make Fences @@ -8161,23 +8286,44 @@ recipes: v: 3839 ==: org.bukkit.inventory.ItemStack type: LEATHER - amount: 32 - wool: - v: 3839 - ==: org.bukkit.inventory.ItemStack - type: WHITE_WOOL amount: 16 iron: v: 3839 ==: org.bukkit.inventory.ItemStack type: IRON_INGOT - amount: 16 + amount: 3 output: saddle: v: 3839 ==: org.bukkit.inventory.ItemStack type: SADDLE amount: 8 + make_harness: + production_time: 4s + name: Make Harnesses + type: PRODUCTION + input: + leather: + v: 3839 + ==: org.bukkit.inventory.ItemStack + type: LEATHER + amount: 16 + glass: + v: 3839 + ==: org.bukkit.inventory.ItemStack + type: GLASS + amount: 6 + wool: + v: 3839 + ==: org.bukkit.inventory.ItemStack + type: WHITE_WOOL + amount: 3 + output: + saddle: + v: 3839 + ==: org.bukkit.inventory.ItemStack + type: WHITE_HARNESS + amount: 8 make_torch: production_time: 4s name: Make Redstone Torches @@ -11943,6 +12089,12 @@ recipes: lore: - Crack it open for a surprise! outputs: + display: + display: + v: 3839 + ==: org.bukkit.inventory.ItemStack + type: EGG + amount: 1 #Food--- 60% egg: chance: 0.15 @@ -13146,6 +13298,12 @@ recipes: lore: - Crack me in a factory for a prize! outputs: + display: + display: + v: 3839 + ==: org.bukkit.inventory.ItemStack + type: PRISMARINE_SHARD + amount: 1 # Trash - 70% of total (0.175 each) cobblestone: chance: 0.175 @@ -13504,140 +13662,154 @@ recipes: ==: org.bukkit.inventory.ItemStack type: ARMADILLO_SPAWN_EGG amount: 1 - # Music Disks - 1% of total (0.000526 each) + # Music Disks - 1% of total (0.000476 each) record_13: - chance: 0.000526 + chance: 0.000476 record_13: v: 3839 ==: org.bukkit.inventory.ItemStack type: MUSIC_DISC_13 amount: 1 record_cats: - chance: 0.000526 + chance: 0.000476 record_cats: v: 3839 ==: org.bukkit.inventory.ItemStack type: MUSIC_DISC_CAT amount: 1 record_blocks: - chance: 0.000526 + chance: 0.000476 record_blocks: v: 3839 ==: org.bukkit.inventory.ItemStack type: MUSIC_DISC_BLOCKS amount: 1 record_chirp: - chance: 0.000526 + chance: 0.000476 record_chirp: v: 3839 ==: org.bukkit.inventory.ItemStack type: MUSIC_DISC_CHIRP amount: 1 record_far: - chance: 0.000526 + chance: 0.000476 record_far: v: 3839 ==: org.bukkit.inventory.ItemStack type: MUSIC_DISC_FAR amount: 1 record_mall: - chance: 0.000526 + chance: 0.000476 record_mall: v: 3839 ==: org.bukkit.inventory.ItemStack type: MUSIC_DISC_MALL amount: 1 record_mellohi: - chance: 0.000526 + chance: 0.000476 record_mellohi: v: 3839 ==: org.bukkit.inventory.ItemStack type: MUSIC_DISC_MELLOHI amount: 1 record_stal: - chance: 0.000526 + chance: 0.000476 record_stal: v: 3839 ==: org.bukkit.inventory.ItemStack type: MUSIC_DISC_STAL amount: 1 record_strad: - chance: 0.000526 + chance: 0.000476 record_strad: v: 3839 ==: org.bukkit.inventory.ItemStack type: MUSIC_DISC_STRAD amount: 1 record_ward: - chance: 0.000526 + chance: 0.000476 record_ward: v: 3839 ==: org.bukkit.inventory.ItemStack type: MUSIC_DISC_WARD amount: 1 record_11: - chance: 0.000526 + chance: 0.000476 record_11: v: 3839 ==: org.bukkit.inventory.ItemStack type: MUSIC_DISC_11 amount: 1 record_wait: - chance: 0.000526 + chance: 0.000476 record_wait: v: 3839 ==: org.bukkit.inventory.ItemStack type: MUSIC_DISC_WAIT amount: 1 record_pigstep: - chance: 0.000526 + chance: 0.000476 record_pigstep: v: 3839 ==: org.bukkit.inventory.ItemStack type: MUSIC_DISC_PIGSTEP amount: 1 record_otherside: - chance: 0.000526 + chance: 0.000476 record_otherside: v: 3839 ==: org.bukkit.inventory.ItemStack type: MUSIC_DISC_OTHERSIDE amount: 1 record_relic: - chance: 0.000526 + chance: 0.000476 record_relic: v: 3839 ==: org.bukkit.inventory.ItemStack type: MUSIC_DISC_RELIC amount: 1 record_5: - chance: 0.000526 + chance: 0.000476 record_5: v: 3839 ==: org.bukkit.inventory.ItemStack type: MUSIC_DISC_5 amount: 1 record_precipice: - chance: 0.000526 + chance: 0.000476 record_5: v: 3839 ==: org.bukkit.inventory.ItemStack type: MUSIC_DISC_PRECIPICE amount: 1 record_creator: - chance: 0.000526 + chance: 0.000476 record_5: v: 3839 ==: org.bukkit.inventory.ItemStack type: MUSIC_DISC_CREATOR amount: 1 record_creator_music_box: - chance: 0.000526 + chance: 0.000476 record_5: v: 3839 ==: org.bukkit.inventory.ItemStack type: MUSIC_DISC_CREATOR_MUSIC_BOX amount: 1 + record_tears: + chance: 0.000476 + record_5: + v: 3839 + ==: org.bukkit.inventory.ItemStack + type: MUSIC_DISC_TEARS + amount: 1 + record_lava_chicken: + chance: 0.000476 + record_5: + v: 3839 + ==: org.bukkit.inventory.ItemStack + type: MUSIC_DISC_LAVA_CHICKEN + amount: 1 #Resources - 25% (Divided seperately over all below) iron_ingot: chance: 0.051 diff --git a/ansible/files/paper-config/plugins/Finale/config.yml b/ansible/files/paper-config/plugins/Finale/config.yml index 866b3a7d9b..f3851837aa 100644 --- a/ansible/files/paper-config/plugins/Finale/config.yml +++ b/ansible/files/paper-config/plugins/Finale/config.yml @@ -94,7 +94,7 @@ weaponModification: netheriteAxe: material: NETHERITE_AXE damage: 6 - armourDamageMultiplier: 2 + armourDamageMultiplier: 1.78 diamondAxe: material: DIAMOND_AXE damage: 5 @@ -296,7 +296,7 @@ damageModifiers: multiplier: 1.0 CRIT: mode: DIRECT - multiplier: 0.82 + multiplier: 0.825 STRENGTH_EFFECT: mode: DIRECT multiplier: 0.86 @@ -315,6 +315,9 @@ damageModifiers: FIREWORK: mode: DIRECT multiplier: 1.0 + CART: + mode: DIRECT + multiplier: 0.75 invulTicksEnabled: false invulnerableTicks: diff --git a/ansible/files/paper-config/plugins/JukeAlert/config.yml b/ansible/files/paper-config/plugins/JukeAlert/config.yml index c237d410f6..79488ca0b7 100644 --- a/ansible/files/paper-config/plugins/JukeAlert/config.yml +++ b/ansible/files/paper-config/plugins/JukeAlert/config.yml @@ -4,6 +4,7 @@ snitchConfigs: ==: org.bukkit.inventory.ItemStack type: NOTE_BLOCK amount: 1 + v: 3839 id: 0 name: Snitch range: 11 @@ -22,6 +23,7 @@ snitchConfigs: ==: org.bukkit.inventory.ItemStack type: JUKEBOX amount: 1 + v: 3839 id: 1 name: Logsnitch range: 11 diff --git a/ansible/files/paper-config/plugins/KiraBukkitGateway/config.yml b/ansible/files/paper-config/plugins/KiraBukkitGateway/config.yml index fc3d7cc425..3034dd4568 100644 --- a/ansible/files/paper-config/plugins/KiraBukkitGateway/config.yml +++ b/ansible/files/paper-config/plugins/KiraBukkitGateway/config.yml @@ -33,3 +33,5 @@ console: bar: key: ip regex: '.*is brand new!.*' + +server-name: "main" diff --git a/ansible/files/paper-config/plugins/LuckPerms/config.yml b/ansible/files/paper-config/plugins/LuckPerms/config.yml index 224b6b5c38..d5af242fa5 100644 --- a/ansible/files/paper-config/plugins/LuckPerms/config.yml +++ b/ansible/files/paper-config/plugins/LuckPerms/config.yml @@ -243,7 +243,7 @@ watch-files: true # configured below. # => custom Uses a messaging service provided using the LuckPerms API. # => auto Attempts to automatically setup a messaging service using redis or sql. -messaging-service: none +messaging-service: pluginmsg # If LuckPerms should automatically push updates after a change has been made with a command. auto-push-updates: true @@ -256,7 +256,7 @@ push-log-entries: true # - If you have LuckPerms installed on your backend servers as well as a BungeeCord proxy, you # should set this option to false on either your backends or your proxies, to avoid players being # messaged twice about log entries. -broadcast-received-log-entries: true +broadcast-received-log-entries: false # Settings for Redis. # Port 6379 is used by default; set address to "host:port" if differs diff --git a/ansible/files/paper-config/plugins/MobLimit/config.yml b/ansible/files/paper-config/plugins/MobLimit/config.yml index 08e7759ac0..b610daf906 100644 --- a/ansible/files/paper-config/plugins/MobLimit/config.yml +++ b/ansible/files/paper-config/plugins/MobLimit/config.yml @@ -9,18 +9,68 @@ reasons: mooshroom: dumb: true sheep: - dumb: true + count: 6 + radius: 32 chicken: dumb: true + count: 5 + radius: 32 + frog: + dumb: true + count: 5 + radius: 32 + axolotl: + count: 2 + radius: 32 + rabbit: + dumb: true + count: 3 + radius: 32 + wolf: + count: 5 + radius: 32 + bucket: + axolotl: + count: 2 + radius: 32 egg: chicken: dumb: true + count: 5 + radius: 32 + sniffer: + count: 5 + radius: 32 + turtle: + count: 5 + radius: 32 + spawn_egg: + sniffer: + count: 5 + radius: 32 + axolotl: + count: 2 + radius: 32 natural: turtle: - count: 8 - # The radius to check for + count: 5 radius: 32 dolphin: count: 1 - # The radius to check for radius: 128 + chicken: + count: 5 + radius: 32 + rabbit: + count: 3 + radius: 32 + wolf: + count: 5 + radius: 32 + place: + hopper_minecart: + count: 8 + radius: 32 + chest_minecart: + count: 5 + radius: 32 diff --git a/ansible/files/paper-config/plugins/PlaceholderAPI/config.yml b/ansible/files/paper-config/plugins/PlaceholderAPI/config.yml new file mode 100644 index 0000000000..068ef77e7a --- /dev/null +++ b/ansible/files/paper-config/plugins/PlaceholderAPI/config.yml @@ -0,0 +1,18 @@ +# PlaceholderAPI +# Version: 2.11.6 +# Created by: extended_clip +# Contributors: https://github.com/PlaceholderAPI/PlaceholderAPI/graphs/contributors +# Issues: https://github.com/PlaceholderAPI/PlaceholderAPI/issues +# Expansions: https://placeholderapi.com/ecloud +# Wiki: https://wiki.placeholderapi.com/ +# Discord: https://helpch.at/discord +# No placeholders are provided with this plugin by default. +# Download placeholders: /papi ecloud +check_updates: false +cloud_enabled: false +cloud_sorting: "name" +boolean: + 'true': 'yes' + 'false': 'no' +date_format: MM/dd/yy HH:mm:ss +debug: false diff --git a/ansible/files/paper-config/plugins/RealisticBiomes/config.yml b/ansible/files/paper-config/plugins/RealisticBiomes/config.yml index c96b031de7..01fb7caf32 100644 --- a/ansible/files/paper-config/plugins/RealisticBiomes/config.yml +++ b/ansible/files/paper-config/plugins/RealisticBiomes/config.yml @@ -498,7 +498,7 @@ plants: biomes: forests: 0.5 birch_forests: 1.0 - flatland: 0.25 + flatlands: 0.25 meadows: 0.25 SPRUCE_SAPLING: diff --git a/ansible/files/paper-config/plugins/SimpleAdminHacks/config.yml b/ansible/files/paper-config/plugins/SimpleAdminHacks/config.yml index cff1602bba..396330a39d 100644 --- a/ansible/files/paper-config/plugins/SimpleAdminHacks/config.yml +++ b/ansible/files/paper-config/plugins/SimpleAdminHacks/config.yml @@ -180,7 +180,7 @@ hacks: disableEnderCrystalDamage: true disableMiningFatigue: true canEquipBanners: true - disableLavaCobbleMountains: true + disableLavaCobbleMountains: false disableWanderingTrader: false preventPearlGlitching: true preventUsingEyeOfEnder: false @@ -351,13 +351,6 @@ hacks: delay: 10000 message: '%Victim% was combat tagged by %Attacker%' broadcast: [OP, CONSOLE] - DisableAI: - enabled: false - # Specify below the living entities and their spawning circumstances you want to disable the AI for. - # NOTE: See https://papermc.io/javadocs/paper/1.16/org/bukkit/entity/EntityType.html - # NOTE: See https://papermc.io/javadocs/paper/1.16/org/bukkit/event/entity/CreatureSpawnEvent.SpawnReason.html - # Note: You can also just state "ALL" for all spawn circumstances - VILLAGER: ALL Experimental: enabled: true combatspy: false @@ -619,3 +612,13 @@ hacks: enabled: false server: main world: world + TooHappyGhasts: + enabled: true + PlayerCap: + enabled: true + FairPlay: + enabled: true + PlayerCount: + enabled: true + Trowel: + enabled: true diff --git a/ansible/files/paper-config/plugins/SuperVanish/config.yml b/ansible/files/paper-config/plugins/SuperVanish/config.yml index 52a33c57fe..ae54601441 100644 --- a/ansible/files/paper-config/plugins/SuperVanish/config.yml +++ b/ansible/files/paper-config/plugins/SuperVanish/config.yml @@ -122,7 +122,7 @@ ExternalInvisibility: ServerList: # Should this plugin adjust the amount of players in the serverlist? (-1 per invisible player) - AdjustAmountOfOnlinePlayers: true + AdjustAmountOfOnlinePlayers: false # Should this plugin hide invisible players in the list of logged in players? # You can view this list when your mouse hovers over the amount of online players. AdjustListOfLoggedInPlayers: true diff --git a/ansible/files/paper-config/plugins/TAB/groups.yml b/ansible/files/paper-config/plugins/TAB/groups.yml index 2ed803735e..64ad2e410a 100644 --- a/ansible/files/paper-config/plugins/TAB/groups.yml +++ b/ansible/files/paper-config/plugins/TAB/groups.yml @@ -15,7 +15,7 @@ example_group: # default settings for all groups, all groups will take properties from this section unless player's primary group overrides a specific setting _DEFAULT_: - tabprefix: "%vault-prefix%" + tabprefix: "%vault-prefix%%civchat_prefix%" tagprefix: "%vault-prefix%" customtabname: "%essentials_nickname%" customtagname: "%essentials_nickname%" # don't forget to enable unlimited nametag mode to make this one work diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-biome-provider-image-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-biome-provider-image-0.1.0-BETA+834404c47.jar deleted file mode 100644 index 099da77598..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-biome-provider-image-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-biome-provider-pipeline-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-biome-provider-pipeline-0.1.0-BETA+834404c47.jar deleted file mode 100644 index fde064e840..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-biome-provider-pipeline-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-biome-provider-single-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-biome-provider-single-0.1.0-BETA+834404c47.jar deleted file mode 100644 index 87978a2302..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-biome-provider-single-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-chunk-generator-noise-3d-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-chunk-generator-noise-3d-0.1.0-BETA+834404c47.jar deleted file mode 100644 index 1142f7a201..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-chunk-generator-noise-3d-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-command-addons-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-command-addons-0.1.0-BETA+834404c47.jar deleted file mode 100644 index 8de541f641..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-command-addons-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-command-packs-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-command-packs-0.1.0-BETA+834404c47.jar deleted file mode 100644 index 64f892c7e5..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-command-packs-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-command-profiler-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-command-profiler-0.1.0-BETA+834404c47.jar deleted file mode 100644 index 7c33d8d380..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-command-profiler-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-command-structures-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-command-structures-0.1.0-BETA+834404c47.jar deleted file mode 100644 index 4ddd4c5f93..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-command-structures-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-config-biome-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-config-biome-0.1.0-BETA+834404c47.jar deleted file mode 100644 index 18bbb044c0..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-config-biome-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-config-distributors-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-config-distributors-0.1.0-BETA+834404c47.jar deleted file mode 100644 index 7615f1363b..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-config-distributors-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-config-feature-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-config-feature-0.1.0-BETA+834404c47.jar deleted file mode 100644 index 0485e22070..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-config-feature-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-config-flora-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-config-flora-0.1.0-BETA+834404c47.jar deleted file mode 100644 index e2877e4f19..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-config-flora-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-config-locators-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-config-locators-0.1.0-BETA+834404c47.jar deleted file mode 100644 index 74240e9e8d..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-config-locators-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-config-noise-function-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-config-noise-function-0.1.0-BETA+834404c47.jar deleted file mode 100644 index bafd0b8835..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-config-noise-function-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-config-ore-0.1.0-BETA+e339b2665-all.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-config-ore-0.1.0-BETA+e339b2665-all.jar deleted file mode 100644 index 965551e262..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-config-ore-0.1.0-BETA+e339b2665-all.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-config-palette-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-config-palette-0.1.0-BETA+834404c47.jar deleted file mode 100644 index 3bfb99e03a..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-config-palette-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-config-structure-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-config-structure-0.1.0-BETA+834404c47.jar deleted file mode 100644 index f06371aa7e..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-config-structure-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-generation-stage-feature-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-generation-stage-feature-0.1.0-BETA+834404c47.jar deleted file mode 100644 index c2c6363bc8..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-generation-stage-feature-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-generation-stage-structure-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-generation-stage-structure-0.1.0-BETA+834404c47.jar deleted file mode 100644 index 303646a2a9..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-generation-stage-structure-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-language-yaml-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-language-yaml-0.1.0-BETA+834404c47.jar deleted file mode 100644 index 1b3904161c..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-language-yaml-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-palette-block-shortcut-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-palette-block-shortcut-0.1.0-BETA+834404c47.jar deleted file mode 100644 index db7d0fa079..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-palette-block-shortcut-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-structure-block-shortcut-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-structure-block-shortcut-0.1.0-BETA+834404c47.jar deleted file mode 100644 index f1922a44b3..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-structure-block-shortcut-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-structure-mutator-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-structure-mutator-0.1.0-BETA+834404c47.jar deleted file mode 100644 index c97e12cc2b..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-structure-mutator-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-structure-sponge-loader-0.1.0-BETA+834404c47-all.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-structure-sponge-loader-0.1.0-BETA+834404c47-all.jar deleted file mode 100644 index 184ed5d9a1..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-structure-sponge-loader-0.1.0-BETA+834404c47-all.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-structure-terrascript-loader-0.1.0-BETA+834404c47-all.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-structure-terrascript-loader-0.1.0-BETA+834404c47-all.jar deleted file mode 100644 index ad2fbb80e4..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-structure-terrascript-loader-0.1.0-BETA+834404c47-all.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-terrascript-function-check-noise-3d-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-terrascript-function-check-noise-3d-0.1.0-BETA+834404c47.jar deleted file mode 100644 index c84f0a18ce..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-terrascript-function-check-noise-3d-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/Terra-terrascript-function-sampler-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/Terra-terrascript-function-sampler-0.1.0-BETA+834404c47.jar deleted file mode 100644 index 37ab74015a..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/Terra-terrascript-function-sampler-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/bootstrap/Terra-api-addon-loader-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/bootstrap/Terra-api-addon-loader-0.1.0-BETA+834404c47.jar deleted file mode 100644 index e68205e030..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/bootstrap/Terra-api-addon-loader-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/addons/bootstrap/Terra-manifest-addon-loader-0.1.0-BETA+834404c47.jar b/ansible/files/paper-config/plugins/Terra/addons/bootstrap/Terra-manifest-addon-loader-0.1.0-BETA+834404c47.jar deleted file mode 100644 index e383e770e8..0000000000 Binary files a/ansible/files/paper-config/plugins/Terra/addons/bootstrap/Terra-manifest-addon-loader-0.1.0-BETA+834404c47.jar and /dev/null differ diff --git a/ansible/files/paper-config/plugins/Terra/config.yml b/ansible/files/paper-config/plugins/Terra/config.yml deleted file mode 100644 index 2c177f8ff5..0000000000 --- a/ansible/files/paper-config/plugins/Terra/config.yml +++ /dev/null @@ -1,20 +0,0 @@ -# This file contains core configuration options for Terra -# -# As you can see, there is not that much to configure here. -# For advanced configuration, use config packs. Config -# packs are loaded from the packs directory. Check the -# wiki for information regarding config pack development. - -debug: - commands: false - log: false - profiler: false - script: false -dump-default: true -biome-search-resolution: 4 -cache: - structure: 32 - sampler: 128 - biome-provider: 32 -script: - max-recursion: 1000 diff --git a/ansible/files/paper-config/plugins/Votifier/config.yml b/ansible/files/paper-config/plugins/Votifier/config.yml new file mode 100644 index 0000000000..c63984b753 --- /dev/null +++ b/ansible/files/paper-config/plugins/Votifier/config.yml @@ -0,0 +1,25 @@ +# The IP to listen to. Use 0.0.0.0 if you wish to listen to all interfaces on your server. (All IP addresses) +# This defaults to the IP you have configured your server to listen on, or 0.0.0.0 if you have not configured this. +host: "0.0.0.0" + +# Port to listen for new votes on +port: -1 + +# Setting this option to true will disable handling of Protocol v1 packets. While the old protocol is not secure, this +# option is currently not recommended as most voting sites only support the old protocol at present. However, if you are +# using NuVotifier's proxy forwarding mechanism, enabling this option will increase your server's security. +disable-v1-protocol: false + +# All tokens, labeled by the serviceName of each server list. +tokens: + # Default token for all server lists, if another isn't supplied. + default: "1" + +# Configuration section for all vote forwarding to NuVotifier +forwarding: + # Sets whether to set up a remote method for fowarding. Supported methods: + # - none - Does not set up a forwarding method. + # - pluginMessaging - Sets up plugin messaging + method: pluginMessaging + pluginMessaging: + channel: nuvotifier:votes diff --git a/ansible/files/paper-config/spigot.yml b/ansible/files/paper-config/spigot.yml index e71774b590..924df58696 100644 --- a/ansible/files/paper-config/spigot.yml +++ b/ansible/files/paper-config/spigot.yml @@ -13,7 +13,7 @@ settings: debug: false sample-count: 12 - player-shuffle: 0 + player-shuffle: 100 user-cache-size: 1000 save-user-cache-on-stop-only: false moved-wrongly-threshold: 1.0625 @@ -21,7 +21,7 @@ settings: timeout-time: ${CIV_WATCHDOG_TIMEOUT_TIME} restart-on-crash: false restart-script: ./start.sh - netty-threads: 4 + netty-threads: 6 attribute: maxHealth: max: 2048.0 @@ -77,21 +77,21 @@ world-settings: animals: 16 monsters: 16 raiders: 16 - misc: 16 - water: 16 + misc: 12 + water: 12 villagers: 16 flying-monsters: 32 wake-up-inactive: - animals-max-per-tick: 4 + animals-max-per-tick: 2 animals-every: 1200 - animals-for: 100 + animals-for: 40 monsters-max-per-tick: 8 monsters-every: 400 monsters-for: 100 villagers-max-per-tick: 1 villagers-every: 600 villagers-for: 10 - flying-monsters-max-per-tick: 8 + flying-monsters-max-per-tick: 1 flying-monsters-every: 200 flying-monsters-for: 100 villagers-work-immunity-after: 200 @@ -101,7 +101,7 @@ world-settings: ignore-spectators: false entity-tracking-range: players: 160 - animals: 32 + animals: 64 monsters: 32 misc: 96 other: 96 diff --git a/ansible/files/proxy-config-offline/plugins/luckperms/config.yml b/ansible/files/proxy-config-offline/plugins/luckperms/config.yml index fdf9734751..aea56d13bb 100644 --- a/ansible/files/proxy-config-offline/plugins/luckperms/config.yml +++ b/ansible/files/proxy-config-offline/plugins/luckperms/config.yml @@ -243,7 +243,7 @@ watch-files: true # configured below. # => custom Uses a messaging service provided using the LuckPerms API. # => auto Attempts to automatically setup a messaging service using redis or sql. -messaging-service: none +messaging-service: pluginmsg # If LuckPerms should automatically push updates after a change has been made with a command. auto-push-updates: true @@ -256,7 +256,7 @@ push-log-entries: true # - If you have LuckPerms installed on your backend servers as well as a BungeeCord proxy, you # should set this option to false on either your backends or your proxies, to avoid players being # messaged twice about log entries. -broadcast-received-log-entries: true +broadcast-received-log-entries: false # Settings for Redis. # Port 6379 is used by default; set address to "host:port" if differs diff --git a/ansible/files/proxy-config/plugins/ajqueue/config.yml b/ansible/files/proxy-config/plugins/ajqueue/config.yml new file mode 100644 index 0000000000..55dc048aad --- /dev/null +++ b/ansible/files/proxy-config/plugins/ajqueue/config.yml @@ -0,0 +1,438 @@ +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# # +# _ ___ # +# (_) / _ \ # +# __ _ _ | | | | _ _ ___ _ _ ___ # +# / _` || || | | || | | | / _ \| | | | / _ \ # +# | (_| || |\ \/' /| |_| || __/| |_| || __/ # +# \__,_|| | \_/\_\ \__,_| \___| \__,_| \___| # +# _/ | # +# |__/ # +# # +# Welcome to the config for ajQueue! # +# # +# Make sure to read the comments above each option to know what that option does. # +# # +# If you have any questions, first make sure you've read the comment above the option, then # +# feel free to join my discord and ask there (invite link is on the plugin page) # +# # +# Happy configuring! # +# # +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + + +# The time the server will wait between sending people in the queue (in seconds, supports decimals) +# Default: 5 +wait-time: 3.0 + +# The time ajQueue will wait between sending players update messages on +# what position they are in the queue, their ETA, and status of the queue (in seconds) +# You can set to any negative number to disable the messages +# Default: 10 +message-time: 15 + + +# Should we require permissions for players to be able to join queues? +# If enabled, players will be required to have the permission ajqueue.queue. to be able to join queues +# Replace with the name of the server or group to let the player queue for +# Default: false +require-permission: false + +# Should we act like servers a player doesn't have permission for doesn't exist? +# So if a player doesn't have permission to queue for a server, and they try to queue for it, +# ajQueue will send the server-not-exist message instead of the noperm message. +# This option does nothing if require-permission is disabled. +# IMO this is pointless, but people keep asking for it so here you go... +# Default: false +act-like-no-permission-servers-dont-exist: false + +# Require a permission for players to be able to join a queue from a server. +# If enabled, players will need the permission ajqueue.joinfrom. to join queues from . +# Replace with the name of the server you want players to join queues from. +# For example, if the player is in the lobby, they will need the ajqueue.joinfrom.lobby permission to be able to join any queues. +# Default: false +joinfrom-server-permission: false + +# Should we enable the ajqueue.bypasspaused permission? +# If enabled, anyone with the permission ajqueue.bypasspaused will be able to join paused servers +# Default: false +enable-bypasspaused-permission: false + + +# What kick reasons should cause the player to be removed from the queue? +# This works on contains, so you only need to include a word or two from the kick message. +# For example, if one of the below kick reasons is 'banned' and the player gets kicked when trying to connect to +# a server in a queue with a message saying "You are banned from this server!" then it will kick them from the queue too. +kick-reasons: + - 'banned' + - 'blacklisted' + + +# When a player is kicked from a server, should we automatically add that player to the queue? +# On BungeeCord, you will still need to use another plugin to make sure the player doesn't get kicked from the proxy completely. +# Default: false +auto-add-to-queue-on-kick: false +# The delay for the above option. +# In seconds, decimals supported. +# Default: 1 +auto-add-to-queue-on-kick-delay: 1 + +# With what kick reasons should we auto-add the player to the queue +# This won't work if auto-add-to-queue-on-kick is disabled. +# If you set it to [], then all kick messages will cause the player to be added to the queue +# This works on contains, so you don't have to include the whole kick message, just a few words. +auto-add-kick-reasons: + - "restarting" + - "full" + - "closed" + +# Server names in this list will be excluded from auto-add-to-queue-on-kick +# For example, if "survival" is in this list, if a player is kicked from the "survival" server they will not +# be auto-added to queue +auto-add-excluded-servers: [] + + +# If a player is in a server, you can have the plugin make them automatically join a queue for another server +# Example with the default values: Player joins the limbo server, they will auto-join the queue for the lobbys group +# Note that you don't have to use groups. Just put the name of a server to use a single server instead. +# Each entry here MUST have a queue server and a target server/queue seperated by a colon. +# ajQueue cannot guess which server you want! +# You do NOT have to set a queue server to use queue commands. +queue-servers: [] + +# How much should we delay queueing players in queue servers? +# You should only use this if you have issues with the instant sending. +# Set to 0 or any negative number to have no delay +# In milliseconds. Maximum value is 3000 (3 seconds) +# You will need to disable skip-queue-server-if-possible to use this option. +# Default: 0 +queue-server-delay: 0 + +# Before a player connects to a queue-server, check if they would be instantly sent to the target server. +# If they would, this option will bypass the queue-server completely and connect them directly to the target server. +# If they would not be sent instantly, then they will be put in the queue in the queue-server as normal. +# Default: true +skip-queue-server-if-possible: true + + +# Should we completely kick the user from the server if they are in a queue-server +# and are kicked from the server with one of the above reasons? +# Note this will do nothing on servers that aren't queue-servers +# (as in the config option queue-servers) +# Default: true +kick-kicked-players: true + +# This option allows you to manually set player caps for servers +# Note that this does NOT override the player cap returned by the server. +# Format: - ':' +# Example: - 'lobbys:50' +# The example above will limit the lobbys group to have a total of 50 players. +# This works on both groups and individual servers +manual-max-players: [] + + +# Server groups are a group of servers that you can queue for. It will send you to the server that is the least full. +# If all servers in the group are full, it will act the same as it would when a single server is full. +# Same if all servers are offline. It will only send players to servers that are online. +# Format: "groupname:server1,server2,etc" +server-groups: [] + +# What balancer should we use for groups? +# If a group is not specified here, then the default one is used +# Example: - "bedwars:minigame" +# The example above will set the bedwars group to use the minigame balancer +# Balancers: +# default - Will send the player to the server in the group with the least number of players +# minigame - Will send the player to the server with the most players, until that server is full, which it will then send to the next server +# first - Will send the player to the first available server in the group (as long as it is joinable) +balancer-types: [] + +# If a player is in a server that is in a group and they re-queue for that group, should we allow it? +# false - prevent player from re-queueing; say "already connected" message +# true - let player re-queue; will send them to a server other than the one they're connected to +# Default: false +allow-group-requeue: false + + +# What priority should we give whitelisted players priority when the server is whitelisted? +# This will have no effect if the server isn't whitelisted +# If you set to 0, this will be disabled +# If you have the free version, set it to 1 to enable +give-whitelisted-players-priority: 0 + +# What priority should we give players that are able to bypass paused priority when the server is paused? +# This will have no effect if the server isn't paused +# If you set to 0, this will be disabled +# If you have the free version, set it to 1 to enable +give-pausedbypass-players-priority: 0 + +# What priority should we give players that are able to bypass full servers priority when the server is full? +# This will have no effect if the server isn't full +# If you are using make-room, this also applies to that +# If you set to 0, this will be disabled +# If you have the free version, set it to 1 to enable +give-fulljoin-players-priority: 0 + + +# Should we remove a player from the queue if they move servers? +# This will remove the player from if they switch to any other server +# Default: false +remove-player-on-server-switch: false + +# Should we enable the server command being a queue command? +# This may require extra setup on bungeecord. See the wiki: +# https://wiki.ajg0702.us/ajqueue/setup/replacing-server-command +# Requires a server restart after changing to apply +# Default: false +enable-server-command: true + +# What servers should we make slash server commands for? +# For example, if survival is in this list, then if a player executes /survival +# then they will be put in the queue for survival +# This works for both servers and groups +# If you have ajQueuePlus, you can also make aliases. +# For example, if you have a server called lobby, and you want people to be able to use /hub, you can use this: +# - "hub:lobby" +# If you have the free version, you can only put the server name, no aliases. +slash-servers: + - "main" + - "pvp" + - "mini" + +# Should we enable /send as an alias for /ajQueue send? +# If on bungeecord, this requires extra setup (same as replacing the server command but with send instead) +# Requires a server restart to apply. +# Default: false +enable-send-alias: false + + +# Should we enable priority messages? +# Configure the priority messages in the option below. +# Default: false +enable-priority-messages: true + +# Messages we send to players with priority queue when they join the queue +# In the free version, "priority" is 1. +# The * will send when a player joins with any priority. +# The format is : +# Example: 1:You have a priority of 1! +priority-messages: + - "8:You have priority because you are a top 10 PvPer!" + - "10:You have priority because you just left the server!" + + +# Should the plugin send a title to the player? +# The title shows what position the player is in the queue +# Default: false +send-title: false + +# Should we log to the proxy console when a player fails to get sent to a server from the queue? +# Enable this if you are having an issue with one player stopping the queue +# Default: false +send-fail-debug: false + +# Should the plugin send an actionbar to the player? +# The actionbar contains some info such as which server they are queued for, what position they are in, estimated time remaining, etc. +send-actionbar: true + +# THIS FEATURE IS ONLY AVAILABLE ON ajQueuePlus (https://www.spigotmc.org/resources/ajqueueplus.79123/) +# This will show players a different name than the actual proxy server name +# for example, instead of showing players "event-a", this option can make it appear as "Event A" +# With this example, you would use this: - "event-a:Event A" +# Note that currently players still have to use the normal names in queue commands and leave commands +# Format: "realname:Alias" +server-aliases: + - "event-a:Event A" + +# The time that a server has to be offline to be marked as offline instead of restarting (in seconds) +# Default: 120 +offline-time: 200 + +# On velocity, should we send a player a message when they are kicked while trying to connect to a server with the queue? +# This has no effect on bungee, because the message is sent from bungee and there's no way to change that in ajQueue +# Default: false +velocity-kick-message: false + +# Should we wait until the server is done loading to load the servers? +# Enable this if you have a plugin that adds servers to the server list during startup. +# Default: false +wait-to-load-servers: false + +# How long should we wait after the server finishes loading to load the server list? +# Only works if the above is enabled. +# This is in milliseconds so 1000 = 1 second +# Default: 500 +wait-to-load-servers-delay: 500 + +# How often (in seconds) we should check for new servers to add queues for. +# If you dynamically add servers, set this to something other than 0. +# To disable automatic reloading of servers, set to 0 +# Default: 0 +reload-servers-interval: 0 + +# Should we let players join more than one queue? +# If enabled, players will be able to be in multiple queues at once. +# If disabled, players will be removed from the previous queue when joining a new queue +# Default: true +allow-multiple-queues: true + +# If the player is queued for multiple servers, which server should we pick to use in things like placeholders and actionbars +# Options are first and last +# Default: last +multi-server-queue-pick: last + +# How long should we wait after a server is online before sending players? +# The server will still show up as offline or restarting until this amount of time after its up +# Meant to let your server 'cool down' after lag from starting up +# In seconds +# Default: 1 +wait-after-online: 1 + +# This is for helping with finding issues with the server pinged +# This will spam the console when enabled +# When this enabled, if servers are offline then it will spam errors. You can ignore them. +# Default: false +pinger-debug: false + +# When a queue is paused, should we prevent players from joining it? +# (instead of having players wait in the queue until it's unpaused) +# Default: false +prevent-joining-paused: false + +# When a server goes back online, should we send all players in the queue instantly? +# Default: false +send-all-when-back-online: true + +# Should we allow tab-completing in the /queue command? +# Any server the player doesn't have permission for (require-permission) +# will not tab-complete +# Default: true +tab-complete-queues: true + +# Should we have no wait time for these servers? +# If the server is joinable, the plugin will attempt to send players who join these queues as soon as they join. +# If the server is not immediately joinable, they will have to wait for the normal wait-time +# This also works with group +# NOTE: Server names are caps sensitive +send-instantly: [] + +# Should we require permissions for queue-servers to work? +# If enabled, players will need to have the ajqueue.queueserver. permission +# being the target server +# Note that this will only affect queue-servers +# Default: false +require-queueserver-permission: false + +# After how many (unsuccessful) attempts of sending the player should we remove them from the queue? +# Set to -1 to make it unlimited (not recommended) +# Default: 10 +max-tries: 10 + +# What QueueHolder should we use? +# By default, the only QueueHolder available is 'default' +# But more may be available via addons (registered via the API) +queue-holder: default + + +# Should we enable the ajqueue.make-room permission? +# The make-room permission will force there to be room in a server. +# So, if a player with this permission queues for a server and has this permission, +# someone from the server will be moved to the lobby to make room +# This can be further configured using the next few options +# Default: false +enable-make-room-permission: false + +# What server should the make-room permission move players to? +# Default: lobby +make-room-kick-to: lobby + +# For the make-room permission, players with the lowest priority are kicked first. +# Of those players, this option decides which to kick. +# true - kick players who have been on the server the longest +# false - kick players who have been on the server the shortest +# Default: true +make-room-kick-longest-playtime: true + + +# Should we check every 4 seconds if a player has a higher priority permission than before +# If they do, they are removed and re-added to the queue (only if they would be put in a higher position) +# Default: false +re-check-priority: true + +# Should we check to make sure that people don't get sent quicker than wait-time? +# Default: true +check-last-player-sent-time: true + +# Should we send debug info to the console when priority queue is used? +# This will print several lines to the console when a player joins a queue, +# so you should probably only use this for debugging purposes +# Default: false +priority-queue-debug: false + +# What protocols do servers support? +# The protocol version number list can be found here: https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Protocol_version_numbers +# Format: server(s):protocol(s) +# Example: 1.17:755,756 +# This example will only allow 1.17 and 1.17.1 on the server called 1.17 +# Example: lobby-1,lobby-2,lobby-3:754,755,756 +# This example will only allow 1.16.5, 1.17, and 1.17.1 on the 3 lobby servers +# Note that this will only restrict queues. +# If you go around the queue (e.g., using the default /server command), then this will not apply +supported-protocols: + - "1.17:755,756" + +# Should the updater be enabled? +enable-updater: false + +# Should we force players to be queued every few seconds for queue-servers? +# This will check every few seconds, and if a player in a queue-server is +# not in the queue for the target server, it will add them. +force-queue-server-target: true + +# How long should the cooldown for queue commands be? (in seconds) +# Set to -1 or 0 to have no cooldown +# Default: 3 +queue-command-cooldown: 3 + +# Should any server switch (including the initial join) count against the queue command cooldown? +include-server-switch-in-cooldown: false + +# The minimum time between pinging the server. (in seconds) +# If ajQueue is pinging your backend servers too often, raise this number +minimum-ping-time: 1.0 + +# In ajQueuePlus, if your permission plugin isn't yet supported, you can use this workaround to +# be able to use levels 1-10 for priority, or 15, 30, 60, and 120 for stayqueued +# If you want more levels than that, contact aj to add support for your permission plugin if possible. +# Does nothing if you are not on ajQueuePlus, or if you have a supported permission plugin +plus-level-fallback: false + + +# Enabling this option will prevent ajQueue from communicating with ajQueue on the backend using Plugin Messaging. +# Generally you should leave this on the default of false, to allow ajQueue to communicate. +# Enabling this will cause all features of ajQueue on the backend to not work! +# ** If you do not fully understand this option, please leave it disabled! ** +# Default: false +disable-proxy-communication: false + +# Should we print some extra stuff to the console that might help aj diagnose some issues? +debug: false + + + +# # # # # # # # # # # # # # # # # # # # # +# # +# End of config. Happy queue-ing :) # +# # +# # # # # # # # # # # # # # # # # # # # # + + + + +# Don't touch this number please +config-version: 53 + + +# This is ONLY here so that they can be moved to messages.yml. Please edit these in messages.yml! +protocol-names: [] +# ^ only edit these in messages.yml diff --git a/ansible/files/proxy-config/plugins/ajqueue/messages.yml b/ansible/files/proxy-config/plugins/ajqueue/messages.yml new file mode 100644 index 0000000000..9e2eb089ee --- /dev/null +++ b/ansible/files/proxy-config/plugins/ajqueue/messages.yml @@ -0,0 +1,207 @@ +status: + offline: + base: '&c{SERVER} is {STATUS}. &7You are in position &f{POS}&7 of &f{LEN}&7.' + offline: offline + restarting: restarting + full: full + restricted: restricted + paused: paused + whitelisted: whitelisted + online: + base: '&7You are in position &f{POS}&7 of &f{LEN}&7' + left-last-queue: '&aYou left the last queue you were in.' + now-in-queue: |- + &aYou are now queued for {SERVER}! &7You are in position &f{POS}&7 of &f{LEN}&7. + &7Type &f/leavequeue&7 or &fclick here&7 to leave the queue! + now-in-empty-queue: '' + sending-now: '&aSending you to &f{SERVER} &anow..' + making-room: Making room for you.. + priority-increased: You now have higher priority! Moving you up in + the queue.. + skipping-queue-server: '' +errors: + server-not-exist: '&cThe server {SERVER} does not exist!' + already-queued: '&cYou are already queued for that server!' + player-only: '&cThis command can only be executed as a player!' + already-connected: '&cYou are already connected to this server!' + cant-join-paused: '&cYou cannot join the queue for {SERVER} because it is paused.' + deny-joining-from-server: '&cYou are not allowed to join queues from this server!' + wrong-version: + base: You must be on {VERSIONS} to join this server! + or: ' or ' + comma: ', ' + too-fast-queue: You're queueing too fast! + kicked-to-make-room: You were moved to the lobby to make room for another + player. + make-room-failed: + player: Failed to make room for you in that server. + admin: Failed to make room for you in that server. Check the console + for more information. +commands: + leave-queue: '&aYou left the queue for {SERVER}!' + reload: '&aConfig and messages reloaded successfully!' + joinqueue: + usage: '&cUsage: /joinqueue ' + kick: + usage: 'Usage: /ajqueue kick [queue]' + no-player: '&cCould not find {PLAYER}! Make sure they are in a queue!' + unknown-server: '&cCould not find queue {QUEUE}. Make sure you spelled it + correctly!' + success: Kicked {PLAYER} from {NUM} queue{s}! + kickall: + usage: 'Usage: /ajqueue kickall ' + success: Kicked {NUM} player{s} from {SERVER}! + pausequeueserver: + unpaused: You are no longer paused! You can now use queue-servers + normally. + paused: You are now paused! You will no longer be sent using + queue-servers. + reminder: 'Reminder: You are currently paused for queue-servers, + so you will not be sent using them! Use /ajQueue pausequeueserver + to un-pause and return to normal behaviour' + leave: + more-args: '&cPlease specify which queue you want to leave! &7You are in these + queues: {QUEUES}' + queues-list-format: '&f{NAME}&7, ' + not-queued: '&cYou are not queued for that server! &7You are in these queues: + {QUEUES}' + no-queues: '&cYou are not queued!' + pause: + more-args: '&cUsage: /ajqueue pause [on/off]' + no-server: '&cThat server does not exist!' + success: '&aThe queue for &f{SERVER} &ais now {PAUSED}' + paused: + 'true': '&epaused' + 'false': '&aun-paused' + send: + player-not-found: '&cThat player could not be found. Make sure they are online!' + usage: 'Usage: /ajqueue send ' + listqueues: + header: '&9Queues:' + format: '{COLOR}{NAME}&7: {COUNT} + queued' +noperm: '&cYou do not have permission to do this!' +format: + time: + mins: '{m}m {s}s' + secs: '{s} seconds' +list: + format: '&b{SERVER} &7({COUNT}): {LIST}' + playerlist: '&9{NAME}&7, ' + total: '&7Total players in queues: &f{TOTAL}' + none: '&7None' +spigot: + actionbar: + online: '&7You are queued for &f{SERVER}&7. You are in position &f{POS}&7 + of &f{LEN}&7' + offline: '&7You are queued for &f{SERVER}&7. &7You are in position &f{POS}&7 + of &f{LEN}&7' +send: '&aAdded &f{PLAYER}&a to the queue for &f{SERVER}' +remove: '&aRemoved &f{PLAYER} from all queues they were in.' +placeholders: + queued: + none: None + position: + none: None + estimated_time: + none: None + status: + online: '&aOnline' + offline: '&cOffline' + restarting: '&cRestarting' + full: '&eFull' + restricted: '&eRestricted' + paused: '&ePaused' + whitelisted: '&eWhitelisted' +title: + title: '' + subtitle: You are #{POS} in the queue! + sending-now: + title: '' + subtitle: Sending you to {SERVER} now.. +max-tries-reached: '&cUnable to connect to {SERVER}. Max retries reached.' +auto-queued: '&aYou''ve been auto-queued for {SERVER} because you were kicked.' +velocity-kick-message: 'You were kicked while trying to join {SERVER}: {REASON}' +updater: + update-available: |- + + An update is available for ajQueue! + You can download it by clicking here + or running /ajQueue update + + no-update: There is not an update available + success: The update has been downloaded! Now just restart the server + already-downloaded: '&aYou have already downloaded an update! &7Restart the server + to apply it' + slow-feedback: '&7Checking for update and downloading...' + disabled: '&cThe updater is disabled! &7Please enable it in the config to download + updates.' + warnings: + could-not-delete-old-jar: '&aUpdate downloaded&e but the old jar could not + be deleted. &7Please delete the old jar before restarting the server.' + errors: + while-checking: '&eAn error occurred while checking for an update. &7See the + console for more info.' + unknown: '&eAn unknown error occurred: {ERROR}' + could-not-find-jar: '&cCould not find the old jar!&7 Make sure it is named + similar to ajQueue-x.x.x.jar' + while-downloading: '&eAn error occurred while downloading an update. &7See + the console for more info.' + missing-update-token: '&cMissing update token! &7See the&f updater-config.yml&f + file for more info.' + invalid-update-token: '&cInvalid update token! &7See the&f updater-config.yml&f + file for more info. If you are lost, please contact support.' + uncaught: '&cAn error occurred while executing this command. &7See the console.' +velocity-built-in-kick-messages: + already-connecting: Already connecting + already-connected: Already connected + success: Success + cancelled: Connection canceled + disconnected: Connection failed with unknown reason +protocol-names: + '5': 1.7.10 + '47': 1.8.9 + '107': '1.9' + '108': 1.9.1 + '109': 1.9.2 + '110': 1.9.4 + '210': 1.10.2 + '315': '1.11' + '316': 1.11.2 + '335': '1.12' + '338': 1.12.1 + '340': 1.12.2 + '393': '1.13' + '401': 1.13.1 + '404': 1.13.2 + '477': '1.14' + '480': 1.14.1 + '485': 1.14.2 + '490': 1.14.3 + '498': 1.14.4 + '573': '1.15' + '575': 1.15.1 + '578': 1.15.2 + '735': '1.16' + '736': 1.16.1 + '751': 1.16.2 + '753': 1.16.3 + '754': 1.16.5 + '755': '1.17' + '756': 1.17.1 + '757': 1.18.1 + '758': 1.18.2 + '759': '1.19' + '760': 1.19.2 + '761': 1.19.3 + '762': 1.19.4 + '763': 1.20.1 + '764': 1.20.2 + '765': 1.20.4 + '766': 1.20.6 + '767': 1.21.1 + '768': 1.21.3 + '769': 1.21.4 + '770': 1.21.5 + '771': 1.21.6 + '772': 1.21.8 diff --git a/ansible/files/proxy-config/plugins/ajqueue/updater-config.yml b/ansible/files/proxy-config/plugins/ajqueue/updater-config.yml new file mode 100644 index 0000000000..cfae621c88 --- /dev/null +++ b/ansible/files/proxy-config/plugins/ajqueue/updater-config.yml @@ -0,0 +1,13 @@ +# This is the config for the updater. It was created so that the updater secret would be in a separate file from the config + +# The Updater Token is used for updating my premium plugins. +# If you have ajQueuePlus, generate one here: https://verify.ajg0702.us/updaterToken +# If you have ajQueue, this is not needed. +updater-token: "" + + +# If you want to disable the updater, you can do that in the normal config.yml + + +# Don't touch this +config-version: 1 \ No newline at end of file diff --git a/ansible/files/proxy-config/plugins/civannouncements/config.yml b/ansible/files/proxy-config/plugins/civannouncements/config.yml index ae8989cbe5..337d847adc 100644 --- a/ansible/files/proxy-config/plugins/civannouncements/config.yml +++ b/ansible/files/proxy-config/plugins/civannouncements/config.yml @@ -1,8 +1,20 @@ + +# messages must be in minimessage format +# https://webui.advntr.dev/ scheduledAnnouncements: - - cron: "29 2 * * *" - message: "{\"text\":\"Restart in 15 minutes\",\"color\":\"gold\"}" - - cron: "30 2 * * *" - message: "{\"text\":\"Restart in 5 minutes\",\"color\":\"gold\"}" - - cron: "31 2 * * *" - message: "Restart in 1 minute" - minimessage: true + - cron: "30 9 * * *" + message: "[CivMC] Restart in 30 minutes" + - cron: "45 9 * * *" + message: "[CivMC] Restart in 15 minutes" + - cron: "55 9 * * *" + message: "[CivMC] Restart in 5 minutes" + - cron: "59 9 * * *" + message: "[CivMC] Restart in 1 minute" + - cron: "30 9 * * *" + message: "Scheduled daily restart in % minute$" + bossbar_seconds: 1800 + +restart: + bar: "Update restart in % minute$" + message: "[CivMC] Server is restarting for an update in % minute$" + kick: "Server is restarting for an update" diff --git a/ansible/files/proxy-config/plugins/civproxy/config.yml b/ansible/files/proxy-config/plugins/civproxy/config.yml new file mode 100644 index 0000000000..8d8610e998 --- /dev/null +++ b/ansible/files/proxy-config/plugins/civproxy/config.yml @@ -0,0 +1,10 @@ +database: + user: ${CIV_MYSQL_USERNAME} + password: ${CIV_MYSQL_PASSWORD} + host: ${CIV_MYSQL_HOST} + port: 3306 + database: civproxy + poolsize: 10 + connection_timeout: 10000 + idle_timeout: 600000 + max_lifetime: 7200000 diff --git a/ansible/files/proxy-config/plugins/kiragateway/config.yml b/ansible/files/proxy-config/plugins/kiragateway/config.yml new file mode 100644 index 0000000000..1dd7ed93fc --- /dev/null +++ b/ansible/files/proxy-config/plugins/kiragateway/config.yml @@ -0,0 +1,10 @@ +# Your RabbitMQ credentials +rabbitmq: + user: ${CIV_RABBITMQ_USERNAME} + password: ${CIV_RABBITMQ_PASSWORD} + host: ${CIV_RABBITMQ_HOST} + port: 5672 + incomingQueue: kira-to-gateway + outgoingQueue: gateway-to-kira + +ops: ["771e81cd-2f11-43b4-bd86-731fe468f131", "82569b12-c44c-4864-8a73-85a9192ee8f9", "2c7c20f7-8472-4780-9b35-4cd4fae460c6", "7d4af441-a721-4e4c-9848-4e92a679f182"] diff --git a/ansible/files/proxy-config/plugins/luckperms/config.yml b/ansible/files/proxy-config/plugins/luckperms/config.yml index fdf9734751..7b14ff76a7 100644 --- a/ansible/files/proxy-config/plugins/luckperms/config.yml +++ b/ansible/files/proxy-config/plugins/luckperms/config.yml @@ -243,7 +243,7 @@ watch-files: true # configured below. # => custom Uses a messaging service provided using the LuckPerms API. # => auto Attempts to automatically setup a messaging service using redis or sql. -messaging-service: none +messaging-service: pluginmsg # If LuckPerms should automatically push updates after a change has been made with a command. auto-push-updates: true diff --git a/ansible/files/proxy-config/plugins/minimotd-velocity/extra-configs/mini.conf b/ansible/files/proxy-config/plugins/minimotd-velocity/extra-configs/mini.conf new file mode 100644 index 0000000000..21c6b982f4 --- /dev/null +++ b/ansible/files/proxy-config/plugins/minimotd-velocity/extra-configs/mini.conf @@ -0,0 +1,17 @@ +icon-enabled=false +motd-enabled=true +motds=[ + { + icon=random + line1="<#3bad4c>Building with more than just blocks." + line2="<#3ec0d1>CivMC Mini" + } +] +player-count-settings { + allow-exceeding-maximum=true + disable-player-list-hover=false + hide-player-count=false + max-players=300 + max-players-enabled=true + servers=[] +} diff --git a/ansible/files/proxy-config/velocity.jar b/ansible/files/proxy-config/velocity.jar new file mode 100644 index 0000000000..5f77692872 Binary files /dev/null and b/ansible/files/proxy-config/velocity.jar differ diff --git a/ansible/files/proxy-config/velocity.toml b/ansible/files/proxy-config/velocity.toml index 5ec93f0f1f..58c7aba9dc 100644 --- a/ansible/files/proxy-config/velocity.toml +++ b/ansible/files/proxy-config/velocity.toml @@ -10,7 +10,7 @@ motd = "<#3bad4c>Building with Tutelle." # What should we display for the maximum number of players? (Velocity does not support a cap # on the number of players online.) -show-max-players = 69 +show-max-players = 110 # Should we authenticate players with Mojang? By default, this is on. online-mode = true diff --git a/ansible/inventories/civmc-New Production.yml b/ansible/inventories/civmc-New Production.yml index aaa8ebd06c..7f8af1f422 100644 --- a/ansible/inventories/civmc-New Production.yml +++ b/ansible/inventories/civmc-New Production.yml @@ -1,7 +1,7 @@ all: hosts: prod-swarm-1: - ansible_host: 158.69.168.66 + ansible_host: 23.163.152.211 ansible_user: actions children: swarm_manager: diff --git a/ansible/playbooks/stop-server.yml b/ansible/playbooks/stop-server.yml index 401e488b8c..3d7115420e 100644 --- a/ansible/playbooks/stop-server.yml +++ b/ansible/playbooks/stop-server.yml @@ -6,6 +6,7 @@ servers: - pvp - paper + - gamma - proxy tasks: diff --git a/ansible/playbooks/update-server-gamma.yml b/ansible/playbooks/update-server-gamma.yml new file mode 100644 index 0000000000..a9902e9e9b --- /dev/null +++ b/ansible/playbooks/update-server-gamma.yml @@ -0,0 +1,26 @@ +- name: Update Gamma Server + hosts: all + become: true + + vars: + servers: + - gamma + + tasks: + - name: Update Serer | Include Preflight + include_tasks: ../tasks/preflight-checks.yml + + - name: Update Server | Expand setting + include_tasks: ../tasks/expand-setting.yml + + - name: Update Server | Include Stop Tasks + include_tasks: + file: '../tasks/stop-server.yml' + + - name: Update Server | Include Deployment Tasks + include_tasks: + file: '../tasks/deploy/{{deploy_task}}.yml' + loop: + - stacks + loop_control: + loop_var: deploy_task diff --git a/ansible/playbooks/update-server-minecraft.yml b/ansible/playbooks/update-server-minecraft.yml index 8ce75b24d3..99b0f7e632 100644 --- a/ansible/playbooks/update-server-minecraft.yml +++ b/ansible/playbooks/update-server-minecraft.yml @@ -6,6 +6,7 @@ servers: - pvp - paper + - gamma - proxy tasks: diff --git a/ansible/playbooks/update-server.yml b/ansible/playbooks/update-server.yml index d6e62b6e97..360fb76911 100644 --- a/ansible/playbooks/update-server.yml +++ b/ansible/playbooks/update-server.yml @@ -6,6 +6,7 @@ servers: - pvp - paper + - gamma - proxy tasks: diff --git a/ansible/src/paper-plugins/BreweryX-3.4.10.jar b/ansible/src/paper-plugins/BreweryX-3.6.0.jar similarity index 79% rename from ansible/src/paper-plugins/BreweryX-3.4.10.jar rename to ansible/src/paper-plugins/BreweryX-3.6.0.jar index 8b34234937..1cb8598b10 100644 Binary files a/ansible/src/paper-plugins/BreweryX-3.4.10.jar and b/ansible/src/paper-plugins/BreweryX-3.6.0.jar differ diff --git a/ansible/src/paper-plugins/Chunky-Bukkit-1.4.28.jar b/ansible/src/paper-plugins/Chunky-Bukkit-1.4.40.jar similarity index 75% rename from ansible/src/paper-plugins/Chunky-Bukkit-1.4.28.jar rename to ansible/src/paper-plugins/Chunky-Bukkit-1.4.40.jar index 270a6ff3fb..f05c9dfc4b 100644 Binary files a/ansible/src/paper-plugins/Chunky-Bukkit-1.4.28.jar and b/ansible/src/paper-plugins/Chunky-Bukkit-1.4.40.jar differ diff --git a/ansible/src/paper-plugins/CraftEnhance-2.5.6.4.1.jar b/ansible/src/paper-plugins/CraftEnhance-2.5.6.4.1.jar deleted file mode 100644 index a16a8f5b72..0000000000 Binary files a/ansible/src/paper-plugins/CraftEnhance-2.5.6.4.1.jar and /dev/null differ diff --git a/ansible/src/paper-plugins/CraftEnhance-2.6.0.0.jar b/ansible/src/paper-plugins/CraftEnhance-2.6.0.0.jar new file mode 100644 index 0000000000..67d90ea5e8 Binary files /dev/null and b/ansible/src/paper-plugins/CraftEnhance-2.6.0.0.jar differ diff --git a/ansible/src/paper-plugins/DiscordSRV-Build-1.29.0.jar b/ansible/src/paper-plugins/DiscordSRV-Build-1.30.0.jar similarity index 88% rename from ansible/src/paper-plugins/DiscordSRV-Build-1.29.0.jar rename to ansible/src/paper-plugins/DiscordSRV-Build-1.30.0.jar index 2723de24b7..ab4ab92bfb 100644 Binary files a/ansible/src/paper-plugins/DiscordSRV-Build-1.29.0.jar and b/ansible/src/paper-plugins/DiscordSRV-Build-1.30.0.jar differ diff --git a/ansible/src/paper-plugins/GSit-2.3.1.jar b/ansible/src/paper-plugins/GSit-2.4.3.jar similarity index 53% rename from ansible/src/paper-plugins/GSit-2.3.1.jar rename to ansible/src/paper-plugins/GSit-2.4.3.jar index 153556765c..2dddfebd88 100644 Binary files a/ansible/src/paper-plugins/GSit-2.3.1.jar and b/ansible/src/paper-plugins/GSit-2.4.3.jar differ diff --git a/ansible/src/paper-plugins/Insights-6.19.5.jar b/ansible/src/paper-plugins/Insights-6.19.7.jar similarity index 57% rename from ansible/src/paper-plugins/Insights-6.19.5.jar rename to ansible/src/paper-plugins/Insights-6.19.7.jar index d5909bbc8f..dc61aa2b27 100644 Binary files a/ansible/src/paper-plugins/Insights-6.19.5.jar and b/ansible/src/paper-plugins/Insights-6.19.7.jar differ diff --git a/ansible/src/paper-plugins/InvSee++.jar b/ansible/src/paper-plugins/InvSee++.jar new file mode 100644 index 0000000000..638d47f784 Binary files /dev/null and b/ansible/src/paper-plugins/InvSee++.jar differ diff --git a/ansible/src/paper-plugins/LuckPerms-Bukkit-5.4.156.jar b/ansible/src/paper-plugins/LuckPerms-Bukkit-5.4.156.jar deleted file mode 100644 index 29897eb20e..0000000000 Binary files a/ansible/src/paper-plugins/LuckPerms-Bukkit-5.4.156.jar and /dev/null differ diff --git a/ansible/src/paper-plugins/LuckPerms-Bukkit-5.5.10.jar b/ansible/src/paper-plugins/LuckPerms-Bukkit-5.5.10.jar new file mode 100644 index 0000000000..063e4b39bb Binary files /dev/null and b/ansible/src/paper-plugins/LuckPerms-Bukkit-5.5.10.jar differ diff --git a/ansible/src/paper-plugins/MobLimit.jar b/ansible/src/paper-plugins/MobLimit.jar index f668a51092..2dace0291a 100644 Binary files a/ansible/src/paper-plugins/MobLimit.jar and b/ansible/src/paper-plugins/MobLimit.jar differ diff --git a/ansible/src/paper-plugins/MythicMobs-5.0.2.jar.disabled b/ansible/src/paper-plugins/MythicMobs-5.0.2.jar.disabled deleted file mode 100644 index 5c92fd03d4..0000000000 Binary files a/ansible/src/paper-plugins/MythicMobs-5.0.2.jar.disabled and /dev/null differ diff --git a/ansible/src/paper-plugins/PlaceholderAPI-2.11.6.jar b/ansible/src/paper-plugins/PlaceholderAPI-2.11.6.jar new file mode 100644 index 0000000000..ae82dd608a Binary files /dev/null and b/ansible/src/paper-plugins/PlaceholderAPI-2.11.6.jar differ diff --git a/ansible/src/paper-plugins/ProtocolLib.jar b/ansible/src/paper-plugins/ProtocolLib.jar index 5cdf1c9e86..0db5bc6d17 100644 Binary files a/ansible/src/paper-plugins/ProtocolLib.jar and b/ansible/src/paper-plugins/ProtocolLib.jar differ diff --git a/ansible/src/paper-plugins/SuperVanish-6.2.20.jar b/ansible/src/paper-plugins/SuperVanish-6.2.21.jar similarity index 70% rename from ansible/src/paper-plugins/SuperVanish-6.2.20.jar rename to ansible/src/paper-plugins/SuperVanish-6.2.21.jar index 6ff7e38902..16ab34cc6c 100644 Binary files a/ansible/src/paper-plugins/SuperVanish-6.2.20.jar and b/ansible/src/paper-plugins/SuperVanish-6.2.21.jar differ diff --git a/ansible/src/paper-plugins/TAB v5.0.7.jar b/ansible/src/paper-plugins/TAB v5.0.7.jar deleted file mode 100644 index 27ef91da95..0000000000 Binary files a/ansible/src/paper-plugins/TAB v5.0.7.jar and /dev/null differ diff --git a/ansible/src/paper-plugins/TAB v5.2.5.jar b/ansible/src/paper-plugins/TAB v5.2.5.jar new file mode 100644 index 0000000000..b2ff7808fb Binary files /dev/null and b/ansible/src/paper-plugins/TAB v5.2.5.jar differ diff --git a/ansible/src/paper-plugins/Terra-bukkit-6.4.3-BETA+ab60f14ff-shaded.jar b/ansible/src/paper-plugins/Terra-bukkit-6.4.3-BETA+ab60f14ff-shaded.jar deleted file mode 100644 index 5d141a2c66..0000000000 Binary files a/ansible/src/paper-plugins/Terra-bukkit-6.4.3-BETA+ab60f14ff-shaded.jar and /dev/null differ diff --git a/ansible/src/paper-plugins/ViaBackwards-5.3.2.jar b/ansible/src/paper-plugins/ViaBackwards-5.3.2.jar deleted file mode 100644 index aa5a1063fe..0000000000 Binary files a/ansible/src/paper-plugins/ViaBackwards-5.3.2.jar and /dev/null differ diff --git a/ansible/src/paper-plugins/ViaBackwards-5.5.0.jar b/ansible/src/paper-plugins/ViaBackwards-5.5.0.jar new file mode 100644 index 0000000000..1509faf859 Binary files /dev/null and b/ansible/src/paper-plugins/ViaBackwards-5.5.0.jar differ diff --git a/ansible/src/paper-plugins/ViaVersion-5.3.2.jar b/ansible/src/paper-plugins/ViaVersion-5.5.0.jar similarity index 50% rename from ansible/src/paper-plugins/ViaVersion-5.3.2.jar rename to ansible/src/paper-plugins/ViaVersion-5.5.0.jar index d8d501c6f8..1e11b84326 100644 Binary files a/ansible/src/paper-plugins/ViaVersion-5.3.2.jar and b/ansible/src/paper-plugins/ViaVersion-5.5.0.jar differ diff --git a/ansible/src/paper-plugins/WorldEditSelectionVisualizer-2.1.7.jar b/ansible/src/paper-plugins/WorldEditSelectionVisualizer-2.1.8.jar similarity index 70% rename from ansible/src/paper-plugins/WorldEditSelectionVisualizer-2.1.7.jar rename to ansible/src/paper-plugins/WorldEditSelectionVisualizer-2.1.8.jar index 65fef93fe4..50b622545e 100644 Binary files a/ansible/src/paper-plugins/WorldEditSelectionVisualizer-2.1.7.jar and b/ansible/src/paper-plugins/WorldEditSelectionVisualizer-2.1.8.jar differ diff --git a/ansible/src/paper-plugins/even-more-fish-2.0.0-SNAPSHOT-197.jar b/ansible/src/paper-plugins/even-more-fish-2.0.0-SNAPSHOT-197.jar deleted file mode 100644 index 6920191358..0000000000 Binary files a/ansible/src/paper-plugins/even-more-fish-2.0.0-SNAPSHOT-197.jar and /dev/null differ diff --git a/ansible/src/paper-plugins/even-more-fish-2.0.14.jar b/ansible/src/paper-plugins/even-more-fish-2.0.14.jar new file mode 100644 index 0000000000..6b40109215 Binary files /dev/null and b/ansible/src/paper-plugins/even-more-fish-2.0.14.jar differ diff --git a/ansible/src/paper-plugins/nuvotifier.jar b/ansible/src/paper-plugins/nuvotifier.jar new file mode 120000 index 0000000000..619be18915 --- /dev/null +++ b/ansible/src/paper-plugins/nuvotifier.jar @@ -0,0 +1 @@ +../proxy-plugins/nuvotifier.jar \ No newline at end of file diff --git a/ansible/src/paper-plugins/orebfuscator-plugin-5.5.2.jar b/ansible/src/paper-plugins/orebfuscator-plugin-5.5.4.jar similarity index 89% rename from ansible/src/paper-plugins/orebfuscator-plugin-5.5.2.jar rename to ansible/src/paper-plugins/orebfuscator-plugin-5.5.4.jar index d53eede1bd..e8f7c42565 100644 Binary files a/ansible/src/paper-plugins/orebfuscator-plugin-5.5.2.jar and b/ansible/src/paper-plugins/orebfuscator-plugin-5.5.4.jar differ diff --git a/ansible/src/paper-plugins/voicechat-bukkit-2.5.35.jar b/ansible/src/paper-plugins/voicechat-bukkit-2.5.35.jar new file mode 100644 index 0000000000..55a2f1218e Binary files /dev/null and b/ansible/src/paper-plugins/voicechat-bukkit-2.5.35.jar differ diff --git a/ansible/src/paper-plugins/worldedit-bukkit-7.3.10.jar b/ansible/src/paper-plugins/worldedit-bukkit-7.3.17.jar similarity index 70% rename from ansible/src/paper-plugins/worldedit-bukkit-7.3.10.jar rename to ansible/src/paper-plugins/worldedit-bukkit-7.3.17.jar index 9eee2af4b8..ab18b9741a 100644 Binary files a/ansible/src/paper-plugins/worldedit-bukkit-7.3.10.jar and b/ansible/src/paper-plugins/worldedit-bukkit-7.3.17.jar differ diff --git a/ansible/src/proxy-plugins/ajQueue-2.8.0-all.jar b/ansible/src/proxy-plugins/ajQueue-2.8.0-all.jar new file mode 100644 index 0000000000..84bb1cecd8 Binary files /dev/null and b/ansible/src/proxy-plugins/ajQueue-2.8.0-all.jar differ diff --git a/ansible/src/paper-plugins/Votifier-2.7.3.jar b/ansible/src/proxy-plugins/nuvotifier.jar similarity index 100% rename from ansible/src/paper-plugins/Votifier-2.7.3.jar rename to ansible/src/proxy-plugins/nuvotifier.jar diff --git a/ansible/tasks/configure/backups.yml b/ansible/tasks/configure/backups.yml index 9c9b583d00..bd60478662 100644 --- a/ansible/tasks/configure/backups.yml +++ b/ansible/tasks/configure/backups.yml @@ -16,7 +16,7 @@ cron: name: "backup" minute: 0 - hour: 6 + hour: 10 user: root job: /opt/backup-and-restart.sh - cron_file: /etc/cron.d/minecraft \ No newline at end of file + cron_file: /etc/cron.d/minecraft diff --git a/ansible/tasks/deploy/stacks.yml b/ansible/tasks/deploy/stacks.yml index b341a0bb33..772a474685 100644 --- a/ansible/tasks/deploy/stacks.yml +++ b/ansible/tasks/deploy/stacks.yml @@ -29,6 +29,7 @@ loop: - { path: '/opt/stacks/minecraft/paper-data', enabled: 'setting.minecraft.enabled and "''paper''" in servers' } - { path: '/opt/stacks/minecraft/pvp-data', enabled: 'setting.minecraft.enabled and "''pvp''" in servers' } + - { path: '/opt/stacks/minecraft/gamma-data', enabled: 'setting.minecraft.enabled and "''gamma''" in servers' } - { path: '/opt/stacks/minecraft/mariadb-data', enabled: setting.minecraft.enabled } - { path: '/opt/stacks/minecraft/postgres-data', enabled: setting.minecraft.enabled } @@ -56,6 +57,8 @@ - { path: 'minecraft/proxy-plugins', enabled: 'setting.minecraft.enabled and ''proxy'' in servers' } - { path: 'minecraft/pvp-config', enabled: 'setting.minecraft.enabled and ''pvp'' in servers' } - { path: 'minecraft/pvp-plugins', enabled: 'setting.minecraft.enabled and ''pvp'' in servers' } + - { path: 'minecraft/gamma-config', enabled: 'setting.minecraft.enabled and ''gamma'' in servers' } + - { path: 'minecraft/gamma-plugins', enabled: 'setting.minecraft.enabled and ''gamma'' in servers' } - name: Deploy Stacks | Synchronize Files become: false @@ -69,10 +72,12 @@ loop: - { file: '../../files/paper-config', target: 'minecraft', enabled: 'setting.minecraft.enabled and ''paper'' in servers' } - { file: '../../files/proxy-config', target: 'minecraft', enabled: 'setting.minecraft.enabled and ''proxy'' in servers' } + - { file: '../../files/gamma-config', target: 'minecraft', enabled: 'setting.minecraft.enabled and ''gamma'' in servers' } - { file: '../../files/pvp-config', target: 'minecraft', enabled: 'setting.minecraft.enabled and ''pvp'' in servers' } - { file: '../../build/paper-plugins', target: 'minecraft', enabled: 'setting.minecraft.enabled and ''paper'' in servers' } - { file: '../../build/proxy-plugins', target: 'minecraft', enabled: 'setting.minecraft.enabled and ''proxy'' in servers' } - { file: '../../build/pvp-plugins', target: 'minecraft', enabled: 'setting.minecraft.enabled and ''pvp'' in servers' } + - { file: '../../build/gamma-plugins', target: 'minecraft', enabled: 'setting.minecraft.enabled and ''gamma'' in servers' } - name: Deploy Stacks | Copy Files shell: 'cp -r /home/{{ansible_user}}/stacks/{{item.file}} /opt/stacks/{{item.file}}' @@ -83,9 +88,11 @@ - { file: 'minecraft/paper-config', enabled: 'setting.minecraft.enabled and ''paper'' in servers' } - { file: 'minecraft/proxy-config', enabled: 'setting.minecraft.enabled and ''proxy'' in servers' } - { file: 'minecraft/pvp-config', enabled: 'setting.minecraft.enabled and ''pvp'' in servers' } + - { file: 'minecraft/gamma-config', enabled: 'setting.minecraft.enabled and ''gamma'' in servers' } - { file: 'minecraft/paper-plugins', enabled: 'setting.minecraft.enabled and ''paper'' in servers' } - { file: 'minecraft/proxy-plugins', enabled: 'setting.minecraft.enabled and ''proxy'' in servers' } - { file: 'minecraft/pvp-plugins', enabled: 'setting.minecraft.enabled and ''pvp'' in servers' } + - { file: 'minecraft/gamma-plugins', enabled: 'setting.minecraft.enabled and ''gamma'' in servers' } - name: Deploy Stacks | Deploy Stacks when: diff --git a/ansible/templates/backup-and-restart.sh b/ansible/templates/backup-and-restart.sh index 4c5f82fa78..52212c4ede 100644 --- a/ansible/templates/backup-and-restart.sh +++ b/ansible/templates/backup-and-restart.sh @@ -2,39 +2,28 @@ echo "$(date) Stopping services for backup..." docker service scale minecraft_waterfall=0 -sleep 1m +sleep 30s docker service scale minecraft_paper=0 +docker service scale minecraft_gamma=0 docker service scale minecraft_pvp=0 -sleep 10m +sleep 5m echo "$(date) Starting backup..." -export AWS_ACCESS_KEY_ID={{secret.backup.s3_access_key_id}} -export AWS_SECRET_ACCESS_KEY={{secret.backup.s3_access_key}} -export RESTIC_PASSWORD={{secret.backup.restic_password}} -export RESTIC_PASSWORD2={{secret.backup.restic_password}} -restic \ - -r {{secret.backup.restic_shortterm_repo}} backup \ - /opt/stacks/minecraft/ \ - --exclude '**orebfuscator_cache'\ - --exclude '**civmodcore_cache'\ - --exclude '**postgres-data'\ + +rsync -av --exclude postgres-data --exclude orebfuscator_cache /opt/stacks/minecraft/ /opt/backups/sync/ +rm /opt/backups/old/* +mv /opt/backups/compressed/* /opt/backups/old +backupname="/opt/backups/compressed/$(date +"%Y_%m_%d_%H")-backup.tar.zstd" echo "$(date) Starting services after backup..." docker service scale minecraft_paper=1 +docker service scale minecraft_gamma=1 docker service scale minecraft_pvp=1 -sleep 5m docker service scale minecraft_waterfall=1 +echo "$(date) Creating achive..." +tar -c --exclude orebfuscator_cache --exclude civmodcore_cache --exclude postgresdata /opt/backups/sync | zstd -T0 -8 -o $backupname echo "$(date) Copying backup to longterm..." -restic \ - -r {{secret.backup.restic_shortterm_repo}} copy \ - --repo2 {{secret.backup.restic_repo}} \ - latest - -echo "$(date) Pruning shortterm backups..." -restic \ - -r {{secret.backup.restic_shortterm_repo}} forget \ - --keep-last 1 \ - --prune +sshpass -p "{{secret.backup.archive_password}}" rsync -a $backupname {{secret.backup.archive_name}} echo "$(date) Backup finished!" diff --git a/ansible/templates/stacks/minecraft.yml.j2 b/ansible/templates/stacks/minecraft.yml.j2 index 7a44c2cb28..2d65b1eeb0 100644 --- a/ansible/templates/stacks/minecraft.yml.j2 +++ b/ansible/templates/stacks/minecraft.yml.j2 @@ -3,7 +3,7 @@ version: '3.8' services: waterfall: - image: itzg/mc-proxy@sha256:4e2e20b6ef7739429dff34b7f7a5c26816004be2d499ad7d7940de197aa7a130 + image: itzg/mc-proxy:java24 restart: unless-stopped tty: true stdin_open: true @@ -11,6 +11,10 @@ services: - minecraft_default ports: # Open port in host mode, to bypass swarm ingress + - target: 8192 + published: 8192 + protocol: tcp + mode: host - target: 25577 published: 25565 protocol: tcp @@ -20,21 +24,30 @@ services: protocol: udp mode: host environment: - VELOCITY_VERSION: '3.4.0-SNAPSHOT' - TYPE: 'VELOCITY' + TYPE: 'CUSTOM' + BUNGEE_JAR_FILE: 'velocity.jar' REPLACE_ENV_VARIABLES: 'true' REPLACE_ENV_VARIABLE_PREFIX: 'CIV' INIT_MEMORY: 2G - MAX_MEMORY: 2G + MAX_MEMORY: 3G CIV_POSTGRES_HOST: minecraft_postgres CIV_POSTGRES_USERNAME: '{{secret.minecraft.postgres.username}}' CIV_POSTGRES_PASSWORD: '{{secret.minecraft.postgres.password}}' CIV_FORWARDING_SECRET: '{{secret.minecraft.forwarding_secret}}' CIV_TIMEOUT_TIME: 30000 CIV_PVP_HOSTNAME: '{{setting.minecraft.pvp_hostname}}' + CIV_GAMMA_HOSTNAME: '{{setting.minecraft.gamma_hostname}}' CIV_HOSTNAME: '{{setting.minecraft.hostname}}' + + CIV_RABBITMQ_HOST: minecraft_rabbitmq + CIV_RABBITMQ_USERNAME: rabbitmq + CIV_RABBITMQ_PASSWORD: '{{secret.minecraft.rabbitmq.password}}' + + CIV_MYSQL_HOST: minecraft_mariadb + CIV_MYSQL_USERNAME: '{{secret.minecraft.mysql.username}}' + CIV_MYSQL_PASSWORD: '{{secret.minecraft.mysql.password}}' deploy: placement: constraints: [node.role == manager] @@ -42,9 +55,12 @@ services: # Config & Plugins - /opt/stacks/minecraft/proxy-config:/config - /opt/stacks/minecraft/proxy-plugins:/plugins + - /opt/PrivateConfig/proxy/config/nuvotifier/config.toml:/config/plugins/nuvotifier/config.toml + - /opt/PrivateConfig/proxy/config/nuvotifier/rsa/public.key:/config/plugins/nuvotifier/rsa/public.key + - /opt/PrivateConfig/proxy/config/nuvotifier/rsa/private.key:/config/plugins/nuvotifier/rsa/private.key paper: - image: itzg/minecraft-server@sha256:edd1968cb701da6a7a358170ad6503323e35fb6ec6984eb31ec1a8a4687b75ec + image: itzg/minecraft-server:stable-java24-graalvm restart: unless-stopped tty: true stdin_open: true @@ -52,18 +68,15 @@ services: networks: - minecraft_default ports: - - target: 8192 - published: 8192 - protocol: tcp - mode: host - target: 5004 published: 5004 protocol: tcp mode: host environment: TYPE: 'paper' - VERSION: '1.21.4' + VERSION: '1.21.8' EULA: 'TRUE' + PAPER_DOWNLOAD_URL: 'https://api.leafmc.one/v2/projects/leaf/versions/1.21.8/builds/113/downloads/leaf-1.21.8-113.jar' REMOVE_OLD_MODS: 'TRUE' COPY_CONFIG_DEST: '/data' @@ -73,7 +86,8 @@ services: INIT_MEMORY: '{{setting.minecraft.resources.memory}}' MAX_MEMORY: '{{setting.minecraft.resources.memory}}' - USE_AIKAR_FLAGS: 'TRUE' + USE_MEOWICE_FLAGS: 'TRUE' + USE_MEOWICE_GRAALVM_FLAGS: 'TRUE' STOP_DURATION: 3600 CIV_SERVER_NAME: prod-server @@ -106,7 +120,6 @@ services: CIV_FORWARDING_SECRET: '{{secret.minecraft.forwarding_secret}}' JVM_OPTS: -agentpath:./libti.so - volumes: # Persistence - /opt/stacks/minecraft/paper-data:/data @@ -115,23 +128,21 @@ services: - /opt/stacks/minecraft/paper-plugins:/plugins # Private Config & Plugins - /opt/PrivateConfig/paper/plugins/Vulcan.jar:/config/plugins/Vulcan.jar - - /opt/PrivateConfig/paper/plugins/packetevents-spigot-2.7.0.jar:/config/plugins/packetevents-spgiot-2.7.0.jar + - /opt/PrivateConfig/paper/plugins/packetevents-spigot-2.9.4.jar:/config/plugins/packetevents-spgiot-2.9.4.jar - /opt/PrivateConfig/paper/config/Vulcan/config.yml:/config/plugins/Vulcan/config.yml - /opt/PrivateConfig/paper/config/HiddenOre/config.yml:/config/plugins/HiddenOre/config.yml - - /opt/PrivateConfig/paper/config/KiraBukkitGateway/config.yml:/config/plugins/KirraBukkitGateway/config.yml + - /opt/PrivateConfig/paper/config/KiraBukkitGateway/config.yml:/config/plugins/KiraBukkitGateway/config.yml - /opt/PrivateConfig/paper/config/BreweryX/config.yml:/config/plugins/BreweryX/config.yml - /opt/PrivateConfig/paper/config/BreweryX/cauldron.yml:/config/plugins/BreweryX/cauldron.yml - /opt/PrivateConfig/paper/config/BreweryX/custom-items.yml:/config/plugins/BreweryX/custom-items.yml - /opt/PrivateConfig/paper/config/BreweryX/recipes.yml:/config/plugins/BreweryX/recipes.yml - /opt/PrivateConfig/paper/config/Terra/packs/CivMC:/config/plugins/Terra/packs/CivMC - - /opt/PrivateConfig/paper/config/Votifier/config.yml:/config/plugins/Votifier/config.yml - - /opt/PrivateConfig/paper/config/Votifier/rsa/public.key:/config/plugins/Votifier/rsa/public.key - - /opt/PrivateConfig/paper/config/Votifier/rsa/private.key:/config/plugins/Votifier/rsa/private.key - /opt/PrivateConfig/paper/config/DiscordSRV/config.yml:/config/plugins/DiscordSRV/config.yml - /opt/PrivateConfig/paper/config/DiscordSRV/alerts.yml:/config/plugins/DiscordSRV/alerts.yml - /opt/PrivateConfig/paper/config/MythicMobs:/config/plugins/MythicMobs - /opt/PrivateConfig/paper/config/RandomSpawn/worlds.yml:/config/plugins/RandomSpawn/worlds.yml - /opt/PrivateConfig/paper/config/Heliodor/config.yml:/config/plugins/Heliodor/config.yml + - /opt/PrivateConfig/paper/config/CivChat2/banned-words.txt:/config/plugins/CivChat2/banned-words.txt deploy: placement: @@ -140,8 +151,105 @@ services: limits: memory: '{{setting.minecraft.resources.memory_limit}}' + gamma: + image: itzg/minecraft-server:stable-java24-graalvm + restart: unless-stopped + tty: true + stdin_open: true + stop_grace_period: 60m + networks: + - minecraft_default + ports: + - target: 8194 + published: 8194 + protocol: tcp + mode: host + - target: 5003 + published: 5003 + protocol: tcp + mode: host + environment: + TYPE: 'paper' + VERSION: '1.21.8' + EULA: 'TRUE' + PAPER_DOWNLOAD_URL: 'https://api.leafmc.one/v2/projects/leaf/versions/1.21.8/builds/113/downloads/leaf-1.21.8-113.jar' + + REMOVE_OLD_MODS: 'TRUE' + COPY_CONFIG_DEST: '/data' + SYNC_SKIP_NEWER_IN_DESTINATION: 'false' + REPLACE_ENV_DURING_SYNC: 'TRUE' + REPLACE_ENV_VARIABLE_PREFIX: 'CIV' + + INIT_MEMORY: '{{setting.minecraft.resources.gamma_memory}}' + MAX_MEMORY: '{{setting.minecraft.resources.gamma_memory}}' + USE_MEOWICE_FLAGS: 'TRUE' + USE_MEOWICE_GRAALVM_FLAGS: 'TRUE' + STOP_DURATION: 3600 + + CIV_SERVER_NAME: prod-server +{% if setting.minecraft.gamma_whitelist %} + ENABLE_WHITELIST: 'true' + CIV_WHITELIST: 'true' +{% else %} + ENABLE_WHITELIST: 'false' + CIV_WHITELIST: 'false' +{% endif %} + + CIV_WORDBANK_SEED: '{{secret.minecraft.gamma_wordbank_seed}}' + CIV_HIDDENORE_DENSITY_SEED: '{{ secret.minecraft.gamma_hiddenore_density_seed }}' + CIV_HIDDENORE_HEIGHT_SEED: '{{ secret.minecraft.gamma_hiddenore_height_seed }}' + + CIV_MYSQL_HOST: minecraft_mariadb + CIV_MYSQL_USERNAME: '{{secret.minecraft.mysql.username}}' + CIV_MYSQL_PASSWORD: '{{secret.minecraft.mysql.password}}' + + CIV_POSTGRES_HOST: minecraft_postgres + CIV_POSTGRES_USERNAME: '{{secret.minecraft.postgres.username}}' + CIV_POSTGRES_PASSWORD: '{{secret.minecraft.postgres.password}}' + + CIV_DATABASE_PREFIX: 'gamma_' + + CIV_RABBITMQ_HOST: minecraft_rabbitmq + CIV_RABBITMQ_USERNAME: rabbitmq + CIV_RABBITMQ_PASSWORD: '{{secret.minecraft.rabbitmq.password}}' + + CIV_WATCHDOG_TIMEOUT_TIME: 60 + CIV_FORWARDING_SECRET: '{{secret.minecraft.forwarding_secret}}' + + JVM_OPTS: -agentpath:./libti.so + volumes: + # Persistence + - /opt/stacks/minecraft/gamma-data:/data + # Config & Plugins + - /opt/stacks/minecraft/gamma-config:/config + - /opt/stacks/minecraft/gamma-plugins:/plugins + # Private Config & Plugins + - /opt/PrivateConfig/gamma/config/RealisticBiomes/config.yml:/config/plugins/RealisticBiomes/config.yml + - /opt/PrivateConfig/gamma/config/RandomSpawn/worlds.yml:/config/plugins/RandomSpawn/worlds.yml + - /opt/PrivateConfig/gamma/config/Heliodor/config.yml:/config/plugins/Heliodor/config.yml + - /opt/PrivateConfig/gamma/config/HiddenOre/config.yml:/config/plugins/HiddenOre/config.yml + - /opt/PrivateConfig/paper/plugins/Vulcan.jar:/config/plugins/Vulcan.jar + - /opt/PrivateConfig/paper/plugins/packetevents-spigot-2.9.4.jar:/config/plugins/packetevents-spgiot-2.9.4.jar + - /opt/PrivateConfig/paper/config/Vulcan/config.yml:/config/plugins/Vulcan/config.yml + - /opt/PrivateConfig/gamma/config/BreweryX/config.yml:/config/plugins/BreweryX/config.yml + - /opt/PrivateConfig/gamma/config/BreweryX/cauldron.yml:/config/plugins/BreweryX/cauldron.yml + - /opt/PrivateConfig/gamma/config/BreweryX/custom-items.yml:/config/plugins/BreweryX/custom-items.yml + - /opt/PrivateConfig/gamma/config/BreweryX/recipes.yml:/config/plugins/BreweryX/recipes.yml + - /opt/PrivateConfig/paper/config/CivChat2/banned-words.txt:/config/plugins/CivChat2/banned-words.txt + - /opt/PrivateConfig/paper/config/EvenMoreFish/baits:/config/plugins/EvenMoreFish/baits + - /opt/PrivateConfig/paper/config/EvenMoreFish/rods:/config/plugins/EvenMoreFish/rods + - /opt/PrivateConfig/paper/config/EvenMoreFish/rarities:/config/plugins/EvenMoreFish/rarities + - /opt/PrivateConfig/paper/config/EvenMoreFish/competitions:/config/plugins/EvenMoreFish/competitions + - /opt/PrivateConfig/paper/config/EvenMoreFish/config.yml:/config/plugins/EvenMoreFish/config.yml + deploy: + placement: + constraints: [node.role == manager] + resources: + limits: + memory: '{{setting.minecraft.resources.gamma_memory_limit}}' + pvp: - image: itzg/minecraft-server@sha256:edd1968cb701da6a7a358170ad6503323e35fb6ec6984eb31ec1a8a4687b75ec + image: itzg/minecraft-server:stable-java24-graalvm restart: unless-stopped tty: true stdin_open: true @@ -155,7 +263,7 @@ services: mode: host environment: TYPE: 'paper' - VERSION: '1.21.4' + VERSION: '1.21.8' EULA: 'TRUE' PAPER_CUSTOM_JAR: 'asp-server.jar' @@ -167,7 +275,8 @@ services: INIT_MEMORY: '{{setting.minecraft.resources.pvp_memory}}' MAX_MEMORY: '{{setting.minecraft.resources.pvp_memory}}' - USE_AIKAR_FLAGS: 'TRUE' + USE_MEOWICE_FLAGS: 'TRUE' + USE_MEOWICE_GRAALVM_FLAGS: 'TRUE' STOP_DURATION: 3600 CIV_SERVER_NAME: prod-server @@ -206,12 +315,13 @@ services: - /opt/stacks/minecraft/pvp-plugins:/plugins # Private Config & Plugins - /opt/PrivateConfig/paper/plugins/Vulcan.jar:/config/plugins/Vulcan.jar - - /opt/PrivateConfig/paper/plugins/packetevents-spigot-2.7.0.jar:/config/plugins/packetevents-spgiot-2.7.0.jar + - /opt/PrivateConfig/paper/plugins/packetevents-spigot-2.9.4.jar:/config/plugins/packetevents-spgiot-2.9.4.jar - /opt/PrivateConfig/paper/config/Vulcan/config.yml:/config/plugins/Vulcan/config.yml - /opt/PrivateConfig/paper/config/BreweryX/config.yml:/config/plugins/BreweryX/config.yml - /opt/PrivateConfig/paper/config/BreweryX/cauldron.yml:/config/plugins/BreweryX/cauldron.yml - /opt/PrivateConfig/paper/config/BreweryX/custom-items.yml:/config/plugins/BreweryX/custom-items.yml - /opt/PrivateConfig/paper/config/BreweryX/recipes.yml:/config/plugins/BreweryX/recipes.yml + - /opt/PrivateConfig/paper/config/CivChat2/banned-words.txt:/config/plugins/CivChat2/banned-words.txt deploy: placement: @@ -221,7 +331,7 @@ services: memory: '{{setting.minecraft.resources.pvp_memory_limit}}' kira: - image: ghcr.io/civmc/kira:2.1.1 + image: ghcr.io/civmc/kira:latest restart: unless-stopped tty: true stdin_open: true diff --git a/ansible/variables/settings.yml b/ansible/variables/settings.yml index b72d2a8544..0cd0042d1c 100644 --- a/ansible/variables/settings.yml +++ b/ansible/variables/settings.yml @@ -9,13 +9,17 @@ settings: enabled: true whitelist: false pvp_whitelist: false + gamma_whitelist: false pvp_hostname: 'pvp.civmc.net' hostname: 'play.civmc.net' + gamma_hostname: 'mini.civmc.net' resources: &default_minecraft_resources - memory: 40G - memory_limit: 50G - pvp_memory: 10G - pvp_memory_limit: 13G + memory: 44G + memory_limit: 48G + pvp_memory: 11G + pvp_memory_limit: 14G + gamma_memory: 21G + gamma_memory_limit: 24G # Optional Stacks auth: enabled: false @@ -37,14 +41,18 @@ settings: <<: *default_minecraft whitelist: true pvp_whitelist: true + gamma_whitelist: true + gamma_hostname: 'mini-test.civmc.net' pvp_hostname: 'pvp-test.civmc.net' hostname: 'test.civmc.net' resources: <<: *default_minecraft_resources - memory: 6G - memory_limit: 8G - pvp_memory: 7G - pvp_memory_limit: 9G + memory: 4G + memory_limit: 6G + pvp_memory: 3G + pvp_memory_limit: 5G + gamma_memory: 4G + gamma_memory_limit: 6G auth: enabled: true # TODO: MOVE maven: diff --git a/build.gradle.kts b/build.gradle.kts index 07cda29739..e385c3b9d2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,8 +21,9 @@ allprojects { repositories { mavenCentral() - maven("https://oss.sonatype.org/content/repositories/snapshots") + maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") maven("https://repo.papermc.io/repository/maven-public/") + maven("https://oss.sonatype.org/content/repositories/snapshots") maven("https://repo.aikar.co/content/groups/aikar/") maven("https://libraries.minecraft.net") maven("https://repo.codemc.io/repository/maven-public/") @@ -30,5 +31,6 @@ allprojects { maven("https://repo.infernalsuite.com/repository/maven-snapshots/") maven("https://repo.rapture.pw/repository/maven-releases/") maven("https://jitpack.io") + maven("https://repo.ajg0702.us/releases") } } diff --git a/containers/provisioning/grafana/dashboards/default.yml b/containers/provisioning/grafana/dashboards/default.yml old mode 100644 new mode 100755 diff --git a/containers/provisioning/grafana/dashboards/files/unified-metrics.json b/containers/provisioning/grafana/dashboards/files/unified-metrics.json old mode 100644 new mode 100755 diff --git a/containers/provisioning/grafana/datasources/influx.yml b/containers/provisioning/grafana/datasources/influx.yml old mode 100644 new mode 100755 diff --git a/containers/provisioning/mariadb/docker-entrypoint-initdb.d/0100_create_databases.sql b/containers/provisioning/mariadb/docker-entrypoint-initdb.d/0100_create_databases.sql old mode 100644 new mode 100755 index eb0852701a..9278cf7ae6 --- a/containers/provisioning/mariadb/docker-entrypoint-initdb.d/0100_create_databases.sql +++ b/containers/provisioning/mariadb/docker-entrypoint-initdb.d/0100_create_databases.sql @@ -35,3 +35,31 @@ CREATE DATABASE pvp_kitpvp; GRANT ALL PRIVILEGES ON `pvp_civduties`.* TO 'mariadb'@'%'; GRANT ALL PRIVILEGES ON `pvp_civmodcore`.* TO 'mariadb'@'%'; GRANT ALL PRIVILEGES ON `pvp_kitpvp`.* TO 'mariadb'@'%'; + +CREATE DATABASE gamma_civduties; +CREATE DATABASE gamma_civmodcore; +CREATE DATABASE gamma_bastion; +CREATE DATABASE gamma_castlegates; +CREATE DATABASE gamma_citadel; +CREATE DATABASE gamma_civchat2; +CREATE DATABASE gamma_donum; +CREATE DATABASE gamma_essenceglue; +CREATE DATABASE gamma_jukealert; +CREATE DATABASE gamma_namelayer; +CREATE DATABASE gamma_exilepearl; +CREATE DATABASE gamma_heliodor; +CREATE DATABASE gamma_realisticbiomes; + +GRANT ALL PRIVILEGES ON `gamma_civduties`.* TO 'mariadb'@'%'; +GRANT ALL PRIVILEGES ON `gamma_civmodcore`.* TO 'mariadb'@'%'; +GRANT ALL PRIVILEGES ON `gamma_bastion`.* TO 'mariadb'@'%'; +GRANT ALL PRIVILEGES ON `gamma_castlegates`.* TO 'mariadb'@'%'; +GRANT ALL PRIVILEGES ON `gamma_citadel`.* TO 'mariadb'@'%'; +GRANT ALL PRIVILEGES ON `gamma_civchat2`.* TO 'mariadb'@'%'; +GRANT ALL PRIVILEGES ON `gamma_donum`.* TO 'mariadb'@'%'; +GRANT ALL PRIVILEGES ON `gamma_essenceglue`.* TO 'mariadb'@'%'; +GRANT ALL PRIVILEGES ON `gamma_jukealert`.* TO 'mariadb'@'%'; +GRANT ALL PRIVILEGES ON `gamma_heliodor`.* TO 'mariadb'@'%'; +GRANT ALL PRIVILEGES ON `gamma_realisticbiomes`.* TO 'mariadb'@'%'; +GRANT ALL PRIVILEGES ON `gamma_namelayer`.* TO 'mariadb'@'%'; +GRANT ALL PRIVILEGES ON `gamma_exilepearl`.* TO 'mariadb'@'%'; diff --git a/containers/provisioning/postgres/docker-entrypoint-initdb.d/0100_create_databases.sql b/containers/provisioning/postgres/docker-entrypoint-initdb.d/0100_create_databases.sql old mode 100644 new mode 100755 diff --git a/docker-compose.yml b/docker-compose.yml index 48e4324330..e0bda11cac 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,7 @@ services: ################# proxy: - image: itzg/mc-proxy@sha256:4e2e20b6ef7739429dff34b7f7a5c26816004be2d499ad7d7940de197aa7a130 + image: itzg/mc-proxy:java24 restart: unless-stopped tty: true stdin_open: true @@ -20,9 +20,11 @@ services: rabbitmq: condition: service_healthy environment: - VELOCITY_VERSION: '3.4.0-SNAPSHOT' - TYPE: 'VELOCITY' + TYPE: 'CUSTOM' + BUNGEE_JAR_FILE: 'velocity.jar' + REMOVE_OLD_MODS: 'TRUE' + SYNC_SKIP_NEWER_IN_DESTINATION: 'false' REPLACE_ENV_VARIABLES: 'true' REPLACE_ENV_VARIABLE_PREFIX: 'CIV' @@ -30,57 +32,28 @@ services: CIV_POSTGRES_USERNAME: postgres CIV_POSTGRES_PASSWORD: postgres + CIV_RABBITMQ_HOST: rabbitmq + CIV_RABBITMQ_USERNAME: rabbitmq + CIV_RABBITMQ_PASSWORD: rabbitmq + CIV_TIMEOUT_TIME: 3000000 - CIV_PVP_HOSTNAME: 'pvp.localhost' CIV_HOSTNAME: 'main.localhost' CIV_FORWARDING_SECRET: '1234' + + CIV_MYSQL_HOST: mariadb + CIV_MYSQL_USERNAME: mariadb + CIV_MYSQL_PASSWORD: mariadb ports: - "25565:25577" volumes: - - ./containers/data/proxy:/data + - ./containers/data/proxy:/server - ./ansible/files/proxy-config:/config - ./ansible/build/proxy-plugins:/plugins - proxy_offline_mode: - image: itzg/mc-proxy@sha256:4e2e20b6ef7739429dff34b7f7a5c26816004be2d499ad7d7940de197aa7a130 - restart: unless-stopped - tty: true - stdin_open: true - profiles: - - offline - depends_on: - mariadb: - condition: service_healthy - postgres: - condition: service_healthy - rabbitmq: - condition: service_healthy - environment: - VELOCITY_VERSION: '3.4.0-SNAPSHOT' - TYPE: 'VELOCITY' - - REPLACE_ENV_VARIABLES: 'true' - REPLACE_ENV_VARIABLE_PREFIX: 'CIV' - - CIV_POSTGRES_HOST: postgres - CIV_POSTGRES_USERNAME: postgres - CIV_POSTGRES_PASSWORD: postgres - - CIV_TIMEOUT_TIME: 3000000 - CIV_PVP_HOSTNAME: 'pvp.localhost' - CIV_HOSTNAME: 'main.localhost' - - CIV_FORWARDING_SECRET: '1234' - ports: - - "25566:25577" - volumes: - - ./containers/data/proxy:/data - - ./ansible/files/proxy-config:/config - - ./ansible/build/proxy-plugins:/plugins paper: - image: itzg/minecraft-server@sha256:edd1968cb701da6a7a358170ad6503323e35fb6ec6984eb31ec1a8a4687b75ec + image: itzg/minecraft-server:stable-java24-graalvm restart: unless-stopped tty: true stdin_open: true @@ -93,8 +66,9 @@ services: condition: service_healthy environment: TYPE: 'paper' - VERSION: '1.21.4' + VERSION: '1.21.8' EULA: 'TRUE' + PAPER_DOWNLOAD_URL: 'https://api.leafmc.one/v2/projects/leaf/versions/1.21.8/builds/113/downloads/leaf-1.21.8-113.jar' REMOVE_OLD_MODS: 'TRUE' COPY_CONFIG_DEST: '/data' @@ -133,6 +107,7 @@ services: - ./containers/data/paper:/data - ./ansible/files/paper-config:/config - ./ansible/build/paper-plugins:/plugins + kira: image: ghcr.io/civmc/kira:2.1.1 @@ -192,6 +167,8 @@ services: test: [ "CMD-SHELL", 'mysqladmin ping --user "$$MYSQL_USER" --password="$$MYSQL_PASSWORD"' ] timeout: 20s retries: 10 + start_period: 40s + start_interval: 5s ports: - "3306:3306" environment: @@ -211,6 +188,8 @@ services: interval: 10s timeout: 5s retries: 5 + start_period: 40s + start_interval: 5s ports: - "5432:5432" environment: @@ -229,6 +208,8 @@ services: interval: 60s timeout: 10s retries: 5 + start_period: 40s + start_interval: 5s ports: - "5672:5672" - "15672:15672" diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index 78bc83a31a..d9597f3238 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -17,6 +17,9 @@ export default defineConfig({ title: "CivMC", description: DESCRIPTION, + // don't force ".html" in URLs, works with github pages + cleanUrls: true, + sitemap: { hostname: SITE_URL, lastmodDateOnly: false, @@ -107,6 +110,7 @@ export default defineConfig({ provider: "local", }, + externalLinkIcon: true, // enables showing the last time the page was updated lastUpdated: {}, @@ -116,8 +120,8 @@ export default defineConfig({ nav: [ { text: "Home", link: "/" }, - { text: "Wiki", link: "/wiki/" }, - { text: "Developer Docs", link: "/dev/" }, + { text: "Wiki", link: "/wiki/", activeMatch: "/wiki/" }, + { text: "Developer Docs", link: "/dev/", activeMatch: "/dev/" }, ], sidebar: { @@ -125,11 +129,15 @@ export default defineConfig({ { text: "Wiki", items: [ + { text: "Server Overview", link: "/wiki/index" }, { text: "New Player Guide", link: "/wiki/new-player-guide", }, - { text: "Server Overview", link: "/wiki/index" }, + { + text: "CivMini", + link: "/wiki/CivMini", + }, { text: "Change Log", link: "/wiki/changelog" }, { text: "Plugins", @@ -203,7 +211,7 @@ export default defineConfig({ { text: "Fun", link: "wiki/plugins/fun/index", - collapsed: true, + collapsed: false, items: [ { text: "Item Exchange", @@ -233,6 +241,10 @@ export default defineConfig({ text: "Arthropod Egg", link: "wiki/plugins/fun/arthropodegg", }, + { + text: "Simple Voice Chat", + link: "wiki/plugins/fun/simplevoicechat", + }, ], }, ], diff --git a/docs/index.md b/docs/index.md index dc7bf3bdd6..eb573962b9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,7 +4,7 @@ layout: home hero: name: "CivMC" - tagline: "A Minecraft server focused on civilization building \n1.21.3 Play.CivMC.net" + tagline: "A Minecraft server focused on civilization building \n1.21.8 Play.CivMC.net" image: src: /logo.png alt: CivMC Logo diff --git a/docs/wiki/CivMini.md b/docs/wiki/CivMini.md new file mode 100644 index 0000000000..54a662d4cf --- /dev/null +++ b/docs/wiki/CivMini.md @@ -0,0 +1,56 @@ +# CivMini +Civmini is the experimental branch server of CivMC. Here new features and mechanics are first trialed, tested and balanced to eventually bring them to the main server. +Because of this, **help from admins for loss of items due to bugs is not given**. And all players are encouraged to switch over to the main server. +CivMini is also used as a queue for players wishing to join the main server when it is full. Either joining the main server (Play.CivMC.Net) while it is full or using the **/queue main** command while on the server. + +There are many things changed or different on CivMini Below is a detailed list of these changes. + +## Minor changes +- **The use of bots is not allowed on CivMini**, all actions have to be done by a real player. Automated farming, mining or other automation of gameplay elements that gives an advantage through use of macros or other non human input can result in a ban. Small exceptions exist such as holding down a button or sprint jumping. +- **Reinforcement Decay doubles every 6 weeks** instead of every 2 months and **localised decay is changed to a 384 blocks** range instead of 512 blocks. +- **Reinforcement strength is lowered by 10-20%** on CivMini. + +| Material | CivMC | CivMini | +| ----------------- | ----- |:-------:| +| Stone | 50 | 45 | +| Iron | 300 | 250 | +| Diamond | 2000 | 1700 | +| Netherbrick | 50 | 45 | +| Gold | 300 | 250 | +| Gilded Blackstone | 2000 | 1700 | + +- On CivMC a 100 block exclusion zone near the world border cannot be reinforced. On Civmini blocks can be reinforced near the world border, but in a **60 block range from the world border reinforcements do not refresh**. This means that any reinforcements placed will decay no matter what. +- Bastions are 15% weaker in CivMini. +- CivMini does not have a kira relay. Not for global chat or personal groups. Creating one using bots is also not allowed. Commands can thus not be done through the kira bot via discord like in CivMC. +- Localchat range has been reduced to 600 blocks on Civmini due to the map being 20% the size of CivMC with a 4500 block radius. +- Iron ore veins have had their yield doubled compared to CivMC, making them more valuable/useful. +- Brewery is disabled. +- Evenmorefish plugin is disabled. + + +## Farming on CivMini +Farming on CivMC is completely overhauled, crops are no longer tied to specific biomes. Instead each crops has been assigned an ideal set of 2 parameters, humidity and temperature. And each biome has been assigned a base of these 2 parameters. +3 noise maps are overlaid on the map that add variety from one block to another. one for each 2 parameters and a 3rd called fertility which determines yield of the crop. +All can be measured with shift right clicking with a set of tools. A lightning rod for humidity, a clock for temperature and a brush for fertility. This last one is most important to finding a good farm spot. + +A more indepth tutorial of finding a good farm spot is given below. +### Small changes + +- All crops and saplings with the exception of mushrooms and nether growables will require direct sunlight to grow. The greenhouse feature of glowstone is disabled. stacked farms are thus not possible. +- Wheat grows everywhere, this makes it possible for every place to obtain food and contribute a small amount to xp production. Wheat is used in every xp recipe. +- All saplings grow in all biomes but only drop saplings when in a growth zone like other crops. + +### Finding a good farm spot +To find a good farm spot start by crafting a brush. Then use that brush to shift right click on the ground. This will display a chat message with that spot's fertility. The fertility noise map is gradual and "blobby" This means that to find a good growth zone all one has to do is follow the curve. Click around and find out which direction has a higher fertility, then continue on in that direction. This would looks something like this in your chat: ![Fertility](media/Fertility.png) +Once you find a high fertility zone use the /rb command to see which crops match the local humidity and temperature values enough to be growable there in high yields. Then follow the outline of the high fertility zone with the brush and mark it. The inside of this marking is your farm plot ready for plowing!. +Once done correctly you'll have a farm looking a little like this: +![Farm](media/Farm.png) +This potato farm for example outputs about a double chest of potatos every harvest. So you don't need a big plot for big gains! + +### Harvesting +All Crops have to be harvested manually to get the increased yield. Automated farms such as by redstone machines will not have the increased yield. Indeed even crops such as cactus, bamboo or kelp have to be harvested by hand. +Saplings function a little different, leaves will decay into high amounts of saplings if in the correct growth zone for that tree. but the tree will still have to be cut down by hand. + +### Farm beacon +A farm beacon is a late game boost to your farms, one farm beacon (unstackable effect) gives a 5% fertility bonus in a 20 block radius around it. This could mean up to 3x the orginial yields. Meaning you have to harvest less often for more yield. +A farm beacon can be crafted with 3 meteoric iron ingots like so: ![Farmbeacon](media/Farmbeacon.png) diff --git a/docs/wiki/index.md b/docs/wiki/index.md index dff97f8324..94052bcfb9 100644 --- a/docs/wiki/index.md +++ b/docs/wiki/index.md @@ -28,7 +28,7 @@ While CivMC can be quite the grind sometimes, there are also many things to do t ## Helpful Links [New Player Guide](./new-player-guide) - A handy guide for those starting out on their journey through the world of CivMC. -[Word Map](https://civmc-map.github.io/) - A player made and maintained map of the world, featuring a map of nations. +[Word Map](https://map.civinfo.net/) - A player made and maintained map of the world, featuring a map of nations. ## Server Plugins Below are explanations on the plugins CivMC uses, and detailed information about them. @@ -63,4 +63,6 @@ Below are explanations on the plugins CivMC uses, and detailed information about * [Arthropod Egg](./plugins/fun/arthropodegg) - Killing animals to put them in eggs. #### Other pages -[Change log](./changelog) - A list of all server updates. +[Change log](./changelog) - A list of all server updates. +[CivMini](./CivMini) - All things different or new on CivMini. + diff --git a/docs/wiki/media/Farm.png b/docs/wiki/media/Farm.png new file mode 100644 index 0000000000..991b38e07a Binary files /dev/null and b/docs/wiki/media/Farm.png differ diff --git a/docs/wiki/media/Farmbeacon.png b/docs/wiki/media/Farmbeacon.png new file mode 100644 index 0000000000..2af81ccae5 Binary files /dev/null and b/docs/wiki/media/Farmbeacon.png differ diff --git a/docs/wiki/media/Fertility.png b/docs/wiki/media/Fertility.png new file mode 100644 index 0000000000..079f3a5e8d Binary files /dev/null and b/docs/wiki/media/Fertility.png differ diff --git a/docs/wiki/new-player-guide.md b/docs/wiki/new-player-guide.md index ab2e67a2bf..0de5511c9f 100644 --- a/docs/wiki/new-player-guide.md +++ b/docs/wiki/new-player-guide.md @@ -25,7 +25,7 @@ The subreddit is the other place where most of the discussion about the server h https://civwiki.org/wiki/Main_Page Here you can read up on CivMC’s 2 year history as well as the rest of the Civ genre! Reading about past servers, especially CivClassic, can help you understand the community better. You can read up on the histories of past wars, nations, and players, which have all shaped what CivMC is today. ### World Map -https://civmc-map.github.io/ +https://map.civinfo.net/ This is a player created and maintained map of the world. It includes many layers of information, but is mainly used by players for the political map. This map shows you what land is currently "Claimed" by nations on the server. This layer is updated about every month and may not be 100% accurate. ## Basic Mechanics diff --git a/docs/wiki/plugins/fun/brewery.md b/docs/wiki/plugins/fun/brewery.md index 903c1d8ccf..fd4892ed87 100644 --- a/docs/wiki/plugins/fun/brewery.md +++ b/docs/wiki/plugins/fun/brewery.md @@ -87,416 +87,419 @@ When trying to find the correct cooking time for a brew, the best way is to look ### Trial and Error Ultimately though, discovering new brewing recipes is a whole bunch of trial and error, trying out different combinations of ingredients, distilling, aging, wood types, and timings. A master brewer is a patient one. -To help you start out however, we have provided some information below of all brews in the game. +To help you start out however, we have provided some information below of all brews in the game. Note that "Difficulty" below is a reflection of a config value not an estimation of how hard it might be to make. What it does determine is how close one has to be to the perfect recipe to get a result or higher quality of that brew. ## List of Discoverable Brews ### Basic Brews The most simple form of brews, made with either base and/or custom ingredients. -| Name | Ingredient Amount | Texture ID | -|:-----------------------:|:-----------------:|:----------:| -| Wheatbeer | 2 | 300001 | -| The Simple Life | 2 | 300002 | -| Pale Ale | 2 | 300003 | -| Pale Lager | 2 | 300004 | -| Pilsner | 2 | 300005 | -| Cider | 1 | 300006 | -| Amber Ale | 3 | 300007 | -| White Ale | 3 | 300008 | -| Super Bock | 2 | 300009 | -| White Wine | 1 | 300010 | -| Red Wine | 1 | 300011 | -| Albarino | 2 | 300012 | -| Alvarinho Portugues | 2 | 300013 | -| Pinot | 1 | 300014 | -| Sauvignnon Blanc | 1 | 300015 | -| Chianti | 2 | 300016 | -| Cabernet | 1 | 300017 | -| Merlot | 2 | 300018 | -| Grenache | 3 | 300019 | -| Champagne | 2 | 300020 | -| Riesling | 2 | 300021 | -| Rose | 2 | 300022 | -| Whiskey | 2 | 300023 | -| Scotch | 2 | 300024 | -| White Whiskey | 3 | 300025 | -| Corn Whiskey | 2 | 300026 | -| Japanese Whiskey | 3 | 300027 | -| Sake | 3 | 300028 | -| Soju | 3 | 300029 | -| Vodka | 2 | 300030 | -| Wheat Vodka | 3 | 300031 | -| Melon Schnapps | 3 | 300032 | -| Irish Cream | 3 | 300033 | -| Kefir | 2 | 300034 | -| Kombucha | 4 | 300035 | -| Absinthe | 3 | 300036 | -| Applejack | 2 | 300037 | -| Basi | 3 | 300038 | -| Borovicka | 1 | 300039 | -| Brandy | 1 | 300040 | -| Cachaca | 2 | 300041 | -| Chicha | 5 | 300042 | -| Tea | 1 | 300043 | -| Black Tea | 1 | 300044 | -| Chamomile Tea | 1 | 300045 | -| Earl Grey | 2 | 300046 | -| Green Tea | 1 | 300047 | -| A Matcha Made in Heaven | 2 | 300048 | -| Mate Tea | 1 | 300049 | -| Redbush Tea | 1 | 300050 | -| English Breakfast Tea | 4 | 300051 | -| Milo | 3 | 300052 | -| Gin | 3 | 300053 | -| Pigeon Gin | 4 | 300054 | -| Mead | 2 | 300055 | -| Metaxa | 1 | 300056 | -| Rum | 1 | 300057 | -| Tequila | 1 | 300058 | -| Tomboy Tequila | 2 | 300059 | -| Amaretto | 1 | 300060 | -| Vermouth | 5 | 300061 | -| Dandelion Liquor | 4 | 300062 | -| Sam Amogus Pale Pilsen | 3 | 300063 | -| Marzen | 3 | 300064 | -| Brown Ale | 4 | 300065 | -| Stout | 5 | 300066 | -| Dark Lager | 4 | 300067 | -| Chardonnay | 3 | 300068 | -| Celebration | 2 | 300069 | -| Bourbon Whiskey | 3 | 300070 | -| Bifrost Vodka | 5 | 300071 | -| Cherry Liqueur | 1 | 300072 | -| Ginjinha | 3 | 300073 | -| Orange Liqueur | 1 | 300074 | -| Sarsaparilla | 3 | 300075 | -| Ginger Beer | 5 | 300076 | -| Lemonade | 2 | 300077 | -| Coca Soda | 4 | 300078 | -| Leninade | 3 | 300079 | -| Limoncello | 3 | 300080 | -| Creme de menthe | 3 | 300081 | -| Chocolate Milk | 3 | 300082 | -| Energy Drink | 2 | 300083 | -| Espresso | 1 | 300084 | -| Americano | 1 | 300085 | -| Latte | 2 | 300086 | -| Mocha | 4 | 300087 | -| Iced Tea | 2 | 300088 | -| Iced Coffee | 4 | 300089 | -| Hot Coco | 5 | 300090 | -| Olive Oil | 1 | 300091 | -| Beetroot Vodka | 2 | 300092 | -| Melon Iced Tea | 3 | 300093 | -| Mango Schnapps | 4 | 300094 | -| Papaya Schnapps | 4 | 300095 | -| Vegetable Smoothie | 3 | 300096 | -| Pumpkin Spice Latte | 8 | 300097 | -| Chocolate Apple Cider | 3 | 300098 | -| Cannonball Rum | 3 | 300099 | -| Frog Grog | 4 | 300100 | -| Dr PVPer | 4 | 300101 | -| Cough Syrup | 2 | 300102 | -| The Gay Agenda | 6 | 300103 | -| Tonic | 2 | 300104 | -| Witch's Tongue | 4 | 300105 | -| Tombstone | 4 | 300106 | -| BloodWeiser | 4 | 300107 | -| De-Coffin-Ated Curse | 3 | 300108 | -| Phantom | 4 | 300109 | -| Eggnog | 8 | 300110 | -| Advocaat | 5 | 300111 | -| Love Potion | 3 | 300112 | +| Name | Ingredient Amount | Difficulty 1-9 | Texture ID | +|:-----------------------:|:-----------------:|:--------------:|:----------:| +| Wheatbeer | 2 | 1 | 300001 | +| The Simple Life | 2 | 2 | 300002 | +| Pale Ale | 2 | 2 | 300003 | +| Pale Lager | 2 | 2 | 300004 | +| Pilsner | 2 | 3 | 300005 | +| Cider | 1 | 5 | 300006 | +| Amber Ale | 3 | 3 | 300007 | +| White Ale | 3 | 4 | 300008 | +| Super Bock | 2 | 2 | 300009 | +| White Wine | 1 | 1 | 300010 | +| Red Wine | 1 | 2 | 300011 | +| Albarino | 2 | 6 | 300012 | +| Alvarinho Portugues | 2 | 3 | 300013 | +| Pinot | 1 | 6 | 300014 | +| Sauvignnon Blanc | 1 | 6 | 300015 | +| Chianti | 2 | 4 | 300016 | +| Cabernet | 1 | 6 | 300017 | +| Merlot | 2 | 6 | 300018 | +| Grenache | 3 | 6 | 300019 | +| Champagne | 2 | 5 | 300020 | +| Riesling | 2 | 6 | 300021 | +| Rose | 2 | 4 | 300022 | +| Whiskey | 2 | 3 | 300023 | +| Scotch | 2 | 4 | 300024 | +| White Whiskey | 3 | 6 | 300025 | +| Corn Whiskey | 2 | 6 | 300026 | +| Japanese Whiskey | 3 | 6 | 300027 | +| Sake | 3 | 5 | 300028 | +| Soju | 3 | 6 | 300029 | +| Vodka | 2 | 4 | 300030 | +| Wheat Vodka | 3 | 4 | 300031 | +| Melon Schnapps | 3 | 5 | 300032 | +| Irish Cream | 3 | 6 | 300033 | +| Kefir | 2 | 3 | 300034 | +| Kombucha | 4 | 4 | 300035 | +| Absinthe | 3 | 5 | 300036 | +| Applejack | 2 | 8 | 300037 | +| Basi | 3 | 6 | 300038 | +| Borovicka | 1 | 6 | 300039 | +| Brandy | 1 | 6 | 300040 | +| Cachaca | 2 | 5 | 300041 | +| Chicha | 5 | 3 | 300042 | +| Tea | 1 | 6 | 300043 | +| Black Tea | 1 | 8 | 300044 | +| Chamomile Tea | 1 | 6 | 300045 | +| Earl Grey | 2 | 7 | 300046 | +| Green Tea | 1 | 6 | 300047 | +| A Matcha Made in Heaven | 2 | 7 | 300048 | +| Mate Tea | 1 | 8 | 300049 | +| Redbush Tea | 1 | 8 | 300050 | +| English Breakfast Tea | 4 | 4 | 300051 | +| Milo | 3 | 2 | 300052 | +| Gin | 3 | 4 | 300053 | +| Pigeon Gin | 4 | 6 | 300054 | +| Mead | 2 | 3 | 300055 | +| Metaxa | 1 | 6 | 300056 | +| Rum | 1 | 6 | 300057 | +| Tequila | 1 | 5 | 300058 | +| Tomboy Tequila | 2 | 7 | 300059 | +| Amaretto | 1 | 6 | 300060 | +| Vermouth | 5 | 3 | 300061 | +| Dandelion Liquor | 4 | 4 | 300062 | +| Sam Amogus Pale Pilsen | 3 | 5 | 300063 | +| Marzen | 3 | 5 | 300064 | +| Brown Ale | 4 | 6 | 300065 | +| Stout | 5 | 5 | 300066 | +| Dark Lager | 4 | 5 | 300067 | +| Chardonnay | 3 | 7 | 300068 | +| Celebration | 2 | 7 | 300069 | +| Bourbon Whiskey | 3 | 7 | 300070 | +| Bifrost Vodka | 5 | 7 | 300071 | +| Cherry Liqueur | 1 | 3 | 300072 | +| Ginjinha | 3 | 4 | 300073 | +| Orange Liqueur | 1 | 3 | 300074 | +| Sarsaparilla | 3 | 5 | 300075 | +| Ginger Beer | 5 | 4 | 300076 | +| Lemonade | 2 | 5 | 300077 | +| Coca Soda | 4 | 4 | 300078 | +| Leninade | 3 | 4 | 300079 | +| Limoncello | 3 | 5 | 300080 | +| Creme de menthe | 3 | 3 | 300081 | +| Chocolate Milk | 3 | 3 | 300082 | +| Energy Drink | 2 | 4 | 300083 | +| Espresso | 1 | 3 | 300084 | +| Americano | 1 | 3 | 300085 | +| Latte | 2 | 4 | 300086 | +| Mocha | 4 | 5 | 300087 | +| Iced Tea | 2 | 2 | 300088 | +| Iced Coffee | 4 | 4 | 300089 | +| Hot Coco | 5 | 4 | 300090 | +| Olive Oil | 1 | 3 | 300091 | +| Beetroot Vodka | 2 | 4 | 300092 | +| Melon Iced Tea | 3 | 3 | 300093 | +| Mango Schnapps | 4 | 6 | 300094 | +| Papaya Schnapps | 4 | 7 | 300095 | +| Vegetable Smoothie | 3 | 3 | 300096 | +| Pumpkin Spice Latte | 8 | 5 | 300097 | +| Chocolate Apple Cider | 3 | 5 | 300098 | +| Cannonball Rum | 3 | 6 | 300099 | +| Frog Grog | 4 | 4 | 300100 | +| Dr PVPer | 4 | 7 | 300101 | +| Cough Syrup | 2 | 8 | 300102 | +| The Gay Agenda | 6 | 8 | 300103 | +| Tonic | 2 | 6 | 300104 | +| Witch's Tongue | 4 | 6 | 300105 | +| Tombstone | 4 | 6 | 300106 | +| BloodWeiser | 4 | 6 | 300107 | +| De-Coffin-Ated Curse | 3 | 6 | 300108 | +| Phantom | 4 | 6 | 300109 | +| Eggnog | 8 | 6 | 300110 | +| Advocaat | 5 | 6 | 300111 | +| Love Potion | 3 | 6 | 300112 | ### Food Brews Brews that restore saturation -| Name | Ingredient Amount | Texture ID | -|:--------------------:|:-----------------:| ----------:| -| Vegetable Soup | 5 | 400001 | -| Ratatouille | 4 | 400002 | -| Laksa | 7 | 400003 | -| Minestrone | 7 | 400004 | -| Pho | 12 | 400005 | -| Chicken Noodle Soup | 10 | 400006 | -| Dumpling Soup | 9 | 400007 | -| Corn Chowder | 11 | 400008 | -| Goulash | 7 | 400009 | -| Tonkatsu Ramen | 5 | 400010 | -| Borscht | 8 | 400011 | -| Balti Gosht | 12 | 400012 | -| Rice Porridge | 2 | 400013 | -| Cranberry Pie | 8 | 400014 | -| Fish Soup | 9 | 400015 | -| Poached Salmon | 6 | 400016 | -| Marmite | 3 | 400017 | -| Mais con Hielo | 4 | 400018 | -| Mediterranean Medly | 11 | 400019 | -| Halloween Candy | 3 | 400020 | -| Cheese | 3 | 400021 | -| Easter Bread | 6 | 400022 | -| Candied Carrots | 5 | 400023 | -| Dyed Eggs | 2 | 400024 | -| Deviled Eggs | 6 | 400025 | -| Carrot Cake | 6 | 400026 | -| Valentines Chocolate | 4 | 400026 | +| Name | Ingredient Amount | Difficulty 1-9 | Texture ID | +|:--------------------:|:-----------------:|:--------------:| ----------:| +| Vegetable Soup | 5 | 6 | 400001 | +| Ratatouille | 4 | 6 | 400002 | +| Laksa | 7 | 6 | 400003 | +| Minestrone | 7 | 6 | 400004 | +| Pho | 12 | 6 | 400005 | +| Chicken Noodle Soup | 10 | 6 | 400006 | +| Dumpling Soup | 9 | 6 | 400007 | +| Corn Chowder | 11 | 6 | 400008 | +| Goulash | 7 | 6 | 400009 | +| Tonkatsu Ramen | 5 | 6 | 400010 | +| Borscht | 8 | 6 | 400011 | +| Balti Gosht | 12 | 6 | 400012 | +| Rice Porridge | 2 | 6 | 400013 | +| Cranberry Pie | 8 | 6 | 400014 | +| Fish Soup | 9 | 6 | 400015 | +| Poached Salmon | 6 | 6 | 400016 | +| Marmite | 3 | 6 | 400017 | +| Mais con Hielo | 4 | 6 | 400018 | +| Mediterranean Medly | 11 | 6 | 400019 | +| Halloween Candy | 3 | 6 | 400020 | +| Cheese | 3 | 6 | 400021 | +| Easter Bread | 6 | 6 | 400022 | +| Candied Carrots | 5 | 6 | 400023 | +| Dyed Eggs | 2 | 6 | 400024 | +| Deviled Eggs | 6 | 6 | 400025 | +| Carrot Cake | 6 | 6 | 400026 | +| Valentines Chocolate | 4 | 6 | 400027 | +| Dark Chocolate | 2 | 6 | 400028 | ### Cocktail Brews Brews made from other brews, sometimes with extra ingredients -| Name | Ingredient Amount | Texture ID | -|:-----------------------------:|:-----------------:|:----------:| -| Martini | 3 | 500001 | -| Hunters Digestif | 2 | 500002 | -| Tropical Cocktail | 3 | 500003 | -| Raider Fuel | 2 | 500004 | -| Redcoat Rum | 5 | 500005 | -| Spicy Juice | 2 | 500006 | -| Zombie | 2 | 500007 | -| 5 o' Block Somewhere | 2 | 500008 | -| Lean | 2 | 500009 | -| Bubble Tea | 4 | 500010 | -| Genderfluid Gin | 3 | 500011 | -| Trans Rights Are Human Rights | 3 | 500012 | -| Bees Knees | 3 | 500013 | -| Cherry Spritz | 2 | 500014 | -| Luck of the Irish | 2 | 500015 | -| Pan Galactic Gargle Blaster | 5 | 500016 | +| Name | Ingredient Amount | Difficulty 1-9 | Texture ID | +|:-----------------------------:|:-----------------:|:--------------:|:----------:| +| Martini | 3 | 8 | 500001 | +| Hunters Digestif | 2 | 8 | 500002 | +| Tropical Cocktail | 3 | 8 | 500003 | +| Raider Fuel | 2 | 6 | 500004 | +| Redcoat Rum | 5 | 7 | 500005 | +| Spicy Juice | 2 | 8 | 500006 | +| Zombie | 2 | 8 | 500007 | +| 5 o' Block Somewhere | 2 | 7 | 500008 | +| Lean | 2 | 7 | 500009 | +| Bubble Tea | 4 | 7 | 500010 | +| Genderfluid Gin | 3 | 8 | 500011 | +| Trans Rights Are Human Rights | 3 | 8 | 500012 | +| Bees Knees | 3 | 8 | 500013 | +| Cherry Spritz | 2 | 8 | 500014 | +| Luck of the Irish | 2 | 8 | 500015 | +| Pan Galactic Gargle Blaster | 5 | 8 | 500016 | ### Drug Brews Brews that provide unique/useful effects -| Name | Ingredient Amount | Texture ID | -|:-------------:|:-----------------:|:----------:| -| Cyanide | 3 | 600001 | -| Cannabis | 1 | 600002 | -| Meth | 3 | 600003 | -| Blue Meth | 3 | 600004 | -| Heroin | 5 | 600005 | -| Oestrogen | 4 | 600006 | -| Caffeine | 5 | 600007 | -| Ivermectin | 3 | 600008 | -| DMT | 4 | 600009 | -| Xanax | 5 | 600010 | -| Steroids | 1 | 600011 | -| Testosterone | 4 | 600012 | -| Vicodin | 5 | 600013 | -| Yakult | 6 | 600014 | -| Cocaine | 5 | 600015 | -| Speed | 4 | 600016 | -| NAD+ | 6 | 600017 | -| Epinephrine | 5 | 600018 | -| Firefoam | 4 | 600019 | -| Nitroglycerin | 5 | 600020 | +| Name | Ingredient Amount | Difficulty 1-9 | Texture ID | +|:-------------:|:-----------------:|:--------------:|:----------:| +| Cyanide | 3 | 8 | 600001 | +| Cannabis | 1 | 8 | 600002 | +| Meth | 3 | 8 | 600003 | +| Blue Meth | 3 | 8 | 600004 | +| Heroin | 5 | 8 | 600005 | +| Oestrogen | 4 | 8 | 600006 | +| Caffeine | 5 | 8 | 600007 | +| Ivermectin | 3 | 8 | 600008 | +| DMT | 4 | 8 | 600009 | +| Xanax | 5 | 8 | 600010 | +| Steroids | 1 | 8 | 600011 | +| Testosterone | 4 | 8 | 600012 | +| Vicodin | 5 | 8 | 600013 | +| Yakult | 6 | 8 | 600014 | +| Cocaine | 5 | 8 | 600015 | +| Speed | 4 | 8 | 600016 | +| NAD+ | 6 | 8 | 600017 | +| Epinephrine | 5 | 8 | 600018 | +| Firefoam | 4 | 8 | 600019 | +| Nitroglycerin | 5 | 8 | 600020 | ### Nation Brews Brews that are part of a nation or group's PR/culture and were submitted by them. -| Nation/Group | Name | Ingredient Amount | Texture ID | -|:-------------------:|:-----------------------------:|:-----------------:|:----------:| -| Icenia | Icenian Pale Ale | 4 | 700001 | -| Dalgon | Dalgon Pale | 3 | 700002 | -| Pacem | Pacem Pilsner | 3 | 700003 | -| MTA | Augustan Cider | 2 | 700004 | -| Wolken | Wolken White Ale | 5 | 700005 | -| Southshire | Southshire Stout | 6 | 700006 | -| MTS | Septembrian Summer Breeze | 2 | 700007 | -| Commonwealth | Commonwealth Chardonnay | 4 | 700008 | -| CDM | Cortesian Chianti | 3 | 700009 | -| Sovia | Cabernet Sovian | 2 | 700010 | -| Lusitania | Lusitan Wine | 3 | 700011 | -| Fempire | Fempire Sparkling Rose | 4 | 700012 | -| Nara | Nara Pufferfish Wine | 4 | 700013 | -| CCTT | CCTT Pumpkin Wine | 3 | 700014 | -| Dirt | Dirt Wine | 3 | 700015 | -| Pavia | Pavian Wiskey | 4 | 700016 | -| Fempire | Fembourbon | 4 | 700017 | -| Griffin | Griffin Gold Whiskey | 4 | 700018 | -| Nara | Nara no Uisuki | 4 | 700019 | -| Gensokyo | Gensokyo no Ginjo Sake | 3 | 700020 | -| Venne | Venne Vodka | 4 | 700021 | -| Winterbourne | Winterbourne Wheat Vodka | 4 | 700022 | -| Estalia | Estalian Schnapps | 5 | 700023 | -| Southshire | Southshire Cream Liquer | 5 | 700024 | -| Ku | Kubucha | 5 | 700025 | -| Yoathl | Yoahpple | 3 | 700026 | -| Lambat | Lambasiat | 4 | 700027 | -| Yoathl | Chicha Alchuahtl | 6 | 700028 | -| Banana Republic | Banana Republic Mead | 4 | 700029 | -| Venne | Vennessy | 3 | 700030 | -| Mery | Smiles of T'shola | 3 | 700031 | -| Danzelonia | Dandedanzilonia | 5 | 700032 | -| Maseters | JaegerMaester | 8 | 700033 | -| Southshire | Shaken Southsire Sunset | 4 | 700034 | -| Pacem | Pacem Proper | 4 | 700035 | -| Venne | Venne Cream Soda | 4 | 700036 | -| Valyria | Valyrian Dragonwine | 4 | 700037 | -| Wheatistan | Wheatistan Whimsical Whiskey | 3 | 700038 | -| Danzilonia | Danzilonian Wine | 4 | 700039 | -| Butternut | Butternut Pumpkin Bourbon | 6 | 700040 | -| New Phoenix | New Phoenix Fire Whiskey | 3 | 700041 | -| Pridelands | Pridelands Mead | 3 | 700042 | -| Acadia | Acadian Absinthe | 4 | 700043 | -| Icenia | Icenian Icebreaker | 4 | 700044 | -| Civmarket | CivMarket Chowder | 6 | 700045 | -| Margaritaville | Margaritaville Margarita | 6 | 700046 | -| Estalia | Vault Breaker | 2 | 700047 | -| Icarus | Nuclear Milk | 4 | 700048 | -| Amboise | Amboise Red Wine | 3 | 700049 | -| Atlas | Atlas Corp Cuban | 4 | 700050 | -| Eire | Irish whiskey | 3 | 700051 | -| Grand Imperium | Imperian Whiskey | 3 | 700052 | -| Warmia | Warmian Nitro | 3 | 700053 | -| Nara | Shiroyama Sake | 4 | 700054 | -| Grand Imperium | Ixi | 2 | 700055 | -| Grand Imperium | Imperian Carrot Juice | 2 | 700056 | -| Warmia | Angmar Vodka | 3 | 700057 | -| SPQR | Mint Chocolate Martini | 4 | 700058 | -| Reggio | Reggio Rum | 5 | 700059 | -| Gang Shi | The Shitter's Brown Ale | 3 | 700060 | -| Temporal Isles | Temporal Sweet Red Wine | 4 | 700061 | -| Attoprak | Secret Sprite | 4 | 700062 | -| Icarus | Boar Bourbon | 3 | 700063 | -| Joja | Joja Cola | 4 | 700064 | -| Brunsvilk | Brunsvilk Jenever | 5 | 700065 | -| Zanaris | Zanarian Razzleberry Delight | 5 | 700066 | -| Zanaris | Faerie's Kiss | 4 | 700067 | -| XXXCIOS | Fruity Tooty Mix | 4 | 700068 | -| Bazariškės | Naminė Gira | 4 | 700069 | -| Griffin | Griffin Rose Wine | 3 | 700070 | -| Temporal Isles | Temporal Tropical Punch | 4 | 700071 | -| Transylvania | Cluj pálinka | 3 | 700072 | -| Griffin | Sealeo Rum Punch | 3 | 700073 | -| Spqr | Spicy Vodka Espresso | 4 | 700074 | -| Griffin | Griffin Melon Cider | 2 | 700075 | -| Reggio | Reggio Surprise | 4 | 700076 | -| Tortugain | Rosy Rum | 3 | 700077 | -| Bergburg | Bergburgbier | 4 | 700078 | -| Brunsvilk | Brunsvilk Sewage Water | 5 | 700079 | -| Iria | Irian Jungle Coffee | 4 | 700080 | -| Thoria | Thorian Cider | 4 | 700081 | -| Nro'meagh | Jevoghnya Brew | 4 | 700082 | -| Ila'Kyavul | Greek Fire | 3 | 700083 | -| Larauve | Larauve Medicinal Tea | 4 | 700084 | -| Bloom | Slushy | 4 | 700085 | -| Volterra | Tendie Tonic | 4 | 700086 | -| Volterra | Volt Vodka | 3 | 700087 | -| Aeros | Aeros Ale | 3 | 700088 | -| Cordoba | VB Longneck | 3 | 700089 | -| Yoathl | Gin of Rails | 3 | 700090 | -| Yoathl | Kobeerlinski | 3 | 700091 | -| Freeport | Frihavn Juniper Ale | 4 | 700092 | -| Freeport | Fen Fyool | 5 | 700093 | -| Midgar | Burning Blizzard | 4 | 700094 | -| Larauve | Larauve Bubble Tea | 4 | 700095 | -| Winterbourne | Winterbourne Lamb Knuckle | 3 | 700096 | -| Winterbourne | Winterale | 3 | 700097 | -| Winterbourne | Winterbourne Blue Ice | 3 | 700098 | -| Banana Republic | Banana Republic Pickleback | 2 | 700099 | -| Meracydia | Meracydian Mulled Cider | 7 | 700100 | -| Meracydia | Valar Winter Cheesecake | 7 | 700101 | -| Meracydia | Meracydian Mule | 5 | 700102 | -| Meracydia | Valar Steamfired Soda | 6 | 700103 | -| Larauve | Larauve Shortcut | 5 | 700104 | -| Vald | Vald Mead | 4 | 700105 | -| Estalia | Donut | 5 | 700106 | -| Gmall | Gmall Grog | 4 | 700107 | -| Doom City | Doom Delight | 5 | 700108 | -| Grebada State | Grenada Club White Rum | 3 | 700109 | -| New Jersey | Cranberry Juice Cocktail | 4 | 700110 | -| Hispania | Walloon Guard Whiskey | 3 | 700111 | -| Hispania | Line Infantry Lager | 3 | 700112 | -| Catholic Church | Sacramental Wine | 2 | 700113 | -| SEC | Security Enhancement Cider | 3 | 700114 | -| Greenfaith | Greenfaith Goulash | 4 | 700115 | -| Greenfaith | Trailblazer's Toddy | 4 | 700116 | -| Icaraki | Icaraki | 2 | 700117 | -| Truidencia | Nidaros Akevitt | 2 | 700118 | -| Truidencia | Warm Milk | 2 | 700119 | -| Amicitia | Amicitian Lager | 4 | 700120 | -| Kaowta | Kaowtanese Melonale | 3 | 700121 | -| ICE Labs | Cocaine Cola | 6 | 700122 | -| Bloom | Bloomin' Onion | 1 | 700123 | -| Baile | Azarath Mead | 4 | 700124 | -| CityOfEngland | Long Live The King! | 4 | 700125 | -| Khalkedonia | Khalkedonian Crimson Cabernet | 4 | 700126 | -| Snowpeak | Drunken Boxer’s Delight | 2 | 700127 | -| Artonia | Artonia Cherry slurpee | 3 | 700128 | -| CFA | Motor Oil | 3 | 700129 | -| Madeira | Madeirense Oceanic Rakija | 4 | 700130 | -| SPQR | Pine Grove Elixer | 3 | 700131 | -| Exyria | fent | 3 | 700132 | -| Khalkedonia | Ayran | 4 | 700133 | -| Eldoria | Eldorian Grand Wine | 6 | 700134 | -| Q'Barra | Q'Barran Hist Sap | 5 | 700135 | -| Q'Barra | Q'Barran Phlegmwine | 4 | 700136 | -| BEBO INC | Rust And Rot | 2 | 700137 | -| Alcantara | Spruce Raki | 3 | 700138 | -| Nalora | Minty Blue Juice | 3 | 700139 | -| Baile | Baile Deep | 4 | 700140 | -| Nalora | Blue Star | 7 | 700141 | -| Exyria | Piwo Tesc-mole | 3 | 700142 | -| IF | New Callisto Coppertop | 4 | 700143 | -| Mayguow | Erynorian Root Beer | 3 | 700144 | -| Soria | Sorian Hjemmebrent | 4 | 700145 | -| GWB | Groveheart Potato Wine | 3 | 700146 | -| Griffin | Griffin Steak Co Liquid Lunch | 2 | 700147 | -| Grenada | Grenada Club Dark Rum | 3 | 700148 | -| Salerno | Salerno Shandy | 2 | 700149 | -| Salerno | Brunsvik Bread Soup | 7 | 700150 | -| Khalkedonia | Shawarma | 10 | 700151 | -| Wellington | Wellington Cherry Liquor | 4 | 700152 | -| IF | Imperial Stout | 5 | 700153 | -| Hello Kitty Islands | Hello Kitty Hurricane | 3 | 700154 | -| Exyria | The White Raven | 3 | 700155 | -| Estalia | Suramirian Star Gazer | 4 | 700156 | -| Exyria | Melen | 3 | 700157 | -| Pavia | Bedfordian Mead | 4 | 700158 | -| Rivia | Syndicate's spirit | 4 | 700159 | -| Rivia | Worker's Stout | 4 | 700160 | -| Pavia | Banker's Dust | 5 | 700161 | -| Pavia | Wyepoint Moonshine | 5 | 700162 | -| Northfort | Northfortian vodka | 2 | 700163 | -| Northfort | Proletarian Strength | 5 | 700164 | -| BEBO Inc | Starch Cola | 2 | 700165 | -| Micronook | Mulled Melonade | 3 | 700166 | -| Dr.Urzork Pharmancy | Snake Oil | 5 | 700167 | +| Nation/Group | Name | Ingredient Amount | Difficulty 1-9 | Texture ID | +|:-------------------:|:-----------------------------:|:-----------------:|:--------------:|:----------:| +| Icenia | Icenian Pale Ale | 4 | 7 | 700001 | +| Dalgon | Dalgon Pale | 3 | 7 | 700002 | +| Pacem | Pacem Pilsner | 3 | 7 | 700003 | +| MTA | Augustan Cider | 2 | 6 | 700004 | +| Wolken | Wolken White Ale | 5 | 7 | 700005 | +| Southshire | Southshire Stout | 6 | 8 | 700006 | +| MTS | Septembrian Summer Breeze | 2 | 6 | 700007 | +| Commonwealth | Commonwealth Chardonnay | 4 | 6 | 700008 | +| CDM | Cortesian Chianti | 3 | 7 | 700009 | +| Sovia | Cabernet Sovian | 2 | 8 | 700010 | +| Lusitania | Lusitan Wine | 3 | 7 | 700011 | +| Fempire | Fempire Sparkling Rose | 4 | 6 | 700012 | +| Nara | Nara Pufferfish Wine | 4 | 5 | 700013 | +| CCTT | CCTT Pumpkin Wine | 3 | 6 | 700014 | +| Dirt | Dirt Wine | 3 | 7 | 700015 | +| Pavia | Pavian Wiskey | 4 | 6 | 700016 | +| Fempire | Fembourbon | 4 | 6 | 700017 | +| Griffin | Griffin Gold Whiskey | 4 | 6 | 700018 | +| Nara | Nara no Uisuki | 4 | 6 | 700019 | +| Gensokyo | Gensokyo no Ginjo Sake | 3 | 6 | 700020 | +| Venne | Venne Vodka | 4 | 8 | 700021 | +| Winterbourne | Winterbourne Wheat Vodka | 4 | 7 | 700022 | +| Estalia | Estalian Schnapps | 5 | 7 | 700023 | +| Southshire | Southshire Cream Liquer | 5 | 6 | 700024 | +| Ku | Kubucha | 5 | 6 | 700025 | +| Yoathl | Yoahpple | 3 | 7 | 700026 | +| Lambat | Lambasiat | 4 | 8 | 700027 | +| Yoathl | Chicha Alchuahtl | 6 | 6 | 700028 | +| Banana Republic | Banana Republic Mead | 4 | 6 | 700029 | +| Venne | Vennessy | 3 | 5 | 700030 | +| Mery | Smiles of T'shola | 3 | 7 | 700031 | +| Danzelonia | Dandedanzilonia | 5 | 7 | 700032 | +| Maseters | JaegerMaester | 8 | 8 | 700033 | +| Southshire | Shaken Southsire Sunset | 4 | 6 | 700034 | +| Pacem | Pacem Proper | 4 | 7 | 700035 | +| Venne | Venne Cream Soda | 4 | 5 | 700036 | +| Valyria | Valyrian Dragonwine | 4 | 4 | 700037 | +| Wheatistan | Wheatistan Whimsical Whiskey | 3 | 5 | 700038 | +| Danzilonia | Danzilonian Wine | 4 | 5 | 700039 | +| Butternut | Butternut Pumpkin Bourbon | 6 | 6 | 700040 | +| New Phoenix | New Phoenix Fire Whiskey | 3 | 4 | 700041 | +| Pridelands | Pridelands Mead | 3 | 5 | 700042 | +| Acadia | Acadian Absinthe | 4 | 5 | 700043 | +| Icenia | Icenian Icebreaker | 4 | 7 | 700044 | +| Civmarket | CivMarket Chowder | 6 | 5 | 700045 | +| Margaritaville | Margaritaville Margarita | 6 | 7 | 700046 | +| Estalia | Vault Breaker | 2 | 7 | 700047 | +| Icarus | Nuclear Milk | 4 | 7 | 700048 | +| Amboise | Amboise Red Wine | 3 | 5 | 700049 | +| Atlas | Atlas Corp Cuban | 4 | 5 | 700050 | +| Eire | Irish whiskey | 3 | 5 | 700051 | +| Grand Imperium | Imperian Whiskey | 3 | 6 | 700052 | +| Warmia | Warmian Nitro | 3 | 6 | 700053 | +| Nara | Shiroyama Sake | 4 | 6 | 700054 | +| Grand Imperium | Ixi | 2 | 7 | 700055 | +| Grand Imperium | Imperian Carrot Juice | 2 | 7 | 700056 | +| Warmia | Angmar Vodka | 3 | 5 | 700057 | +| SPQR | Mint Chocolate Martini | 4 | 5 | 700058 | +| Reggio | Reggio Rum | 5 | 5 | 700059 | +| Gang Shi | The Shitter's Brown Ale | 3 | 6 | 700060 | +| Temporal Isles | Temporal Sweet Red Wine | 4 | 5 | 700061 | +| Attoprak | Secret Sprite | 4 | 5 | 700062 | +| Icarus | Boar Bourbon | 3 | 5 | 700063 | +| Joja | Joja Cola | 4 | 5 | 700064 | +| Brunsvilk | Brunsvilk Jenever | 5 | 5 | 700065 | +| Zanaris | Zanarian Razzleberry Delight | 5 | 5 | 700066 | +| Zanaris | Faerie's Kiss | 4 | 5 | 700067 | +| XXXCIOS | Fruity Tooty Mix | 4 | 5 | 700068 | +| Bazariškės | Naminė Gira | 4 | 6 | 700069 | +| Griffin | Griffin Rose Wine | 3 | 6 | 700070 | +| Temporal Isles | Temporal Tropical Punch | 4 | 6 | 700071 | +| Transylvania | Cluj pálinka | 3 | 6 | 700072 | +| Griffin | Sealeo Rum Punch | 3 | 6 | 700073 | +| Spqr | Spicy Vodka Espresso | 4 | 6 | 700074 | +| Griffin | Griffin Melon Cider | 2 | 6 | 700075 | +| Reggio | Reggio Surprise | 4 | 6 | 700076 | +| Tortugain | Rosy Rum | 3 | 6 | 700077 | +| Bergburg | Bergburgbier | 4 | 6 | 700078 | +| Brunsvilk | Brunsvilk Sewage Water | 5 | 6 | 700079 | +| Iria | Irian Jungle Coffee | 4 | 6 | 700080 | +| Thoria | Thorian Cider | 4 | 6 | 700081 | +| Nro'meagh | Jevoghnya Brew | 4 | 6 | 700082 | +| Ila'Kyavul | Greek Fire | 3 | 6 | 700083 | +| Larauve | Larauve Medicinal Tea | 4 | 6 | 700084 | +| Bloom | Slushy | 4 | 6 | 700085 | +| Volterra | Tendie Tonic | 4 | 6 | 700086 | +| Volterra | Volt Vodka | 3 | 6 | 700087 | +| Aeros | Aeros Ale | 3 | 6 | 700088 | +| Cordoba | VB Longneck | 3 | 6 | 700089 | +| Yoathl | Gin of Rails | 3 | 6 | 700090 | +| Yoathl | Kobeerlinski | 3 | 6 | 700091 | +| Freeport | Frihavn Juniper Ale | 4 | 6 | 700092 | +| Freeport | Fen Fyool | 5 | 6 | 700093 | +| Midgar | Burning Blizzard | 4 | 6 | 700094 | +| Larauve | Larauve Bubble Tea | 4 | 6 | 700095 | +| Winterbourne | Winterbourne Lamb Knuckle | 3 | 6 | 700096 | +| Winterbourne | Winterale | 3 | 6 | 700097 | +| Winterbourne | Winterbourne Blue Ice | 3 | 6 | 700098 | +| Banana Republic | Banana Republic Pickleback | 2 | 6 | 700099 | +| Meracydia | Meracydian Mulled Cider | 7 | 6 | 700100 | +| Meracydia | Valar Winter Cheesecake | 7 | 6 | 700101 | +| Meracydia | Meracydian Mule | 5 | 6 | 700102 | +| Meracydia | Valar Steamfired Soda | 6 | 6 | 700103 | +| Larauve | Larauve Shortcut | 5 | 6 | 700104 | +| Vald | Vald Mead | 4 | 6 | 700105 | +| Estalia | Donut | 5 | 6 | 700106 | +| Gmall | Gmall Grog | 4 | 6 | 700107 | +| Doom City | Doom Delight | 5 | 6 | 700108 | +| Grebada State | Grenada Club White Rum | 3 | 6 | 700109 | +| New Jersey | Cranberry Juice Cocktail | 4 | 6 | 700110 | +| Hispania | Walloon Guard Whiskey | 3 | 6 | 700111 | +| Hispania | Line Infantry Lager | 3 | 6 | 700112 | +| Catholic Church | Sacramental Wine | 2 | 6 | 700113 | +| SEC | Security Enhancement Cider | 3 | 6 | 700114 | +| Greenfaith | Greenfaith Goulash | 4 | 6 | 700115 | +| Greenfaith | Trailblazer's Toddy | 4 | 6 | 700116 | +| Icaraki | Icaraki | 2 | 6 | 700117 | +| Truidencia | Nidaros Akevitt | 2 | 6 | 700118 | +| Truidencia | Warm Milk | 2 | 6 | 700119 | +| Amicitia | Amicitian Lager | 4 | 6 | 700120 | +| Kaowta | Kaowtanese Melonale | 3 | 6 | 700121 | +| ICE Labs | Cocaine Cola | 6 | 6 | 700122 | +| Bloom | Bloomin' Onion | 1 | 6 | 700123 | +| Baile | Azarath Mead | 4 | 6 | 700124 | +| CityOfEngland | Long Live The King! | 4 | 6 | 700125 | +| Khalkedonia | Khalkedonian Crimson Cabernet | 4 | 6 | 700126 | +| Snowpeak | Drunken Boxer’s Delight | 2 | 6 | 700127 | +| Artonia | Artonia Cherry slurpee | 3 | 6 | 700128 | +| CFA | Motor Oil | 3 | 6 | 700129 | +| Madeira | Madeirense Oceanic Rakija | 4 | 6 | 700130 | +| SPQR | Pine Grove Elixer | 3 | 6 | 700131 | +| Exyria | fent | 3 | 6 | 700132 | +| Khalkedonia | Ayran | 4 | 6 | 700133 | +| Eldoria | Eldorian Grand Wine | 6 | 6 | 700134 | +| Q'Barra | Q'Barran Hist Sap | 5 | 6 | 700135 | +| Q'Barra | Q'Barran Phlegmwine | 4 | 6 | 700136 | +| BEBO INC | Rust And Rot | 2 | 6 | 700137 | +| Alcantara | Spruce Raki | 3 | 6 | 700138 | +| Nalora | Minty Blue Juice | 3 | 8 | 700139 | +| Baile | Baile Deep | 4 | 6 | 700140 | +| Nalora | Blue Star | 7 | 6 | 700141 | +| Exyria | Piwo Tesc-mole | 3 | 6 | 700142 | +| IF | New Callisto Coppertop | 4 | 6 | 700143 | +| Mayguow | Erynorian Root Beer | 3 | 6 | 700144 | +| Soria | Sorian Hjemmebrent | 4 | 6 | 700145 | +| GWB | Groveheart Potato Wine | 3 | 6 | 700146 | +| Griffin | Griffin Steak Co Liquid Lunch | 2 | 6 | 700147 | +| Grenada | Grenada Club Dark Rum | 3 | 6 | 700148 | +| Salerno | Salerno Shandy | 2 | 6 | 700149 | +| Salerno | Brunsvik Bread Soup | 7 | 6 | 700150 | +| Khalkedonia | Shawarma | 10 | 6 | 700151 | +| Wellington | Wellington Cherry Liquor | 4 | 6 | 700152 | +| IF | Imperial Stout | 5 | 6 | 700153 | +| Hello Kitty Islands | Hello Kitty Hurricane | 3 | 6 | 700154 | +| Exyria | The White Raven | 3 | 6 | 700155 | +| Estalia | Suramirian Star Gazer | 4 | 6 | 700156 | +| Exyria | Melen | 3 | 6 | 700157 | +| Pavia | Bedfordian Mead | 4 | 6 | 700158 | +| Rivia | Syndicate's spirit | 4 | 6 | 700159 | +| Rivia | Worker's Stout | 4 | 6 | 700160 | +| Pavia | Banker's Dust | 5 | 6 | 700161 | +| Pavia | Wyepoint Moonshine | 5 | 6 | 700162 | +| Northfort | Northfortian vodka | 2 | 6 | 700163 | +| Northfort | Proletarian Strength | 5 | 6 | 700164 | +| BEBO Inc | Starch Cola | 2 | 6 | 700165 | +| Micronook | Mulled Melonade | 3 | 6 | 700166 | +| Dr.Urzork Pharmancy | Snake Oil | 5 | 6 | 700167 | ### Player Brews Brews specific to individuals either for their contribution to the genre/server or by way of donating a high amount to the server via patreon. -| Name | Ingredient Amount | Texture ID | -|:----------------------------------:|:-----------------:|:----------:| -| Lord Marzenpan | 2 | 800001 | -| SoundTech's Soundcheck | 3 | 800002 | -| WingZero's Wingman | 3 | 800003 | -| Ahri's Touhou Twister | 3 | 800004 | -| Maxoplopy's Beer Garden | 3 | 800005 | -| Diet_Cola's Rum and Cola | 3 | 800006 | -| Icekeeper's Icepick | 3 | 800007 | -| ProgrammerDan's Power Coffee | 1 | 800008 | -| TTK2's Closing Speech | 3 | 800009 | -| Infra's Code Injection | 3 | 800010 | -| Rourke750's Vermouth Reviver | 3 | 800011 | -| TealNerd's Sneaker Schnapps | 3 | 800012 | -| Eroc's Top of the Rock | 4 | 800013 | -| RedDevel's Devilish Wine | 3 | 800014 | -| Xfier's Firesnap | 4 | 800015 | -| Orinnari's Amaretto | 3 | 800016 | -| Awoo's Git reset Hard Head | 3 | 800017 | -| Gjum's Grape Juice on Ice | 4 | 800018 | -| NoTruePunkin Spiced Mead | 4 | 800019 | -| Kingtell's Royal Absinthe | 5 | 800020 | -| Gamer Gwua Bathwater | 3 | 800021 | -| Okx's Oxeye Oxkick | 3 | 800022 | -| Leo's Orange Creamsicle | 6 | 800023 | -| Colton's Love Martini | 3 | 800024 | -| Golden Pick Stout | 4 | 800025 | -| Astro's Pill | 6 | 800027 | -| Jeremy's Slippy-Sloppy Screwdriver | 3 | 800028 | -| Trizzzx's Worcestershire sauce | 4 | 800029 | -| StrawBeary Dreams | 6 | 800030 | +| Name | Ingredient Amount | Difficulty 1-9 | Texture ID | +|:---------------------------------------------:|:-----------------:|:--------------:|:----------:| +| Lord Marzenpan | 2 | 8 | 800001 | +| SoundTech's Soundcheck | 3 | 8 | 800002 | +| WingZero's Wingman | 3 | 8 | 800003 | +| Ahri's Touhou Twister | 3 | 8 | 800004 | +| Maxoplopy's Beer Garden | 3 | 8 | 800005 | +| Diet_Cola's Rum and Cola | 3 | 8 | 800006 | +| Icekeeper's Icepick | 3 | 8 | 800007 | +| ProgrammerDan's Power Coffee | 1 | 8 | 800008 | +| TTK2's Closing Speech | 3 | 8 | 800009 | +| Infra's Code Injection | 3 | 8 | 800010 | +| Rourke750's Vermouth Reviver | 3 | 8 | 800011 | +| TealNerd's Sneaker Schnapps | 3 | 8 | 800012 | +| Eroc's Top of the Rock | 4 | 8 | 800013 | +| RedDevel's Devilish Wine | 3 | 8 | 800014 | +| Xfier's Firesnap | 4 | 8 | 800015 | +| Orinnari's Amaretto | 3 | 8 | 800016 | +| Awoo's Git reset Hard Head | 3 | 8 | 800017 | +| Gjum's Grape Juice on Ice | 4 | 8 | 800018 | +| NoTruePunkin Spiced Mead | 4 | 8 | 800019 | +| Kingtell's Royal Absinthe | 5 | 8 | 800020 | +| Gamer Gwua Bathwater | 3 | 8 | 800021 | +| Okx's Oxeye Oxkick | 3 | 8 | 800022 | +| Leo's Orange Creamsicle | 6 | 8 | 800023 | +| Colton's Love Martini | 3 | 8 | 800024 | +| Golden Pick Stout | 4 | 7 | 800025 | +| Katz Catnip Kaffee 6 | 8 | 8 | 800026 | +| Astro's Pill | 6 | 7 | 800027 | +| Jeremy's Slippy-Sloppy Screwdriver | 3 | 8 | 800028 | +| Trizzzx's Worcestershire sauce | 4 | 8 | 800029 | +| StrawBeary Dreams | 6 | 8 | 800030 | +| Pisco Sour | 3 | 6 | 800031 | ## List of Custom Ingredients diff --git a/docs/wiki/plugins/fun/elevators.md b/docs/wiki/plugins/fun/elevators.md index 7eb827d97b..d1ecea618f 100644 --- a/docs/wiki/plugins/fun/elevators.md +++ b/docs/wiki/plugins/fun/elevators.md @@ -4,4 +4,21 @@ description: Elevators Plugin --- # Elevators -(WIP) \ No newline at end of file + +An elevator is a block that allows teleporting either directly upwards or downwards to another elevator. CivMC uses lodestones as elevators. + +### Mechanics + +When standing on an elevator, by default, jump to go to the elevator above, and shift to go to the elevator below. In the server config (`/config`) you can change this setting so that instead when you right click you go to the elevator above and left click to go to the elevator below. + +![Elevator Config](https://github.com/user-attachments/assets/699117fe-aafa-4096-b523-caa163c23d65) + +To be considered valid, an elevator block must have at least two (2) empty air blocks above it (you are able to teleport to a lodestone if there is water in the two blocks above it). You can place blocks like a trapdoor and iron bars on a lodestone to restrict movement down to the lodestone, making it one-way to the elevator above only. + +In this example, you can move from the bottom to the top lodestones but you cannot go from the top lodestones to the bottom ones. + +![image](https://github.com/user-attachments/assets/f1be70f7-b436-4afb-830c-c9d0f15e2c0f) + + + +[Reinforcing](https://civmc.net/wiki/plugins/essential/citadel.html) an elevator will not lock it to that [NameLayer](https://civmc.net/wiki/plugins/essential/namelayer.html) group. It will remain usable by all players. diff --git a/docs/wiki/plugins/fun/itemexchange.md b/docs/wiki/plugins/fun/itemexchange.md index bafe9e8c7f..5d17ca520d 100644 --- a/docs/wiki/plugins/fun/itemexchange.md +++ b/docs/wiki/plugins/fun/itemexchange.md @@ -2,6 +2,83 @@ title: Item Exchange description: Item exchange plugin --- +An Item Exchange is a mechanic that allows users to trade items at predefined rates at a chest. -# Item Exchange -(WIP) \ No newline at end of file +### Mechanics +Shop chests are created when they contain "Exchange Rules" (encoded buttons which specify the rulse of an exchange). Exchanges are made of pairs of inputs and outputs (though an output rule is not strictly necessary and a "donation" item exchange uses only an input rule). Bulk exchange rules are a single encoded button which contains within it one or more pairs of Exchange Rules. You can create a bulk exchange rule by placing an input and output button within a crafting grid. You can add bulk exchange buttons to other bulk exchange buttons to save space in your shop chest. + +Players can view and cycle through a shop's available exchanges by punching a shop chest, preferably with an empty hand. If the player then punches the chest on a given exchange with a matching input, if the shop chest is stocked and has space available, then the exchange is transacted. + +Shops can, by default, be made with chests, trapped chests, barrels, dispensers, and droppers. You can check which containers are supported using the `/ieinfo shopblocks` command. + +![ShopBlocks SS](https://github.com/user-attachments/assets/0c4830ae-0af9-4b37-be15-2243dbcbd2f7) + +ItemExchange differs from other market and trade plugins in that it functions on a series of rules and criteria, rather than selling specific items. An Exchange Rule that ***ONLY*** specifies that the output is a Diamond Pickaxe will be able to output ***ANY*** Diamond Pickaxe, enchanted or not, used or not, repaired or not, named or not, etc. + +### Creating an Exchange Rule +There are three ways to create an Exchange Rule, each via the `/iecreate` command (`/iec`): + +- `/iereate ` while holding an item in your hand will create a rule with its criteria as close to matching that item as possible. +- `/iecreate [amount]` will create a barebones rule of just the material and the amount, which defaults to 1 if not specified. See [Paper's Material list](https://jd.papermc.io/paper/1.21.5/org/bukkit/Material.html) for reference. +- `/iecreate` while looking at a chest (or other supported container) containing one or two stacks will add a corresponding input and output rule for those items. If the container is reinforced, you must have the required permissions for this command to function. + +### Modifying an Exchange Rule + +Exchange Rules can be modified using the `/ieset` command (`/ies`) while holding the button: +- `/ieset material ` will update the material. See [Paper's Material list](https://jd.papermc.io/paper/1.21.5/org/bukkit/Material.html) for reference. +- `/ieset amount ` will update the amount, which must be higher than zero. +- `/ieset switchio` will filp the exchange rule from an input to an output, and vice versa. + +Exchange Rules can also contain *modifiers*, which are additional bits of critieria that can increase the specificity of an exchange: + +- ```/ieset durability [damage]``` + Updates how damaged the item should be. + - Not specifying the damage (or inputting `ANY`) will ignore durability. + - Inputting `USED` will only accept damaged items. + +- ```/ieset repair [level]``` + Sets the repair level of the item. + - Not specifying the level ignores it. + - Prefixing with `@` (e.g., `@9`) accepts only exactly level 9. + - Using just `9` accepts level 9 or lower. + +- ```/ieset ignoreenchants``` + Ignores all enchantments. + +- ```/ieset allowenchants``` + Ignores unspecified enchantments. + +- ```/ieset denyenchants``` + Ensures only specified enchants are allowed. + +- ```/ieset enchant <+/?/->[level]``` + Reset enchantment rules. + - Example: `/ieset enchant +DURABILITY3` adds a required enchant. + - Example: `/ieset enchant -DURABILITY` excludes it. + - Example: `/ieset enchant ?DURABILITY` resets the rule. + See [Paper's Enchantment List](https://jd.papermc.io/paper/1.21.5/org/bukkit/enchantments/Enchantment.html) for valid enchant names. + +- ```/ieset ignoredisplayname``` + Ignores item display names. + +- ```/ieset displayname [name]``` + Sets required display name. Omitting name ignores display names. + +- ```/ieset ignorelore``` + Ignores item lore. + +- ```/ieset lore [lore]``` + Sets required lore. + Use `;` to separate lines, e.g. + ```/ieset lore First Line;Second Line``` + +- ```/ieset group [group]``` + Sets a [NameLayer](https://civmc.net/wiki/plugins/essential/namelayer.html) group requirement (only for input rules). + Omitting the group disables the requirement. + +### Relays + +Ender chests are used as ***relays***, which when clicked search for shop chests within 4 blocks and return their contents. The ender chest needs to connected to the shop chest with transparent blocks. They can be used to add a moderate layer of protection to shop chests selling valuable goods, by putting distance between the shop chest, itself andt he relay, where other players interact with it. + + +![RelayChest](https://github.com/user-attachments/assets/ec4b0a76-2160-4850-b283-6047fb781194) diff --git a/docs/wiki/plugins/fun/simplevoicechat.md b/docs/wiki/plugins/fun/simplevoicechat.md new file mode 100644 index 0000000000..9393df0c20 --- /dev/null +++ b/docs/wiki/plugins/fun/simplevoicechat.md @@ -0,0 +1,11 @@ +# Simple Voice Chat + +Simple Voice Chat is a plugin that allows players to optionally communicate with each other using voice chat. +Getting started with Simple Voice Chat is easy, and it can be set up in just a few steps. + +## Installation + +Download the latest version of Simple Voice Chat from either [Modrinth](https://modrinth.com/plugin/simple-voice-chat) or [CurseForge](https://www.curseforge.com/minecraft/mc-mods/simple-voice-chat), and place in your mods folder like any other mod. + +After you join the server, just follow the instructions after pressing the `V` key to set up your microphone and speakers. +(More instructions can be found in the [Simple Voice Chat Wiki](https://modrepo.de/minecraft/voicechat/wiki/client_setup).) diff --git a/docs/wiki/plugins/unique/hiddenore.md b/docs/wiki/plugins/unique/hiddenore.md index c4ee0240f4..810ca44af0 100644 --- a/docs/wiki/plugins/unique/hiddenore.md +++ b/docs/wiki/plugins/unique/hiddenore.md @@ -6,7 +6,7 @@ description: Hiddenore plugin # HiddenOre Every time a block that could contain ores is broken, there's a chance that ores will spawn around the player. Ores spawned this way depend on the Y level with spawn rates similar to vanilla ore distribution. Mining can be done at the same y-level for every ore like you'd do in vanilla Minecraft. -It is recommended to use silk touch when mining ores, to then smelt the ores using an ore smelter factory for better rates than fortune might give. +It is recommended to use silk touch when mining redstone, coal, diamond and lapis lazuli, to then smelt the ores using an ore smelter factory for better rates than fortune might give. Fortune should be used to mine copper, iron, and gold, then smelt it using an ore smelter factory. ## Veins @@ -14,16 +14,121 @@ Ores can also spawn in veins. Veins can take many different shapes depending on A common strategy for mining out these veins is to mine a box around every found ore, stretching as much as 11 blocks from the center ore found. Every time an ore is found a message will appear in chat notifying the player that they've discovered a new ore. It is common to use blocks or a waypoint mod to mark the ores to gain a sense of the shape of the vein to better be able to mine one out. -Any ore can be found in a vein but **diamond ore only spawns in veins.** +Any ore can be found in a vein but **diamond ore only spawns in veins.** (Note: there were cave dusted diamonds at the start of the world, however, the majority of these have been mined out by players) ![Diamond Vein](media/Diamondvein.png) -## Netherite pickaxe bonus +## Netherite Pickaxe Bonus -Netherite pickaxes provide a multiplier to ore spawn rate, and give diamond veins a chance to spawn multiple ores at once, instead of only 1 per discovery as per normal for diamond tools and below. Making Netherite tools a worthwhile investment for any miner. +Netherite pickaxes provide a multiplier to ore spawn rate, and give diamond veins a chance to spawn multiple ores at once (up to triple), instead of only 1 per discovery as per normal for diamond tools and below. Making Netherite tools a worthwhile investment for any miner. ## Fossils While mining there's a chance a fossil might drop, these fossils can be broken in the **ore smelter factory** for a small chance at loot. The most common loot is either dirt, sand, or gravel. ---Table of all drops/chances-- +#### Advanced Smelter Loot Table + +| Probability | Drop | +| ------------- | ------------- | +| 18.04% | Dirt | +| 18.04% | Cobblestone | +| 18.04% | Sand | +| 18.04% | Gravel | +| 18.04% | Terracotta | +| 0.09% , ~50 Possibilities | Player Head (Patrons, Notable Players, etc.) | +| 0.09% | 1 Creeper Spawn Egg | +| 0.09% | 1 Zombie Spawn Egg | +| 0.09% | 1 Skeleton Spawn Egg | +| 0.09% | 1 Spider Spawn Egg | +| 0.09% | 1 Blaze Spawn Egg | +| 0.09% | 1 Ghast Spawn Egg | +| 0.09% | 1 Guardian Spawn Egg | +| 0.09% | 1 Magma Cube Spawn Egg | +| 0.09% | 1 Slime Spawn Egg | +| 0.09% | 1 Witch Spawn Egg | +| 0.09% | 1 Villager Spawn Egg | +| 0.09% | 1 Cave Spider Spawn Egg | +| 0.09% | 1 Enderman Spawn Egg | +| 0.09% | 1 Zombified Piglin Spawn Egg | +| 0.09% | 1 Iron Pickaxe | +| 0.09% | 1 Iron Shovel | +| 0.09% | 1 Iron Axe | +| 0.09% | 1 Iron Sword | +| 0.09% | 5 Note Block | +| 0.09% | 1 Diamond | +| 0.09% | 1 Emerald | +| 0.09% | 1 Iron Block | +| 0.09% | 1 Redstone Block | +| 0.09% | 1 Lapis Block | +| 0.09% | 1 Minecart | +| 0.09% | 1 Mooshroom Spawn Egg | +| 0.09% | 1 Horse Spawn Egg | +| 0.09% | 1 Rabbit Spawn Egg | +| 0.09% | 1 Ocelot Spawn Egg | +| 0.09% | 1 Squid Spawn Egg | +| 0.09% | 1 Wolf Spawn Egg | +| 0.09% | 1 Axolotl Spawn Egg | +| 0.09% | 1 Bee Spawn Egg | +| 0.09% | 1 Panda Spawn Egg | +| 0.09% | 1 Dolphin Spawn Egg | +| 0.09% | 1 Stray Spawn Egg | +| 0.09% | 1 Strider Spawn Egg | +| 0.09% | 1 Hoglin Spawn Egg | +| 0.09% | 1 Cat Spawn Egg | +| 0.09% | 1 Fox Spawn Egg | +| 0.09% | 1 Iron Horse Armor | +| 0.09% | 1 Heart Of The Sea | +| 0.09% | 5 Prismarine Shard | +| 0.09% | 3 Prismarine Crystals | +| 0.09% | 1 Jukebox | +| 0.09% | 1 Llama Spawn Egg | +| 0.09% | 1 Parrot Spawn Egg | +| 0.09% | 1 Polar Bear Spawn Egg | +| 0.09% | 1 Donkey Spawn Egg | +| 0.09% | 1 Skeleton Horse Spawn Egg | +| 0.09% | 1 Zombie Horse Spawn Egg | +| 0.09% | 1 Endermite Spawn Egg | +| 0.09% | 1 Silverfish Spawn Egg | +| 0.09% | 1 Vex Spawn Egg | +| 0.09% | 1 Vindicator Spawn Egg | +| 0.01% | 1 Diamond Horse Armor | +| 0.01% | 1 Diamond Pickaxe | +| 0.01% | 1 Diamond Axe | +| 0.01% | 1 Diamond Shovel | +| 0.01% | 1 Sponge | +| 0.01% | 1 Diamond Chestplate | +| 0.01% | 1 Diamond Leggings | +| 0.01% | 1 Diamond Helmet | +| 0.01% | 1 Diamond Boots | +| 0.01% | 64 Iron Ingot | +| 0.01% | 1 Diamond Block | +| 0.01% | 2048 Charcoal | +| 0.01% | 1 Bone Block | +| 0.01% | 1 Music Disc 13 | +| 0.01% | 1 Music Disc Cat | +| 0.01% | 1 Music Disc Blocks | +| 0.01% | 1 Music Disc Chirp | +| 0.01% | 1 Music Disc Far | +| 0.01% | 1 Music Disc Mall | +| 0.01% | 1 Music Disc Mellohi | +| 0.01% | 1 Music Disc Stal | +| 0.01% | 1 Music Disc Strad | +| 0.01% | 1 Music Disc Ward | +| 0.01% | 1 Music Disc 11 | +| 0.01% | 1 Music Disc Wait | +| 0.01% | 1 Music Disc Pigstep | +| 0.01% | 1 Music Disc Otherside | +| 0.004% | 1 Beacon | +| 0.004% | 1 Enchanted Book | +| 0.004% | 1 Enchanted Book | +| 0.004% | 1 Enchanted Book | +| 0.004% | 1 Enchanted Book | +| 0.004% | 1 Enchanted Book | +| 0.004% | 1 Enchanted Book | +| 0.004% | 1 Enchanted Book | +| 0.0004% | 1 Paper | +| 0.0004% | 1 Clock | +| 0.0004% | 1 Apollo's Bow | +| 0.0004% | 1 Imcando Pickaxe | +| 0.00000001% | 1 Dragon Egg | + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 60d1dd326c..dedaea630f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,15 +1,18 @@ [versions] -paper = "1.21.4-R0.1-SNAPSHOT" +paper = "1.21.8-R0.1-SNAPSHOT" junit = "5.8.2" nuotifier = "2.7.2" +velocity = "3.4.0-SNAPSHOT" +configurate = "4.2.0" [plugins] -paper-userdev = { id = "io.papermc.paperweight.userdev", version = "2.0.0-beta.11" } -shadow = { id = "com.github.johnrengelman.shadow", version = "8.1.1" } +paper-userdev = { id = "io.papermc.paperweight.userdev", version = "2.0.0-beta.17" } +shadow = { id = "com.gradleup.shadow", version = "8.3.8" } runpaper = { id = "xyz.jpenilla.run-paper", version = "2.3.1" } [libraries] paper-api = { group = "io.papermc.paper", name = "paper-api", version.ref = "paper" } +velocity-api = { group = "com.velocitypowered", name = "velocity-api", version.ref = "velocity"} aikar-acf = { group = "co.aikar", name = "acf-paper", version = "0.5.1-SNAPSHOT" } aikar-taskchain = { group = "co.aikar", name = "taskchain-bukkit", version = "3.7.2" } @@ -18,13 +21,17 @@ commons-lang3 = { group = "org.apache.commons", name = "commons-lang3", version commons-collections4 = { group = "org.apache.commons", name = "commons-collections4", version = "4.4" } commons-math3 = { group = "org.apache.commons", name = "commons-math3", version = "3.6.1" } -rabbitmq-client = { group = "com.rabbitmq", name = "amqp-client", version = "5.17.1" } +cron-utils = { group = "com.cronutils", name = "cron-utils", version = "9.2.1" } + +configurate-yaml = { group = "org.spongepowered", name = "configurate-yaml", version.ref = "configurate" } + +rabbitmq-client = { group = "com.rabbitmq", name = "amqp-client", version = "5.26.0" } nuvotifier-api = { group = "com.github.NuVotifier.NuVotifier", name = "nuvotifier-api", version.ref = "nuotifier" } nuvotifier-bukkit = { group = "com.github.NuVotifier.NuVotifier", name = "nuvotifier-bukkit", version.ref = "nuotifier" } vault-api = { group = "com.github.MilkBowl", name = "VaultAPI", version = "1.7" } -luckperms-api = { group = "net.luckperms", name = "api", version = "5.0" } +luckperms-api = { group = "net.luckperms", name = "api", version = "5.4" } aswm-api = { group = "com.infernalsuite.asp", name = "api", version = "4.0.0-SNAPSHOT"} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135c49..a4b76b9530 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cea7a793a8..d4081da476 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a4269..f3b75f3b0d 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 93e3f59f13..9d21a21834 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/plugins/announcements-velocity/build.gradle.kts b/plugins/announcements-velocity/build.gradle.kts new file mode 100644 index 0000000000..b0fc77030b --- /dev/null +++ b/plugins/announcements-velocity/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + alias(libs.plugins.shadow) +} + +version = "1.0.0" + +dependencies { + compileOnly(libs.velocity.api) + annotationProcessor(libs.velocity.api) + + api(libs.cron.utils) + api(libs.configurate.yaml) +} diff --git a/plugins/announcements-velocity/src/main/java/net/civmc/announcements/AnnouncementsPlugin.java b/plugins/announcements-velocity/src/main/java/net/civmc/announcements/AnnouncementsPlugin.java new file mode 100644 index 0000000000..22c0657e08 --- /dev/null +++ b/plugins/announcements-velocity/src/main/java/net/civmc/announcements/AnnouncementsPlugin.java @@ -0,0 +1,182 @@ +package net.civmc.announcements; + +import com.cronutils.model.Cron; +import com.cronutils.model.CronType; +import com.cronutils.model.definition.CronDefinitionBuilder; +import com.cronutils.model.time.ExecutionTime; +import com.cronutils.parser.CronParser; +import com.google.inject.Inject; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; +import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.plugin.annotation.DataDirectory; +import com.velocitypowered.api.proxy.ProxyServer; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.time.Instant; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import net.civmc.announcements.update.UpdateCommand; +import net.civmc.announcements.update.UpdateListener; +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import net.kyori.adventure.title.Title; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.spongepowered.configurate.CommentedConfigurationNode; +import org.spongepowered.configurate.ConfigurationNode; +import org.spongepowered.configurate.yaml.YamlConfigurationLoader; + +@Plugin(id = "civannouncements", name = "Civ Announcements", version = "1.0.0", + url = "https://civmc.net", description = "Sends various announcements", authors = {"Huskydog9988"}) +public class AnnouncementsPlugin { + + private final CronParser cronParser = new CronParser(CronDefinitionBuilder.instanceDefinitionFor(CronType.UNIX)); + + private final ProxyServer server; + private final Logger logger; + private final Path dataDirectory; + + private record Announcement(Component message, boolean title, Duration bossBarLength) { + + } + + private final Map scheduledAnnouncements = new HashMap<>(); + private final Map lastExecutionTimes = new ConcurrentHashMap<>(); + + private final BossBarManager bossBars; + + private ZonedDateTime startupTime; + + private @Nullable CommentedConfigurationNode config; + + @Inject + public AnnouncementsPlugin(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) { + this.server = server; + this.logger = logger; + this.dataDirectory = dataDirectory; + this.bossBars = new BossBarManager(server, this); + } + + @Subscribe + public void onProxyInitialization(ProxyInitializeEvent event) { + startupTime = ZonedDateTime.now(); + + loadConfig(); + scheduleTasks(); + + // task to check if a scheduled announcement should be sent + server.getScheduler().buildTask(this, this::sendScheduledMessages).repeat(1, TimeUnit.SECONDS).schedule(); + bossBars.init(); + server.getEventManager().register(this, bossBars); + + Component barMessage = MiniMessage.miniMessage().deserialize(config.node("restart").node("bar").getString()); + Component joinMessage = MiniMessage.miniMessage().deserialize(config.node("restart").node("message").getString()); + Component kickMessage = MiniMessage.miniMessage().deserialize(config.node("restart").node("kick").getString()); + + UpdateListener listener = new UpdateListener(server, this, joinMessage, kickMessage); + server.getEventManager().register(this, listener); + server.getCommandManager().register(server.getCommandManager().metaBuilder("update").plugin(this).build(), + new UpdateCommand(server, this, listener, bossBars, barMessage)); + } + + private void scheduleTasks() { + var minimessageSerializer = MiniMessage.miniMessage(); + + // read scheduled announcements from config + List announcements = config.node("scheduledAnnouncements").childrenList(); + for (ConfigurationNode announcement : announcements) { + Cron cron = cronParser.parse(Objects.requireNonNull(announcement.node("cron").getString())); + // convert message to Component + Component formatedMsg = minimessageSerializer.deserialize(Objects.requireNonNull(announcement.node("message").getString())); + boolean showTitle = announcement.node("title").getBoolean(false); + int bossBar = announcement.node("bossbar_seconds").getInt(0); + scheduledAnnouncements.put(cron, new Announcement(formatedMsg, showTitle, bossBar > 0 ? Duration.ofSeconds(bossBar) : null)); + } + } + + /** + * Checks if a scheduled message needs to be sent, and sends them if so + */ + private void sendScheduledMessages() { + ZonedDateTime now = ZonedDateTime.now().withNano(0); + + for (Cron cron : scheduledAnnouncements.keySet()) { + ExecutionTime executionTime = ExecutionTime.forCron(cron); + + // get last time a cron *should* have run + executionTime.lastExecution(now).ifPresent(lastExecution -> { + if (lastExecution.isBefore(startupTime)) { + return; + } + // Check if we should execute, and if con already executed at that time + if (executionTime.isMatch(now) && !lastExecution.equals(lastExecutionTimes.get(cron))) { + // Execute and update the last execution time + Announcement announcement = scheduledAnnouncements.get(cron); + lastExecutionTimes.put(cron, lastExecution); + if (announcement.bossBarLength != null) { + BossBar bossBar = BossBar.bossBar(announcement.message, 1f, BossBar.Color.PINK, BossBar.Overlay.PROGRESS); + bossBars.addBossBar(bossBar, announcement.message, Instant.now().plus(announcement.bossBarLength)); + } else if (announcement.title) { + // send title to all players + Title title = Title.title(announcement.message, Component.empty()); + server.showTitle(title); + } else { + server.sendMessage(announcement.message); + } + + logger.info("Announcement sent: {}", PlainTextComponentSerializer.plainText().serialize(announcement.message)); + } + }); + } + } + + + /** + * Loads the config from disk, and creates it if necessary + */ + private void loadConfig() { + try { + // ensure data directory exists + if (!Files.exists(dataDirectory)) { + Files.createDirectories(dataDirectory); + } + } catch (IOException e) { + logger.error("Could not create data directory: {}", dataDirectory, e); + return; + } + + // create config file if it doesn't exist + Path configFile = dataDirectory.resolve("config.yml"); + if (!Files.exists(configFile)) { + try (InputStream in = getClass().getResourceAsStream("/config.yml")) { + if (in != null) { + Files.copy(in, configFile); + logger.info("Default configuration file created."); + } else { + logger.error("Default configuration file is missing in resources!"); + return; + } + } catch (IOException e) { + logger.error("Could not create default configuration file: {}", configFile, e); + } + } + + YamlConfigurationLoader loader = YamlConfigurationLoader.builder().path(configFile).build(); + try { + config = loader.load(); + } catch (IOException e) { + throw new RuntimeException("Could not load configuration file: " + configFile, e); + } + } +} diff --git a/plugins/announcements-velocity/src/main/java/net/civmc/announcements/BossBarManager.java b/plugins/announcements-velocity/src/main/java/net/civmc/announcements/BossBarManager.java new file mode 100644 index 0000000000..cb46d9d008 --- /dev/null +++ b/plugins/announcements-velocity/src/main/java/net/civmc/announcements/BossBarManager.java @@ -0,0 +1,106 @@ +package net.civmc.announcements; + +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.player.ServerPostConnectEvent; +import com.velocitypowered.api.proxy.ProxyServer; +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.text.Component; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class BossBarManager { + private record ActiveBossBar(BossBar bar, Component text, Instant start, Instant end) { + + } + private final List bossBars = new ArrayList<>(); + private final Lock bossBarLock = new ReentrantLock(); + + private final ProxyServer server; + private final AnnouncementsPlugin plugin; + + public BossBarManager(ProxyServer server, AnnouncementsPlugin plugin) { + this.server = server; + this.plugin = plugin; + } + + public void init() { + server.getScheduler().buildTask(plugin, this::tickBossBars).repeat(250, TimeUnit.MILLISECONDS).schedule(); + } + + public Runnable addBossBar(BossBar bar, Component text, Instant expiration) { + ActiveBossBar active = new ActiveBossBar(bar, text, Instant.now(), expiration); + bossBarLock.lock(); + try { + bossBars.add(active); + if (text != null) { + bar.name(TimeComponent.replace(text, Duration.between(Instant.now(), expiration))); + } + server.showBossBar(bar); + } finally { + bossBarLock.unlock(); + } + return () -> { + bossBarLock.lock(); + try { + for (Iterator iterator = bossBars.iterator(); iterator.hasNext(); ) { + ActiveBossBar bossBar = iterator.next(); + if (bossBar == active) { + server.hideBossBar(bar); + iterator.remove(); + return; + } + } + } finally { + bossBarLock.unlock(); + } + }; + } + + private void tickBossBars() { + bossBarLock.lock(); + try { + for (Iterator iterator = bossBars.iterator(); iterator.hasNext(); ) { + ActiveBossBar bossBar = iterator.next(); + + Instant now = Instant.now(); + if (now.isAfter(bossBar.end)) { + iterator.remove(); + server.hideBossBar(bossBar.bar); + continue; + } + + if (bossBar.text != null) { + bossBar.bar.name(TimeComponent.replace(bossBar.text, Duration.between(now, bossBar.end()))); + } + + long totalMillis = bossBar.start.until(bossBar.end, ChronoUnit.MILLIS); + long progessMillis = bossBar.start.until(now, ChronoUnit.MILLIS); + + float progress = 1 - (progessMillis / (float) totalMillis); + bossBar.bar.progress(progress); + } + } finally { + bossBarLock.unlock(); + } + } + + @Subscribe + public void on(ServerPostConnectEvent event) { + bossBarLock.lock(); + try { + for (ActiveBossBar bossBar : bossBars) { + event.getPlayer().showBossBar(bossBar.bar); + } + } finally { + bossBarLock.unlock(); + } + + } +} diff --git a/plugins/announcements-velocity/src/main/java/net/civmc/announcements/TimeComponent.java b/plugins/announcements-velocity/src/main/java/net/civmc/announcements/TimeComponent.java new file mode 100644 index 0000000000..4b95157698 --- /dev/null +++ b/plugins/announcements-velocity/src/main/java/net/civmc/announcements/TimeComponent.java @@ -0,0 +1,15 @@ +package net.civmc.announcements; + +import java.time.Duration; +import net.kyori.adventure.text.Component; + +public class TimeComponent { + + public static Component replace(Component component, Duration duration) { + long minutes = Math.ceilDiv(duration.getSeconds(), 60); + return component + .replaceText(b -> b.matchLiteral("%").replacement(Long.toString(minutes))) + .replaceText(b -> b.matchLiteral("$").replacement(minutes == 1 ? "" : "s")); + } + +} diff --git a/plugins/announcements-velocity/src/main/java/net/civmc/announcements/update/UpdateCommand.java b/plugins/announcements-velocity/src/main/java/net/civmc/announcements/update/UpdateCommand.java new file mode 100644 index 0000000000..2792d909c4 --- /dev/null +++ b/plugins/announcements-velocity/src/main/java/net/civmc/announcements/update/UpdateCommand.java @@ -0,0 +1,83 @@ +package net.civmc.announcements.update; + +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.command.SimpleCommand; +import com.velocitypowered.api.proxy.ProxyServer; +import java.time.Instant; +import net.civmc.announcements.AnnouncementsPlugin; +import net.civmc.announcements.BossBarManager; +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.text.Component; + +public class UpdateCommand implements SimpleCommand { + + private final ProxyServer server; + private final AnnouncementsPlugin plugin; + private final UpdateListener listener; + private final BossBarManager bossBarManager; + private final Component message; + + + private volatile Runnable remove; + + public UpdateCommand(ProxyServer server, AnnouncementsPlugin plugin, UpdateListener listener, BossBarManager bossBarManager, Component message) { + this.server = server; + this.plugin = plugin; + this.listener = listener; + this.bossBarManager = bossBarManager; + this.message = message; + } + + @Override + public void execute(Invocation invocation) { + CommandSource source = invocation.source(); + String[] args = invocation.arguments(); + if (args.length > 0 && args[0].equalsIgnoreCase("cancel")) { + if (remove != null) { + remove.run(); + } + listener.cancel(); + source.sendPlainMessage("Cancelled restart."); + return; + } + + if (args.length < 2) { + source.sendPlainMessage("Usage: /" + invocation.alias() + " "); + return; + } + + if (listener.isRestarting()) { + source.sendPlainMessage("An update is already scheduled."); + return; + } + + long timestamp; + try { + timestamp = Long.parseLong(args[0]); + } catch (NumberFormatException ex) { + source.sendPlainMessage("Invalid timestamp"); + return; + } + + + Instant start = Instant.now(); + if (timestamp <= start.getEpochSecond()) { + source.sendPlainMessage("Timestamp is in the past"); + return; + } + + Instant end = Instant.ofEpochSecond(timestamp); + + boolean block = Boolean.parseBoolean(args[1]); + + listener.setRestart(end, block); + BossBar bossBar = BossBar.bossBar(message, 1f, BossBar.Color.PINK, BossBar.Overlay.PROGRESS); + remove = bossBarManager.addBossBar(bossBar, message, end); + source.sendPlainMessage("Scheduled update"); + } + + @Override + public boolean hasPermission(Invocation invocation) { + return invocation.source().hasPermission("civannouncements.restart"); + } +} diff --git a/plugins/announcements-velocity/src/main/java/net/civmc/announcements/update/UpdateListener.java b/plugins/announcements-velocity/src/main/java/net/civmc/announcements/update/UpdateListener.java new file mode 100644 index 0000000000..beb87110a2 --- /dev/null +++ b/plugins/announcements-velocity/src/main/java/net/civmc/announcements/update/UpdateListener.java @@ -0,0 +1,80 @@ +package net.civmc.announcements.update; + +import com.velocitypowered.api.event.ResultedEvent; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.LoginEvent; +import java.time.Duration; +import java.time.Instant; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.scheduler.ScheduledTask; +import net.civmc.announcements.AnnouncementsPlugin; +import net.civmc.announcements.TimeComponent; +import net.kyori.adventure.text.Component; + +public class UpdateListener { + + private final ProxyServer server; + private final AnnouncementsPlugin plugin; + private final Component text; + private final Component kick; + + private volatile Instant restart; + private volatile boolean block; + + private volatile ScheduledTask task; + + public UpdateListener(ProxyServer proxy, AnnouncementsPlugin plugin, Component text, Component kick) { + this.server = proxy; + this.plugin = plugin; + this.text = text; + this.kick = kick; + } + + public void cancel() { + if (task != null) { + task.cancel(); + } + this.restart = null; + this.block = false; + } + + public boolean isRestarting() { + return this.restart != null; + } + + public void setRestart(Instant restart, boolean block) { + this.restart = restart; + this.block = block; + Duration duration = Duration.between(Instant.now(), restart); + if (this.block) { + task = server.getScheduler().buildTask(plugin, () -> { + for (Player player : server.getAllPlayers()) { + if (!player.hasPermission("civannouncements.bypass")) { + player.disconnect(kick); + } + } + }).delay(duration.plusSeconds(1)).schedule(); + } + for (Player player : server.getAllPlayers()) { + player.sendMessage(TimeComponent.replace(text, duration)); + } + } + + @Subscribe + public void on(LoginEvent event) { + if (restart == null) { + return; + } + Duration duration = Duration.between(Instant.now(), restart); + if (!duration.isNegative()) { + server.getScheduler().buildTask(plugin, + () -> event.getPlayer().sendMessage(TimeComponent.replace(text, duration))) + .delay(Duration.ofSeconds(3)).schedule(); + return; + } + if (block && !event.getPlayer().hasPermission("civannouncements.bypass")) { + event.setResult(ResultedEvent.ComponentResult.denied(kick)); + } + } +} diff --git a/plugins/announcements-velocity/src/main/resources/config.yml b/plugins/announcements-velocity/src/main/resources/config.yml new file mode 100644 index 0000000000..913683d71a --- /dev/null +++ b/plugins/announcements-velocity/src/main/resources/config.yml @@ -0,0 +1,9 @@ + +# messages must be in minimessage format +# https://webui.advntr.dev/ +scheduledAnnouncements: [] + +restart: + bar: "Update restart in % minute$" + message: "[CivMC] Server is restarting for an update in % minute$" + kick: "Server is restarting for an update" diff --git a/plugins/banstick-paper/build.gradle.kts b/plugins/banstick-paper/build.gradle.kts index 88fa3d1651..0f52f7dcc9 100644 --- a/plugins/banstick-paper/build.gradle.kts +++ b/plugins/banstick-paper/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - id("io.papermc.paperweight.userdev") - id("com.github.johnrengelman.shadow") + alias(libs.plugins.paper.userdev) + alias(libs.plugins.shadow) } version = "2.0.1" diff --git a/plugins/bastion-paper/build.gradle.kts b/plugins/bastion-paper/build.gradle.kts index 37c8ed89a5..360c479c3a 100644 --- a/plugins/bastion-paper/build.gradle.kts +++ b/plugins/bastion-paper/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("io.papermc.paperweight.userdev") + alias(libs.plugins.paper.userdev) } version = "3.0.1" diff --git a/plugins/bastion-paper/src/main/java/isaac/bastion/storage/BastionBlockStorage.java b/plugins/bastion-paper/src/main/java/isaac/bastion/storage/BastionBlockStorage.java index b4a41e92c9..110c3fc077 100644 --- a/plugins/bastion-paper/src/main/java/isaac/bastion/storage/BastionBlockStorage.java +++ b/plugins/bastion-paper/src/main/java/isaac/bastion/storage/BastionBlockStorage.java @@ -1,10 +1,13 @@ package isaac.bastion.storage; +import com.github.davidmoten.rtree2.Entry; +import com.github.davidmoten.rtree2.RTree; +import com.github.davidmoten.rtree2.geometry.Rectangle; +import com.github.davidmoten.rtree2.geometry.internal.PointDouble; import isaac.bastion.Bastion; import isaac.bastion.BastionBlock; import isaac.bastion.BastionType; import isaac.bastion.event.BastionCreateEvent; -import isaac.bastion.manager.EnderPearlManager; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -28,14 +31,13 @@ import vg.civcraft.mc.citadel.model.Reinforcement; import vg.civcraft.mc.civmodcore.dao.ManagedDatasource; import vg.civcraft.mc.civmodcore.world.locations.QTBox; -import vg.civcraft.mc.civmodcore.world.locations.SparseQuadTree; public class BastionBlockStorage { private ManagedDatasource db; private Logger log; - private Map> blocks; + private Map> blocks; private Set changed; private Set bastions; private Map> groups; @@ -222,7 +224,14 @@ public void updated(BastionBlock bastion) { * @return A set of QTBoxes (bastions) that overlap with the location */ public Set forLocation(Location loc) { - return blocks.get(loc.getWorld()).find(loc.getBlockX(), loc.getBlockZ()); + Iterable> search = blocks.get(loc.getWorld()).search(PointDouble.create(loc.getBlockX(), loc.getBlockZ())); + Set result = new HashSet<>(); + for (Entry qt : search) { + if (qt.value().inField(loc)) { + result.add(qt.value()); + } + } + return result; } /** @@ -233,25 +242,23 @@ public Set forLocation(Location loc) { * @return A set of bastions a pearl could collide with */ public Set getPossibleTeleportBlocking(Location loc, double maxDistance) { - Set boxes = blocks.get(loc.getWorld()).find(loc.getBlockX(), loc.getBlockY(), true); + Iterable> search = blocks.get(loc.getWorld()).search(PointDouble.create(loc.getBlockX(), loc.getBlockZ()), maxDistance); double maxDistanceSquared = maxDistance * maxDistance; double maxBoxDistanceSquared = maxDistanceSquared * 2.0; Set result = new TreeSet<>(); - for (QTBox box : boxes) { - if (box instanceof BastionBlock) { - BastionBlock bastion = (BastionBlock) box; - BastionType type = bastion.getType(); - // Skip bastions who don't do midair blocking. - if (!type.isBlockPearls() || !type.isBlockMidair()) continue; - // Check on other conditions. - if (((type.isSquare() && bastion.getLocation().distanceSquared(loc) <= maxBoxDistanceSquared) || - (!type.isSquare() && bastion.getLocation().distanceSquared(loc) <= maxDistanceSquared)) && - (!type.isRequireMaturity() || bastion.isMature())) { - result.add(bastion); - } + for (Entry entry : search) { + BastionBlock bastion = entry.value(); + BastionType type = bastion.getType(); + // Skip bastions who don't do midair blocking. + if (!type.isBlockPearls() || !type.isBlockMidair()) continue; + // Check on other conditions. + if (((type.isSquare() && bastion.getLocation().distanceSquared(loc) <= maxBoxDistanceSquared) || + (!type.isSquare() && bastion.getLocation().distanceSquared(loc) <= maxDistanceSquared)) && + (!type.isRequireMaturity() || bastion.isMature())) { + result.add(bastion); } } return result; @@ -265,28 +272,25 @@ public Set getPossibleTeleportBlocking(Location loc, double maxDis * @return A set of bastions a flying player could collide with */ public Set getPossibleFlightBlocking(double maxDistance, Location... locs) { - Set boxes = null; Set result = new TreeSet<>(); double maxDistanceSquared = maxDistance * maxDistance; double maxBoxDistanceSquared = maxDistanceSquared * 2.0; for (Location loc : locs) { - boxes = blocks.get(loc.getWorld()).find(loc.getBlockX(), loc.getBlockZ(), true); + Iterable> search = blocks.get(loc.getWorld()).search(PointDouble.create(loc.getBlockX(), loc.getBlockZ()), maxDistance); Location yLoc = loc.clone(); - for (QTBox box : boxes) { - if (box instanceof BastionBlock) { - BastionBlock bastion = (BastionBlock) box; - BastionType type = bastion.getType(); - // Don't add bastions that don't block flight - if (!type.isBlockElytra()) continue; - yLoc.setY(bastion.getLocation().getY()); - // Fixed for square field nearness, using diagonal distance as max -- (radius * sqrt(2)) ^ 2 - if (((type.isSquare() && bastion.getLocation().distanceSquared(yLoc) <= maxBoxDistanceSquared) || - (!type.isSquare() && bastion.getLocation().distanceSquared(yLoc) <= maxDistanceSquared)) && - (!type.isElytraRequireMature() || bastion.isMature())) { - result.add(bastion); - } + for (Entry entry : search) { + BastionBlock bastion = entry.value(); + BastionType type = bastion.getType(); + // Don't add bastions that don't block flight + if (!type.isBlockElytra()) continue; + yLoc.setY(bastion.getLocation().getY()); + // Fixed for square field nearness, using diagonal distance as max -- (radius * sqrt(2)) ^ 2 + if (((type.isSquare() && bastion.getLocation().distanceSquared(yLoc) <= maxBoxDistanceSquared) || + (!type.isSquare() && bastion.getLocation().distanceSquared(yLoc) <= maxDistanceSquared)) && + (!type.isElytraRequireMature() || bastion.isMature())) { + result.add(bastion); } } } @@ -333,10 +337,8 @@ public BastionType getTypeAtLocation(Location loc) { */ //@SuppressWarnings("deprecation") public void loadBastions() { - int enderSearchRadius = EnderPearlManager.MAX_TELEPORT + 100; for (World world : Bukkit.getWorlds()) { - SparseQuadTree bastionsForWorld = new SparseQuadTree<>(enderSearchRadius); - blocks.put(world, bastionsForWorld); + blocks.put(world, RTree.star().create()); try (Connection conn = db.getConnection(); PreparedStatement ps = conn.prepareStatement("select * from bastion_blocks where loc_world=?;")) { ps.setString(1, world.getName()); @@ -354,7 +356,7 @@ public void loadBastions() { if (died) { dead.put(loc, block.getType().getName()); } else { - addBastion(block, bastionsForWorld); + addBastion(block); } } } catch (SQLException e) { @@ -450,12 +452,8 @@ public void removeGroups(List groupIds) { } private void addBastion(BastionBlock bastion) { - addBastion(bastion, blocks.get(bastion.getLocation().getWorld())); - } - - private void addBastion(BastionBlock bastion, SparseQuadTree bastionsForWorld) { bastions.add(bastion); - bastionsForWorld.add(bastion); + blocks.put(bastion.getLocation().getWorld(), blocks.get(bastion.getLocation().getWorld()).add(bastion, bastion.asRectangle())); if (bastion.getListGroupId() != null) { synchronized (groups) { @@ -482,7 +480,7 @@ private void removeBastion(BastionBlock bastion) { } bastions.remove(bastion); - blocks.get(bastion.getLocation().getWorld()).remove(bastion); + blocks.put(bastion.getLocation().getWorld(), blocks.get(bastion.getLocation().getWorld()).delete(bastion, bastion.asRectangle())); } /** diff --git a/plugins/build.gradle.kts b/plugins/build.gradle.kts index 9b8b167c9b..df8ba83777 100644 --- a/plugins/build.gradle.kts +++ b/plugins/build.gradle.kts @@ -53,7 +53,7 @@ subprojects { create("maven") { from(components["java"]) } - pluginManager.withPlugin("com.github.johnrengelman.shadow") { + pluginManager.withPlugin("com.gradleup.shadow") { create("shadow") { from(components["java"]) } @@ -76,7 +76,7 @@ subprojects { } } - pluginManager.withPlugin("com.github.johnrengelman.shadow") { + pluginManager.withPlugin("com.gradleup.shadow") { tasks { named("build") { dependsOn("shadowJar") diff --git a/plugins/castlegates-paper/build.gradle.kts b/plugins/castlegates-paper/build.gradle.kts index 8c4d232981..d2c7cff825 100644 --- a/plugins/castlegates-paper/build.gradle.kts +++ b/plugins/castlegates-paper/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("io.papermc.paperweight.userdev") + alias(libs.plugins.paper.userdev) } version = "2.0.2" diff --git a/plugins/citadel-paper/build.gradle.kts b/plugins/citadel-paper/build.gradle.kts index 3035765c7b..96052f0ea0 100644 --- a/plugins/citadel-paper/build.gradle.kts +++ b/plugins/citadel-paper/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("io.papermc.paperweight.userdev") + alias(libs.plugins.paper.userdev) } version = "5.2.4" diff --git a/plugins/citadel-paper/src/main/java/vg/civcraft/mc/citadel/CitadelConfigManager.java b/plugins/citadel-paper/src/main/java/vg/civcraft/mc/citadel/CitadelConfigManager.java index adce71d307..ed6619d471 100644 --- a/plugins/citadel-paper/src/main/java/vg/civcraft/mc/citadel/CitadelConfigManager.java +++ b/plugins/citadel-paper/src/main/java/vg/civcraft/mc/citadel/CitadelConfigManager.java @@ -335,8 +335,9 @@ private void parseWorldBorderBuffers(ConfigurationSection config) { } double centerX = insideCenter.getDouble("x", 0.0); double centerZ = insideCenter.getDouble("z", 0.0); - logger.info("Parsed World Border Buffer zone for world " + world.getName() + " with radius " + worldBorderBufferSize + " in shape " + worldBorderShape + " centered at " + centerX + ", " + centerZ); - buffers.put(world.getUID(), new WorldBorderBuffers(centerX, centerZ, worldBorderShape, worldBorderBufferSize)); + boolean decay = insideWorld.getBoolean("decay", false); + logger.info("Parsed World Border Buffer zone for world " + world.getName() + " with radius " + worldBorderBufferSize + " in shape " + worldBorderShape + " centered at " + centerX + ", " + centerZ + ", decay: " + decay); + buffers.put(world.getUID(), new WorldBorderBuffers(centerX, centerZ, worldBorderShape, worldBorderBufferSize, decay)); } } diff --git a/plugins/citadel-paper/src/main/java/vg/civcraft/mc/citadel/ReinforcementLogic.java b/plugins/citadel-paper/src/main/java/vg/civcraft/mc/citadel/ReinforcementLogic.java index 11258e7348..662f157d32 100644 --- a/plugins/citadel-paper/src/main/java/vg/civcraft/mc/citadel/ReinforcementLogic.java +++ b/plugins/citadel-paper/src/main/java/vg/civcraft/mc/citadel/ReinforcementLogic.java @@ -1,7 +1,9 @@ package vg.civcraft.mc.citadel; import java.time.Instant; +import java.util.Map; import java.util.Objects; +import java.util.UUID; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; @@ -20,6 +22,7 @@ import vg.civcraft.mc.citadel.events.ReinforcementCreationEvent; import vg.civcraft.mc.citadel.events.ReinforcementDestructionEvent; import vg.civcraft.mc.citadel.model.Reinforcement; +import vg.civcraft.mc.citadel.model.WorldBorderBuffers; import vg.civcraft.mc.citadel.reinforcementtypes.ReinforcementType; import vg.civcraft.mc.civmodcore.world.WorldUtils; import vg.civcraft.mc.namelayer.group.Group; @@ -91,18 +94,37 @@ public static float getDamageApplied(Reinforcement reinforcement, Material type, } public static double getDecayDamage(Reinforcement reinforcement) { + double inactiveDecay; + if (reinforcement.getGroup() != null) { - //long lastRefresh = reinforcement.getGroup().getActivityTimeStamp(); ActivityMap map = Citadel.getInstance().getActivityMap(); - return map.getLastActivityTime(reinforcement.getGroup(), reinforcement.getLocation()) + inactiveDecay = map.getLastActivityTime(reinforcement.getGroup(), reinforcement.getLocation()) .map(Instant::toEpochMilli) .map(lastRefresh -> reinforcement.getType().getDecayDamageMultipler(lastRefresh)) .orElse(1d); } else { - return reinforcement.getType().getDeletedGroupMultiplier(); + inactiveDecay = reinforcement.getType().getDeletedGroupMultiplier(); } + + return inactiveDecay * getBufferDecayDamage(reinforcement); } + private static double getBufferDecayDamage(Reinforcement reinforcement) { + Location location = reinforcement.getLocation(); + WorldBorderBuffers buffer = Citadel.getInstance().getConfigManager().getWorldBorderBuffers() + .get(location.getWorld().getUID()); + if (!buffer.decay()) { + return 1; + } + + if (!buffer.checkIfOutside(location.getX(), location.getZ())) { + return 1; + } + + return reinforcement.getType().getDecayDamageMultipler(reinforcement.getCreationTime()); + } + + public static Reinforcement getReinforcementAt(Location location) { return Citadel.getInstance().getReinforcementManager().getReinforcement(location); } diff --git a/plugins/citadel-paper/src/main/java/vg/civcraft/mc/citadel/listener/WorldBorderListener.java b/plugins/citadel-paper/src/main/java/vg/civcraft/mc/citadel/listener/WorldBorderListener.java index d74b35b13f..7f166301ad 100644 --- a/plugins/citadel-paper/src/main/java/vg/civcraft/mc/citadel/listener/WorldBorderListener.java +++ b/plugins/citadel-paper/src/main/java/vg/civcraft/mc/citadel/listener/WorldBorderListener.java @@ -1,6 +1,8 @@ package vg.civcraft.mc.citadel.listener; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.UUID; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; @@ -15,6 +17,7 @@ public class WorldBorderListener implements Listener { private Map buffers; + private Set warned = new HashSet<>(); public WorldBorderListener() { this.buffers = Citadel.getInstance().getConfigManager().getWorldBorderBuffers(); @@ -28,7 +31,15 @@ public void onReinCreation(ReinforcementCreationEvent event) { } WorldBorderBuffers buffer = buffers.get(world.getUID()); Location reinforcementLocation = event.getReinforcement().getLocation(); - if (buffer.checkIfOutside(reinforcementLocation.getX(), reinforcementLocation.getZ())) { + if (!buffer.checkIfOutside(reinforcementLocation.getX(), reinforcementLocation.getZ())) { + return; + } + + if (buffer.decay()) { + if (warned.add(event.getPlayer().getUniqueId())) { + event.getPlayer().sendMessage(Component.text("Reinforcing this close to the border will cause your reinforcements to instantly start decaying").color(NamedTextColor.RED)); + } + } else { event.getPlayer().sendMessage(Component.text("You cannot reinforce this close to the border!").color(NamedTextColor.RED)); event.setCancelled(true); } diff --git a/plugins/citadel-paper/src/main/java/vg/civcraft/mc/citadel/model/ActivityDB.java b/plugins/citadel-paper/src/main/java/vg/civcraft/mc/citadel/model/ActivityDB.java index e28f1cd40d..8a2d4b9309 100644 --- a/plugins/citadel-paper/src/main/java/vg/civcraft/mc/citadel/model/ActivityDB.java +++ b/plugins/citadel-paper/src/main/java/vg/civcraft/mc/citadel/model/ActivityDB.java @@ -91,7 +91,7 @@ public void select(short worldId, int x, int z, int resolution, Consumer Component.empty() + .append(Component.text("<")) + .append(sourceDisplayName) + .append(Component.text("> ")) + .append(message); public final static String chatPlayerIsOffline = ChatColor.YELLOW + "That player is offline."; @@ -52,7 +57,5 @@ public class ChatStrings { public final static Component chatPlayerAfk = Component.text("That player is currently AFK.", NamedTextColor.AQUA); - public final static String chatGroupMessage = ChatColor.GRAY + "[%s] %s: " + ChatColor.WHITE + "%s"; - public final static String globalMuted = ChatColor.RED + "You are muted from global and local chat for %s"; } diff --git a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/CivChat2.java b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/CivChat2.java index 2854bface0..728d71ddf5 100644 --- a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/CivChat2.java +++ b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/CivChat2.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.bukkit.Bukkit; import vg.civcraft.mc.civchat2.broadcaster.BungeeServerBroadcaster; import vg.civcraft.mc.civchat2.broadcaster.BungeeServerListener; import vg.civcraft.mc.civchat2.broadcaster.NoopServerBroadcaster; @@ -12,6 +13,7 @@ import vg.civcraft.mc.civchat2.listeners.CivChat2Listener; import vg.civcraft.mc.civchat2.listeners.KillListener; import vg.civcraft.mc.civchat2.listeners.NewPlayerListener; +import vg.civcraft.mc.civchat2.prefix.StarManager; import vg.civcraft.mc.civchat2.utility.CivChat2Config; import vg.civcraft.mc.civchat2.utility.CivChat2FileLogger; import vg.civcraft.mc.civchat2.utility.CivChat2Log; @@ -57,11 +59,17 @@ public void onEnable() { broadcaster = new NoopServerBroadcaster(); } - chatMan = new CivChat2Manager(instance, broadcaster); + StarManager starManager = new StarManager(getConfig().getBoolean("chat.playtimeStars")); + + chatMan = new CivChat2Manager(instance, broadcaster, starManager); log.debug("Debug Enabled"); commandManager = new CivChatCommandManager(this); registerNameLayerPermissions(); registerCivChatEvents(); + + if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) { + new CivChatPlaceholders(starManager).register(); + } } @Override @@ -79,11 +87,12 @@ public ServerBroadcaster getBroadcaster() { public boolean debugEnabled() { return config.getDebug(); } - public CivChat2Log getCivChat2Log() { return log; } + + private void registerCivChatEvents() { getServer().getPluginManager().registerEvents(new CivChat2Listener(chatMan), this); getServer().getPluginManager().registerEvents(new KillListener(config, databaseManager, settingsManager), this); diff --git a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/CivChat2Manager.java b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/CivChat2Manager.java index 3cc11c70bd..af5fed5fbd 100644 --- a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/CivChat2Manager.java +++ b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/CivChat2Manager.java @@ -1,8 +1,10 @@ package vg.civcraft.mc.civchat2; import com.google.common.base.Preconditions; -import com.google.common.base.Strings; -import java.awt.Color; +import io.papermc.paper.chat.ChatRenderer; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -10,9 +12,11 @@ import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.concurrent.TimeUnit; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; -import net.md_5.bungee.api.chat.TextComponent; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Location; @@ -22,16 +26,19 @@ import vg.civcraft.mc.civchat2.event.GlobalChatEvent; import vg.civcraft.mc.civchat2.event.GroupChatEvent; import vg.civcraft.mc.civchat2.event.PrivateMessageEvent; +import vg.civcraft.mc.civchat2.prefix.StarManager; import vg.civcraft.mc.civchat2.utility.CivChat2Config; import vg.civcraft.mc.civchat2.utility.CivChat2FileLogger; import vg.civcraft.mc.civchat2.utility.ScoreboardHUD; import vg.civcraft.mc.civmodcore.chat.ChatUtils; +import vg.civcraft.mc.civmodcore.players.settings.impl.LongSetting; import vg.civcraft.mc.civmodcore.utilities.TextUtil; import vg.civcraft.mc.namelayer.GroupManager; import vg.civcraft.mc.namelayer.NameAPI; import vg.civcraft.mc.namelayer.group.Group; import vg.civcraft.mc.namelayer.permission.PermissionType; + public class CivChat2Manager { private CivChat2Config config; @@ -63,10 +70,22 @@ public class CivChat2Manager { private final ServerBroadcaster broadcaster; - public CivChat2Manager(CivChat2 pluginInstance, ServerBroadcaster broadcaster) { + private LongSetting banSetting; + + private String filterRelayGroup; + + private int muteTimeSeconds; + + private Group modsGroup; + + private final StarManager starManager; + + public CivChat2Manager(CivChat2 pluginInstance, ServerBroadcaster broadcaster, StarManager starManager) { + instance = pluginInstance; this.broadcaster = broadcaster; + this.starManager = starManager; config = instance.getPluginConfig(); chatLog = instance.getCivChat2FileLogger(); DBM = instance.getDatabaseManager(); @@ -76,8 +95,16 @@ public CivChat2Manager(CivChat2 pluginInstance, ServerBroadcaster broadcaster) { replyList = new HashMap<>(); afkPlayers = new HashMap<>(); scoreboardHUD = new ScoreboardHUD(); + bannedWords = loadBannedWords(); + muteTimeSeconds = config.getMuteTimeSeconds(); + banSetting = instance.getCivChat2SettingsManager().getGlobalChatMuteSetting(); + filterRelayGroup = config.getFilterRelayGroup(); } + + + + /** * Gets the channel for player to player chat * @@ -131,7 +158,7 @@ public void addChatChannel(Player player1, Player player2) { * @param receiver Player Receiving the message * @param chatMessage Message to send from sender to receive */ - public void sendPrivateMsg(Player sender, Player receiver, String chatMessage) { + public void sendPrivateMsg(Player sender, Player receiver, Component chatMessage) { PrivateMessageEvent event = new PrivateMessageEvent(sender, receiver, chatMessage); Bukkit.getPluginManager().callEvent(event); @@ -139,17 +166,14 @@ public void sendPrivateMsg(Player sender, Player receiver, String chatMessage) { if (event.isCancelled()) { return; } - StringBuilder sb = new StringBuilder(); - String senderName = customNames.containsKey(sender.getUniqueId()) ? customNames.get(sender.getUniqueId()) - : sender.getDisplayName(); - String receiverName = customNames.containsKey(receiver.getUniqueId()) ? customNames.get(receiver.getUniqueId()) - : receiver.getDisplayName(); - - String senderMessage = sb.append(ChatColor.LIGHT_PURPLE).append("To ").append(receiverName) - .append(ChatColor.LIGHT_PURPLE).append(": ").append(chatMessage).toString(); - sb = new StringBuilder(); - String receiverMessage = sb.append(ChatColor.LIGHT_PURPLE).append("From ").append(senderName) - .append(ChatColor.LIGHT_PURPLE).append(": ").append(chatMessage).toString(); + Component senderName = getCustomName(sender); + Component receiverName = getCustomName(receiver); + + Component receiverMessage = Component.empty().color(NamedTextColor.LIGHT_PURPLE) + .append(Component.text("From ")) + .append(senderName) + .append(Component.text(": ")) + .append(chatMessage); if (isPlayerAfk(receiver)) { receiver.sendMessage(receiverMessage); @@ -163,10 +187,15 @@ public void sendPrivateMsg(Player sender, Player receiver, String chatMessage) { sender.sendMessage(parse(ChatStrings.chatNeedToUnignore, receiverName)); return; } - chatLog.logPrivateMessage(sender, chatMessage, receiver.getName()); + chatLog.logPrivateMessage(sender, PlainTextComponentSerializer.plainText().serialize(chatMessage), receiver.getName()); replyList.put(receiver.getUniqueId(), sender.getUniqueId()); replyList.put(sender.getUniqueId(), receiver.getUniqueId()); - sender.sendMessage(senderMessage); + sender.sendMessage(Component.empty().color(NamedTextColor.LIGHT_PURPLE) + .append(Component.text("To ")) + .append(receiverName) + .append(Component.text(": ")) + .append(chatMessage) + ); receiver.sendMessage(receiverMessage); } @@ -177,20 +206,22 @@ public void sendPrivateMsg(Player sender, Player receiver, String chatMessage) { * @param chatMessage Message to send * @param recipients Players in range to receive the message */ - public void broadcastMessage(Player sender, String chatMessage, String messageFormat, Set recipients) { + public void broadcastMessage(Player sender, Component chatMessage, ChatRenderer messageFormat, Set recipients) { + + Preconditions.checkNotNull(sender, "sender"); Preconditions.checkNotNull(chatMessage, "chatMessage"); - Preconditions.checkArgument(!Strings.isNullOrEmpty(messageFormat), "messageFormat"); Preconditions.checkNotNull(recipients, "recipients"); - GlobalChatEvent event = new GlobalChatEvent(sender, chatMessage, messageFormat); + GlobalChatEvent event = new GlobalChatEvent(sender, chatMessage); Bukkit.getPluginManager().callEvent(event); if (event.isCancelled()) { return; } + long mutedUntil = instance.getCivChat2SettingsManager().getGlobalChatMuteSetting().getValue(sender); Group targetChatGroup = groupChatChannels.get(sender.getUniqueId()); if (mutedUntil > System.currentTimeMillis()) { @@ -199,6 +230,11 @@ public void broadcastMessage(Player sender, String chatMessage, String messageFo return; } } + // Chat filter check - block message if it contains banned words + if (containsBannedWord(PlainTextComponentSerializer.plainText().serialize(chatMessage)) && !sender.hasPermission("civchat2.globalmute")) { + flagMessage(sender, chatMessage); + return; + } int range = config.getChatRange(); int height = config.getYInc(); @@ -221,29 +257,62 @@ public void broadcastMessage(Player sender, String chatMessage, String messageFo if (range <= 0 || receiver.getWorld().equals(sender.getWorld())) { double receiverDistance = range <= 0 ? 0 : location.distance(receiver.getLocation()); if (receiverDistance <= range) { - net.md_5.bungee.api.ChatColor newColor; + TextColor newColor; if (config.useDynamicRangeColoring()) { int comp = (int) (255 - (128.0 * receiverDistance) / range); - newColor = net.md_5.bungee.api.ChatColor.of(new Color(comp, comp, comp)); + newColor = TextColor.color(comp, comp, comp); } else { - newColor = net.md_5.bungee.api.ChatColor.of(config.getColorAtDistance(receiverDistance)); + newColor = NamedTextColor.NAMES.valueOrThrow(config.getColorAtDistance(receiverDistance).toLowerCase()); } - newColor = newColor != null ? newColor : net.md_5.bungee.api.ChatColor.of(defaultColor); - - String senderName = customNames.containsKey(sender.getUniqueId()) - ? customNames.get(sender.getUniqueId()) - : sender.getDisplayName(); - TextComponent text = new TextComponent(String.format(messageFormat, senderName + ChatColor.RESET, "")); - TextComponent msgPart = new TextComponent(chatMessage); - msgPart.setColor(newColor); - receiver.spigot().sendMessage(text, msgPart); + + receiver.sendMessage(messageFormat.render(sender, getCustomName(sender), Component.empty().color(newColor).append(chatMessage), receiver)); receivers.add(receiver.getName()); } } } } receivers.remove(sender.getName()); - chatLog.logGlobalMessage(sender, chatMessage, receivers); + chatLog.logGlobalMessage(sender, PlainTextComponentSerializer.plainText().serialize(chatMessage), receivers); + } + + private Component getCustomName(Player sender) { + return Component.empty() + .append(Component.text(starManager.getPrefix(sender))) + .append(Component.text(customNames.containsKey(sender.getUniqueId()) ? customNames.get(sender.getUniqueId()) : sender.getDisplayName())) + .hoverEvent(starManager.hover(sender)); + } + + + /** + * Flags a message as inappropriate and mutes the sender + * + * @param sender The player who sent the message + * @param chatMessage The message content + */ + public void flagMessage(Player sender, Component chatMessage) { + //Flag inappropriate message, mute sender for X seconds (defined in config) + sender.sendMessage(ChatColor.RED + "Your message has been flagged for inappropriate content."); + if (muteTimeSeconds > 0) { + banSetting.setValue(sender, System.currentTimeMillis() + TimeUnit.SECONDS.toMillis((long)muteTimeSeconds)); // mute player automatically + } + + String plain = PlainTextComponentSerializer.plainText().serialize(chatMessage); + Group modsGroup = GroupManager.getGroup(filterRelayGroup); + if (modsGroup == null) { + instance.getLogger().warning(sender.getName() + " sent a filtered message: " + plain + " No filter relay group set, if this is unintentional please set filterRelayGroup in the config.yml"); + return; + } + + + // Log the filtered message to console and mods + Component senderName = getCustomName(sender); + + Component filtered = Component.text("[Filtered]: " + plain, NamedTextColor.RED); + Set modPlayers = doSendGroupMsg(sender.getUniqueId(), senderName, modsGroup, filtered); + + broadcaster.broadcastGroup(sender.getUniqueId(), sender.getDisplayName(), senderName, modsGroup.getName(), filtered); + + chatLog.logGroupMessage(sender, plain, modsGroup.getName(), modPlayers); } /** @@ -345,6 +414,41 @@ public void addGroupChat(Player player, Group group) { scoreboardHUD.updateScoreboardHUD(player); } + + // Load banned words once when CivChat2Manager is created + private final Set bannedWords; + + private Set loadBannedWords() { + + Set words = new HashSet<>(); + try { + File file = new File(instance.getDataFolder(), "banned-words.txt"); + if (file.exists()) { + List lines = Files.readAllLines(file.toPath()); + for (String line : lines) { + String cleanWord = line.strip().toLowerCase(); + if (!cleanWord.isEmpty()) { + words.add(cleanWord); + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } + return words; + } + + private boolean containsBannedWord(String message) { + String lowerMessage = message.toLowerCase(); + for (String bannedWord : bannedWords) { + if (lowerMessage.contains(bannedWord)) { + return true; + } + } + return false; + } + + /** * Method to send a message to a group * @@ -352,11 +456,12 @@ public void addGroupChat(Player player, Group group) { * @param group Group to send the message too * @param message Message to send to the group */ - public void sendGroupMsg(Player sender, Group group, String message) { + public void sendGroupMsg(Player sender, Group group, Component message) { Preconditions.checkNotNull(sender, "sender"); Preconditions.checkNotNull(group, "group"); - Preconditions.checkArgument(!Strings.isNullOrEmpty(message), "message"); + + String plainMessage = PlainTextComponentSerializer.plainText().serialize(message); if (group.getName().equals(config.getGlobalChatGroupName())) { long mutedUntil = instance.getCivChat2SettingsManager().getGlobalChatMuteSetting().getValue(sender); @@ -364,31 +469,35 @@ public void sendGroupMsg(Player sender, Group group, String message) { sender.sendMessage(String.format(ChatStrings.globalMuted, TextUtil.formatDuration(mutedUntil - System.currentTimeMillis()))); return; } + if (containsBannedWord(plainMessage) && !sender.hasPermission("civchat2.globalmute")) { + flagMessage(sender, message); + return; + } } - GroupChatEvent event = new GroupChatEvent(sender, group.getName(), message); + + GroupChatEvent event = new GroupChatEvent(sender, group.getName(), plainMessage); Bukkit.getPluginManager().callEvent(event); if (event.isCancelled()) { return; } - String senderName = customNames.containsKey(sender.getUniqueId()) ? customNames.get(sender.getUniqueId()) - : sender.getDisplayName(); + Component senderName = getCustomName(sender); Set players = doSendGroupMsg(sender.getUniqueId(), senderName, group, message); - chatLog.logGroupMessage(sender, message, group.getName(), players); + chatLog.logGroupMessage(sender, plainMessage, group.getName(), players); broadcaster.broadcastGroup(sender.getUniqueId(), sender.getName(), senderName, group.getName(), message); } - public void sendRemoteGroupMsg(UUID senderId, String senderName, String senderDisplayName, String groupName, String message) { + public void sendRemoteGroupMsg(UUID senderId, String senderName, Component senderDisplayName, String groupName, Component message) { Group group = GroupManager.getGroup(groupName); if (group == null) { return; } Set players = doSendGroupMsg(senderId, senderDisplayName, group, message); - chatLog.logRemoteGroupMessage(senderName, message, group.getName(), players); + chatLog.logRemoteGroupMessage(senderName, PlainTextComponentSerializer.plainText().serialize(message), group.getName(), players); } - private Set doSendGroupMsg(UUID senderId, String senderName, Group group, String message) { + private Set doSendGroupMsg(UUID senderId, Component senderName, Group group, Component message) { List members = new ArrayList<>(); List membersUUID = group.getAllMembers(); for (UUID uuid : membersUUID) { @@ -400,8 +509,6 @@ private Set doSendGroupMsg(UUID senderId, String senderName, Group group } } - String formatted = parse(ChatStrings.chatGroupMessage, group.getName(), senderName, message); - for (Player receiver : members) { if (DBM.isIgnoringGroup(receiver.getUniqueId(), group.getName())) { continue; @@ -409,14 +516,28 @@ private Set doSendGroupMsg(UUID senderId, String senderName, Group group if (DBM.isIgnoringPlayer(receiver.getUniqueId(), senderId)) { continue; } - receiver.sendMessage(formatted); + + TextColor color = + CivChat2.getInstance().getCivChat2SettingsManager() + .showGroupColors(receiver.getUniqueId()) + ? group.getGroupColor() : NamedTextColor.GRAY; + + Component compMessage = Component.text("[", NamedTextColor.GRAY) + .append(Component.text(group.getName(), color)) + .append(Component.text("] ", NamedTextColor.GRAY)) + .append(senderName) + .append(Component.text(": ", NamedTextColor.GRAY)) + .append(Component.empty().color(NamedTextColor.WHITE).append(message)); + + receiver.sendMessage(compMessage); } Set players = new HashSet<>(); for (Player player : members) { - players.add(NameAPI.getCurrentName(player.getUniqueId())); + if (!senderId.equals(player.getUniqueId())) { + players.add(NameAPI.getCurrentName(player.getUniqueId())); + } } - players.remove(senderName); return players; } diff --git a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/CivChatPlaceholders.java b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/CivChatPlaceholders.java new file mode 100644 index 0000000000..c0ab8ae43d --- /dev/null +++ b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/CivChatPlaceholders.java @@ -0,0 +1,52 @@ +package vg.civcraft.mc.civchat2; + +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import vg.civcraft.mc.civchat2.prefix.StarManager; + +public class CivChatPlaceholders extends PlaceholderExpansion { + private final StarManager starManager; + + public CivChatPlaceholders(StarManager starManager) { + this.starManager = starManager; + } + + @Override + public @Nullable String onPlaceholderRequest(Player player, @NotNull String params) { + params = params.toLowerCase(); + + if (params.equalsIgnoreCase("prefix")) { + return starManager.getPrefix(player) + ChatColor.RESET; + } else { + return null; + } + } + + @Override + public @NotNull String getIdentifier() { + return "civchat"; + } + + @Override + public @NotNull String getAuthor() { + return "Okx"; + } + + @Override + public @NotNull String getVersion() { + return "1.0"; + } + + @Override + public boolean persist() { + return true; + } + + @Override + public boolean canRegister() { + return true; + } +} diff --git a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/broadcaster/BungeeServerBroadcaster.java b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/broadcaster/BungeeServerBroadcaster.java index 852bcfd913..567b9abada 100644 --- a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/broadcaster/BungeeServerBroadcaster.java +++ b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/broadcaster/BungeeServerBroadcaster.java @@ -2,6 +2,8 @@ import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import vg.civcraft.mc.civchat2.CivChat2; @@ -14,7 +16,7 @@ public class BungeeServerBroadcaster implements ServerBroadcaster { @Override - public void broadcastGroup(UUID senderId, String senderName, String senderDisplayName, String groupName, String chatMessage) { + public void broadcastGroup(UUID senderId, String senderName, Component senderDisplayName, String groupName, Component chatMessage) { Iterator it = Bukkit.getOnlinePlayers().iterator(); if (!it.hasNext()) { return; @@ -32,9 +34,9 @@ public void broadcastGroup(UUID senderId, String senderName, String senderDispla msgout.writeLong(System.currentTimeMillis()); msgout.writeUTF(senderId.toString()); msgout.writeUTF(senderName); - msgout.writeUTF(senderDisplayName); + msgout.writeUTF(GsonComponentSerializer.gson().serialize(senderDisplayName)); msgout.writeUTF(groupName); - msgout.writeUTF(chatMessage); + msgout.writeUTF(GsonComponentSerializer.gson().serialize(chatMessage)); } catch (IOException ex) { return; } diff --git a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/broadcaster/BungeeServerListener.java b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/broadcaster/BungeeServerListener.java index a0bf70b3a9..1a18397430 100644 --- a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/broadcaster/BungeeServerListener.java +++ b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/broadcaster/BungeeServerListener.java @@ -2,6 +2,7 @@ import com.google.common.io.ByteArrayDataInput; import com.google.common.io.ByteStreams; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import org.bukkit.entity.Player; import org.bukkit.plugin.messaging.PluginMessageListener; import org.jetbrains.annotations.NotNull; @@ -40,7 +41,7 @@ public void onPluginMessageReceived(@NotNull String channel, @NotNull Player pla if (System.currentTimeMillis() - timestamp > 5000) { return; } - manager.sendRemoteGroupMsg(UUID.fromString(msgIn.readUTF()), msgIn.readUTF(), msgIn.readUTF(), msgIn.readUTF(), msgIn.readUTF()); + manager.sendRemoteGroupMsg(UUID.fromString(msgIn.readUTF()), msgIn.readUTF(), GsonComponentSerializer.gson().deserialize(msgIn.readUTF()), msgIn.readUTF(), GsonComponentSerializer.gson().deserialize(msgIn.readUTF())); } else { CivChat2.getInstance().getLogger().log(Level.WARNING, "Unknown type '" + type + "' in CivChat2 plugin message"); } diff --git a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/broadcaster/NoopServerBroadcaster.java b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/broadcaster/NoopServerBroadcaster.java index d557218437..18b2acdf89 100644 --- a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/broadcaster/NoopServerBroadcaster.java +++ b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/broadcaster/NoopServerBroadcaster.java @@ -1,11 +1,12 @@ package vg.civcraft.mc.civchat2.broadcaster; +import net.kyori.adventure.text.Component; import java.util.UUID; public class NoopServerBroadcaster implements ServerBroadcaster { @Override - public void broadcastGroup(UUID senderId, String senderName, String senderDisplayName, String groupName, String chatMessage) { + public void broadcastGroup(UUID senderId, String senderName, Component senderDisplayName, String groupName, Component chatMessage) { } } diff --git a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/broadcaster/ServerBroadcaster.java b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/broadcaster/ServerBroadcaster.java index dfdadffcd6..90b5ed8601 100644 --- a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/broadcaster/ServerBroadcaster.java +++ b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/broadcaster/ServerBroadcaster.java @@ -1,7 +1,8 @@ package vg.civcraft.mc.civchat2.broadcaster; +import net.kyori.adventure.text.Component; import java.util.UUID; public interface ServerBroadcaster { - void broadcastGroup(UUID senderId, String senderName, String senderDisplayName, String groupName, String chatMessage); + void broadcastGroup(UUID senderId, String senderName, Component senderDisplayName, String groupName, Component chatMessage); } diff --git a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/commands/Exit.java b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/commands/Exit.java index 1b82113504..73ba804aea 100644 --- a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/commands/Exit.java +++ b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/commands/Exit.java @@ -9,6 +9,7 @@ import co.aikar.commands.annotation.Optional; import java.util.HashSet; import java.util.Set; +import net.kyori.adventure.text.Component; import org.bukkit.entity.Player; import vg.civcraft.mc.civchat2.ChatStrings; import vg.civcraft.mc.civchat2.CivChat2; @@ -26,9 +27,7 @@ public void execute(Player player, @Optional String message) { player.sendMessage(ChatStrings.chatMovedToGlobal); return; } - StringBuilder chatMsg = new StringBuilder(); - chatMsg.append(message); Set players = new HashSet<>(CivChat2.getInstance().getServer().getOnlinePlayers()); - chatMan.broadcastMessage(player, chatMsg.toString(), localChatFormat, players); + chatMan.broadcastMessage(player, Component.text(message), localChatFormat, players); } } diff --git a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/commands/GlobalChat.java b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/commands/GlobalChat.java index 87cb654fe0..0440f2ebc9 100644 --- a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/commands/GlobalChat.java +++ b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/commands/GlobalChat.java @@ -2,10 +2,10 @@ import co.aikar.commands.BaseCommand; import co.aikar.commands.annotation.CommandAlias; -import co.aikar.commands.annotation.CommandCompletion; import co.aikar.commands.annotation.Description; import co.aikar.commands.annotation.Optional; import co.aikar.commands.annotation.Syntax; +import net.kyori.adventure.text.Component; import org.bukkit.entity.Player; import vg.civcraft.mc.civchat2.ChatStrings; import vg.civcraft.mc.civchat2.CivChat2; @@ -60,12 +60,12 @@ public void execute(Player player, @Optional String chatMessage) { } } else { // Send message to global group if (currentGroup != null) { - chatMan.sendGroupMsg(player, globalGroup, chatMessage); + chatMan.sendGroupMsg(player, globalGroup, Component.text(chatMessage)); } else { if (chatMan.getChannel(player) != null) { chatMan.removeChannel(player); } - chatMan.sendGroupMsg(player, globalGroup, chatMessage); + chatMan.sendGroupMsg(player, globalGroup, Component.text(chatMessage)); } } } diff --git a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/commands/GroupChat.java b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/commands/GroupChat.java index 8ba9b54989..362807ebea 100644 --- a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/commands/GroupChat.java +++ b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/commands/GroupChat.java @@ -6,6 +6,7 @@ import co.aikar.commands.annotation.Description; import co.aikar.commands.annotation.Optional; import co.aikar.commands.annotation.Syntax; +import net.kyori.adventure.text.Component; import org.bukkit.entity.Player; import vg.civcraft.mc.civchat2.ChatStrings; import vg.civcraft.mc.civchat2.CivChat2; @@ -69,21 +70,19 @@ public void execute(Player player, @Optional String targetGroup, @Optional Strin chatMan.addGroupChat(player, group); } } else { - StringBuilder chatMsg = new StringBuilder(); - chatMsg.append(chatMessage); if (isGroupChatting) { // Player already groupchatting check if it's this group Group curGroup = chatMan.getGroupChatting(player); if (curGroup == group) { - chatMan.sendGroupMsg(player, group, chatMsg.toString()); + chatMan.sendGroupMsg(player, group, Component.text(chatMessage)); } else { - chatMan.sendGroupMsg(player, group, chatMsg.toString()); + chatMan.sendGroupMsg(player, group, Component.text(chatMessage)); } } else { if (chatMan.getChannel(player) != null) { chatMan.removeChannel(player); } - chatMan.sendGroupMsg(player, group, chatMsg.toString()); + chatMan.sendGroupMsg(player, group, Component.text(chatMessage)); } } } diff --git a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/commands/LocalChat.java b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/commands/LocalChat.java index 67cb31d342..e23a59b171 100644 --- a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/commands/LocalChat.java +++ b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/commands/LocalChat.java @@ -8,6 +8,7 @@ import co.aikar.commands.annotation.Optional; import java.util.HashSet; import java.util.Set; +import net.kyori.adventure.text.Component; import org.bukkit.entity.Player; import vg.civcraft.mc.civchat2.ChatStrings; import vg.civcraft.mc.civchat2.CivChat2; @@ -28,6 +29,6 @@ public void execute(Player player, @Optional String chatMessage) { player.sendMessage(ChatStrings.chatMovedToGlobal); return; } - chatMan.broadcastMessage(player, chatMessage, localChatFormat, players); + chatMan.broadcastMessage(player, Component.text(chatMessage), localChatFormat, players); } } diff --git a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/commands/Reply.java b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/commands/Reply.java index 75f4d44c98..429ee93aae 100644 --- a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/commands/Reply.java +++ b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/commands/Reply.java @@ -6,6 +6,7 @@ import co.aikar.commands.annotation.Optional; import co.aikar.commands.annotation.Syntax; import java.util.UUID; +import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import vg.civcraft.mc.civchat2.ChatStrings; @@ -38,7 +39,7 @@ public void execute(Player player, @Optional String chatMessage) { } if (!(chatMessage == null)) { - chatMan.sendPrivateMsg(player, receiver, chatMessage); + chatMan.sendPrivateMsg(player, receiver, Component.text(chatMessage)); return; } // Player to chat with reply user diff --git a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/commands/Tell.java b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/commands/Tell.java index eca9004d9c..6b161a9dda 100644 --- a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/commands/Tell.java +++ b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/commands/Tell.java @@ -7,6 +7,7 @@ import co.aikar.commands.annotation.Optional; import co.aikar.commands.annotation.Syntax; import java.util.UUID; +import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import vg.civcraft.mc.civchat2.ChatStrings; @@ -51,7 +52,7 @@ public void execute(Player player, @Optional String targetPlayer, @Optional Stri } if (!(chatMessage == null)) { - chatMan.sendPrivateMsg(player, receiver, chatMessage); + chatMan.sendPrivateMsg(player, receiver, Component.text(chatMessage)); return; } else { CivChatDAO db = CivChat2.getInstance().getDatabaseManager(); diff --git a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/database/CivChatDAO.java b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/database/CivChatDAO.java index 7c806a90f0..3be2707a6b 100644 --- a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/database/CivChatDAO.java +++ b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/database/CivChatDAO.java @@ -249,7 +249,7 @@ public List getIgnoredGroups(UUID playerUUID) { getIgnoredGroups.setString(1, playerUUID.toString()); ResultSet rs = getIgnoredGroups.executeQuery(); while (rs.next()) { - ignoredGroupsList.add(rs.getString("group")); + ignoredGroupsList.add(rs.getString("ignoredGroup")); } ignoredGroups.put(playerUUID, ignoredGroupsList); return ignoredGroupsList; diff --git a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/event/GlobalChatEvent.java b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/event/GlobalChatEvent.java index 12a59794ce..4adb6cd9c4 100644 --- a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/event/GlobalChatEvent.java +++ b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/event/GlobalChatEvent.java @@ -1,5 +1,6 @@ package vg.civcraft.mc.civchat2.event; +import net.kyori.adventure.text.Component; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; @@ -7,21 +8,18 @@ public class GlobalChatEvent extends PlayerEvent implements Cancellable { - private final String message; - - private final String format; + private final Component message; private boolean cancelled; // Handler list for spigot events private static final HandlerList handlers = new HandlerList(); - public GlobalChatEvent(final Player player, final String message, final String format) { + public GlobalChatEvent(final Player player, final Component message) { super(player); this.message = message; - this.format = format; } /** @@ -29,7 +27,7 @@ public GlobalChatEvent(final Player player, final String message, final String f * * @return The chat message */ - public String getMessage() { + public Component getMessage() { return message; } @@ -56,14 +54,4 @@ public void setCancelled(boolean cancelled) { this.cancelled = cancelled; } - - /** - * Gets the format to use to display this chat message. - * - * @return The message format - */ - public String getFormat() { - - return format; - } } diff --git a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/event/PrivateMessageEvent.java b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/event/PrivateMessageEvent.java index 78084d23b8..248d24c2ab 100644 --- a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/event/PrivateMessageEvent.java +++ b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/event/PrivateMessageEvent.java @@ -1,5 +1,6 @@ package vg.civcraft.mc.civchat2.event; +import net.kyori.adventure.text.Component; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.HandlerList; @@ -9,14 +10,14 @@ public class PrivateMessageEvent extends PlayerEvent implements Cancellable { private final Player receiver; - private final String message; + private final Component message; private boolean cancelled; // Handler list for spigot events private static final HandlerList handlers = new HandlerList(); - public PrivateMessageEvent(final Player player, final Player receiver, final String message) { + public PrivateMessageEvent(final Player player, final Player receiver, final Component message) { super(player); this.receiver = receiver; @@ -38,7 +39,7 @@ public Player getReceiver() { * * @return The chat message */ - public String getMessage() { + public Component getMessage() { return message; } diff --git a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/listeners/CivChat2Listener.java b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/listeners/CivChat2Listener.java index f58fa250a8..94c90a8bcd 100644 --- a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/listeners/CivChat2Listener.java +++ b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/listeners/CivChat2Listener.java @@ -1,5 +1,10 @@ package vg.civcraft.mc.civchat2.listeners; +import io.papermc.paper.event.player.AsyncChatEvent; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; @@ -9,11 +14,11 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.entity.PlayerDeathEvent; -import org.bukkit.event.player.AsyncPlayerChatEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerKickEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.scheduler.BukkitRunnable; +import vg.civcraft.mc.civchat2.ChatStrings; import vg.civcraft.mc.civchat2.CivChat2; import vg.civcraft.mc.civchat2.CivChat2Manager; import vg.civcraft.mc.civchat2.database.CivChatDAO; @@ -25,10 +30,6 @@ import vg.civcraft.mc.namelayer.group.Group; import vg.civcraft.mc.namelayer.permission.PermissionType; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; - /* * @author jjj5311 * @@ -119,7 +120,7 @@ public void onGlobalChatEvent(GlobalChatEvent localchat) { } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onPlayerChatEvent(final AsyncPlayerChatEvent asyncPlayerChatEvent) { + public void onPlayerChatEvent(final AsyncChatEvent asyncPlayerChatEvent) { asyncPlayerChatEvent.setCancelled(true); // This needs to be done sync to avoid a rare deadlock due to minecraft @@ -129,7 +130,7 @@ public void onPlayerChatEvent(final AsyncPlayerChatEvent asyncPlayerChatEvent) { @Override public void run() { - String chatMessage = asyncPlayerChatEvent.getMessage(); + Component chatMessage = asyncPlayerChatEvent.message(); Player sender = asyncPlayerChatEvent.getPlayer(); UUID chatChannel = chatman.getChannel(sender); Group groupChat = chatman.getGroupChatting(sender); @@ -164,8 +165,13 @@ public void run() { + "You have been removed from groupchat because you were removed from the group or lost the permission required to groupchat"); } } - chatman.broadcastMessage(sender, chatMessage, asyncPlayerChatEvent.getFormat(), - asyncPlayerChatEvent.getRecipients()); + Set playerViewers = new HashSet<>(); + for (Audience viewer : asyncPlayerChatEvent.viewers()) { + if (viewer instanceof Player playerViewer) { + playerViewers.add(playerViewer); + } + } + chatman.broadcastMessage(sender, chatMessage, ChatStrings.localChatFormat, playerViewers); } }.runTask(CivChat2.getInstance()); } diff --git a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/prefix/StarManager.java b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/prefix/StarManager.java new file mode 100644 index 0000000000..66b5cc3919 --- /dev/null +++ b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/prefix/StarManager.java @@ -0,0 +1,107 @@ +package vg.civcraft.mc.civchat2.prefix; + +import com.programmerdan.minecraft.banstick.data.BSPlayer; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.event.HoverEventSource; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import vg.civcraft.mc.civchat2.CivChat2; + +public class StarManager { + + private static final String STAR = "⋆"; + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("d MMM uuuu"); + + private final boolean playtimeStars; + + public StarManager(boolean playtimeStars) { + this.playtimeStars = playtimeStars; + } + + private long getJoined(Player player) { + if (!Bukkit.getPluginManager().isPluginEnabled("BanStick")) { + return player.getFirstPlayed(); + } + + BSPlayer bsPlayer = BSPlayer.byUUID(player.getUniqueId()); + if (bsPlayer == null) { + return 0; + } + return bsPlayer.getFirstAdd().toInstant().toEpochMilli(); + } + + public HoverEventSource hover(Player player) { + long firstPlayed = getJoined(player); + String joined = firstPlayed == 0 ? "unknown" : FORMATTER.format(LocalDateTime.ofInstant(Instant.ofEpochMilli(firstPlayed), ZoneId.systemDefault())); + + String rank; + if (player.hasPermission("civchat.admin")) { + rank = "Admin"; + } else if (player.hasPermission("civchat.superfriend")) { + rank = "Superfriend"; + } else if (player.hasPermission("civchat.mod")) { + rank = "Moderator"; + } else if (player.hasPermission("civchat.powerplayer")) { + rank = "Civ Power Player"; + } else if (player.hasPermission("civchat.grinder")) { + rank = "Civ Grinder"; + } else if (player.hasPermission("civchat.bigdonator")) { + rank = "Loyal Patron"; + } else if (player.hasPermission("civchat.donator")) { + rank = "Namecolor Patron"; + } else { + rank = "None"; + } + + return HoverEvent.showText(Component.text("Rank: " + rank + "\nJoined: " + joined)); + } + + public String getPrefix(Player player) { + if (!CivChat2.getInstance().getCivChat2SettingsManager().isShowPrefix(player.getUniqueId())) { + return ""; + } + + if (player.hasPermission("civchat.admin")) { + return ChatColor.DARK_RED + STAR; + } else if (player.hasPermission("civchat.superfriend")) { + return ChatColor.RED + STAR + STAR; + } else if (player.hasPermission("civchat.mod")) { + return ChatColor.RED + STAR; + } + + int greenStars = 0; + if (player.hasPermission("civchat.powerplayer")) { + greenStars = 4; + } else if (player.hasPermission("civchat.grinder")) { + greenStars = 3; + } else if (player.hasPermission("civchat.bigdonator")) { + greenStars = 2; + } else if (player.hasPermission("civchat.donator")) { + greenStars = 1; + } + + StringBuilder stars = new StringBuilder(); + if (playtimeStars) { + long firstPlayed = getJoined(player); + int yellowStars = firstPlayed == 0 ? 0 : (int) LocalDateTime.ofInstant(Instant.ofEpochMilli(firstPlayed), ZoneId.systemDefault()).until(LocalDateTime.now(), ChronoUnit.YEARS); + yellowStars = Math.max(0, yellowStars - greenStars); + if (yellowStars > 0) { + stars.append(ChatColor.YELLOW); + } + stars.append(STAR.repeat(yellowStars)); + } + if (greenStars > 0) { + stars.append(ChatColor.GREEN); + } + stars.append(STAR.repeat(greenStars)); + + return stars.toString(); + } +} diff --git a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/utility/CivChat2Config.java b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/utility/CivChat2Config.java index 15c67e2b75..6e269584bd 100644 --- a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/utility/CivChat2Config.java +++ b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/utility/CivChat2Config.java @@ -94,6 +94,21 @@ public boolean useDynamicRangeColoring() { return config.getBoolean("chat.dynamicColoring"); } + private String filterRelayGroup; + + public String getFilterRelayGroup() { + return config.getString("info.filterRelayGroup"); + } + + private int muteTimeSeconds; + + public int getMuteTimeSeconds() { + return config.getInt("info.muteTimeSeconds"); + } + + + + public synchronized String getColorAtDistance(double distance) { if (chatColor == null) { diff --git a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/utility/CivChat2SettingsManager.java b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/utility/CivChat2SettingsManager.java index d38a8c14f3..42c67b3c17 100644 --- a/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/utility/CivChat2SettingsManager.java +++ b/plugins/civchat2-paper/src/main/java/vg/civcraft/mc/civchat2/utility/CivChat2SettingsManager.java @@ -23,7 +23,9 @@ public class CivChat2SettingsManager { private LongSetting chatUnmuteTimer; private EnumSetting killMessageFormat; private BooleanSetting showAFKStatus; + private BooleanSetting showPrefix; private DisplayLocationSetting afkStatusLocation; + private BooleanSetting showGroupColors; public CivChat2SettingsManager() { initSettings(); @@ -71,6 +73,13 @@ private void initSettings() { afkStatusLocation = new DisplayLocationSetting(CivChat2.getInstance(), DisplayLocationSetting.DisplayLocation.SIDEBAR, "AFK Status Location", "afkStatusLocation", new ItemStack(Material.ARROW), "the AFK status"); PlayerSettingAPI.registerSetting(afkStatusLocation, menu); + + showPrefix = new BooleanSetting(CivChat2.getInstance(), true, "Show vanity prefix", "showPrefix", + "Should the star prefix be shown?"); + PlayerSettingAPI.registerSetting(showPrefix, menu); + showGroupColors = new BooleanSetting(CivChat2.getInstance(), true, "Show group colors in chat", "showGroupColors", + "Disabling this setting will make all group colors in chat, gray"); + PlayerSettingAPI.registerSetting(showGroupColors, menu); } public LongSetting getGlobalChatMuteSetting() { @@ -111,6 +120,14 @@ public KillMessageFormat getKillMessageFormat(UUID uuid) { public boolean getShowAFKStatus(UUID uuid) {return showAFKStatus.getValue(uuid); } + public boolean isShowPrefix(UUID uuid) { + return showPrefix.getValue(uuid); + } + + public boolean showGroupColors(UUID uuid) { + return showGroupColors.getValue(uuid); + } + public DisplayLocationSetting getAfkStatusLocation() { return afkStatusLocation; } public enum KillMessageFormat { diff --git a/plugins/civchat2-paper/src/main/resources/config.yml b/plugins/civchat2-paper/src/main/resources/config.yml index 32d0615a63..b5f2e9877d 100644 --- a/plugins/civchat2-paper/src/main/resources/config.yml +++ b/plugins/civchat2-paper/src/main/resources/config.yml @@ -22,7 +22,12 @@ chat: color: GRAY globalGroup: '!' joinGlobalGroupByDefault: false + playtimeStars: true info: + ##FilterRelayGroup: Namelayer to send filtered messages from ! and local to for moderation - set to null to disable + filterRelayGroup: "Mods" + #muteTimeSeconds: How long to automute someone for sending a message with a banned word - 0 to disable + muteTimeSeconds: 3600 #debug: lots of debugging logging will show up in the console def=false debug: false #groups: if NameLayer groups is enabled def=true diff --git a/plugins/civchat2-paper/src/main/resources/plugin.yml b/plugins/civchat2-paper/src/main/resources/plugin.yml index 616e643e61..1c6d77b825 100644 --- a/plugins/civchat2-paper/src/main/resources/plugin.yml +++ b/plugins/civchat2-paper/src/main/resources/plugin.yml @@ -2,6 +2,7 @@ name: CivChat2 main: vg.civcraft.mc.civchat2.CivChat2 version: ${version} api-version: 1.21.3 +softdepend: [PlaceholderAPI, BanStick] authors: - jjj5311 - Maxopoly diff --git a/plugins/civduties-paper/build.gradle.kts b/plugins/civduties-paper/build.gradle.kts index 2263e7fb0f..56031b19fc 100644 --- a/plugins/civduties-paper/build.gradle.kts +++ b/plugins/civduties-paper/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("io.papermc.paperweight.userdev") + alias(libs.plugins.paper.userdev) } version = "1.5.0-SNAPSHOT" diff --git a/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/ModeManager.java b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/ModeManager.java index 697c982a05..868e18d47a 100644 --- a/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/ModeManager.java +++ b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/ModeManager.java @@ -4,6 +4,10 @@ import java.util.logging.Level; import java.util.logging.Logger; import net.minecraft.nbt.CompoundTag; +import net.minecraft.util.ProblemReporter; +import net.minecraft.world.level.storage.TagValueInput; +import net.minecraft.world.level.storage.TagValueOutput; +import net.minecraft.world.level.storage.ValueInput; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.GameMode; @@ -15,7 +19,7 @@ import vg.civcraft.mc.civduties.configuration.Tier; import vg.civcraft.mc.civduties.database.DatabaseManager; import vg.civcraft.mc.civduties.external.VaultManager; -import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; +import vg.civcraft.mc.civmodcore.nbt.NbtCompound; public class ModeManager { @@ -41,12 +45,11 @@ public boolean isInDuty(Player player) { } public boolean enableDutyMode(Player player, Tier tier) { - CompoundTag nmsCompound = new CompoundTag(); + TagValueOutput nmsCompound = TagValueOutput.createWithContext(ProblemReporter.DISCARDING, ((CraftPlayer) player).getHandle().registryAccess()); CraftPlayer cPlayer = (CraftPlayer) player; cPlayer.getHandle().saveWithoutId(nmsCompound); - NBTCompound compound = new NBTCompound(nmsCompound); String serverName = Bukkit.getServer().getName(); - db.savePlayerData(player.getUniqueId(), compound.getRAW(), serverName, tier.getName()); + db.savePlayerData(player.getUniqueId(), nmsCompound.buildResult(), serverName, tier.getName()); vaultManager.addPermissionsToPlayer(player, tier.getTemporaryPermissions()); vaultManager.addPlayerToGroups(player, tier.getTemporaryGroups()); @@ -66,21 +69,21 @@ public boolean disableDutyMode(Player player, Tier tier) { if (!isInDuty(player)) { return false; } - NBTCompound input = new NBTCompound(db.getPlayerData(player.getUniqueId()).getData()); + NbtCompound input = new NbtCompound(db.getPlayerData(player.getUniqueId()).getData()); // Inform the client the gamemode was changed to fix graphical issues on the // client side // Teleport the players using the bukkit api to avoid triggering nocheat // movement detection - double[] location = input.getDoubleArray("Pos"); - UUID worldUUID = new UUID(input.getLong("WorldUUIDMost"), input.getLong("WorldUUIDLeast")); + double[] location = input.getDoubleArray("Pos", true); + UUID worldUUID = new UUID(input.getLong("WorldUUIDMost", 0L), input.getLong("WorldUUIDLeast", 0L)); Location targetLocation = new Location(Bukkit.getWorld(worldUUID), location[0], location[1], location[2]); player.teleport(targetLocation); - player.setGameMode(getGameModeByValue(input.getInt("playerGameType"))); + player.setGameMode(getGameModeByValue(input.getInt("playerGameType", 0))); Bukkit.getScheduler().scheduleSyncDelayedTask(CivDuties.getInstance(), () -> { player.teleport(targetLocation); }, 3L); CraftPlayer cPlayer = (CraftPlayer) player; - cPlayer.getHandle().load(input.getRAW()); + cPlayer.getHandle().load(TagValueInput.createGlobal(ProblemReporter.DISCARDING, input.internal())); db.removePlayerData(player.getUniqueId()); diff --git a/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/database/DatabaseManager.java b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/database/DatabaseManager.java index df2c0e3e41..f0f383f360 100644 --- a/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/database/DatabaseManager.java +++ b/plugins/civduties-paper/src/main/java/vg/civcraft/mc/civduties/database/DatabaseManager.java @@ -11,7 +11,7 @@ import net.minecraft.nbt.CompoundTag; import vg.civcraft.mc.civduties.CivDuties; import vg.civcraft.mc.civmodcore.dao.ManagedDatasource; -import vg.civcraft.mc.civmodcore.nbt.NBTSerialization; +import vg.civcraft.mc.civmodcore.nbt.NbtUtils; public class DatabaseManager { @@ -38,7 +38,7 @@ public void savePlayerData(UUID uuid, CompoundTag compound, String serverName, S PreparedStatement addPlayerData = conn.prepareStatement( "insert into DutiesPlayerData(uuid, entity, serverName, tierName) values(?,?,?,?);")) { addPlayerData.setString(1, uuid.toString()); - addPlayerData.setBytes(2, NBTSerialization.toBytes(compound)); + addPlayerData.setBytes(2, NbtUtils.toBytes(compound)); addPlayerData.setString(3, serverName); addPlayerData.setString(4, tierName); addPlayerData.execute(); @@ -57,7 +57,7 @@ public PlayerData getPlayerData(UUID uuid) { getPlayerData.setString(1, uuid.toString()); try (ResultSet rs = getPlayerData.executeQuery()) { if (rs.next()) { - CompoundTag compound = NBTSerialization.fromBytes(rs.getBytes("entity")); + CompoundTag compound = NbtUtils.fromBytes(rs.getBytes("entity")); String server = rs.getString("serverName"); String tierName = rs.getString("tierName"); PlayerData data = new PlayerData(compound, server, tierName); diff --git a/plugins/civmodcore-paper/build.gradle.kts b/plugins/civmodcore-paper/build.gradle.kts index f25a632deb..5e6859b531 100644 --- a/plugins/civmodcore-paper/build.gradle.kts +++ b/plugins/civmodcore-paper/build.gradle.kts @@ -1,7 +1,7 @@ plugins { - id("io.papermc.paperweight.userdev") - id("com.github.johnrengelman.shadow") - id("xyz.jpenilla.run-paper") + alias(libs.plugins.paper.userdev) + alias(libs.plugins.shadow) + alias(libs.plugins.runpaper) } version = "3.0.6" @@ -11,6 +11,7 @@ dependencies { paperDevBundle(libs.versions.paper) } + api("com.github.davidmoten:rtree2:0.9.3") api(libs.aikar.acf) api(libs.aikar.taskchain) api(libs.hikaricp) diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/ACivMod.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/ACivMod.java index 5ed648a5ec..7bb32ac36b 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/ACivMod.java +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/ACivMod.java @@ -20,20 +20,10 @@ public abstract class ACivMod extends JavaPlugin { private final Set> configClasses = new HashSet<>(0); - /** - * Primary constructor used by the real server - */ protected ACivMod() { super(); } - /** - * Secondary constructor used for testing - */ - protected ACivMod(JavaPluginLoader loader, PluginDescriptionFile description, File dataFolder, File file) { - super(loader, description, dataFolder, file); - } - @Override public void onEnable() { // Self disable when a hard dependency is disabled diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/CivModCorePlugin.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/CivModCorePlugin.java index 7a171ab3ef..206ebc4f49 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/CivModCorePlugin.java +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/CivModCorePlugin.java @@ -1,12 +1,8 @@ package vg.civcraft.mc.civmodcore; -import java.io.File; -import java.sql.SQLException; import org.bukkit.Bukkit; import org.bukkit.configuration.serialization.ConfigurationSerialization; import org.bukkit.entity.HumanEntity; -import org.bukkit.plugin.PluginDescriptionFile; -import org.bukkit.plugin.java.JavaPluginLoader; import vg.civcraft.mc.civmodcore.chat.dialog.DialogManager; import vg.civcraft.mc.civmodcore.commands.ChunkMetaCommand; import vg.civcraft.mc.civmodcore.commands.CommandManager; @@ -15,7 +11,6 @@ import vg.civcraft.mc.civmodcore.dao.ManagedDatasource; import vg.civcraft.mc.civmodcore.inventory.gui.ClickableInventoryListener; import vg.civcraft.mc.civmodcore.inventory.items.EnchantUtils; -import vg.civcraft.mc.civmodcore.inventory.items.SpawnEggUtils; import vg.civcraft.mc.civmodcore.players.PlayerNames; import vg.civcraft.mc.civmodcore.players.scoreboard.bottom.BottomLineAPI; import vg.civcraft.mc.civmodcore.players.scoreboard.side.ScoreBoardAPI; @@ -31,20 +26,10 @@ public class CivModCorePlugin extends ACivMod { - /** - * Primary constructor used by the real server - */ public CivModCorePlugin() { super(); } - /** - * Secondary constructor used for testing - */ - protected CivModCorePlugin(JavaPluginLoader loader, PluginDescriptionFile description, File dataFolder, File file) { - super(loader, description, dataFolder, file); - } - private static CivModCorePlugin instance; private CivModCoreConfig config; @@ -86,7 +71,7 @@ public void onEnable() { registerListener(new ClickableInventoryListener()); registerListener(DialogManager.INSTANCE); registerListener(new ScoreBoardListener()); - registerListener(new PlayerNames()); + registerListener(new PlayerNames(this)); // Register commands this.commands = new CommandManager(this); this.commands.init(); @@ -95,7 +80,6 @@ public void onEnable() { this.commands.registerCommand(new ChunkMetaCommand()); // Load APIs EnchantUtils.loadEnchantAbbreviations(); - SpawnEggUtils.init(); BottomLineAPI.init(); this.skinCache = new SkinCache(this, this.config.getSkinCacheThreads()); diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/chat/ChatUtils.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/chat/ChatUtils.java index c0bbe7c020..d95b6be841 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/chat/ChatUtils.java +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/chat/ChatUtils.java @@ -200,6 +200,7 @@ public static String parseColorTags(@NotNull String string) { * @return Returns true if the component is null or has no visible content. * @deprecated Has been deprecated due to Paper's move to Kyori's Adventure. */ + @Deprecated public static boolean isNullOrEmpty(@Nullable final Component component) { if (component == null || component == Component.empty()) { return true; diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/entities/EntityUtils.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/entities/EntityUtils.java index 1827b8fffe..1f444181f1 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/entities/EntityUtils.java +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/entities/EntityUtils.java @@ -9,7 +9,7 @@ public final class EntityUtils { /** - * Attempts to retrieve an entity type by its slug or id. + * Attempts to retrieve an entity type by its slug. * * @param value The value to search for a matching entity type by. * @return Returns a matched entity type or null. @@ -23,13 +23,6 @@ public static EntityType getEntityType(final String value) { return EntityType.valueOf(value.toUpperCase()); } catch (final Throwable ignored) { } - try { - final EntityType type = EntityType.fromId(Short.parseShort(value)); - if (type != null) { - return type; - } - } catch (final Throwable ignored) { - } return null; } } diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/ClonedInventory.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/ClonedInventory.java index 33be60e382..93138ab5ce 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/ClonedInventory.java +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/ClonedInventory.java @@ -255,20 +255,15 @@ public static ClonedInventory cloneInventory(final Inventory inventory, final bo if (!forceClone && inventory instanceof ClonedInventory) { return (ClonedInventory) inventory; } - Inventory clone; - if (inventory.getType() == InventoryType.CHEST) { - clone = Bukkit.createInventory(inventory.getHolder(), inventory.getSize()); - } else { - clone = Bukkit.createInventory(inventory.getHolder(), inventory.getType()); - } - final ItemStack[] contents = inventory.getContents().clone(); + final ItemStack[] contents = inventory.getStorageContents().clone(); + Inventory clone = Bukkit.createInventory(inventory.getHolder(), contents.length); for (int i = 0; i < contents.length; i++) { final ItemStack item = contents[i]; if (item != null) { contents[i] = item.clone(); } } - clone.setContents(contents); + clone.setStorageContents(contents); return new ClonedInventory(clone); } diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/InventoryUtils.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/InventoryUtils.java index c145b515ff..70cc20ff14 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/InventoryUtils.java +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/InventoryUtils.java @@ -2,18 +2,13 @@ import com.google.common.base.Preconditions; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; import org.apache.commons.lang3.ArrayUtils; -import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; public final class InventoryUtils { @@ -53,76 +48,6 @@ public static List getViewingPlayers(@Nullable final Inventory inventory .collect(Collectors.toCollection(ArrayList::new)); } - /** - *

Attempts to find the first safe place to put an item.

- * - * @param inventory The inventory to attempt to find a slot in. - * @param item The item to find a place for. - * @return Returns an index of a slot that it's safe to add to. A return value of -1 means no safe place. Even if - * the return value is -1 it may still be possible to add the item stack to the inventory, as this - * function attempts to find the first slot that the given item stack can fit into wholly; that if it can - * technically fit but has to be distributed then there's no "first empty". - */ - public static int firstEmpty(@Nullable final Inventory inventory, final ItemStack item) { - if (inventory == null) { - return -1; - } - // If there's a slot free, then just return that. Otherwise, if - // the item is invalid, just return whatever slot was returned. - final int index = inventory.firstEmpty(); - if (index >= 0 || !ItemUtils.isValidItem(item)) { - return index; - } - // If gets here, then we're certain that there's no stacks free. - // If the amount of the item to add is larger than a stack, then - // it can't be merged with another stack. So just back out. - final int remainder = item.getMaxStackSize() - item.getAmount(); - if (remainder <= 0) { - return -1; - } - // Find all items that match with the given item to see if there's - // a stack that can be merged with. If none can be found, back out. - for (final Map.Entry entry : inventory.all(item).entrySet()) { - if (entry.getValue().getAmount() <= remainder) { - return entry.getKey(); - } - } - return -1; - } - - /** - * Clears an inventory of items. - * - * @param inventory The inventory to clear of items. - */ - public static void clearInventory(@NotNull final Inventory inventory) { - final ItemStack[] contents = inventory.getContents(); - Arrays.fill(contents, new ItemStack(Material.AIR)); - inventory.setContents(contents); - } - - /** - * Checks whether an inventory has more than one viewer. - * - * @param inventory The inventory to check. - * @return Returns true if an inventory has multiple viewers. - */ - public static boolean hasOtherViewers(@Nullable final Inventory inventory) { - return inventory != null && inventory.getViewers().size() > 1; - } - - /** - * Checks whether a certain amount of slots would be considered a valid chest inventory. - * - * @param slots The slot amount to check. - * @return Returns true if the slot count is or between 1-6 multiples of 9. - */ - public static boolean isValidChestSize(final int slots) { - return slots > 0 - && slots <= 54 - && (slots % 9) == 0; - } - /** * Will safely add a set of items to an inventory. If not all items are added, it's not committed to the inventory. * @@ -137,14 +62,11 @@ public static boolean safelyAddItemsToInventory(final Inventory inventory, final } final Inventory clone = ClonedInventory.cloneInventory(inventory); for (final ItemStack itemToAdd : items) { - if (firstEmpty(clone, itemToAdd) < 0) { - return false; - } if (!clone.addItem(itemToAdd).isEmpty()) { return false; } } - inventory.setContents(clone.getContents()); + inventory.setStorageContents(clone.getStorageContents()); return true; } @@ -167,7 +89,7 @@ public static boolean safelyRemoveItemsFromInventory(final Inventory inventory, return false; } } - inventory.setContents(clone.getContents()); + inventory.setStorageContents(clone.getStorageContents()); return true; } @@ -196,8 +118,8 @@ public static boolean safelyTransactBetweenInventories(final Inventory from, if (!safelyAddItemsToInventory(toClone, items)) { return false; } - from.setContents(fromClone.getContents()); - to.setContents(toClone.getContents()); + from.setStorageContents(fromClone.getStorageContents()); + to.setStorageContents(toClone.getStorageContents()); return true; } @@ -230,8 +152,8 @@ public static boolean safelyTradeBetweenInventories(final Inventory formerInvent if (!safelyAddItemsToInventory(latterClone, formerItems)) { return false; } - formerInventory.setContents(formerClone.getContents()); - latterInventory.setContents(latterClone.getContents()); + formerInventory.setStorageContents(formerClone.getStorageContents()); + latterInventory.setStorageContents(latterClone.getStorageContents()); return true; } diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/LClickable.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/LClickable.java index 8bdb2c0f0f..f8d0e5e98c 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/LClickable.java +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/LClickable.java @@ -21,6 +21,11 @@ public LClickable(Material mat, String name, Consumer clickFunction) { ItemUtils.setComponentDisplayName(this.item, Component.text(name)); } + public LClickable(Material mat, Component component, Consumer clickFunction) { + this(mat, clickFunction); + ItemUtils.setComponentDisplayName(this.item, component); + } + public LClickable(Material mat, String name, Consumer clickFunction, String... lore) { this(mat, name, clickFunction); if (lore.length > 0) { diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/Scrollbar.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/Scrollbar.java index 9b8b561782..bdd3b7dee5 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/Scrollbar.java +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/gui/components/Scrollbar.java @@ -66,10 +66,11 @@ private int calculatePageAmount() { // modulo scroll offset - 2, because a normal page has forward and backwards // buttons int modOffset = contentAmount % (scrollOffset); + // divide remaining contentAmount (after subtracting first page's content) by scroll offset int basicRowCalc = contentAmount / (scrollOffset); - if (modOffset <= 1) { - // there would be one leftover element in a new page, but we can just put that - // in the previous page instead of a next button + if (modOffset == 0) { + // scroll offsets fit cleanly into pages (would fill entirely), so we can just put + // an item in the last slot of the last page instead of a next button for 1 item return basicRowCalc + 1; } return basicRowCalc + 2; diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/EnchantUtils.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/EnchantUtils.java index 4d97dc7cd8..e84241069b 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/EnchantUtils.java +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/EnchantUtils.java @@ -12,6 +12,7 @@ import java.util.stream.Collectors; import net.kyori.adventure.text.Component; import net.kyori.adventure.translation.Translatable; +import org.bukkit.NamespacedKey; import org.bukkit.Registry; import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.ItemStack; @@ -98,7 +99,7 @@ public static void loadEnchantAbbreviations() { if (!missing.isEmpty()) { //noinspection deprecation LOGGER.warning("The following enchants are missing abbreviations: %s.".formatted( - missing.stream().map(Enchantment::getName).collect(Collectors.joining(",")) + missing.stream().map(Enchantment::getKey).map(NamespacedKey::asString).collect(Collectors.joining(",")) )); } } diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/MaterialUtils.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/MaterialUtils.java index 230a8ae3fd..aad11a7ede 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/MaterialUtils.java +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/MaterialUtils.java @@ -16,7 +16,6 @@ *
    *
  • See also:
  • *
  • {@link MoreTags MoreTags}
  • - *
  • {@link SpawnEggUtils SpawnEggUtils}
  • *
*/ public final class MaterialUtils { diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/SpawnEggUtils.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/SpawnEggUtils.java deleted file mode 100644 index 80658a16a9..0000000000 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/inventory/items/SpawnEggUtils.java +++ /dev/null @@ -1,153 +0,0 @@ -package vg.civcraft.mc.civmodcore.inventory.items; - -import com.google.common.collect.BiMap; -import com.google.common.collect.ImmutableBiMap; -import java.util.HashSet; -import java.util.Set; -import java.util.stream.Collectors; -import org.apache.commons.collections4.CollectionUtils; -import org.bukkit.Material; -import org.bukkit.entity.EntityType; -import org.jetbrains.annotations.Nullable; -import vg.civcraft.mc.civmodcore.utilities.CivLogger; - -/** - * Class of static APIs for Spawn Eggs. - */ -public final class SpawnEggUtils { - - private static final BiMap SPAWN_EGGS = ImmutableBiMap.builder() - .put(Material.AXOLOTL_SPAWN_EGG, EntityType.AXOLOTL) - .put(Material.ALLAY_SPAWN_EGG, EntityType.ALLAY) - .put(Material.BAT_SPAWN_EGG, EntityType.BAT) - .put(Material.BEE_SPAWN_EGG, EntityType.BEE) - .put(Material.BLAZE_SPAWN_EGG, EntityType.BLAZE) - .put(Material.BREEZE_SPAWN_EGG, EntityType.BREEZE) - .put(Material.CAT_SPAWN_EGG, EntityType.CAT) - .put(Material.CAMEL_SPAWN_EGG, EntityType.CAMEL) - .put(Material.CAVE_SPIDER_SPAWN_EGG, EntityType.CAVE_SPIDER) - .put(Material.CHICKEN_SPAWN_EGG, EntityType.CHICKEN) - .put(Material.COD_SPAWN_EGG, EntityType.COD) - .put(Material.COW_SPAWN_EGG, EntityType.COW) - .put(Material.CREEPER_SPAWN_EGG, EntityType.CREEPER) - .put(Material.DOLPHIN_SPAWN_EGG, EntityType.DOLPHIN) - .put(Material.DONKEY_SPAWN_EGG, EntityType.DONKEY) - .put(Material.DROWNED_SPAWN_EGG, EntityType.DROWNED) - .put(Material.ELDER_GUARDIAN_SPAWN_EGG, EntityType.ELDER_GUARDIAN) - .put(Material.ENDERMAN_SPAWN_EGG, EntityType.ENDERMAN) - .put(Material.ENDERMITE_SPAWN_EGG, EntityType.ENDERMITE) - .put(Material.ENDER_DRAGON_SPAWN_EGG, EntityType.ENDER_DRAGON) - .put(Material.EVOKER_SPAWN_EGG, EntityType.EVOKER) - .put(Material.FOX_SPAWN_EGG, EntityType.FOX) - .put(Material.FROG_SPAWN_EGG, EntityType.FROG) - .put(Material.GHAST_SPAWN_EGG, EntityType.GHAST) - .put(Material.GLOW_SQUID_SPAWN_EGG, EntityType.GLOW_SQUID) - .put(Material.GOAT_SPAWN_EGG, EntityType.GOAT) - .put(Material.GUARDIAN_SPAWN_EGG, EntityType.GUARDIAN) - .put(Material.HOGLIN_SPAWN_EGG, EntityType.HOGLIN) - .put(Material.HORSE_SPAWN_EGG, EntityType.HORSE) - .put(Material.HUSK_SPAWN_EGG, EntityType.HUSK) - .put(Material.IRON_GOLEM_SPAWN_EGG, EntityType.IRON_GOLEM) - .put(Material.LLAMA_SPAWN_EGG, EntityType.LLAMA) - .put(Material.MAGMA_CUBE_SPAWN_EGG, EntityType.MAGMA_CUBE) - .put(Material.MOOSHROOM_SPAWN_EGG, EntityType.MOOSHROOM) - .put(Material.MULE_SPAWN_EGG, EntityType.MULE) - .put(Material.OCELOT_SPAWN_EGG, EntityType.OCELOT) - .put(Material.PANDA_SPAWN_EGG, EntityType.PANDA) - .put(Material.PARROT_SPAWN_EGG, EntityType.PARROT) - .put(Material.PHANTOM_SPAWN_EGG, EntityType.PHANTOM) - .put(Material.PIG_SPAWN_EGG, EntityType.PIG) - .put(Material.PIGLIN_BRUTE_SPAWN_EGG, EntityType.PIGLIN_BRUTE) - .put(Material.PIGLIN_SPAWN_EGG, EntityType.PIGLIN) - .put(Material.PILLAGER_SPAWN_EGG, EntityType.PILLAGER) - .put(Material.POLAR_BEAR_SPAWN_EGG, EntityType.POLAR_BEAR) - .put(Material.PUFFERFISH_SPAWN_EGG, EntityType.PUFFERFISH) - .put(Material.RABBIT_SPAWN_EGG, EntityType.RABBIT) - .put(Material.RAVAGER_SPAWN_EGG, EntityType.RAVAGER) - .put(Material.SALMON_SPAWN_EGG, EntityType.SALMON) - .put(Material.SHEEP_SPAWN_EGG, EntityType.SHEEP) - .put(Material.SHULKER_SPAWN_EGG, EntityType.SHULKER) - .put(Material.SILVERFISH_SPAWN_EGG, EntityType.SILVERFISH) - .put(Material.SKELETON_HORSE_SPAWN_EGG, EntityType.SKELETON_HORSE) - .put(Material.SKELETON_SPAWN_EGG, EntityType.SKELETON) - .put(Material.SLIME_SPAWN_EGG, EntityType.SLIME) - .put(Material.SNIFFER_SPAWN_EGG, EntityType.SNIFFER) - .put(Material.SNOW_GOLEM_SPAWN_EGG, EntityType.SNOW_GOLEM) - .put(Material.SPIDER_SPAWN_EGG, EntityType.SPIDER) - .put(Material.SQUID_SPAWN_EGG, EntityType.SQUID) - .put(Material.STRAY_SPAWN_EGG, EntityType.STRAY) - .put(Material.STRIDER_SPAWN_EGG, EntityType.STRIDER) - .put(Material.TADPOLE_SPAWN_EGG, EntityType.TADPOLE) - .put(Material.TRADER_LLAMA_SPAWN_EGG, EntityType.TRADER_LLAMA) - .put(Material.TROPICAL_FISH_SPAWN_EGG, EntityType.TROPICAL_FISH) - .put(Material.TURTLE_SPAWN_EGG, EntityType.TURTLE) - .put(Material.VEX_SPAWN_EGG, EntityType.VEX) - .put(Material.VILLAGER_SPAWN_EGG, EntityType.VILLAGER) - .put(Material.VINDICATOR_SPAWN_EGG, EntityType.VINDICATOR) - .put(Material.WANDERING_TRADER_SPAWN_EGG, EntityType.WANDERING_TRADER) - .put(Material.WARDEN_SPAWN_EGG, EntityType.WARDEN) - .put(Material.WITCH_SPAWN_EGG, EntityType.WITCH) - .put(Material.WITHER_SKELETON_SPAWN_EGG, EntityType.WITHER_SKELETON) - .put(Material.WITHER_SPAWN_EGG, EntityType.WITHER) - .put(Material.WOLF_SPAWN_EGG, EntityType.WOLF) - .put(Material.ZOGLIN_SPAWN_EGG, EntityType.ZOGLIN) - .put(Material.ZOMBIE_SPAWN_EGG, EntityType.ZOMBIE) - .put(Material.ZOMBIE_HORSE_SPAWN_EGG, EntityType.ZOMBIE_HORSE) - .put(Material.ZOMBIFIED_PIGLIN_SPAWN_EGG, EntityType.ZOMBIFIED_PIGLIN) - .put(Material.ZOMBIE_VILLAGER_SPAWN_EGG, EntityType.ZOMBIE_VILLAGER) - .build(); - - public static void init() { - final var logger = CivLogger.getLogger(SpawnEggUtils.class); - // Determine if there's any enchants missing names - final Set missing = new HashSet<>(); - CollectionUtils.addAll(missing, Material.values()); - missing.removeIf(material -> !material.name().endsWith("_SPAWN_EGG") || SPAWN_EGGS.containsKey(material)); - if (!missing.isEmpty()) { - logger.warning("The following spawn eggs are missing: " + - missing.stream().map(Enum::name).collect(Collectors.joining(",")) + "."); - } - } - - /** - * Tests if a material is that of a spawn egg. - * - * @param material The material to test. - * @return Returns true if the material is that of a spawn egg. - */ - public static boolean isSpawnEgg(final Material material) { - if (material == null) { - return false; - } - return SPAWN_EGGS.containsKey(material); - } - - /** - * Gets the spawned entity type for a spawn egg. - * - * @param material The material, must be a spawn egg otherwise it's a guaranteed null. - * @return Returns the entity type that will be spawned from the spawn egg, or null. - */ - @Nullable - public static EntityType getEntityType(final Material material) { - if (material == null) { - return null; - } - return SPAWN_EGGS.get(material); - } - - /** - * Gets the spawn egg material from an entity type. - * - * @param type The type of entity to match to the spawn egg. - * @return Returns a spawn egg material, or null. - */ - @Nullable - public static Material getSpawnEgg(final EntityType type) { - if (type == null) { - return null; - } - return SPAWN_EGGS.inverse().get(type); - } - -} diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTSerialization.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTSerialization.java deleted file mode 100644 index 047064dc3c..0000000000 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTSerialization.java +++ /dev/null @@ -1,105 +0,0 @@ -package vg.civcraft.mc.civmodcore.nbt; - -import com.google.common.annotations.Beta; -import com.google.common.io.ByteArrayDataInput; -import com.google.common.io.ByteArrayDataOutput; -import com.google.common.io.ByteStreams; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.logging.Level; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.NbtAccounter; -import net.minecraft.nbt.NbtIo; -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.reflect.MethodUtils; -import org.bukkit.craftbukkit.util.CraftNBTTagConfigSerializer; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; -import vg.civcraft.mc.civmodcore.utilities.CivLogger; - -public final class NBTSerialization { - - private static final CivLogger LOGGER = CivLogger.getLogger(NBTSerialization.class); - - @Beta - public static CompoundTag fromMap(final Map data) { - return (CompoundTag) CraftNBTTagConfigSerializer.deserialize(data); - } - - @Beta - public static ListTag fromList(final List data) { - return (ListTag) CraftNBTTagConfigSerializer.deserialize(data); - } - - /** - * Attempts to serialize an NBTCompound into a data array. - * - * @param nbt The NBTCompound to serialize. - * @return Returns a data array representing the given NBTCompound serialized, or otherwise null. - */ - @SuppressWarnings("UnstableApiUsage") - @Nullable - public static byte[] toBytes(final CompoundTag nbt) { - if (nbt == null) { - return null; - } - final ByteArrayDataOutput output = ByteStreams.newDataOutput(); - try { - NbtIo.write(nbt, output); - } catch (final IOException exception) { - LOGGER.log(Level.WARNING, "Could not serialise NBT to bytes!", exception); - return null; - } - return output.toByteArray(); - } - - /** - * Attempts to deserialize NBT data into an NBTCompound. - * - * @param bytes The NBT data as a byte array. - * @return Returns an NBTCompound if the deserialization was successful, or otherwise null. - */ - @SuppressWarnings("UnstableApiUsage") - @Nullable - public static CompoundTag fromBytes(final byte[] bytes) { - if (ArrayUtils.isEmpty(bytes)) { - return null; - } - final ByteArrayDataInput input = ByteStreams.newDataInput(bytes); - try { - return NbtIo.read(input, NbtAccounter.unlimitedHeap()); - } catch (final IOException exception) { - LOGGER.log(Level.WARNING, "Could not deserialise NBT from bytes!", exception); - return null; - } - } - - /** - * Dynamically retrieves a serializable's {@link NBTSerializable#fromNBT(NBTCompound) fromNBT} method. - * - * @param The type of the serializable. - * @param clazz The serializable's class. - * @return Returns a deserializer function. - */ - @SuppressWarnings("unchecked") - @NotNull - public static NBTDeserializer getDeserializer(@NotNull final Class clazz) { - final var method = MethodUtils.getMatchingAccessibleMethod(clazz, "fromNBT", NBTCompound.class); - if (!Objects.equals(clazz, method.getReturnType())) { - throw new IllegalArgumentException("That class hasn't implemented its own fromNBT method.. please fix"); - } - return (nbt) -> { - try { - return (T) method.invoke(null, nbt); - } catch (final IllegalAccessException | InvocationTargetException exception) { - throw new NBTSerializationException(exception); - } - }; - } - -} diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTSerializationException.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTSerializationException.java deleted file mode 100644 index 8c7ed4727d..0000000000 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTSerializationException.java +++ /dev/null @@ -1,31 +0,0 @@ -package vg.civcraft.mc.civmodcore.nbt; - -import java.io.Serial; -import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; - -/** - * Exception that ought to be used within {@link NBTSerializable#toNBT(NBTCompound)} and - * {@link NBTSerializable#fromNBT(NBTCompound)}. - */ -public class NBTSerializationException extends RuntimeException { - - @Serial - private static final long serialVersionUID = 606023177729327630L; - - public NBTSerializationException() { - super(); - } - - public NBTSerializationException(String message) { - super(message); - } - - public NBTSerializationException(String message, Throwable cause) { - super(message, cause); - } - - public NBTSerializationException(Throwable cause) { - super(cause); - } - -} diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/NbtCompound.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/NbtCompound.java new file mode 100644 index 0000000000..c1a15884b1 --- /dev/null +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/NbtCompound.java @@ -0,0 +1,613 @@ +package vg.civcraft.mc.civmodcore.nbt; + +import com.mojang.serialization.Dynamic; +import java.util.Collections; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import net.minecraft.core.UUIDUtil; +import net.minecraft.nbt.ByteArrayTag; +import net.minecraft.nbt.ByteTag; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.DoubleTag; +import net.minecraft.nbt.FloatTag; +import net.minecraft.nbt.IntArrayTag; +import net.minecraft.nbt.IntTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.LongArrayTag; +import net.minecraft.nbt.LongTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.ShortTag; +import net.minecraft.nbt.StringTag; +import net.minecraft.nbt.Tag; +import org.apache.commons.lang3.EnumUtils; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnmodifiableView; +import vg.civcraft.mc.civmodcore.nbt.exceptions.InvalidNbtValue; +import vg.civcraft.mc.civmodcore.nbt.exceptions.UnexpectedNbtTypeException; + +public record NbtCompound( + @NotNull CompoundTag internal +) { + public NbtCompound { + Objects.requireNonNull(internal); + } + + public NbtCompound() { + this(new CompoundTag()); + } + + public int size() { + return internal().size(); + } + + public boolean isEmpty() { + return internal().isEmpty(); + } + + public @UnmodifiableView @NotNull Set<@NotNull String> keys() { + return Collections.unmodifiableSet(internal().keySet()); + } + + public void remove( + final @NotNull String key + ) { + internal().remove(Objects.requireNonNull(key)); + } + + public void clear() { + internal().keySet().clear(); + } + + @ApiStatus.Internal + public @Nullable Tag get( + final @NotNull String key + ) { + return internal().get(Objects.requireNonNull(key)); + } + + @Contract("_, !null -> !null") + public @Nullable Boolean getBoolean( + final @NotNull String key, + final Boolean fallbackIfNull + ) { + return switch (get(key)) { + case final ByteTag match -> switch (match.byteValue()) { + case 0 -> false; + case 1 -> true; + default -> throw new InvalidNbtValue(); + }; + case null -> fallbackIfNull; + case final Tag unknown -> throw new UnexpectedNbtTypeException(ByteTag.class, unknown); + }; + } + + public void setBoolean( + final @NotNull String key, + final boolean value + ) { + internal().putBoolean(Objects.requireNonNull(key), value); + } + + @Contract("_, !null -> !null") + public @Nullable Byte getByte( + final @NotNull String key, + final Byte fallbackIfNull + ) { + return switch (get(key)) { + case final ByteTag match -> match.byteValue(); + case null -> fallbackIfNull; + case final Tag unknown -> throw new UnexpectedNbtTypeException(ByteTag.class, unknown); + }; + } + + public void setByte( + final @NotNull String key, + final byte value + ) { + internal().putByte(Objects.requireNonNull(key), value); + } + + @Contract("_, !null -> !null") + public @Nullable Short getShort( + final @NotNull String key, + final Short fallbackIfNull + ) { + return switch (get(key)) { + case final ShortTag match -> match.shortValue(); + case null -> fallbackIfNull; + case final Tag unknown -> throw new UnexpectedNbtTypeException(ShortTag.class, unknown); + }; + } + + public void setShort( + final @NotNull String key, + final short value + ) { + internal().putShort(Objects.requireNonNull(key), value); + } + + @Contract("_, !null -> !null") + public @Nullable Integer getInt( + final @NotNull String key, + final Integer fallbackIfNull + ) { + return switch (get(key)) { + case final IntTag match -> match.intValue(); + case null -> fallbackIfNull; + case final Tag unknown -> throw new UnexpectedNbtTypeException(IntTag.class, unknown); + }; + } + + public void setInt( + final @NotNull String key, + final int value + ) { + internal().putInt(Objects.requireNonNull(key), value); + } + + @Contract("_, !null -> !null") + public @Nullable Long getLong( + final @NotNull String key, + final Long fallbackIfNull + ) { + return switch (get(key)) { + case final LongTag match -> match.longValue(); + case null -> fallbackIfNull; + case final Tag unknown -> throw new UnexpectedNbtTypeException(LongTag.class, unknown); + }; + } + + public void setLong( + final @NotNull String key, + final long value + ) { + internal().putLong(Objects.requireNonNull(key), value); + } + + @Contract("_, !null -> !null") + public @Nullable Float getFloat( + final @NotNull String key, + final Float fallbackIfNull + ) { + return switch (get(key)) { + case final FloatTag match -> match.floatValue(); + case null -> fallbackIfNull; + case final Tag unknown -> throw new UnexpectedNbtTypeException(FloatTag.class, unknown); + }; + } + + public void setFloat( + final @NotNull String key, + final float value + ) { + internal().putFloat(Objects.requireNonNull(key), value); + } + + @Contract("_, !null -> !null") + public @Nullable Double getDouble( + final @NotNull String key, + final Double fallbackIfNull + ) { + return switch (get(key)) { + case final DoubleTag match -> match.doubleValue(); + case null -> fallbackIfNull; + case final Tag unknown -> throw new UnexpectedNbtTypeException(DoubleTag.class, unknown); + }; + } + + public void setDouble( + final @NotNull String key, + final double value + ) { + internal().putDouble(Objects.requireNonNull(key), value); + } + + @Contract("_, !null -> !null") + public @Nullable UUID getUuid( + final @NotNull String key, + final UUID fallbackIfNull + ) { + return switch (get(key)) { + case final IntArrayTag match -> UUIDUtil.readUUID(new Dynamic<>(NbtOps.INSTANCE, match)); + case null -> fallbackIfNull; + case final Tag unknown -> throw new UnexpectedNbtTypeException(IntArrayTag.class, unknown); + }; + } + + public void setUuid( + final @NotNull String key, + final UUID value + ) { + if (value == null) { + remove(key); + return; + } + setIntArray(key, UUIDUtil.uuidToIntArray(value)); + } + + @Contract("_, !null -> !null") + public @Nullable String getString( + final @NotNull String key, + final String fallbackIfNull + ) { + return switch (get(key)) { + case final StringTag match -> match.value(); + case null -> fallbackIfNull; + case final Tag unknown -> throw new UnexpectedNbtTypeException(StringTag.class, unknown); + }; + } + + public void setString( + final @NotNull String key, + final String value + ) { + Objects.requireNonNull(key); + if (value == null) { + remove(key); + } else { + internal().putString(key, value); + } + } + + @Contract("_, true -> !null") + public @Nullable NbtCompound getCompound( + final @NotNull String key, + final boolean createEmptyIfNull + ) { + return switch (get(key)) { + case final CompoundTag match -> new NbtCompound(match); + case null -> createEmptyIfNull ? new NbtCompound() : null; + case final Tag unknown -> throw new UnexpectedNbtTypeException(CompoundTag.class, unknown); + }; + } + + public void setCompound( + final @NotNull String key, + final NbtCompound value + ) { + Objects.requireNonNull(key); + if (value == null) { + remove(key); + } else { + internal().put(key, value.internal()); + } + } + + // ------------------------------------------------------------ + // Array Functions + // ------------------------------------------------------------ + + @Contract("_, true -> !null") + public byte @Nullable [] getByteArray( + final @NotNull String key, + final boolean createEmptyIfNull + ) { + return switch (get(key)) { + case final ByteArrayTag match -> match.getAsByteArray(); + case null -> createEmptyIfNull ? new byte[0] : null; + case final Tag unknown -> throw new UnexpectedNbtTypeException(ByteArrayTag.class, unknown); + }; + } + + public void setByteArray( + final @NotNull String key, + final byte[] bytes + ) { + if (bytes == null) { + remove(key); + return; + } + internal().putByteArray(Objects.requireNonNull(key), bytes); + } + + @Contract("_, true -> !null") + public short @Nullable [] getShortArray( + final @NotNull String key, + final boolean createEmptyIfNull + ) { + return switch (get(key)) { + case final ListTag match -> { + final var array = new short[match.size()]; + for (int i = 0; i < array.length; i++) { + array[i] = switch (match.get(i)) { + case final ShortTag element -> element.shortValue(); + case null -> throw new InvalidNbtValue(new NullPointerException()); + case final Tag unknown -> throw new InvalidNbtValue(new UnexpectedNbtTypeException(ShortTag.class, unknown)); + }; + } + yield array; + } + case null -> createEmptyIfNull ? new short[0] : null; + case final Tag unknown -> throw new UnexpectedNbtTypeException(ListTag.class, unknown); + }; + } + + public void setShortArray( + final @NotNull String key, + final short[] shorts + ) { + if (shorts == null) { + remove(key); + return; + } + final var list = new ListTag(); + for (final short value : shorts) { + list.add(ShortTag.valueOf(value)); + } + internal().put(Objects.requireNonNull(key), list); + } + + @Contract("_, true -> !null") + public int @Nullable [] getIntArray( + final @NotNull String key, + final boolean createEmptyIfNull + ) { + return switch (get(key)) { + case final IntArrayTag match -> match.getAsIntArray(); + case null -> createEmptyIfNull ? new int[0] : null; + case final Tag unknown -> throw new UnexpectedNbtTypeException(IntArrayTag.class, unknown); + }; + } + + public void setIntArray( + final @NotNull String key, + final int[] ints + ) { + if (ints == null) { + remove(key); + return; + } + internal().putIntArray(Objects.requireNonNull(key), ints); + } + + @Contract("_, true -> !null") + public long @Nullable [] getLongArray( + final @NotNull String key, + final boolean createEmptyIfNull + ) { + return switch (get(key)) { + case final LongArrayTag match -> match.getAsLongArray(); + case null -> createEmptyIfNull ? new long[0] : null; + case final Tag unknown -> throw new UnexpectedNbtTypeException(LongArrayTag.class, unknown); + }; + } + + public void setLongArray( + final @NotNull String key, + final long[] longs + ) { + if (longs == null) { + remove(key); + return; + } + internal().putLongArray(Objects.requireNonNull(key), longs); + } + + @Contract("_, true -> !null") + public float @Nullable [] getFloatArray( + final @NotNull String key, + final boolean createEmptyIfNull + ) { + return switch (get(key)) { + case final ListTag match -> { + final var array = new float[match.size()]; + for (int i = 0; i < array.length; i++) { + array[i] = switch (match.get(i)) { + case final FloatTag element -> element.floatValue(); + case null -> throw new InvalidNbtValue(new NullPointerException()); + case final Tag unknown -> throw new InvalidNbtValue(new UnexpectedNbtTypeException(FloatTag.class, unknown)); + }; + } + yield array; + } + case null -> createEmptyIfNull ? new float[0] : null; + case final Tag unknown -> throw new UnexpectedNbtTypeException(ListTag.class, unknown); + }; + } + + public void setFloatArray( + final @NotNull String key, + final float[] floats + ) { + if (floats == null) { + remove(key); + return; + } + final var list = new ListTag(); + for (final float value : floats) { + list.add(FloatTag.valueOf(value)); + } + internal().put(Objects.requireNonNull(key), list); + } + + @Contract("_, true -> !null") + public double @Nullable [] getDoubleArray( + final @NotNull String key, + final boolean createEmptyIfNull + ) { + return switch (get(key)) { + case final ListTag match -> { + final var array = new double[match.size()]; + for (int i = 0; i < array.length; i++) { + array[i] = switch (match.get(i)) { + case final DoubleTag element -> element.doubleValue(); + case null -> throw new InvalidNbtValue(new NullPointerException()); + case final Tag unknown -> throw new InvalidNbtValue(new UnexpectedNbtTypeException(DoubleTag.class, unknown)); + }; + } + yield array; + } + case null -> createEmptyIfNull ? new double[0] : null; + case final Tag unknown -> throw new UnexpectedNbtTypeException(ListTag.class, unknown); + }; + } + + public void setDoubleArray( + final @NotNull String key, + final double[] doubles + ) { + if (doubles == null) { + remove(key); + return; + } + final var list = new ListTag(); + for (final double value : doubles) { + list.add(DoubleTag.valueOf(value)); + } + internal().put(Objects.requireNonNull(key), list); + } + + @Contract("_, true -> !null") + public @NotNull UUID @Nullable [] getUuidArray( + final @NotNull String key, + final boolean createEmptyIfNull + ) { + return switch (get(key)) { + case final ListTag match -> { + final var array = new UUID[match.size()]; + for (int i = 0; i < array.length; i++) { + array[i] = switch (match.get(i)) { + case final IntArrayTag element -> UUIDUtil.readUUID(new Dynamic<>(NbtOps.INSTANCE, element)); + case null -> throw new InvalidNbtValue(new NullPointerException()); + case final Tag unknown -> throw new UnexpectedNbtTypeException(IntArrayTag.class, unknown); + }; + } + yield array; + } + case null -> createEmptyIfNull ? new UUID[0] : null; + case final Tag unknown -> throw new UnexpectedNbtTypeException(ListTag.class, unknown); + }; + } + + /** + * Sets an array of UUIDs to a key. + * + * @param key The key to set to values to. + * @param uuids The values to set to the key. + */ + public void setUuidArray( + final @NotNull String key, + final UUID[] uuids + ) { + if (uuids == null) { + remove(key); + return; + } + final var list = new ListTag(); + for (final UUID value : uuids) { + list.add(new IntArrayTag(UUIDUtil.uuidToIntArray(value))); + } + internal().put(Objects.requireNonNull(key), list); + } + + @Contract("_, true -> !null") + public @NotNull String @Nullable [] getStringArray( + final @NotNull String key, + final boolean createEmptyIfNull + ) { + return switch (get(key)) { + case final ListTag match -> { + final var array = new String[match.size()]; + for (int i = 0; i < array.length; i++) { + array[i] = switch (match.get(i)) { + case final StringTag element -> element.value(); + case null -> throw new InvalidNbtValue(new NullPointerException()); + case final Tag unknown -> throw new InvalidNbtValue(new UnexpectedNbtTypeException(StringTag.class, unknown)); + }; + } + yield array; + } + case null -> createEmptyIfNull ? new String[0] : null; + case final Tag unknown -> throw new UnexpectedNbtTypeException(ListTag.class, unknown); + }; + } + + public void setStringArray( + final @NotNull String key, + final @NotNull String @Nullable [] strings + ) { + if (strings == null) { + remove(key); + return; + } + final var list = new ListTag(); + for (final String value : strings) { + list.add(StringTag.valueOf(value)); + } + internal().put(Objects.requireNonNull(key), list); + } + + @Contract("_, true -> !null") + public @NotNull NbtCompound @Nullable [] getCompoundArray( + final @NotNull String key, + final boolean createEmptyIfNull + ) { + return switch (get(key)) { + case final ListTag match -> { + final var array = new NbtCompound[match.size()]; + for (int i = 0; i < array.length; i++) { + array[i] = switch (match.get(i)) { + case final CompoundTag element -> new NbtCompound(element); + case null -> throw new InvalidNbtValue(new NullPointerException()); + case final Tag unknown -> throw new InvalidNbtValue(new UnexpectedNbtTypeException(CompoundTag.class, unknown)); + }; + } + yield array; + } + case null -> createEmptyIfNull ? new NbtCompound[0] : null; + case final Tag unknown -> throw new UnexpectedNbtTypeException(ListTag.class, unknown); + }; + } + + public void setCompoundArray( + final @NotNull String key, + final @NotNull NbtCompound @Nullable [] compounds + ) { + if (compounds == null) { + remove(key); + return; + } + final var list = new ListTag(); + for (final NbtCompound value : compounds) { + list.add(value.internal()); + } + internal().put(Objects.requireNonNull(key), list); + } + + // ------------------------------------------------------------ + // Convenience functions + // ------------------------------------------------------------ + + public @NotNull NbtCompound ensureCompound( + final @NotNull String key + ) { + Objects.requireNonNull(key); + if (internal().get(key) instanceof final CompoundTag tag) { + return new NbtCompound(tag); + } + final var created = new CompoundTag(); + internal().put(key, created); + return new NbtCompound(created); + } + + @Contract("_, _, !null -> !null") + public > @Nullable T getEnum( + final @NotNull String key, + final @NotNull Class enumClass, + final T fallbackIfNull + ) { + return EnumUtils.getEnum(enumClass, getString(key, null), fallbackIfNull); + } + + public void setEnum( + final @NotNull String key, + final Enum value + ) { + setString(key, value == null ? null : value.name()); + } +} + diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/NbtUtils.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/NbtUtils.java new file mode 100644 index 0000000000..4107e3bcdb --- /dev/null +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/NbtUtils.java @@ -0,0 +1,48 @@ +package vg.civcraft.mc.civmodcore.nbt; + +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import java.io.IOException; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtAccounter; +import net.minecraft.nbt.NbtIo; +import org.apache.commons.lang3.ArrayUtils; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class NbtUtils { + private static final Logger LOGGER = LoggerFactory.getLogger(NbtUtils.class); + + public static byte @Nullable [] toBytes( + final CompoundTag nbt + ) { + if (nbt == null) { + return null; + } + final ByteArrayDataOutput output = ByteStreams.newDataOutput(); + try { + NbtIo.write(nbt, output); + } catch (final IOException exception) { + LOGGER.warn("Could not serialise NBT to bytes!", exception); + return null; + } + return output.toByteArray(); + } + + public static @Nullable CompoundTag fromBytes( + final byte[] bytes + ) { + if (ArrayUtils.isEmpty(bytes)) { + return null; + } + final ByteArrayDataInput input = ByteStreams.newDataInput(bytes); + try { + return NbtIo.read(input, NbtAccounter.unlimitedHeap()); + } catch (final IOException exception) { + LOGGER.warn("Could not deserialise NBT from bytes!", exception); + return null; + } + } +} diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/exceptions/InvalidNbtValue.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/exceptions/InvalidNbtValue.java new file mode 100644 index 0000000000..bfe96be133 --- /dev/null +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/exceptions/InvalidNbtValue.java @@ -0,0 +1,30 @@ +package vg.civcraft.mc.civmodcore.nbt.exceptions; + +import org.jetbrains.annotations.NotNull; + +/** + * This is when an NBT value is the correct type but has an invalid or unexpected value. For example, booleans are + * stored as bytes, so this is thrown if that byte represents any value other than 0 or 1. + */ +public final class InvalidNbtValue extends NbtException { + public InvalidNbtValue() { + super(); + } + + /** + * @apiNote When dealing when NBT lists, each element can really be anything. So throw this if, for example, you + * expect a list of booleans, but encounter a string, by passing in a {@link UnexpectedNbtTypeException}. + * Or pass in a {@link NullPointerException} if the element is somehow null. + */ + public InvalidNbtValue( + final @NotNull Throwable cause + ) { + super(cause); + } + + public InvalidNbtValue( + final @NotNull String message + ) { + super(message); + } +} diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/exceptions/NbtException.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/exceptions/NbtException.java new file mode 100644 index 0000000000..42c4454978 --- /dev/null +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/exceptions/NbtException.java @@ -0,0 +1,28 @@ +package vg.civcraft.mc.civmodcore.nbt.exceptions; + +import org.jetbrains.annotations.NotNull; + +public abstract class NbtException extends RuntimeException { + public NbtException() { + super(); + } + + public NbtException( + final @NotNull String message + ) { + super(message); + } + + public NbtException( + final @NotNull String message, + final @NotNull Throwable cause + ) { + super(message, cause); + } + + public NbtException( + final @NotNull Throwable cause + ) { + super(cause); + } +} diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/exceptions/UnexpectedNbtTypeException.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/exceptions/UnexpectedNbtTypeException.java new file mode 100644 index 0000000000..f0faa6959b --- /dev/null +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/exceptions/UnexpectedNbtTypeException.java @@ -0,0 +1,20 @@ +package vg.civcraft.mc.civmodcore.nbt.exceptions; + +import net.minecraft.nbt.Tag; +import org.jetbrains.annotations.NotNull; + +/** + * This is for when you expected, for example, a {@link net.minecraft.nbt.StringTag} but got a {@link net.minecraft.nbt.ByteTag} + * instead. + * + * @apiNote This will likely only be relevant when interacting with Mojang NBT since most other things have a dedicated + * {@code .getString()} method, for example. + */ +public final class UnexpectedNbtTypeException extends NbtException { + public UnexpectedNbtTypeException( + final @NotNull Class expected, + final @NotNull Object found + ) { + super("Expected type [" + expected.getName() + "], but found [" + found.getClass().getName() + "] instead!"); + } +} diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/extensions/NBTTagCompoundExtensions.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/extensions/NBTTagCompoundExtensions.java deleted file mode 100644 index d907b335ee..0000000000 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/extensions/NBTTagCompoundExtensions.java +++ /dev/null @@ -1,88 +0,0 @@ -package vg.civcraft.mc.civmodcore.nbt.extensions; - -import java.util.Objects; -import java.util.UUID; -import net.minecraft.nbt.CompoundTag; -import org.jetbrains.annotations.NotNull; -import vg.civcraft.mc.civmodcore.utilities.UuidUtils; - -/** - * Set of extension methods for {@link net.minecraft.nbt.CompoundTag}. - */ -public final class NBTTagCompoundExtensions { - - private static final String UUID_MOST_SUFFIX = "Most"; - private static final String UUID_LEAST_SUFFIX = "Least"; - - /** - * @param self The NBTTagCompound to check if there's a UUID on. - * @param key The key of the UUID. - * @return Returns true if there's a UUID value for that key. - */ - public static boolean hasUUID(@NotNull final CompoundTag self, - final String key) { - return self.hasUUID(key); - } - - /** - * @param self The NBTTagCompound to get the UUID from. - * @param key The key of the UUID. - * @return Returns a UUID, defaulted to 00000000-0000-0000-0000-000000000000. - */ - @NotNull - public static UUID getUUID(@NotNull final CompoundTag self, - final String key) { - if (!hasUUID(self, key)) { - return UuidUtils.IDENTITY; - } - return self.getUUID(key); - } - - /** - * @param self The NBTTagCompound to get the UUID from. - * @param key The key of the UUID. - * @param value The UUID value. - */ - public static void setUUID(@NotNull final CompoundTag self, - @NotNull final String key, - final UUID value) { - Objects.requireNonNull(key); - setUUID(self, key, value, false); - } - - /** - * @param self The NBTTagCompound to get the UUID from. - * @param key The key of the UUID. - * @param value The UUID value. - * @param useLegacyFormat Whether to use Mojang's legacy least+most format. - */ - public static void setUUID(@NotNull final CompoundTag self, - @NotNull final String key, - final UUID value, - final boolean useLegacyFormat) { - Objects.requireNonNull(key); - if (value == null) { - removeUUID(self, key); - return; - } - if (useLegacyFormat) { - self.putLong(key + UUID_MOST_SUFFIX, value.getMostSignificantBits()); - self.putLong(key + UUID_LEAST_SUFFIX, value.getLeastSignificantBits()); - return; - } - self.putUUID(key, value); - } - - /** - * @param self The NBTTagCompound to remove the UUID from. - * @param key The key of the UUID. - */ - public static void removeUUID(@NotNull final CompoundTag self, - @NotNull final String key) { - Objects.requireNonNull(key); - self.remove(key); - self.remove(key + UUID_MOST_SUFFIX); - self.remove(key + UUID_LEAST_SUFFIX); - } - -} diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/extensions/NBTTagListExtensions.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/extensions/NBTTagListExtensions.java deleted file mode 100644 index a642d75a05..0000000000 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/extensions/NBTTagListExtensions.java +++ /dev/null @@ -1,625 +0,0 @@ -package vg.civcraft.mc.civmodcore.nbt.extensions; - -import java.util.UUID; -import net.minecraft.nbt.ByteTag; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.DoubleTag; -import net.minecraft.nbt.FloatTag; -import net.minecraft.nbt.IntArrayTag; -import net.minecraft.nbt.IntTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.LongArrayTag; -import net.minecraft.nbt.LongTag; -import net.minecraft.nbt.NbtUtils; -import net.minecraft.nbt.ShortTag; -import net.minecraft.nbt.StringTag; -import net.minecraft.nbt.Tag; -import org.jetbrains.annotations.NotNull; -import vg.civcraft.mc.civmodcore.nbt.NBTType; -import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; -import vg.civcraft.mc.civmodcore.utilities.UuidUtils; - -/** - * Set of extension methods for {@link net.minecraft.nbt.ListTag}. - */ -public final class NBTTagListExtensions { - - /** - * @param self The NBTTagList to get the element type of. - * @return Returns the NBTTagList's element type. Match it against {@link NBTType}. If it matches - * {@link NBTType#END} then it's an empty list. - */ - public static byte getElementType(@NotNull final ListTag self) { - return self.getElementType(); - } - - /** - * Checks whether a given NBT element is appropriate for the given list. - * - * @param self The NBTTagList to check against. - * @param value The value the check the appropriateness for. - * @return Returns true if the type is appropriate for the list. - */ - public static boolean isAppropriateType(@NotNull final ListTag self, - final Tag value) { - /** This is based off of {@link ListTag#updateType(Tag)} */ - if (value == null || value.getId() == NBTType.END) { - return false; - } - final var elementType = getElementType(self); - return elementType == NBTType.END || elementType == value.getId(); - } - - /** - * @param self The NBTTagList to get the boolean from. - * @param index The index of the boolean. - * @return Returns a boolean, defaulted to false. - */ - public static boolean getBoolean(@NotNull final ListTag self, - final int index) { - return getByte(self, index) != (byte) 0; - } - - /** - * @param self The NBTTagList to set the boolean to. - * @param index The index of the boolean to set. - * @param value The value of the boolean. - */ - public static void setBoolean(@NotNull final ListTag self, - final int index, - final boolean value) { - self.set(index, ByteTag.valueOf(value)); - } - - /** - * @param self The NBTTagList to add the boolean to. - * @param index The index of the boolean to add to. - * @param value The value of the boolean. - */ - public static void addBoolean(@NotNull final ListTag self, - final int index, - final boolean value) { - self.add(index, ByteTag.valueOf(value)); - } - - /** - * @param self The NBTTagList to add the boolean to. - * @param value The value of the boolean. - */ - public static void addBoolean(@NotNull final ListTag self, - final boolean value) { - addElement(self, ByteTag.valueOf(value)); - } - - /** - * @param self The NBTTagList to get the byte from. - * @param index The index of the byte. - * @return Returns a byte, defaulted to 0x00. - */ - public static byte getByte(@NotNull final ListTag self, - final int index) { - if (self.get(index) instanceof ByteTag nbtByte) { - return nbtByte.getAsByte(); - } - return (byte) 0; - } - - /** - * @param self The NBTTagList to set the byte to. - * @param index The index of the byte to set. - * @param value The value of the byte. - */ - public static void setByte(@NotNull final ListTag self, - final int index, - final byte value) { - self.set(index, ByteTag.valueOf(value)); - } - - /** - * @param self The NBTTagList to add the byte to. - * @param index The index of the byte to add to. - * @param value The value of the byte. - */ - public static void addByte(@NotNull final ListTag self, - final int index, - final byte value) { - self.add(index, ByteTag.valueOf(value)); - } - - /** - * @param self The NBTTagList to add the boolean to. - * @param value The value of the boolean. - */ - public static void addByte(@NotNull final ListTag self, - final byte value) { - addElement(self, ByteTag.valueOf(value)); - } - - /** - * @param self The NBTTagList to get the short from. - * @param index The index of the short. - * @return Returns a short, defaulted to 0. - */ - public static short getShort(@NotNull final ListTag self, - final int index) { - return self.getShort(index); - } - - /** - * @param self The NBTTagList to set the short to. - * @param index The index of the short to set. - * @param value The value of the short. - */ - public static void setShort(@NotNull final ListTag self, - final int index, - final short value) { - self.set(index, ShortTag.valueOf(value)); - } - - /** - * @param self The NBTTagList to add the short to. - * @param index The index of the short to add to. - * @param value The value of the short. - */ - public static void addShort(@NotNull final ListTag self, - final int index, - final short value) { - self.add(index, ShortTag.valueOf(value)); - } - - /** - * @param self The NBTTagList to add the short to. - * @param value The value of the short. - */ - public static void addShort(@NotNull final ListTag self, - final short value) { - addElement(self, ShortTag.valueOf(value)); - } - - /** - * @param self The NBTTagList to get the integer from. - * @param index The index of the integer. - * @return Returns an integer, defaulted to 0. - */ - public static int getInt(@NotNull final ListTag self, - final int index) { - return self.getInt(index); - } - - /** - * @param self The NBTTagList to set the integer to. - * @param index The index of the integer to set. - * @param value The value of the integer. - */ - public static void setInt(@NotNull final ListTag self, - final int index, - final int value) { - self.set(index, IntTag.valueOf(value)); - } - - /** - * @param self The NBTTagList to add the integer to. - * @param index The index of the integer to add to. - * @param value The value of the integer. - */ - public static void addInt(@NotNull final ListTag self, - final int index, - final int value) { - self.add(index, IntTag.valueOf(value)); - } - - /** - * @param self The NBTTagList to add the integer to. - * @param value The value of the integer. - */ - public static void addInt(@NotNull final ListTag self, - final int value) { - addElement(self, IntTag.valueOf(value)); - } - - /** - * @param self The NBTTagList to get the long from. - * @param index The index of the long. - * @return Returns a long, defaulted to 0L. - */ - public static long getLong(@NotNull final ListTag self, - final int index) { - if (self.get(index) instanceof LongTag nbtLong) { - return nbtLong.getAsLong(); - } - return 0L; - } - - /** - * @param self The NBTTagList to set the long to. - * @param index The index of the long to set. - * @param value The value of the long. - */ - public static void setLong(@NotNull final ListTag self, - final int index, - final long value) { - self.set(index, LongTag.valueOf(value)); - } - - /** - * @param self The NBTTagList to add the long to. - * @param index The index of the long to add to. - * @param value The value of the long. - */ - public static void addLong(@NotNull final ListTag self, - final int index, - final long value) { - self.add(index, LongTag.valueOf(value)); - } - - /** - * @param self The NBTTagList to add the long to. - * @param value The value of the long. - */ - public static void addLong(@NotNull final ListTag self, - final long value) { - addElement(self, LongTag.valueOf(value)); - } - - /** - * @param self The NBTTagList to get the float from. - * @param index The index of the float. - * @return Returns a float, defaulted to 0.0f. - */ - public static float getFloat(@NotNull final ListTag self, - final int index) { - return self.getFloat(index); - } - - /** - * @param self The NBTTagList to set the float to. - * @param index The index of the float to set. - * @param value The value of the float. - */ - public static void setFloat(@NotNull final ListTag self, - final int index, - final float value) { - self.set(index, FloatTag.valueOf(value)); - } - - /** - * @param self The NBTTagList to add the float to. - * @param index The index of the float to add to. - * @param value The value of the float. - */ - public static void addFloat(@NotNull final ListTag self, - final int index, - final float value) { - self.add(index, FloatTag.valueOf(value)); - } - - /** - * @param self The NBTTagList to add the float to. - * @param value The value of the float. - */ - public static void addFloat(@NotNull final ListTag self, - final float value) { - addElement(self, FloatTag.valueOf(value)); - } - - /** - * @param self The NBTTagList to get the double from. - * @param index The index of the double. - * @return Returns a double, defaulted to 0.0d. - */ - public static double getDouble(@NotNull final ListTag self, - final int index) { - return self.getDouble(index); - } - - /** - * @param self The NBTTagList to set the double to. - * @param index The index of the double to set. - * @param value The value of the double. - */ - public static void setDouble(@NotNull final ListTag self, - final int index, - final double value) { - self.set(index, DoubleTag.valueOf(value)); - } - - /** - * @param self The NBTTagList to add the double to. - * @param index The index of the double to add to. - * @param value The value of the double. - */ - public static void addDouble(@NotNull final ListTag self, - final int index, - final double value) { - self.add(index, DoubleTag.valueOf(value)); - } - - /** - * @param self The NBTTagList to add the double to. - * @param value The value of the double. - */ - public static void addDouble(@NotNull final ListTag self, - final double value) { - addElement(self, DoubleTag.valueOf(value)); - } - - /** - * @param self The NBTTagList to get the UUID from. - * @param index The index of the UUID. - * @return Returns a UUID, defaulted to 00000000-0000-0000-0000-000000000000. - */ - public static UUID getUUID(@NotNull final ListTag self, - final int index) { - final Tag value = self.get(index); - if (value instanceof IntArrayTag nbtIntArray) { - /** Copied from {@link net.minecraft.nbt.CompoundTag#getUUID(String)} */ - return NbtUtils.loadUUID(nbtIntArray); - } - if (value instanceof CompoundTag nbtCompound) { - return NBTTagCompoundExtensions.getUUID(nbtCompound, NBTCompound.UUID_KEY); - } - return UuidUtils.IDENTITY; - } - - /** - * @param self The NBTTagList to set the UUID to. - * @param index The index of the UUID to set. - * @param value The value of the UUID. - */ - public static void setUUID(@NotNull final ListTag self, - final int index, - final UUID value) { - /** Copied from {@link CompoundTag#putUUID(String, UUID)} */ - self.set(index, NbtUtils.createUUID(value)); - } - - /** - * @param self The NBTTagList to add the UUID to. - * @param index The index of the UUID to add to. - * @param value The value of the UUID. - */ - public static void addUUID(@NotNull final ListTag self, - final int index, - final UUID value) { - /** Copied from {@link CompoundTag#putUUID(String, UUID)} */ - self.add(index, NbtUtils.createUUID(value)); - } - - /** - * @param self The NBTTagList to add the UUID to. - * @param value The value of the UUID. - */ - public static void addUUID(@NotNull final ListTag self, - final UUID value) { - /** Copied from {@link CompoundTag#putUUID(String, UUID)} */ - addElement(self, NbtUtils.createUUID(value)); - } - - /** - * @param self The NBTTagList to get the String from. - * @param index The index of the String. - * @return Returns a String, defaulted to "". - */ - public static String getString(@NotNull final ListTag self, - final int index) { - if (self.get(index) instanceof StringTag nbtString) { - return nbtString.getAsString(); - } - return ""; - } - - /** - * @param self The NBTTagList to set the String to. - * @param index The index of the String to set. - * @param value The value of the String. - */ - public static void setString(@NotNull final ListTag self, - final int index, - final String value) { - self.set(index, StringTag.valueOf(value)); - } - - /** - * @param self The NBTTagList to add the String to. - * @param index The index of the String to add to. - * @param value The value of the String. - */ - public static void addString(@NotNull final ListTag self, - final int index, - final String value) { - self.add(index, StringTag.valueOf(value)); - } - - /** - * @param self The NBTTagList to add the String to. - * @param value The value of the String. - */ - public static void addString(@NotNull final ListTag self, - final String value) { - addElement(self, StringTag.valueOf(value)); - } - - /** - * @param self The NBTTagList to get the compound from. - * @param index The index of the compound. - * @return Returns a compound, defaulted to {}. - */ - public static CompoundTag getCompound(@NotNull final ListTag self, - final int index) { - if (self.get(index) instanceof CompoundTag nbtCompound) { - return nbtCompound; - } - return new CompoundTag(); - } - - /** - * @param self The NBTTagList to set the compound to. - * @param index The index of the compound to set. - * @param value The value of the compound. - */ - public static void setCompound(@NotNull final ListTag self, - final int index, - final CompoundTag value) { - self.set(index, value); - } - - /** - * @param self The NBTTagList to add the compound to. - * @param index The index of the compound to add to. - * @param value The value of the compound. - */ - public static void addCompound(@NotNull final ListTag self, - final int index, - final CompoundTag value) { - self.add(index, value); - } - - /** - * @param self The NBTTagList to add the compound to. - * @param value The value of the compound. - */ - public static void addCompound(@NotNull final ListTag self, - final CompoundTag value) { - addElement(self, value); - } - - /** - * @param self The NBTTagList to get the integer array from. - * @param index The index of the integer array. - * @return Returns an integer array, defaulted to []. - */ - public static int[] getIntArray(@NotNull final ListTag self, - final int index) { - return self.getIntArray(index); - } - - /** - * @param self The NBTTagList to set the integer array to. - * @param index The index of the integer array to set. - * @param values The value of the integer array. - */ - public static void setIntArray(@NotNull final ListTag self, - final int index, - final int[] values) { - self.set(index, new IntArrayTag(values)); - } - - /** - * @param self The NBTTagList to add the integer array to. - * @param index The index of the integer array to add to. - * @param values The value of the integer array. - */ - public static void addIntArray(@NotNull final ListTag self, - final int index, - final int[] values) { - self.add(index, new IntArrayTag(values)); - } - - /** - * @param self The NBTTagList to add the integer array to. - * @param values The value of the integer array. - */ - public static void addIntArray(@NotNull final ListTag self, - final int[] values) { - addElement(self, new IntArrayTag(values)); - } - - /** - * @param self The NBTTagList to get the long array from. - * @param index The index of the long array. - * @return Returns an long array, defaulted to []. - */ - public static long[] getLongArray(@NotNull final ListTag self, - final int index) { - return self.getLongArray(index); - } - - /** - * @param self The NBTTagList to set the long array to. - * @param index The index of the long array to set. - * @param values The value of the long array. - */ - public static void setLongArray(@NotNull final ListTag self, - final int index, - final long[] values) { - self.set(index, new LongArrayTag(values)); - } - - /** - * @param self The NBTTagList to add the long array to. - * @param index The index of the long array to add to. - * @param values The value of the long array. - */ - public static void addLongArray(@NotNull final ListTag self, - final int index, - final long[] values) { - self.add(index, new LongArrayTag(values)); - } - - /** - * @param self The NBTTagList to add the long array to. - * @param values The value of the integer array. - */ - public static void addLongArray(@NotNull final ListTag self, - final long[] values) { - addElement(self, new LongArrayTag(values)); - } - - /** - * @param self The NBTTagList to get the list from. - * @param index The index of the list. - * @return Returns an list, defaulted to []. - */ - public static ListTag getList(@NotNull final ListTag self, - final int index) { - return self.getList(index); - } - - /** - * @param self The NBTTagList to set the list to. - * @param index The index of the list to set. - * @param value The value of the list. - */ - public static void setList(@NotNull final ListTag self, - final int index, - final ListTag value) { - self.set(index, value); - } - - /** - * @param self The NBTTagList to add the list to. - * @param index The index of the list to add to. - * @param value The value of the list. - */ - public static void addList(@NotNull final ListTag self, - final int index, - final ListTag value) { - self.add(index, value); - } - - /** - * @param self The NBTTagList to add the list to. - * @param value The value of the list. - */ - public static void addList(@NotNull final ListTag self, - final ListTag value) { - addElement(self, value); - } - - /** - * An alternative for {@link ListTag#add(Object)} for that respects type consistency. - * - * @param self The NBTTagList to add the list to. - * @param value The NBT element to add. - */ - public static void addElement(@NotNull final ListTag self, - @NotNull final Tag value) { - if (!isAppropriateType(self, value)) { - throw new UnsupportedOperationException(String.format( - "Trying to add tag of type %d to list of %d", - value.getId(), getElementType(self))); - } - self.add(value); - } - -} diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/wrappers/NBTCompound.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/wrappers/NBTCompound.java deleted file mode 100644 index bb6636af80..0000000000 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/wrappers/NBTCompound.java +++ /dev/null @@ -1,878 +0,0 @@ -package vg.civcraft.mc.civmodcore.nbt.wrappers; - -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.stream.Stream; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.DoubleTag; -import net.minecraft.nbt.FloatTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.ShortTag; -import net.minecraft.nbt.StringTag; -import net.minecraft.nbt.Tag; -import org.apache.commons.lang3.StringUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import vg.civcraft.mc.civmodcore.nbt.NBTType; -import vg.civcraft.mc.civmodcore.nbt.extensions.NBTTagListExtensions; -import vg.civcraft.mc.civmodcore.utilities.UuidUtils; - -public class NBTCompound { - - public static final String NULL_STRING = "\u0000"; - public static final String UUID_KEY = "uuid"; - private static final String UUID_MOST_SUFFIX = "Most"; - private static final String UUID_LEAST_SUFFIX = "Least"; - - private final CompoundTag tag; - - /** - * Creates a new NBTCompound. - */ - public NBTCompound() { - this.tag = new CompoundTag(); - } - - /** - * Creates a new NBTCompound based on an existing inner-map. - */ - public NBTCompound(@NotNull final Map raw) { - this.tag = new CompoundTag(Objects.requireNonNull(raw)) { - }; - } - - /** - * Creates a new NBTCompound by wrapping a given NMS compound. - * - * @param tag The NBTTagCompound to wrap. - */ - public NBTCompound(@NotNull final CompoundTag tag) { - this.tag = Objects.requireNonNull(tag); - } - - /** - * @return Returns the internal NMS compound. - */ - @NotNull - public CompoundTag getRAW() { - return this.tag; - } - - /** - * Returns the size of the tag compound. - * - * @return The size of the tag compound. - */ - public int size() { - return this.tag.size(); - } - - /** - * Checks if the tag compound is empty. - * - * @return Returns true if the tag compound is empty. - */ - public boolean isEmpty() { - return this.tag.isEmpty(); - } - - /** - * Checks if the tag compound contains a particular key. - * - * @param key The key to check. - * @return Returns true if the contains the given key. - */ - public boolean hasKey(final String key) { - return this.tag.getAllKeys().contains(key); - } - - /** - * Checks if the tag compound contains a particular key of a particular type. - * - * @param key The key to check. - * @param type The type to check for. - * @return Returns true if the contains the given key of the given type. - */ - public boolean hasKeyOfType(@NotNull final String key, - final int type) { - return this.tag.contains(key, type); - } - - /** - * Gets the keys within this compound. - * - * @return Returns the set of keys. - */ - @NotNull - public Set getKeys() { - return this.tag.getAllKeys(); - } - - /** - *

Removes a key and its respective value from the tag compound, if it exists.

- * - *

Note: If you're removing a UUID, use {@link NBTCompound#removeUUID(String)} instead.

- * - * @param key The key to remove. - */ - public void remove(final String key) { - this.tag.remove(key); - } - - /** - * Clears all values from the tag compound. - */ - public void clear() { - this.tag.getAllKeys().clear(); - } - - /** - * Adopts a copy of the NBT data from another compound. - * - * @param nbt The NBT data to copy and adopt. - */ - public void adopt(@NotNull final NBTCompound nbt) { - Objects.requireNonNull(nbt); - if (this == nbt || this.tag == nbt.tag) { - return; - } - this.tag.getAllKeys().clear(); - for (String key : nbt.tag.getAllKeys()) { - this.tag.put(key, nbt.tag.get(key)); - } - } - - /** - * Gets a primitive boolean value from a key. - * - * @param key The key to get the boolean from. - * @return The value of the key, default: FALSE - */ - public boolean getBoolean(@NotNull final String key) { - Objects.requireNonNull(key); - return this.tag.getBoolean(key); - } - - /** - * Sets a primitive boolean value to a key. - * - * @param key The key to set to the boolean to. - * @param value The boolean to set to the key. - */ - public void setBoolean(@NotNull final String key, - final boolean value) { - Objects.requireNonNull(key); - this.tag.putBoolean(key, value); - } - - /** - * Gets a primitive byte value from a key. - * - * @param key The key to get the byte from. - * @return The value of the key, default: 0 - */ - public byte getByte(@NotNull final String key) { - Objects.requireNonNull(key); - return this.tag.getByte(key); - } - - /** - * Sets a primitive byte value to a key. - * - * @param key The key to set to the byte to. - * @param value The byte to set to the key. - */ - public void setByte(@NotNull final String key, - final byte value) { - Objects.requireNonNull(key); - this.tag.putByte(key, value); - } - - /** - * Gets a primitive short value from a key. - * - * @param key The key to get the short from. - * @return The value of the key, default: 0 - */ - public short getShort(@NotNull final String key) { - Objects.requireNonNull(key); - return this.tag.getShort(key); - } - - /** - * Sets a primitive short value to a key. - * - * @param key The key to set to the short to. - * @param value The short to set to the key. - */ - public void setShort(@NotNull final String key, - final short value) { - Objects.requireNonNull(key); - this.tag.putShort(key, value); - } - - /** - * Gets a primitive integer value from a key. - * - * @param key The key to get the integer from. - * @return The value of the key, default: 0 - */ - public int getInt(@NotNull final String key) { - Objects.requireNonNull(key); - return this.tag.getInt(key); - } - - /** - * Sets a primitive integer value to a key. - * - * @param key The key to set to the integer to. - * @param value The integer to set to the key. - */ - public void setInt(@NotNull final String key, - final int value) { - Objects.requireNonNull(key); - this.tag.putInt(key, value); - } - - /** - * Gets a primitive long value from a key. - * - * @param key The key to get the long from. - * @return The value of the key, default: 0L - */ - public long getLong(@NotNull final String key) { - Objects.requireNonNull(key); - return this.tag.getLong(key); - } - - /** - * Sets a primitive long value to a key. - * - * @param key The key to set to the long to. - * @param value The long to set to the key. - */ - public void setLong(@NotNull final String key, - final long value) { - Objects.requireNonNull(key); - this.tag.putLong(key, value); - } - - /** - * Gets a primitive float value from a key. - * - * @param key The key to get the float from. - * @return The value of the key, default: 0f - */ - public float getFloat(@NotNull final String key) { - Objects.requireNonNull(key); - return this.tag.getFloat(key); - } - - /** - * Sets a primitive float value to a key. - * - * @param key The key to set to the float to. - * @param value The float to set to the key. - */ - public void setFloat(@NotNull final String key, - final float value) { - Objects.requireNonNull(key); - this.tag.putFloat(key, value); - } - - /** - * Gets a primitive double value from a key. - * - * @param key The key to get the double from. - * @return The value of the key, default: 0d - */ - public double getDouble(@NotNull final String key) { - Objects.requireNonNull(key); - return this.tag.getDouble(key); - } - - /** - * Sets a primitive double value to a key. - * - * @param key The key to set to the double to. - * @param value The double to set to the key. - */ - public void setDouble(@NotNull final String key, - final double value) { - Objects.requireNonNull(key); - this.tag.putDouble(key, value); - } - - /** - * Checks whether a UUID exists at the given key. - * - * @param key The key of the UUID. - * @return Returns true if a UUID exists at the given key. - */ - public boolean hasUUID(@NotNull final String key) { - return this.tag.hasUUID(key); - } - - /** - * Gets a UUID value from a key. - * - * @param key The key to get the UUID from. - * @return The value of the key, default: 00000000-0000-0000-0000-000000000000 - */ - public UUID getUUID(@NotNull final String key) { - Objects.requireNonNull(key); - return !this.tag.hasUUID(key) ? UuidUtils.IDENTITY : this.tag.getUUID(key); - } - - /** - * Gets a UUID value from a key. - * - * @param key The key to get the UUID from. - * @return The value of the key, default: NULL - */ - @Nullable - public UUID getNullableUUID(@NotNull final String key) { - Objects.requireNonNull(key); - return !this.tag.hasUUID(key) ? null : this.tag.getUUID(key); - } - - /** - * Sets a UUID value to a key. - * - * @param key The key to set to the UUID to. - * @param value The UUID to set to the key. - */ - public void setUUID(@NotNull final String key, - final UUID value) { - setUUID(key, value, false); - } - - /** - * Sets a UUID value to a key. - * - * @param key The key to set to the UUID to. - * @param value The UUID to set to the key. - * @param useMojangFormat Whether to save as Mojang's least+most, or the updated int array. - */ - public void setUUID(@NotNull final String key, - final UUID value, - final boolean useMojangFormat) { - Objects.requireNonNull(key); - if (value == null) { - removeUUID(key); - } else { - if (useMojangFormat) { - this.tag.putLong(key + UUID_MOST_SUFFIX, value.getMostSignificantBits()); - this.tag.putLong(key + UUID_LEAST_SUFFIX, value.getLeastSignificantBits()); - } else { - this.tag.putUUID(key, value); - } - } - } - - /** - * Removes a UUID value, which is necessary because Bukkit stores UUIDs by splitting up the two significant parts - * into their own values. - * - * @param key The key of the UUID to remove. - */ - public void removeUUID(@NotNull final String key) { - Objects.requireNonNull(key); - remove(key); - remove(key + UUID_MOST_SUFFIX); - remove(key + UUID_LEAST_SUFFIX); - } - - /** - * Gets a String value from a key. - * - * @param key The key to get the String from. - * @return The value of the key, default: "" - */ - @NotNull - public String getString(@NotNull final String key) { - Objects.requireNonNull(key); - return this.tag.getString(key); - } - - /** - * Gets a String value from a key. - * - * @param key The key to get the String from. - * @return The value of the key, default: NULL - */ - @Nullable - public String getNullableString(@NotNull final String key) { - Objects.requireNonNull(key); - if (!this.tag.contains(key, 8)) { - return null; - } - final String value = this.tag.getString(key); - if (NULL_STRING.equals(value)) { - return null; - } - return value; - } - - /** - * Sets a String value to a key. - * - * @param key The key to set to the String to. - * @param value The String to set to the key. - */ - public void setString(@NotNull final String key, - final String value) { - Objects.requireNonNull(key); - if (value == null) { - remove(key); - } else { - this.tag.putString(key, value); - } - } - - /** - * Gets an NBT compound from a key. - * - * @param key The key to get the NBT compound from. - * @return The value of the key, default: {} - */ - @NotNull - public NBTCompound getCompound(@NotNull final String key) { - final var found = getNullableCompound(key); - return found == null ? new NBTCompound() : found; - } - - /** - * Gets an NBT compound from a key. - * - * @param key The key to get the NBT compound from. - * @return The value of the key, default: NULL - */ - @Nullable - public NBTCompound getNullableCompound(@NotNull final String key) { - Objects.requireNonNull(key); - final var found = this.tag.get(key); - if (found instanceof CompoundTag compound) { - return new NBTCompound(compound); - } - return null; - } - - /** - * Sets an NBT compound to a key. - * - * @param key The key to set to the NBT compound to. - * @param value The NBT compound to set to the key. - */ - public void setCompound(@NotNull final String key, - final NBTCompound value) { - Objects.requireNonNull(key); - if (value == null) { - remove(key); - } else { - this.tag.put(key, value.tag); - } - } - - /** - * Gets a Component value from a key. - * - * @param key The key of the Component. - * @return Returns a Component, defaulted to {@link Component#empty()} - */ - public Component getComponent(@NotNull final String key) { - Objects.requireNonNull(key); - if (hasKeyOfType(key, NBTType.STRING)) { - return Component.empty(); - } else { - return GsonComponentSerializer.gson().deserialize(getString(key)); - } - } - - /** - * Sets a Component value to a key. - * - * @param key The key of the Component to set. - * @param value The Component value to set. - */ - public void setComponent(@NotNull final String key, - @NotNull final Component value) { - Objects.requireNonNull(key); - Objects.requireNonNull(value); - setString(key, GsonComponentSerializer.gson().serialize(value)); - } - - // ------------------------------------------------------------ - // Array Functions - // ------------------------------------------------------------ - - /** - * Gets an array of primitive booleans from a key. - * - * @param key The key to of the array. - * @return The values of the key, default: empty array - */ - @NotNull - public boolean[] getBooleanArray(@NotNull final String key) { - Objects.requireNonNull(key); - final byte[] cache = getByteArray(key); - final boolean[] result = new boolean[cache.length]; - for (int i = 0; i < cache.length; i++) { - result[i] = cache[i] != 0; - } - return result; - } - - /** - * Sets an array of primitive booleans to a key. - * - * @param key The key to set to array to. - * @param booleans The booleans to set to the key. - */ - public void setBooleanArray(@NotNull final String key, - final boolean[] booleans) { - Objects.requireNonNull(key); - if (booleans == null) { - remove(key); - return; - } - final byte[] converted = new byte[booleans.length]; - for (int i = 0; i < booleans.length; i++) { - converted[i] = (byte) (booleans[i] ? 1 : 0); - } - setByteArray(key, converted); - } - - /** - * Gets an array of primitive bytes from a key. - * - * @param key The key to get the values of. - * @return The values of the key, default: empty array - */ - @NotNull - public byte[] getByteArray(@NotNull final String key) { - Objects.requireNonNull(key); - return this.tag.getByteArray(key); - } - - /** - * Sets an array of primitive bytes to a key. - * - * @param key The key to set to the bytes to. - * @param bytes The bytes to set to the key. - */ - public void setByteArray(@NotNull final String key, - final byte[] bytes) { - Objects.requireNonNull(key); - if (bytes == null) { - remove(key); - return; - } - this.tag.putByteArray(key, bytes); - } - - /** - * Gets an array of primitive shorts from a key. - * - * @param key The key to get the values of. - * @return The values of the key, default: empty array - */ - @NotNull - public short[] getShortArray(@NotNull final String key) { - Objects.requireNonNull(key); - final ListTag list = this.tag.getList(key, NBTType.SHORT); - final short[] result = new short[list.size()]; - for (int i = 0; i < result.length; i++) { - if (list.get(i) instanceof ShortTag nbtShort) { - result[i] = nbtShort.getAsShort(); - } - } - return result; - } - - /** - * Sets an array of primitive bytes to a key. - * - * @param key The key to set to values to. - * @param shorts The shorts to set to the key. - */ - public void setShortArray(@NotNull final String key, - final short[] shorts) { - Objects.requireNonNull(key); - if (shorts == null) { - remove(key); - return; - } - final ListTag list = new ListTag(); - for (final short value : shorts) { - list.add(ShortTag.valueOf(value)); - } - this.tag.put(key, list); - } - - /** - * Gets an array of primitive integers from a key. - * - * @param key The key to get the values of. - * @return The values of the key, default: empty array - */ - @NotNull - public int[] getIntArray(@NotNull final String key) { - Objects.requireNonNull(key); - return this.tag.getIntArray(key); - } - - /** - * Sets an array of primitive integers to a key. - * - * @param key The key to set to values to. - * @param ints The values to set to the key. - */ - public void setIntArray(@NotNull final String key, - final int[] ints) { - Objects.requireNonNull(key); - if (ints == null) { - remove(key); - return; - } - this.tag.putIntArray(key, ints); - } - - /** - * Gets an array of primitive longs from a key. - * - * @param key The key to get the values of. - * @return The values of the key, default: empty array - */ - @NotNull - public long[] getLongArray(@NotNull final String key) { - Objects.requireNonNull(key); - return this.tag.getLongArray(key); - } - - /** - * Sets an array of primitive longs to a key. - * - * @param key The key to set to values to. - * @param longs The values to set to the key. - */ - public void setLongArray(@NotNull final String key, - final long[] longs) { - Objects.requireNonNull(key); - if (longs == null) { - remove(key); - return; - } - this.tag.putLongArray(key, longs); - } - - /** - * Gets an array of primitive floats from a key. - * - * @param key The key to get the values of. - * @return The values of the key, default: empty array - */ - @NotNull - public float[] getFloatArray(@NotNull final String key) { - Objects.requireNonNull(key); - final ListTag list = this.tag.getList(key, NBTType.FLOAT); - final float[] result = new float[list.size()]; - for (int i = 0; i < result.length; i++) { - result[i] = list.getFloat(i); - } - return result; - } - - /** - * Sets an array of primitive floats to a key. - * - * @param key The key to set to values to. - * @param floats The values to set to the key. - */ - public void setFloatArray(@NotNull final String key, - final float[] floats) { - Objects.requireNonNull(key); - if (floats == null) { - remove(key); - return; - } - final ListTag list = new ListTag(); - for (final float value : floats) { - list.add(FloatTag.valueOf(value)); - } - this.tag.put(key, list); - } - - /** - * Gets an array of primitive doubles from a key. - * - * @param key The key to get the values of. - * @return The values of the key, default: empty array - */ - @NotNull - public double[] getDoubleArray(@NotNull final String key) { - Objects.requireNonNull(key); - final ListTag list = this.tag.getList(key, NBTType.DOUBLE); - final double[] result = new double[list.size()]; - for (int i = 0; i < result.length; i++) { - result[i] = list.getDouble(i); - } - return result; - } - - /** - * Sets an array of primitive doubles to a key. - * - * @param key The key to set to values to. - * @param doubles The values to set to the key. - */ - public void setDoubleArray(@NotNull final String key, - final double[] doubles) { - Objects.requireNonNull(key); - if (doubles == null) { - remove(key); - return; - } - final ListTag list = new ListTag(); - for (final double value : doubles) { - list.add(DoubleTag.valueOf(value)); - } - this.tag.put(key, list); - } - - /** - * Gets an array of UUIDs from a key. - * - * @param key The key to get the values of. - * @return The values of the key, default: empty array - */ - @NotNull - public UUID[] getUUIDArray(@NotNull final String key) { - Objects.requireNonNull(key); - if (this.tag.get(key) instanceof ListTag list) { - final UUID[] result = new UUID[list.size()]; - for (int i = 0, l = list.size(); i < l; i++) { - result[i] = NBTTagListExtensions.getUUID(list, i); - } - return result; - } - return new UUID[0]; - } - - /** - * Sets an array of UUIDs to a key. - * - * @param key The key to set to values to. - * @param uuids The values to set to the key. - */ - public void setUUIDArray(@NotNull final String key, - final UUID[] uuids) { - Objects.requireNonNull(key); - if (uuids == null) { - remove(key); - return; - } - final ListTag list = new ListTag(); - for (final UUID value : uuids) { - NBTTagListExtensions.addUUID(list, value); - } - this.tag.put(key, list); - } - - /** - * Gets an array of Strings from a key. - * - * @param key The key to get the values of. - * @return The values of the key, default: empty array - */ - @NotNull - public String[] getStringArray(@NotNull final String key) { - Objects.requireNonNull(key); - final ListTag list = this.tag.getList(key, NBTType.STRING); - final String[] result = new String[list.size()]; - for (int i = 0; i < result.length; i++) { - result[i] = list.get(i) instanceof StringTag nbtString ? nbtString.getAsString() : ""; - } - return result; - } - - /** - * Sets an array of Strings to a key. - * - * @param key The key to set to values to. - * @param strings The values to set to the key. - */ - public void setStringArray(@NotNull final String key, - final String[] strings) { - Objects.requireNonNull(key); - if (strings == null) { - remove(key); - return; - } - final ListTag list = new ListTag(); - List.of(strings).forEach((string) -> list.add(StringTag.valueOf(string))); - this.tag.put(key, list); - } - - /** - * Gets an array of tag compounds from a key. - * - * @param key The key to get the values of. - * @return The values of the key, default: empty array - */ - @NotNull - public NBTCompound[] getCompoundArray(@NotNull final String key) { - Objects.requireNonNull(key); - final ListTag list = this.tag.getList(key, NBTType.COMPOUND); - final NBTCompound[] result = new NBTCompound[list.size()]; - for (int i = 0; i < result.length; i++) { - result[i] = new NBTCompound(list.getCompound(i)); - } - return result; - } - - /** - * Sets an array of tag compounds to a key. - * - * @param key The key to set to values to. - * @param compounds The values to set to the key. - */ - public void setCompoundArray(@NotNull final String key, - final NBTCompound[] compounds) { - Objects.requireNonNull(key); - if (compounds == null) { - remove(key); - return; - } - final ListTag list = new ListTag(); - list.addAll(Stream.of(compounds).map((nbt) -> nbt.tag).toList()); - this.tag.put(key, list); - } - - // ------------------------------------------------------------ - // Object Overrides - // ------------------------------------------------------------ - - @Override - public boolean equals(final Object object) { - if (this == object) { - return true; - } - if (object instanceof NBTCompound other) { - return Objects.equals(this.tag, other.tag); - } - return false; - } - - @NotNull - @Override - public String toString() { - return "NBTCompound" + this.tag; - } - -} diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/players/PlayerNames.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/players/PlayerNames.java index 9097a37b14..dafd8073cb 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/players/PlayerNames.java +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/players/PlayerNames.java @@ -3,6 +3,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.stream.Stream; import org.apache.commons.lang3.StringUtils; @@ -12,6 +13,7 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerLoginEvent; +import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; @@ -19,14 +21,18 @@ public final class PlayerNames implements Listener { private static final Set names = new HashSet<>(); - public PlayerNames() { + public PlayerNames(Plugin plugin) { names.clear(); - names.addAll( - Stream.of(Bukkit.getOfflinePlayers()) + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + OfflinePlayer[] players = Bukkit.getOfflinePlayers(); + List namesList = Stream.of(players) .map(OfflinePlayer::getName) .filter(StringUtils::isNotBlank) - .toList() - ); + .toList(); + Bukkit.getScheduler().runTask(plugin, () -> { + names.addAll(namesList); + }); + }); } @EventHandler( diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/players/settings/PlayerSetting.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/players/settings/PlayerSetting.java index 077b9aad01..804ae2a0ee 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/players/settings/PlayerSetting.java +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/players/settings/PlayerSetting.java @@ -8,7 +8,7 @@ import java.util.Map; import java.util.TreeMap; import java.util.UUID; -import org.apache.commons.lang.WordUtils; +import org.apache.commons.lang3.text.WordUtils; import org.bukkit.ChatColor; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/utilities/CivLogger.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/utilities/CivLogger.java index 3dd635924b..0ce0237398 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/utilities/CivLogger.java +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/utilities/CivLogger.java @@ -4,6 +4,7 @@ import java.util.Objects; import java.util.logging.LogRecord; import java.util.logging.Logger; +import io.papermc.paper.plugin.configuration.PluginMeta; import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.reflect.FieldUtils; import org.bukkit.Bukkit; @@ -67,7 +68,7 @@ private static CivLogger INTERNAL_generateLogger(@NotNull final ClassLoader load final var descriptionField = FieldUtils.getDeclaredField(PluginClassLoader.class, "description", true); try { final var description = (PluginDescriptionFile) descriptionField.get(loader); - final var logger = PaperPluginLogger.getLogger(description); + final var logger = PaperPluginLogger.getLogger((PluginMeta) description); return new CivLogger(logger, clazz.getSimpleName()); } catch (final IllegalAccessException ignored) { } diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/world/locations/QTBox.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/world/locations/QTBox.java index eb15b2b936..d14e5b33a9 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/world/locations/QTBox.java +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/world/locations/QTBox.java @@ -1,5 +1,8 @@ package vg.civcraft.mc.civmodcore.world.locations; +import com.github.davidmoten.rtree2.geometry.Rectangle; +import com.github.davidmoten.rtree2.geometry.internal.RectangleDouble; + public interface QTBox { int qtXMin(); @@ -14,4 +17,7 @@ public interface QTBox { int qtZMax(); + default Rectangle asRectangle() { + return RectangleDouble.create(qtXMin(), qtZMin(), qtXMax(), qtZMax()); + } } diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/world/locations/chunkmeta/ChunkCoord.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/world/locations/chunkmeta/ChunkCoord.java index d8f7d04fd7..c4c4819996 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/world/locations/chunkmeta/ChunkCoord.java +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/world/locations/chunkmeta/ChunkCoord.java @@ -1,6 +1,8 @@ package vg.civcraft.mc.civmodcore.world.locations.chunkmeta; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; @@ -52,14 +54,20 @@ public World getWorld() { void addChunkMeta(ChunkMeta chunkMeta) { chunkMeta.setWorld(this.world); - chunkMetas.put(chunkMeta.getPluginID(), chunkMeta); + synchronized (chunkMetas) { + chunkMetas.put(chunkMeta.getPluginID(), chunkMeta); + } } /** * Writes all data held by this instance to the database */ void fullyPersist() { - for (ChunkMeta chunkMeta : chunkMetas.values()) { + List> values; + synchronized (chunkMetas) { + values = new ArrayList<>(chunkMetas.values()); + } + for (ChunkMeta chunkMeta : values) { persistChunkMeta(chunkMeta); } } @@ -70,7 +78,10 @@ void fullyPersist() { * @param id Internal id of the plugin to save data for */ void persistPlugin(short id) { - ChunkMeta chunkMeta = chunkMetas.get(id); + ChunkMeta chunkMeta; + synchronized (chunkMetas) { + chunkMeta = chunkMetas.get(id); + } if (chunkMeta != null) { persistChunkMeta(chunkMeta); } @@ -99,11 +110,13 @@ private static void persistChunkMeta(ChunkMeta chunkMeta) { * Forget all data which is not supposed to be held in memory permanently */ void deleteNonPersistentData() { - Iterator>> iter = chunkMetas.entrySet().iterator(); - while (iter.hasNext()) { - ChunkMeta meta = iter.next().getValue(); - if (!meta.loadAlways()) { - iter.remove(); + synchronized (chunkMetas) { + Iterator>> iter = chunkMetas.entrySet().iterator(); + while (iter.hasNext()) { + ChunkMeta meta = iter.next().getValue(); + if (!meta.loadAlways()) { + iter.remove(); + } } } } @@ -143,13 +156,17 @@ ChunkMeta getMeta(short pluginID, boolean alwaysLoaded) { if (!alwaysLoaded) loadAll(LoadStatisticManager.MainThreadIndex); - return chunkMetas.get(pluginID); + synchronized (chunkMetas) { + return chunkMetas.get(pluginID); + } } boolean hasPermanentlyLoadedData() { - for (ChunkMeta meta : chunkMetas.values()) { - if (meta.loadAlways()) { - return true; + synchronized (chunkMetas) { + for (ChunkMeta meta : chunkMetas.values()) { + if (meta.loadAlways()) { + return true; + } } } return false; @@ -213,11 +230,13 @@ void minecraftChunkLoaded() { boolean hasBeenLoadedBefore = this.lastLoadedTime != INVALID_TIME; this.lastLoadedTime = System.currentTimeMillis(); if (hasBeenLoadedBefore) { - for (ChunkMeta meta : chunkMetas.values()) { - try { - meta.handleChunkCacheReuse(); - } catch (Exception ex) { - ex.printStackTrace(); + synchronized (chunkMetas) { + for (ChunkMeta meta : chunkMetas.values()) { + try { + meta.handleChunkCacheReuse(); + } catch (Exception ex) { + ex.printStackTrace(); + } } } } @@ -237,11 +256,13 @@ public boolean isChunkLoaded() { */ void minecraftChunkUnloaded() { this.lastUnloadedTime = System.currentTimeMillis(); - for (ChunkMeta meta : chunkMetas.values()) { - try { - meta.handleChunkUnload(); - } catch (Exception ex) { - ex.printStackTrace(); + synchronized (chunkMetas) { + for (ChunkMeta meta : chunkMetas.values()) { + try { + meta.handleChunkUnload(); + } catch (Exception ex) { + ex.printStackTrace(); + } } } } diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/world/locations/chunkmeta/ChunkMetaListener.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/world/locations/chunkmeta/ChunkMetaListener.java index 62d855dae3..ed17409999 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/world/locations/chunkmeta/ChunkMetaListener.java +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/world/locations/chunkmeta/ChunkMetaListener.java @@ -34,13 +34,17 @@ public ChunkMetaListener(GlobalChunkMetaManager manager, ChunkMetaViewTracker vi this.viewTracker = viewTracker; this.unloadQueue = new LinkedBlockingQueue<>(); unloadConsumer = new Thread(() -> { - while (true) { - try { - Chunk chunk = unloadQueue.take(); - manager.unloadChunkData(chunk); - } catch (InterruptedException e) { - e.printStackTrace(); + try { + while (true) { + try { + Chunk chunk = unloadQueue.take(); + manager.unloadChunkData(chunk); + } catch (RuntimeException e) { + CHUNK_META_LOGGER.warn("Handling chunk unloads", e); + } } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } }, "CivModCore chunk unload handler"); unloadConsumer.start(); diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/world/locations/chunkmeta/GlobalChunkMetaManager.java b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/world/locations/chunkmeta/GlobalChunkMetaManager.java index 624eae72c7..895865fa72 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/world/locations/chunkmeta/GlobalChunkMetaManager.java +++ b/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/world/locations/chunkmeta/GlobalChunkMetaManager.java @@ -125,6 +125,9 @@ public void insertChunkMeta(short pluginID, World world, int chunkX, int chunkZ, } void loadChunkData(Chunk chunk) { + if (isExempt(chunk.getWorld())) { + return; + } WorldChunkMetaManager worldManager = worldToManager.get(chunk.getWorld().getUID()); if (worldManager == null) { throw new IllegalStateException("No world manager for chunk at " + chunk.toString()); @@ -133,6 +136,9 @@ void loadChunkData(Chunk chunk) { } void unloadChunkData(Chunk chunk) { + if (isExempt(chunk.getWorld())) { + return; + } WorldChunkMetaManager worldManager = worldToManager.get(chunk.getWorld().getUID()); if (worldManager == null) { throw new IllegalStateException("No world manager for chunk at " + chunk.toString()); @@ -141,6 +147,9 @@ void unloadChunkData(Chunk chunk) { } public void registerWorld(short id, World world) { + if (isExempt(world)) { + return; + } WorldChunkMetaManager manager = new WorldChunkMetaManager(world, id, this.chunkLoadingThreadCount, this.logger); worldToManager.put(world.getUID(), manager); } @@ -150,4 +159,8 @@ public void disableWorlds() { manager.disable(); } } + + private boolean isExempt(World world) { + return world.getName().startsWith("rankedarena.") || world.getName().startsWith("dynamicarena."); + } } diff --git a/plugins/civmodcore-paper/src/test/java/vg/civcraft/mc/civmodcore/nbt/NBTTests.java b/plugins/civmodcore-paper/src/test/java/vg/civcraft/mc/civmodcore/nbt/NBTTests.java index f42ed5766b..01fa75d237 100644 --- a/plugins/civmodcore-paper/src/test/java/vg/civcraft/mc/civmodcore/nbt/NBTTests.java +++ b/plugins/civmodcore-paper/src/test/java/vg/civcraft/mc/civmodcore/nbt/NBTTests.java @@ -1,14 +1,10 @@ package vg.civcraft.mc.civmodcore.nbt; -import java.util.HashMap; -import java.util.Map; import net.minecraft.nbt.CompoundTag; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; public class NBTTests { - @Test public void testStringSerialization() { // Setup @@ -19,10 +15,10 @@ public void testStringSerialization() { "voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non " + "proident, sunt in culpa qui officia deserunt mollit anim id est laborum."; // Process - final var nbt = new NBTCompound(); + final var nbt = new NbtCompound(); nbt.setString(STRING_KEY, expectedString); // Check - Assertions.assertEquals(expectedString, nbt.getString(STRING_KEY)); + Assertions.assertEquals(expectedString, nbt.getString(STRING_KEY, null)); } @Test @@ -31,10 +27,10 @@ public void testStringArraySerialization() { String STRING_ARRAY_KEY = "test_string_array"; String[] expectedStringArray = {"one", "two", "three"}; // Process - final var nbt = new NBTCompound(); + final var nbt = new NbtCompound(); nbt.setStringArray(STRING_ARRAY_KEY, expectedStringArray); // Check - Assertions.assertArrayEquals(expectedStringArray, nbt.getStringArray(STRING_ARRAY_KEY)); + Assertions.assertArrayEquals(expectedStringArray, nbt.getStringArray(STRING_ARRAY_KEY, false)); } @Test @@ -49,13 +45,13 @@ public void testByteSerialization() { "Proin fermentum leo vel orci porta non pulvinar. Facilisis magna etiam tempor orci eu lobortis " + "elementum nibh tellus. Aliquet eget sit amet tellus cras adipiscing enim."; // Process - final var nbt = new NBTCompound(); + final var nbt = new NbtCompound(); nbt.setString(STRING_KEY, expectedString); - final byte[] data = NBTSerialization.toBytes(nbt.getRAW()); - final CompoundTag actual = NBTSerialization.fromBytes(data); + final byte[] data = NbtUtils.toBytes(nbt.internal()); + final CompoundTag actual = NbtUtils.fromBytes(data); // Check Assertions.assertNotNull(actual); - Assertions.assertEquals(expectedString, actual.getString(STRING_KEY)); + Assertions.assertEquals(expectedString, actual.getString(STRING_KEY).orElseThrow()); } @Test @@ -63,12 +59,12 @@ public void testNullSerialization() { // Setup String STRING_KEY = "test_null_string"; // Process - final var nbt = new NBTCompound(); + final var nbt = new NbtCompound(); nbt.setString(STRING_KEY, null); - final byte[] data = NBTSerialization.toBytes(nbt.getRAW()); - final var actual = new NBTCompound(NBTSerialization.fromBytes(data)); + final byte[] data = NbtUtils.toBytes(nbt.internal()); + final var actual = new NbtCompound(NbtUtils.fromBytes(data)); // Check - Assertions.assertNull(actual.getNullableString(STRING_KEY)); + Assertions.assertNull(actual.getString(STRING_KEY, null)); } @Test @@ -86,48 +82,10 @@ public void testNBTClearing() { "euismod. Cras semper auctor neque vitae tempus. Leo a diam sollicitudin tempor id eu. Non sodales " + "neque sodales ut etiam. Elementum integer enim neque volutpat ac tincidunt vitae semper quis."; // Process - final var nbt = new NBTCompound(); + final var nbt = new NbtCompound(); nbt.setString(STRING_KEY, expectedString); nbt.clear(); // Check - Assertions.assertNull(nbt.getNullableString(STRING_KEY)); + Assertions.assertNull(nbt.getString(STRING_KEY, null)); } - -// TODO: Who knows. -// @Test -// public void testItemStackSerialisation() { -// // Setup -// final var item = new ItemStack(Material.STONE); -// ItemUtils.handleItemMeta(item, (ItemMeta meta) -> { -// meta.displayName(Component.text("Hello World!")); -// MetaUtils.setComponentLore(meta, Component.text("Testing!", -// NamedTextColor.YELLOW, TextDecoration.UNDERLINED)); -// return true; -// }); -// // Process -// final var nbt = NBTHelper.itemStackToNBT(item); -// final var parsed = NBTHelper.itemStackFromNBT(nbt); -// // Check -// Assertions.assertEquals(item, parsed); -// } - - @Test - public void testMapDeserialisation() { - // Setup - final CompoundTag targetNBT = new CompoundTag() {{ - put("EntityTag", new CompoundTag() {{ - putString("id", "minecraft:vex"); - }}); - }}; - final Map testData = new HashMap<>() {{ - put("EntityTag", new HashMap() {{ - put("id", "minecraft:vex"); - }}); - }}; - // Process - final CompoundTag convertedNBT = NBTSerialization.fromMap(testData); - // Check - Assertions.assertEquals(targetNBT, convertedNBT); - } - } diff --git a/plugins/civproxy-velocity/build.gradle.kts b/plugins/civproxy-velocity/build.gradle.kts new file mode 100644 index 0000000000..288588003b --- /dev/null +++ b/plugins/civproxy-velocity/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + alias(libs.plugins.shadow) +} +version = "1.0.0" + +dependencies { + compileOnly("com.velocitypowered:velocity-api:3.4.0-SNAPSHOT") + compileOnly("us.ajg0702.queue.api:api:2.8.0") + api(libs.hikaricp) + api(libs.configurate.yaml) + api("org.mariadb.jdbc:mariadb-java-client:3.5.6") + compileOnly(libs.luckperms.api) + annotationProcessor("com.velocitypowered:velocity-api:3.4.0-SNAPSHOT") +} diff --git a/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/CivProxyPlugin.java b/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/CivProxyPlugin.java new file mode 100644 index 0000000000..31f1b31798 --- /dev/null +++ b/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/CivProxyPlugin.java @@ -0,0 +1,112 @@ +package net.civmc.civproxy; + +import com.google.inject.Inject; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; +import com.velocitypowered.api.plugin.Dependency; +import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.plugin.annotation.DataDirectory; +import com.velocitypowered.api.proxy.ProxyServer; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import javax.sql.DataSource; +import net.civmc.civproxy.renamer.PlayerRenamer; +import org.slf4j.Logger; +import org.spongepowered.configurate.CommentedConfigurationNode; +import org.spongepowered.configurate.yaml.YamlConfigurationLoader; + +@Plugin(id = "civproxy", name = "CivProxy", version = "1.0.0", authors = {"Okx"}, dependencies = {@Dependency(id = "ajqueue"), @Dependency(id = "luckperms")}) +public class CivProxyPlugin { + + private final ProxyServer server; + private final Logger logger; + + private CommentedConfigurationNode config; + + private DataSource source; + + @Inject + public CivProxyPlugin(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) { + this.server = server; + this.logger = logger; + + loadConfig(dataDirectory); + } + + public Logger getLogger() { + return logger; + } + + @Subscribe + public void onProxyInitialization(ProxyInitializeEvent event) { + loadConnection(); + new PlayerCount(this, server).start(); + new PlayerRenamer(this, server, source).start(); + new QueueListener(this, server).start(); + } + + private void loadConnection() { + CommentedConfigurationNode database = config.node("database"); + + try { + Class.forName("org.mariadb.jdbc.Driver"); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + HikariConfig config = new HikariConfig(); + config.setJdbcUrl("jdbc:" + database.node("driver").getString("mariadb") + "://" + database.node("host").getString("localhost") + ":" + + database.node("port").getInt(3306) + "/" + database.node("database").getString("minecraft")); + config.setConnectionTimeout(database.node("connection_timeout").getInt(10_000)); + config.setIdleTimeout(database.node("idle_timeout").getInt(600_000)); + config.setMaxLifetime(database.node("max_lifetime").getInt(7_200_000)); + config.setMaximumPoolSize(database.node("poolsize").getInt(10)); + config.setUsername(database.node("user").getString("root")); + String password = database.node("password").getString(); + if (password != null && !password.isBlank()) { + config.setPassword(password); + } + this.source = new HikariDataSource(config); + } + + /** + * Loads the config from disk, and creates it if necessary + */ + private void loadConfig(Path dataDirectory) { + try { + // ensure data directory exists + if (!Files.exists(dataDirectory)) { + Files.createDirectories(dataDirectory); + } + } catch (IOException e) { + logger.error("Could not create data directory: {}", dataDirectory, e); + return; + } + + // create config file if it doesn't exist + Path configFile = dataDirectory.resolve("config.yml"); + if (!Files.exists(configFile)) { + try (InputStream in = getClass().getResourceAsStream("/config.yml")) { + if (in != null) { + Files.copy(in, configFile); + logger.info("Default configuration file created."); + } else { + logger.error("Default configuration file is missing in resources!"); + return; + } + } catch (IOException e) { + logger.error("Could not create default configuration file: {}", configFile, e); + } + } + + YamlConfigurationLoader loader = YamlConfigurationLoader.builder().path(configFile).build(); + try { + config = loader.load(); + } catch (IOException e) { + throw new RuntimeException("Could not load configuration file: " + configFile, e); + } + } +} diff --git a/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/PlayerCount.java b/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/PlayerCount.java new file mode 100644 index 0000000000..fc2ba381af --- /dev/null +++ b/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/PlayerCount.java @@ -0,0 +1,46 @@ +package net.civmc.civproxy; + +import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; + +public class PlayerCount { + + private static final MinecraftChannelIdentifier PLAYER_COUNT_ID = MinecraftChannelIdentifier.create("civproxy", "player_count"); + + private final CivProxyPlugin plugin; + private final ProxyServer server; + + public PlayerCount(CivProxyPlugin plugin, ProxyServer server) { + this.plugin = plugin; + this.server = server; + } + + public void start() { + server.getScheduler().buildTask(plugin, () -> { + try { + Map count = new HashMap<>(); + for (RegisteredServer server : server.getAllServers()) { + count.put(server.getServerInfo().getName(), server.getPlayersConnected().size()); + } + + for (RegisteredServer server : server.getAllServers()) { + server.sendPluginMessage(PLAYER_COUNT_ID, out -> { + out.writeInt(count.size() - 1); + for (Map.Entry entry : count.entrySet()) { + if (!entry.getKey().equals(server.getServerInfo().getName())) { + out.writeUTF(entry.getKey()); + out.writeInt(entry.getValue()); + } + } + }); + } + } catch (RuntimeException ex) { + plugin.getLogger().warn("Ticking player counts", ex); + } + }).delay(Duration.ZERO).repeat(Duration.ofSeconds(2)).schedule(); + } +} diff --git a/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/QueueListener.java b/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/QueueListener.java new file mode 100644 index 0000000000..e8cd1bc895 --- /dev/null +++ b/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/QueueListener.java @@ -0,0 +1,170 @@ +package net.civmc.civproxy; + +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.DisconnectEvent; +import com.velocitypowered.api.event.player.KickedFromServerEvent; +import com.velocitypowered.api.event.player.ServerPostConnectEvent; +import com.velocitypowered.api.event.player.ServerPreConnectEvent; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import java.time.Instant; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import net.kyori.adventure.text.Component; +import net.luckperms.api.LuckPermsProvider; +import net.luckperms.api.model.data.TemporaryNodeMergeStrategy; +import net.luckperms.api.model.user.UserManager; +import net.luckperms.api.node.types.PermissionNode; +import us.ajg0702.queue.api.AjQueueAPI; +import us.ajg0702.queue.api.QueueManager; +import us.ajg0702.queue.api.players.AdaptedPlayer; + +public class QueueListener { + + private final Map players = new ConcurrentHashMap<>(); + + private final CivProxyPlugin plugin; + private final ProxyServer server; + + public QueueListener(CivProxyPlugin plugin, ProxyServer server) { + this.plugin = plugin; + this.server = server; + } + + record QueueRecord(Instant instant, String server) { + + } + + public void start() { + server.getEventManager().register(plugin, this); + } + + @Subscribe + public void onToMain(KickedFromServerEvent event) { + // Other than banned players, kicked servers should go to the queue on the PvP server + + String name = event.getServer().getServerInfo().getName(); + if (name.equals("pvp")) { + return; + } + if (event.getPlayer().getCurrentServer().isPresent()) { + return; + } + event.setResult(KickedFromServerEvent.RedirectPlayer.create(server.getServer("pvp").get())); + + players.put(event.getPlayer(), new QueueRecord(Instant.now(), name)); + } + + @Subscribe + public void onFromPvP(KickedFromServerEvent event) { + String name = event.getServer().getServerInfo().getName(); + if (!name.equals("pvp")) { + return; + } + + if (!(event.getResult() instanceof KickedFromServerEvent.RedirectPlayer)) { + return; + } + + event.setResult(KickedFromServerEvent.DisconnectPlayer.create(event.getServerKickReason().orElse(Component.text("Disconnected")))); + } + + @Subscribe + public void onConnect(ServerPreConnectEvent event) { + // If we are close to the cap, don't allow direct connections to the server and force them to go to the queue + // This prevents players being able to snipe positions and bypass the queue + + if (event.getPreviousServer() != null) { + return; + } + RegisteredServer mainServer = event.getOriginalServer(); + String name = mainServer.getServerInfo().getName(); + if (name.equals("pvp")) { + return; + } + + if (mainServer.getPlayersConnected().size() >= 145 && !event.getPlayer().hasPermission("joinbypass.use")) { +// RegisteredServer mini = server.getServer("mini").orElse(null); +// if (mini != null && mini.getPlayersConnected().size() < 110) { +// event.setResult(ServerPreConnectEvent.ServerResult.allowed(server.getServer("mini").get())); +// } else { + event.setResult(ServerPreConnectEvent.ServerResult.allowed(server.getServer("pvp").get())); +// } + + players.put(event.getPlayer(), new QueueRecord(Instant.now(), name)); + } + } + + @Subscribe + public void onConnect(ServerPostConnectEvent event) { + // Add players coming from the main server to the queue + + QueueRecord record = players.remove(event.getPlayer()); + if (record != null && record.instant().isAfter(Instant.now().minusSeconds(60))) { + QueueManager queueManager = AjQueueAPI.getInstance().getQueueManager(); + AdaptedPlayer player = AjQueueAPI.getInstance().getPlatformMethods().getPlayer(event.getPlayer().getUniqueId()); + queueManager.addToQueue(player, record.server()); + } + } + + @Subscribe + public void onChangeFromMain(ServerPreConnectEvent event) { + if (event.getPreviousServer() == null) { + return; + } + String name = event.getPreviousServer().getServerInfo().getName(); + if (name.equals("pvp")) { + return; + } + + addPriority(event.getPlayer(), name); + } + + @Subscribe + public void onKick(KickedFromServerEvent event) { + RegisteredServer server = event.getServer(); + String name = server.getServerInfo().getName(); + if (name.equals("pvp") + || event.kickedDuringServerConnect() + || event.getPlayer().getCurrentServer().map(c -> c.getServerInfo().getName().equals("pvp")).orElse(true)) { + return; + } + + addPriority(event.getPlayer(), name); + } + + @Subscribe + public void onDisconnect(DisconnectEvent event) { + ServerConnection server = event.getPlayer().getCurrentServer().orElse(null); + if (server == null) { + return; + } + String name = server.getServerInfo().getName(); + if (name.equals("pvp")) { + return; + } + + addPriority(event.getPlayer(), name); + } + + private void addPriority(Player player, String server) { + // Players who just disconnected get 5 minutes of queue priority + + UserManager userManager = LuckPermsProvider.get().getUserManager(); + userManager.loadUser(player.getUniqueId()).thenAccept(user -> { + if (user == null) { + return; + } + user.data().add( + PermissionNode.builder() + .permission("ajqueue.serverpriority." + server + ".10") + .expiry(5, TimeUnit.MINUTES) + .build(), + TemporaryNodeMergeStrategy.REPLACE_EXISTING_IF_DURATION_LONGER); + userManager.saveUser(user); + }); + } +} diff --git a/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/database/Migrator.java b/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/database/Migrator.java new file mode 100644 index 0000000000..5843ada3d4 --- /dev/null +++ b/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/database/Migrator.java @@ -0,0 +1,65 @@ +package net.civmc.civproxy.database; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; +import java.util.NavigableMap; +import java.util.Objects; +import java.util.TreeMap; + +public class Migrator { + + private final Map> migrations = new HashMap<>(); + + public void registerMigration(String namespace, int id, String... sql) { + Objects.requireNonNull(namespace); + Objects.requireNonNull(sql); + if (namespace.length() > 64) { + throw new IllegalArgumentException("namespace must not be longer than 64 characters"); + } + if (id < 0) { + throw new IllegalArgumentException("migration id must be at least 0"); + } + boolean present = this.migrations.computeIfAbsent(namespace, k -> new TreeMap<>()).putIfAbsent(id, sql) != null; + if (present) { + throw new IllegalStateException("Migration already exists with namespace " + namespace + " and ID " + id); + } + } + + public void migrate(Connection connection) throws SQLException { + connection.setAutoCommit(false); + connection.createStatement().executeUpdate("CREATE TABLE IF NOT EXISTS migrations (" + + "namespace VARCHAR(64) PRIMARY KEY," + + "id INT NOT NULL)"); + + for (Map.Entry> entry : migrations.entrySet()) { + PreparedStatement getMigrationId = connection.prepareStatement("SELECT id FROM migrations WHERE namespace = ? FOR UPDATE"); + getMigrationId.setString(1, entry.getKey()); + ResultSet resultSet = getMigrationId.executeQuery(); + int minId; + if (resultSet.next()) { + minId = resultSet.getInt("id"); + } else { + minId = -1; + } + + NavigableMap value = entry.getValue().tailMap(minId, false); + int maxId = entry.getValue().lastKey(); + for (String[] migration : value.sequencedValues()) { + for (String sql : migration) { + connection.createStatement().executeUpdate(sql); + } + } + + if (maxId != minId) { + PreparedStatement setMigrationId = connection.prepareStatement("REPLACE INTO migations (namespace, id) VALUES (?, ?)"); + setMigrationId.setString(1, entry.getKey()); + setMigrationId.setInt(2, maxId); + } + } + connection.setAutoCommit(true); + } +} diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/database/AssociationList.java b/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/renamer/AssociationList.java similarity index 84% rename from plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/database/AssociationList.java rename to plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/renamer/AssociationList.java index 2377bf5db3..c1a07182e9 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/database/AssociationList.java +++ b/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/renamer/AssociationList.java @@ -1,4 +1,4 @@ -package vg.civcraft.mc.namelayer.database; +package net.civmc.civproxy.renamer; import java.sql.Connection; import java.sql.PreparedStatement; @@ -7,13 +7,13 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; -import java.util.logging.Level; -import java.util.logging.Logger; -import vg.civcraft.mc.civmodcore.dao.ManagedDatasource; +import javax.sql.DataSource; +import net.civmc.civproxy.database.Migrator; +import org.slf4j.Logger; public class AssociationList { - private ManagedDatasource db; + private DataSource db; private Logger logger; private static final String addPlayer = "call addplayertotable(?, ?)"; // order player name, uuid @@ -22,15 +22,16 @@ public class AssociationList { private static final String changePlayerName = "update Name_player set player=? where uuid=?"; private static final String getAllPlayerInfo = "select * from Name_player"; - public AssociationList(Logger logger, ManagedDatasource db) { + public AssociationList(Logger logger, DataSource db) { this.db = db; this.logger = logger; } - public void registerMigrations() { + public void migrate() { + Migrator migrator = new Migrator(); // creates the player table // Where uuid and host names will be stored - db.registerMigration(-1, false, + migrator.registerMigration("renamer", 0, "CREATE TABLE IF NOT EXISTS `Name_player` (" + "`uuid` varchar(40) NOT NULL," + "`player` varchar(40) NOT NULL," @@ -41,7 +42,7 @@ public void registerMigrations() { + "amount int(10) not null," + "primary key (player));"); - db.registerMigration(0, false, + migrator.registerMigration("renamer", 1, "drop procedure if exists addplayertotable", "create definer=current_user procedure addplayertotable(" + "in pl varchar(40), in uu varchar(40)) sql security invoker begin " @@ -83,8 +84,12 @@ public void registerMigrations() { + "END LOOP setName;" + "end if;" + "end"); - // For future migrations, check the max migrations that is combination of here and - // GroupManagerDao! + + try { + migrator.migrate(db.getConnection()); + } catch (SQLException e) { + throw new RuntimeException(e); + } } /** @@ -102,10 +107,10 @@ public UUID getUUID(String playername) { String uuid = set.getString("uuid"); return UUID.fromString(uuid); } catch (SQLException se) { - logger.log(Level.WARNING, "Failed to get UUID for playername " + playername, se); + logger.warn("Failed to get UUID for playername " + playername, se); } } catch (SQLException e) { - logger.log(Level.WARNING, "Failed to set up query to get UUID for playername " + playername, e); + logger.warn("Failed to set up query to get UUID for playername " + playername, e); } return null; } @@ -125,10 +130,10 @@ public String getCurrentName(UUID uuid) { String playername = set.getString("player"); return playername; } catch (SQLException se) { - logger.log(Level.WARNING, "Failed to get current player name for UUID " + uuid, se); + logger.warn("Failed to get current player name for UUID " + uuid, se); } } catch (SQLException e) { - logger.log(Level.WARNING, "Failed to set up query to get current player name for UUID " + uuid, e); + logger.warn("Failed to set up query to get current player name for UUID " + uuid, e); } return null; } @@ -140,9 +145,9 @@ public void addPlayer(String playername, UUID uuid) { addPlayer.setString(2, uuid.toString()); addPlayer.execute(); } catch (SQLException e) { - logger.log(Level.WARNING, "Failed to add new player mapping {0} <==> {1}, due to {2}", + logger.warn("Failed to add new player mapping {0} <==> {1}, due to {2}", new Object[]{playername, uuid, e.getMessage()}); - logger.log(Level.WARNING, "Add new player failure: ", e); + logger.warn("Add new player failure: ", e); } } @@ -153,9 +158,9 @@ public void changePlayer(String newName, UUID uuid) { changePlayerName.setString(2, uuid.toString()); changePlayerName.execute(); } catch (SQLException e) { - logger.log(Level.WARNING, "Failed to change player name mapping {0} <==> {1}, due to {2}", + logger.warn("Failed to change player name mapping {0} <==> {1}, due to {2}", new Object[]{newName, uuid, e.getMessage()}); - logger.log(Level.WARNING, "Change player failure: ", e); + logger.warn("Change player failure: ", e); return; // don't add on failure } } @@ -181,7 +186,7 @@ public PlayerMappingInfo getAllPlayerInfo() { uuidMapping.put(uuid, playername); } } catch (SQLException e) { - logger.log(Level.WARNING, "Failed to get all player info", e); + logger.warn("Failed to get all player info", e); } return new PlayerMappingInfo(nameMapping, uuidMapping); } diff --git a/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/renamer/ChangePlayerNameCommand.java b/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/renamer/ChangePlayerNameCommand.java new file mode 100644 index 0000000000..7a91f03e7e --- /dev/null +++ b/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/renamer/ChangePlayerNameCommand.java @@ -0,0 +1,45 @@ +package net.civmc.civproxy.renamer; + +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.command.SimpleCommand; +import com.velocitypowered.api.proxy.ProxyServer; +import net.kyori.adventure.text.Component; +import java.util.UUID; + +public class ChangePlayerNameCommand implements SimpleCommand { + + private final ProxyServer server; + private final AssociationList associationList; + + public ChangePlayerNameCommand(ProxyServer server, AssociationList associationList) { + this.server = server; + this.associationList = associationList; + } + + @Override + public void execute(Invocation invocation) { + CommandSource source = invocation.source(); + String[] args = invocation.arguments(); + if (args.length < 2) { + source.sendPlainMessage("Usage: /" + invocation.alias() + " "); + return; + } + + UUID uuid = associationList.getUUID(args[0]); + if (uuid == null) { + source.sendPlainMessage("Player not found"); + return; + } + String newName = args[1].length() >= 16 ? args[1].substring(0, 16) : args[1]; + associationList.changePlayer(newName, uuid); + source.sendPlainMessage("Changed name of " + args[0] + " to " + newName); + + server.getPlayer(uuid).ifPresent(player -> + player.disconnect(Component.text("Your name has been changed! Please rejoin."))); + } + + @Override + public boolean hasPermission(Invocation invocation) { + return invocation.source().hasPermission("civproxy.changeplayername"); + } +} diff --git a/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/renamer/PlayerRenamer.java b/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/renamer/PlayerRenamer.java new file mode 100644 index 0000000000..ef502bb3b2 --- /dev/null +++ b/plugins/civproxy-velocity/src/main/java/net/civmc/civproxy/renamer/PlayerRenamer.java @@ -0,0 +1,43 @@ +package net.civmc.civproxy.renamer; + +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.player.GameProfileRequestEvent; +import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.util.GameProfile; +import javax.sql.DataSource; +import net.civmc.civproxy.CivProxyPlugin; + +public class PlayerRenamer { + + private final CivProxyPlugin plugin; + private final ProxyServer server; + + private final AssociationList associations; + + public PlayerRenamer(CivProxyPlugin plugin, ProxyServer server, DataSource source) { + this.plugin = plugin; + this.server = server; + this.associations = new AssociationList(plugin.getLogger(), source); + } + + @Subscribe + public void on(GameProfileRequestEvent requestEvent) { + GameProfile profile = requestEvent.getGameProfile(); + associations.addPlayer(profile.getName(), profile.getId()); + + String name = associations.getCurrentName(profile.getId()); + if (name == null) { + associations.addPlayer(profile.getName(), profile.getId()); + name = associations.getCurrentName(profile.getId()); + } + + requestEvent.setGameProfile(requestEvent.getGameProfile().withName(name)); + } + + public void start() { + associations.migrate(); + server.getEventManager().register(plugin, this); + server.getCommandManager().register(server.getCommandManager().metaBuilder("changeplayername").aliases("nlcpn").plugin(plugin).build(), + new ChangePlayerNameCommand(server, associations)); + } +} diff --git a/plugins/civspy-paper/build.gradle.kts b/plugins/civspy-paper/build.gradle.kts index 11a9b09a7b..361ad80aed 100644 --- a/plugins/civspy-paper/build.gradle.kts +++ b/plugins/civspy-paper/build.gradle.kts @@ -1,6 +1,6 @@ plugins { id("io.papermc.paperweight.userdev") - id("com.github.johnrengelman.shadow") + id("com.gradleup.shadow") id("xyz.jpenilla.run-paper") } diff --git a/plugins/combattagplus-paper/build.gradle.kts b/plugins/combattagplus-paper/build.gradle.kts index 78587a87f4..7847d5a942 100644 --- a/plugins/combattagplus-paper/build.gradle.kts +++ b/plugins/combattagplus-paper/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - id("io.papermc.paperweight.userdev") - id("xyz.jpenilla.run-paper") + alias(libs.plugins.paper.userdev) + alias(libs.plugins.runpaper) } version = "2.0.1" diff --git a/plugins/combattagplus-paper/src/main/java/net/minelink/ctplus/NpcManager.java b/plugins/combattagplus-paper/src/main/java/net/minelink/ctplus/NpcManager.java index 45eed22516..39db12f740 100644 --- a/plugins/combattagplus-paper/src/main/java/net/minelink/ctplus/NpcManager.java +++ b/plugins/combattagplus-paper/src/main/java/net/minelink/ctplus/NpcManager.java @@ -66,8 +66,7 @@ public Npc spawn(Player player) { if (plugin.getSettings().playEffect()) { Location l = entity.getLocation(); l.getWorld().playEffect(l, Effect.MOBSPAWNER_FLAMES, 0, 64); - // NOTE: Do not directly access the values in the sound enum, as that can change across versions\ - l.getWorld().playSound(l, EXPLODE_SOUND, 0.9F, 0); + l.getWorld().playSound(l, Sound.ENTITY_GENERIC_EXPLODE, 0.9F, 0); } // Create and start the NPCs despawn task @@ -121,22 +120,4 @@ public NpcDespawnTask getDespawnTask(Npc npc) { public boolean hasDespawnTask(Npc npc) { return despawnTasks.containsKey(npc); } - - // Use reflection - private static final Sound EXPLODE_SOUND; - - static { - Sound sound; - try { - sound = Sound.valueOf("ENTITY_GENERIC_EXPLODE"); // 1.9 name - } catch (IllegalArgumentException e) { - try { - sound = Sound.valueOf("EXPLODE"); // 1.8 name - } catch (IllegalArgumentException e2) { - throw new AssertionError("Unable to find explosion sound"); - } - } - EXPLODE_SOUND = sound; - } - } diff --git a/plugins/combattagplus-paper/src/main/java/net/minelink/ctplus/NpcNameGeneratorImpl.java b/plugins/combattagplus-paper/src/main/java/net/minelink/ctplus/NpcNameGeneratorImpl.java index 1f301785bb..848f9b052e 100644 --- a/plugins/combattagplus-paper/src/main/java/net/minelink/ctplus/NpcNameGeneratorImpl.java +++ b/plugins/combattagplus-paper/src/main/java/net/minelink/ctplus/NpcNameGeneratorImpl.java @@ -1,7 +1,7 @@ package net.minelink.ctplus; import net.minelink.ctplus.compat.base.NpcNameGenerator; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.bukkit.Bukkit; import org.bukkit.entity.Player; diff --git a/plugins/combattagplus-paper/src/main/java/net/minelink/ctplus/Settings.java b/plugins/combattagplus-paper/src/main/java/net/minelink/ctplus/Settings.java index a7a7912d34..27a64c31a1 100644 --- a/plugins/combattagplus-paper/src/main/java/net/minelink/ctplus/Settings.java +++ b/plugins/combattagplus-paper/src/main/java/net/minelink/ctplus/Settings.java @@ -16,7 +16,7 @@ import java.util.Map; import java.util.concurrent.TimeUnit; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.bukkit.ChatColor; import org.bukkit.configuration.Configuration; import org.bukkit.configuration.file.YamlConfiguration; diff --git a/plugins/combattagplus-paper/src/main/java/net/minelink/ctplus/listener/PlayerListener.java b/plugins/combattagplus-paper/src/main/java/net/minelink/ctplus/listener/PlayerListener.java index b6eef677c9..8c48fde048 100644 --- a/plugins/combattagplus-paper/src/main/java/net/minelink/ctplus/listener/PlayerListener.java +++ b/plugins/combattagplus-paper/src/main/java/net/minelink/ctplus/listener/PlayerListener.java @@ -9,7 +9,7 @@ import net.minelink.ctplus.task.SafeLogoutTask; import net.minelink.ctplus.task.TagUpdateTask; -import org.apache.commons.lang.WordUtils; +import org.apache.commons.lang3.text.WordUtils; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.entity.Player; diff --git a/plugins/combattagplus-paper/src/main/java/net/minelink/ctplus/nms/NpcPlayerConnection.java b/plugins/combattagplus-paper/src/main/java/net/minelink/ctplus/nms/NpcPlayerConnection.java index 26ad2939d3..95cfcad753 100644 --- a/plugins/combattagplus-paper/src/main/java/net/minelink/ctplus/nms/NpcPlayerConnection.java +++ b/plugins/combattagplus-paper/src/main/java/net/minelink/ctplus/nms/NpcPlayerConnection.java @@ -1,14 +1,16 @@ package net.minelink.ctplus.nms; +import io.papermc.paper.util.KeepAlive; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.network.CommonListenerCookie; import net.minecraft.server.network.ServerGamePacketListenerImpl; +import java.util.HashSet; public class NpcPlayerConnection extends ServerGamePacketListenerImpl { public NpcPlayerConnection(ServerPlayer player) { - super(MinecraftServer.getServer(), new NpcNetworkManager(), player, new CommonListenerCookie(player.gameProfile, -1, player.clientInformation(), false)); + super(MinecraftServer.getServer(), new NpcNetworkManager(), player, new CommonListenerCookie(player.gameProfile, -1, player.clientInformation(), false, null, new HashSet<>(), new KeepAlive())); } } diff --git a/plugins/combattagplus-paper/src/main/java/net/minelink/ctplus/nms/NpcPlayerHelperImpl.java b/plugins/combattagplus-paper/src/main/java/net/minelink/ctplus/nms/NpcPlayerHelperImpl.java index ca71aac16b..b57e7eef4a 100644 --- a/plugins/combattagplus-paper/src/main/java/net/minelink/ctplus/nms/NpcPlayerHelperImpl.java +++ b/plugins/combattagplus-paper/src/main/java/net/minelink/ctplus/nms/NpcPlayerHelperImpl.java @@ -2,9 +2,14 @@ import com.google.common.collect.Lists; import com.mojang.datafixers.util.Pair; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.EnumMap; +import java.util.List; import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.NbtUtils; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.game.ClientGamePacketListener; import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket; @@ -13,13 +18,18 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.ProblemReporter; +import net.minecraft.world.ItemStackWithSlot; +import net.minecraft.world.entity.EntityEquipment; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.entity.ai.attributes.AttributeInstance; import net.minecraft.world.entity.ai.attributes.AttributeMap; -import net.minecraft.world.food.FoodData; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; import net.minecraft.world.level.storage.PlayerDataStorage; +import net.minecraft.world.level.storage.TagValueInput; +import net.minecraft.world.level.storage.TagValueOutput; +import net.minecraft.world.level.storage.ValueInput; import net.minelink.ctplus.compat.base.NpcIdentity; import net.minelink.ctplus.compat.base.NpcPlayerHelper; import org.bukkit.Bukkit; @@ -28,12 +38,6 @@ import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.entity.Player; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.lang.reflect.Field; -import java.util.List; - public class NpcPlayerHelperImpl implements NpcPlayerHelper { @Override @@ -73,7 +77,7 @@ public void despawn(Player player) { serverPlayer.connection.send(packet); } - ServerLevel worldServer = entity.serverLevel(); + ServerLevel worldServer = entity.level(); worldServer.chunkSource.removeEntity(entity); worldServer.getPlayers(serverPlayer -> serverPlayer instanceof NpcPlayer).remove(entity); removePlayerList(player); @@ -120,7 +124,7 @@ public void updateEquipment(Player player) { List> list = Lists.newArrayList(); list.add(Pair.of(slot, item)); Packet packet = new ClientboundSetEquipmentPacket(entity.getId(), list); - entity.serverLevel().chunkSource.broadcast(entity, packet); + entity.level().chunkSource.broadcast(entity, packet); } } @@ -137,39 +141,30 @@ public void syncOffline(Player player) { if (p != null && p.isOnline()) return; PlayerDataStorage worldStorage = ((CraftWorld) Bukkit.getWorlds().getFirst()).getHandle().getServer().playerDataStorage; - CompoundTag playerNbt = worldStorage.load(identity.getName(), identity.getId().toString()).orElse(null); - - // foodTickTimer is now private in 1.8.3 -- still private in 1.12 -- still private in 1.20.6 - Field foodTickTimerField; - int foodTickTimer; - try { - //Although we can use Mojang mappings when developing, We need to use the obfuscated field name - //until we can run a full Mojmapped server. I personally used this site when updating to 1.20.6: - // https://mappings.cephx.dev/1.20.6/net/minecraft/world/food/FoodData.html - foodTickTimerField = FoodData.class.getDeclaredField("d"); // todo fix - foodTickTimerField.setAccessible(true); - foodTickTimer = foodTickTimerField.getInt(entity.getFoodData()); - } catch (NoSuchFieldException | IllegalAccessException e) { - throw new RuntimeException(e); - } + CompoundTag playerNbt = worldStorage.load(identity.getName(), identity.getId().toString(), ProblemReporter.DISCARDING).orElse(null); playerNbt.putShort("Air", (short) entity.getAirSupply()); // Health is now just a float; fractional is not stored separately. (1.12) playerNbt.putFloat("Health", entity.getHealth()); playerNbt.putFloat("AbsorptionAmount", entity.getAbsorptionAmount()); playerNbt.putInt("XpTotal", entity.experienceLevel); - playerNbt.putInt("foodLevel", entity.getFoodData().getFoodLevel()); - playerNbt.putInt("foodTickTimer", foodTickTimer); - playerNbt.putFloat("foodSaturationLevel", entity.getFoodData().getSaturationLevel()); - playerNbt.putFloat("foodExhaustionLevel", entity.getFoodData().exhaustionLevel); playerNbt.putShort("Fire", (short) entity.getRemainingFireTicks()); - playerNbt.put("Inventory", npcPlayer.getInventory().save(new ListTag())); + TagValueOutput output = TagValueOutput.createWrappingWithContext(ProblemReporter.DISCARDING, ((CraftPlayer) player).getHandle().registryAccess(), playerNbt); + entity.getFoodData().addAdditionalSaveData(output); + NbtUtils.addCurrentDataVersion(output); + npcPlayer.getInventory().save(output.list("Inventory", ItemStackWithSlot.CODEC)); + EntityEquipment equipment = new EntityEquipment(); + for (EquipmentSlot slot : EquipmentSlot.values()) { + equipment.set(slot, npcPlayer.getItemBySlot(slot)); + } + output.store("equipment", EntityEquipment.CODEC, equipment); File file1 = new File(worldStorage.getPlayerDir(), identity.getId() + ".dat.tmp"); File file2 = new File(worldStorage.getPlayerDir(), identity.getId() + ".dat"); try { - NbtIo.writeCompressed(playerNbt, new FileOutputStream(file1)); + CompoundTag compoundTag = output.buildResult(); + NbtIo.writeCompressed(compoundTag, new FileOutputStream(file1)); } catch (IOException e) { throw new RuntimeException("Failed to save player data for " + identity.getName(), e); } diff --git a/plugins/donum-paper/src/main/java/com/github/civcraft/donum/misc/ItemMapBlobHandling.java b/plugins/donum-paper/src/main/java/com/github/civcraft/donum/misc/ItemMapBlobHandling.java index 0e54a17c3f..6f8d0317be 100644 --- a/plugins/donum-paper/src/main/java/com/github/civcraft/donum/misc/ItemMapBlobHandling.java +++ b/plugins/donum-paper/src/main/java/com/github/civcraft/donum/misc/ItemMapBlobHandling.java @@ -34,11 +34,9 @@ public class ItemMapBlobHandling { public static byte[] turnItemMapIntoBlob(ItemMap im) { YamlConfiguration yaml = new YamlConfiguration(); int count = 0; - for (Entry entry : im.getAllItems().entrySet()) { - ItemStack is = entry.getKey().clone(); - is.setAmount(entry.getValue()); + for (ItemStack is : im.getItemStackRepresentation()) { //yaml doesnt allow int as keys, so we have to add a string - yaml.set("bla" + String.valueOf(count), is); + yaml.set("bla" + count, is); count++; } return compress(yaml.saveToString()); diff --git a/plugins/essenceglue-paper/build.gradle.kts b/plugins/essenceglue-paper/build.gradle.kts index 54ee291442..340775fda5 100644 --- a/plugins/essenceglue-paper/build.gradle.kts +++ b/plugins/essenceglue-paper/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("io.papermc.paperweight.userdev") + alias(libs.plugins.paper.userdev) } version = "2.0.0-SNAPSHOT" diff --git a/plugins/exilepearl-paper/build.gradle.kts b/plugins/exilepearl-paper/build.gradle.kts index 0938d56c17..09dbd03380 100644 --- a/plugins/exilepearl-paper/build.gradle.kts +++ b/plugins/exilepearl-paper/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("io.papermc.paperweight.userdev") + alias(libs.plugins.paper.userdev) } version = "2.1.6" @@ -18,5 +18,5 @@ dependencies { compileOnly(project(":plugins:banstick-paper")) compileOnly(project(":plugins:randomspawn-paper")) - compileOnly(files("../../ansible/src/paper-plugins/BreweryX-3.4.10.jar")) + compileOnly(files("../../ansible/src/paper-plugins/BreweryX-3.6.0.jar")) } diff --git a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/BorderHandler.java b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/BorderHandler.java deleted file mode 100644 index 540572999a..0000000000 --- a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/BorderHandler.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.devotedmc.ExilePearl; - -import com.devotedmc.ExilePearl.util.ExilePearlRunnable; -import org.bukkit.event.Listener; - -public interface BorderHandler extends ExilePearlRunnable, Listener { - -} diff --git a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/ExilePearlApi.java b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/ExilePearlApi.java index e1363a46c7..f9249e169c 100644 --- a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/ExilePearlApi.java +++ b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/ExilePearlApi.java @@ -127,13 +127,6 @@ public interface ExilePearlApi extends Plugin, PearlAccess, PearlLogger, PlayerP */ boolean isBanStickEnabled(); - /** - * Gets whether RandomSpawn hooks are enabled - * - * @return True if it is enabled - */ - boolean isRandomSpawnEnabled(); - /** * Gets whether CombatTag hooks are enabled * diff --git a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/command/CmdShowAllPearls.java b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/command/CmdShowAllPearls.java index c9e397ffb6..7ab47565bb 100644 --- a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/command/CmdShowAllPearls.java +++ b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/command/CmdShowAllPearls.java @@ -44,7 +44,7 @@ public class CmdShowAllPearls extends PearlCommand { private static final DateFormat DATE_FORMAT = new SimpleDateFormat("dd MMM yyyy"); private static final Map COOLDOWNS = new HashMap<>(); - private static final long COOLDOWN = 10_000; // 10 seconds + private static final long COOLDOWN = 2_000; private static final Set TOGGLES = new HashSet<>(); public CmdShowAllPearls(final ExilePearlApi pearlApi) { diff --git a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/CoreExilePearl.java b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/CoreExilePearl.java index 4c79356166..537617db8a 100644 --- a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/CoreExilePearl.java +++ b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/CoreExilePearl.java @@ -9,8 +9,6 @@ import com.devotedmc.ExilePearl.holder.*; import com.devotedmc.ExilePearl.storage.PearlUpdateStorage; import com.google.common.base.Preconditions; -import org.apache.commons.lang.builder.EqualsBuilder; -import org.apache.commons.lang.builder.HashCodeBuilder; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; @@ -254,13 +252,13 @@ public void setHealth(int health) { if (health < 0) { health = 0; } - + if (health > pearlApi.getPearlConfig().getPearlHealthMaxValue()) { health = pearlApi.getPearlConfig().getPearlHealthMaxValue(); } - + this.health = health; - + if(storageEnabled) { storage.updatePearlHealth(this); } @@ -458,14 +456,7 @@ private void checkPearlValid() { @Override public int hashCode() { - return new HashCodeBuilder(17, 31) // two randomly chosen prime numbers - .append(playerId) - .append(killedBy) - .append(getLocation()) - .append(health) - .append(pearledOn) - .append(freedOffline) - .toHashCode(); + return Objects.hash(playerId, killedBy, getLocation(), health, pearlApi, freedOffline); } @Override @@ -479,14 +470,7 @@ public boolean equals(final Object o) { CoreExilePearl other = (CoreExilePearl) o; - return new EqualsBuilder() - .append(playerId, other.playerId) - .append(killedBy, other.killedBy) - .append(getLocation(), other.getLocation()) - .append(health, other.health) - .append(pearledOn, other.pearledOn) - .append(freedOffline, other.freedOffline) - .isEquals(); + return Objects.equals(playerId, other.playerId) && Objects.equals(killedBy, other.killedBy) && Objects.equals(getLocation(), other.getLastOnline()) && Objects.equals(health, other.health) && Objects.equals(pearledOn, other.pearledOn) && Objects.equals(freedOffline, other.freedOffline); } diff --git a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/CoreLoreGenerator.java b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/CoreLoreGenerator.java index 6a1e4f5deb..a213665b5f 100644 --- a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/CoreLoreGenerator.java +++ b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/CoreLoreGenerator.java @@ -212,7 +212,7 @@ private List getValidLore(ItemStack is) { * @return true if it's valid */ private List getValidLore(ItemStack is, ItemMeta im) { - if (!(is.getType().equals(Material.PLAYER_HEAD) || is.getType().equals(Material.ENDER_PEARL))) { + if (!(is.getType().equals(Material.ENDER_PEARL))) { return null; } diff --git a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/CorePluginFactory.java b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/CorePluginFactory.java index 7685d8a0e1..66f28f7162 100644 --- a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/CorePluginFactory.java +++ b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/CorePluginFactory.java @@ -1,6 +1,5 @@ package com.devotedmc.ExilePearl.core; -import com.devotedmc.ExilePearl.BorderHandler; import com.devotedmc.ExilePearl.BrewHandler; import com.devotedmc.ExilePearl.DamageLogger; import com.devotedmc.ExilePearl.ExilePearl; @@ -152,10 +151,6 @@ public SuicideHandler createSuicideHandler() { return new PlayerSuicideTask(pearlApi); } - public BorderHandler createPearlBorderHandler() { - return new PearlBoundaryTask(pearlApi); - } - public BrewHandler createBrewHandler() { if (Bukkit.getPluginManager().isPluginEnabled("Brewery")) { return new BreweryHandler(); diff --git a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/DamageLog.java b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/DamageLog.java index a97d69526d..6b8b451256 100644 --- a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/DamageLog.java +++ b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/DamageLog.java @@ -9,9 +9,8 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.UUID; -import org.apache.commons.lang.builder.EqualsBuilder; -import org.apache.commons.lang.builder.HashCodeBuilder; import org.bukkit.entity.Player; /** @@ -23,7 +22,7 @@ class DamageLog { private final Clock clock; private final UUID playerId; - private final Map damagers = new HashMap(); + private final Map damagers = new HashMap<>(); /** * Creates a new DamageLog instance @@ -138,10 +137,7 @@ public String toString() { @Override public int hashCode() { - return new HashCodeBuilder(17, 31) - .append(playerId) - .append(damagers) - .toHashCode(); + return Objects.hash(playerId, damagers); } @Override @@ -155,9 +151,6 @@ public boolean equals(final Object o) { DamageLog other = (DamageLog) o; - return new EqualsBuilder() - .append(playerId, other.playerId) - .append(damagers, other.damagers) - .isEquals(); + return Objects.equals(playerId, other.playerId) && Objects.equals(damagers, other.damagers); } } diff --git a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/DamageRecord.java b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/DamageRecord.java index e41a50879b..dcdf8def49 100644 --- a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/DamageRecord.java +++ b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/DamageRecord.java @@ -2,9 +2,8 @@ import com.devotedmc.ExilePearl.util.Clock; import com.google.common.base.Preconditions; +import java.util.Objects; import java.util.UUID; -import org.apache.commons.lang.builder.EqualsBuilder; -import org.apache.commons.lang.builder.HashCodeBuilder; /** * Tracks damage dealt from a player. @@ -108,11 +107,7 @@ public String toString() { @Override public int hashCode() { - return new HashCodeBuilder(17, 31) - .append(damager) - .append(amount) - .append(time) - .toHashCode(); + return Objects.hash(damager, amount, time); } @Override @@ -126,10 +121,6 @@ public boolean equals(final Object o) { DamageRecord other = (DamageRecord) o; - return new EqualsBuilder() - .append(damager, other.damager) - .append(amount, other.amount) - .append(time, other.time) - .isEquals(); + return Objects.equals(damager, other.damager) && Objects.equals(amount, other.amount) && Objects.equals(time, other.time); } } diff --git a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/ExilePearlCore.java b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/ExilePearlCore.java index 642892b4ab..34f51f8898 100644 --- a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/ExilePearlCore.java +++ b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/ExilePearlCore.java @@ -11,7 +11,6 @@ import com.devotedmc.ExilePearl.listener.ExileListener; import com.devotedmc.ExilePearl.listener.JukeAlertListener; import com.devotedmc.ExilePearl.listener.PlayerListener; -import com.devotedmc.ExilePearl.listener.RandomSpawnListener; import com.devotedmc.ExilePearl.storage.CoreStorageProvider; import com.devotedmc.ExilePearl.storage.PluginStorage; import com.devotedmc.ExilePearl.util.BastionWrapper; @@ -72,8 +71,7 @@ final class ExilePearlCore implements ExilePearlApi { private final PearlManager pearlManager; private final LoreProvider loreGenerator; private final ExilePearlRunnable pearlDecayWorker; - ; - private final BorderHandler borderHandler; + private final SuicideHandler suicideHandler; private final DamageLogger damageLogger; private BrewHandler brewHandler; @@ -84,7 +82,6 @@ final class ExilePearlCore implements ExilePearlApi { private final CivChatListener chatListener; private final BastionListener bastionListener; private final JukeAlertListener jukeAlertListener; - private final RandomSpawnListener randomSpawnListener; private final BanStickListener banStickListener; private final HashSet> commands; @@ -105,7 +102,6 @@ public ExilePearlCore(final Plugin plugin) { pearlManager = pearlFactory.createPearlManager(); loreGenerator = pearlFactory.createLoreGenerator(); pearlDecayWorker = pearlFactory.createPearlDecayWorker(); - borderHandler = pearlFactory.createPearlBorderHandler(); suicideHandler = pearlFactory.createSuicideHandler(); damageLogger = pearlFactory.createDamageLogger(); @@ -115,7 +111,6 @@ public ExilePearlCore(final Plugin plugin) { chatListener = new CivChatListener(this); bastionListener = new BastionListener(this); jukeAlertListener = new JukeAlertListener(this); - randomSpawnListener = new RandomSpawnListener(this); banStickListener = new BanStickListener(this); commands = new HashSet<>(); @@ -138,7 +133,6 @@ public void onEnable() { pearlConfig.addConfigurable(playerListener); pearlConfig.addConfigurable(exileListener); pearlConfig.addConfigurable(pearlDecayWorker); - pearlConfig.addConfigurable(borderHandler); pearlConfig.addConfigurable(suicideHandler); pearlConfig.addConfigurable(damageLogger); @@ -163,7 +157,6 @@ public void onEnable() { // Register events getServer().getPluginManager().registerEvents(playerListener, this); getServer().getPluginManager().registerEvents(suicideHandler, this); - getServer().getPluginManager().registerEvents(borderHandler, this); getServer().getPluginManager().registerEvents(exileListener, this); if (isCitadelEnabled()) { this.getServer().getPluginManager().registerEvents(citadelListener, this); @@ -189,11 +182,6 @@ public void onEnable() { } else { logIgnoringHooks("JukeAlert"); } - if (isRandomSpawnEnabled()) { - this.getServer().getPluginManager().registerEvents(randomSpawnListener, this); - } else { - logIgnoringHooks("RandomSpawn"); - } if (isBanStickEnabled()) { this.getServer().getPluginManager().registerEvents(banStickListener, this); } @@ -201,7 +189,6 @@ public void onEnable() { // Start tasks pearlDecayWorker.start(); - borderHandler.start(); suicideHandler.start(); if (pearlConfig.getDamageLogEnabled()) { damageLogger.start(); @@ -250,7 +237,6 @@ private void registerExileBroadcastPermissions() { public void onDisable() { HandlerList.unregisterAll(this); pearlDecayWorker.stop(); - borderHandler.stop(); suicideHandler.stop(); storage.disconnect(); } @@ -497,12 +483,6 @@ public boolean isJukeAlertEnabled() { return Bukkit.getPluginManager().isPluginEnabled("JukeAlert"); } - @Override - public boolean isRandomSpawnEnabled() { - return Bukkit.getPluginManager().isPluginEnabled("RandomSpawn"); - } - - @Override public boolean isBanStickEnabled() { return Bukkit.getPluginManager().isPluginEnabled("BanStick"); @@ -708,4 +688,9 @@ public int getExiledAlts(UUID player, boolean includeSelf) { public ExilePearl getPrimaryPearl(UUID player) { return pearlManager.getPrimaryPearl(player); } + + @Override + public @NotNull String namespace() { + return plugin.namespace(); + } } diff --git a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/PearlBoundaryTask.java b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/PearlBoundaryTask.java deleted file mode 100644 index e6d79a1672..0000000000 --- a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/core/PearlBoundaryTask.java +++ /dev/null @@ -1,436 +0,0 @@ -package com.devotedmc.ExilePearl.core; - -import com.devotedmc.ExilePearl.BorderHandler; -import com.devotedmc.ExilePearl.ExilePearl; -import com.devotedmc.ExilePearl.ExilePearlApi; -import com.devotedmc.ExilePearl.PearlType; -import com.devotedmc.ExilePearl.config.PearlConfig; -import com.devotedmc.ExilePearl.event.PlayerFreedEvent; -import com.devotedmc.ExilePearl.event.PlayerPearledEvent; -import com.devotedmc.ExilePearl.util.BastionWrapper; -import com.google.common.collect.ImmutableList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.UUID; -import org.bukkit.Chunk; -import org.bukkit.EntityEffect; -import org.bukkit.GameMode; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.Sound; -import org.bukkit.Tag; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; -import org.bukkit.util.Vector; -import vg.civcraft.mc.civmodcore.chat.ChatUtils; - -/** - * This class tracks the pearls players that are online and prevents them - * from entering the zone around their pearl location. - *

- * It also prevents them from entering bastion fields that they don't - * have permission on. (this part isn't done yet) - * - * @author Gordon - */ -final class PearlBoundaryTask extends ExilePearlTask implements BorderHandler { - - private Set pearledPlayers = new HashSet<>(); - - private int radius = 1000; - private double bastionDamage = 1; - - public static final int KNOCKBACK = 3; - - //these material IDs are acceptable for places to teleport player; breathable blocks and water - public static final LinkedHashSet safeOpenBlocks = new LinkedHashSet<>(Arrays.asList( - new Material[]{ - Material.AIR, Material.WATER, Material.RAIL, Material.ACTIVATOR_RAIL, - Material.POWERED_RAIL, Material.DETECTOR_RAIL, Material.SHORT_GRASS, Material.FERN, - Material.LARGE_FERN, Material.DEAD_BUSH, Material.BROWN_MUSHROOM, Material.RED_MUSHROOM, - Material.TORCH, Material.REDSTONE_WIRE, Material.WHEAT, Material.LADDER, Material.LEVER, Material.STONE_PRESSURE_PLATE} - )); - - public static final LinkedHashSet okBaseBlocks = new LinkedHashSet<>(); - - //these material IDs are ones we don't want to drop the player onto, like cactus or lava or fire or activated Ender portal - public static final LinkedHashSet painfulBlocks = - new LinkedHashSet<>(Arrays.asList(new Material[]{Material.LAVA, Material.FIRE, Material.CACTUS, Material.END_PORTAL})); - - static { - safeOpenBlocks.addAll(Tag.BUTTONS.getValues()); - safeOpenBlocks.addAll(Tag.CARPETS.getValues()); - okBaseBlocks.addAll(Tag.LOGS.getValues()); - okBaseBlocks.addAll(Tag.LEAVES.getValues()); - } - - public PearlBoundaryTask(final ExilePearlApi pearlApi) { - super(pearlApi); - } - - - @Override - public String getTaskName() { - return "Pearl Boundary"; - } - - - @Override - public int getTickInterval() { - return TICKS_PER_SECOND; - } - - @Override - public void start() { - - // Track any players who are already online - for (ExilePearl p : pearlApi.getPearls()) { - Player player = p.getPlayer(); - if (player != null && player.isOnline()) { - pearledPlayers.add(player.getUniqueId()); - } - } - - super.start(); - if (enabled) { - pearlApi.log("Using pearl radius value of %d.", radius); - } - } - - @Override - public void run() { - if (radius == 0) - return; - - Collection players = ImmutableList.copyOf(pearledPlayers); - - for (UUID uid : players) { - checkPlayer(uid); - } - } - - - /** - * Checks whether a player needs to be snapped back - * - * @param playerId The player to check - */ - private void checkPlayer(UUID playerId) { - Player player = pearlApi.getPlayer(playerId); - - if (player == null || !player.isOnline()) { - return; - } - - // Ignore creative/spectating players - if (player.getGameMode() == GameMode.CREATIVE || player.getGameMode() == GameMode.SPECTATOR) { - return; - } - - // Ignore if not pearled - ExilePearl pearl = pearlApi.getPearl(player.getUniqueId()); - if (pearl == null) { - return; - } - - // Ignore if dead already - if (player.isDead()) { - return; - } - - if (pearl.getPearlType() != PearlType.PRISON) { - if (!pushoutBastion(player)) { - checkBastion(player); - } - } - - // Ignore non-block holders - if (!pearl.getHolder().isBlock()) { - return; - } - - Location pearlLocation = pearl.getLocation(); - Location playerLocation = player.getLocation(); - - // Ignore if different world - if (pearlLocation.getWorld() != playerLocation.getWorld()) { - return; - } - - // Ignore if player outside the radius - double distance = Math.sqrt(Math.pow(pearlLocation.getX() - playerLocation.getX(), 2) + Math.pow(pearlLocation.getZ() - playerLocation.getZ(), 2)); - if (distance >= radius) { - return; - } - - Location newLoc = getCorrectedLocation(pearlLocation, playerLocation, pearl.getPlayer().isFlying()); - if (newLoc != null) { - player.teleport(newLoc, TeleportCause.PLUGIN); - pearl.getPlayer().sendMessage(ChatUtils.parseColor(String.format("You can't come within %d blocks of your pearl at (%d, %d).", radius, - pearl.getLocation().getBlockX(), pearl.getLocation().getBlockZ()))); - } - } - - private Location getCorrectedLocation(Location pearlLocation, Location playerLocation, boolean flying) { - - double x = pearlLocation.getX(); - double z = pearlLocation.getZ(); - double xLoc = playerLocation.getX(); - double zLoc = playerLocation.getZ(); - double yLoc = playerLocation.getY(); - double radiusSquared = (radius * radius) + 5; - - // algorithm originally from: http://stackoverflow.com/questions/300871/best-way-to-find-a-point-on-a-circle-closest-to-a-given-point - // modified by Lang Lukas to support elliptical border shape - - //Transform the ellipse to a circle with radius 1 (we need to transform the point the same way) - double dX = xLoc - x; - double dZ = zLoc - z; - double dU = Math.sqrt(dX * dX + dZ * dZ); //distance of the untransformed point from the center - double dT = Math.sqrt(dX * dX / radiusSquared + dZ * dZ / radiusSquared); //distance of the transformed point from the center - double f = (1 / dT + KNOCKBACK / dU); //"correction" factor for the distances - - xLoc = x + dX * f; - zLoc = z + dZ * f; - - int ixLoc = Location.locToBlock(xLoc); - int izLoc = Location.locToBlock(zLoc); - - // Make sure the chunk we're checking in is actually loaded - Chunk tChunk = pearlLocation.getWorld().getChunkAt(ixLoc >> 4, ixLoc >> 4); - if (!tChunk.isLoaded()) { - tChunk.load(); - } - - yLoc = getSafeY(playerLocation.getWorld(), ixLoc, Location.locToBlock(yLoc), izLoc, flying); - if (yLoc == -1) { - return null; - } - - return new Location(playerLocation.getWorld(), Math.round(xLoc) + 0.5, yLoc, Math.round(zLoc) + 0.5, playerLocation.getYaw(), playerLocation.getPitch()); - } - - // find closest safe Y position from the starting position - private double getSafeY(World world, int X, int Y, int Z, boolean flying) { - // artificial height limit of 127 added for Nether worlds since CraftBukkit still incorrectly returns 255 for their max height, leading to players sent to the "roof" of the Nether - final boolean isNether = world.getEnvironment() == World.Environment.NETHER; - int limTop = isNether ? 125 : (world.getMaxHeight() - 2); - final int highestBlockBoundary = Math.min(world.getHighestBlockYAt(X, Z) + 1, limTop); - - // if Y is larger than the world can be and user can fly, return Y - Unless we are in the Nether, we might not want players on the roof - if (flying && Y > limTop && !isNether) - return Y; - - // make sure Y values are within the boundaries of the world. - if (Y > limTop) { - if (isNether) - Y = limTop; // because of the roof, the nether can not rely on highestBlockBoundary, so limTop has to be used - else { - if (flying) - Y = limTop; - else - Y = highestBlockBoundary; // there will never be a save block to stand on for Y values > highestBlockBoundary - } - } - if (Y < 0) - Y = 0; - - // for non Nether worlds we don't need to check upwards to the world-limit, it is enough to check up to the highestBlockBoundary, unless player is flying - if (!isNether && !flying) { - limTop = highestBlockBoundary; - // Expanding Y search method adapted from Acru's code in the Nether plugin - - // look full up first. Start at highest Y; we want to prefer landing on the surface. - int lastOkY = -1; - for (int y2 = limTop - 1; (y2 >= Y); y2--) { - // Look above. - if (isSafeSpot(world, X, y2, Z, flying)) { - Material below = world.getBlockAt(X, y2 - 1, Z).getType(); - if (okBaseBlocks.contains(below)) { - lastOkY = y2; - } else { - return y2; - } - } - } - // We don't prefer to teleport into trees, but beats caves. If we found an option, take it. - if (lastOkY > -1) { - return lastOkY; - } - } else { - // look full up first, starting from active Y (try to preserve level height for nether and flying) - for (int y2 = Y; (y2 > limTop); y2++) { - // Look above. - if (y2 < limTop) { - if (isSafeSpot(world, X, y2, Z, flying)) - return y2; - } - } - } - // the look fully below. - for (int y1 = Y; (y1 > 0); y1--) { - // Look below. - if (y1 > 0) { - if (isSafeSpot(world, X, y1, Z, flying)) - return y1; - } - } - // this should help prevent so many cavespawns on pushback. - - return -1.0; // no safe Y location?!?!? Must be a rare spot in a Nether world or something - } - - - /** - * Checks if a particular spot consists of 2 breathable blocks over something relatively solid - * - * @param world The world - * @param X The x coordinate - * @param Y The y coordinate - * @param Z The z coordinate - * @param flying whether the player is flying - * @return true if the spot is safe - */ - private boolean isSafeSpot(World world, int X, int Y, int Z, boolean flying) { - boolean safe = safeOpenBlocks.contains(world.getBlockAt(X, Y, Z).getType()) // target block open and safe - && safeOpenBlocks.contains(world.getBlockAt(X, Y + 1, Z).getType()); // above target block open and safe - if (!safe || flying) - return safe; - - Material below = world.getBlockAt(X, Y - 1, Z).getType(); - return (safe - && (!safeOpenBlocks.contains(below) || below == Material.WATER) // below target block not open/breathable (so presumably solid), or is water - && !painfulBlocks.contains(below) // below target block not painful - ); - } - - - /** - * Tracks pearled players that log in - * - * @param e The event args - */ - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onPlayerJoin(PlayerJoinEvent e) { - if (pearlApi.isPlayerExiled(e.getPlayer())) { - pearledPlayers.add(e.getPlayer().getUniqueId()); - } - } - - /** - * Stops tracking pearled players that log out - * - * @param e The event args - */ - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onPlayerQuit(PlayerQuitEvent e) { - pearledPlayers.remove(e.getPlayer().getUniqueId()); - } - - /** - * Tracks newly pearled players - * - * @param e The event args - */ - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onPlayerPearled(PlayerPearledEvent e) { - Player p = e.getPearl().getPlayer(); - if (p != null && p.isOnline()) { - pearledPlayers.add(e.getPearl().getPlayerId()); - } - } - - /** - * Stops tracking freed players - * - * @param e The event args - */ - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onPlayerFreed(PlayerFreedEvent e) { - pearledPlayers.remove(e.getPearl().getPlayerId()); - } - - @Override - public void loadConfig(PearlConfig config) { - this.radius = config.getRulePearlRadius(); - this.bastionDamage = config.getBastionDamage(); - } - - /** - * Gets whether a player is being tracked - * - * @param player The player to check - * @return true if the player is being tracked - */ - public boolean isPlayerTracked(Player player) { - return pearledPlayers.contains(player.getUniqueId()); - } - - /** - * Checks whether the exiled player is inside any bastion fields they don't have - * permission on and deals them damage if they are. - * - * @param player The player to check - */ - private void checkBastion(Player player) { - if (pearlApi.isPlayerInUnpermittedBastion(player) && player.getHealth() > 0) { - player.setHealth(Math.max(0, player.getHealth() - bastionDamage)); - player.playSound(player.getLocation(), Sound.ENTITY_GENERIC_HURT, 1.0F, 1.0F); - player.playEffect(EntityEffect.HURT); - player.sendMessage(ChatUtils.parseColor("You aren't allowed in this bastion field when exiled.")); - } - } - - /** - * Makes a best effort to push a player out of overlapping bastion fields using some fancy vector math. - * Will work with all current types of bastions, square and circle, and resolves the vector intersections - * with ease ... if I remember my maths. - * - * @param player The player to check - * @return true if the player was in a bastion field AND could be pushed back safely, false otherwise. - * False does _not_ mean they are not in a bastion field. - */ - private boolean pushoutBastion(Player player) { - List bastions = pearlApi.getPlayerInUnpermittedBastion(player); - if (bastions.isEmpty()) { - return false; - } - Location loc = player.getLocation().clone(); - Vector v = null; - for (BastionWrapper computeShell : bastions) { - if (v == null) { - v = computeShell.getPushout(loc, KNOCKBACK); - } else { - Vector p = computeShell.getPushout(loc, KNOCKBACK); - if (p != null) { - v.add(p); - } - } - } - if (v != null) { - Location tLoc = loc.add(v); - - double yLoc = getSafeY(loc.getWorld(), tLoc.getBlockX(), tLoc.getBlockY(), tLoc.getBlockZ(), player.isFlying()); - if (yLoc == -1) - return false; - - Location newLoc = new Location(loc.getWorld(), tLoc.getX(), yLoc, tLoc.getZ(), tLoc.getYaw(), tLoc.getPitch()); - - player.teleport(newLoc, TeleportCause.PLUGIN); - - player.sendMessage(ChatUtils.parseColor("You aren't allowed to enter this bastion field when exiled.")); - return true; - } - - return false; - } -} diff --git a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/holder/BlockHolder.java b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/holder/BlockHolder.java index 53219f03a4..6e0db620ca 100644 --- a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/holder/BlockHolder.java +++ b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/holder/BlockHolder.java @@ -79,7 +79,6 @@ public HolderVerifyResult validate(final ExilePearl pearl) { // In the container inventory? List potentialPearls = new ArrayList<>(); - potentialPearls.addAll(inv.all(Material.PLAYER_HEAD).values()); potentialPearls.addAll(inv.all(Material.ENDER_PEARL).values()); for (ItemStack item : potentialPearls) { if (pearl.validateItemStack(item)) { @@ -101,7 +100,6 @@ public HolderVerifyResult validate(final ExilePearl pearl) { Inventory relInv = ((InventoryHolder) relState).getInventory(); potentialPearls = new ArrayList<>(); - potentialPearls.addAll(relInv.all(Material.PLAYER_HEAD).values()); potentialPearls.addAll(relInv.all(Material.ENDER_PEARL).values()); for (ItemStack item : potentialPearls) { if (pearl.validateItemStack(item)) { diff --git a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/listener/PlayerListener.java b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/listener/PlayerListener.java index 5585f05749..337b951cce 100644 --- a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/listener/PlayerListener.java +++ b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/listener/PlayerListener.java @@ -178,7 +178,6 @@ public void onInventoryOpen(InventoryOpenEvent e) { Inventory inv = e.getInventory(); HashMap potentialPearls = new HashMap<>(); potentialPearls.putAll(inv.all(Material.ENDER_PEARL)); - potentialPearls.putAll(inv.all(Material.PLAYER_HEAD)); for (Entry entry : potentialPearls.entrySet()) { ItemStack newitem = validatePearl(entry.getValue()); if (newitem != null) { @@ -199,7 +198,7 @@ private ItemStack validatePearl(ItemStack item) { return null; } - if ((item.getType() == Material.PLAYER_HEAD || item.getType() == Material.ENDER_PEARL) + if ((item.getType() == Material.ENDER_PEARL) && item.getEnchantmentLevel(Enchantment.UNBREAKING) != 0) { ExilePearl pearl = pearlApi.getPearlFromItemStack(item); if (pearl == null || pearl.getFreedOffline()) { diff --git a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/listener/RandomSpawnListener.java b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/listener/RandomSpawnListener.java deleted file mode 100644 index 52f6184654..0000000000 --- a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/listener/RandomSpawnListener.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.devotedmc.ExilePearl.listener; - -import com.devotedmc.ExilePearl.ExilePearl; -import com.devotedmc.ExilePearl.ExilePearlApi; -import me.josvth.randomspawn.events.NewPlayerSpawn; -import org.bukkit.Location; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; - -public class RandomSpawnListener extends RuleListener { - - - public RandomSpawnListener(ExilePearlApi pearlApi) { - super(pearlApi); - } - - - /** - * Prevents exiled players from random-spawning within their pearl radius - * - * @param e The event args - */ - @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) - public void onRandomSpawn(NewPlayerSpawn e) { - int radius = config.getRulePearlRadius(); - if (radius <= 0) { - return; - } - - // TODO: @okx -// ExilePearl pearl = pearlApi.getPearl(e.getPlayer().getUniqueId()); -// if (pearl == null) { -// return; -// } -// -// Location pearlLocation = pearl.getLocation(); -// Location playerLocation = e.getLocation(); -// -// if (pearlLocation.getWorld() != playerLocation.getWorld()) { -// return; -// } -// -// double distance = Math.sqrt(Math.pow(pearlLocation.getX() - playerLocation.getX(), 2) + Math.pow(pearlLocation.getZ() - playerLocation.getZ(), 2)); -// -// if (distance < radius) { -// e.setCancelled(true); -// } - } -} diff --git a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/util/SpawnUtil.java b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/util/SpawnUtil.java index 2c97eab1ad..90c4008fc7 100644 --- a/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/util/SpawnUtil.java +++ b/plugins/exilepearl-paper/src/main/java/com/devotedmc/ExilePearl/util/SpawnUtil.java @@ -1,6 +1,5 @@ package com.devotedmc.ExilePearl.util; -import com.devotedmc.ExilePearl.ExilePearlPlugin; import me.josvth.randomspawn.RandomSpawn; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -22,7 +21,7 @@ public static void spawnPlayer(Player player, World world) { public static Location chooseSpawn(World world) { Location spawn = null; - if (ExilePearlPlugin.getApi().isRandomSpawnEnabled()) { + if (Bukkit.getPluginManager().isPluginEnabled("RandomSpawn")) { RandomSpawn randomSpawn = (RandomSpawn) Bukkit.getPluginManager().getPlugin("RandomSpawn"); spawn = randomSpawn.chooseSpawn(world); } diff --git a/plugins/factorymod-paper/build.gradle.kts b/plugins/factorymod-paper/build.gradle.kts index 77053c962b..a0af4c9aef 100644 --- a/plugins/factorymod-paper/build.gradle.kts +++ b/plugins/factorymod-paper/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("io.papermc.paperweight.userdev") + alias(libs.plugins.paper.userdev) } version = "3.1.0" diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/ConfigParser.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/ConfigParser.java index 2416c5ae75..db647f3c8d 100644 --- a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/ConfigParser.java +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/ConfigParser.java @@ -37,7 +37,7 @@ import com.github.igotyou.FactoryMod.structures.PipeStructure; import com.github.igotyou.FactoryMod.utility.FactoryGarbageCollector; import com.github.igotyou.FactoryMod.utility.FactoryModGUI; -import org.apache.commons.lang.WordUtils; +import org.apache.commons.lang3.text.WordUtils; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Material; diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/FactoryModManager.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/FactoryModManager.java index 363f67664f..344749d9c9 100644 --- a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/FactoryModManager.java +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/FactoryModManager.java @@ -26,7 +26,7 @@ import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; -import org.bukkit.block.Chest; +import org.bukkit.block.Container; import org.bukkit.block.Dispenser; import org.bukkit.block.Dropper; import org.bukkit.entity.Player; @@ -94,6 +94,7 @@ public FactoryModManager(FactoryMod plugin, Material factoryInteractionMaterial, possibleInteractionBlock.add(Material.CRAFTING_TABLE); possibleInteractionBlock.add(Material.FURNACE); possibleInteractionBlock.add(Material.CHEST); + possibleInteractionBlock.add(Material.BARREL); possibleInteractionBlock.add(Material.TRAPPED_CHEST); // sorter @@ -267,7 +268,7 @@ public void attemptCreation(Block b, Player p) { IFactoryEgg egg = null; for (Entry entry : eggs.entrySet()) { if (entry.getKey() - .containedExactlyIn(((Chest) (fccs.getChest().getState())).getInventory())) { + .containedExactlyIn(((Container) (fccs.getChest().getState())).getInventory())) { egg = entry.getValue(); break; } @@ -277,7 +278,7 @@ public void attemptCreation(Block b, Player p) { if (f != null) { // Trigger lazy-initialize default crafting table IOSelector ((FurnCraftChestFactory) f).getTableIOSelector(); - ((Chest) (fccs.getChest().getState())).getInventory().clear(); + ((Container) (fccs.getChest().getState())).getInventory().clear(); addFactory(f); p.sendMessage(ChatColor.GREEN + "Successfully created " + f.getName()); LoggingUtils.log(f.getLogData() + " was created by " + p.getName()); diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/factories/FurnCraftChestFactory.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/factories/FurnCraftChestFactory.java index f2e6955dd3..07ffc178e7 100644 --- a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/factories/FurnCraftChestFactory.java +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/factories/FurnCraftChestFactory.java @@ -34,6 +34,7 @@ import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.Chest; +import org.bukkit.block.Barrel; import org.bukkit.block.Furnace; import org.bukkit.entity.Player; import org.bukkit.inventory.FurnaceInventory; @@ -106,6 +107,7 @@ public FurnCraftChestFactory(IInteractionManager im, IRepairManager rm, IPowerMa * should be */ public Inventory getInventory() { + // do not need to check for barrels because they are not included in the getChest() function, which is used for double chest detection/force loading if (getChest().getType() != Material.CHEST && getChest().getType() != Material.TRAPPED_CHEST) { return null; } @@ -145,6 +147,9 @@ private void getInventoriesForIoType(List combinedInvList, if (relBlock.getType() == Material.CHEST || relBlock.getType() == Material.TRAPPED_CHEST) { combinedInvList.add(((Chest) relBlock.getState()).getInventory()); } + if (relBlock.getType() == Material.BARREL) { + combinedInvList.add(((Barrel) relBlock.getState()).getInventory()); + } } Block tblock = fccs.getCraftingTable(); for (BlockFace relativeFace : ioTypeFunc.apply(getTableIOSelector()).apply(facing)) { @@ -152,6 +157,9 @@ private void getInventoriesForIoType(List combinedInvList, if (relBlock.getType() == Material.CHEST || relBlock.getType() == Material.TRAPPED_CHEST) { combinedInvList.add(((Chest) relBlock.getState()).getInventory()); } + if (relBlock.getType() == Material.BARREL) { + combinedInvList.add(((Barrel) relBlock.getState()).getInventory()); + } } } @@ -349,6 +357,12 @@ public void attemptToActivate(Player p, boolean onStartUp) { return; } + // Check that there is an output selected at all + if (furnaceIoSelector.getOutputCount() == 0 && tableIoSelector.getOutputCount() == 0) { + p.sendMessage(String.format("%sFailed to activate factory, it has no IO Outputs configured", ChatColor.RED)); + return; + } + if (!onStartUp && currentRecipe instanceof Upgraderecipe && FactoryMod.getInstance().getManager().isCitadelEnabled()) { // only allow permitted members to upgrade the factory Reinforcement rein = ReinforcementLogic.getReinforcementAt(mbs.getCenter()); @@ -538,6 +552,14 @@ public void run() { // if the production timer has reached the recipes production // time remove input from chest, and add output material else { + // check that there is an output to place the item + // check is put here instead of on fuel tick for performance reasons, but will waste charcoal + if (getOutputInventory().getSize() == 0) { + sendActivatorMessage(ChatColor.RED + currentRecipe.getName() + " in " + name + " deactivated because it has no output inventory"); + deactivate(); + return; + } + LoggingUtils.log("Executing recipe " + currentRecipe.getName() + " for " + getLogData()); if (currentRecipe instanceof InputRecipe) { RecipeExecuteEvent ree = new RecipeExecuteEvent(this, (InputRecipe) currentRecipe); diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/InputRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/InputRecipe.java index 569a1039dc..6a31f669c7 100644 --- a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/InputRecipe.java +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/InputRecipe.java @@ -9,7 +9,7 @@ import java.util.List; import java.util.Map.Entry; import java.util.concurrent.TimeUnit; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.inventory.Inventory; diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PrintBookRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PrintBookRecipe.java index d36d79e077..9e6307c3b9 100644 --- a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PrintBookRecipe.java +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PrintBookRecipe.java @@ -84,18 +84,18 @@ protected ItemStack createBook(ItemStack printingPlateStack, int amount) { bookItem.setData(DataComponentTypes.WRITTEN_BOOK_CONTENT, printingPlateStack.getData(DataComponentTypes.WRITTEN_BOOK_CONTENT)); } else { // Handle legacy (pre-1.20.5) plates bookItem.editMeta(BookMeta.class, meta -> { - meta.title(Component.text(bookData.get("title").getAsString())); - meta.author(Component.text(bookData.get("author").getAsString())); - meta.setGeneration(BookMeta.Generation.values()[((IntTag) bookData.get("generation")).getAsInt()]); + meta.title(Component.text(bookData.get("title").asString().get())); + meta.author(Component.text(bookData.get("author").asString().get())); + meta.setGeneration(BookMeta.Generation.values()[((IntTag) bookData.get("generation")).intValue()]); List pages = new ArrayList<>(); ((ListTag) bookData.get("pages")).forEach(page -> { - pages.add(GsonComponentSerializer.gson().deserialize(page.getAsString())); + pages.add(GsonComponentSerializer.gson().deserialize(page.asString().get())); }); meta.pages(pages); }); } - + return bookItem; } diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PrintNoteRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PrintNoteRecipe.java index 17207fda2f..5a2decc9fd 100644 --- a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PrintNoteRecipe.java +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/PrintNoteRecipe.java @@ -114,7 +114,7 @@ private BookInfo getBookInfo(ItemStack printingPlateStack) { if (this.secureNote) { net.minecraft.world.item.ItemStack bookItem = CraftItemStack.asNMSCopy(printingPlateStack); - String bookSN = bookItem.get(DataComponents.CUSTOM_DATA).copyTag().getString("SN"); + String bookSN = bookItem.get(DataComponents.CUSTOM_DATA).copyTag().getString("SN").get(); info.lines.add(bookSN); } diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/RandomOutputRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/RandomOutputRecipe.java index 5a760ccf66..5591f54d69 100644 --- a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/RandomOutputRecipe.java +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/RandomOutputRecipe.java @@ -21,6 +21,7 @@ public class RandomOutputRecipe extends InputRecipe { private Map outputs; private static Random rng; private ItemMap lowestChanceMap; + private ItemMap displayOutput; public RandomOutputRecipe(String identifier, String name, int productionTime, ItemMap input, Map outputs, ItemMap displayOutput) { @@ -42,8 +43,10 @@ public RandomOutputRecipe(String identifier, String name, int productionTime, It if (lowestChanceMap == null) { lowestChanceMap = new ItemMap(new ItemStack(Material.STONE)); } + this.displayOutput = lowestChanceMap; } else { lowestChanceMap = displayOutput; + this.displayOutput = displayOutput; } } @@ -96,7 +99,7 @@ public ItemMap getRandomOutput() { @Override public Material getRecipeRepresentationMaterial() { - return input.getItemStackRepresentation().get(0).getType(); + return displayOutput.getItemStackRepresentation().get(0).getType(); } @Override diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/heliodor/HeliodorRefillRecipe.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/heliodor/HeliodorRefillRecipe.java index f5e155aee1..591324ea6b 100644 --- a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/heliodor/HeliodorRefillRecipe.java +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/recipes/heliodor/HeliodorRefillRecipe.java @@ -32,10 +32,10 @@ public boolean applyEffect(Inventory inputInv, Inventory outputInv, FurnCraftChe logBeforeRecipeRun(combo, fccf); GemInput gemInput = getGemsToRemove(inputInv.getStorageContents()); - if (gemInput == null) { + if (gemInput == null || gemInput.amount < this.count) { return false; } - int amount = gemInput.amount; + int amount = this.count; ItemMap toRemove = input.clone(); ItemStack gem = HeliodorGem.createHeliodorGem(gemInput.charge, Math.min(100, gemInput.charge + this.addMaxCharge)); diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/structures/FurnCraftChestStructure.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/structures/FurnCraftChestStructure.java index 73aaa2acea..10fde22ae1 100644 --- a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/structures/FurnCraftChestStructure.java +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/structures/FurnCraftChestStructure.java @@ -26,6 +26,7 @@ public FurnCraftChestStructure(Block center) { LinkedList chestBlocks = new LinkedList<>(); chestBlocks.addAll(searchForBlockOnAllSides(center, Material.CHEST)); chestBlocks.addAll(searchForBlockOnAllSides(center, Material.TRAPPED_CHEST)); + chestBlocks.addAll(searchForBlockOnAllSides(center, Material.BARREL)); for (Block b : chestBlocks) { BlockFace chestFace = center.getFace(b); if (chestFace == null) continue; // fricc off nullcheck @@ -54,7 +55,8 @@ public void recheckComplete() { && furnace.getBlock().getType() == Material.FURNACE && chest != null && (chest.getBlock().getType() == Material.CHEST - || chest.getBlock().getType() == Material.TRAPPED_CHEST); + || chest.getBlock().getType() == Material.TRAPPED_CHEST + || chest.getBlock().getType() == Material.BARREL); } public boolean isComplete() { @@ -77,6 +79,9 @@ public Block getChest() { Material.CHEST); MultiBlockStructure.searchForBlockOnAllSides(chest.getBlock(), Material.TRAPPED_CHEST); + + // not necessary to check for more blocks if the "chest" is a barrel because it cannot be double + return chest.getBlock(); } @@ -84,7 +89,8 @@ public boolean relevantBlocksDestroyed() { return craftingTable.getBlock().getType() != Material.CRAFTING_TABLE && furnace.getBlock().getType() != Material.FURNACE && chest.getBlock().getType() != Material.CHEST - && chest.getBlock().getType() != Material.TRAPPED_CHEST; + && chest.getBlock().getType() != Material.TRAPPED_CHEST + && chest.getBlock().getType() != Material.BARREL; } public List getRelevantBlocks() { diff --git a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/IOConfigSection.java b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/IOConfigSection.java index 61e3782ce3..cdae3aced4 100644 --- a/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/IOConfigSection.java +++ b/plugins/factorymod-paper/src/main/java/com/github/igotyou/FactoryMod/utility/IOConfigSection.java @@ -64,12 +64,12 @@ private Clickable getIoButton(Direction dir) { private Clickable getIoClickable(Material adjacentType, Direction dir, String dirLabel) { IOSelector.IOState dirState = ioSelector.getState(dir); - boolean chestMissing = adjacentType != Material.CHEST && adjacentType != Material.TRAPPED_CHEST; + boolean chestMissing = adjacentType != Material.CHEST && adjacentType != Material.TRAPPED_CHEST && adjacentType != Material.BARREL; ItemStack display; if (chestMissing) { display = new ItemStack(Material.BARRIER); ItemUtils.addComponentLore(display, Component - .text("") + .text("") .style(Style.style(TextDecoration.BOLD)) .color(TextColor.color(255, 0, 0))); } else { diff --git a/plugins/finale-paper/build.gradle.kts b/plugins/finale-paper/build.gradle.kts index 374d9b3c4b..967894b2a5 100644 --- a/plugins/finale-paper/build.gradle.kts +++ b/plugins/finale-paper/build.gradle.kts @@ -1,9 +1,5 @@ plugins { - id("io.papermc.paperweight.userdev") -} - -repositories { - maven("https://repo.dmulloy2.net/repository/public") + alias(libs.plugins.paper.userdev) } version = "2.1.0" diff --git a/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/ConfigParser.java b/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/ConfigParser.java index 9a1d84104f..3206300027 100644 --- a/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/ConfigParser.java +++ b/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/ConfigParser.java @@ -19,7 +19,7 @@ import com.github.maxopoly.finale.misc.knockback.KnockbackModifier; import com.github.maxopoly.finale.misc.knockback.KnockbackType; import com.github.maxopoly.finale.misc.warpfruit.WarpFruitTracker; -import org.apache.commons.lang.WordUtils; +import org.apache.commons.lang3.text.WordUtils; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Color; diff --git a/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/Finale.java b/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/Finale.java index 39458b9de2..680393d45d 100644 --- a/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/Finale.java +++ b/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/Finale.java @@ -52,6 +52,8 @@ public static Finale getPlugin() { private EnchantmentDisableListener enchantmentDisableListener; private WeaponModificationListener weaponModificationListener; + private PearlCoolDownListener pearlCoolDownListener; + public CombatTagPlusManager getCombatTagPlusManager() { return ctpManager; } @@ -103,7 +105,7 @@ private void registerListener() { // are enabled. if (config.isPearlEnabled()) { Bukkit.getPluginManager() - .registerEvents(new PearlCoolDownListener(config.getPearlCoolDown(), config.combatTagOnPearl(), + .registerEvents(pearlCoolDownListener = new PearlCoolDownListener(config.getPearlCoolDown(), config.combatTagOnPearl(), ctpManager), this); } Bukkit.getPluginManager().registerEvents(weaponModificationListener = new WeaponModificationListener(), this); @@ -130,6 +132,10 @@ private void registerListener() { Bukkit.getPluginManager().registerEvents(new GappleListener(), this); } + public PearlCoolDownListener getPearlCoolDownListener() { + return pearlCoolDownListener; + } + private void registerCommands() { commandManager.registerCommand(new CardinalCommand()); commandManager.registerCommand(new CombatConfigCommand()); diff --git a/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/combat/CombatUtil.java b/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/combat/CombatUtil.java index 65fbb91f06..9eb37c1bfc 100644 --- a/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/combat/CombatUtil.java +++ b/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/combat/CombatUtil.java @@ -28,10 +28,9 @@ import net.minecraft.world.entity.boss.EnderDragonPart; import net.minecraft.world.entity.decoration.ArmorStand; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.SwordItem; import net.minecraft.world.item.enchantment.EnchantmentHelper; -import net.minecraft.world.level.Level; import net.minecraft.world.phys.Vec3; +import org.bukkit.Tag; import org.bukkit.craftbukkit.entity.CraftPlayer; import org.bukkit.craftbukkit.util.CraftVector; import org.bukkit.entity.Player; @@ -62,7 +61,7 @@ public static void attack(ServerPlayer attacker, Entity victim) { if (victim.isAttackable() && !victim.skipAttackInteraction(attacker)) { float damage = (float) attacker.getAttribute(Attributes.ATTACK_DAMAGE).getValue(); DamageSource damagesource = attacker.damageSources().playerAttack(attacker); - float f1 = EnchantmentHelper.modifyDamage(attacker.serverLevel(), attacker.getWeaponItem(), victim, damagesource, damage) - damage; + float f1 = EnchantmentHelper.modifyDamage(attacker.level(), attacker.getWeaponItem(), victim, damagesource, damage) - damage; float f2; boolean shouldDamage = true; @@ -72,11 +71,11 @@ public static void attack(ServerPlayer attacker, Entity victim) { damage *= 0.2F + f2 * f2 * 0.8F; f1 *= f2; } - Level world = attacker.level(); + ServerLevel world = attacker.level(); if (damage > 0.0F || f1 > 0.0F) { boolean dealtExtraKnockback = false; byte baseKnockbackLevel = 1; - float knockbackLevel = baseKnockbackLevel + EnchantmentHelper.modifyKnockback(attacker.serverLevel(), attacker.getWeaponItem(), victim, damagesource, (float) attacker.getAttributeValue(Attributes.ATTACK_KNOCKBACK)); + float knockbackLevel = baseKnockbackLevel + EnchantmentHelper.modifyKnockback(attacker.level(), attacker.getWeaponItem(), victim, damagesource, (float) attacker.getAttributeValue(Attributes.ATTACK_KNOCKBACK)); if (sprintHandler.isSprinting(attacker) && shouldDamage) { if (config.getCombatSounds().isKnockbackEnabled()) { @@ -110,7 +109,7 @@ public static void attack(ServerPlayer attacker, Entity victim) { if (shouldDamage && !shouldCrit && !dealtExtraKnockback && attacker.onGround() && d0 < Mth.square(d1)) { ItemStack itemstack = attacker.getItemInHand(InteractionHand.MAIN_HAND); - if (itemstack.getItem() instanceof SwordItem) { + if (Tag.ITEMS_SWORDS.isTagged(itemstack.asBukkitMirror().getType())) { shouldSweep = true; } } @@ -153,7 +152,7 @@ public static void attack(ServerPlayer attacker, Entity victim) { } // CraftBukkit end - EnchantmentHelper.doPostAttackEffects(attacker.serverLevel(), victim, damagesource); + EnchantmentHelper.doPostAttackEffects(attacker.level(), victim, damagesource); } } @@ -218,7 +217,7 @@ public static void attack(ServerPlayer attacker, Entity victim) { doPostHurtEnemy = attacker.getWeaponItem().hurtEnemy(livingVictim, attacker); } - EnchantmentHelper.doPostAttackEffects(attacker.serverLevel(), victim, damagesource); + EnchantmentHelper.doPostAttackEffects(attacker.level(), victim, damagesource); if (!itemstack1.isEmpty() && object instanceof LivingEntity) { if (doPostHurtEnemy) { @@ -238,10 +237,10 @@ public static void attack(ServerPlayer attacker, Entity victim) { attacker.awardStat(Stats.DAMAGE_DEALT, Math.round(f5 * 10.0F)); - if (world instanceof ServerLevel && f5 > 2.0F) { + if (f5 > 2.0F) { int k = (int) ((double) f5 * 0.5D); - ((ServerLevel) world).sendParticles(ParticleTypes.DAMAGE_INDICATOR, victim.getX(), + world.sendParticles(ParticleTypes.DAMAGE_INDICATOR, victim.getX(), victim.getY() + (double) (victim.getEyeHeight() * 0.5F), victim.getZ(), k, 0.1D, 0.0D, 0.1D, 0.2D); } diff --git a/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/listeners/DamageListener.java b/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/listeners/DamageListener.java index 047e134254..0072f0d72c 100644 --- a/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/listeners/DamageListener.java +++ b/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/listeners/DamageListener.java @@ -67,6 +67,9 @@ public void damageEntity(EntityDamageByEntityEvent e) { if (e.getDamager().getType() == EntityType.TRIDENT) { handleTrident(e); } + if (e.getDamager().getType() == EntityType.TNT_MINECART) { + handleMinecart(e); + } if (e.getDamager().getType() == EntityType.FIREWORK_ROCKET) { DamageModificationConfig fireworkModifier = modifiers.get(DamageModificationConfig.Type.FIREWORK); if (fireworkModifier != null) { @@ -127,6 +130,13 @@ private void handleTrident(EntityDamageByEntityEvent e) { e.setDamage(damage); } + private void handleMinecart(EntityDamageByEntityEvent e) { + DamageModificationConfig modifier = modifiers.get(DamageModificationConfig.Type.CART); + if (modifier != null) { + e.setDamage(modifier.modify(e.getDamage())); + } + } + private void handleArrow(EntityDamageByEntityEvent e) { DamageModificationConfig arrowModifier = modifiers.get(DamageModificationConfig.Type.ARROW); if (arrowModifier != null) { diff --git a/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/listeners/NetheriteFireResistanceListener.java b/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/listeners/NetheriteFireResistanceListener.java index 4c66ff947a..acda5e1cec 100644 --- a/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/listeners/NetheriteFireResistanceListener.java +++ b/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/listeners/NetheriteFireResistanceListener.java @@ -9,6 +9,7 @@ import org.bukkit.plugin.Plugin; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; +import vg.civcraft.mc.civmodcore.inventory.CustomItem; public class NetheriteFireResistanceListener { @@ -31,6 +32,7 @@ public void start() { public void applyFireResistance() { for (Player player : Bukkit.getOnlinePlayers()) { int pieces = 0; + int mpieces = 0; for (ItemStack armour : player.getInventory().getArmorContents()) { if (armour == null) { continue; @@ -39,8 +41,12 @@ public void applyFireResistance() { switch (type) { case NETHERITE_BOOTS, NETHERITE_HELMET, NETHERITE_CHESTPLATE, NETHERITE_LEGGINGS -> pieces++; } + String customItem = CustomItem.getCustomItemKey(armour); + if (customItem != null && customItem.startsWith("meteoric_iron_")) { + mpieces++; + } } - if (pieces == 4) { + if (pieces == 4 || mpieces == 4) { PotionEffect currentEffect = player.getPotionEffect(PotionEffectType.FIRE_RESISTANCE); if (currentEffect == null || currentEffect.getDuration() < 5 * 20 + 19) { player.addPotionEffect(new PotionEffect( diff --git a/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/listeners/WeaponModificationListener.java b/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/listeners/WeaponModificationListener.java index 5ebbe215e3..65ac1d8722 100644 --- a/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/listeners/WeaponModificationListener.java +++ b/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/listeners/WeaponModificationListener.java @@ -6,10 +6,7 @@ import com.github.maxopoly.finale.misc.TippedArrowModifier; import com.github.maxopoly.finale.misc.WeaponModifier; import java.util.List; -import java.util.Map; -import io.papermc.paper.potion.PotionMix; import net.kyori.adventure.text.Component; -import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.attribute.Attribute; @@ -22,9 +19,7 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.PotionMeta; -import org.bukkit.potion.PotionData; import org.bukkit.potion.PotionEffect; -import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionType; public class WeaponModificationListener implements Listener { diff --git a/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/misc/DamageModificationConfig.java b/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/misc/DamageModificationConfig.java index 67dc66a32f..b48df9d2ce 100644 --- a/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/misc/DamageModificationConfig.java +++ b/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/misc/DamageModificationConfig.java @@ -3,7 +3,7 @@ public class DamageModificationConfig { public enum Type { - ALL, SWORD, SHARPNESS_ENCHANT, STRENGTH_EFFECT, ARROW, POWER_ENCHANT, CRIT, TRIDENT, IMPALE_ENCHANT, FIREWORK + ALL, SWORD, SHARPNESS_ENCHANT, STRENGTH_EFFECT, ARROW, POWER_ENCHANT, CRIT, TRIDENT, IMPALE_ENCHANT, FIREWORK, CART } private double multiplier; diff --git a/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/misc/ally/AllyHandler.java b/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/misc/ally/AllyHandler.java index ab34399d31..f4b2380906 100644 --- a/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/misc/ally/AllyHandler.java +++ b/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/misc/ally/AllyHandler.java @@ -51,7 +51,7 @@ public void run() { Team allyTeam = getAllyTeam(player); for (UUID allyID : playerAlliesEntry.getValue()) { Player ally = Bukkit.getPlayer(allyID); - if (ally.isOnline()) { + if (ally != null && ally.isOnline()) { allyTeam.addEntry(ally.getName()); } } @@ -77,15 +77,16 @@ public void init() { } new BukkitRunnable() { - @Override public void run() { - for (Map.Entry> entry : playerAllies.entrySet()) { + for (Iterator>> iterator = playerAllies.entrySet().iterator(); iterator.hasNext(); ) { + Map.Entry> entry = iterator.next(); Player player = Bukkit.getPlayer(entry.getKey()); + if (player == null) { + continue; + } Set allyIDs = entry.getValue(); - Iterator allyIDIterator = allyIDs.iterator(); - while (allyIDIterator.hasNext()) { - UUID allyID = allyIDIterator.next(); + for (UUID allyID : allyIDs) { Player ally = Bukkit.getPlayer(allyID); if (ally != null && ally.isOnline()) { Team allyTeam = getAllyTeam(player); diff --git a/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/misc/arrow/ArrowFragment.java b/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/misc/arrow/ArrowFragment.java index ba3fd998f6..225342e9ab 100644 --- a/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/misc/arrow/ArrowFragment.java +++ b/plugins/finale-paper/src/main/java/com/github/maxopoly/finale/misc/arrow/ArrowFragment.java @@ -92,7 +92,12 @@ public boolean progress() { if (localDamage > 0) { nearby.damage(localDamage, arrow); - nearby.addPotionEffects(effects); + + List reducedDuration = new ArrayList<>(); + for (PotionEffect effect : effects) { + reducedDuration.add(new PotionEffect(effect.getType(), effect.getDuration() / 8, effect.getAmplifier())); + } + nearby.addPotionEffects(reducedDuration); } } } diff --git a/plugins/heliodor-paper/build.gradle.kts b/plugins/heliodor-paper/build.gradle.kts index f3ded5a51a..382f6b3130 100644 --- a/plugins/heliodor-paper/build.gradle.kts +++ b/plugins/heliodor-paper/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("io.papermc.paperweight.userdev") + alias(libs.plugins.paper.userdev) } version = "1.0.0" @@ -10,4 +10,5 @@ dependencies { } compileOnly(project(":plugins:civmodcore-paper")) + compileOnly(project(":plugins:combattagplus-paper")) } diff --git a/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/HeliodorPlugin.java b/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/HeliodorPlugin.java index 49f8db69f6..b803a8969d 100644 --- a/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/HeliodorPlugin.java +++ b/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/HeliodorPlugin.java @@ -6,6 +6,7 @@ import java.util.function.Supplier; import net.civmc.heliodor.backpack.BackpackListener; import net.civmc.heliodor.command.HeliodorDebugCommand; +import net.civmc.heliodor.farmbeacon.FarmBeaconListener; import net.civmc.heliodor.heliodor.PickaxeBreakListener; import net.civmc.heliodor.heliodor.VeinDetectListener; import net.civmc.heliodor.heliodor.infusion.InfusionListener; @@ -13,6 +14,7 @@ import net.civmc.heliodor.heliodor.infusion.chunkmeta.CauldronDao; import net.civmc.heliodor.heliodor.infusion.chunkmeta.CauldronInfuseData; import net.civmc.heliodor.heliodor.infusion.chunkmeta.CauldronInfusion; +import net.civmc.heliodor.vein.EnderEyeListener; import net.civmc.heliodor.vein.OrePredicate; import net.civmc.heliodor.vein.SqlVeinDao; import net.civmc.heliodor.vein.VeinCache; @@ -80,6 +82,10 @@ public void onEnable() { getServer().getPluginManager().registerEvents(new InfusionListener(infusionManager, chunkMetaView), this); } + FarmBeaconListener farmBeaconListener = new FarmBeaconListener(); + getServer().getPluginManager().registerEvents(farmBeaconListener, this); + farmBeaconListener.protect(protector); + Bukkit.getScheduler().runTaskTimer(this, this.recipes, 15 * 20, 15 * 20); getCommand("heliodor").setExecutor(new HeliodorDebugCommand(veinCache, veinSpawner, oreLocationsKey)); @@ -125,7 +131,8 @@ private void initVeins() { configPosList, meteoricIronConfigSection.getInt("min_position_radius"), meteoricIronConfigSection.getInt("max_position_radius"), - meteoricIronConfigSection.getInt("max_bury") + meteoricIronConfigSection.getInt("max_bury"), + meteoricIronConfigSection.getBoolean("override-ender-eyes") ); SqlVeinDao veinDao = new SqlVeinDao(database); @@ -138,6 +145,10 @@ private void initVeins() { veinSpawner = new VeinSpawner(this, veinDao, veinCache, meteoricIronConfig); veinSpawner.start(); + if (meteoricIronConfig.overrideEnderEyes()) { + getServer().getPluginManager().registerEvents(new EnderEyeListener(meteoricIronConfig.config().world(), meteoricIronConfig.positions()), this); + } + getServer().getPluginManager().registerEvents(new PickaxeBreakListener(veinCache, meteoricIronConfig.config().lowDistance(), meteoricIronConfig.config().highDistance(), diff --git a/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/HeliodorRecipeGiver.java b/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/HeliodorRecipeGiver.java index 19e1500dda..0e9dc0245a 100644 --- a/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/HeliodorRecipeGiver.java +++ b/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/HeliodorRecipeGiver.java @@ -4,17 +4,15 @@ import net.civmc.heliodor.heliodor.HeliodorGem; import net.civmc.heliodor.heliodor.HeliodorPickaxe; import net.civmc.heliodor.meteoriciron.FactoryUpgrade; +import net.civmc.heliodor.farmbeacon.FarmBeacon; import net.civmc.heliodor.meteoriciron.MeteoricIron; import net.civmc.heliodor.meteoriciron.MeteoricIronArmour; import net.civmc.heliodor.meteoriciron.MeteoricIronTools; import org.bukkit.Bukkit; -import org.bukkit.Keyed; import org.bukkit.NamespacedKey; import org.bukkit.entity.Player; import org.bukkit.inventory.CraftingRecipe; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.Recipe; -import org.bukkit.inventory.ShapedRecipe; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; import java.util.ArrayList; @@ -33,6 +31,7 @@ public HeliodorRecipeGiver(Plugin plugin) { register(MeteoricIronArmour.getRecipes(plugin)); register(Backpack.getRecipes(plugin)); register(FactoryUpgrade.getRecipes(plugin)); + register(FarmBeacon.getRecipes(plugin)); } @Override diff --git a/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/backpack/BackpackListener.java b/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/backpack/BackpackListener.java index 19ac1711f1..2ac92f2199 100644 --- a/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/backpack/BackpackListener.java +++ b/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/backpack/BackpackListener.java @@ -2,7 +2,8 @@ import net.civmc.heliodor.HeliodorPlugin; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import net.minelink.ctplus.CombatTagPlus; +import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.NamespacedKey; import org.bukkit.block.Block; @@ -22,7 +23,6 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.persistence.PersistentDataType; import org.bukkit.plugin.java.JavaPlugin; -import java.util.List; public class BackpackListener implements Listener { @@ -125,6 +125,10 @@ public void on(InventoryDragEvent event) { @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void on(PlayerDeathEvent event) { + if (Bukkit.getPluginManager().isPluginEnabled("CombatTagPlus") && !JavaPlugin.getPlugin(CombatTagPlus.class).getTagManager().isTagged(event.getPlayer().getUniqueId())) { + return; + } + Inventory enderChest = event.getPlayer().getEnderChest(); for (ItemStack item : enderChest.getStorageContents()) { if (item != null && !item.isEmpty()) { diff --git a/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/farmbeacon/FarmBeacon.java b/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/farmbeacon/FarmBeacon.java new file mode 100644 index 0000000000..d50cf6a2f0 --- /dev/null +++ b/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/farmbeacon/FarmBeacon.java @@ -0,0 +1,48 @@ +package net.civmc.heliodor.farmbeacon; + +import java.util.List; +import net.civmc.heliodor.meteoriciron.MeteoricIron; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.CraftingRecipe; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.ShapedRecipe; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.recipe.CraftingBookCategory; +import org.bukkit.plugin.Plugin; +import vg.civcraft.mc.civmodcore.inventory.CustomItem; + +public interface FarmBeacon { + + static ItemStack createFarmBeacon() { + ItemStack item = new ItemStack(Material.BEACON); + ItemMeta meta = item.getItemMeta(); + meta.itemName(Component.text("Farm Beacon", NamedTextColor.LIGHT_PURPLE)); + meta.lore(List.of(Component.text("Allows crops to grow larger and more bountiful", NamedTextColor.WHITE), + Component.text("Increases soil fertility +6%", NamedTextColor.WHITE), + Component.text("within a 20 block radius.", NamedTextColor.WHITE), + Component.text("Takes 6 days to become effective.", NamedTextColor.WHITE), + Component.text("Requires a beacon base and sky exposure", NamedTextColor.WHITE))); + meta.setEnchantmentGlintOverride(true); + meta.setFireResistant(true); + item.setItemMeta(meta); + CustomItem.registerCustomItem("farm_beacon", item); + + return item; + } + + static boolean isFarmBeacon(ItemStack item) { + return CustomItem.isCustomItem(item, "farm_beacon"); + } + + static List getRecipes(Plugin plugin) { + ShapedRecipe recipe = new ShapedRecipe(new NamespacedKey(plugin, "meteoric_farm_beacon"), FarmBeacon.createFarmBeacon()) + .shape("nnn", "nbn", "nnn") + .setIngredient('n', Material.NETHERITE_INGOT) + .setIngredient('b', Material.BEACON); + recipe.setCategory(CraftingBookCategory.MISC); + return List.of(recipe); + } +} diff --git a/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/farmbeacon/FarmBeaconListener.java b/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/farmbeacon/FarmBeaconListener.java new file mode 100644 index 0000000000..486232528d --- /dev/null +++ b/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/farmbeacon/FarmBeaconListener.java @@ -0,0 +1,81 @@ +package net.civmc.heliodor.farmbeacon; + +import net.civmc.heliodor.BlockProtector; +import net.civmc.heliodor.HeliodorPlugin; +import net.civmc.heliodor.backpack.Backpack; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.block.Beacon; +import org.bukkit.block.Block; +import org.bukkit.event.Event; +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.BlockPlaceEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.plugin.java.JavaPlugin; + +public class FarmBeaconListener implements Listener { + + public static final NamespacedKey FARM_BEACON_KEY = new NamespacedKey(JavaPlugin.getPlugin(HeliodorPlugin.class), "farm_beacon"); + + public void protect(BlockProtector protector) { + protector.addPredicate(l -> l.getBlock().getState(false) instanceof Beacon beacon + && beacon.getPersistentDataContainer().has(FARM_BEACON_KEY)); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockPlaceEvent event) { + ItemStack item = event.getItemInHand(); + if (!FarmBeacon.isFarmBeacon(item)) { + return; + } + + Block block = event.getBlock(); + Beacon type = (Beacon) block.getState(false); + + type.getPersistentDataContainer().set(FARM_BEACON_KEY, PersistentDataType.LONG, System.currentTimeMillis()); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockBreakEvent event) { + Block block = event.getBlock(); + if (block.getType() != Material.BEACON) { + return; + } + + Beacon state = (Beacon) block.getState(false); + + if (!state.getPersistentDataContainer().has(FARM_BEACON_KEY)) { + return; + } + + event.setDropItems(false); + block.getWorld().dropItemNaturally(block.getLocation().add(0.5, 0.5, 0.5), FarmBeacon.createFarmBeacon()); + } + + @EventHandler + public void on(PlayerInteractEvent event) { + Action action = event.getAction(); + Block block = event.getClickedBlock(); + if (block == null || block.getType() != Material.BEACON || action != Action.RIGHT_CLICK_BLOCK) { + return; + } + + boolean flag = event.getPlayer().getInventory().getItemInMainHand().getType() != Material.AIR || event.getPlayer().getInventory().getItemInOffHand().getType() != Material.AIR; + boolean flag1 = event.getPlayer().isSneaking() && flag; + if (flag1) { + return; + } + + Beacon state = (Beacon) block.getState(false); + + if (state.getPersistentDataContainer().has(FARM_BEACON_KEY)) { + event.setUseInteractedBlock(Event.Result.DENY); + } + } +} diff --git a/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/vein/EnderEyeListener.java b/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/vein/EnderEyeListener.java new file mode 100644 index 0000000000..950e26037a --- /dev/null +++ b/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/vein/EnderEyeListener.java @@ -0,0 +1,51 @@ +package net.civmc.heliodor.vein; + +import net.civmc.heliodor.vein.data.VerticalBlockPos; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.EnderSignal; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntitySpawnEvent; +import java.util.List; + +public class EnderEyeListener implements Listener { + private final String worldName; + private final List positions; + + public EnderEyeListener(String worldName, List positions) { + this.worldName = worldName; + this.positions = positions; + } + + @EventHandler + public void on(EntitySpawnEvent event) { + if (!(event.getEntity() instanceof EnderSignal eye)) { + return; + } + + World world = event.getEntity().getWorld(); + if (!world.getName().equals(worldName)) { + return; + } + + Location closestLocation = null; + double distanceSquared = Double.MAX_VALUE; + + if (positions.isEmpty()) { + return; + } + + for (VerticalBlockPos position : positions) { + Location portal = new Location(world, position.x(), world.getMinHeight(), position.z()); + double portalDistanceSquared = portal.distanceSquared(eye.getLocation()); + if (portalDistanceSquared < distanceSquared) { + closestLocation = portal; + distanceSquared = portalDistanceSquared; + } + } + + eye.setTargetLocation(closestLocation); + } + +} diff --git a/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/vein/data/MeteoricIronVeinConfig.java b/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/vein/data/MeteoricIronVeinConfig.java index 420a851c7c..e83865c6e7 100644 --- a/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/vein/data/MeteoricIronVeinConfig.java +++ b/plugins/heliodor-paper/src/main/java/net/civmc/heliodor/vein/data/MeteoricIronVeinConfig.java @@ -2,6 +2,6 @@ import java.util.List; -public record MeteoricIronVeinConfig(VeinConfig config, List positions, int minPositionRadius, int maxPositionRadius, int maxBury) { +public record MeteoricIronVeinConfig(VeinConfig config, List positions, int minPositionRadius, int maxPositionRadius, int maxBury, boolean overrideEnderEyes) { public static final String TYPE_NAME = "meteorite"; } diff --git a/plugins/heliodor-paper/src/main/resources/config.yml b/plugins/heliodor-paper/src/main/resources/config.yml index e5d2b64a1e..3f6905720e 100644 --- a/plugins/heliodor-paper/src/main/resources/config.yml +++ b/plugins/heliodor-paper/src/main/resources/config.yml @@ -24,6 +24,8 @@ meteoric_iron_vein: max_spawns: 1 # the positions that this vein should spawn around, e.g. nether portals positions: [] + # true if ender eye targets should be forced to target the positions + override-ender-eyes: false # the minimum distance from a nether portal the centre of a vein should spawn from min_position_radius: 1 # the maximum distance from a nether portal the centre of a vein should spawn from diff --git a/plugins/heliodor-paper/src/main/resources/plugin.yml b/plugins/heliodor-paper/src/main/resources/plugin.yml index 595ee860be..87ed9571a3 100644 --- a/plugins/heliodor-paper/src/main/resources/plugin.yml +++ b/plugins/heliodor-paper/src/main/resources/plugin.yml @@ -5,6 +5,7 @@ author: Okx api-version: 1.21 description: Implements various mechanics relating to custom ores, separately from HiddenOre depend: [CivModCore] +softdepend: [CombatTagPlus] commands: heliodor: permission: helidor.admin diff --git a/plugins/hiddenore-paper/build.gradle.kts b/plugins/hiddenore-paper/build.gradle.kts index fd5a358cc2..287545ff8a 100644 --- a/plugins/hiddenore-paper/build.gradle.kts +++ b/plugins/hiddenore-paper/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("io.papermc.paperweight.userdev") + alias(libs.plugins.paper.userdev) } version = "2.0.0-SNAPSHOT" diff --git a/plugins/hiddenore-paper/src/main/java/com/github/devotedmc/hiddenore/HiddenOre.java b/plugins/hiddenore-paper/src/main/java/com/github/devotedmc/hiddenore/HiddenOre.java index 0851ccab36..45563e6798 100644 --- a/plugins/hiddenore-paper/src/main/java/com/github/devotedmc/hiddenore/HiddenOre.java +++ b/plugins/hiddenore-paper/src/main/java/com/github/devotedmc/hiddenore/HiddenOre.java @@ -56,9 +56,6 @@ public void run() { breakHandler = new BlockBreakListener(plugin); this.getServer().getPluginManager().registerEvents(breakHandler, this); - commandHandler = new CommandHandler(this); - this.getCommand("hiddenore").setExecutor(commandHandler); - worldGen = new ArrayList<>(); ConfigurationSection worldGenConfig = Config.instance.getWorldGenerations(); @@ -70,6 +67,10 @@ public void run() { worldGen.add(list); } } + + commandHandler = new CommandHandler(this, worldGen); + this.getCommand("hiddenore").setExecutor(commandHandler); + } @Override diff --git a/plugins/hiddenore-paper/src/main/java/com/github/devotedmc/hiddenore/commands/CommandHandler.java b/plugins/hiddenore-paper/src/main/java/com/github/devotedmc/hiddenore/commands/CommandHandler.java index 80c9460ba1..888bef9ddf 100644 --- a/plugins/hiddenore-paper/src/main/java/com/github/devotedmc/hiddenore/commands/CommandHandler.java +++ b/plugins/hiddenore-paper/src/main/java/com/github/devotedmc/hiddenore/commands/CommandHandler.java @@ -4,6 +4,7 @@ import java.util.Map; import java.util.UUID; +import com.github.devotedmc.hiddenore.listeners.WorldGenerationListener; import org.bukkit.Bukkit; import org.bukkit.NamespacedKey; import org.bukkit.command.Command; @@ -25,10 +26,12 @@ */ public class CommandHandler implements CommandExecutor { - final HiddenOre plugin; + private final HiddenOre plugin; + private final List worldGenerationListeners; - public CommandHandler(HiddenOre instance) { - plugin = instance; + public CommandHandler(HiddenOre instance, List worldGenerationListeners) { + this.plugin = instance; + this.worldGenerationListeners = worldGenerationListeners; } public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { @@ -63,35 +66,53 @@ public boolean onCommand(CommandSender sender, Command cmd, String label, String sender.sendMessage("Generating all drops, this could cause lag"); - Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() { - @Override - public void run() { - long delay = 0l; - Map> worldBlockConfigs = Config.instance.blockConfigs.getOrDefault(world, Config.instance.blockConfigs.get(null)); - if (worldBlockConfigs == null) { - sender.sendMessage("No drops configured for blocks in this world."); - return; - } - for (NamespacedKey blockConf : worldBlockConfigs.keySet()) { - for (BlockConfig block : worldBlockConfigs.get(blockConf)) { - for (String dropConf : block.getDrops()) { - DropConfig drop = block.getDropConfig(dropConf); - for (DropItemConfig item : drop.drops) { - Bukkit.getScheduler().runTaskLater(plugin, new Runnable() { - @Override - public void run() { - sender.sendMessage(String.format("Block: %s, drop: %s", blockConf.toString(), dropConf)); - Item dropped = player.getWorld().dropItem(player.getLocation().add(0, 1.0, 0), item.render(vmult)); - dropped.setPickupDelay(20); - } - }, delay++); - } + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + long delay = 0l; + Map> worldBlockConfigs = Config.instance.blockConfigs.getOrDefault(world, Config.instance.blockConfigs.get(null)); + if (worldBlockConfigs == null) { + sender.sendMessage("No drops configured for blocks in this world."); + return; + } + for (NamespacedKey blockConf : worldBlockConfigs.keySet()) { + for (BlockConfig block : worldBlockConfigs.get(blockConf)) { + for (String dropConf : block.getDrops()) { + DropConfig drop = block.getDropConfig(dropConf); + for (DropItemConfig item : drop.drops) { + Bukkit.getScheduler().runTaskLater(plugin, () -> { + sender.sendMessage(String.format("Block: %s, drop: %s", blockConf.toString(), dropConf)); + Item dropped = player.getWorld().dropItem(player.getLocation().add(0, 1.0, 0), item.render(vmult)); + dropped.setPickupDelay(20); + }, delay++); } } } } }); + return true; + } else if ("clearores".equals(args[0])) { + if (!(sender instanceof Player player)) { + return false; + } + for (WorldGenerationListener worldGenerationListener : worldGenerationListeners) { + if (worldGenerationListener.getWorldName().equals(player.getWorld().getName())) { + worldGenerationListener.clearManually(sender, Integer.parseInt(args[1])); + return true; + } + } + sender.sendMessage("No world generation listener found"); + return true; + } else if ("isprocessed".equals(args[0])) { + if (!(sender instanceof Player player)) { + return false; + } + for (WorldGenerationListener worldGenerationListener : worldGenerationListeners) { + if (worldGenerationListener.getWorldName().equals(player.getWorld().getName())) { + sender.sendMessage("Chunk processed: " + worldGenerationListener.isProcessed(player.getChunk())); + return true; + } + } + sender.sendMessage("No world generation listener found"); return true; } } else { diff --git a/plugins/hiddenore-paper/src/main/java/com/github/devotedmc/hiddenore/listeners/BlockBreakListener.java b/plugins/hiddenore-paper/src/main/java/com/github/devotedmc/hiddenore/listeners/BlockBreakListener.java index a8d37e86ad..a2ea1ecfa1 100644 --- a/plugins/hiddenore-paper/src/main/java/com/github/devotedmc/hiddenore/listeners/BlockBreakListener.java +++ b/plugins/hiddenore-paper/src/main/java/com/github/devotedmc/hiddenore/listeners/BlockBreakListener.java @@ -1,5 +1,6 @@ package com.github.devotedmc.hiddenore.listeners; +import com.github.devotedmc.hiddenore.util.FakePlayer; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; @@ -63,11 +64,9 @@ public void onBlockBreak(BlockBreakEvent event) { } public static void spoofBlockBreak(Location playerLoc, Block block, ItemStack inHand) { - throw new UnsupportedOperationException("FakePlayer has been deleted because it's too much of a hassle." + - "Re-add it and uncomment the code in this function to get ore dusting."); -// HiddenOre.getPlugin().getBreakListener().doBlockBreak( -// new BlockBreakEvent(block, new FakePlayer(playerLoc, inHand)) -// ); + HiddenOre.getPlugin().getBreakListener().doBlockBreak( + new BlockBreakEvent(block, new FakePlayer(playerLoc, inHand)) + ); } /** diff --git a/plugins/hiddenore-paper/src/main/java/com/github/devotedmc/hiddenore/listeners/WorldGenerationListener.java b/plugins/hiddenore-paper/src/main/java/com/github/devotedmc/hiddenore/listeners/WorldGenerationListener.java index a5c2719b68..1e4fe4c938 100644 --- a/plugins/hiddenore-paper/src/main/java/com/github/devotedmc/hiddenore/listeners/WorldGenerationListener.java +++ b/plugins/hiddenore-paper/src/main/java/com/github/devotedmc/hiddenore/listeners/WorldGenerationListener.java @@ -1,23 +1,41 @@ package com.github.devotedmc.hiddenore.listeners; +import ca.spottedleaf.moonrise.common.util.TickThread; import com.github.devotedmc.hiddenore.BlockConfig; import com.github.devotedmc.hiddenore.Config; import com.github.devotedmc.hiddenore.HiddenOre; +import java.lang.ref.PhantomReference; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; import java.util.logging.Level; +import it.unimi.dsi.fastutil.Pair; +import it.unimi.dsi.fastutil.ints.IntIntImmutablePair; +import it.unimi.dsi.fastutil.ints.IntIntPair; +import org.bukkit.Bukkit; import org.bukkit.Chunk; import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; +import org.bukkit.command.CommandSender; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.world.ChunkPopulateEvent; import org.bukkit.inventory.ItemStack; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.scheduler.BukkitTask; +import org.checkerframework.checker.units.qual.C; +import org.spigotmc.AsyncCatcher; /** * Populator to strip out blocks selectively from a world during generation. @@ -30,6 +48,8 @@ public class WorldGenerationListener implements Listener { String worldName = null; UUID worldUUID = null; + private final NamespacedKey processedKey; + /** * When creating, pass in a config with three sub-elements. Now supports UUID reference of world. *
@@ -70,6 +90,82 @@ public WorldGenerationListener(ConfigurationSection config) { } } } + + this.processedKey = new NamespacedKey(HiddenOre.getPlugin(), "ore_processed"); + } + + public String getWorldName() { + return worldName; + } + + public void clearManually(CommandSender sender, int radius) { + Deque chunks = new ArrayDeque<>(); + World world = HiddenOre.getPlugin().getServer().getWorld(worldName); + for (int cx = -radius; cx <= radius; cx += 16) { + for (int cz = -radius; cz <= radius; cz += 16) { + chunks.add(new IntIntImmutablePair(cx >> 4, cz >> 4)); + } + } + sender.sendMessage("Clearing " + chunks.size() + " chunks..."); + Bukkit.getScheduler().runTaskAsynchronously(HiddenOre.getPlugin(), () -> { + int total = chunks.size(); + int processed = 0; + int nextProgress = Math.ceilDiv(total, 100); + while (!chunks.isEmpty()) { + List> futures = new ArrayList<>(); + for (int i = 0; i < 32; i++) { + IntIntPair poll = chunks.poll(); + if (poll == null) { + break; + } + futures.add(world.getChunkAtAsync(poll.firstInt(), poll.secondInt()).thenAccept(chunk -> { + Runnable r = () -> { + if (chunk.getPersistentDataContainer().getOrDefault(processedKey, PersistentDataType.BOOLEAN, false)) { + return; + } + clear(chunk); + if (Config.caveOres) { + generateCaveOres(chunk); + } + chunk.getPersistentDataContainer().set(processedKey, PersistentDataType.BOOLEAN, true); + }; + if (!Bukkit.isPrimaryThread()) { + HiddenOre.getPlugin().getLogger().log(Level.WARNING, "Chunk not loaded on primary thread"); + CountDownLatch latch = new CountDownLatch(1); + Bukkit.getScheduler().runTask(HiddenOre.getPlugin(), () -> { + r.run(); + latch.countDown(); + }); + try { + latch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } else { + r.run(); + } + })); + } + for (CompletableFuture c : futures) { + try { + c.join(); + } catch (RuntimeException ex) { + HiddenOre.getPlugin().getLogger().log(Level.WARNING, "Processing chunk", ex); + } + processed++; + nextProgress--; + if (nextProgress == 0) { + String message = "Processed " + processed + "/" + total + " chunks"; + sender.sendMessage(message); + Bukkit.getConsoleSender().sendMessage(message); + nextProgress = Math.ceilDiv(total, 100); + } + } + } + String message = "Processed " + total + "/" + total + " chunks"; + sender.sendMessage(message); + Bukkit.getConsoleSender().sendMessage(message); + }); } /** @@ -83,11 +179,18 @@ public WorldGenerationListener(ConfigurationSection config) { */ @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) public void postGenerationOreClear(ChunkPopulateEvent event) { + if (true) { + return; + } if (replacements == null || (worldName == null && worldUUID == null)) { return; } Chunk chunk = event.getChunk(); + if (chunk.getPersistentDataContainer().getOrDefault(processedKey, PersistentDataType.BOOLEAN, false)) { + return; + } + World world = chunk.getWorld(); @@ -97,33 +200,10 @@ public void postGenerationOreClear(ChunkPopulateEvent event) { clear(chunk); - int x = chunk.getX(); - int z = chunk.getZ(); - - // check adjacent chunks, which by contract - // might have been updated. - if (world.isChunkLoaded(x - 1, z)) { - chunk = world.getChunkAt(x - 1, z); - clear(chunk); - } - - if (world.isChunkLoaded(x + 1, z)) { - chunk = world.getChunkAt(x + 1, z); - clear(chunk); - } - - if (world.isChunkLoaded(x, z - 1)) { - chunk = world.getChunkAt(x, z - 1); - clear(chunk); - } - - if (world.isChunkLoaded(x, z + 1)) { - chunk = world.getChunkAt(x, z + 1); - clear(chunk); - } if (Config.caveOres) { generateCaveOres(chunk); } + chunk.getPersistentDataContainer().set(processedKey, PersistentDataType.BOOLEAN, true); } private void clear(Chunk chunk) { @@ -180,4 +260,8 @@ private void generateCaveOres(Chunk chunk) { } } } + + public boolean isProcessed(Chunk chunk) { + return chunk.getPersistentDataContainer().getOrDefault(processedKey, PersistentDataType.BOOLEAN, false); + } } diff --git a/plugins/hiddenore-paper/src/main/java/com/github/devotedmc/hiddenore/util/FakePlayer.java b/plugins/hiddenore-paper/src/main/java/com/github/devotedmc/hiddenore/util/FakePlayer.java new file mode 100644 index 0000000000..0417b4a2ad --- /dev/null +++ b/plugins/hiddenore-paper/src/main/java/com/github/devotedmc/hiddenore/util/FakePlayer.java @@ -0,0 +1,4074 @@ +package com.github.devotedmc.hiddenore.util; + +import com.destroystokyo.paper.ClientOption; +import com.destroystokyo.paper.Title; +import com.destroystokyo.paper.block.TargetBlockInfo; +import com.destroystokyo.paper.block.TargetBlockInfo.FluidMode; +import com.destroystokyo.paper.entity.TargetEntityInfo; +import com.destroystokyo.paper.profile.PlayerProfile; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.ListIterator; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +import io.papermc.paper.connection.PlayerGameConnection; +import io.papermc.paper.datacomponent.DataComponentType; +import io.papermc.paper.entity.LookAnchor; +import io.papermc.paper.entity.PlayerGiveResult; +import io.papermc.paper.entity.TeleportFlag; +import io.papermc.paper.math.Position; +import io.papermc.paper.threadedregions.scheduler.EntityScheduler; +import io.papermc.paper.world.damagesource.CombatTracker; +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.identity.Identity; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.util.TriState; +import net.md_5.bungee.api.chat.BaseComponent; +import org.bukkit.BanEntry; +import org.bukkit.Chunk; +import org.bukkit.DyeColor; +import org.bukkit.Effect; +import org.bukkit.EntityEffect; +import org.bukkit.FluidCollisionMode; +import org.bukkit.GameMode; +import org.bukkit.Input; +import org.bukkit.Instrument; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Note; +import org.bukkit.Particle; +import org.bukkit.Server; +import org.bukkit.ServerLinks; +import org.bukkit.Sound; +import org.bukkit.SoundCategory; +import org.bukkit.Statistic; +import org.bukkit.WeatherType; +import org.bukkit.World; +import org.bukkit.WorldBorder; +import org.bukkit.advancement.Advancement; +import org.bukkit.advancement.AdvancementProgress; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.block.PistonMoveReaction; +import org.bukkit.block.Sign; +import org.bukkit.block.TileState; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.sign.Side; +import org.bukkit.conversations.Conversation; +import org.bukkit.conversations.ConversationAbandonedEvent; +import org.bukkit.damage.DamageSource; +import org.bukkit.entity.*; +import org.bukkit.entity.memory.MemoryKey; +import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityRegainHealthEvent; +import org.bukkit.event.inventory.InventoryCloseEvent.Reason; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.player.PlayerKickEvent; +import org.bukkit.event.player.PlayerResourcePackStatusEvent.Status; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.InventoryView; +import org.bukkit.inventory.InventoryView.Property; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MainHand; +import org.bukkit.inventory.Merchant; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.map.MapView; +import org.bukkit.metadata.MetadataValue; +import org.bukkit.permissions.Permission; +import org.bukkit.permissions.PermissionAttachment; +import org.bukkit.permissions.PermissionAttachmentInfo; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.plugin.Plugin; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.scoreboard.Scoreboard; +import org.bukkit.util.BoundingBox; +import org.bukkit.util.RayTraceResult; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Range; +import org.jetbrains.annotations.Unmodifiable; +import org.jetbrains.annotations.UnmodifiableView; + +@SuppressWarnings("removal") +public class FakePlayer implements Player { + + private final ItemStack inHand; + private final Location location; + + public FakePlayer(final Location location, final ItemStack inHand) { + this.inHand = inHand; + this.location = location; + } + + @Override + public String getName() { + return "Spoof"; + } + + @Override + public PlayerInventory getInventory() { + return new PlayerInventory() { + + @Override + public int getSize() { + return 0; + } + + @Override + public int getMaxStackSize() { + return 0; + } + + @Override + public void setMaxStackSize(int size) { + } + + @Override + public ItemStack getItem(int index) { + return null; + } + + @Override + public HashMap addItem(ItemStack... items) throws IllegalArgumentException { + return new HashMap<>(); + } + + @Override + public HashMap removeItem(ItemStack... items) throws IllegalArgumentException { + return new HashMap<>(); + } + + @Override + public ItemStack[] getContents() { + return null; + } + + @Override + public void setContents(ItemStack[] items) throws IllegalArgumentException { + } + + @Override + public ItemStack[] getStorageContents() { + return null; + } + + @Override + public void setStorageContents(ItemStack[] items) throws IllegalArgumentException { + } + + @Override + public boolean contains(Material material) throws IllegalArgumentException { + return false; + } + + @Override + public boolean contains(ItemStack item) { + return false; + } + + @Override + public boolean contains(Material material, int amount) throws IllegalArgumentException { + return false; + } + + @Override + public boolean contains(ItemStack item, int amount) { + return false; + } + + @Override + public boolean containsAtLeast(ItemStack item, int amount) { + return false; + } + + @Override + public HashMap all(Material material) throws IllegalArgumentException { + return new HashMap<>(); + } + + @Override + public HashMap all(ItemStack item) { + return new HashMap<>(); + } + + @Override + public int first(Material material) throws IllegalArgumentException { + return 0; + } + + @Override + public int first(ItemStack item) { + return 0; + } + + @Override + public int firstEmpty() { + return 0; + } + + @Override + public void remove(Material material) throws IllegalArgumentException { + } + + @Override + public void remove(ItemStack item) { + } + + @Override + public void clear(int index) { + } + + @Override + public void clear() { + } + + @Override + public int close() { + return 0; + } + + @Override + public List getViewers() { + return new ArrayList<>(); + } + + @Override + public InventoryType getType() { + return InventoryType.PLAYER; + } + + @Override + public ListIterator iterator() { + return null; + } + + @Override + public ListIterator iterator(int index) { + return null; + } + + @Override + public Location getLocation() { + return location; + } + + @Override + public ItemStack[] getArmorContents() { + return null; + } + + @Override + public ItemStack[] getExtraContents() { + return null; + } + + @Override + public ItemStack getHelmet() { + return null; + } + + @Override + public ItemStack getChestplate() { + return null; + } + + @Override + public ItemStack getLeggings() { + return null; + } + + @Override + public ItemStack getBoots() { + return null; + } + + @Override + public void setItem(int index, ItemStack item) { + } + + @Override + public void setArmorContents(ItemStack[] items) { + } + + @Override + public void setExtraContents(ItemStack[] items) { + } + + @Override + public void setHelmet(ItemStack helmet) { + } + + @Override + public void setChestplate(ItemStack chestplate) { + } + + @Override + public void setLeggings(ItemStack leggings) { + } + + @Override + public void setBoots(ItemStack boots) { + } + + @Override + public ItemStack getItemInMainHand() { + return inHand; + } + + @Override + public void setItemInMainHand(ItemStack item) { + } + + @Override + public ItemStack getItemInOffHand() { + return null; + } + + @Override + public void setItemInOffHand(ItemStack item) { + } + + @Override + public ItemStack getItemInHand() { + return inHand; + } + + @Override + public void setItemInHand(ItemStack stack) { + } + + @Override + public int getHeldItemSlot() { + return 0; + } + + @Override + public void setHeldItemSlot(int slot) { + } + + @Override + public HumanEntity getHolder() { + return null; + } + + @Override + public void setItem(EquipmentSlot slot, ItemStack item) { + } + + @Override + public ItemStack getItem(EquipmentSlot slot) { + return null; + } + + @Override + public InventoryHolder getHolder(boolean arg0) { + return null; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public HashMap removeItemAnySlot(ItemStack... arg0) throws IllegalArgumentException { + return null; + } + }; + } + + @Override + public Inventory getEnderChest() { + return null; + } + + @Override + public MainHand getMainHand() { + return null; + } + + @Override + public boolean setWindowProperty(Property prop, int value) { + return false; + } + + @Override + public int getEnchantmentSeed() { + return 0; + } + + @Override + public void setEnchantmentSeed(int i) { + + } + + @Override + public InventoryView getOpenInventory() { + return null; + } + + @Override + public InventoryView openInventory(Inventory inventory) { + return null; + } + + @Override + public InventoryView openWorkbench(Location location, boolean force) { + return null; + } + + @Override + public InventoryView openEnchanting(Location location, boolean force) { + return null; + } + + @Override + public void openInventory(InventoryView inventory) { + } + + @Override + public InventoryView openMerchant(Villager trader, boolean force) { + return null; + } + + @Override + public void closeInventory() { + } + + @Override + public ItemStack getItemInHand() { + return inHand; + } + + @Override + public void setItemInHand(ItemStack item) { + } + + @Override + public ItemStack getItemOnCursor() { + return null; + } + + @Override + public void setItemOnCursor(ItemStack item) { + } + + @Override + public boolean isSleeping() { + return false; + } + + @Override + public boolean isClimbing() { + return false; + } + + @Override + public int getSleepTicks() { + return 0; + } + + @Override + public GameMode getGameMode() { + return null; + } + + @Override + public void setGameMode(GameMode mode) { + } + + @Override + public boolean isBlocking() { + return false; + } + + @Override + public int getExpToLevel() { + return 0; + } + + @Override + public double getEyeHeight() { + return 0; + } + + @Override + public double getEyeHeight(boolean ignoreSneaking) { + return 0; + } + + @Override + public Location getEyeLocation() { + return location; + } + + @Override + public List getLineOfSight(Set transparent, int maxDistance) { + return null; + } + + public Block getTargetBlock(HashSet transparent, int maxDistance) { + return null; + } + + @Override + public Block getTargetBlock(Set transparent, int maxDistance) { + return null; + } + + public List getLastTwoTargetBlocks(HashSet transparent, int maxDistance) { + return null; + } + + @Override + public List getLastTwoTargetBlocks(Set transparent, int maxDistance) { + return null; + } + + @Override + public int getRemainingAir() { + return 0; + } + + @Override + public void setRemainingAir(int ticks) { + } + + @Override + public int getMaximumAir() { + return 0; + } + + @Override + public void setMaximumAir(int ticks) { + } + + @Override + public int getMaximumNoDamageTicks() { + return 0; + } + + @Override + public void setMaximumNoDamageTicks(int ticks) { + } + + @Override + public double getLastDamage() { + return 0; + } + + @Override + public void setLastDamage(double damage) { + + } + + @Override + public int getNoDamageTicks() { + + return 0; + } + + @Override + public void setNoDamageTicks(int ticks) { + + } + + @Override + public int getNoActionTicks() { + return 0; + } + + @Override + public void setNoActionTicks(int i) { + + } + + @Override + public Player getKiller() { + + return null; + } + + @Override + public boolean addPotionEffect(PotionEffect effect) { + + return false; + } + + @Override + public boolean addPotionEffect(PotionEffect effect, boolean force) { + + return false; + } + + @Override + public boolean addPotionEffects(Collection effects) { + + return false; + } + + @Override + public boolean hasPotionEffect(PotionEffectType type) { + + return false; + } + + @Override + public void removePotionEffect(PotionEffectType type) { + + } + + @Override + public Collection getActivePotionEffects() { + + return null; + } + + @Override + public boolean clearActivePotionEffects() { + return false; + } + + @Override + public boolean hasLineOfSight(Entity other) { + + return false; + } + + @Override + public boolean hasLineOfSight(@NotNull Location location) { + return false; + } + + @Override + public boolean getRemoveWhenFarAway() { + + return false; + } + + @Override + public void setRemoveWhenFarAway(boolean remove) { + + } + + @Override + public EntityEquipment getEquipment() { + + return null; + } + + @Override + public void setCanPickupItems(boolean pickup) { + + } + + @Override + public boolean getCanPickupItems() { + + return false; + } + + @Override + public boolean isLeashed() { + + return false; + } + + @Override + public Entity getLeashHolder() throws IllegalStateException { + + return null; + } + + @Override + public boolean setLeashHolder(Entity holder) { + + return false; + } + + @Override + public boolean isGliding() { + + return false; + } + + @Override + public void setGliding(boolean gliding) { + + } + + @Override + public void setAI(boolean ai) { + + } + + @Override + public boolean hasAI() { + + return false; + } + + @Override + public void setCollidable(boolean collidable) { + + } + + @Override + public boolean isCollidable() { + + return false; + } + + @Override + public AttributeInstance getAttribute(Attribute attribute) { + + return null; + } + + @Override + public void registerAttribute(@NotNull Attribute attribute) { + + } + + @Override + public Location getLocation() { + return location; + } + + @Override + public Location getLocation(Location loc) { + return location; + } + + @Override + public void setVelocity(Vector velocity) { + + } + + @Override + public Vector getVelocity() { + + return null; + } + + @Override + public World getWorld() { + return location.getWorld(); + } + + @Override + public boolean teleport(Location location) { + + return false; + } + + @Override + public boolean teleport(Location location, TeleportCause cause) { + + return false; + } + + @Override + public boolean teleport(Entity destination) { + + return false; + } + + @Override + public boolean teleport(Entity destination, TeleportCause cause) { + + return false; + } + + @Override + public @NotNull CompletableFuture teleportAsync(@NotNull Location location, @NotNull PlayerTeleportEvent.TeleportCause teleportCause, @NotNull TeleportFlag @NotNull ... teleportFlags) { + return null; + } + + @Override + public List getNearbyEntities(double x, double y, double z) { + + return null; + } + + @Override + public int getEntityId() { + + return 0; + } + + @Override + public int getFireTicks() { + + return 0; + } + + @Override + public int getMaxFireTicks() { + + return 0; + } + + @Override + public void setFireTicks(int ticks) { + + } + + @Override + public void setVisualFire(boolean bl) { + + } + + @Override + public void setVisualFire(@NotNull TriState fire) { + + } + + @Override + public boolean isVisualFire() { + return false; + } + + @Override + public @NotNull TriState getVisualFire() { + return null; + } + + @Override + public int getFreezeTicks() { + return 0; + } + + @Override + public int getMaxFreezeTicks() { + return 0; + } + + @Override + public void setFreezeTicks(int i) { + + } + + @Override + public boolean isFrozen() { + return false; + } + + @Override + public boolean isFreezeTickingLocked() { + return false; + } + + @Override + public void lockFreezeTicks(boolean locked) { + + } + + @Override + public void remove() { + + } + + @Override + public boolean isDead() { + + return false; + } + + @Override + public boolean isValid() { + + return false; + } + + @Override + public Server getServer() { + + return null; + } + + @Override + public Entity getPassenger() { + + return null; + } + + @Override + public boolean setPassenger(Entity passenger) { + + return false; + } + + @Override + public boolean isEmpty() { + + return false; + } + + @Override + public boolean eject() { + + return false; + } + + @Override + public @NotNull ItemStack getPickItemStack() { + return null; + } + + @Override + public float getFallDistance() { + + return 0; + } + + @Override + public void setFallDistance(float distance) { + + } + + @Override + public void setLastDamageCause(EntityDamageEvent event) { + + } + + @Override + public EntityDamageEvent getLastDamageCause() { + + return null; + } + + @Override + public UUID getUniqueId() { + + return null; + } + + @Override + public int getTicksLived() { + + return 0; + } + + @Override + public void setTicksLived(int value) { + + } + + @Override + public void playEffect(EntityEffect type) { + + } + + @Override + public EntityType getType() { + + return null; + } + + @Override + public @NotNull Sound getSwimSound() { + return null; + } + + @Override + public @NotNull Sound getSwimSplashSound() { + return null; + } + + @Override + public @NotNull Sound getSwimHighSpeedSplashSound() { + return null; + } + + @Override + public boolean isInsideVehicle() { + + return false; + } + + @Override + public boolean leaveVehicle() { + + return false; + } + + @Override + public Entity getVehicle() { + + return null; + } + + @Override + public void setCustomName(String name) { + + } + + @Override + public @Nullable + Component customName() { + return null; + } + + @Override + public void customName(@Nullable Component component) { + + } + + @Override + public String getCustomName() { + + return "Spoof"; + } + + @Override + public void setCustomNameVisible(boolean flag) { + + } + + @Override + public boolean isCustomNameVisible() { + + return false; + } + + @Override + public void setVisibleByDefault(boolean b) { + + } + + @Override + public boolean isVisibleByDefault() { + return false; + } + + @Override + public @NotNull Set getTrackedBy() { + return null; + } + + @Override + public boolean isTrackedBy(@NotNull Player player) { + return false; + } + + @Override + public void setGlowing(boolean flag) { + + } + + @Override + public boolean isGlowing() { + + return false; + } + + @Override + public void setInvulnerable(boolean flag) { + + } + + @Override + public boolean isInvulnerable() { + + return false; + } + + @Override + public void setMetadata(String metadataKey, MetadataValue newMetadataValue) { + + } + + @Override + public List getMetadata(String metadataKey) { + + return null; + } + + @Override + public boolean hasMetadata(String metadataKey) { + + return false; + } + + @Override + public void removeMetadata(String metadataKey, Plugin owningPlugin) { + + } + + @Override + public void sendMessage(String message) { + + } + + @Override + public void sendMessage(String[] messages) { + + } + + @Override + public boolean isPermissionSet(String name) { + + return false; + } + + @Override + public boolean isPermissionSet(Permission perm) { + + return false; + } + + @Override + public boolean hasPermission(String name) { + + return false; + } + + @Override + public boolean hasPermission(Permission perm) { + + return false; + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value) { + + return null; + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin) { + + return null; + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, String name, boolean value, int ticks) { + + return null; + } + + @Override + public PermissionAttachment addAttachment(Plugin plugin, int ticks) { + + return null; + } + + @Override + public void removeAttachment(PermissionAttachment attachment) { + + } + + @Override + public void recalculatePermissions() { + + } + + @Override + public Set getEffectivePermissions() { + + return null; + } + + @Override + public boolean isOp() { + + return false; + } + + @Override + public void setOp(boolean value) { + + } + + @Override + public void damage(double amount) { + + } + + @Override + public void damage(double amount, Entity source) { + + } + + @Override + public void damage(double v, @NotNull DamageSource damageSource) { + + } + + @Override + public double getHealth() { + + return 0; + } + + @Override + public void setHealth(double health) { + + } + + @Override + public void heal(double amount, @NotNull EntityRegainHealthEvent.RegainReason reason) { + + } + + @Override + public double getMaxHealth() { + + return 0; + } + + @Override + public void setMaxHealth(double health) { + + } + + @Override + public void resetMaxHealth() { + + } + + @Override + public T launchProjectile(Class projectile) { + + return null; + } + + @Override + public T launchProjectile(Class projectile, Vector velocity) { + + return null; + } + + @Override + public @NotNull T launchProjectile(@NotNull Class aClass, @org.jetbrains.annotations.Nullable Vector vector, @org.jetbrains.annotations.Nullable Consumer consumer) { + return null; + } + + @Override + public boolean isConversing() { + + return false; + } + + @Override + public void acceptConversationInput(String input) { + + } + + @Override + public boolean beginConversation(Conversation conversation) { + + return false; + } + + @Override + public void abandonConversation(Conversation conversation) { + + } + + @Override + public void abandonConversation(Conversation conversation, ConversationAbandonedEvent details) { + + } + + @Override + public boolean isOnline() { + + return false; + } + + @Override + public boolean isConnected() { + return false; + } + + @Override + public boolean isBanned() { + + return false; + } + + @Override + public > @org.jetbrains.annotations.Nullable E ban(@org.jetbrains.annotations.Nullable String s, @org.jetbrains.annotations.Nullable Date date, @org.jetbrains.annotations.Nullable String s1) { + return null; + } + + @Override + public > @org.jetbrains.annotations.Nullable E ban(@org.jetbrains.annotations.Nullable String s, @org.jetbrains.annotations.Nullable Instant instant, @org.jetbrains.annotations.Nullable String s1) { + return null; + } + + @Override + public > @org.jetbrains.annotations.Nullable E ban(@org.jetbrains.annotations.Nullable String s, @org.jetbrains.annotations.Nullable Duration duration, @org.jetbrains.annotations.Nullable String s1) { + return null; + } + + @Override + public boolean isWhitelisted() { + + return false; + } + + @Override + public void setWhitelisted(boolean value) { + + } + + @Override + public Player getPlayer() { + + return null; + } + + @Override + public long getFirstPlayed() { + + return 0; + } + + @Override + public long getLastPlayed() { + + return 0; + } + + @Override + public boolean hasPlayedBefore() { + + return false; + } + + @Override + public Map serialize() { + + return null; + } + + @Override + public void sendPluginMessage(Plugin source, String channel, byte[] message) { + + } + + @Override + public Set getListeningPluginChannels() { + + return null; + } + + @Override + public @NotNull Identity identity() { + return Player.super.identity(); + } + + @Override + public @UnmodifiableView @NotNull Iterable activeBossBars() { + return null; + } + + @Override + public @NotNull Component displayName() { + return Component.text("Spoof"); + } + + @Override + public void displayName(@Nullable Component component) { + + } + + @Override + public String getDisplayName() { + return "Spoof"; + } + + @Override + public void setDisplayName(String name) { + + } + + @Override + public void playerListName(@Nullable Component component) { + + } + + @Override + public @Nullable Component playerListName() { + return null; + } + + @Override + public @Nullable Component playerListHeader() { + return null; + } + + @Override + public @Nullable Component playerListFooter() { + return null; + } + + @Override + public String getPlayerListName() { + return "Spoof"; + } + + @Override + public void setPlayerListName(String name) { + + } + + @Override + public int getPlayerListOrder() { + return 0; + } + + @Override + public void setPlayerListOrder(int order) { + + } + + @Override + public void setCompassTarget(Location loc) { + + } + + @Override + public Location getCompassTarget() { + + return null; + } + + @Override + public InetSocketAddress getAddress() { + + return null; + } + + @Override + public @org.jetbrains.annotations.Nullable InetSocketAddress getHAProxyAddress() { + return null; + } + + @Override + public boolean isTransferred() { + return false; + } + + @Override + public @NotNull CompletableFuture retrieveCookie(@NotNull NamespacedKey key) { + return null; + } + + @Override + public void storeCookie(@NotNull NamespacedKey key, @NotNull byte[] value) { + + } + + @Override + public void transfer(@NotNull String host, int port) { + + } + + @Override + public void sendRawMessage(String message) { + + } + + @Override + public void kickPlayer(String message) { + + } + + @Override + public void kick() { + + } + + @Override + public void kick(@Nullable Component component) { + + } + + @Override + public void kick(@Nullable Component component, + PlayerKickEvent.Cause cause) { + + } + + @Override + public > @org.jetbrains.annotations.Nullable E ban(@org.jetbrains.annotations.Nullable String s, @org.jetbrains.annotations.Nullable Date date, @org.jetbrains.annotations.Nullable String s1, boolean b) { + return null; + } + + @Override + public > @org.jetbrains.annotations.Nullable E ban(@org.jetbrains.annotations.Nullable String s, @org.jetbrains.annotations.Nullable Instant instant, @org.jetbrains.annotations.Nullable String s1, boolean b) { + return null; + } + + @Override + public > @org.jetbrains.annotations.Nullable E ban(@org.jetbrains.annotations.Nullable String s, @org.jetbrains.annotations.Nullable Duration duration, @org.jetbrains.annotations.Nullable String s1, boolean b) { + return null; + } + + @Override + public @org.jetbrains.annotations.Nullable BanEntry banIp(@org.jetbrains.annotations.Nullable String s, @org.jetbrains.annotations.Nullable Date date, @org.jetbrains.annotations.Nullable String s1, boolean b) { + return null; + } + + @Override + public @org.jetbrains.annotations.Nullable BanEntry banIp(@org.jetbrains.annotations.Nullable String s, @org.jetbrains.annotations.Nullable Instant instant, @org.jetbrains.annotations.Nullable String s1, boolean b) { + return null; + } + + @Override + public @org.jetbrains.annotations.Nullable BanEntry banIp(@org.jetbrains.annotations.Nullable String s, @org.jetbrains.annotations.Nullable Duration duration, @org.jetbrains.annotations.Nullable String s1, boolean b) { + return null; + } + + @Override + public void chat(String msg) { + + } + + @Override + public boolean performCommand(String command) { + + return false; + } + + @Override + public boolean isSneaking() { + + return false; + } + + @Override + public void setSneaking(boolean sneak) { + + } + + @Override + public void setPose(@NotNull Pose pose, boolean b) { + + } + + @Override + public boolean hasFixedPose() { + return false; + } + + @Override + public boolean isSprinting() { + + return false; + } + + @Override + public void setSprinting(boolean sprinting) { + + } + + @Override + public void saveData() { + + } + + @Override + public void loadData() { + + } + + @Override + public void setSleepingIgnored(boolean isSleeping) { + + } + + @Override + public boolean isSleepingIgnored() { + + return false; + } + + @Override + public void playNote(Location loc, byte instrument, byte note) { + + } + + @Override + public void playNote(Location loc, Instrument instrument, Note note) { + + } + + @Override + public void playSound(Location location, Sound sound, float volume, float pitch) { + + } + + @Override + public void playSound(Location location, String sound, float volume, float pitch) { + + } + + @Override + public void playEffect(Location loc, Effect effect, int data) { + + } + + @Override + public void playEffect(Location loc, Effect effect, T data) { + + } + + @Override + public boolean breakBlock(@NotNull Block block) { + return false; + } + + @Override + public void sendBlockChange(Location loc, Material material, byte data) { + + } + + @Override + public void sendSignChange(Location loc, String[] lines) throws IllegalArgumentException { + + } + + @Override + public void sendMap(MapView map) { + + } + + @Override + public void showWinScreen() { + + } + + @Override + public boolean hasSeenWinScreen() { + return false; + } + + @Override + public void setHasSeenWinScreen(boolean b) { + + } + + @Override + public void updateInventory() { + + } + + @Override + public @Nullable GameMode getPreviousGameMode() { + return null; + } + + @Override + public void incrementStatistic(Statistic statistic) throws IllegalArgumentException { + + } + + @Override + public void decrementStatistic(Statistic statistic) throws IllegalArgumentException { + + } + + @Override + public void incrementStatistic(Statistic statistic, int amount) throws IllegalArgumentException { + + } + + @Override + public void decrementStatistic(Statistic statistic, int amount) throws IllegalArgumentException { + + } + + @Override + public void setStatistic(Statistic statistic, int newValue) throws IllegalArgumentException { + + } + + @Override + public int getStatistic(Statistic statistic) throws IllegalArgumentException { + + return 0; + } + + @Override + public void incrementStatistic(Statistic statistic, Material material) throws IllegalArgumentException { + + } + + @Override + public void decrementStatistic(Statistic statistic, Material material) throws IllegalArgumentException { + + } + + @Override + public int getStatistic(Statistic statistic, Material material) throws IllegalArgumentException { + + return 0; + } + + @Override + public void incrementStatistic(Statistic statistic, Material material, int amount) throws IllegalArgumentException { + + } + + @Override + public void decrementStatistic(Statistic statistic, Material material, int amount) throws IllegalArgumentException { + + } + + @Override + public void setStatistic(Statistic statistic, Material material, int newValue) throws IllegalArgumentException { + + } + + @Override + public void incrementStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException { + + } + + @Override + public void decrementStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException { + + } + + @Override + public int getStatistic(Statistic statistic, EntityType entityType) throws IllegalArgumentException { + + return 0; + } + + @Override + public void incrementStatistic(Statistic statistic, EntityType entityType, int amount) + throws IllegalArgumentException { + + } + + @Override + public void decrementStatistic(Statistic statistic, EntityType entityType, int amount) { + + } + + @Override + public void setStatistic(Statistic statistic, EntityType entityType, int newValue) { + + } + + @Override + public void setPlayerTime(long time, boolean relative) { + + } + + @Override + public long getPlayerTime() { + + return 0; + } + + @Override + public long getPlayerTimeOffset() { + + return 0; + } + + @Override + public boolean isPlayerTimeRelative() { + + return false; + } + + @Override + public void resetPlayerTime() { + + } + + @Override + public void setPlayerWeather(WeatherType type) { + + } + + @Override + public WeatherType getPlayerWeather() { + + return null; + } + + @Override + public void resetPlayerWeather() { + + } + + @Override + public void giveExp(int amount) { + + } + + @Override + public int getExpCooldown() { + return 0; + } + + @Override + public void setExpCooldown(int i) { + + } + + @Override + public void giveExpLevels(int amount) { + + } + + @Override + public float getExp() { + + return 0; + } + + @Override + public void setExp(float exp) { + + } + + @Override + public int getLevel() { + + return 0; + } + + @Override + public void setLevel(int level) { + + } + + @Override + public int getTotalExperience() { + + return 0; + } + + @Override + public void setTotalExperience(int exp) { + + } + + @Override + public @Range(from = 0L, to = 2147483647L) int calculateTotalExperiencePoints() { + return 0; + } + + @Override + public void setExperienceLevelAndProgress(@Range(from = 0L, to = 2147483647L) int i) { + + } + + @Override + public int getExperiencePointsNeededForNextLevel() { + return 0; + } + + @Override + public float getExhaustion() { + + return 0; + } + + @Override + public void setExhaustion(float value) { + + } + + @Override + public float getSaturation() { + + return 0; + } + + @Override + public void setSaturation(float value) { + + } + + @Override + public int getFoodLevel() { + + return 0; + } + + @Override + public void setFoodLevel(int value) { + + } + + @Override + public int getSaturatedRegenRate() { + return 0; + } + + @Override + public void setSaturatedRegenRate(int i) { + + } + + @Override + public int getUnsaturatedRegenRate() { + return 0; + } + + @Override + public void setUnsaturatedRegenRate(int i) { + + } + + @Override + public int getStarvationRate() { + return 0; + } + + @Override + public void setStarvationRate(int i) { + + } + + @Override + public @org.jetbrains.annotations.Nullable Location getLastDeathLocation() { + return null; + } + + @Override + public void setLastDeathLocation(@org.jetbrains.annotations.Nullable Location location) { + + } + + @Override + public @org.jetbrains.annotations.Nullable Firework fireworkBoost(@NotNull ItemStack itemStack) { + return null; + } + + @Override + public Location getBedSpawnLocation() { + + return null; + } + + @Override + public @org.jetbrains.annotations.Nullable Location getRespawnLocation() { + return null; + } + + @Override + public @org.jspecify.annotations.Nullable Location getRespawnLocation(boolean loadLocationAndValidate) { + return null; + } + + @Override + public void setBedSpawnLocation(Location location) { + + } + + @Override + public void setRespawnLocation(@org.jetbrains.annotations.Nullable Location location) { + + } + + @Override + public void setBedSpawnLocation(Location location, boolean force) { + + } + + @Override + public void setRespawnLocation(@org.jetbrains.annotations.Nullable Location location, boolean b) { + + } + + @Override + public Collection getEnderPearls() { + return null; + } + + @Override + public Input getCurrentInput() { + return null; + } + + @Override + public boolean getAllowFlight() { + + return false; + } + + @Override + public void setAllowFlight(boolean flight) { + + } + + @Override + public void setFlyingFallDamage(@NotNull TriState triState) { + + } + + @Override + public @NotNull TriState hasFlyingFallDamage() { + return null; + } + + @Override + public void hidePlayer(Plugin plugin, Player player) { + + } + + @Override + public void hidePlayer(Player player) { + + } + + @Override + public void showPlayer(Plugin plugin, Player player) { + + } + + @Override + public void showPlayer(Player player) { + + } + + @Override + public boolean canSee(Player player) { + + return false; + } + + @Override + public void hideEntity(Plugin plugin, Entity entity) { + + } + + @Override + public void showEntity(Plugin plugin, Entity entity) { + + } + + @Override + public boolean canSee(Entity entity) { + return false; + } + + @Override + public boolean isListed(@NotNull Player player) { + return false; + } + + @Override + public boolean unlistPlayer(@NotNull Player player) { + return false; + } + + @Override + public boolean listPlayer(@NotNull Player player) { + return false; + } + + @Override + public boolean isOnGround() { + + return false; + } + + @Override + public boolean isFlying() { + + return false; + } + + @Override + public void setFlying(boolean value) { + + } + + @Override + public void setFlySpeed(float value) throws IllegalArgumentException { + + } + + @Override + public void setWalkSpeed(float value) throws IllegalArgumentException { + + } + + @Override + public float getFlySpeed() { + + return 0; + } + + @Override + public float getWalkSpeed() { + + return 0; + } + + @Override + public void setTexturePack(String url) { + + } + + @Override + public void setResourcePack(String url) { + + } + + @Override + public Scoreboard getScoreboard() { + + return null; + } + + @Override + public void setScoreboard(Scoreboard scoreboard) throws IllegalArgumentException, IllegalStateException { + + } + + @Override + public @Nullable WorldBorder getWorldBorder() { + return null; + } + + @Override + public void setWorldBorder(@Nullable WorldBorder border) { + + } + + @Override + public boolean isHealthScaled() { + + return false; + } + + @Override + public void setHealthScaled(boolean scale) { + + } + + @Override + public void setHealthScale(double scale) throws IllegalArgumentException { + + } + + @Override + public double getHealthScale() { + + return 0; + } + + @Override + public void sendHealthUpdate(double v, int i, float v1) { + + } + + @Override + public void sendHealthUpdate() { + + } + + @Override + public Entity getSpectatorTarget() { + + return null; + } + + @Override + public void setSpectatorTarget(Entity entity) { + + } + + @Override + public void sendTitle(String title, String subtitle) { + + } + + @Override + public void resetTitle() { + + } + + @Override + public void spawnParticle(Particle particle, Location location, int count) { + + } + + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count) { + + } + + @Override + public void spawnParticle(Particle particle, Location location, int count, T data) { + + } + + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count, T data) { + + } + + @Override + public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, + double offsetZ) { + + } + + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, + double offsetY, double offsetZ) { + + } + + @Override + public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, + double offsetZ, T data) { + + } + + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, + double offsetY, double offsetZ, T data) { + + } + + @Override + public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, + double offsetZ, double extra) { + + } + + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, + double offsetY, double offsetZ, double extra) { + + } + + @Override + public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, + double offsetZ, double extra, T data) { + + } + + @Override + public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, + double offsetY, double offsetZ, double extra, T data) { + + } + + @Override + public void spawnParticle(@NotNull Particle particle, @NotNull Location location, int i, double v, double v1, double v2, double v3, @org.jetbrains.annotations.Nullable T t, boolean b) { + + } + + @Override + public void spawnParticle(@NotNull Particle particle, double v, double v1, double v2, int i, double v3, double v4, double v5, double v6, @org.jetbrains.annotations.Nullable T t, boolean b) { + + } + + @Override + public Spigot spigot() { + return null; + } + + @Override + public void sendEntityEffect(@NotNull EntityEffect effect, @NotNull Entity target) { + + } + + @Override + public PlayerGiveResult give(Collection items, boolean dropIfFull) { + return null; + } + + @Override + public int getDeathScreenScore() { + return 0; + } + + @Override + public void setDeathScreenScore(int score) { + + } + + @Override + public PlayerGameConnection getConnection() { + return null; + } + + @Override + public Component name() { + return null; + } + + @Override + public Component teamDisplayName() { + return null; + } + + @Override + public boolean isSilent() { + return false; + } + + @Override + public void setSilent(boolean flag) { + } + + @Override + public boolean hasGravity() { + return false; + } + + @Override + public void setGravity(boolean gravity) { + } + + @Override + public void stopSound(Sound sound) { + } + + @Override + public void stopSound(String sound) { + } + + @Override + public int getCooldown(Material arg0) { + return 0; + } + + @Override + public Entity getShoulderEntityLeft() { + return null; + } + + @Override + public Entity getShoulderEntityRight() { + return null; + } + + @Override + public boolean hasCooldown(Material arg0) { + return false; + } + + @Override + public boolean isHandRaised() { + return false; + } + + @Override + public @NotNull EquipmentSlot getHandRaised() { + return null; + } + + @Override + public @Nullable ItemStack getItemInUse() { + return null; + } + + @Override + public int getItemInUseTicks() { + return 0; + } + + @Override + public void setItemInUseTicks(int i) { + + } + + @Override + public InventoryView openMerchant(Merchant arg0, boolean arg1) { + return null; + } + + @Override + public void setCooldown(Material arg0, int arg1) { + + } + + @Override + public boolean isDeeplySleeping() { + return false; + } + + @Override + public boolean hasCooldown(ItemStack item) { + return false; + } + + @Override + public int getCooldown(ItemStack item) { + return 0; + } + + @Override + public void setCooldown(ItemStack item, int ticks) { + + } + + @Override + public int getCooldown(Key key) { + return 0; + } + + @Override + public void setCooldown(Key key, int ticks) { + + } + + @Override + public void setShoulderEntityLeft(Entity arg0) { + + } + + @Override + public void setShoulderEntityRight(Entity arg0) { + + } + + @Override + public PotionEffect getPotionEffect(PotionEffectType arg0) { + return null; + } + + @Override + public boolean addPassenger(Entity arg0) { + return false; + } + + @Override + public boolean addScoreboardTag(String arg0) { + return false; + } + + @Override + public double getHeight() { + return 1.0; + } + + @Override + public List getPassengers() { + return null; + } + + @Override + public int getPortalCooldown() { + return 0; + } + + @Override + public Set getScoreboardTags() { + return null; + } + + @Override + public double getWidth() { + return 0.33; + } + + @Override + public boolean removePassenger(Entity arg0) { + return false; + } + + @Override + public boolean removeScoreboardTag(String arg0) { + return false; + } + + @Override + public void setPortalCooldown(int arg0) { + + } + + @Override + public AdvancementProgress getAdvancementProgress(Advancement arg0) { + return null; + } + + @Override + public String getLocale() { + return null; + } + + @Override + public void playSound(Location arg0, Sound arg1, SoundCategory arg2, float arg3, float arg4) { + + } + + @Override + public void playSound(Location arg0, String arg1, SoundCategory arg2, float arg3, float arg4) { + + } + + @Override + public void playSound(@NotNull Location location, @NotNull Sound sound, @NotNull SoundCategory soundCategory, float v, float v1, long l) { + + } + + @Override + public void playSound(@NotNull Location location, @NotNull String s, @NotNull SoundCategory soundCategory, float v, float v1, long l) { + + } + + @Override + public void playSound(@NotNull Entity entity, @NotNull Sound sound, float volume, float pitch) { + + } + + @Override + public void playSound(@NotNull Entity entity, @NotNull String s, float v, float v1) { + + } + + @Override + public void playSound(@NotNull Entity entity, @NotNull Sound sound, + @NotNull SoundCategory category, float volume, float pitch) { + + } + + @Override + public void playSound(@NotNull Entity entity, @NotNull String s, @NotNull SoundCategory soundCategory, float v, float v1) { + + } + + @Override + public void playSound(@NotNull Entity entity, @NotNull Sound sound, @NotNull SoundCategory soundCategory, float v, float v1, long l) { + + } + + @Override + public void playSound(@NotNull Entity entity, @NotNull String s, @NotNull SoundCategory soundCategory, float v, float v1, long l) { + + } + + @Override + public void sendTitle(String arg0, String arg1, int arg2, int arg3, int arg4) { + + } + + @Override + public void setResourcePack(String arg0, byte[] arg1) { + + } + + @Override + public void setResourcePack(@NotNull String url, + @Nullable byte[] hash, + @Nullable String prompt) { + } + + @Override + public void setResourcePack(@NotNull String url, @Nullable byte[] hash, boolean force) { + + } + + @Override + public void setResourcePack(@NotNull String url, @Nullable byte[] hash, + @Nullable String prompt, boolean force) { + + } + + @Override + public void setResourcePack(@NotNull String url, @Nullable byte[] hash, + @Nullable Component prompt, boolean force) { + + } + + @Override + public void setResourcePack(@NotNull UUID uuid, @NotNull String s, @org.jetbrains.annotations.Nullable byte[] bytes, @org.jetbrains.annotations.Nullable String s1, boolean b) { + + } + + @Override + public void setResourcePack(@NotNull UUID uuid, @NotNull String s, byte @org.jetbrains.annotations.Nullable [] bytes, @org.jetbrains.annotations.Nullable Component component, boolean b) { + + } + + @Override + public void stopSound(Sound arg0, SoundCategory arg1) { + + } + + @Override + public void stopSound(String arg0, SoundCategory arg1) { + + } + + @Override + public void stopSound(@NotNull SoundCategory soundCategory) { + + } + + @Override + public void stopAllSounds() { + + } + + @Override + public PistonMoveReaction getPistonMoveReaction() { + return null; + } + + @Override + public boolean isSwimming() { + return false; + } + + @Override + public void setSwimming(boolean arg0) { + + } + + @Override + public void sendBlockChange(Location arg0, BlockData arg1) { + + } + + @Override + public void sendBlockChanges(@NotNull Collection collection) { + + } + + @Override + public void sendBlockChanges(@NotNull Collection collection, boolean b) { + + } + + @Override + public void sendBlockDamage(@NotNull Location location, float f) { + + } + + @Override + public void sendMultiBlockChange(@NotNull Map map) { + + } + + @Override + public void sendBlockDamage(@NotNull Location location, float v, @NotNull Entity entity) { + + } + + @Override + public void sendBlockDamage(@NotNull Location location, float v, int i) { + + } + + @Override + public void sendEquipmentChange(LivingEntity livingEntity, EquipmentSlot equipmentSlot, ItemStack itemStack) { + + } + + @Override + public void sendEquipmentChange(@NotNull LivingEntity livingEntity, @NotNull Map map) { + + } + + @Override + public void sendSignChange(@NotNull Location location, @org.jetbrains.annotations.Nullable List list, @NotNull DyeColor dyeColor, boolean b) throws IllegalArgumentException { + + } + + @Override + public boolean isRiptiding() { + return false; + } + + @Override + public void setRiptiding(boolean riptiding) { + + } + + @Override + public boolean isPersistent() { + return false; + } + + @Override + public void setPersistent(boolean arg0) { + } + + @Override + public String getPlayerListFooter() { + return null; + } + + @Override + public String getPlayerListHeader() { + return null; + } + + @Override + public void setPlayerListFooter(String arg0) { + } + + @Override + public void setPlayerListHeader(String arg0) { + } + + @Override + public void setPlayerListHeaderFooter(String arg0, String arg1) { + } + + @Override + public void updateCommands() { + } + + @Override + public boolean sleep(Location location, boolean force) { + // TODO Auto-generated method stub + return false; + } + + @Override + public void wakeup(boolean setSpawnLocation) { + // TODO Auto-generated method stub + + } + + @Override + public void startRiptideAttack(int duration, float attackStrength, @Nullable ItemStack attackItem) { + + } + + @Override + public Location getBedLocation() { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean discoverRecipe(NamespacedKey recipe) { + // TODO Auto-generated method stub + return false; + } + + @Override + public int discoverRecipes(Collection recipes) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public boolean undiscoverRecipe(NamespacedKey recipe) { + // TODO Auto-generated method stub + return false; + } + + @Override + public int undiscoverRecipes(Collection recipes) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public Block getTargetBlockExact(int maxDistance) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Block getTargetBlockExact(int maxDistance, FluidCollisionMode fluidCollisionMode) { + // TODO Auto-generated method stub + return null; + } + + @Override + public RayTraceResult rayTraceBlocks(double maxDistance) { + // TODO Auto-generated method stub + return null; + } + + @Override + public RayTraceResult rayTraceBlocks(double maxDistance, FluidCollisionMode fluidCollisionMode) { + // TODO Auto-generated method stub + return null; + } + + @Override + public T getMemory(MemoryKey memoryKey) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setMemory(MemoryKey memoryKey, T memoryValue) { + // TODO Auto-generated method stub + + } + + @Override + public @org.jetbrains.annotations.Nullable Sound getHurtSound() { + return null; + } + + @Override + public @org.jetbrains.annotations.Nullable Sound getDeathSound() { + return null; + } + + @Override + public @NotNull Sound getFallDamageSound(int i) { + return null; + } + + @Override + public @NotNull Sound getFallDamageSoundSmall() { + return null; + } + + @Override + public @NotNull Sound getFallDamageSoundBig() { + return null; + } + + @Override + public @NotNull Sound getDrinkingSound(@NotNull ItemStack itemStack) { + return null; + } + + @Override + public @NotNull Sound getEatingSound(@NotNull ItemStack itemStack) { + return null; + } + + @Override + public boolean canBreatheUnderwater() { + return false; + } + + @Override + public double getAbsorptionAmount() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void setAbsorptionAmount(double arg0) { + // TODO Auto-generated method stub + + } + + @Override + public BoundingBox getBoundingBox() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setRotation(float yaw, float pitch) { + // TODO Auto-generated method stub + + } + + @Override + public boolean teleport(@NotNull Location location, @NotNull PlayerTeleportEvent.TeleportCause teleportCause, @NotNull TeleportFlag @NotNull ... teleportFlags) { + return false; + } + + @Override + public void lookAt(double v, double v1, double v2, @NotNull LookAnchor lookAnchor) { + + } + + @Override + public void lookAt(@NotNull Entity entity, @NotNull LookAnchor lookAnchor, @NotNull LookAnchor lookAnchor1) { + + } + + @Override + public void showElderGuardian(boolean b) { + + } + + @Override + public int getWardenWarningCooldown() { + return 0; + } + + @Override + public void setWardenWarningCooldown(int i) { + + } + + @Override + public int getWardenTimeSinceLastWarning() { + return 0; + } + + @Override + public void setWardenTimeSinceLastWarning(int i) { + + } + + @Override + public int getWardenWarningLevel() { + return 0; + } + + @Override + public void setWardenWarningLevel(int i) { + + } + + @Override + public void increaseWardenWarningLevel() { + + } + + @Override + public @NotNull Duration getIdleDuration() { + return null; + } + + @Override + public void resetIdleDuration() { + + } + + @Override + public @NotNull @Unmodifiable Set getSentChunkKeys() { + return Collections.emptySet(); + } + + @Override + public @NotNull @Unmodifiable Set getSentChunks() { + return Collections.emptySet(); + } + + @Override + public boolean isChunkSent(long l) { + return false; + } + + @Override + public BlockFace getFacing() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Pose getPose() { + // TODO Auto-generated metod stub + return null; + } + + @Override + public @NotNull SpawnCategory getSpawnCategory() { + return null; + } + + @Override + public boolean isInWorld() { + return false; + } + + @Override + public @org.jetbrains.annotations.Nullable String getAsString() { + return ""; + } + + @Override + public @org.jetbrains.annotations.Nullable EntitySnapshot createSnapshot() { + return null; + } + + @Override + public @NotNull Entity copy() { + return null; + } + + @Override + public @NotNull Entity copy(@NotNull Location location) { + return null; + } + + @Override + public PersistentDataContainer getPersistentDataContainer() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void sendSignChange(Location loc, String[] lines, DyeColor dyeColor) throws IllegalArgumentException { + // TODO Auto-generated method stub + + } + + @Override + public void sendSignChange(@NotNull Location location, + @Nullable String[] strings, + @NotNull DyeColor dyeColor, boolean bl) + throws IllegalArgumentException { + + } + + @Override + public void sendBlockUpdate(@NotNull Location location, @NotNull TileState tileState) throws IllegalArgumentException { + + } + + @Override + public void sendPotionEffectChange(@NotNull LivingEntity livingEntity, @NotNull PotionEffect potionEffect) { + + } + + @Override + public void sendPotionEffectChangeRemove(@NotNull LivingEntity livingEntity, @NotNull PotionEffectType potionEffectType) { + + } + + @Override + public int getClientViewDistance() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public @NotNull Locale locale() { + return Locale.ENGLISH; + } + + @Override + public int getPing() { + return 0; + } + + @Override + public void openBook(ItemStack book) { + // TODO Auto-generated method stub + + } + + @Override + public float getAttackCooldown() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public boolean hasDiscoveredRecipe(NamespacedKey recipe) { + // TODO Auto-generated method stub + return false; + } + + @Override + public Set getDiscoveredRecipes() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void attack(Entity target) { + // TODO Auto-generated method stub + + } + + @Override + public void swingMainHand() { + // TODO Auto-generated method stub + + } + + @Override + public void swingOffHand() { + // TODO Auto-generated method stub + + } + + @Override + public void playHurtAnimation(float v) { + + } + + @Override + public Set getCollidableExemptions() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void sendExperienceChange(float progress) { + // TODO Auto-generated method stub + + } + + @Override + public void sendExperienceChange(float progress, int level) { + // TODO Auto-generated method stub + + } + + @Override + public void closeInventory(Reason arg0) { + // TODO Auto-generated method stub + + } + + @Override + public boolean dropItem(boolean arg0) { + // TODO Auto-generated method stub + return false; + } + + @Override + public @org.jspecify.annotations.Nullable Item dropItem(int slot, int amount, boolean throwRandomly, @org.jspecify.annotations.Nullable Consumer entityOperation) { + return null; + } + + @Override + public @org.jspecify.annotations.Nullable Item dropItem(EquipmentSlot slot, int amount, boolean throwRandomly, @org.jspecify.annotations.Nullable Consumer entityOperation) { + return null; + } + + @Override + public @org.jspecify.annotations.Nullable Item dropItem(ItemStack itemStack, boolean throwRandomly, @org.jspecify.annotations.Nullable Consumer entityOperation) { + return null; + } + + @Override + public Location getPotentialBedLocation() { + // TODO Auto-generated method stub + return null; + } + + @Override + public @org.jspecify.annotations.Nullable Location getPotentialRespawnLocation() { + return null; + } + + @Override + public @org.jetbrains.annotations.Nullable FishHook getFishHook() { + return null; + } + + @Override + public InventoryView openAnvil(Location arg0, boolean arg1) { + // TODO Auto-generated method stub + return null; + } + + @Override + public InventoryView openCartographyTable(Location arg0, boolean arg1) { + // TODO Auto-generated method stub + return null; + } + + @Override + public InventoryView openGrindstone(Location arg0, boolean arg1) { + // TODO Auto-generated method stub + return null; + } + + @Override + public InventoryView openLoom(Location arg0, boolean arg1) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void openSign(Sign arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void openSign(@NotNull Sign sign, @NotNull Side side) { + + } + + @Override + public void openVirtualSign(Position block, Side side) { + + } + + @Override + public void showDemoScreen() { + + } + + @Override + public boolean isAllowingServerListings() { + return false; + } + + @Override + public InventoryView openSmithingTable(Location arg0, boolean arg1) { + // TODO Auto-generated method stub + return null; + } + + @Override + public InventoryView openStonecutter(Location arg0, boolean arg1) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Entity releaseLeftShoulderEntity() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Entity releaseRightShoulderEntity() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void clearActiveItem() { + // TODO Auto-generated method stub + + } + + @Override + public int getActiveItemRemainingTime() { + return 0; + } + + @Override + public void setActiveItemRemainingTime(@Range(from = 0L, to = 2147483647L) int i) { + + } + + @Override + public boolean hasActiveItem() { + return false; + } + + @Override + public int getActiveItemUsedTime() { + return 0; + } + + @Override + public @NotNull EquipmentSlot getActiveItemHand() { + return null; + } + + @Override + public float getSidewaysMovement() { + return 0; + } + + @Override + public float getUpwardsMovement() { + return 0; + } + + @Override + public float getForwardsMovement() { + return 0; + } + + @Override + public void startUsingItem(@NotNull EquipmentSlot equipmentSlot) { + + } + + @Override + public void completeUsingActiveItem() { + + } + + @Override + public ItemStack getActiveItem() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getArrowCooldown() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getArrowsInBody() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getArrowsStuck() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public EntityCategory getCategory() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getHandRaisedTime() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public float getHurtDirection() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getItemUseRemainingTime() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getShieldBlockingDelay() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public Block getTargetBlock(int arg0, FluidMode arg1) { + // TODO Auto-generated method stub + return null; + } + + @Override + public BlockFace getTargetBlockFace(int arg0, FluidMode arg1) { + // TODO Auto-generated method stub + return null; + } + + @Override + public @org.jetbrains.annotations.Nullable BlockFace getTargetBlockFace(int i, @NotNull FluidCollisionMode fluidCollisionMode) { + return null; + } + + @Override + public TargetBlockInfo getTargetBlockInfo(int arg0, FluidMode arg1) { + // TODO Auto-generated method stub + return null; + } + + @Override + public Entity getTargetEntity(int arg0, boolean arg1) { + // TODO Auto-generated method stub + return null; + } + + @Override + public TargetEntityInfo getTargetEntityInfo(int arg0, boolean arg1) { + // TODO Auto-generated method stub + return null; + } + + @Override + public @org.jetbrains.annotations.Nullable RayTraceResult rayTraceEntities(int i, boolean b) { + return null; + } + + @Override + public boolean isInvisible() { + // TODO Auto-generated method stub + return false; + } + + @Override + public void setNoPhysics(boolean b) { + + } + + @Override + public boolean hasNoPhysics() { + return false; + } + + @Override + public boolean isJumping() { + // TODO Auto-generated method stub + return false; + } + + @Override + public void playPickupItemAnimation(Item arg0, int arg1) { + // TODO Auto-generated method stub + + } + + @Override + public void setArrowCooldown(int arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void setArrowsInBody(int arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void setArrowsInBody(int i, boolean b) { + + } + + @Override + public void setNextArrowRemoval(@Range(from = 0L, to = 2147483647L) int i) { + + } + + @Override + public int getNextArrowRemoval() { + return 0; + } + + @Override + public int getBeeStingerCooldown() { + return 0; + } + + @Override + public void setBeeStingerCooldown(int i) { + + } + + @Override + public int getBeeStingersInBody() { + return 0; + } + + @Override + public void setBeeStingersInBody(int i) { + + } + + @Override + public void setNextBeeStingerRemoval(@Range(from = 0L, to = 2147483647L) int i) { + + } + + @Override + public int getNextBeeStingerRemoval() { + return 0; + } + + @Override + public void setArrowsStuck(int arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void setHurtDirection(float arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void knockback(double v, double v1, double v2) { + + } + + @Override + public void broadcastSlotBreak(@NotNull EquipmentSlot equipmentSlot) { + + } + + @Override + public void broadcastSlotBreak(@NotNull EquipmentSlot equipmentSlot, @NotNull Collection collection) { + + } + + @Override + public @NotNull ItemStack damageItemStack(@NotNull ItemStack itemStack, int i) { + return null; + } + + @Override + public void damageItemStack(@NotNull EquipmentSlot equipmentSlot, int i) { + + } + + @Override + public float getBodyYaw() { + return 0; + } + + @Override + public void setBodyYaw(float v) { + + } + + @Override + public boolean canUseEquipmentSlot(@NotNull EquipmentSlot equipmentSlot) { + return false; + } + + @Override + public @NotNull CombatTracker getCombatTracker() { + return null; + } + + @Override + public void setInvisible(boolean arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void setJumping(boolean arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void setKiller(Player arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void setShieldBlockingDelay(int arg0) { + // TODO Auto-generated method stub + + } + + @Override + public boolean fromMobSpawner() { + // TODO Auto-generated method stub + return false; + } + + @Override + public Chunk getChunk() { + // TODO Auto-generated method stub + return null; + } + + @Override + public SpawnReason getEntitySpawnReason() { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isUnderWater() { + return false; + } + + @Override + public Location getOrigin() { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isInBubbleColumn() { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isInLava() { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isInRain() { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isInWater() { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isInWaterOrBubbleColumn() { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isInWaterOrRain() { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isInWaterOrRainOrBubbleColumn() { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isTicking() { + // TODO Auto-generated method stub + return false; + } + + @Override + public void sendMessage(UUID arg0, String arg1) { + // TODO Auto-generated method stub + + } + + @Override + public void sendMessage(UUID arg0, String[] arg1) { + // TODO Auto-generated method stub + + } + + @Override + public void sendRawMessage(UUID arg0, String arg1) { + // TODO Auto-generated method stub + + } + + @Override + public long getLastLogin() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public long getLastSeen() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public int getProtocolVersion() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public InetSocketAddress getVirtualHost() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int applyMending(int arg0) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public Firework boostElytra(ItemStack arg0) { + // TODO Auto-generated method stub + return null; + } + + @Override + public void sendOpLevel(byte b) { + + } + + @Override + public void addAdditionalChatCompletions(@NotNull Collection collection) { + + } + + @Override + public void removeAdditionalChatCompletions(@NotNull Collection collection) { + + } + + @Override + public @NotNull Set getTrackedPlayers() { + return null; + } + + @Override + public boolean spawnAt(Location location, SpawnReason spawnReason) { + return false; + } + + @Override + public boolean isInPowderedSnow() { + return false; + } + + @Override + public double getX() { + return 0; + } + + @Override + public double getY() { + return 0; + } + + @Override + public double getZ() { + return 0; + } + + @Override + public float getPitch() { + return 0; + } + + @Override + public float getYaw() { + return 0; + } + + @Override + public boolean collidesAt(@NotNull Location location) { + return false; + } + + @Override + public boolean wouldCollideUsing(@NotNull BoundingBox boundingBox) { + return false; + } + + @Override + public @NotNull EntityScheduler getScheduler() { + return null; + } + + @Override + public @NotNull String getScoreboardEntryName() { + return null; + } + + @Override + public void broadcastHurtAnimation(@NotNull Collection players) { + + } + + @Override + public boolean getAffectsSpawning() { + // TODO Auto-generated method stub + return false; + } + + @Override + public String getClientBrandName() { + // TODO Auto-generated method stub + return null; + } + + @Override + public T getClientOption(ClientOption arg0) { + // TODO Auto-generated method stub + return null; + } + + @Override + public float getCooldownPeriod() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public float getCooledAttackStrength(float arg0) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public PlayerProfile getPlayerProfile() { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getResourcePackHash() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Status getResourcePackStatus() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getViewDistance() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void giveExp(int arg0, boolean arg1) { + // TODO Auto-generated method stub + + } + + @Override + public boolean hasResourcePack() { + // TODO Auto-generated method stub + return false; + } + + @Override + public void addResourcePack(@NotNull UUID uuid, @NotNull String s, @org.jetbrains.annotations.Nullable byte[] bytes, @org.jetbrains.annotations.Nullable String s1, boolean b) { + + } + + @Override + public void removeResourcePack(@NotNull UUID uuid) { + + } + + @Override + public void removeResourcePacks() { + + } + + @Override + public void hideTitle() { + // TODO Auto-generated method stub + + } + + @Override + public void sendHurtAnimation(float v) { + + } + + @Override + public void sendLinks(@NotNull ServerLinks serverLinks) { + + } + + @Override + public void addCustomChatCompletions(@NotNull Collection collection) { + + } + + @Override + public void removeCustomChatCompletions(@NotNull Collection collection) { + + } + + @Override + public void setCustomChatCompletions(@NotNull Collection collection) { + + } + + @Override + public void resetCooldown() { + // TODO Auto-generated method stub + + } + + @Override + public void sendActionBar(String arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void sendActionBar(BaseComponent... arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void sendActionBar(char arg0, String arg1) { + // TODO Auto-generated method stub + + } + + @Override + public void sendTitle(Title arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void setAffectsSpawning(boolean arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void setPlayerListHeaderFooter(BaseComponent[] arg0, BaseComponent[] arg1) { + // TODO Auto-generated method stub + + } + + @Override + public void setPlayerListHeaderFooter(BaseComponent arg0, BaseComponent arg1) { + // TODO Auto-generated method stub + + } + + @Override + public void setPlayerProfile(PlayerProfile arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void setResourcePack(String arg0, String arg1) { + // TODO Auto-generated method stub + + } + + @Override + public void setResourcePack(@NotNull String string, + @NotNull String string2, boolean bl) { + + } + + @Override + public void setResourcePack(@NotNull String string, + @NotNull String string2, boolean bl, + @Nullable Component component) { + + } + + @Override + public void setSubtitle(BaseComponent[] arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void setSubtitle(BaseComponent arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void setTitleTimes(int arg0, int arg1, int arg2) { + // TODO Auto-generated method stub + + } + + @Override + public void setViewDistance(int arg0) { + // TODO Auto-generated method stub + + } + + @Override + public int getSimulationDistance() { + return 0; + } + + @Override + public void setSimulationDistance(int simulationDistance) { + + } + + @Override + public int getNoTickViewDistance() { + return 0; + } + + @Override + public void setNoTickViewDistance(int i) { + + } + + @Override + public int getSendViewDistance() { + return 0; + } + + @Override + public void setSendViewDistance(int i) { + + } + + @Override + public void showTitle(BaseComponent[] arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void showTitle(BaseComponent arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void showTitle(BaseComponent[] arg0, BaseComponent[] arg1, int arg2, int arg3, int arg4) { + // TODO Auto-generated method stub + + } + + @Override + public void showTitle(BaseComponent arg0, BaseComponent arg1, int arg2, int arg3, int arg4) { + // TODO Auto-generated method stub + + } + + @Override + public void updateTitle(Title arg0) { + // TODO Auto-generated method stub + + } + + @Override + public @NotNull TriState getFrictionState() { + return null; + } + + @Override + public void setFrictionState(@NotNull TriState triState) { + + } + + @Override + public @org.jspecify.annotations.Nullable T getData(DataComponentType.Valued type) { + return null; + } + + @Override + public @org.jspecify.annotations.Nullable T getDataOrDefault(DataComponentType.Valued type, @org.jspecify.annotations.Nullable T fallback) { + return null; + } + + @Override + public boolean hasData(DataComponentType type) { + return false; + } +} diff --git a/plugins/hiddenore-paper/src/main/resources/plugin.yml b/plugins/hiddenore-paper/src/main/resources/plugin.yml index 8685882343..6734697582 100644 --- a/plugins/hiddenore-paper/src/main/resources/plugin.yml +++ b/plugins/hiddenore-paper/src/main/resources/plugin.yml @@ -14,6 +14,7 @@ commands: /hiddenore -- reloads /hiddenore save -- forces save of tracking /hiddenore generate [num] -- drops all drops near the player issuing the command, use sparingly. + /hiddenore clearores [radius] -- clears all ores manually permission: hiddenore.adv permissions: hiddenore.*: diff --git a/plugins/itemexchange-paper/build.gradle.kts b/plugins/itemexchange-paper/build.gradle.kts index 5fe6095b13..d2e56285a3 100644 --- a/plugins/itemexchange-paper/build.gradle.kts +++ b/plugins/itemexchange-paper/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("io.papermc.paperweight.userdev") + alias(libs.plugins.paper.userdev) } version = "2.0.2" diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/commands/CreateCommand.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/commands/CreateCommand.java index fe039ce29a..1d7f6ac170 100644 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/commands/CreateCommand.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/commands/CreateCommand.java @@ -60,14 +60,15 @@ public void createFromShop(Player player) { continue; } } - Inventory inventory = BlockInventoryRequestEvent.emit(block, player, - BlockInventoryRequestEvent.Purpose.ACCESS).getInventory(); - if (inventory == null) { + BlockInventoryRequestEvent event = BlockInventoryRequestEvent.emit(block, player, + BlockInventoryRequestEvent.Purpose.ACCESS); + if (event.isCancelled()) { player.sendMessage(ChatColor.RED + "You do not have access to that."); return; } ItemStack inputItem = null; ItemStack outputItem = null; + Inventory inventory = event.getInventory(); for (ItemStack item : inventory.getContents()) { if (!ItemUtils.isValidItem(item)) { continue; diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/glues/namelayer/GroupModifier.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/glues/namelayer/GroupModifier.java index 035ca2322f..7667443a94 100644 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/glues/namelayer/GroupModifier.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/glues/namelayer/GroupModifier.java @@ -17,7 +17,7 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; -import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; +import vg.civcraft.mc.civmodcore.nbt.NbtCompound; import vg.civcraft.mc.namelayer.GroupManager; import vg.civcraft.mc.namelayer.group.Group; @@ -55,15 +55,15 @@ public boolean isBroken() { } @Override - public void toNBT(final @NotNull NBTCompound nbt) { + public void toNBT(final @NotNull NbtCompound nbt) { nbt.setInt(ID_KEY, getGroupId()); nbt.setString(NAME_KEY, getGroupName()); } - public static @NotNull GroupModifier fromNBT(final @NotNull NBTCompound nbt) { + public static @NotNull GroupModifier fromNBT(final @NotNull NbtCompound nbt) { final var modifier = new GroupModifier(); - modifier.setGroupId(nbt.getInt(ID_KEY)); - modifier.setGroupName(nbt.getString(NAME_KEY)); + modifier.setGroupId(nbt.getInt(ID_KEY, 0)); + modifier.setGroupName(nbt.getString(NAME_KEY, null)); return modifier; } diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/BulkExchangeRule.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/BulkExchangeRule.java index 84ec5b5923..ec6933e604 100644 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/BulkExchangeRule.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/BulkExchangeRule.java @@ -11,13 +11,14 @@ import net.kyori.adventure.text.format.NamedTextColor; import net.minecraft.core.component.DataComponents; import net.minecraft.world.item.component.CustomData; +import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; import vg.civcraft.mc.civmodcore.inventory.items.MetaUtils; -import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; +import vg.civcraft.mc.civmodcore.nbt.NbtCompound; public record BulkExchangeRule(List rules) implements ExchangeData { @@ -34,33 +35,33 @@ public boolean isBroken() { } @Override - public void toNBT(@NotNull final NBTCompound nbt) { + public void toNBT(@NotNull final NbtCompound nbt) { nbt.setCompoundArray(RULES_KEY, this.rules.stream() .map((rule) -> { - final var ruleNBT = new NBTCompound(); + final var ruleNBT = new NbtCompound(); rule.toNBT(ruleNBT); return ruleNBT; }) - .toArray(NBTCompound[]::new)); + .toArray(NbtCompound[]::new)); } @NotNull - public static BulkExchangeRule fromNBT(@NotNull final NBTCompound nbt) { - return new BulkExchangeRule(Arrays.stream(nbt.getCompoundArray(RULES_KEY)) + public static BulkExchangeRule fromNBT(@NotNull final NbtCompound nbt) { + return new BulkExchangeRule(Arrays.stream(nbt.getCompoundArray(RULES_KEY, true)) .map(ExchangeRule::fromNBT) .collect(Collectors.toCollection(ArrayList::new))); } public ItemStack toItem() { ItemStack item = ItemExchangeConfig.getRuleItem(); - final var itemNBT = new NBTCompound(); + final var itemNBT = new NbtCompound(); toNBT(itemNBT); - CustomData customData = CustomData.EMPTY.update(nbt -> nbt.put(BULK_KEY, itemNBT.getRAW())); - - net.minecraft.world.item.ItemStack nmsItem = ItemUtils.getNMSItemStack(item); - nmsItem.set(DataComponents.CUSTOM_DATA, customData); - item = nmsItem.getBukkitStack(); + CustomData.update( + DataComponents.CUSTOM_DATA, + CraftItemStack.unwrap(item), + (nbt) -> nbt.put(BULK_KEY, itemNBT.internal()) + ); ItemUtils.handleItemMeta(item, (ItemMeta meta) -> { meta.displayName(Component.text() @@ -78,22 +79,23 @@ public ItemStack toItem() { @Nullable public static BulkExchangeRule fromItem(final ItemStack item) { - if (!ItemUtils.isValidItem(item) - || item.getType() != ItemExchangeConfig.getRuleItemMaterial()) { + if (item == null || item.getType() != ItemExchangeConfig.getRuleItemMaterial()) { return null; } - final CustomData itemNBT = ItemUtils.getNMSItemStack(item).get(DataComponents.CUSTOM_DATA); - if (itemNBT != null && itemNBT.copyTag().contains(BULK_KEY)) { - var t = new NBTCompound(itemNBT.copyTag()); - - final var rulesNBT = t.getCompound(BULK_KEY).getCompoundArray(RULES_KEY); - final var rules = new ArrayList(rulesNBT.length); - for (final var ruleNBT : rulesNBT) { - rules.add(ExchangeRule.fromNBT(ruleNBT)); - } - return new BulkExchangeRule(rules); + final CustomData customData = CraftItemStack.unwrap(item).get(DataComponents.CUSTOM_DATA); + if (customData == null) { + return null; } - return null; + final var itemNBT = new NbtCompound(customData.copyTag()); + final NbtCompound bulkNBT = itemNBT.getCompound(BULK_KEY, false); + if (bulkNBT == null) { + return null; + } + final NbtCompound[] rulesNBT = bulkNBT.getCompoundArray(RULES_KEY, true); + final var rules = new ArrayList(rulesNBT.length); + for (final var ruleNBT : rulesNBT) { + rules.add(ExchangeRule.fromNBT(ruleNBT)); + } + return new BulkExchangeRule(rules); } - } diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/ExchangeRule.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/ExchangeRule.java index 3d01348396..4e1884a766 100644 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/ExchangeRule.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/ExchangeRule.java @@ -9,26 +9,26 @@ import com.untamedears.itemexchange.rules.modifiers.LoreModifier; import com.untamedears.itemexchange.utility.ModifierStorage; import com.untamedears.itemexchange.utility.Utilities; +import com.untamedears.itemexchange.utility.nbt.NBTSerialization; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import net.minecraft.core.component.DataComponents; -import net.minecraft.nbt.CompoundTag; import net.minecraft.world.item.component.CustomData; import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.EnumUtils; import org.bukkit.ChatColor; import org.bukkit.Material; +import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; import vg.civcraft.mc.civmodcore.inventory.InventoryUtils; import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; -import vg.civcraft.mc.civmodcore.nbt.NBTSerialization; -import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; +import vg.civcraft.mc.civmodcore.inventory.items.MaterialUtils; +import vg.civcraft.mc.civmodcore.nbt.NbtCompound; import vg.civcraft.mc.civmodcore.utilities.NullUtils; /** @@ -208,32 +208,32 @@ public ModifierStorage getModifiers() { } @Override - public void toNBT(@NotNull final NBTCompound nbt) { + public void toNBT(@NotNull final NbtCompound nbt) { nbt.setInt(VERSION_KEY, 4); nbt.setString(TYPE_KEY, this.type.name()); nbt.setString(MATERIAL_KEY, this.material.name()); nbt.setInt(AMOUNT_KEY, this.amount); nbt.setCompoundArray(MODIFIERS_KEY, this.modifiers.stream() .map((modifier) -> { - final var modifierNBT = new NBTCompound(); + final var modifierNBT = new NbtCompound(); modifier.toNBT(modifierNBT); modifierNBT.setString(CLASS_KEY, modifier.getSlug()); return modifierNBT; }) - .toArray(NBTCompound[]::new)); + .toArray(NbtCompound[]::new)); } @NotNull - public static ExchangeRule fromNBT(@NotNull final NBTCompound nbt) { + public static ExchangeRule fromNBT(@NotNull final NbtCompound nbt) { final var rule = new ExchangeRule(); - rule.type = EnumUtils.getEnum(Type.class, nbt.getString(TYPE_KEY)); - rule.material = EnumUtils.getEnum(Material.class, nbt.getString(MATERIAL_KEY)); - rule.amount = nbt.getInt(AMOUNT_KEY); + rule.type = nbt.getEnum(TYPE_KEY, Type.class, null); + rule.material = MaterialUtils.getMaterial(nbt.getString(MATERIAL_KEY, null)); + rule.amount = nbt.getInt(AMOUNT_KEY, 0); rule.modifiers.clear(); final var modifierRegistrar = ItemExchangePlugin.modifierRegistrar(); - Arrays.stream(nbt.getCompoundArray(MODIFIERS_KEY)) + Arrays.stream(nbt.getCompoundArray(MODIFIERS_KEY, true)) .map((modifierNBT) -> { - final var template = modifierRegistrar.getModifier(modifierNBT.getString(CLASS_KEY)); + final var template = modifierRegistrar.getModifier(modifierNBT.getString(CLASS_KEY, null)); if (template == null) { return null; } @@ -242,23 +242,22 @@ public static ExchangeRule fromNBT(@NotNull final NBTCompound nbt) { .filter(Objects::nonNull) .forEachOrdered(rule.modifiers::put); // Legacy Support - if (nbt.hasKeyOfType(LEGACY_DISPLAY_NAME_KEY, 8) && !nbt.getBoolean(LEGACY_IGNORE_DISPLAY_NAME_KEY)) { - DisplayNameModifier displayName = rule.modifiers.get(DisplayNameModifier.class); - if (displayName == null) { - displayName = (DisplayNameModifier) DisplayNameModifier.TEMPLATE.construct(); - displayName.setDisplayName(nbt.getString(LEGACY_DISPLAY_NAME_KEY)); - rule.modifiers.put(displayName); + if (nbt.getString(LEGACY_DISPLAY_NAME_KEY, null) instanceof final String displayName + && Boolean.FALSE.equals(nbt.getBoolean(LEGACY_IGNORE_DISPLAY_NAME_KEY, null)) + ) { + DisplayNameModifier modifier = rule.modifiers.get(DisplayNameModifier.class); + if (modifier == null) { + modifier = (DisplayNameModifier) DisplayNameModifier.TEMPLATE.construct(); + modifier.setDisplayName(displayName); + rule.modifiers.put(modifier); } } - if (nbt.hasKeyOfType(LEGACY_LORE_KEY, 9)) { - LoreModifier lore = rule.modifiers.get(LoreModifier.class); - if (lore == null) { - lore = (LoreModifier) LoreModifier.TEMPLATE.construct(); - lore.setLore(switch (nbt.getStringArray(LEGACY_LORE_KEY)) { - case final String[] lines -> Arrays.asList(lines); - case null -> Collections.emptyList(); - }); - rule.modifiers.put(lore); + if (nbt.getStringArray(LEGACY_LORE_KEY, false) instanceof final String[] lore) { + LoreModifier modifier = rule.modifiers.get(LoreModifier.class); + if (modifier == null) { + modifier = (LoreModifier) LoreModifier.TEMPLATE.construct(); + modifier.setLore(Arrays.asList(lore)); + rule.modifiers.put(modifier); } } return rule; @@ -401,7 +400,7 @@ public ItemStack[] getStock(Inventory inventory) { return new ItemStack[0]; } int requiredAmount = getAmount(); - for (ItemStack item : inventory.getContents()) { + for (ItemStack item : inventory.getStorageContents()) { if (requiredAmount <= 0) { break; } @@ -434,14 +433,14 @@ public ItemStack[] getStock(Inventory inventory) { */ public ItemStack toItem() { ItemStack item = ItemExchangeConfig.getRuleItem(); - final var itemNBT = new NBTCompound(); + final var itemNBT = new NbtCompound(); toNBT(itemNBT); - CustomData customData = CustomData.EMPTY.update(nbt -> nbt.put(RULE_KEY, itemNBT.getRAW())); - - net.minecraft.world.item.ItemStack nmsItem = ItemUtils.getNMSItemStack(item); - nmsItem.set(DataComponents.CUSTOM_DATA, customData); - item = nmsItem.getBukkitStack(); + CustomData.update( + DataComponents.CUSTOM_DATA, + CraftItemStack.unwrap(item), + (nbt) -> nbt.put(RULE_KEY, itemNBT.internal()) + ); ItemUtils.handleItemMeta(item, (ItemMeta meta) -> { meta.setDisplayName(getRuleTitle()); @@ -458,15 +457,18 @@ public ItemStack toItem() { * @return Returns an exchange rule if found, or null. */ public static ExchangeRule fromItem(ItemStack item) { - if (!ItemUtils.isValidItem(item) - || item.getType() != ItemExchangeConfig.getRuleItemMaterial()) { + if (item == null || item.getType() != ItemExchangeConfig.getRuleItemMaterial()) { + return null; + } + final CustomData customData = CraftItemStack.unwrap(item).get(DataComponents.CUSTOM_DATA); + if (customData == null) { return null; } - final CustomData itemNBT = ItemUtils.getNMSItemStack(item).get(DataComponents.CUSTOM_DATA); - if (itemNBT != null && itemNBT.copyTag().contains(RULE_KEY)) { - return fromNBT(new NBTCompound((CompoundTag) itemNBT.copyTag().get(RULE_KEY))); + final var itemNBT = new NbtCompound(customData.copyTag()); + final NbtCompound ruleNBT = itemNBT.getCompound(RULE_KEY, false); + if (ruleNBT == null) { + return null; } - return null; + return fromNBT(ruleNBT); } - } diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/interfaces/ExchangeData.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/interfaces/ExchangeData.java index 9dcee4533a..00b34ff976 100644 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/interfaces/ExchangeData.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/interfaces/ExchangeData.java @@ -1,8 +1,8 @@ package com.untamedears.itemexchange.rules.interfaces; +import com.untamedears.itemexchange.utility.nbt.NBTSerializable; import java.util.ArrayList; import java.util.List; -import vg.civcraft.mc.civmodcore.nbt.NBTSerializable; import vg.civcraft.mc.civmodcore.utilities.Validation; /** diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/interfaces/ModifierData.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/interfaces/ModifierData.java index 0c0481fc10..6a927dd797 100644 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/interfaces/ModifierData.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/interfaces/ModifierData.java @@ -2,15 +2,14 @@ import co.aikar.commands.BaseCommand; import com.untamedears.itemexchange.rules.ExchangeRule; +import com.untamedears.itemexchange.utility.nbt.NBTSerializable; +import com.untamedears.itemexchange.utility.nbt.NBTSerialization; import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.Objects; -import org.apache.commons.lang.IllegalClassException; import org.bukkit.inventory.ItemStack; import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; -import vg.civcraft.mc.civmodcore.nbt.NBTSerializable; -import vg.civcraft.mc.civmodcore.nbt.NBTSerialization; -import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; +import vg.civcraft.mc.civmodcore.nbt.NbtCompound; /** * Abstract class that represents a modifier. @@ -52,7 +51,7 @@ public final ModifierData construct() { return getClass().getConstructor().newInstance(); } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException error) { - throw new IllegalClassException("That Modifier cannot be constructed... please make sure it has a " + + throw new IllegalStateException("That Modifier cannot be constructed... please make sure it has a " + "public, zero argument constructor."); } } @@ -70,7 +69,7 @@ public final ModifierData construct() { * Duplicates this modifier. */ public final ModifierData duplicate() { - final var nbt = new NBTCompound(); + final var nbt = new NbtCompound(); toNBT(nbt); return NBTSerialization.getDeserializer(getClass()).fromNBT(nbt); } diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/BookModifier.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/BookModifier.java index bf0c6dffc0..4f79de68d5 100644 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/BookModifier.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/BookModifier.java @@ -9,14 +9,12 @@ import java.util.List; import java.util.Objects; import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.EnumUtils; import org.bukkit.ChatColor; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.BookMeta; import org.bukkit.inventory.meta.BookMeta.Generation; import org.jetbrains.annotations.NotNull; -import vg.civcraft.mc.civmodcore.nbt.NBTType; -import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; +import vg.civcraft.mc.civmodcore.nbt.NbtCompound; @CommandAlias(SetCommand.ALIAS) @Modifier(slug = "BOOK", order = 1000) @@ -89,7 +87,7 @@ public boolean conforms(ItemStack item) { } @Override - public void toNBT(@NotNull final NBTCompound nbt) { + public void toNBT(@NotNull final NbtCompound nbt) { if (hasTitle()) { nbt.setString(TITLE_KEY, this.title); } @@ -97,7 +95,7 @@ public void toNBT(@NotNull final NBTCompound nbt) { nbt.setString(AUTHOR_KEY, this.author); } if (hasGeneration()) { - nbt.setString(GENERATION_KEY, this.generation.name()); + nbt.setEnum(GENERATION_KEY, this.generation); } nbt.setBoolean(HAS_PAGES_KEY, this.hasPages); if (hasPages()) { @@ -106,20 +104,20 @@ public void toNBT(@NotNull final NBTCompound nbt) { } @NotNull - public static BookModifier fromNBT(@NotNull final NBTCompound nbt) { + public static BookModifier fromNBT(@NotNull final NbtCompound nbt) { final var modifier = new BookModifier(); - if (nbt.hasKeyOfType(TITLE_KEY, NBTType.STRING)) { - modifier.setTitle(nbt.getString(TITLE_KEY)); + if (nbt.getString(TITLE_KEY, null) instanceof final String title) { + modifier.setTitle(title); } - if (nbt.hasKeyOfType(AUTHOR_KEY, NBTType.STRING)) { - modifier.setAuthor(nbt.getString(AUTHOR_KEY)); + if (nbt.getString(AUTHOR_KEY, null) instanceof final String author) { + modifier.setAuthor(author); } - if (nbt.hasKeyOfType(GENERATION_KEY, NBTType.STRING)) { - modifier.setGeneration(EnumUtils.getEnum(Generation.class, nbt.getString(GENERATION_KEY))); + if (nbt.getEnum(GENERATION_KEY, BookMeta.Generation.class, null) instanceof final BookMeta.Generation generation) { + modifier.setGeneration(generation); } - if (nbt.getBoolean(HAS_PAGES_KEY)) { + if (nbt.getBoolean(HAS_PAGES_KEY, false)) { modifier.setHasPages(true); - modifier.setBookHash(nbt.getInt(BOOK_HASH_KEY)); + modifier.setBookHash(nbt.getInt(BOOK_HASH_KEY, 0)); } return modifier; } diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/DamageableModifier.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/DamageableModifier.java index 78e0dd55de..89e9c0f3ea 100644 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/DamageableModifier.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/DamageableModifier.java @@ -21,7 +21,7 @@ import org.bukkit.inventory.meta.Damageable; import org.jetbrains.annotations.NotNull; import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; -import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; +import vg.civcraft.mc.civmodcore.nbt.NbtCompound; @CommandAlias(SetCommand.ALIAS) @Modifier(slug = "DAMAGE", order = 500) @@ -77,14 +77,14 @@ public boolean conforms(ItemStack item) { } @Override - public void toNBT(@NotNull final NBTCompound nbt) { + public void toNBT(@NotNull final NbtCompound nbt) { nbt.setInt(DAMAGE_KEY, getDamage()); } @NotNull - public static DamageableModifier fromNBT(@NotNull final NBTCompound nbt) { + public static DamageableModifier fromNBT(@NotNull final NbtCompound nbt) { final var modifier = new DamageableModifier(); - modifier.setDamage(nbt.getInt(DAMAGE_KEY)); + modifier.setDamage(nbt.getInt(DAMAGE_KEY, 0)); return modifier; } diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/DisplayNameModifier.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/DisplayNameModifier.java index 7bbc470829..5ac9873e79 100644 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/DisplayNameModifier.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/DisplayNameModifier.java @@ -16,7 +16,7 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; -import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; +import vg.civcraft.mc.civmodcore.nbt.NbtCompound; @CommandAlias(SetCommand.ALIAS) @Modifier(slug = "DISPLAY", order = 100) @@ -59,7 +59,7 @@ public boolean conforms(ItemStack item) { } @Override - public void toNBT(@NotNull final NBTCompound nbt) { + public void toNBT(@NotNull final NbtCompound nbt) { if (hasDisplayName()) { nbt.setString(DISPLAY_NAME_KEY, getDisplayName()); } else { @@ -68,9 +68,9 @@ public void toNBT(@NotNull final NBTCompound nbt) { } @NotNull - public static DisplayNameModifier fromNBT(@NotNull final NBTCompound nbt) { + public static DisplayNameModifier fromNBT(@NotNull final NbtCompound nbt) { final var modifier = new DisplayNameModifier(); - modifier.setDisplayName(nbt.getString(DISPLAY_NAME_KEY)); + modifier.setDisplayName(nbt.getString(DISPLAY_NAME_KEY, null)); return modifier; } diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/EnchantModifier.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/EnchantModifier.java index f9cafea8c3..41953f12c8 100644 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/EnchantModifier.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/EnchantModifier.java @@ -33,7 +33,7 @@ import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import vg.civcraft.mc.civmodcore.inventory.items.EnchantUtils; -import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; +import vg.civcraft.mc.civmodcore.nbt.NbtCompound; import vg.civcraft.mc.civmodcore.utilities.KeyedUtils; import vg.civcraft.mc.civmodcore.utilities.MoreMapUtils; @@ -88,7 +88,7 @@ public boolean conforms(ItemStack item) { } @Override - public void toNBT(@NotNull final NBTCompound nbt) { + public void toNBT(@NotNull final NbtCompound nbt) { nbt.setCompound(REQUIRED_KEY, NBTEncodings.encodeLeveledEnchants(getRequiredEnchants())); nbt.setStringArray(EXCLUDED_KEY, getExcludedEnchants().stream() .map(KeyedUtils::getString) @@ -98,14 +98,14 @@ public void toNBT(@NotNull final NBTCompound nbt) { } @NotNull - public static EnchantModifier fromNBT(@NotNull final NBTCompound nbt) { + public static EnchantModifier fromNBT(@NotNull final NbtCompound nbt) { final var modifier = new EnchantModifier(); - modifier.setRequiredEnchants(NBTEncodings.decodeLeveledEnchants(nbt.getCompound(REQUIRED_KEY))); - modifier.setExcludedEnchants(Arrays.stream(nbt.getStringArray(EXCLUDED_KEY)) + modifier.setRequiredEnchants(NBTEncodings.decodeLeveledEnchants(nbt.getCompound(REQUIRED_KEY, true))); + modifier.setExcludedEnchants(Arrays.stream(nbt.getStringArray(EXCLUDED_KEY, true)) .map(EnchantUtils::getEnchantment) .filter(Objects::nonNull) .collect(Collectors.toCollection(HashSet::new))); - modifier.setAllowUnlistedEnchants(nbt.getBoolean(UNLISTED_KEY)); + modifier.setAllowUnlistedEnchants(nbt.getBoolean(UNLISTED_KEY, false)); return modifier; } diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/EnchantStorageModifier.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/EnchantStorageModifier.java index e3d27c0a34..dd569bac9b 100644 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/EnchantStorageModifier.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/EnchantStorageModifier.java @@ -18,7 +18,7 @@ import org.bukkit.inventory.meta.EnchantmentStorageMeta; import org.jetbrains.annotations.NotNull; import vg.civcraft.mc.civmodcore.inventory.items.EnchantUtils; -import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; +import vg.civcraft.mc.civmodcore.nbt.NbtCompound; import vg.civcraft.mc.civmodcore.utilities.MoreMapUtils; @CommandAlias(SetCommand.ALIAS) @@ -72,14 +72,14 @@ public boolean conforms(ItemStack item) { } @Override - public void toNBT(@NotNull final NBTCompound nbt) { + public void toNBT(@NotNull final NbtCompound nbt) { nbt.setCompound(ENCHANTS_KEY, NBTEncodings.encodeLeveledEnchants(getEnchants())); } @NotNull - public static EnchantStorageModifier fromNBT(@NotNull final NBTCompound nbt) { + public static EnchantStorageModifier fromNBT(@NotNull final NbtCompound nbt) { final var modifier = new EnchantStorageModifier(); - modifier.setEnchants(NBTEncodings.decodeLeveledEnchants(nbt.getCompound(ENCHANTS_KEY))); + modifier.setEnchants(NBTEncodings.decodeLeveledEnchants(nbt.getCompound(ENCHANTS_KEY, true))); return modifier; } diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/LoreModifier.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/LoreModifier.java index 1be812677e..2c824711c4 100644 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/LoreModifier.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/LoreModifier.java @@ -12,7 +12,6 @@ import com.untamedears.itemexchange.utility.ModifierHandler; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -24,7 +23,7 @@ import org.jetbrains.annotations.NotNull; import vg.civcraft.mc.civmodcore.chat.ChatUtils; import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; -import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; +import vg.civcraft.mc.civmodcore.nbt.NbtCompound; @CommandAlias(SetCommand.ALIAS) @Modifier(slug = "LORE", order = 300) @@ -64,7 +63,7 @@ public boolean isBroken() { } @Override - public void toNBT(@NotNull final NBTCompound nbt) { + public void toNBT(@NotNull final NbtCompound nbt) { if (this.lore == null) { nbt.remove(LORE_KEY); } else { @@ -73,11 +72,11 @@ public void toNBT(@NotNull final NBTCompound nbt) { } @NotNull - public static LoreModifier fromNBT(@NotNull final NBTCompound nbt) { + public static LoreModifier fromNBT(@NotNull final NbtCompound nbt) { final var modifier = new LoreModifier(); - modifier.setLore(switch (nbt.getStringArray(LORE_KEY)) { + modifier.setLore(switch (nbt.getStringArray(LORE_KEY, false)) { case final String[] lines -> Arrays.asList(lines); - case null -> Collections.emptyList(); + case null -> null; }); return modifier; } diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/PotionModifier.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/PotionModifier.java index 6db7415201..ff0e4521f1 100644 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/PotionModifier.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/PotionModifier.java @@ -13,7 +13,7 @@ import java.util.Collections; import java.util.List; import java.util.stream.Collectors; -import org.apache.commons.lang.WordUtils; +import org.apache.commons.lang3.text.WordUtils; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; @@ -21,7 +21,7 @@ import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionType; import org.jetbrains.annotations.NotNull; -import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; +import vg.civcraft.mc.civmodcore.nbt.NbtCompound; import vg.civcraft.mc.civmodcore.utilities.NullUtils; @CommandAlias(SetCommand.ALIAS) @@ -77,21 +77,21 @@ public boolean conforms(ItemStack item) { } @Override - public void toNBT(@NotNull final NBTCompound nbt) { + public void toNBT(@NotNull final NbtCompound nbt) { nbt.setCompound(BASE_KEY, NBTEncodings.encodePotionData(this.base)); nbt.setCompoundArray(EFFECTS_KEY, getEffects().stream() .map(NBTEncodings::encodePotionEffect) - .toArray(NBTCompound[]::new)); + .toArray(NbtCompound[]::new)); } - public static PotionModifier fromNBT(@NotNull final NBTCompound nbt) { + public static PotionModifier fromNBT(@NotNull final NbtCompound nbt) { final var modifier = new PotionModifier(); - PotionType type = NBTEncodings.decodePotionData(nbt.getCompound(BASE_KEY)); + PotionType type = NBTEncodings.decodePotionData(nbt.getCompound(BASE_KEY, true)); if (type == null) { return null; // "UNCRAFTABLE" potion which is removed in 1.21 } modifier.setPotionData(type); - modifier.setEffects(Arrays.stream(nbt.getCompoundArray(EFFECTS_KEY)) + modifier.setEffects(Arrays.stream(nbt.getCompoundArray(EFFECTS_KEY, true)) .map(NBTEncodings::decodePotionEffect) .collect(Collectors.toCollection(ArrayList::new))); return modifier; diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/RepairModifier.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/RepairModifier.java index 0a5640f941..8d9e0320d2 100644 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/RepairModifier.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/RepairModifier.java @@ -22,7 +22,7 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.Repairable; import org.jetbrains.annotations.NotNull; -import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; +import vg.civcraft.mc.civmodcore.nbt.NbtCompound; /** *

This additional represents a repair level condition.

@@ -74,14 +74,14 @@ public boolean conforms(ItemStack item) { } @Override - public void toNBT(@NotNull final NBTCompound nbt) { + public void toNBT(@NotNull final NbtCompound nbt) { nbt.setInt(LEVEL_KEY, getRepairCost()); } @NotNull - public static RepairModifier fromNBT(@NotNull final NBTCompound nbt) { + public static RepairModifier fromNBT(@NotNull final NbtCompound nbt) { final var modifier = new RepairModifier(); - modifier.setRepairCost(nbt.getInt(LEVEL_KEY)); + modifier.setRepairCost(nbt.getInt(LEVEL_KEY, 0)); return modifier; } diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/_ExampleModifier.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/_ExampleModifier.java deleted file mode 100644 index 4c5fd30498..0000000000 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/rules/modifiers/_ExampleModifier.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.untamedears.itemexchange.rules.modifiers; - -import co.aikar.commands.annotation.CommandAlias; -import com.untamedears.itemexchange.commands.SetCommand; -import com.untamedears.itemexchange.rules.ExchangeRule; -import com.untamedears.itemexchange.rules.interfaces.ExchangeData; -import com.untamedears.itemexchange.rules.interfaces.Modifier; -import com.untamedears.itemexchange.rules.interfaces.ModifierData; -import java.util.List; -import org.apache.commons.lang3.NotImplementedException; -import org.bukkit.inventory.ItemStack; -import org.jetbrains.annotations.NotNull; -import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; -import vg.civcraft.mc.civmodcore.nbt.NBTSerializable; -import vg.civcraft.mc.civmodcore.nbt.NBTSerializationException; -import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; -import vg.civcraft.mc.civmodcore.utilities.Validation; - -@CommandAlias(SetCommand.ALIAS) // This is needed to make commands work -@Modifier(slug = "EXAMPLE", order = 12345) -public final class _ExampleModifier extends ModifierData { - - // Make sure to have a template instance to make registration easier - public static final _ExampleModifier TEMPLATE = new _ExampleModifier(); - - /** - * Constructs a new instance of a modifier. - * - * @param item The item to base this exchange data on. You can assume that the item has passed a - * {@link ItemUtils#isValidItem(ItemStack)} check. - * @return Returns a new instance of the extended class. - */ - @Override - public _ExampleModifier construct(ItemStack item) { - return null; - } - - /** - * Essentially a renamed {@link ExchangeData#isValid()} to bring more clarity. isValid is still being used - * to maintain usage of {@link Validation#checkValidity(Validation)}. This method is intended to convey - * whether this rule is broken, not whether it should be filtered. - * - * @return Returns true if this rule is broken. - */ - @Override - public boolean isBroken() { - return false; - } - - /** - * Checks if an arbitrary item conforms to this exchange data's requirements. - * - * @param item The arbitrary item to check. You can assume that the item has passed a - * {@link ItemUtils#isValidItem(ItemStack)} check. - * @return Returns true if the given item conforms. - */ - @Override - public boolean conforms(ItemStack item) { - return false; - } - - /** - * Serializes a class into an NBTCompound. - * - * @param nbt The NBTCompound to serialize into, which should NEVER be null, so feel free to throw an - * {@link NBTSerializationException} if it is. You can generally assume that the nbt compound is new and - * therefore empty, but you may wish to check or manually {@link NBTCompound#clear() empty it}, though the - * latter may cause other issues. - * @throws NBTSerializationException This is thrown if the implementation has a fatal error serializing. - */ - @Override - public void toNBT(@NotNull final NBTCompound nbt) { - throw new NotImplementedException("Please implement me on your class!"); - } - - /** - * Deserializes a class into an NBTCompound. - * - * @param nbt The NBTCompound to deserialize from, which should NEVER be null, so feel free to throw an - * {@link NBTSerializationException} if it is. - * @throws NBTSerializationException This is thrown if the implementation has a fatal error deserializing. - */ - @NotNull - public static NBTSerializable fromNBT(@NotNull final NBTCompound nbt) { - final var modifier = new _ExampleModifier(); - // Something here - return modifier; - } - - /** - * @return Returns a new listing, or null. - */ - @Override - public String getDisplayListing() { - return null; - } - - /** - * @return Returns a set of strings to be displayed as part of {@link ExchangeRule}'s details. Note: Null or - * empty lists are supported and convey to not list anything. - */ - @Override - public List getDisplayInfo() { - return null; - } - -} diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/NBTEncodings.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/NBTEncodings.java index 3f1f1497a6..19806d5242 100644 --- a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/NBTEncodings.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/NBTEncodings.java @@ -9,7 +9,7 @@ import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionType; import vg.civcraft.mc.civmodcore.inventory.items.EnchantUtils; -import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; +import vg.civcraft.mc.civmodcore.nbt.NbtCompound; import vg.civcraft.mc.civmodcore.utilities.KeyedUtils; import vg.civcraft.mc.civmodcore.utilities.MoreMapUtils; @@ -25,8 +25,8 @@ public final class NBTEncodings { // Leveled Enchantments // ------------------------------------------------------------ - public static NBTCompound encodeLeveledEnchants(Map enchants) { - NBTCompound nbt = new NBTCompound(); + public static NbtCompound encodeLeveledEnchants(Map enchants) { + NbtCompound nbt = new NbtCompound(); if (enchants == null) { return nbt; } @@ -39,17 +39,17 @@ public static NBTCompound encodeLeveledEnchants(Map enchan return nbt; } - public static Map decodeLeveledEnchants(NBTCompound nbt) { + public static Map decodeLeveledEnchants(NbtCompound nbt) { Map enchants = Maps.newHashMap(); if (nbt == null) { return enchants; } - for (String slug : nbt.getKeys()) { + for (String slug : nbt.keys()) { Enchantment enchantment = EnchantUtils.getEnchantment(slug); if (enchantment == null) { continue; } - enchants.put(enchantment, nbt.getInt(slug)); + enchants.put(enchantment, nbt.getInt(slug, 0)); } return enchants; } @@ -58,20 +58,17 @@ public static Map decodeLeveledEnchants(NBTCompound nbt) { // Potion Data // ------------------------------------------------------------ - public static NBTCompound encodePotionData(PotionType type) { - NBTCompound nbt = new NBTCompound(); - if (type == null) { - return nbt; - } - nbt.setString(TYPE_KEY, type.name()); + public static NbtCompound encodePotionData(PotionType type) { + NbtCompound nbt = new NbtCompound(); + nbt.setEnum(TYPE_KEY, type); return nbt; } - public static PotionType decodePotionData(NBTCompound nbt) { + public static PotionType decodePotionData(NbtCompound nbt) { if (nbt == null) { return null; } - String type = nbt.getString(TYPE_KEY); + String type = nbt.getString(TYPE_KEY, null); if ("UNCRAFTABLE".equals(type)) { return null; } @@ -82,8 +79,8 @@ public static PotionType decodePotionData(NBTCompound nbt) { // Potion Effect // ------------------------------------------------------------ - public static NBTCompound encodePotionEffect(PotionEffect effect) { - NBTCompound nbt = new NBTCompound(); + public static NbtCompound encodePotionEffect(PotionEffect effect) { + NbtCompound nbt = new NbtCompound(); if (effect == null) { return nbt; } @@ -95,16 +92,16 @@ public static NBTCompound encodePotionEffect(PotionEffect effect) { return nbt; } - public static PotionEffect decodePotionEffect(NBTCompound nbt) { + public static PotionEffect decodePotionEffect(NbtCompound nbt) { if (nbt == null) { return null; } return new PotionEffect( - Objects.requireNonNull(Registry.POTION_EFFECT_TYPE.get(NamespacedKey.minecraft(nbt.getString(TYPE_KEY)))), - nbt.getInt(DURATION_KEY), - nbt.getInt(AMPLIFIER_KEY), - nbt.getBoolean(AMBIENT_KEY), - nbt.getBoolean(PARTICLES_KEY)); + Objects.requireNonNull(Registry.POTION_EFFECT_TYPE.get(NamespacedKey.minecraft(nbt.getString(TYPE_KEY, null)))), + nbt.getInt(DURATION_KEY, null), + nbt.getInt(AMPLIFIER_KEY, null), + nbt.getBoolean(AMBIENT_KEY, null), + nbt.getBoolean(PARTICLES_KEY, null)); } } diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTDeserializer.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/nbt/NBTDeserializer.java similarity index 52% rename from plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTDeserializer.java rename to plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/nbt/NBTDeserializer.java index 99aa708630..69f8832ea5 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTDeserializer.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/nbt/NBTDeserializer.java @@ -1,16 +1,16 @@ -package vg.civcraft.mc.civmodcore.nbt; +package com.untamedears.itemexchange.utility.nbt; import org.jetbrains.annotations.NotNull; -import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; +import vg.civcraft.mc.civmodcore.nbt.NbtCompound; /** * Function that's returned by {@link NBTSerialization#getDeserializer(Class)} to retrieve a serializable's version of - * {@link NBTSerializable#fromNBT(NBTCompound)}. + * {@link NBTSerializable#fromNBT(NbtCompound)}. */ +@Deprecated @FunctionalInterface public interface NBTDeserializer { - - @NotNull - T fromNBT(@NotNull final NBTCompound nbt); - + @NotNull T fromNBT( + final @NotNull NbtCompound nbt + ); } diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTSerializable.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/nbt/NBTSerializable.java similarity index 79% rename from plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTSerializable.java rename to plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/nbt/NBTSerializable.java index 0e471fb813..aec178c806 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTSerializable.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/nbt/NBTSerializable.java @@ -1,11 +1,11 @@ -package vg.civcraft.mc.civmodcore.nbt; +package com.untamedears.itemexchange.utility.nbt; import org.apache.commons.lang3.NotImplementedException; import org.jetbrains.annotations.NotNull; -import vg.civcraft.mc.civmodcore.nbt.wrappers.NBTCompound; +import vg.civcraft.mc.civmodcore.nbt.NbtCompound; +@Deprecated public interface NBTSerializable { - /** * Serializes this class onto a given NBTCompound. * @@ -13,7 +13,9 @@ public interface NBTSerializable { * {@link NBTSerializationException} if it is. You can generally assume that the NBTCompound is new and * therefore empty, but you may wish to check that. */ - void toNBT(@NotNull final NBTCompound nbt); + void toNBT( + final @NotNull NbtCompound nbt + ); /** *

Deserializes a given NBTCompound into a new class instance.

@@ -24,9 +26,9 @@ public interface NBTSerializable { * {@link NBTSerializationException} if it is. * @return Returns a new instance of this class. */ - @NotNull - public static NBTSerializable fromNBT(@NotNull final NBTCompound nbt) { + public static @NotNull NBTSerializable fromNBT( + final @NotNull NbtCompound nbt + ) { throw new NotImplementedException("Please implement me on your class!"); } - } diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/nbt/NBTSerialization.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/nbt/NBTSerialization.java new file mode 100644 index 0000000000..fdafe9f927 --- /dev/null +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/nbt/NBTSerialization.java @@ -0,0 +1,34 @@ +package com.untamedears.itemexchange.utility.nbt; + +import java.lang.reflect.InvocationTargetException; +import java.util.Objects; +import org.apache.commons.lang3.reflect.MethodUtils; +import org.jetbrains.annotations.NotNull; +import vg.civcraft.mc.civmodcore.nbt.NbtCompound; + +@Deprecated +public final class NBTSerialization { + /** + * Dynamically retrieves a serializable's {@link NBTSerializable#fromNBT(NbtCompound) fromNBT} method. + * + * @param The type of the serializable. + * @param clazz The serializable's class. + * @return Returns a deserializer function. + */ + @SuppressWarnings("unchecked") + public static @NotNull NBTDeserializer getDeserializer( + final @NotNull Class clazz + ) { + final var method = MethodUtils.getMatchingAccessibleMethod(clazz, "fromNBT", NbtCompound.class); + if (!Objects.equals(clazz, method.getReturnType())) { + throw new IllegalArgumentException("That class hasn't implemented its own fromNBT method.. please fix"); + } + return (nbt) -> { + try { + return (T) method.invoke(null, nbt); + } catch (final IllegalAccessException | InvocationTargetException exception) { + throw new NBTSerializationException(exception); + } + }; + } +} diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/nbt/NBTSerializationException.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/nbt/NBTSerializationException.java new file mode 100644 index 0000000000..d360ee6b35 --- /dev/null +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/nbt/NBTSerializationException.java @@ -0,0 +1,38 @@ +package com.untamedears.itemexchange.utility.nbt; + +import java.io.Serial; +import org.jetbrains.annotations.NotNull; +import vg.civcraft.mc.civmodcore.nbt.NbtCompound; +import vg.civcraft.mc.civmodcore.nbt.exceptions.NbtException; + +/** + * Exception that ought to be used within {@link NBTSerializable#toNBT(NbtCompound)} and {@link NBTSerializable#fromNBT(NbtCompound)}. + */ +@Deprecated +public class NBTSerializationException extends NbtException { + @Serial + private static final long serialVersionUID = 606023177729327630L; + + public NBTSerializationException() { + super(); + } + + public NBTSerializationException( + final @NotNull String message + ) { + super(message); + } + + public NBTSerializationException( + final @NotNull String message, + final @NotNull Throwable cause + ) { + super(message, cause); + } + + public NBTSerializationException( + final @NotNull Throwable cause + ) { + super(cause); + } +} diff --git a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTType.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/nbt/NBTType.java similarity index 79% rename from plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTType.java rename to plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/nbt/NBTType.java index 1ec65fa009..90607cad3b 100644 --- a/plugins/civmodcore-paper/src/main/java/vg/civcraft/mc/civmodcore/nbt/NBTType.java +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/nbt/NBTType.java @@ -1,6 +1,5 @@ -package vg.civcraft.mc.civmodcore.nbt; +package com.untamedears.itemexchange.utility.nbt; -import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.Tag; @@ -8,16 +7,13 @@ * NBT Type IDs for usage with: * *
    - *
  • {@link CompoundTag#contains(String, int)}}
  • *
  • {@link Tag#getType()} ()}
  • *
  • {@link ListTag#getId()} // list element type
  • *
  • etc...
  • *
- *

- * This is a better version of {@link org.bukkit.craftbukkit.util.CraftMagicNumbers} */ +@Deprecated public final class NBTType { - public static final byte END = (byte) 0; public static final byte BYTE = (byte) 1; public static final byte SHORT = (byte) 2; @@ -31,5 +27,4 @@ public final class NBTType { public static final byte COMPOUND = (byte) 10; public static final byte INT_ARRAY = (byte) 11; public static final byte LONG_ARRAY = (byte) 12; - } diff --git a/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/nbt/package-info.java b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/nbt/package-info.java new file mode 100644 index 0000000000..54781b5334 --- /dev/null +++ b/plugins/itemexchange-paper/src/main/java/com/untamedears/itemexchange/utility/nbt/package-info.java @@ -0,0 +1,6 @@ +/** + * This package was moved from CivModCore to ItemExchange because 1) it's the only plugin that uses it, and 2) to avoid + * any other plugin from potentially using it. It's bad and needs to be removed. But given that that would require a + * substantial refactor to ItemExchange, localising this package to ItemExchange seems like a reasonable compromise. + */ +package com.untamedears.itemexchange.utility.nbt; diff --git a/plugins/jukealert-paper/build.gradle.kts b/plugins/jukealert-paper/build.gradle.kts index 759fe92821..6386874888 100644 --- a/plugins/jukealert-paper/build.gradle.kts +++ b/plugins/jukealert-paper/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - id("io.papermc.paperweight.userdev") - id("com.github.johnrengelman.shadow") + alias(libs.plugins.paper.userdev) + alias(libs.plugins.shadow) } version = "3.0.8" @@ -10,7 +10,6 @@ dependencies { paperDevBundle(libs.versions.paper) } - api("com.github.davidmoten:rtree2:0.9.3") compileOnly(project(":plugins:civmodcore-paper")) compileOnly(project(":plugins:namelayer-paper")) compileOnly(project(":plugins:citadel-paper")) diff --git a/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/JukeAlert.java b/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/JukeAlert.java index 66caae7ea9..831da98eac 100644 --- a/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/JukeAlert.java +++ b/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/JukeAlert.java @@ -115,7 +115,9 @@ public void onEnable() { private void registerJukeAlertEvents() { PluginManager pm = getServer().getPluginManager(); - pm.registerEvents(new LoggableActionListener(snitchManager), this); + LoggableActionListener loggableActionListener = new LoggableActionListener(snitchManager); + pm.registerEvents(loggableActionListener, this); + loggableActionListener.setupScheduler(this); pm.registerEvents(new SnitchLifeCycleListener(snitchManager, snitchConfigManager, getLogger()), this); } } diff --git a/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/SnitchManager.java b/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/SnitchManager.java index 88cb9c810b..848896f8fb 100644 --- a/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/SnitchManager.java +++ b/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/SnitchManager.java @@ -108,7 +108,7 @@ private RTree getTreeFor(Location loc) { RTree tree = treesByWorld.get(loc.getWorld().getUID()); if (tree == null) { JukeAlert.getInstance().getLogger().info("Tree for world " + loc.getWorld().getUID() + " does not exist, creating"); - tree = RTree.create(); + tree = RTree.star().create(); treesByWorld.put(loc.getWorld().getUID(), tree); } return tree; diff --git a/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/database/JukeAlertDAO.java b/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/database/JukeAlertDAO.java index 93dbf367ac..3e6cb704b5 100644 --- a/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/database/JukeAlertDAO.java +++ b/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/database/JukeAlertDAO.java @@ -5,11 +5,9 @@ import com.untamedears.jukealert.model.Snitch; import com.untamedears.jukealert.model.SnitchFactoryType; import com.untamedears.jukealert.model.SnitchTypeManager; -import com.untamedears.jukealert.model.actions.ActionCacheState; import com.untamedears.jukealert.model.actions.LoggedActionFactory; import com.untamedears.jukealert.model.actions.LoggedActionPersistence; import com.untamedears.jukealert.model.actions.abstr.LoggableAction; -import com.untamedears.jukealert.model.actions.abstr.LoggablePlayerAction; import com.untamedears.jukealert.model.appender.AbstractSnitchAppender; import com.untamedears.jukealert.model.appender.DormantCullingAppender; import com.untamedears.jukealert.model.appender.LeverToggleAppender; diff --git a/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/listener/LoggableActionListener.java b/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/listener/LoggableActionListener.java index 554ee2e222..02daf0916c 100644 --- a/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/listener/LoggableActionListener.java +++ b/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/listener/LoggableActionListener.java @@ -31,14 +31,18 @@ import java.util.TreeMap; import java.util.UUID; import java.util.function.Function; +import java.util.logging.Level; +import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.World; import org.bukkit.block.DoubleChest; import org.bukkit.entity.Boat; import org.bukkit.entity.ChestBoat; import org.bukkit.entity.Entity; import org.bukkit.entity.EntityType; +import org.bukkit.entity.HappyGhast; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.entity.SpawnCategory; @@ -70,6 +74,7 @@ import org.bukkit.event.vehicle.VehicleMoveEvent; import org.bukkit.inventory.BlockInventoryHolder; import org.bukkit.inventory.InventoryHolder; +import org.bukkit.plugin.Plugin; import vg.civcraft.mc.namelayer.NameAPI; public class LoggableActionListener implements Listener { @@ -82,6 +87,24 @@ public LoggableActionListener(SnitchManager snitchManager) { this.insideFields = new TreeMap<>(); } + public void setupScheduler(Plugin plugin) { + Bukkit.getScheduler().runTaskTimer(plugin, () -> { + try { + for (World world : Bukkit.getWorlds()) { + for (Player player : world.getPlayers()) { + Entity entity = player.getVehicle(); + if (!(entity instanceof HappyGhast)) { + continue; + } + handleSnitchEntry(player, entity.getLocation()); + } + } + } catch (RuntimeException ex) { + plugin.getLogger().log(Level.WARNING, "Ticking ghast positions", ex); + } + }, 0, 1); + } + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) public void enterSnitchProximity(PlayerMoveEvent event) { Location from = event.getFrom(); diff --git a/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/model/actions/abstr/PlayerAction.java b/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/model/actions/abstr/PlayerAction.java index 422f2c8b45..c981a97351 100644 --- a/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/model/actions/abstr/PlayerAction.java +++ b/plugins/jukealert-paper/src/main/java/com/untamedears/jukealert/model/actions/abstr/PlayerAction.java @@ -26,7 +26,7 @@ public UUID getPlayer() { @Override public boolean hasPlayer() { - return true; + return player != null; } protected String getFormattedTime( diff --git a/plugins/jukealert-paper/src/main/resources/config.yml b/plugins/jukealert-paper/src/main/resources/config.yml index 8b85d133a3..76c6bae83c 100644 --- a/plugins/jukealert-paper/src/main/resources/config.yml +++ b/plugins/jukealert-paper/src/main/resources/config.yml @@ -17,6 +17,7 @@ snitchConfigs: ==: org.bukkit.inventory.ItemStack type: NOTE_BLOCK amount: 1 + v: 3839 id: 0 name: Snitch range: @@ -38,6 +39,7 @@ snitchConfigs: ==: org.bukkit.inventory.ItemStack type: JUKEBOX amount: 1 + v: 3839 id: 1 name: Logsnitch range: 11 diff --git a/plugins/kirabukkitgateway-paper/build.gradle.kts b/plugins/kirabukkitgateway-paper/build.gradle.kts index 8706a3dabf..a4fa7d830d 100644 --- a/plugins/kirabukkitgateway-paper/build.gradle.kts +++ b/plugins/kirabukkitgateway-paper/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - id("io.papermc.paperweight.userdev") - id("com.github.johnrengelman.shadow") + alias(libs.plugins.paper.userdev) + alias(libs.plugins.shadow) } version = "2.0.3" diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/ConfigParser.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/ConfigParser.java index ecb63d0120..f7d8959fd8 100644 --- a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/ConfigParser.java +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/ConfigParser.java @@ -89,4 +89,7 @@ public List getConsoleProcessors() { return result; } + public String getServerName() { + return config.getString("server-name"); + } } diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/KiraBukkitGatewayPlugin.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/KiraBukkitGatewayPlugin.java index 7011d7b0d2..91c12b84f2 100644 --- a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/KiraBukkitGatewayPlugin.java +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/KiraBukkitGatewayPlugin.java @@ -1,9 +1,7 @@ package com.github.maxopoly.KiraBukkitGateway; -import com.github.maxopoly.KiraBukkitGateway.auth.AuthcodeManager; import com.github.maxopoly.KiraBukkitGateway.command.CreateDiscordGroupChatCommand; import com.github.maxopoly.KiraBukkitGateway.command.DeleteDiscordGroupChatCommand; -import com.github.maxopoly.KiraBukkitGateway.command.GenerateDiscordAuthCodeCommand; import com.github.maxopoly.KiraBukkitGateway.command.ReloadKiraCommand; import com.github.maxopoly.KiraBukkitGateway.command.SyncDiscordChannelAccessCommand; import com.github.maxopoly.KiraBukkitGateway.impersonation.KiraLuckPermsWrapper; @@ -29,7 +27,6 @@ public class KiraBukkitGatewayPlugin extends ACivMod { private RabbitHandler rabbit; private RabbitCommands rabbitCommands; - private AuthcodeManager authcodeManager; private KiraLuckPermsWrapper permsWrapper; private ConfigParser config; private List logAppenders; @@ -38,7 +35,6 @@ public class KiraBukkitGatewayPlugin extends ACivMod { public void onEnable() { super.onEnable(); instance = this; - authcodeManager = new AuthcodeManager(12); reload(); setupPermissions(); this.permsWrapper = new KiraLuckPermsWrapper(); @@ -54,7 +50,6 @@ public void onEnable() { private void registerCommands() { commandManager.registerCommand(new CreateDiscordGroupChatCommand()); commandManager.registerCommand(new DeleteDiscordGroupChatCommand()); - commandManager.registerCommand(new GenerateDiscordAuthCodeCommand()); commandManager.registerCommand(new ReloadKiraCommand()); commandManager.registerCommand(new SyncDiscordChannelAccessCommand()); } @@ -65,13 +60,13 @@ public void reload() { rabbit.shutdown(); } rabbit = new RabbitHandler(config.getRabbitConfig(), config.getIncomingQueueName(), - config.getOutgoingQueueName(), getLogger()); + config.getOutgoingQueueName(), config.getServerName(), getLogger()); if (!rabbit.setup()) { Bukkit.getPluginManager().disablePlugin(this); return; } rabbit.beginAsyncListen(); - rabbitCommands = new RabbitCommands(rabbit); + rabbitCommands = new RabbitCommands(rabbit, config.getServerName()); Logger logger = (Logger) LogManager.getRootLogger(); if (logAppenders != null) { for (KiraLogAppender appender : logAppenders) { @@ -102,10 +97,6 @@ public static KiraBukkitGatewayPlugin getInstance() { return instance; } - public AuthcodeManager getAuthcodeManager() { - return authcodeManager; - } - public RabbitCommands getRabbit() { return rabbitCommands; } diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/command/GenerateDiscordAuthCodeCommand.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/command/GenerateDiscordAuthCodeCommand.java deleted file mode 100644 index f19e76ff62..0000000000 --- a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/command/GenerateDiscordAuthCodeCommand.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.github.maxopoly.KiraBukkitGateway.command; - -import co.aikar.commands.BaseCommand; -import co.aikar.commands.annotation.CommandAlias; -import co.aikar.commands.annotation.Description; -import com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin; -import com.github.maxopoly.KiraBukkitGateway.rabbit.RabbitCommands; -import org.bukkit.ChatColor; -import org.bukkit.entity.Player; - -public class GenerateDiscordAuthCodeCommand extends BaseCommand { - - @CommandAlias("discordauth") - @Description("Create an auth code for linking your ingame account to your Discord account") - public void execute(Player sender) { - Player p = (Player) sender; - String code = KiraBukkitGatewayPlugin.getInstance().getAuthcodeManager().getNewCode(); - if (code == null) { - sender.sendMessage(ChatColor.RED + "Failed to generate code. You should probably tell an admin about this"); - return; - } - // lets make the code upper case to make it easier on people - code = code.toUpperCase(); - RabbitCommands rabbit = KiraBukkitGatewayPlugin.getInstance().getRabbit(); - rabbit.sendAuthCode(code, p.getName(), p.getUniqueId()); - sender.sendMessage(String.format( - "%sYour code is '%s'. Execute '/auth %s' in the official discord to authenticate and link your account. Note that upper/lower case does not matter.", - ChatColor.GOLD, code, code)); - } -} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/KiraLuckPermsWrapper.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/KiraLuckPermsWrapper.java index bb62da0dc8..0c5fcc3c1c 100644 --- a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/KiraLuckPermsWrapper.java +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/KiraLuckPermsWrapper.java @@ -17,8 +17,15 @@ public KiraLuckPermsWrapper() { api = LuckPermsProvider.get(); } - public boolean hasPermission(UUID uuid, String permission) { + public User loadUser(UUID uuid) { User user = api.getUserManager().getUser(uuid); + if (user != null) { + return user; + } + return api.getUserManager().loadUser(uuid).join(); + } + + public boolean hasPermission(User user, String permission) { if (user == null) { return false; } diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/PseudoPlayer.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/PseudoPlayer.java index fa149b6386..8b862e8b06 100644 --- a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/PseudoPlayer.java +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/impersonation/PseudoPlayer.java @@ -2,13 +2,17 @@ import com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin; import java.net.InetSocketAddress; -import java.util.Arrays; import java.util.Collection; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.function.Consumer; +import net.kyori.adventure.audience.MessageType; +import net.kyori.adventure.identity.Identity; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import net.luckperms.api.model.user.User; import org.bukkit.Bukkit; import org.bukkit.Effect; import org.bukkit.EntityEffect; @@ -67,36 +71,47 @@ public class PseudoPlayer extends CraftPlayer { private String name; private UUID uuid; private OfflinePlayer offlinePlayer; - private List replies; private long discordChannelId; private PseudoSpigotPlayer spigotPlayer; + private Consumer feedback; - public PseudoPlayer(UUID uuid, long channelId) { + private final User user; + + public PseudoPlayer(UUID uuid, long channelId, Consumer feedback) { super((CraftServer) Bukkit.getServer(), PseudoPlayerIdentity.generate(uuid, "")); if (uuid == null) { throw new IllegalArgumentException("No null uuid allowed"); } offlinePlayer = Bukkit.getOfflinePlayer(uuid); + user = KiraBukkitGatewayPlugin.getInstance().getPermsWrapper().loadUser(uuid); if (offlinePlayer == null) { - throw new IllegalArgumentException("No such player known: " + uuid.toString()); + throw new IllegalArgumentException("No such player known: " + uuid); } name = offlinePlayer.getName(); this.discordChannelId = channelId; this.uuid = uuid; this.spigotPlayer = new PseudoSpigotPlayer(this); - replies = new LinkedList<>(); - } - - public synchronized List collectReplies() { - List replyCopy = replies; - replies = null; - return replyCopy; + this.feedback = feedback; } public OfflinePlayer getOfflinePlayer() { return offlinePlayer; } + public void sendMessage(String message) { + this.sendMessage(LegacyComponentSerializer.legacySection().deserialize(message)); + } + + public void sendMessage(String... messages) { + for (String message : messages) { + this.sendMessage(message); + } + } + + public void sendMessage(Identity identity, Component message, MessageType type) { + this.feedback.accept(message); + } + public void closeInventory() { throw new InvalidCommandAttemptException(); } @@ -616,23 +631,6 @@ public void setMetadata(String arg0, MetadataValue arg1) { throw new InvalidCommandAttemptException(); } - - public synchronized void sendMessage(String msg) { - if (replies == null) { - KiraBukkitGatewayPlugin.getInstance().getRabbit().replyToUser(uuid, msg, discordChannelId); - } else { - replies.add(msg); - } - } - - - public void sendMessage(String[] arg0) { - StringBuilder sb = new StringBuilder(); - Arrays.stream(arg0).forEach(s -> sb.append(s + '\n')); - sendMessage(sb.toString()); - } - - public PermissionAttachment addAttachment(Plugin arg0) { throw new InvalidCommandAttemptException(); } @@ -659,12 +657,12 @@ public Set getEffectivePermissions() { public boolean hasPermission(String arg0) { - return KiraBukkitGatewayPlugin.getInstance().getPermsWrapper().hasPermission(uuid, arg0); + return KiraBukkitGatewayPlugin.getInstance().getPermsWrapper().hasPermission(user, arg0); } public boolean hasPermission(Permission arg0) { - return KiraBukkitGatewayPlugin.getInstance().getPermsWrapper().hasPermission(uuid, arg0.getName()); + return KiraBukkitGatewayPlugin.getInstance().getPermsWrapper().hasPermission(user, arg0.getName()); } @@ -1158,11 +1156,6 @@ public void sendBlockChange(Location arg0, Material arg1, byte arg2) { } - public boolean sendChunkChange(Location arg0, int arg1, int arg2, int arg3, byte[] arg4) { - throw new InvalidCommandAttemptException(); - } - - public void sendMap(MapView arg0) { throw new InvalidCommandAttemptException(); } diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/RabbitCommands.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/RabbitCommands.java index d430f023ee..11a866c4b4 100644 --- a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/RabbitCommands.java +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/RabbitCommands.java @@ -12,19 +12,12 @@ public class RabbitCommands { - private RabbitHandler internal; + private final RabbitHandler internal; + private final String serverName; - public RabbitCommands(RabbitHandler internalRabbit) { + public RabbitCommands(RabbitHandler internalRabbit, String serverName) { this.internal = internalRabbit; - } - - public void sendAuthCode(String code, String playerName, UUID playerUUID) { - nonNullArgs(code, playerName, playerUUID); - JsonObject json = new JsonObject(); - json.addProperty("uuid", playerUUID.toString()); - json.addProperty("name", playerName); - json.addProperty("code", code); - sendInternal("addauth", json); + this.serverName = serverName; } public void sendGroupChatMessage(String group, String sender, String msg) { @@ -85,6 +78,7 @@ public void createGroupChatChannel(String group, Collection members, UUID json.addProperty("creator", creator.toString()); json.addProperty("guildID", guildID); json.addProperty("channelID", channelID); + json.addProperty("server", serverName); JsonArray array = new JsonArray(); members.forEach(uuid -> array.add(uuid.toString())); json.add("members", array); @@ -99,22 +93,6 @@ public void deleteGroupChatChannel(String group, UUID sender) { sendInternal("deletegroupchat", json); } - public void removeGroupMember(String group, UUID member) { - nonNullArgs(group, member); - JsonObject json = new JsonObject(); - json.addProperty("group", group); - json.addProperty("member", member.toString()); - sendInternal("removegroupmember", json); - } - - public void addGroupMember(String group, UUID member) { - nonNullArgs(group, member); - JsonObject json = new JsonObject(); - json.addProperty("group", group); - json.addProperty("member", member.toString()); - sendInternal("addgroupmember", json); - } - public void replyToUser(UUID user, String msg, long channelId) { nonNullArgs(user, msg); JsonObject json = new JsonObject(); @@ -147,6 +125,7 @@ public void sendSnitchHit(Player victim, Location location, String snitchName, S private void sendInternal(String id, JsonObject json) { json.addProperty("timestamp", System.currentTimeMillis()); json.addProperty("packettype", id); + json.addProperty("server", serverName); Gson gson = new Gson(); String payload = gson.toJson(json); internal.sendMessage(payload); diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/RabbitHandler.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/RabbitHandler.java index c6b4aa3f25..438bdfdf9d 100644 --- a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/RabbitHandler.java +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/RabbitHandler.java @@ -19,12 +19,14 @@ public class RabbitHandler { private Connection conn; private Channel incomingChannel; private Channel outgoingChannel; + private final String serverName; private RabbitInputHandler inputProcessor; - public RabbitHandler(ConnectionFactory connFac, String incomingQueue, String outgoingQueue, Logger logger) { + public RabbitHandler(ConnectionFactory connFac, String incomingQueue, String outgoingQueue, String serverName, Logger logger) { this.connectionFactory = connFac; this.incomingQueue = incomingQueue; this.outgoingQueue = outgoingQueue; + this.serverName = serverName; this.logger = logger; inputProcessor = new RabbitInputHandler(logger); } @@ -34,7 +36,7 @@ public boolean setup() { conn = connectionFactory.newConnection(); incomingChannel = conn.createChannel(); outgoingChannel = conn.createChannel(); - incomingChannel.queueDeclare(incomingQueue, false, false, false, null); + incomingChannel.exchangeDeclare(incomingQueue, "direct", false, false, null); outgoingChannel.queueDeclare(outgoingQueue, false, false, false, null); return true; } catch (IOException | TimeoutException e) { @@ -51,7 +53,7 @@ public void run() { DeliverCallback deliverCallback = (consumerTag, delivery) -> { try { String message = new String(delivery.getBody(), "UTF-8"); - System.out.println(" [x] Received '" + message + "'"); + logger.info(" [x] Received '" + message + "'"); inputProcessor.handle(message); } catch (Exception e) { logger.severe("Exception in rabbit handling: " + e.toString()); @@ -59,7 +61,9 @@ public void run() { } }; try { - incomingChannel.basicConsume(incomingQueue, true, deliverCallback, consumerTag -> { + String queue = incomingChannel.queueDeclare().getQueue(); + incomingChannel.queueBind(queue, incomingQueue, serverName); + incomingChannel.basicConsume(queue, true, deliverCallback, consumerTag -> { }); } catch (IOException e) { logger.severe("Error in rabbit listener: " + e.toString()); diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/in/GroupChatMessageHandler.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/in/GroupChatMessageHandler.java index 74c62778b3..56aaba3e9e 100644 --- a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/in/GroupChatMessageHandler.java +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/in/GroupChatMessageHandler.java @@ -5,6 +5,7 @@ import com.github.maxopoly.KiraBukkitGateway.rabbit.RabbitInput; import com.google.gson.JsonObject; import java.util.UUID; +import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import vg.civcraft.mc.civchat2.CivChat2; import vg.civcraft.mc.namelayer.GroupManager; @@ -21,7 +22,7 @@ public void handle(final JsonObject input) { final var groupName = input.get("group").getAsString(); final var message = input.get("message").getAsString(); - final var fakeSender = new PseudoPlayer(senderUUID, -1); + final var fakeSender = new PseudoPlayer(senderUUID, -1, null); final var logger = KiraBukkitGatewayPlugin.getInstance().getLogger(); final var foundGroup = GroupManager.getGroup(groupName); @@ -37,7 +38,7 @@ public void handle(final JsonObject input) { } Bukkit.getScheduler().scheduleSyncDelayedTask(KiraBukkitGatewayPlugin.getInstance(), () -> { - CivChat2.getInstance().getCivChat2Manager().sendGroupMsg(fakeSender, foundGroup, message); + CivChat2.getInstance().getCivChat2Manager().sendGroupMsg(fakeSender, foundGroup, Component.text(message)); }); } diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/AbstractRequestHandler.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/AbstractRequestHandler.java index 6d2f15862f..3921bd03b2 100644 --- a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/AbstractRequestHandler.java +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/AbstractRequestHandler.java @@ -2,6 +2,9 @@ import com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin; import com.google.gson.JsonObject; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.CraftServer; public abstract class AbstractRequestHandler { @@ -27,4 +30,8 @@ protected void sendRequestSessionReply(JsonObject json) { KiraBukkitGatewayPlugin.getInstance().getRabbit().replyToRequestSession(json); } + protected boolean dispatchCommand(CommandSender rawSender, String commandLine) { + CraftServer server = (CraftServer) Bukkit.getServer(); + return server.getCommandMap().dispatch(rawSender, commandLine); + } } diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/ConsoleCommandHandler.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/ConsoleCommandHandler.java index ad0bd2b209..d4b2202e26 100644 --- a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/ConsoleCommandHandler.java +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/ConsoleCommandHandler.java @@ -21,12 +21,6 @@ public void handle(JsonObject input, JsonObject output, long channelId) { String command = input.get("command").getAsString(); OfflinePlayer player = Bukkit.getOfflinePlayer(sender); Logger logger = KiraBukkitGatewayPlugin.getInstance().getLogger(); - if (player == null) { - logger.warning("Console player with uuid " + sender + " does not exist"); - output.addProperty("replymsg", "You are not a known player on the server"); - sendRequestSessionReply(output); - return; - } if (!player.isOp()) { logger.warning("Non op player " + sender + " tried to run console command"); output.addProperty("replymsg", "You are not op"); @@ -35,7 +29,7 @@ public void handle(JsonObject input, JsonObject output, long channelId) { } PseudoConsoleSender console = new PseudoConsoleSender(sender, Bukkit.getConsoleSender(), channelId); Bukkit.getScheduler().runTask(KiraBukkitGatewayPlugin.getInstance(), () -> { - Bukkit.getServer().dispatchCommand(console, command); + dispatchCommand(console, command); StringBuilder sb = new StringBuilder(); for (String s : console.getRepliesAndFinish()) { sb.append(KiraUtil.cleanUp(s)); diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/IngameCommandHandler.java b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/IngameCommandHandler.java index 550552c9a2..7502132b9f 100644 --- a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/IngameCommandHandler.java +++ b/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/rabbit/request/IngameCommandHandler.java @@ -3,9 +3,10 @@ import com.github.maxopoly.KiraBukkitGateway.KiraBukkitGatewayPlugin; import com.github.maxopoly.KiraBukkitGateway.impersonation.PseudoPlayer; import com.google.gson.JsonObject; -import java.util.List; +import java.util.ArrayList; import java.util.UUID; import java.util.logging.Logger; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.bukkit.Bukkit; public class IngameCommandHandler extends AbstractRequestHandler { @@ -19,11 +20,12 @@ public void handle(JsonObject input, JsonObject output, long channelId) { UUID runner = UUID.fromString(input.get("uuid").getAsString()); String command = input.get("command").getAsString(); Logger logger = KiraBukkitGatewayPlugin.getInstance().getLogger(); - logger.info("Running command '" + command + "' for " + runner.toString()); + logger.info("Running command '" + command + "' for " + runner); Bukkit.getScheduler().runTask(KiraBukkitGatewayPlugin.getInstance(), () -> { - PseudoPlayer player = new PseudoPlayer(runner, channelId); + ArrayList messages = new ArrayList<>(); + PseudoPlayer pseudoPlayerSender = new PseudoPlayer(runner, channelId, component -> messages.add(PlainTextComponentSerializer.plainText().serialize(component))); try { - Bukkit.getServer().dispatchCommand(player, command); + dispatchCommand(pseudoPlayerSender, command); } catch (Exception e) { output.addProperty("reply", "You can not run this command from out of game"); logger.warning("Failed to run command from external source: " + e.getMessage()); @@ -31,9 +33,8 @@ public void handle(JsonObject input, JsonObject output, long channelId) { sendRequestSessionReply(output); return; } - List replies = player.collectReplies(); StringBuilder sb = new StringBuilder(); - for (String reply : replies) { + for (String reply : messages) { sb.append(reply); sb.append('\n'); } @@ -42,5 +43,4 @@ public void handle(JsonObject input, JsonObject output, long channelId) { }); } - } diff --git a/plugins/kirabukkitgateway-paper/src/main/resources/config.yml b/plugins/kirabukkitgateway-paper/src/main/resources/config.yml index ed5fd3bbcd..1d709acf19 100644 --- a/plugins/kirabukkitgateway-paper/src/main/resources/config.yml +++ b/plugins/kirabukkitgateway-paper/src/main/resources/config.yml @@ -33,3 +33,5 @@ console: bar: key: ip regex: '.*is brand new!.*' + +server-name: "main" diff --git a/plugins/kiragateway-velocity/build.gradle.kts b/plugins/kiragateway-velocity/build.gradle.kts new file mode 100644 index 0000000000..bb3c13ddf4 --- /dev/null +++ b/plugins/kiragateway-velocity/build.gradle.kts @@ -0,0 +1,16 @@ +import org.gradle.api.tasks.Copy + +plugins { + alias(libs.plugins.shadow) +} + +version = "1.0" + +dependencies { + compileOnly(libs.velocity.api) + annotationProcessor(libs.velocity.api) + + implementation(libs.configurate.yaml) + api(libs.rabbitmq.client) + compileOnly(libs.luckperms.api) +} diff --git a/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/Config.java b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/Config.java new file mode 100644 index 0000000000..47763f629b --- /dev/null +++ b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/Config.java @@ -0,0 +1,84 @@ +package net.civmc.kiragatewayvelocity; + +import com.rabbitmq.client.ConnectionFactory; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.configurate.CommentedConfigurationNode; +import org.spongepowered.configurate.yaml.YamlConfigurationLoader; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; + +public class Config { + + /** + * Loads the config from disk, and creates it if necessary + */ + public static @Nullable CommentedConfigurationNode loadConfig() { + KiraGateway plugin = KiraGateway.getInstance(); + + try { + // ensure data directory exists + if (!Files.exists(plugin.dataDirectory)) { + Files.createDirectories(plugin.dataDirectory); + } + } catch (IOException e) { + plugin.logger.error("Could not create data directory at {}", plugin.dataDirectory); + plugin.logger.error("Failed to create data directory", e); + return null; + } + + // create config file if it doesn't exist + Path configFile = plugin.dataDirectory.resolve("config.yml"); + if (!Files.exists(configFile)) { + try (InputStream in = plugin.getClass().getResourceAsStream("/config.yml")) { + if (in != null) { + Files.copy(in, configFile); + plugin.logger.info("Default configuration file created."); + } else { + plugin.logger.error("Default configuration file is missing in resources!"); + return null; + } + } catch (IOException e) { + plugin.logger.error("Could not create default configuration file at {}", configFile); + plugin.logger.error("Failed to create default config", e); + } + } + + YamlConfigurationLoader loader = YamlConfigurationLoader.builder().path(configFile).build(); + try { + return loader.load(); + } catch (IOException e) { + throw new RuntimeException("Could not load configuration file: " + configFile, e); + } + } + + /** + * Reads the RabbitMQ configuration from the config file + * @return + */ + public static @Nullable ConnectionFactory getRabbitConfig(CommentedConfigurationNode config) { + if (config == null) { + return null; + } + + ConnectionFactory connFac = new ConnectionFactory(); + var user = config.node("rabbitmq", "user"); + if (!user.empty()) { + connFac.setUsername(user.getString()); + } + var password = config.node("rabbitmq", "password"); + if (!password.empty()) { + connFac.setPassword(password.getString()); + } + var host = config.node("rabbitmq", "host"); + if (!host.empty()) { + connFac.setHost(host.getString()); + } + var port = config.node("rabbitmq", "port"); + if (!port.empty() && port.getInt(-1) != -1) { + connFac.setPort(port.getInt()); + } + return connFac; + } +} diff --git a/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/KiraGateway.java b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/KiraGateway.java new file mode 100644 index 0000000000..a0b086c846 --- /dev/null +++ b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/KiraGateway.java @@ -0,0 +1,111 @@ +package net.civmc.kiragatewayvelocity; + +import com.google.inject.Inject; +import com.velocitypowered.api.command.CommandManager; +import com.velocitypowered.api.command.CommandMeta; +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; +import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; +import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.plugin.annotation.DataDirectory; +import com.velocitypowered.api.proxy.ProxyServer; +import java.nio.file.Path; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.spongepowered.configurate.CommentedConfigurationNode; +import net.civmc.kiragatewayvelocity.auth.AuthcodeManager; +import net.civmc.kiragatewayvelocity.commands.DiscordAuth; +import net.civmc.kiragatewayvelocity.rabbit.RabbitCommands; +import net.civmc.kiragatewayvelocity.rabbit.RabbitHandler; + +@Plugin( + id = "kiragateway", + name = "kiragateway", + version = "1.0", + url = "https://civmc.net", + authors = {"Huskydog9988"} +) +public class KiraGateway { + + public static final String PROXY_SERVER_NAME = "proxy"; + + private final ProxyServer proxy; + public final Logger logger; + public final Path dataDirectory; + + private static KiraGateway instance; + private AuthcodeManager authcodeManager; + private RabbitHandler rabbitHandler; + private RabbitCommands rabbitCommands; + private @Nullable CommentedConfigurationNode config; + + @Inject + public KiraGateway(ProxyServer proxy, Logger logger, @DataDirectory Path dataDirectory) { + this.proxy = proxy; + this.logger = logger; + this.dataDirectory = dataDirectory; + } + + @Subscribe + public void onProxyInitialization(ProxyInitializeEvent event) { + instance = this; + + config = Config.loadConfig(); + if (config == null) { + logger.error("Failed to load configuration"); + return; + } + + authcodeManager = new AuthcodeManager(12); + rabbitHandler = new RabbitHandler( + Config.getRabbitConfig(config), + config.node("rabbitmq", "incomingQueue").getString(), + config.node("rabbitmq", "outgoingQueue").getString(), + logger + ); + if (!rabbitHandler.setup()) { + logger.error("Failed to setup rabbitmq"); + return; + } + rabbitHandler.beginAsyncListen(); + rabbitCommands = new RabbitCommands(rabbitHandler); + + registerCommands(); + } + + @Subscribe + public void onProxyShutdown(ProxyShutdownEvent event) { + rabbitHandler.shutdown(); + } + + private void registerCommands() { + CommandManager commandManager = proxy.getCommandManager(); + + // discordauth command + CommandMeta discordAuthMeta = commandManager.metaBuilder("discordauth") + // .aliases("auth") + .plugin(this) + .build(); + commandManager.register(discordAuthMeta, new DiscordAuth()); + } + + public AuthcodeManager getAuthcodeManager() { + return authcodeManager; + } + + public RabbitCommands getRabbit() { + return rabbitCommands; + } + + public static KiraGateway getInstance() { + return instance; + } + + public ProxyServer getProxy() { + return proxy; + } + + public CommentedConfigurationNode getConfig() { + return config; + } +} diff --git a/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/KiraUtil.java b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/KiraUtil.java new file mode 100644 index 0000000000..da6c4e9f57 --- /dev/null +++ b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/KiraUtil.java @@ -0,0 +1,9 @@ +package net.civmc.kiragatewayvelocity; + +public class KiraUtil { + + public static String cleanUp(String s) { + return s.replaceAll("[^\\p{InBasic_Latin}\\p{InLatin-1Supplement}]", ""); + } + +} diff --git a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/auth/AuthcodeManager.java b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/auth/AuthcodeManager.java similarity index 88% rename from plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/auth/AuthcodeManager.java rename to plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/auth/AuthcodeManager.java index ee99fa8842..6203870239 100644 --- a/plugins/kirabukkitgateway-paper/src/main/java/com/github/maxopoly/KiraBukkitGateway/auth/AuthcodeManager.java +++ b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/auth/AuthcodeManager.java @@ -1,4 +1,4 @@ -package com.github.maxopoly.KiraBukkitGateway.auth; +package net.civmc.kiragatewayvelocity.auth; import java.security.SecureRandom; import java.util.HashSet; @@ -6,7 +6,7 @@ public class AuthcodeManager { - private static final String validCharacters = "abcdefghijklmnopqrstuvwxyz0123456789"; + private static final String validCharacters = "abcdefghjkmnpqrstuvwxyz123456789"; private static final int maxRecursionDepth = 20; private Set currentCodes; diff --git a/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/commands/DiscordAuth.java b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/commands/DiscordAuth.java new file mode 100644 index 0000000000..c97461945e --- /dev/null +++ b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/commands/DiscordAuth.java @@ -0,0 +1,35 @@ +package net.civmc.kiragatewayvelocity.commands; + +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.command.SimpleCommand; +import com.velocitypowered.api.proxy.Player; +import net.civmc.kiragatewayvelocity.KiraGateway; +import net.civmc.kiragatewayvelocity.rabbit.RabbitCommands; +import net.kyori.adventure.text.minimessage.MiniMessage; + +public final class DiscordAuth implements SimpleCommand { + + @Override + public void execute(final Invocation invocation) { + CommandSource source = invocation.source(); + var mm = MiniMessage.miniMessage(); + + if (source instanceof Player player) { + + String code = KiraGateway.getInstance().getAuthcodeManager().getNewCode(); + if (code == null) { + player.sendMessage(mm.deserialize("Failed to generate auth code. You should probably tell an admin about this")); + return; + } + // lets make the code upper case to make it easier on people + code = code.toUpperCase(); + RabbitCommands rabbit = KiraGateway.getInstance().getRabbit(); + rabbit.sendAuthCode(code, player.getUsername(), player.getUniqueId()); + source.sendMessage(mm.deserialize(String.format( + "Your code is '%s'. Execute '/auth %s' in the official discord to authenticate and link your account. Note that upper/lower case does not matter.", + code, code, code))); + } else { + source.sendMessage(mm.deserialize("This command can only be run by a player.")); + } + } +} diff --git a/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/impersonation/PseudoConsoleSender.java b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/impersonation/PseudoConsoleSender.java new file mode 100644 index 0000000000..393d88cd58 --- /dev/null +++ b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/impersonation/PseudoConsoleSender.java @@ -0,0 +1,52 @@ +package net.civmc.kiragatewayvelocity.impersonation; + +import com.velocitypowered.api.permission.Tristate; +import com.velocitypowered.api.proxy.ConsoleCommandSource; +import net.civmc.kiragatewayvelocity.KiraGateway; +import net.kyori.adventure.audience.MessageType; +import net.kyori.adventure.identity.Identity; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class PseudoConsoleSender implements ConsoleCommandSource { + + private List replies = new ArrayList<>(); + private final UUID actualUser; + private final long discordChannelId; + + public PseudoConsoleSender(UUID actualUser, long discordChannelId) { + this.actualUser = actualUser; + this.discordChannelId = discordChannelId; + } + + public synchronized List getRepliesAndFinish() { + List result = replies; + replies = null; + return result; + } + + private synchronized void handleReply(String input) { + if (replies != null) { + replies.add(input); + return; + } + KiraGateway.getInstance().getRabbit().replyToUser(actualUser, input, discordChannelId); + } + + /** + * @noinspection UnstableApiUsage + */ + @Override + public void sendMessage(@NotNull Identity identity, @NotNull Component message, @NotNull MessageType type) { + handleReply(PlainTextComponentSerializer.plainText().serialize(message)); + } + + @Override + public Tristate getPermissionValue(String permission) { + return Tristate.TRUE; + } +} diff --git a/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/rabbit/AbstractRequestHandler.java b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/rabbit/AbstractRequestHandler.java new file mode 100644 index 0000000000..b6b44adeb7 --- /dev/null +++ b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/rabbit/AbstractRequestHandler.java @@ -0,0 +1,36 @@ +package net.civmc.kiragatewayvelocity.rabbit; + +import com.google.gson.JsonObject; +import com.velocitypowered.api.command.CommandSource; +import com.velocitypowered.api.proxy.ProxyServer; +import net.civmc.kiragatewayvelocity.KiraGateway; +import java.util.concurrent.CompletableFuture; + +public abstract class AbstractRequestHandler { + + private String identifier; + private boolean async; + + public AbstractRequestHandler(String identifier, boolean isAsync) { + this.identifier = identifier; + this.async = isAsync; + } + + public String getIdentifier() { + return identifier; + } + + public boolean isAsync() { + return async; + } + + public abstract void handle(JsonObject input, JsonObject output, long channelId); + + protected void sendRequestSessionReply(JsonObject json) { + KiraGateway.getInstance().getRabbit().replyToRequestSession(json); + } + + protected CompletableFuture dispatchCommand(ProxyServer server, CommandSource rawSender, String commandLine) { + return server.getCommandManager().executeAsync(rawSender, commandLine); + } +} diff --git a/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/rabbit/ConsoleCommandHandler.java b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/rabbit/ConsoleCommandHandler.java new file mode 100644 index 0000000000..12d1ec26f0 --- /dev/null +++ b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/rabbit/ConsoleCommandHandler.java @@ -0,0 +1,50 @@ +package net.civmc.kiragatewayvelocity.rabbit; + +import com.google.gson.JsonObject; +import java.util.UUID; +import net.civmc.kiragatewayvelocity.KiraGateway; +import net.civmc.kiragatewayvelocity.KiraUtil; +import net.civmc.kiragatewayvelocity.impersonation.PseudoConsoleSender; +import org.slf4j.Logger; +import org.spongepowered.configurate.serialize.SerializationException; + +public class ConsoleCommandHandler extends AbstractRequestHandler { + + public ConsoleCommandHandler() { + super("consolemessageop", true); + } + + @Override + public void handle(JsonObject input, JsonObject output, long channelId) { + UUID sender = UUID.fromString(input.get("sender").getAsString()); + String command = input.get("command").getAsString(); + Logger logger = KiraGateway.getInstance().getInstance().logger; + try { + boolean isOp = false; + for (String op : KiraGateway.getInstance().getConfig().node("ops").getList(String.class)) { + if (op.equals(sender.toString())) { + isOp = true; + break; + } + } + if (!isOp) { + logger.warn("Non op player " + sender + " tried to run console command"); + output.addProperty("replymsg", "You are not op"); + sendRequestSessionReply(output); + return; + } + } catch (SerializationException e) { + throw new RuntimeException(e); + } + PseudoConsoleSender console = new PseudoConsoleSender(sender, channelId); + dispatchCommand(KiraGateway.getInstance().getProxy(), console, command).thenAccept(ignored -> { + StringBuilder sb = new StringBuilder(); + for (String s : console.getRepliesAndFinish()) { + sb.append(KiraUtil.cleanUp(s)); + sb.append('\n'); + } + output.addProperty("replymsg", sb.toString()); + sendRequestSessionReply(output); + }); + } +} diff --git a/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/rabbit/RabbitCommands.java b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/rabbit/RabbitCommands.java new file mode 100644 index 0000000000..4e5d80d47c --- /dev/null +++ b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/rabbit/RabbitCommands.java @@ -0,0 +1,56 @@ +package net.civmc.kiragatewayvelocity.rabbit; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import java.util.UUID; +import net.civmc.kiragatewayvelocity.KiraGateway; +import net.civmc.kiragatewayvelocity.KiraUtil; + +public class RabbitCommands { + + private final RabbitHandler internal; + + public RabbitCommands(RabbitHandler internalRabbit) { + this.internal = internalRabbit; + } + + public void sendAuthCode(String code, String playerName, UUID playerUUID) { + nonNullArgs(code, playerName, playerUUID); + JsonObject json = new JsonObject(); + json.addProperty("uuid", playerUUID.toString()); + json.addProperty("name", playerName); + json.addProperty("code", code); + sendInternal("addauth", json); + } + + private void sendInternal(String id, JsonObject json) { + json.addProperty("server", KiraGateway.PROXY_SERVER_NAME); + json.addProperty("timestamp", System.currentTimeMillis()); + json.addProperty("packettype", id); + Gson gson = new Gson(); + String payload = gson.toJson(json); + internal.sendMessage(payload); + } + + private void nonNullArgs(Object... objects) { + for (Object o : objects) { + if (o == null) { + throw new IllegalArgumentException("Arguments cant be null"); + } + } + } + + public void replyToUser(UUID user, String msg, long channelId) { + nonNullArgs(user, msg); + JsonObject json = new JsonObject(); + json.addProperty("user", user.toString()); + json.addProperty("msg", KiraUtil.cleanUp(msg)); + json.addProperty("channel", channelId); + sendInternal("replytouser", json); + } + + public void replyToRequestSession(JsonObject json) { + nonNullArgs(json); + sendInternal("requestsession", json); + } +} diff --git a/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/rabbit/RabbitHandler.java b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/rabbit/RabbitHandler.java new file mode 100644 index 0000000000..a751245711 --- /dev/null +++ b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/rabbit/RabbitHandler.java @@ -0,0 +1,87 @@ +package net.civmc.kiragatewayvelocity.rabbit; + +import com.rabbitmq.client.Channel; +import com.rabbitmq.client.Connection; +import com.rabbitmq.client.ConnectionFactory; +import java.io.IOException; +import java.util.concurrent.TimeoutException; +import com.rabbitmq.client.DeliverCallback; +import net.civmc.kiragatewayvelocity.KiraGateway; +import org.slf4j.Logger; + +public class RabbitHandler { + + private ConnectionFactory connectionFactory; + private String incomingQueue; + private String outgoingQueue; + private Logger logger; + private Connection conn; + private Channel incomingChannel; + private Channel outgoingChannel; + private final RabbitInputHandler inputProcessor; + + public RabbitHandler(ConnectionFactory connFac, String incomingQueue, String outgoingQueue, Logger logger) { + this.connectionFactory = connFac; + this.incomingQueue = incomingQueue; + this.outgoingQueue = outgoingQueue; + this.logger = logger; + this.inputProcessor = new RabbitInputHandler(logger); + } + + public boolean setup() { + try { + conn = connectionFactory.newConnection(); + incomingChannel = conn.createChannel(); + outgoingChannel = conn.createChannel(); + incomingChannel.exchangeDeclare(incomingQueue, "direct", false, false, null); + outgoingChannel.queueDeclare(outgoingQueue, false, false, false, null); + return true; + } catch (IOException | TimeoutException e) { + logger.error("Failed to setup rabbit connection", e); + return false; + } + } + + public void beginAsyncListen() { + KiraGateway.getInstance().getProxy().getScheduler().buildTask(KiraGateway.getInstance(), () -> { + DeliverCallback deliverCallback = (consumerTag, delivery) -> { + try { + String message = new String(delivery.getBody(), "UTF-8"); + logger.info(" [x] Received '" + message + "'"); + inputProcessor.handle(message); + } catch (Exception e) { + logger.error("Exception in rabbit handling", e); + } + }; + try { + logger.info("Starting listen"); + String queue = incomingChannel.queueDeclare().getQueue(); + incomingChannel.queueBind(queue, incomingQueue, KiraGateway.PROXY_SERVER_NAME); + incomingChannel.basicConsume(queue, true, deliverCallback, consumerTag -> { + }); + } catch (IOException e) { + logger.error("Error in rabbit listener", e); + } + }).schedule(); + } + + public void shutdown() { + try { + incomingChannel.close(); + outgoingChannel.close(); + conn.close(); + } catch (IOException | TimeoutException e) { + logger.error("Failed to close rabbit connection", e); + } + } + + public boolean sendMessage(String msg) { + try { + outgoingChannel.basicPublish("", outgoingQueue, null, msg.getBytes("UTF-8")); + return true; + } catch (IOException e) { + logger.error("Failed to send rabbit message", e); + return false; + } + } +} diff --git a/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/rabbit/RabbitInput.java b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/rabbit/RabbitInput.java new file mode 100644 index 0000000000..59964eab69 --- /dev/null +++ b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/rabbit/RabbitInput.java @@ -0,0 +1,19 @@ +package net.civmc.kiragatewayvelocity.rabbit; + +import com.google.gson.JsonObject; + +public abstract class RabbitInput { + + private String identifier; + + public RabbitInput(String identifier) { + this.identifier = identifier; + } + + public String getIdentifier() { + return identifier; + } + + public abstract void handle(JsonObject input); + +} diff --git a/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/rabbit/RabbitInputHandler.java b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/rabbit/RabbitInputHandler.java new file mode 100644 index 0000000000..b507f3a8c8 --- /dev/null +++ b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/rabbit/RabbitInputHandler.java @@ -0,0 +1,54 @@ +package net.civmc.kiragatewayvelocity.rabbit; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import java.util.HashMap; +import java.util.Map; +import org.slf4j.Logger; + +public class RabbitInputHandler { + + private Map commands; + private Logger logger; + + public RabbitInputHandler(Logger logger) { + this.commands = new HashMap<>(); + this.logger = logger; + registerCommands(); + } + + private void registerCommands() { + registerCommand(new RequestSessionHandler()); + } + + private void registerCommand(RabbitInput command) { + commands.put(command.getIdentifier().toLowerCase(), command); + } + + public void handle(String input) { + if (input == null || input.equals("")) { + logger.info("Invalid empty input in rabbit handler"); + return; + } + int spaceIndex = input.indexOf(" "); + String arguments; + String command; + if (spaceIndex == -1) { + arguments = ""; + command = input; + } else { + arguments = input.substring(spaceIndex + 1); + command = input.substring(0, spaceIndex); + } + RabbitInput comm = commands.get(command); + if (comm == null) { + logger.error("Received invalid rabbit command: " + input); + return; + } + JsonParser parser = new JsonParser(); + JsonElement json = parser.parse(arguments); + comm.handle((JsonObject) json); + } + +} diff --git a/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/rabbit/RequestSessionHandler.java b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/rabbit/RequestSessionHandler.java new file mode 100644 index 0000000000..b37813b403 --- /dev/null +++ b/plugins/kiragateway-velocity/src/main/java/net/civmc/kiragatewayvelocity/rabbit/RequestSessionHandler.java @@ -0,0 +1,47 @@ +package net.civmc.kiragatewayvelocity.rabbit; + +import com.google.gson.JsonObject; +import net.civmc.kiragatewayvelocity.KiraGateway; +import java.util.Map; +import java.util.TreeMap; + +public class RequestSessionHandler extends RabbitInput { + + private static final String idField = "RequestSessionId"; + private static final String keyField = "RequestSessionKey"; + private static final String channelField = "DiscordChannelKey"; + + private Map handlers; + + public RequestSessionHandler() { + super("requestsession"); + registerHandlers(); + } + + private void registerHandlers() { + handlers = new TreeMap<>(); + registerHandler(new ConsoleCommandHandler()); + } + + private void registerHandler(AbstractRequestHandler handler) { + handlers.put(handler.getIdentifier(), handler); + } + + @Override + public void handle(JsonObject input) { + long id = input.get(idField).getAsLong(); + String type = input.get(keyField).getAsString(); + long channelId = input.has(channelField) ? input.get(channelField).getAsLong() : -1; + JsonObject reply = new JsonObject(); + reply.addProperty(idField, id); + AbstractRequestHandler handler = handlers.get(type); + if (handler == null) { + throw new IllegalArgumentException(type + " is not a valid request"); + } + handler.handle(input, reply, channelId); + if (!handler.isAsync()) { + KiraGateway.getInstance().getRabbit().replyToRequestSession(reply); + } + } + +} diff --git a/plugins/kiragateway-velocity/src/main/resources/config.yml b/plugins/kiragateway-velocity/src/main/resources/config.yml new file mode 100644 index 0000000000..9bc4d75d04 --- /dev/null +++ b/plugins/kiragateway-velocity/src/main/resources/config.yml @@ -0,0 +1,10 @@ +# Your RabbitMQ credentials +rabbitmq: + user: username + password: password + host: localhost + port: 5672 + incomingQueue: kira-to-gateway + outgoingQueue: gateway-to-kira + +ops: [] diff --git a/plugins/kitpvp-paper/build.gradle.kts b/plugins/kitpvp-paper/build.gradle.kts index d01c7321a3..20de300c48 100644 --- a/plugins/kitpvp-paper/build.gradle.kts +++ b/plugins/kitpvp-paper/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - id("io.papermc.paperweight.userdev") - id("com.github.johnrengelman.shadow") + alias(libs.plugins.paper.userdev) + alias(libs.plugins.shadow) } version = "1.0.0" @@ -12,7 +12,9 @@ dependencies { compileOnly(project(":plugins:civmodcore-paper")) compileOnly(project(":plugins:finale-paper")) + compileOnly("net.luckperms:api:5.4") - compileOnly(files("../../ansible/src/paper-plugins/BreweryX-3.4.10.jar")) + compileOnly(files("../../ansible/src/paper-plugins/BreweryX-3.6.0.jar")) compileOnly(libs.aswm.api) + compileOnly("me.clip:placeholderapi:2.11.6") } diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/KitApplier.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/KitApplier.java index a12c29adf5..d9033957a3 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/KitApplier.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/KitApplier.java @@ -2,7 +2,7 @@ import com.dre.brewery.api.BreweryApi; import com.github.maxopoly.finale.Finale; -import net.civmc.kitpvp.data.Kit; +import net.civmc.kitpvp.kit.Kit; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.format.NamedTextColor; diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/KitPvpPlugin.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/KitPvpPlugin.java index c87497be6b..4833acc8bb 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/KitPvpPlugin.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/KitPvpPlugin.java @@ -1,6 +1,7 @@ package net.civmc.kitpvp; import java.sql.SQLException; +import java.util.List; import net.civmc.kitpvp.anvil.AnvilGui; import net.civmc.kitpvp.arena.ArenaCleaner; import net.civmc.kitpvp.arena.ArenaCommand; @@ -8,9 +9,18 @@ import net.civmc.kitpvp.arena.MysqlLoader; import net.civmc.kitpvp.arena.PrivateArenaListener; import net.civmc.kitpvp.arena.RespawnListener; +import net.civmc.kitpvp.arena.data.Arena; import net.civmc.kitpvp.arena.data.SqlArenaDao; import net.civmc.kitpvp.command.ClearCommand; import net.civmc.kitpvp.command.KitCommand; +import net.civmc.kitpvp.ranked.EloCommand; +import net.civmc.kitpvp.ranked.RankedCommand; +import net.civmc.kitpvp.ranked.RankedPlaceholders; +import net.civmc.kitpvp.ranked.RankedPlayers; +import net.civmc.kitpvp.ranked.RankedQueueListener; +import net.civmc.kitpvp.ranked.RankedQueueManager; +import net.civmc.kitpvp.ranked.SqlRankedDao; +import net.civmc.kitpvp.ranked.UnrankedCommand; import net.civmc.kitpvp.snapshot.DeathListener; import net.civmc.kitpvp.snapshot.InventorySnapshotManager; import net.civmc.kitpvp.snapshot.ViewInventorySnapshotCommand; @@ -38,12 +48,15 @@ public void onEnable() { saveDefaultConfig(); DatabaseCredentials credentials = (DatabaseCredentials) getConfig().get("database"); source = ManagedDatasource.construct(this, credentials); - getCommand("kit").setExecutor(new KitCommand(new SqlKitPvpDao(source), anvilGui)); + SqlKitPvpDao dao = new SqlKitPvpDao(source); + SqlRankedDao ranked = new SqlRankedDao(source); + getCommand("kit").setExecutor(new KitCommand(dao, ranked, anvilGui)); new WarpMain(this, source); getCommand("clear").setExecutor(new ClearCommand()); InventorySnapshotManager inventorySnapshotManager = new InventorySnapshotManager(); - getServer().getPluginManager().registerEvents(new DeathListener(inventorySnapshotManager), this); + DeathListener deathListener = new DeathListener(inventorySnapshotManager); + getServer().getPluginManager().registerEvents(deathListener, this); getCommand("viewinventorysnapshot").setExecutor(new ViewInventorySnapshotCommand(inventorySnapshotManager)); if (Bukkit.getPluginManager().isPluginEnabled("BreweryX")) { @@ -58,16 +71,33 @@ public void onEnable() { int maxArenas = getConfig().getInt("max_arenas"); try { ArenaManager manager = new ArenaManager(maxArenas, this, spawnProvider, new MysqlLoader(source)); + SqlArenaDao arenaDao = new SqlArenaDao(source); + source.updateDatabase(); + + List arenas = arenaDao.getArenas(); + Arena rankedArena = null; + for (Arena arena : arenas) { + if (arena.name().equals(getConfig().getString("ranked_arena"))) { + rankedArena = arena; + } + } + + RankedPlayers players = new RankedPlayers(ranked); + RankedQueueManager queueManager = new RankedQueueManager(dao, ranked, manager, spawnProvider, rankedArena, players); + getCommand("ranked").setExecutor(new RankedCommand(queueManager)); + getCommand("unranked").setExecutor(new UnrankedCommand(queueManager)); + getCommand("elo").setExecutor(new EloCommand(players)); + getServer().getPluginManager().registerEvents(new RankedQueueListener(queueManager, deathListener), this); + new RankedPlaceholders(players).register(); + PrivateArenaListener privateArenaListener = new PrivateArenaListener(spawnProvider, manager); getServer().getPluginManager().registerEvents(privateArenaListener, this); - getCommand("arena").setExecutor(new ArenaCommand(this, new SqlArenaDao(source), manager, privateArenaListener)); + getCommand("arena").setExecutor(new ArenaCommand(this, arenaDao, ranked, queueManager, manager, privateArenaListener)); getServer().getPluginManager().registerEvents(new RespawnListener(manager), this); Bukkit.getScheduler().runTaskTimer(this, new ArenaCleaner(manager), 20 * 60, 20 * 60); } catch (SQLException e) { throw new RuntimeException(e); } - - source.updateDatabase(); } @Override diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/ArenaCleaner.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/ArenaCleaner.java index ed2f1af281..ba36745f8f 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/ArenaCleaner.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/ArenaCleaner.java @@ -24,11 +24,14 @@ public ArenaCleaner(ArenaManager arenaManager) { public void run() { try { for (LoadedArena loadedArena : this.arenaManager.getArenas()) { - String arenaName = this.arenaManager.getArenaName(loadedArena.arena().name(), loadedArena.owner()); + String arenaName = this.arenaManager.getArenaName(loadedArena); World world = Bukkit.getWorld(arenaName); if (world == null) { continue; } + if (loadedArena.owner() == null) { + continue; + } if (world.getPlayerCount() == 0 && Bukkit.getPlayer(loadedArena.owner().getId()) == null) { emptyArenas.putIfAbsent(loadedArena, System.currentTimeMillis()); diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/ArenaCommand.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/ArenaCommand.java index 225bc56246..b2b12a8b7f 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/ArenaCommand.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/ArenaCommand.java @@ -4,6 +4,8 @@ import net.civmc.kitpvp.arena.data.Arena; import net.civmc.kitpvp.arena.data.ArenaDao; import net.civmc.kitpvp.arena.gui.ArenaGui; +import net.civmc.kitpvp.ranked.RankedDao; +import net.civmc.kitpvp.ranked.RankedQueueManager; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.format.NamedTextColor; @@ -26,12 +28,16 @@ public class ArenaCommand implements CommandExecutor { private final JavaPlugin plugin; private final ArenaDao dao; + private final RankedDao rankedDao; + private final RankedQueueManager rankedQueueManager; private final ArenaManager manager; private final PrivateArenaListener privateArenaListener; - public ArenaCommand(JavaPlugin plugin, ArenaDao dao, ArenaManager manager, PrivateArenaListener privateArenaListener) { + public ArenaCommand(JavaPlugin plugin, ArenaDao dao, RankedDao rankedDao, RankedQueueManager rankedQueueManager, ArenaManager manager, PrivateArenaListener privateArenaListener) { this.plugin = plugin; this.dao = dao; + this.rankedDao = rankedDao; + this.rankedQueueManager = rankedQueueManager; this.manager = manager; this.privateArenaListener = privateArenaListener; } @@ -172,7 +178,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command LoadedArena playerArena = null; for (LoadedArena arena : manager.getArenas()) { - if (arena.owner().equals(player.getPlayerProfile())) { + if (!arena.ranked() && arena.owner().equals(player.getPlayerProfile())) { playerArena = arena; break; } @@ -215,7 +221,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command LoadedArena playerArena = null; for (LoadedArena arena : manager.getArenas()) { - if (arena.owner().equals(player.getPlayerProfile())) { + if (!arena.ranked() && arena.owner().equals(player.getPlayerProfile())) { playerArena = arena; break; } @@ -254,7 +260,12 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command } return true; } else if (args.length == 0) { - new ArenaGui(dao, manager).open(player); + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + double elo = rankedDao.getElo(player.getUniqueId()); + Bukkit.getScheduler().runTask(plugin, () -> { + new ArenaGui(dao, rankedQueueManager, elo, manager).open(player); + }); + }); return true; } return false; diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/ArenaManager.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/ArenaManager.java index f93ff3f3de..ee5fc8449e 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/ArenaManager.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/ArenaManager.java @@ -3,10 +3,12 @@ import com.destroystokyo.paper.profile.PlayerProfile; import java.io.IOException; import java.util.ArrayList; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.SequencedMap; import java.util.UUID; +import java.util.function.Consumer; import java.util.logging.Level; import com.infernalsuite.asp.api.AdvancedSlimePaperAPI; import com.infernalsuite.asp.api.exceptions.CorruptedWorldException; @@ -43,6 +45,7 @@ public class ArenaManager { private final SpawnProvider spawn; private final MysqlLoader templateLoader; private final SequencedMap arenas = new LinkedHashMap<>(); + private final List rankedArenas = new ArrayList<>(); public ArenaManager(int maxArenas, JavaPlugin plugin, SpawnProvider spawn, MysqlLoader templateLoader) { this.maxArenas = maxArenas; @@ -72,8 +75,11 @@ public void deleteArena(PlayerProfile owner) { if (removedArena == null) { return; } - Arena arena = removedArena.arena(); - String worldName = getArenaName(arena.name(), owner); + deleteLoadedArena(removedArena); + } + + public void deleteLoadedArena(LoadedArena removedArena) { + String worldName = getArenaName(removedArena); World world = Bukkit.getWorld(worldName); if (world != null) { Location spawn = this.spawn.getSpawn(); @@ -88,11 +94,26 @@ public void deleteArena(PlayerProfile owner) { worldPlayer.kick(Component.text("The arena you were in was deleted")); } } + Bukkit.unloadWorld(world, false); + } + for (Iterator iterator = rankedArenas.iterator(); iterator.hasNext(); ) { + LoadedArena rankedArena = iterator.next(); + if (rankedArena.equals(removedArena)) { + iterator.remove(); + break; + } } - Bukkit.unloadWorld(worldName, false); } - public String getArenaName(String arena, PlayerProfile owner) { + public String getArenaName(LoadedArena arena) { + if (arena.ranked()) { + return "rankedarena." + arena.rankedId(); + } else { + return getArenaName(arena.arena().name(), arena.owner()); + } + } + + private String getArenaName(String arena, PlayerProfile owner) { return "dynamicarena." + owner.getName() + "." + arena; } @@ -100,6 +121,58 @@ public boolean isArena(String worldName) { return worldName.startsWith("dynamicarena."); } + private void setupWorld(World world) { + world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false); + world.setGameRule(GameRule.DO_FIRE_TICK, false); + world.setGameRule(GameRule.DO_WEATHER_CYCLE, false); + world.setGameRule(GameRule.DO_MOB_SPAWNING, false); + world.setGameRule(GameRule.ANNOUNCE_ADVANCEMENTS, false); + world.setGameRule(GameRule.RANDOM_TICK_SPEED, 0); + world.setGameRule(GameRule.DO_VINES_SPREAD, false); + world.setGameRule(GameRule.KEEP_INVENTORY, false); + world.setGameRule(GameRule.LOCATOR_BAR, false); + world.setGameRule(GameRule.DO_INSOMNIA, false); + world.setFullTime(6000); + } + + public boolean createRankedArena(Arena arena, Consumer callback) { + AdvancedSlimePaperAPI api = AdvancedSlimePaperAPI.instance(); + SlimeWorld slimeWorld; + try { + slimeWorld = api.readWorld(templateLoader, arena.name(), true, SLIME_PROPERTIES); + } catch (UnknownWorldException e) { + throw new RuntimeException(e); + } catch (IOException | NewerFormatException | CorruptedWorldException e) { + plugin.getLogger().log(Level.WARNING, "Unable to load arena", e); + return false; + } + + Bukkit.getScheduler().runTask(plugin, () -> { + int rankedId = 0; + boolean increased = true; + while (increased) { + increased = false; + for (LoadedArena loaded : rankedArenas) { + if (loaded.rankedId() == rankedId) { + rankedId++; + increased = true; + } + } + } + String worldName = "rankedarena." + rankedId; + api.loadWorld(slimeWorld.clone(worldName), true); + World world = Bukkit.getWorld(worldName); + if (world == null) { + return; + } + this.setupWorld(world); + LoadedArena loaded = new LoadedArena(null, arena, null, rankedId); + rankedArenas.add(loaded); + callback.accept(loaded); + }); + return true; + } + public void createArena(Player player, Arena arena, boolean isPublic) { if (maxArenas >= 0 && this.arenas.size() >= maxArenas && !player.hasPermission("kitpvp.admin")) { player.sendMessage(Component.text("You cannot create an arena because there are too many arenas loaded", NamedTextColor.RED)); @@ -125,18 +198,8 @@ public void createArena(Player player, Arena arena, boolean isPublic) { if (world == null) { return; } - - world.setGameRule(GameRule.DO_DAYLIGHT_CYCLE, false); - world.setGameRule(GameRule.DO_FIRE_TICK, false); - world.setGameRule(GameRule.DO_WEATHER_CYCLE, false); - world.setGameRule(GameRule.DO_MOB_SPAWNING, false); - world.setGameRule(GameRule.ANNOUNCE_ADVANCEMENTS, false); - world.setGameRule(GameRule.RANDOM_TICK_SPEED, 0); - world.setGameRule(GameRule.DO_VINES_SPREAD, false); - world.setGameRule(GameRule.KEEP_INVENTORY, false); - world.setFullTime(6000); - - arenas.put(player.getUniqueId(), new LoadedArena(player.getPlayerProfile(), arena, isPublic ? null : new ArrayList<>())); + this.setupWorld(world); + arenas.put(player.getUniqueId(), new LoadedArena(player.getPlayerProfile(), arena, isPublic ? null : new ArrayList<>(), -1)); player.sendMessage(Component.text("Your world is ready. Open ", NamedTextColor.GOLD) .append(Component.text("/arena", NamedTextColor.YELLOW)) diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/LoadedArena.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/LoadedArena.java index 00b8a1d15b..fe166e4ab4 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/LoadedArena.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/LoadedArena.java @@ -7,7 +7,10 @@ public record LoadedArena( PlayerProfile owner, Arena arena, - List invitedPlayers + List invitedPlayers, + int rankedId ) { - + public boolean ranked() { + return rankedId >= 0; + } } diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/PrivateArenaListener.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/PrivateArenaListener.java index 43a4d259f6..4a081a5136 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/PrivateArenaListener.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/PrivateArenaListener.java @@ -35,7 +35,7 @@ private boolean shouldRemove(LoadedArena arena, Player player) { return arena.invitedPlayers() != null && !arena.invitedPlayers().contains(player.getPlayerProfile()) && !player.hasPermission("kitpvp.admin") - && manager.getArenaName(arena.arena().name(), arena.owner()).equals(player.getWorld().getName()); + && manager.getArenaName(arena).equals(player.getWorld().getName()); } public void remove(LoadedArena arena, Player player) { diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/RespawnListener.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/RespawnListener.java index 0ebc1fcf60..6cdf3f7dc0 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/RespawnListener.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/RespawnListener.java @@ -1,6 +1,5 @@ package net.civmc.kitpvp.arena; -import net.civmc.kitpvp.spawn.SpawnProvider; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.event.EventHandler; @@ -22,7 +21,7 @@ public void on(PlayerRespawnEvent event) { String worldName = world.getName(); if (manager.isArena(worldName)) { for (LoadedArena arena : manager.getArenas()) { - if (manager.getArenaName(arena.arena().name(), arena.owner()).equals(worldName)) { + if (manager.getArenaName(arena).equals(worldName)) { Location spawn = arena.arena().spawn().clone(); spawn.setWorld(world); event.setRespawnLocation(spawn); diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/gui/ArenaGui.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/gui/ArenaGui.java index 00426660cd..c806d6bdd3 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/gui/ArenaGui.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/arena/gui/ArenaGui.java @@ -7,8 +7,11 @@ import net.civmc.kitpvp.arena.LoadedArena; import net.civmc.kitpvp.arena.data.Arena; import net.civmc.kitpvp.arena.data.ArenaDao; +import net.civmc.kitpvp.kit.KitCost; +import net.civmc.kitpvp.ranked.RankedQueueManager; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextDecoration; import org.bukkit.Bukkit; import org.bukkit.GameMode; @@ -19,16 +22,21 @@ import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; import vg.civcraft.mc.civmodcore.inventory.gui.Clickable; +import vg.civcraft.mc.civmodcore.inventory.gui.DecorationStack; import vg.civcraft.mc.civmodcore.inventory.gui.IClickable; import vg.civcraft.mc.civmodcore.inventory.gui.MultiPageView; public class ArenaGui { private final ArenaDao dao; + private final RankedQueueManager rankedQueueManager; + private final double elo; private final ArenaManager manager; - public ArenaGui(ArenaDao dao, ArenaManager manager) { + public ArenaGui(ArenaDao dao, RankedQueueManager rankedQueueManager, double elo, ArenaManager manager) { this.dao = dao; + this.rankedQueueManager = rankedQueueManager; + this.elo = elo; this.manager = manager; } @@ -37,7 +45,7 @@ public void open(Player player) { List loadedArenas = manager.getArenas(); loadedArenas.sort(Comparator.comparingInt(s -> { - World world = Bukkit.getWorld(manager.getArenaName(s.arena().name(), s.owner())); + World world = Bukkit.getWorld(manager.getArenaName(s)); if (world == null) { return -1; } else { @@ -45,6 +53,9 @@ public void open(Player player) { } })); for (LoadedArena loadedArena : loadedArenas) { + if (loadedArena.ranked()) { + continue; + } Arena arena = loadedArena.arena(); if (!player.hasPermission("kitpvp.admin") && loadedArena.invitedPlayers() != null @@ -72,11 +83,13 @@ public void open(Player player) { if (hasAccess) { lore.add(Component.text("Shift right click to delete", NamedTextColor.AQUA).decoration(TextDecoration.ITALIC, false)); } - World world = Bukkit.getWorld(manager.getArenaName(arena.name(), loadedArena.owner())); + World world = Bukkit.getWorld(manager.getArenaName(loadedArena)); if (world != null && world.getPlayerCount() > 0) { lore.add(Component.text("Currently playing:", NamedTextColor.GOLD).decoration(TextDecoration.ITALIC, false)); for (Player worldPlayer : world.getPlayers()) { - lore.add(Component.text("- " + worldPlayer.getName(), NamedTextColor.YELLOW).decoration(TextDecoration.ITALIC, false)); + if (worldPlayer.getGameMode() == GameMode.SURVIVAL) { + lore.add(Component.text("- " + worldPlayer.getName(), NamedTextColor.YELLOW).decoration(TextDecoration.ITALIC, false)); + } } } meta.lore(lore); @@ -113,6 +126,28 @@ protected void onShiftRightClick(@NotNull Player clicker) { }); } + ItemStack queueArena = new ItemStack(Material.BEACON); + ItemMeta queueArenaMeta = queueArena.getItemMeta(); + TextColor darkishBlue = TextColor.color(55, 159, 163); + queueArenaMeta.itemName(Component.text("Queue for ranked 1v1", NamedTextColor.GOLD)); + queueArenaMeta.lore(List.of( + Component.empty().append(Component.text("Your elo: ", darkishBlue).append(Component.text(Math.round(elo), NamedTextColor.AQUA)).decoration(TextDecoration.ITALIC, false)), + Component.empty().append(Component.text("Maximum kit cost: ", darkishBlue).append(Component.text(KitCost.MAX_POINTS + " points", NamedTextColor.AQUA)).decoration(TextDecoration.ITALIC, false)), + Component.empty().append(Component.text("Or type /ranked to join or leave the queue", NamedTextColor.AQUA).decoration(TextDecoration.ITALIC, false)), + Component.empty().append(Component.text("Change your kit by clicking the diamond sword in a /kit", NamedTextColor.AQUA).decoration(TextDecoration.ITALIC, false)) + )); + queueArena.setItemMeta(queueArenaMeta); + +// ItemStack queueUnrankedArena = new ItemStack(Material.BEDROCK); +// ItemMeta queueUnrankedArenaMeta = queueUnrankedArena.getItemMeta(); +// queueUnrankedArenaMeta.itemName(Component.text("Queue for unranked 1v1", NamedTextColor.GOLD)); +// queueUnrankedArenaMeta.lore(List.of( +// Component.empty().append(Component.text("Maximum kit cost: ", darkishBlue).append(Component.text(KitCost.MAX_POINTS + " points", NamedTextColor.AQUA)).decoration(TextDecoration.ITALIC, false)), +// Component.empty().append(Component.text("Or type /unranked to join or leave the queue", NamedTextColor.AQUA).decoration(TextDecoration.ITALIC, false)), +// Component.empty().append(Component.text("Change your kit by clicking the diamond sword in a /kit", NamedTextColor.AQUA).decoration(TextDecoration.ITALIC, false)) +// )); +// queueUnrankedArena.setItemMeta(queueUnrankedArenaMeta); + ItemStack createArena = new ItemStack(Material.PAPER); ItemMeta createArenaMeta = createArena.getItemMeta(); createArenaMeta.itemName(Component.text("Create new arena", NamedTextColor.GREEN)); @@ -124,8 +159,11 @@ protected void onShiftRightClick(@NotNull Player clicker) { createPrivateArena.setItemMeta(createPrivateArenaMeta); MultiPageView view = new MultiPageView(player, arenas, "Arenas", true); - view.setMenuSlot(new ArenaClickable(createArena, true), 0); - view.setMenuSlot(new ArenaClickable(createPrivateArena, false), 1); + view.setMenuSlot(new QueueClickable(queueArena), 0); +// view.setMenuSlot(new QueueUnrankedClickable(queueUnrankedArena), 1); + view.setMenuSlot(new DecorationStack(new ItemStack(Material.AIR)), 1); + view.setMenuSlot(new ArenaClickable(createArena, true), 2); + view.setMenuSlot(new ArenaClickable(createPrivateArena, false), 3); view.showScreen(); } @@ -150,4 +188,36 @@ protected void clicked(@NotNull Player clicker) { } } + class QueueClickable extends Clickable { + + public QueueClickable(ItemStack item) { + super(item); + } + + @Override + protected void clicked(@NotNull Player clicker) { + if (!rankedQueueManager.isInQueue(clicker)) { + rankedQueueManager.joinQueue(clicker, false); + clicker.closeInventory(); + } + } + } + + + class QueueUnrankedClickable extends Clickable { + + public QueueUnrankedClickable(ItemStack item) { + super(item); + } + + @Override + protected void clicked(@NotNull Player clicker) { + if (!rankedQueueManager.isInUnrankedQueue(clicker)) { + rankedQueueManager.joinUnrankedQueue(clicker, false); + clicker.closeInventory(); + } + } + } + + } diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/command/ClearCommand.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/command/ClearCommand.java index 2d6bd64df0..45dbdc8fce 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/command/ClearCommand.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/command/ClearCommand.java @@ -16,6 +16,10 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command if (!(sender instanceof Player player)) { return false; } + if (player.getWorld().getName().startsWith("rankedarena.")) { + player.sendMessage(Component.text("Clear command is deactivated in ranked arenas.", NamedTextColor.RED)); + return true; + } KitApplier.reset(player); player.sendMessage(Component.text("Your inventory has been cleared", NamedTextColor.GREEN)); return true; diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/command/KitCommand.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/command/KitCommand.java index ffa09eb826..835cb2a03a 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/command/KitCommand.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/command/KitCommand.java @@ -4,9 +4,10 @@ import net.civmc.kitpvp.KitApplier; import net.civmc.kitpvp.KitPvpPlugin; import net.civmc.kitpvp.anvil.AnvilGui; -import net.civmc.kitpvp.data.Kit; -import net.civmc.kitpvp.data.KitPvpDao; +import net.civmc.kitpvp.kit.Kit; +import net.civmc.kitpvp.kit.KitPvpDao; import net.civmc.kitpvp.gui.KitListGui; +import net.civmc.kitpvp.ranked.RankedDao; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; @@ -21,10 +22,12 @@ public class KitCommand implements CommandExecutor { private final KitPvpDao dao; + private final RankedDao rankedDao; private final AnvilGui anvilGui; - public KitCommand(KitPvpDao dao, AnvilGui anvilGui) { + public KitCommand(KitPvpDao dao, RankedDao rankedDao, AnvilGui anvilGui) { this.dao = dao; + this.rankedDao = rankedDao; this.anvilGui = anvilGui; } @@ -34,6 +37,11 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command return false; } + if (player.getWorld().getName().startsWith("rankedarena.")) { + player.sendMessage(Component.text("Kit command is deactivated in ranked arenas.", NamedTextColor.RED)); + return true; + } + KitPvpPlugin plugin = JavaPlugin.getPlugin(KitPvpPlugin.class); if (args.length > 0 && args[0].equalsIgnoreCase("copy")) { if (args.length < 3 || args.length > 4) { @@ -113,7 +121,7 @@ public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command }); return true; } else if (args.length == 0) { - new KitListGui(dao, anvilGui, player); + new KitListGui(dao, rankedDao, anvilGui, player); return true; } return false; diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/ConfirmDeletionGui.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/ConfirmDeletionGui.java index aea774c1fe..ebcbbd2e7d 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/ConfirmDeletionGui.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/ConfirmDeletionGui.java @@ -1,8 +1,8 @@ package net.civmc.kitpvp.gui; import net.civmc.kitpvp.KitPvpPlugin; -import net.civmc.kitpvp.data.Kit; -import net.civmc.kitpvp.data.KitPvpDao; +import net.civmc.kitpvp.kit.Kit; +import net.civmc.kitpvp.kit.KitPvpDao; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/EditKitGui.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/EditKitGui.java index ab58a202b5..3d58d5c25a 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/EditKitGui.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/EditKitGui.java @@ -7,14 +7,17 @@ import net.civmc.kitpvp.KitPvpPlugin; import net.civmc.kitpvp.anvil.AnvilGui; import net.civmc.kitpvp.anvil.AnvilGuiListener; -import net.civmc.kitpvp.data.Kit; -import net.civmc.kitpvp.data.KitPvpDao; import net.civmc.kitpvp.gui.selection.ArmourSlotSelectionGui; import net.civmc.kitpvp.gui.selection.CountSelectionGui; import net.civmc.kitpvp.gui.selection.EnchantmentGui; import net.civmc.kitpvp.gui.selection.IconSelectionGui; import net.civmc.kitpvp.gui.selection.ItemCategorySelectionGui; +import net.civmc.kitpvp.kit.Kit; +import net.civmc.kitpvp.kit.KitPvpDao; +import net.civmc.kitpvp.kit.KitCost; +import net.civmc.kitpvp.ranked.RankedDao; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextDecoration; import org.bukkit.Bukkit; @@ -31,6 +34,8 @@ public class EditKitGui { private final KitPvpDao dao; + private final RankedDao rankedDao; + private int rankedKit; private final AnvilGui anvilGui; private final Player player; private Kit kit; @@ -38,8 +43,10 @@ public class EditKitGui { private final KitListGui gui; private final boolean canEdit; - public EditKitGui(KitPvpDao dao, AnvilGui anvilGui, Player player, Kit kit, KitListGui gui) { + public EditKitGui(KitPvpDao dao, RankedDao rankedDao, int rankedKit, AnvilGui anvilGui, Player player, Kit kit, KitListGui gui) { this.dao = dao; + this.rankedDao = rankedDao; + this.rankedKit = rankedKit; this.anvilGui = anvilGui; this.player = player; this.kit = kit; @@ -71,6 +78,39 @@ public void open() { copy.setItemMeta(copyMeta); inventory.setItem(copy, 0); + ItemStack rankedKit = new ItemStack(Material.DIAMOND_SWORD); + ItemMeta rankedKitMeta = rankedKit.getItemMeta(); + rankedKitMeta.itemName(Component.text("Select as ranked kit", NamedTextColor.GOLD)); + List lore = new ArrayList<>(); + if (kit.id() == this.rankedKit) { + rankedKitMeta.setEnchantmentGlintOverride(true); + lore.add(Component.text("Currently selected", NamedTextColor.GOLD, TextDecoration.BOLD).decoration(TextDecoration.ITALIC, false)); + } + lore.add(Component.empty().append(Component.text("Maximum " + KitCost.MAX_POINTS + " points", NamedTextColor.GOLD)).decoration(TextDecoration.ITALIC, false)); + int cost = KitCost.getCost(kit.items()); + lore.add(Component.empty().append(Component.text("Current cost: ", NamedTextColor.GOLD).append(Component.text(cost + " points", NamedTextColor.YELLOW))).decoration(TextDecoration.ITALIC, false)); + if (cost > KitCost.MAX_POINTS) { + lore.add(Component.text("Kit is too expensive for ranked!", NamedTextColor.RED)); + } + rankedKitMeta.lore(lore); + rankedKit.setItemMeta(rankedKitMeta); + inventory.setSlot(new Clickable(rankedKit) { + @Override + protected void clicked(@NotNull Player clicker) { + if (!kit.name().equals(EditKitGui.this.rankedKit)) { + JavaPlugin plugin = JavaPlugin.getPlugin(KitPvpPlugin.class); + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + rankedDao.setKit(player.getUniqueId(), kit.id()); + Bukkit.getScheduler().runTask(plugin, () -> { + EditKitGui.this.rankedKit = kit.id(); + inventory.setOnClose(null); + open(); + }); + }); + } + } + }, 1); + if (player.hasPermission("kitpvp.admin")) { ItemStack isPublic = new ItemStack(kit.isPublic() ? Material.OAK_FENCE : Material.OAK_FENCE_GATE); ItemMeta isPublicMeta = isPublic.getItemMeta(); diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/KitListGui.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/KitListGui.java index 3260913c49..c7aa0ec18f 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/KitListGui.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/KitListGui.java @@ -9,8 +9,9 @@ import net.civmc.kitpvp.KitPvpPlugin; import net.civmc.kitpvp.anvil.AnvilGui; import net.civmc.kitpvp.anvil.AnvilGuiListener; -import net.civmc.kitpvp.data.Kit; -import net.civmc.kitpvp.data.KitPvpDao; +import net.civmc.kitpvp.kit.Kit; +import net.civmc.kitpvp.kit.KitPvpDao; +import net.civmc.kitpvp.ranked.RankedDao; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextDecoration; @@ -37,6 +38,8 @@ public class KitListGui { } private final KitPvpDao dao; + private final RankedDao rankedDao; + private int rankedKit; private final AnvilGui anvilGui; private final Player player; private final List kits = new ArrayList<>(); @@ -45,8 +48,9 @@ public class KitListGui { private boolean ready = false; private boolean openWhenReady = true; - public KitListGui(KitPvpDao dao, AnvilGui anvilGui, Player player) { + public KitListGui(KitPvpDao dao, RankedDao rankedDao, AnvilGui anvilGui, Player player) { this.dao = dao; + this.rankedDao = rankedDao; this.anvilGui = anvilGui; this.player = player; this.view = new FastMultiPageView(player, this::kitSupplier, "Kits", 6); @@ -95,9 +99,12 @@ public boolean onRename(String name) { player.sendMessage(Component.text("A kit with that name already exists", NamedTextColor.RED)); return; } + + int rankedKit = rankedDao.getKit(player.getUniqueId()); + Bukkit.getScheduler().runTask(plugin, () -> { invalidate(); - new EditKitGui(KitListGui.this.dao, anvilGui, clicker, createdKit, KitListGui.this); + new EditKitGui(KitListGui.this.dao, rankedDao, rankedKit, anvilGui, clicker, createdKit, KitListGui.this); }); }); return true; @@ -114,14 +121,15 @@ public void invalidate() { KitPvpPlugin plugin = JavaPlugin.getPlugin(KitPvpPlugin.class); Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { List playerKits = this.dao.getKits(player.getUniqueId()); - + int rankedKit = this.rankedDao.getKit(player.getUniqueId()); Bukkit.getScheduler().runTask(plugin, () -> { this.kits.clear(); this.kits.addAll(playerKits.stream().sorted( Comparator .comparing(Kit::isPublic).reversed() - .thenComparing(Kit::name)) + .thenComparing(k -> k.name().toLowerCase())) .toList()); + this.rankedKit = rankedKit; this.ready = true; if (this.openWhenReady) { this.openWhenReady = false; @@ -155,7 +163,8 @@ private List kitSupplier(int start, int max) { } if (kit.isPublic() && !player.hasPermission("kitpvp.admin")) { iconMeta.lore(List.of( - Component.text("Left click to load", NamedTextColor.AQUA).decoration(TextDecoration.ITALIC, false) + Component.text("Left click to load", NamedTextColor.AQUA).decoration(TextDecoration.ITALIC, false), + Component.text("Right click to view", NamedTextColor.AQUA).decoration(TextDecoration.ITALIC, false) )); icon.setItemMeta(iconMeta); clickables.add(new Clickable(icon) { @@ -164,6 +173,11 @@ protected void clicked(@NotNull Player clicker) { clicker.closeInventory(); KitApplier.applyKit(kit, clicker); } + + @Override + protected void onRightClick(@NotNull Player clicker) { + new EditKitGui(KitListGui.this.dao, KitListGui.this.rankedDao, rankedKit, KitListGui.this.anvilGui, clicker, kit, KitListGui.this); + } }); } else { iconMeta.lore(List.of( @@ -181,7 +195,7 @@ protected void clicked(@NotNull Player clicker) { @Override protected void onRightClick(@NotNull Player clicker) { - new EditKitGui(KitListGui.this.dao, KitListGui.this.anvilGui, clicker, kit, KitListGui.this); + new EditKitGui(KitListGui.this.dao, KitListGui.this.rankedDao, rankedKit, KitListGui.this.anvilGui, clicker, kit, KitListGui.this); } @Override diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/ArmourSlotSelectionGui.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/ArmourSlotSelectionGui.java index 652068a6cd..b35450c3f2 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/ArmourSlotSelectionGui.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/ArmourSlotSelectionGui.java @@ -1,8 +1,11 @@ package net.civmc.kitpvp.gui.selection; -import net.civmc.kitpvp.data.Kit; -import net.civmc.kitpvp.data.KitPvpDao; +import net.civmc.kitpvp.kit.Kit; +import net.civmc.kitpvp.kit.KitCustomItem; +import net.civmc.kitpvp.kit.KitItem; +import net.civmc.kitpvp.kit.KitPvpDao; import net.civmc.kitpvp.gui.EditKitGui; +import net.civmc.kitpvp.kit.KitCost; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Material; @@ -33,10 +36,22 @@ public void addItems(ClickableInventory inventory) { inventory.setSlot(toClickable(none, null), 18); for (int i = 0; i < items.size(); i++) { - inventory.setSlot(toClickable(new ItemStack(items.get(i))), 19 + i); + for (KitItem item : KitItem.values()) { + if (item.getItem() == items.get(i)) { + ItemStack stack = new ItemStack(items.get(i)); + inventory.setSlot(toClickable(KitCost.setPoints(stack, item.getCost()), stack), 19 + i); + break; + } + } } for (int i = 0; i < custom.size(); i++) { - inventory.setSlot(toClickable(CustomItem.getCustomItem(custom.get(i))), 19 + items.size() + i); + for (KitCustomItem item : KitCustomItem.values()) { + if (item.getItem().equals(custom.get(i))) { + ItemStack stack = CustomItem.getCustomItem(custom.get(i)); + inventory.setSlot(toClickable(KitCost.setPoints(stack, item.getCost()), stack), 19 + items.size() + i); + break; + } + } } } } diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/BlockSelectionGui.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/BlockSelectionGui.java index a962b98438..187cb064de 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/BlockSelectionGui.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/BlockSelectionGui.java @@ -1,13 +1,17 @@ package net.civmc.kitpvp.gui.selection; -import net.civmc.kitpvp.data.Kit; -import net.civmc.kitpvp.data.KitPvpDao; +import net.civmc.kitpvp.kit.Kit; +import net.civmc.kitpvp.kit.KitCategory; +import net.civmc.kitpvp.kit.KitItem; +import net.civmc.kitpvp.kit.KitPvpDao; import net.civmc.kitpvp.gui.EditKitGui; -import org.bukkit.Material; +import net.civmc.kitpvp.kit.KitCost; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import vg.civcraft.mc.civmodcore.inventory.gui.ClickableInventory; +import java.util.List; + public class BlockSelectionGui extends ItemSelectionGui { public BlockSelectionGui(KitPvpDao dao, Player player, int slot, Kit kit, Runnable parent, EditKitGui gui) { @@ -16,47 +20,11 @@ public BlockSelectionGui(KitPvpDao dao, Player player, int slot, Kit kit, Runnab @Override public void addItems(ClickableInventory inventory) { - Material[] types = new Material[] { - Material.OBSIDIAN, - Material.COBBLESTONE, - Material.COBWEB, - Material.IRON_DOOR, - Material.OAK_DOOR, - Material.OAK_PLANKS, - Material.OAK_LOG, - Material.CRAFTING_TABLE, - Material.WHITE_WOOL, - Material.BONE_BLOCK, - Material.SANDSTONE, - Material.GRAVEL, - Material.ICE, - Material.PACKED_ICE, - Material.BLUE_ICE, - Material.OAK_TRAPDOOR, - Material.HAY_BLOCK, - Material.SOUL_SAND, - Material.SOUL_SOIL, - Material.DIRT, - Material.SAND, - Material.STONE, - Material.TNT, - Material.SCAFFOLDING, - Material.SLIME_BLOCK, - Material.HONEY_BLOCK, - Material.GLOWSTONE, - Material.GLASS, - Material.NETHERRACK, - Material.RAIL, - Material.IRON_BLOCK, - Material.GOLD_BLOCK, - Material.BEACON, - Material.COBBLED_DEEPSLATE, - Material.LAPIS_BLOCK, - Material.REDSTONE_BLOCK, - Material.COPPER_BLOCK - }; - for (int i = 0; i < types.length; i++) { - inventory.setSlot(toClickable(new ItemStack(types[i])), i); + List items = KitItem.getItems(KitCategory.BLOCK); + for (int i = 0; i < items.size(); i++) { + KitItem item = items.get(i); + ItemStack created = new ItemStack(item.getItem()); + inventory.setSlot(toClickable(KitCost.setPoints(created, item.getCost()), created), i); } } } diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/CountSelectionGui.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/CountSelectionGui.java index 4a3ce7f858..ccd01da528 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/CountSelectionGui.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/CountSelectionGui.java @@ -3,8 +3,8 @@ import net.civmc.kitpvp.KitPvpPlugin; import net.civmc.kitpvp.anvil.AnvilGui; import net.civmc.kitpvp.anvil.AnvilGuiListener; -import net.civmc.kitpvp.data.Kit; -import net.civmc.kitpvp.data.KitPvpDao; +import net.civmc.kitpvp.kit.Kit; +import net.civmc.kitpvp.kit.KitPvpDao; import net.civmc.kitpvp.gui.EditKitGui; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/DrugsSelectionGui.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/DrugsSelectionGui.java index c875d77209..57bcf881d7 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/DrugsSelectionGui.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/DrugsSelectionGui.java @@ -1,25 +1,14 @@ package net.civmc.kitpvp.gui.selection; import com.dre.brewery.Brew; -import com.dre.brewery.api.BreweryApi; import com.dre.brewery.recipe.BRecipe; -import net.civmc.kitpvp.KitPvpPlugin; -import net.civmc.kitpvp.data.Kit; -import net.civmc.kitpvp.data.KitPvpDao; +import net.civmc.kitpvp.kit.Kit; +import net.civmc.kitpvp.kit.KitDrugs; +import net.civmc.kitpvp.kit.KitPvpDao; import net.civmc.kitpvp.gui.EditKitGui; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import org.bukkit.Bukkit; -import org.bukkit.Color; -import org.bukkit.Material; +import net.civmc.kitpvp.kit.KitCost; import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.PotionMeta; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.potion.PotionType; -import org.jetbrains.annotations.NotNull; -import vg.civcraft.mc.civmodcore.inventory.gui.Clickable; import vg.civcraft.mc.civmodcore.inventory.gui.ClickableInventory; public class DrugsSelectionGui extends ItemSelectionGui { @@ -31,34 +20,14 @@ public DrugsSelectionGui(KitPvpDao dao, Player player, int slot, Kit kit, Runnab @Override public void addItems(ClickableInventory inventory) { int slot = 0; - for (String drug : new String[]{ - "Cyanide", - "Cannabis", - "Meth", - "Blue Meth", - "Heroin", - "Oestrogen", - "Caffeine", - "Ivermectin", - "DMT", - "Xanax", - "Steroids", - "Testosterone", - "Vicodin", - "Yakult", - "Cocaine", - "Speed", - "NAD+", - "Epinephrine", - "Firefoam", - "Nitroglycerin" - }) { - BRecipe matching = BRecipe.getMatching(drug); + for (KitDrugs drug : KitDrugs.values()) { + BRecipe matching = BRecipe.getMatching(drug.getBrew()); if (matching != null) { Brew brew = matching.createBrew(10); ItemStack item = brew.createItem(matching); brew.seal(item, null); - inventory.setSlot(toClickable(item), slot++); + + inventory.setSlot(toClickable(KitCost.setPoints(item, drug.getCost()), item), slot++); } } } diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/EnchantmentGui.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/EnchantmentGui.java index 41d78b0ef8..0bd2d911c7 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/EnchantmentGui.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/EnchantmentGui.java @@ -8,9 +8,10 @@ import net.civmc.kitpvp.KitPvpPlugin; import net.civmc.kitpvp.anvil.AnvilGui; import net.civmc.kitpvp.anvil.AnvilGuiListener; -import net.civmc.kitpvp.data.Kit; -import net.civmc.kitpvp.data.KitPvpDao; +import net.civmc.kitpvp.kit.Kit; +import net.civmc.kitpvp.kit.KitPvpDao; import net.civmc.kitpvp.gui.EditKitGui; +import net.civmc.kitpvp.kit.KitCost; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.minecraft.core.component.DataComponents; @@ -47,12 +48,19 @@ public void addItems(ClickableInventory inventory) { List enchants = RegistryAccess.registryAccess().getRegistry(RegistryKey.ENCHANTMENT).stream() .sorted(Comparator.comparingInt(Enchantment::getMaxLevel).reversed()) .toList(); + OUTER: for (Enchantment enchantment : enchants) { if (enchantment.canEnchantItem(kitItem) && !enchantment.isCursed() && enchantment != Enchantment.BANE_OF_ARTHROPODS && enchantment != Enchantment.SMITE && enchantment != Enchantment.MENDING) { + for (Enchantment currentEnchantment : kitItem.getEnchantments().keySet()) { + if (currentEnchantment.conflictsWith(enchantment) && currentEnchantment != enchantment) { + slot++; + continue OUTER; + } + } int pos = ENCHANT_START_SLOTS[slot++]; for (int level = 1; level <= enchantment.getMaxLevel(); level++) { ItemStack book = new ItemStack(Material.ENCHANTED_BOOK); @@ -62,7 +70,12 @@ public void addItems(ClickableInventory inventory) { ItemStack enchantedKitItem = kitItem.clone(); enchantedKitItem.addEnchantment(enchantment, level); - inventory.setSlot(toClickable(book, enchantedKitItem), pos++); + int cost = KitCost.ENCHANTMENT_COST_PER_LEVEL.getOrDefault(enchantment, 0) * level; + if (cost > 0) { + inventory.setSlot(toClickable(KitCost.setPoints(book, cost), enchantedKitItem), pos++); + } else { + inventory.setSlot(toClickable(book, enchantedKitItem), pos++); + } } } } @@ -92,7 +105,7 @@ public void addItems(ClickableInventory inventory) { ItemMeta breakableItemMeta = breakableItem.getItemMeta(); breakableItemMeta.setUnbreakable(false); breakableItem.setItemMeta(breakableItemMeta); - inventory.setSlot(toClickable(breakable, breakableItem), 52); + inventory.setSlot(toClickable(KitCost.setPoints(breakable, -100), breakableItem), 52); } else { ItemStack unbreakable = new ItemStack(Material.BEDROCK); ItemMeta unbreakableMeta = unbreakable.getItemMeta(); @@ -103,7 +116,7 @@ public void addItems(ClickableInventory inventory) { ItemMeta unbreakableItemMeta = unbreakableItem.getItemMeta(); unbreakableItemMeta.setUnbreakable(true); unbreakableItem.setItemMeta(unbreakableItemMeta); - inventory.setSlot(toClickable(unbreakable, unbreakableItem), 52); + inventory.setSlot(toClickable(KitCost.setPoints(unbreakable, 100), unbreakableItem), 52); } } diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/FoodSelectionGui.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/FoodSelectionGui.java index 9bc3483d0e..4c8f6e710d 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/FoodSelectionGui.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/FoodSelectionGui.java @@ -1,9 +1,11 @@ package net.civmc.kitpvp.gui.selection; -import net.civmc.kitpvp.data.Kit; -import net.civmc.kitpvp.data.KitPvpDao; +import net.civmc.kitpvp.kit.Kit; +import net.civmc.kitpvp.kit.KitCategory; +import net.civmc.kitpvp.kit.KitItem; +import net.civmc.kitpvp.kit.KitPvpDao; import net.civmc.kitpvp.gui.EditKitGui; -import org.bukkit.Material; +import net.civmc.kitpvp.kit.KitCost; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import vg.civcraft.mc.civmodcore.inventory.gui.ClickableInventory; @@ -16,15 +18,15 @@ public FoodSelectionGui(KitPvpDao dao, Player player, int slot, Kit kit, Runnabl @Override public void addItems(ClickableInventory inventory) { - inventory.setSlot(toClickable(new ItemStack(Material.COOKED_PORKCHOP)), 0); - inventory.setSlot(toClickable(new ItemStack(Material.GOLDEN_APPLE)), 1); - inventory.setSlot(toClickable(new ItemStack(Material.BAKED_POTATO)), 2); - inventory.setSlot(toClickable(new ItemStack(Material.COOKED_BEEF)), 3); - inventory.setSlot(toClickable(new ItemStack(Material.GOLDEN_CARROT)), 4); - inventory.setSlot(toClickable(new ItemStack(Material.SWEET_BERRIES)), 5); - inventory.setSlot(toClickable(new ItemStack(Material.PUMPKIN_PIE)), 6); - inventory.setSlot(toClickable(new ItemStack(Material.BREAD)), 7); - inventory.setSlot(toClickable(new ItemStack(Material.COOKED_CHICKEN)), 8); - inventory.setSlot(toClickable(new ItemStack(Material.CHORUS_FRUIT)), 9); + int slot = 0; + for (KitItem item : KitItem.getItems(KitCategory.FOOD)) { + ItemStack stack = new ItemStack(item.getItem()); + if (item.getCost() != 0) { + inventory.setSlot(toClickable(KitCost.setPoints(stack, item.getCost()), stack), slot); + } else { + inventory.setSlot(toClickable(stack), slot); + } + slot++; + } } } diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/GenericArmourSelectionGui.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/GenericArmourSelectionGui.java index 33cd885b6b..d7501f79ff 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/GenericArmourSelectionGui.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/GenericArmourSelectionGui.java @@ -1,12 +1,15 @@ package net.civmc.kitpvp.gui.selection; -import net.civmc.kitpvp.data.Kit; -import net.civmc.kitpvp.data.KitPvpDao; +import net.civmc.kitpvp.kit.Kit; +import net.civmc.kitpvp.kit.KitCustomItem; +import net.civmc.kitpvp.kit.KitItem; +import net.civmc.kitpvp.kit.KitPvpDao; import net.civmc.kitpvp.gui.EditKitGui; -import org.bukkit.Material; +import net.civmc.kitpvp.kit.KitCost; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import vg.civcraft.mc.civmodcore.inventory.CustomItem; +import vg.civcraft.mc.civmodcore.inventory.gui.Clickable; import vg.civcraft.mc.civmodcore.inventory.gui.ClickableInventory; public class GenericArmourSelectionGui extends ItemSelectionGui { @@ -15,45 +18,54 @@ public GenericArmourSelectionGui(KitPvpDao dao, Player player, int slot, Kit kit super(dao, "Armour", player, slot, kit, parent, gui); } + private Clickable toClickable(KitItem kitItem) { + ItemStack item = new ItemStack(kitItem.getItem()); + return toClickable(KitCost.setPoints(item, kitItem.getCost()), item); + } + + private Clickable toClickable(KitCustomItem kitItem) { + ItemStack item = CustomItem.getCustomItem(kitItem.getItem()); + return toClickable(KitCost.setPoints(item, kitItem.getCost()), item); + } + @Override public void addItems(ClickableInventory inventory) { - inventory.setSlot(toClickable(new ItemStack(Material.LEATHER_HELMET)), 0); - inventory.setSlot(toClickable(new ItemStack(Material.LEATHER_CHESTPLATE)), 9); - inventory.setSlot(toClickable(new ItemStack(Material.LEATHER_LEGGINGS)), 18); - inventory.setSlot(toClickable(new ItemStack(Material.LEATHER_BOOTS)), 27); - - inventory.setSlot(toClickable(new ItemStack(Material.GOLDEN_HELMET)), 1); - inventory.setSlot(toClickable(new ItemStack(Material.GOLDEN_CHESTPLATE)), 10); - inventory.setSlot(toClickable(new ItemStack(Material.GOLDEN_LEGGINGS)), 19); - inventory.setSlot(toClickable(new ItemStack(Material.GOLDEN_BOOTS)), 28); - - inventory.setSlot(toClickable(new ItemStack(Material.CHAINMAIL_HELMET)), 2); - inventory.setSlot(toClickable(new ItemStack(Material.CHAINMAIL_CHESTPLATE)), 11); - inventory.setSlot(toClickable(new ItemStack(Material.CHAINMAIL_LEGGINGS)), 20); - inventory.setSlot(toClickable(new ItemStack(Material.CHAINMAIL_BOOTS)), 29); - - inventory.setSlot(toClickable(new ItemStack(Material.IRON_HELMET)), 3); - inventory.setSlot(toClickable(new ItemStack(Material.IRON_CHESTPLATE)), 12); - inventory.setSlot(toClickable(new ItemStack(Material.IRON_LEGGINGS)), 21); - inventory.setSlot(toClickable(new ItemStack(Material.IRON_BOOTS)), 30); - - inventory.setSlot(toClickable(new ItemStack(Material.DIAMOND_HELMET)), 4); - inventory.setSlot(toClickable(new ItemStack(Material.DIAMOND_CHESTPLATE)), 13); - inventory.setSlot(toClickable(new ItemStack(Material.DIAMOND_LEGGINGS)), 22); - inventory.setSlot(toClickable(new ItemStack(Material.DIAMOND_BOOTS)), 31); - - inventory.setSlot(toClickable(new ItemStack(Material.NETHERITE_HELMET)), 5); - inventory.setSlot(toClickable(new ItemStack(Material.NETHERITE_CHESTPLATE)), 14); - inventory.setSlot(toClickable(new ItemStack(Material.NETHERITE_LEGGINGS)), 23); - inventory.setSlot(toClickable(new ItemStack(Material.NETHERITE_BOOTS)), 32); - - - inventory.setSlot(toClickable(CustomItem.getCustomItem("meteoric_iron_helmet")), 6); - inventory.setSlot(toClickable(CustomItem.getCustomItem("meteoric_iron_chestplate")), 15); - inventory.setSlot(toClickable(CustomItem.getCustomItem("meteoric_iron_leggings")), 24); - inventory.setSlot(toClickable(CustomItem.getCustomItem("meteoric_iron_boots")), 33); - - inventory.setSlot(toClickable(new ItemStack(Material.TURTLE_HELMET)), 7); - inventory.setSlot(toClickable(new ItemStack(Material.ELYTRA)), 16); + inventory.setSlot(toClickable(KitItem.LEATHER_HELMET), 0); + inventory.setSlot(toClickable(KitItem.LEATHER_CHESTPLATE), 9); + inventory.setSlot(toClickable(KitItem.LEATHER_LEGGINGS), 18); + inventory.setSlot(toClickable(KitItem.LEATHER_BOOTS), 27); + + inventory.setSlot(toClickable(KitItem.GOLDEN_HELMET), 1); + inventory.setSlot(toClickable(KitItem.GOLDEN_CHESTPLATE), 10); + inventory.setSlot(toClickable(KitItem.GOLDEN_LEGGINGS), 19); + inventory.setSlot(toClickable(KitItem.GOLDEN_BOOTS), 28); + + inventory.setSlot(toClickable(KitItem.CHAINMAIL_HELMET), 2); + inventory.setSlot(toClickable(KitItem.CHAINMAIL_CHESTPLATE), 11); + inventory.setSlot(toClickable(KitItem.CHAINMAIL_LEGGINGS), 20); + inventory.setSlot(toClickable(KitItem.CHAINMAIL_BOOTS), 29); + + inventory.setSlot(toClickable(KitItem.IRON_HELMET), 3); + inventory.setSlot(toClickable(KitItem.IRON_CHESTPLATE), 12); + inventory.setSlot(toClickable(KitItem.IRON_LEGGINGS), 21); + inventory.setSlot(toClickable(KitItem.IRON_BOOTS), 30); + + inventory.setSlot(toClickable(KitItem.DIAMOND_HELMET), 4); + inventory.setSlot(toClickable(KitItem.DIAMOND_CHESTPLATE), 13); + inventory.setSlot(toClickable(KitItem.DIAMOND_LEGGINGS), 22); + inventory.setSlot(toClickable(KitItem.DIAMOND_BOOTS), 31); + + inventory.setSlot(toClickable(KitItem.NETHERITE_HELMET), 5); + inventory.setSlot(toClickable(KitItem.NETHERITE_CHESTPLATE), 14); + inventory.setSlot(toClickable(KitItem.NETHERITE_LEGGINGS), 23); + inventory.setSlot(toClickable(KitItem.NETHERITE_BOOTS), 32); + + inventory.setSlot(toClickable(KitCustomItem.METEORIC_IRON_HELMET), 6); + inventory.setSlot(toClickable(KitCustomItem.METEORIC_IRON_CHESTPLATE), 15); + inventory.setSlot(toClickable(KitCustomItem.METEORIC_IRON_LEGGINGS), 24); + inventory.setSlot(toClickable(KitCustomItem.METEORIC_IRON_BOOTS), 33); + + inventory.setSlot(toClickable(KitItem.TURTLE_HELMET), 7); + inventory.setSlot(toClickable(KitItem.ELYTRA), 16); } } diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/IconSelectionGui.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/IconSelectionGui.java index 78e5dd8fe6..c6fb822fd0 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/IconSelectionGui.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/IconSelectionGui.java @@ -1,8 +1,8 @@ package net.civmc.kitpvp.gui.selection; import net.civmc.kitpvp.KitPvpPlugin; -import net.civmc.kitpvp.data.Kit; -import net.civmc.kitpvp.data.KitPvpDao; +import net.civmc.kitpvp.kit.Kit; +import net.civmc.kitpvp.kit.KitPvpDao; import net.civmc.kitpvp.gui.EditKitGui; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; @@ -57,6 +57,17 @@ public void open() { Material.ELYTRA, Material.CROSSBOW, Material.BOW, + Material.COBWEB, + Material.TNT_MINECART, + Material.SUGAR, + Material.SPLASH_POTION, + Material.POTION, + Material.GOLDEN_APPLE, + Material.ENDER_PEARL, + Material.SHIELD, + Material.CHORUS_FRUIT, + Material.WATER_BUCKET, + Material.OBSIDIAN, }) { inventory.setSlot(getSlot(inventory, m), slot++); } diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/ItemCategorySelectionGui.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/ItemCategorySelectionGui.java index d0c4ecccc5..41c8d38c1e 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/ItemCategorySelectionGui.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/ItemCategorySelectionGui.java @@ -1,7 +1,7 @@ package net.civmc.kitpvp.gui.selection; -import net.civmc.kitpvp.data.Kit; -import net.civmc.kitpvp.data.KitPvpDao; +import net.civmc.kitpvp.kit.Kit; +import net.civmc.kitpvp.kit.KitPvpDao; import net.civmc.kitpvp.gui.EditKitGui; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/ItemSelectionGui.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/ItemSelectionGui.java index 911a75647a..f7431bf197 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/ItemSelectionGui.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/ItemSelectionGui.java @@ -2,8 +2,8 @@ import com.github.maxopoly.finale.Finale; import net.civmc.kitpvp.KitPvpPlugin; -import net.civmc.kitpvp.data.Kit; -import net.civmc.kitpvp.data.KitPvpDao; +import net.civmc.kitpvp.kit.Kit; +import net.civmc.kitpvp.kit.KitPvpDao; import net.civmc.kitpvp.gui.EditKitGui; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/PotionsSelectionGui.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/PotionsSelectionGui.java index ec9712e8d9..e922677c84 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/PotionsSelectionGui.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/PotionsSelectionGui.java @@ -1,9 +1,11 @@ package net.civmc.kitpvp.gui.selection; import net.civmc.kitpvp.KitPvpPlugin; -import net.civmc.kitpvp.data.Kit; -import net.civmc.kitpvp.data.KitPvpDao; +import net.civmc.kitpvp.kit.Kit; +import net.civmc.kitpvp.kit.KitPotion; +import net.civmc.kitpvp.kit.KitPvpDao; import net.civmc.kitpvp.gui.EditKitGui; +import net.civmc.kitpvp.kit.KitCost; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextDecoration; @@ -11,11 +13,9 @@ import org.bukkit.Color; import org.bukkit.Material; import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.PotionMeta; import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.potion.PotionType; import org.jetbrains.annotations.NotNull; import vg.civcraft.mc.civmodcore.inventory.gui.Clickable; import vg.civcraft.mc.civmodcore.inventory.gui.ClickableInventory; @@ -31,51 +31,12 @@ public PotionsSelectionGui(KitPvpDao dao, Player player, int slot, Kit kit, Runn @Override public void addItems(ClickableInventory inventory) { int slot = 0; - for (PotionType potionType : new PotionType[] { - PotionType.NIGHT_VISION, - PotionType.LONG_NIGHT_VISION, - PotionType.INVISIBILITY, - PotionType.LONG_INVISIBILITY, - PotionType.LEAPING, - PotionType.LONG_LEAPING, - PotionType.STRONG_LEAPING, - PotionType.FIRE_RESISTANCE, - PotionType.LONG_FIRE_RESISTANCE, - PotionType.SWIFTNESS, - PotionType.LONG_SWIFTNESS, - PotionType.STRONG_SWIFTNESS, - PotionType.SLOWNESS, - PotionType.LONG_SLOWNESS, - PotionType.STRONG_SLOWNESS, - PotionType.WATER_BREATHING, - PotionType.LONG_WATER_BREATHING, - PotionType.HEALING, - PotionType.STRONG_HEALING, - PotionType.HARMING, - PotionType.STRONG_HARMING, - PotionType.POISON, - PotionType.LONG_POISON, - PotionType.STRONG_POISON, - PotionType.REGENERATION, - PotionType.LONG_REGENERATION, - PotionType.STRONG_REGENERATION, - PotionType.STRENGTH, - PotionType.LONG_STRENGTH, - PotionType.STRONG_STRENGTH, - PotionType.WEAKNESS, - PotionType.LONG_WEAKNESS, - PotionType.LUCK, - PotionType.TURTLE_MASTER, - PotionType.LONG_TURTLE_MASTER, - PotionType.STRONG_TURTLE_MASTER, - PotionType.SLOW_FALLING, - PotionType.LONG_SLOW_FALLING, - }) { + for (KitPotion potionType : KitPotion.values()) { ItemStack potion = new ItemStack(base); PotionMeta meta = (PotionMeta) potion.getItemMeta(); - meta.setBasePotionType(potionType); + meta.setBasePotionType(potionType.getType()); potion.setItemMeta(meta); - inventory.setSlot(toClickable(potion), slot++); + inventory.setSlot(toClickable(KitCost.setPoints(potion, potionType.getCost() + (base == Material.TIPPED_ARROW ? 3 : 0)), potion), slot++); } Runnable redraw = () -> Bukkit.getScheduler().runTask(JavaPlugin.getProvidingPlugin(KitPvpPlugin.class), () -> { inventory.setOnClose(null); @@ -86,7 +47,7 @@ public void addItems(ClickableInventory inventory) { ItemStack showDrinkablePotions = new ItemStack(Material.POTION); PotionMeta meta = (PotionMeta) showDrinkablePotions.getItemMeta(); meta.setColor(Color.YELLOW); - meta.addItemFlags(ItemFlag.HIDE_ADDITIONAL_TOOLTIP); + meta.setHideTooltip(true); meta.displayName(Component.text("Show drinkable potions", NamedTextColor.GOLD).decoration(TextDecoration.ITALIC, false)); showDrinkablePotions.setItemMeta(meta); inventory.setSlot(new Clickable(showDrinkablePotions) { @@ -101,7 +62,7 @@ protected void clicked(@NotNull Player clicker) { ItemStack showSplashPotions = new ItemStack(Material.SPLASH_POTION); PotionMeta meta = (PotionMeta) showSplashPotions.getItemMeta(); meta.setColor(Color.YELLOW); - meta.addItemFlags(ItemFlag.HIDE_ADDITIONAL_TOOLTIP); + meta.setHideTooltip(true); meta.displayName(Component.text("Show splash potions", NamedTextColor.GOLD).decoration(TextDecoration.ITALIC, false)); showSplashPotions.setItemMeta(meta); inventory.setSlot(new Clickable(showSplashPotions) { @@ -116,7 +77,7 @@ protected void clicked(@NotNull Player clicker) { ItemStack showTippedArrows = new ItemStack(Material.TIPPED_ARROW); PotionMeta meta = (PotionMeta) showTippedArrows.getItemMeta(); meta.setColor(Color.YELLOW); - meta.addItemFlags(ItemFlag.HIDE_ADDITIONAL_TOOLTIP); + meta.setHideTooltip(true); meta.displayName(Component.text("Show tipped arrows", NamedTextColor.GOLD).decoration(TextDecoration.ITALIC, false)); showTippedArrows.setItemMeta(meta); inventory.setSlot(new Clickable(showTippedArrows) { diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/WeaponsAndToolsSelectionGui.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/WeaponsAndToolsSelectionGui.java index 99e47f83ff..e2cfc640a9 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/WeaponsAndToolsSelectionGui.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/gui/selection/WeaponsAndToolsSelectionGui.java @@ -1,12 +1,15 @@ package net.civmc.kitpvp.gui.selection; -import net.civmc.kitpvp.data.Kit; -import net.civmc.kitpvp.data.KitPvpDao; +import net.civmc.kitpvp.kit.Kit; +import net.civmc.kitpvp.kit.KitCustomItem; +import net.civmc.kitpvp.kit.KitItem; +import net.civmc.kitpvp.kit.KitPvpDao; import net.civmc.kitpvp.gui.EditKitGui; -import org.bukkit.Material; +import net.civmc.kitpvp.kit.KitCost; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import vg.civcraft.mc.civmodcore.inventory.CustomItem; +import vg.civcraft.mc.civmodcore.inventory.gui.Clickable; import vg.civcraft.mc.civmodcore.inventory.gui.ClickableInventory; public class WeaponsAndToolsSelectionGui extends ItemSelectionGui { @@ -15,71 +18,81 @@ public WeaponsAndToolsSelectionGui(KitPvpDao dao, Player player, int slot, Kit k super(dao, "Weapons and Tools", player, slot, kit, parent, gui); } + private Clickable toClickable(KitItem kitItem) { + ItemStack item = new ItemStack(kitItem.getItem()); + return toClickable(KitCost.setPoints(item, kitItem.getCost()), item); + } + + private Clickable toClickable(KitCustomItem kitItem) { + ItemStack item = CustomItem.getCustomItem(kitItem.getItem()); + return toClickable(KitCost.setPoints(item, kitItem.getCost()), item); + } + @Override public void addItems(ClickableInventory inventory) { - inventory.setSlot(toClickable(new ItemStack(Material.WOODEN_SWORD)), 0); - inventory.setSlot(toClickable(new ItemStack(Material.WOODEN_AXE)), 9); - inventory.setSlot(toClickable(new ItemStack(Material.WOODEN_PICKAXE)), 18); - inventory.setSlot(toClickable(new ItemStack(Material.WOODEN_SHOVEL)), 27); - inventory.setSlot(toClickable(new ItemStack(Material.WOODEN_HOE)), 36); - - inventory.setSlot(toClickable(new ItemStack(Material.GOLDEN_SWORD)), 1); - inventory.setSlot(toClickable(new ItemStack(Material.GOLDEN_AXE)), 10); - inventory.setSlot(toClickable(new ItemStack(Material.GOLDEN_PICKAXE)), 19); - inventory.setSlot(toClickable(new ItemStack(Material.GOLDEN_SHOVEL)), 28); - inventory.setSlot(toClickable(new ItemStack(Material.GOLDEN_HOE)), 37); - - inventory.setSlot(toClickable(new ItemStack(Material.STONE_SWORD)), 2); - inventory.setSlot(toClickable(new ItemStack(Material.STONE_AXE)), 11); - inventory.setSlot(toClickable(new ItemStack(Material.STONE_PICKAXE)), 20); - inventory.setSlot(toClickable(new ItemStack(Material.STONE_SHOVEL)), 29); - inventory.setSlot(toClickable(new ItemStack(Material.STONE_HOE)), 38); - - inventory.setSlot(toClickable(new ItemStack(Material.IRON_SWORD)), 3); - inventory.setSlot(toClickable(new ItemStack(Material.IRON_AXE)), 12); - inventory.setSlot(toClickable(new ItemStack(Material.IRON_PICKAXE)), 21); - inventory.setSlot(toClickable(new ItemStack(Material.IRON_SHOVEL)), 30); - inventory.setSlot(toClickable(new ItemStack(Material.IRON_HOE)), 39); - - inventory.setSlot(toClickable(new ItemStack(Material.DIAMOND_SWORD)), 4); - inventory.setSlot(toClickable(new ItemStack(Material.DIAMOND_AXE)), 13); - inventory.setSlot(toClickable(new ItemStack(Material.DIAMOND_PICKAXE)), 22); - inventory.setSlot(toClickable(new ItemStack(Material.DIAMOND_SHOVEL)), 31); - inventory.setSlot(toClickable(new ItemStack(Material.DIAMOND_HOE)), 40); - - inventory.setSlot(toClickable(new ItemStack(Material.NETHERITE_SWORD)), 5); - inventory.setSlot(toClickable(new ItemStack(Material.NETHERITE_AXE)), 14); - inventory.setSlot(toClickable(new ItemStack(Material.NETHERITE_PICKAXE)), 23); - inventory.setSlot(toClickable(new ItemStack(Material.NETHERITE_SHOVEL)), 32); - inventory.setSlot(toClickable(new ItemStack(Material.NETHERITE_HOE)), 41); - - inventory.setSlot(toClickable(new ItemStack(Material.SHIELD)), 6); - inventory.setSlot(toClickable(new ItemStack(Material.TRIDENT)), 7); - inventory.setSlot(toClickable(new ItemStack(Material.FLINT_AND_STEEL)), 8); - - inventory.setSlot(toClickable(new ItemStack(Material.SHEARS)), 15); - inventory.setSlot(toClickable(new ItemStack(Material.ENDER_PEARL)), 16); - inventory.setSlot(toClickable(new ItemStack(Material.FIREWORK_ROCKET)), 17); - - inventory.setSlot(toClickable(new ItemStack(Material.BUCKET)), 24); - inventory.setSlot(toClickable(new ItemStack(Material.WATER_BUCKET)), 25); - inventory.setSlot(toClickable(new ItemStack(Material.LAVA_BUCKET)), 26); - - inventory.setSlot(toClickable(new ItemStack(Material.MILK_BUCKET)), 33); - inventory.setSlot(toClickable(new ItemStack(Material.POWDER_SNOW_BUCKET)), 34); - inventory.setSlot(toClickable(new ItemStack(Material.ARROW)), 35); - - inventory.setSlot(toClickable(new ItemStack(Material.BOW)), 42); - inventory.setSlot(toClickable(new ItemStack(Material.CROSSBOW)), 43); - inventory.setSlot(toClickable(new ItemStack(Material.FISHING_ROD)), 44); - - inventory.setSlot(toClickable(CustomItem.getCustomItem("meteoric_iron_sword")), 46); - inventory.setSlot(toClickable(CustomItem.getCustomItem("meteoric_iron_sword_knockback1")), 47); - inventory.setSlot(toClickable(CustomItem.getCustomItem("meteoric_iron_sword_knockback")), 48); - inventory.setSlot(toClickable(CustomItem.getCustomItem("meteoric_iron_axe")), 49); - inventory.setSlot(toClickable(CustomItem.getCustomItem("meteoric_iron_pickaxe")), 50); - inventory.setSlot(toClickable(CustomItem.getCustomItem("backpack")), 51); - inventory.setSlot(toClickable(new ItemStack(Material.OAK_BOAT)), 52); - inventory.setSlot(toClickable(new ItemStack(Material.TNT_MINECART)), 53); + inventory.setSlot(toClickable(KitItem.WOODEN_SWORD), 0); + inventory.setSlot(toClickable(KitItem.WOODEN_AXE), 9); + inventory.setSlot(toClickable(KitItem.WOODEN_PICKAXE), 18); + inventory.setSlot(toClickable(KitItem.WOODEN_SHOVEL), 27); + inventory.setSlot(toClickable(KitItem.WOODEN_HOE), 36); + + inventory.setSlot(toClickable(KitItem.GOLDEN_SWORD), 1); + inventory.setSlot(toClickable(KitItem.GOLDEN_AXE), 10); + inventory.setSlot(toClickable(KitItem.GOLDEN_PICKAXE), 19); + inventory.setSlot(toClickable(KitItem.GOLDEN_SHOVEL), 28); + inventory.setSlot(toClickable(KitItem.GOLDEN_HOE), 37); + + inventory.setSlot(toClickable(KitItem.STONE_SWORD), 2); + inventory.setSlot(toClickable(KitItem.STONE_AXE), 11); + inventory.setSlot(toClickable(KitItem.STONE_PICKAXE), 20); + inventory.setSlot(toClickable(KitItem.STONE_SHOVEL), 29); + inventory.setSlot(toClickable(KitItem.STONE_HOE), 38); + + inventory.setSlot(toClickable(KitItem.IRON_SWORD), 3); + inventory.setSlot(toClickable(KitItem.IRON_AXE), 12); + inventory.setSlot(toClickable(KitItem.IRON_PICKAXE), 21); + inventory.setSlot(toClickable(KitItem.IRON_SHOVEL), 30); + inventory.setSlot(toClickable(KitItem.IRON_HOE), 39); + + inventory.setSlot(toClickable(KitItem.DIAMOND_SWORD), 4); + inventory.setSlot(toClickable(KitItem.DIAMOND_AXE), 13); + inventory.setSlot(toClickable(KitItem.DIAMOND_PICKAXE), 22); + inventory.setSlot(toClickable(KitItem.DIAMOND_SHOVEL), 31); + inventory.setSlot(toClickable(KitItem.DIAMOND_HOE), 40); + + inventory.setSlot(toClickable(KitItem.NETHERITE_SWORD), 5); + inventory.setSlot(toClickable(KitItem.NETHERITE_AXE), 14); + inventory.setSlot(toClickable(KitItem.NETHERITE_PICKAXE), 23); + inventory.setSlot(toClickable(KitItem.NETHERITE_SHOVEL), 32); + inventory.setSlot(toClickable(KitItem.NETHERITE_HOE), 41); + + inventory.setSlot(toClickable(KitItem.SHIELD), 6); + inventory.setSlot(toClickable(KitItem.TRIDENT), 7); + inventory.setSlot(toClickable(KitItem.FLINT_AND_STEEL), 8); + + inventory.setSlot(toClickable(KitItem.SHEARS), 15); + inventory.setSlot(toClickable(KitItem.ENDER_PEARL), 16); + inventory.setSlot(toClickable(KitItem.FIREWORK_ROCKET), 17); + + inventory.setSlot(toClickable(KitItem.BUCKET), 24); + inventory.setSlot(toClickable(KitItem.WATER_BUCKET), 25); + inventory.setSlot(toClickable(KitItem.LAVA_BUCKET), 26); + + inventory.setSlot(toClickable(KitItem.MILK_BUCKET), 33); + inventory.setSlot(toClickable(KitItem.POWDER_SNOW_BUCKET), 34); + inventory.setSlot(toClickable(KitItem.ARROW), 35); + + inventory.setSlot(toClickable(KitItem.BOW), 42); + inventory.setSlot(toClickable(KitItem.CROSSBOW), 43); + inventory.setSlot(toClickable(KitItem.FISHING_ROD), 44); + + inventory.setSlot(toClickable(KitCustomItem.METEORIC_IRON_SWORD), 46); + inventory.setSlot(toClickable(KitCustomItem.METEORIC_IRON_SWORD_KNOCKBACK1), 47); + inventory.setSlot(toClickable(KitCustomItem.METEORIC_IRON_SWORD_KNOCKBACK), 48); + inventory.setSlot(toClickable(KitCustomItem.METEORIC_IRON_AXE), 49); + inventory.setSlot(toClickable(KitCustomItem.METEORIC_IRON_PICKAXE), 50); + inventory.setSlot(toClickable(KitCustomItem.BACKPACK), 51); + inventory.setSlot(toClickable(KitItem.OAK_BOAT), 52); + inventory.setSlot(toClickable(KitItem.TNT_MINECART), 53); } } diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/data/Kit.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/kit/Kit.java similarity index 96% rename from plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/data/Kit.java rename to plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/kit/Kit.java index 1d8fab7172..ff92495b96 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/data/Kit.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/kit/Kit.java @@ -1,4 +1,4 @@ -package net.civmc.kitpvp.data; +package net.civmc.kitpvp.kit; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/kit/KitCategory.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/kit/KitCategory.java new file mode 100644 index 0000000000..9bbc4192a9 --- /dev/null +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/kit/KitCategory.java @@ -0,0 +1,6 @@ +package net.civmc.kitpvp.kit; + +public enum KitCategory { + BLOCK, + TOOL, FOOD, ARMOUR +} diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/kit/KitCost.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/kit/KitCost.java new file mode 100644 index 0000000000..05a240b67e --- /dev/null +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/kit/KitCost.java @@ -0,0 +1,113 @@ +package net.civmc.kitpvp.kit; + +import com.dre.brewery.Brew; +import com.dre.brewery.recipe.BRecipe; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.PotionMeta; +import vg.civcraft.mc.civmodcore.inventory.CustomItem; + +public class KitCost { + + public static final int MAX_POINTS = 50; + + public static final Map ENCHANTMENT_COST_PER_LEVEL = new HashMap<>(); + + static { + ENCHANTMENT_COST_PER_LEVEL.put(Enchantment.PROTECTION, 1); + ENCHANTMENT_COST_PER_LEVEL.put(Enchantment.SHARPNESS, 1); + ENCHANTMENT_COST_PER_LEVEL.put(Enchantment.KNOCKBACK, 1); + ENCHANTMENT_COST_PER_LEVEL.put(Enchantment.POWER, 1); + ENCHANTMENT_COST_PER_LEVEL.put(Enchantment.THORNS, 1); + ENCHANTMENT_COST_PER_LEVEL.put(Enchantment.SWIFT_SNEAK, 100); + ENCHANTMENT_COST_PER_LEVEL.put(Enchantment.PIERCING, 1); + } + + public static ItemStack setPoints(ItemStack item, int points) { + ItemStack cloned = item.clone(); + if (points == 0) { + return cloned; + } + ItemMeta clonedMeta = cloned.getItemMeta(); + List lore = clonedMeta.hasLore() ? clonedMeta.lore() : new ArrayList<>(); + lore.add(0, Component.text(points + " point" + (points == 1 ? "" : "s"), NamedTextColor.YELLOW).decoration(TextDecoration.ITALIC, false)); + clonedMeta.lore(lore); + cloned.setItemMeta(clonedMeta); + return cloned; + } + + public static int getCost(ItemStack[] items) { + int cost = 0; + for (ItemStack item : items) { + cost += getCost(item); + } + return cost; + } + + public static int getCost(ItemStack item) { + if (item.isEmpty()) { + return 0; + } + + String key = CustomItem.getCustomItemKey(item); + if (key != null) { + for (KitCustomItem customItem : KitCustomItem.values()) { + if (customItem.getItem().equals(key)) { + return customItem.getCost(); + } + } + } + + int cost = 0; + + for (Map.Entry entry : item.getEnchantments().entrySet()) { + cost += ENCHANTMENT_COST_PER_LEVEL.getOrDefault(entry.getKey(), 0) * entry.getValue(); + } + + for (KitItem kitItem : KitItem.values()) { + if (item.getType() == kitItem.getItem()) { + cost += kitItem.getCost(); + break; + } + } + + if (item.hasItemMeta() && item.getItemMeta() instanceof PotionMeta meta) { + if (item.getType() == Material.TIPPED_ARROW) { + cost += 4; + } + for (KitPotion potion : KitPotion.values()) { + if (meta.getBasePotionType() == potion.getType()) { + cost += potion.getCost(); + } + } + } + + if (Bukkit.getPluginManager().isPluginEnabled("BreweryX")) { + Brew brew = Brew.get(item); + if (brew != null) { + for (KitDrugs drugs : KitDrugs.values()) { + BRecipe recipe = BRecipe.getMatching(drugs.getBrew()); + if (recipe != null && recipe.getRecipeName().equals(brew.getCurrentRecipe().getRecipeName())) { + cost += drugs.getCost(); + } + } + } + } + + if (item.getItemMeta().isUnbreakable()) { + cost += 100; + } + + return cost; + } +} diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/kit/KitCustomItem.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/kit/KitCustomItem.java new file mode 100644 index 0000000000..02262fc080 --- /dev/null +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/kit/KitCustomItem.java @@ -0,0 +1,31 @@ +package net.civmc.kitpvp.kit; + +public enum KitCustomItem { + METEORIC_IRON_HELMET("meteoric_iron_helmet", 16), + METEORIC_IRON_CHESTPLATE("meteoric_iron_chestplate", 18), + METEORIC_IRON_LEGGINGS("meteoric_iron_leggings", 17), + METEORIC_IRON_BOOTS("meteoric_iron_boots", 15), + METEORIC_IRON_SWORD("meteoric_iron_sword", 16), + METEORIC_IRON_SWORD_KNOCKBACK1("meteoric_iron_sword_knockback1", 16), + METEORIC_IRON_SWORD_KNOCKBACK("meteoric_iron_sword_knockback", 16), + METEORIC_IRON_AXE("meteoric_iron_axe", 12), + METEORIC_IRON_PICKAXE("meteoric_iron_pickaxe", 12), + BACKPACK("backpack", 100), + ; + + private final String item; + private final int cost; + + KitCustomItem(String item, int cost) { + this.item = item; + this.cost = cost; + } + + public String getItem() { + return item; + } + + public int getCost() { + return cost; + } +} diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/kit/KitDrugs.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/kit/KitDrugs.java new file mode 100644 index 0000000000..42380f9fe9 --- /dev/null +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/kit/KitDrugs.java @@ -0,0 +1,41 @@ +package net.civmc.kitpvp.kit; + +public enum KitDrugs { + Cyanide("Cyanide", 0), + Cannabis("Cannabis", 6), + Meth("Meth", 6), + Blue_Meth("Blue Meth", 6), + Heroin("Heroin", 6), + Oestrogen("Oestrogen", 6), + Caffeine("Caffeine", 6), + Ivermectin("Ivermectin", 6), + DMT("DMT", 6), + Xanax("Xanax", 6), + Steroids("Steroids", 6), + Testosterone("Testosterone", 6), + Vicodin("Vicodin", 6), + Yakult("Yakult", 6), + Cocaine("Cocaine", 6), + Speed("Speed", 6), + NAD("NAD+", 6), + Epinephrine("Epinephrine", 6), + Firefoam("Firefoam", 6), + Nitroglycerin("Nitroglycerin", 6), + ; + + private final String item; + private final int cost; + + KitDrugs(String item, int cost) { + this.item = item; + this.cost = cost; + } + + public String getBrew() { + return item; + } + + public int getCost() { + return cost; + } +} diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/kit/KitItem.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/kit/KitItem.java new file mode 100644 index 0000000000..a845f67c7f --- /dev/null +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/kit/KitItem.java @@ -0,0 +1,183 @@ +package net.civmc.kitpvp.kit; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import org.bukkit.Material; + +public enum KitItem { + LEATHER_HELMET(Material.LEATHER_HELMET, KitCategory.ARMOUR, 0), + LEATHER_CHESTPLATE(Material.LEATHER_CHESTPLATE, KitCategory.ARMOUR, 0), + LEATHER_LEGGINGS(Material.LEATHER_LEGGINGS, KitCategory.ARMOUR, 0), + LEATHER_BOOTS(Material.LEATHER_BOOTS, KitCategory.ARMOUR, 0), + + GOLDEN_HELMET(Material.GOLDEN_HELMET, KitCategory.ARMOUR, 0), + GOLDEN_CHESTPLATE(Material.GOLDEN_CHESTPLATE, KitCategory.ARMOUR, 0), + GOLDEN_LEGGINGS(Material.GOLDEN_LEGGINGS, KitCategory.ARMOUR, 0), + GOLDEN_BOOTS(Material.GOLDEN_BOOTS, KitCategory.ARMOUR, 0), + + CHAINMAIL_HELMET(Material.CHAINMAIL_HELMET, KitCategory.ARMOUR, 0), + CHAINMAIL_CHESTPLATE(Material.CHAINMAIL_CHESTPLATE, KitCategory.ARMOUR, 0), + CHAINMAIL_LEGGINGS(Material.CHAINMAIL_LEGGINGS, KitCategory.ARMOUR, 0), + CHAINMAIL_BOOTS(Material.CHAINMAIL_BOOTS, KitCategory.ARMOUR, 0), + + IRON_HELMET(Material.IRON_HELMET, KitCategory.ARMOUR, 0), + IRON_CHESTPLATE(Material.IRON_CHESTPLATE, KitCategory.ARMOUR, 0), + IRON_LEGGINGS(Material.IRON_LEGGINGS, KitCategory.ARMOUR, 0), + IRON_BOOTS(Material.IRON_BOOTS, KitCategory.ARMOUR, 0), + + DIAMOND_HELMET(Material.DIAMOND_HELMET, KitCategory.ARMOUR, 1), + DIAMOND_CHESTPLATE(Material.DIAMOND_CHESTPLATE, KitCategory.ARMOUR, 1), + DIAMOND_LEGGINGS(Material.DIAMOND_LEGGINGS, KitCategory.ARMOUR, 1), + DIAMOND_BOOTS(Material.DIAMOND_BOOTS, KitCategory.ARMOUR, 1), + + NETHERITE_HELMET(Material.NETHERITE_HELMET, KitCategory.ARMOUR, 7), + NETHERITE_CHESTPLATE(Material.NETHERITE_CHESTPLATE, KitCategory.ARMOUR, 5), + NETHERITE_LEGGINGS(Material.NETHERITE_LEGGINGS, KitCategory.ARMOUR, 5), + NETHERITE_BOOTS(Material.NETHERITE_BOOTS, KitCategory.ARMOUR, 7), + + OBSIDIAN(Material.OBSIDIAN, KitCategory.BLOCK, 3), + COBBLESTONE(Material.COBBLESTONE, KitCategory.BLOCK, 0), + COBWEB(Material.COBWEB, KitCategory.BLOCK, 6), + IRON_DOOR(Material.IRON_DOOR, KitCategory.BLOCK, 0), + OAK_DOOR(Material.OAK_DOOR, KitCategory.BLOCK, 0), + OAK_PLANKS(Material.OAK_PLANKS, KitCategory.BLOCK, 0), + OAK_LOG(Material.OAK_LOG, KitCategory.BLOCK, 0), + CRAFTING_TABLE(Material.CRAFTING_TABLE, KitCategory.BLOCK, 0), + WHITE_WOOL(Material.WHITE_WOOL, KitCategory.BLOCK, 0), + BONE_BLOCK(Material.BONE_BLOCK, KitCategory.BLOCK, 0), + SANDSTONE(Material.SANDSTONE, KitCategory.BLOCK, 0), + GRAVEL(Material.GRAVEL, KitCategory.BLOCK, 0), + ICE(Material.ICE, KitCategory.BLOCK, 0), + PACKED_ICE(Material.PACKED_ICE, KitCategory.BLOCK, 0), + BLUE_ICE(Material.BLUE_ICE, KitCategory.BLOCK, 0), + OAK_TRAPDOOR(Material.OAK_TRAPDOOR, KitCategory.BLOCK, 0), + HAY_BLOCK(Material.HAY_BLOCK, KitCategory.BLOCK, 0), + SOUL_SAND(Material.SOUL_SAND, KitCategory.BLOCK, 0), + SOUL_SOIL(Material.SOUL_SOIL, KitCategory.BLOCK, 0), + DIRT(Material.DIRT, KitCategory.BLOCK, 0), + SAND(Material.SAND, KitCategory.BLOCK, 0), + STONE(Material.STONE, KitCategory.BLOCK, 0), + TNT(Material.TNT, KitCategory.BLOCK, 10), + SCAFFOLDING(Material.SCAFFOLDING, KitCategory.BLOCK, 0), + SLIME_BLOCK(Material.SLIME_BLOCK, KitCategory.BLOCK, 0), + HONEY_BLOCK(Material.HONEY_BLOCK, KitCategory.BLOCK, 0), + GLOWSTONE(Material.GLOWSTONE, KitCategory.BLOCK, 0), + GLASS(Material.GLASS, KitCategory.BLOCK, 2), + NETHERRACK(Material.NETHERRACK, KitCategory.BLOCK, 0), + RAIL(Material.RAIL, KitCategory.BLOCK, 0), + IRON_BLOCK(Material.IRON_BLOCK, KitCategory.BLOCK, 0), + GOLD_BLOCK(Material.GOLD_BLOCK, KitCategory.BLOCK, 0), + BEACON(Material.BEACON, KitCategory.BLOCK, 10), + COBBLED_DEEPSLATE(Material.COBBLED_DEEPSLATE, KitCategory.BLOCK, 0), + LAPIS_BLOCK(Material.LAPIS_BLOCK, KitCategory.BLOCK, 0), + REDSTONE_BLOCK(Material.REDSTONE_BLOCK, KitCategory.BLOCK, 0), + COPPER_BLOCK(Material.COPPER_BLOCK, KitCategory.BLOCK, 0), + BIRCH_PLANKS(Material.BIRCH_PLANKS, KitCategory.BLOCK, 0), + DARK_OAK_PLANKS(Material.DARK_OAK_PLANKS, KitCategory.BLOCK, 0), + + WOODEN_SWORD(Material.WOODEN_SWORD, KitCategory.TOOL, 0), + WOODEN_AXE(Material.WOODEN_AXE, KitCategory.TOOL, 0), + WOODEN_PICKAXE(Material.WOODEN_PICKAXE, KitCategory.TOOL, 0), + WOODEN_SHOVEL(Material.WOODEN_SHOVEL, KitCategory.TOOL, 0), + WOODEN_HOE(Material.WOODEN_HOE, KitCategory.TOOL, 0), + + GOLDEN_SWORD(Material.GOLDEN_SWORD, KitCategory.TOOL, 0), + GOLDEN_AXE(Material.GOLDEN_AXE, KitCategory.TOOL, 0), + GOLDEN_PICKAXE(Material.GOLDEN_PICKAXE, KitCategory.TOOL, 0), + GOLDEN_SHOVEL(Material.GOLDEN_SHOVEL, KitCategory.TOOL, 0), + GOLDEN_HOE(Material.GOLDEN_HOE, KitCategory.TOOL, 0), + + STONE_SWORD(Material.STONE_SWORD, KitCategory.TOOL, 0), + STONE_AXE(Material.STONE_AXE, KitCategory.TOOL, 0), + STONE_PICKAXE(Material.STONE_PICKAXE, KitCategory.TOOL, 0), + STONE_SHOVEL(Material.STONE_SHOVEL, KitCategory.TOOL, 0), + STONE_HOE(Material.STONE_HOE, KitCategory.TOOL, 0), + + IRON_SWORD(Material.IRON_SWORD, KitCategory.TOOL, 0), + IRON_AXE(Material.IRON_AXE, KitCategory.TOOL, 0), + IRON_PICKAXE(Material.IRON_PICKAXE, KitCategory.TOOL, 0), + IRON_SHOVEL(Material.IRON_SHOVEL, KitCategory.TOOL, 0), + IRON_HOE(Material.IRON_HOE, KitCategory.TOOL, 0), + + DIAMOND_SWORD(Material.DIAMOND_SWORD, KitCategory.TOOL, 1), + DIAMOND_AXE(Material.DIAMOND_AXE, KitCategory.TOOL, 1), + DIAMOND_PICKAXE(Material.DIAMOND_PICKAXE, KitCategory.TOOL, 1), + DIAMOND_SHOVEL(Material.DIAMOND_SHOVEL, KitCategory.TOOL, 1), + DIAMOND_HOE(Material.DIAMOND_HOE, KitCategory.TOOL, 1), + + NETHERITE_SWORD(Material.NETHERITE_SWORD, KitCategory.TOOL, 5), + NETHERITE_AXE(Material.NETHERITE_AXE, KitCategory.TOOL, 6), + NETHERITE_PICKAXE(Material.NETHERITE_PICKAXE, KitCategory.TOOL, 5), + NETHERITE_SHOVEL(Material.NETHERITE_SHOVEL, KitCategory.TOOL, 5), + NETHERITE_HOE(Material.NETHERITE_HOE, KitCategory.TOOL, 5), + + SHIELD(Material.SHIELD, KitCategory.TOOL, 0), + TRIDENT(Material.TRIDENT, KitCategory.TOOL, 4), + FLINT_AND_STEEL(Material.FLINT_AND_STEEL, KitCategory.TOOL, 0), + + SHEARS(Material.SHEARS, KitCategory.TOOL, 0), + ENDER_PEARL(Material.ENDER_PEARL, KitCategory.TOOL, 2), + FIREWORK_ROCKET(Material.FIREWORK_ROCKET, KitCategory.TOOL, 1), + + BUCKET(Material.BUCKET, KitCategory.TOOL, 0), + WATER_BUCKET(Material.WATER_BUCKET, KitCategory.TOOL, 0), + LAVA_BUCKET(Material.LAVA_BUCKET, KitCategory.TOOL, 0), + + MILK_BUCKET(Material.MILK_BUCKET, KitCategory.TOOL, 0), + POWDER_SNOW_BUCKET(Material.POWDER_SNOW_BUCKET, KitCategory.TOOL, 0), + ARROW(Material.ARROW, KitCategory.TOOL, 0), + + BOW(Material.BOW, KitCategory.TOOL, 1), + CROSSBOW(Material.CROSSBOW, KitCategory.TOOL, 4), + FISHING_ROD(Material.FISHING_ROD, KitCategory.TOOL, 1), + OAK_BOAT(Material.OAK_BOAT, KitCategory.TOOL, 1), + TNT_MINECART(Material.TNT_MINECART, KitCategory.TOOL, 3), + + ELYTRA(Material.ELYTRA, KitCategory.ARMOUR, 100), + TURTLE_HELMET(Material.TURTLE_HELMET, KitCategory.ARMOUR, 2), + + COOKED_PORKCHOP(Material.COOKED_PORKCHOP, KitCategory.FOOD, 0), + GOLDEN_APPLE(Material.GOLDEN_APPLE, KitCategory.FOOD, 5), + BAKED_POTATO(Material.BAKED_POTATO, KitCategory.FOOD, 0), + COOKED_BEEF(Material.COOKED_BEEF, KitCategory.FOOD, 0), + GOLDEN_CARROT(Material.GOLDEN_CARROT, KitCategory.FOOD, 0), + SWEET_BERRIES(Material.SWEET_BERRIES, KitCategory.FOOD, 0), + PUMPKIN_PIE(Material.PUMPKIN_PIE, KitCategory.FOOD, 0), + BREAD(Material.BREAD, KitCategory.FOOD, 0), + COOKED_CHICKEN(Material.COOKED_CHICKEN, KitCategory.FOOD, 0), + CHORUS_FRUIT(Material.CHORUS_FRUIT, KitCategory.FOOD, 10), + ; + + private static final Map> itemsByCategory = new EnumMap<>(KitCategory.class); + + static { + for (KitItem item : values()) { + itemsByCategory.computeIfAbsent(item.category, k -> new ArrayList<>()).add(item); + } + } + + private final Material item; + private final KitCategory category; + private final int cost; + + KitItem(Material item, KitCategory category, int cost) { + this.item = item; + this.category = category; + this.cost = cost; + } + + public Material getItem() { + return item; + } + + public int getCost() { + return cost; + } + + public static List getItems(KitCategory category) { + return Collections.unmodifiableList(itemsByCategory.get(category)); + } +} diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/kit/KitPotion.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/kit/KitPotion.java new file mode 100644 index 0000000000..f17aa35a68 --- /dev/null +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/kit/KitPotion.java @@ -0,0 +1,61 @@ +package net.civmc.kitpvp.kit; + +import org.bukkit.potion.PotionType; + +public enum KitPotion { + NIGHT_VISION(PotionType.NIGHT_VISION, 0), + LONG_NIGHT_VISION(PotionType.LONG_NIGHT_VISION, 0), + INVISIBILITY(PotionType.INVISIBILITY, 0), + LONG_INVISIBILITY(PotionType.LONG_INVISIBILITY, 0), + LEAPING(PotionType.LEAPING, 0), + LONG_LEAPING(PotionType.LONG_LEAPING, 0), + STRONG_LEAPING(PotionType.STRONG_LEAPING, 0), + FIRE_RESISTANCE(PotionType.FIRE_RESISTANCE, 0), + LONG_FIRE_RESISTANCE(PotionType.LONG_FIRE_RESISTANCE, 0), + SWIFTNESS(PotionType.SWIFTNESS, 0), + LONG_SWIFTNESS(PotionType.LONG_SWIFTNESS, 0), + STRONG_SWIFTNESS(PotionType.STRONG_SWIFTNESS, 0), + SLOWNESS(PotionType.SLOWNESS, 1), + LONG_SLOWNESS(PotionType.LONG_SLOWNESS, 2), + STRONG_SLOWNESS(PotionType.STRONG_SLOWNESS, 2), + WATER_BREATHING(PotionType.WATER_BREATHING, 0), + LONG_WATER_BREATHING(PotionType.LONG_WATER_BREATHING, 0), + HEALING(PotionType.HEALING, 0), + STRONG_HEALING(PotionType.STRONG_HEALING, 0), + HARMING(PotionType.HARMING, 1), + STRONG_HARMING(PotionType.STRONG_HARMING, 2), + POISON(PotionType.POISON, 1), + LONG_POISON(PotionType.LONG_POISON, 2), + STRONG_POISON(PotionType.STRONG_POISON,2), + REGENERATION(PotionType.REGENERATION, 0), + LONG_REGENERATION(PotionType.LONG_REGENERATION, 0), + STRONG_REGENERATION(PotionType.STRONG_REGENERATION, 0), + STRENGTH(PotionType.STRENGTH, 0), + LONG_STRENGTH(PotionType.LONG_STRENGTH, 0), + STRONG_STRENGTH(PotionType.STRONG_STRENGTH, 0), + WEAKNESS(PotionType.WEAKNESS, 1), + LONG_WEAKNESS(PotionType.LONG_WEAKNESS, 3), + LUCK(PotionType.LUCK, 0), + TURTLE_MASTER(PotionType.TURTLE_MASTER, 5), + LONG_TURTLE_MASTER(PotionType.LONG_TURTLE_MASTER, 5), + STRONG_TURTLE_MASTER(PotionType.STRONG_TURTLE_MASTER, 5), + SLOW_FALLING(PotionType.SLOW_FALLING, 0), + LONG_SLOW_FALLING(PotionType.LONG_SLOW_FALLING, 0), + ; + + private final PotionType type; + private final int cost; + + KitPotion(PotionType type, int cost) { + this.type = type; + this.cost = cost; + } + + public PotionType getType() { + return type; + } + + public int getCost() { + return cost; + } +} diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/data/KitPvpDao.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/kit/KitPvpDao.java similarity index 89% rename from plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/data/KitPvpDao.java rename to plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/kit/KitPvpDao.java index d06382e765..052828e835 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/data/KitPvpDao.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/kit/KitPvpDao.java @@ -1,4 +1,4 @@ -package net.civmc.kitpvp.data; +package net.civmc.kitpvp.kit; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; @@ -7,6 +7,7 @@ public interface KitPvpDao { Kit getKit(String name, UUID player); + Kit getKit(int id); List getKits(UUID player); Kit createKit(String name, UUID player); Kit setPublicKit(int id, boolean isPublic); diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/AutoQueueSetting.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/AutoQueueSetting.java new file mode 100644 index 0000000000..f40903e9b6 --- /dev/null +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/AutoQueueSetting.java @@ -0,0 +1,23 @@ +package net.civmc.kitpvp.ranked; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.java.JavaPlugin; +import vg.civcraft.mc.civmodcore.players.settings.impl.IntegerSetting; + +public final class AutoQueueSetting extends IntegerSetting { + + public AutoQueueSetting(JavaPlugin plugin) { + super(plugin, 15, "Auto queue", "autoqueue", new ItemStack(Material.NETHER_STAR), + "The time it takes for you to be auto requeued. Set to 0 to disable.", false); + } + + @Override + public String toText(Integer value) { + if (value == null || value < 1) { + return "disabled"; + } + return Integer.toString(value); + } + +} diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/Elo.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/Elo.java new file mode 100644 index 0000000000..70745db4bd --- /dev/null +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/Elo.java @@ -0,0 +1,21 @@ +package net.civmc.kitpvp.ranked; + +public class Elo { + + private static final double K = 32; + + public static EloChange getChange(double player, double opponent) { + double playerExpected = 1.0 / (1.0 + Math.pow(10, (opponent - player) / 400)); + + return new EloChange( + K * (1 - playerExpected), + K * (0.5 - playerExpected), + K * (0 - playerExpected) + ); + } + + public record EloChange(double win, double draw, double loss) { + + } + +} diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/EloCommand.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/EloCommand.java new file mode 100644 index 0000000000..4827a4b572 --- /dev/null +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/EloCommand.java @@ -0,0 +1,34 @@ +package net.civmc.kitpvp.ranked; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +public class EloCommand implements CommandExecutor { + + private final RankedPlayers players; + + public EloCommand(RankedPlayers players) { + this.players = players; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String @NotNull [] args) { + if (!(sender instanceof Player player)) { + return false; + } + + int elo = (int) Math.round(players.getElo(player.getUniqueId())); + int rank = players.getRank(player.getUniqueId()); + + player.sendMessage(Component.empty().append(Component.text("Elo: ", NamedTextColor.GOLD)).append(Component.text(elo, NamedTextColor.YELLOW))); + + String rankPlace = rank < 0 ? "unknown" : "#" + (rank + 1); + player.sendMessage(Component.empty().append(Component.text("Rank: ", NamedTextColor.GOLD)).append(Component.text(rankPlace, NamedTextColor.YELLOW))); + return true; + } +} diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/KitPvpMenu.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/KitPvpMenu.java new file mode 100644 index 0000000000..61572534f6 --- /dev/null +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/KitPvpMenu.java @@ -0,0 +1,18 @@ +package net.civmc.kitpvp.ranked; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSettingAPI; +import vg.civcraft.mc.civmodcore.players.settings.gui.MenuSection; + +/** + * The menu for RailSwitch. This is what all RailSwitch settings will be registered to. + */ +public final class KitPvpMenu extends MenuSection { + + public KitPvpMenu() { + super("Kit PvP", "PvP server settings", PlayerSettingAPI.getMainMenu(), + new ItemStack(Material.TNT)); + } + +} diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/RankedCommand.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/RankedCommand.java new file mode 100644 index 0000000000..2bdfa060d0 --- /dev/null +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/RankedCommand.java @@ -0,0 +1,37 @@ +package net.civmc.kitpvp.ranked; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +public class RankedCommand implements CommandExecutor { + + private final RankedQueueManager rankedQueueManager; + + public RankedCommand(RankedQueueManager rankedQueueManager) { + this.rankedQueueManager = rankedQueueManager; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String @NotNull [] args) { + if (!(sender instanceof Player player)) { + return false; + } + + if (this.rankedQueueManager.isInQueue(player) || (args.length > 0 && args[0].equals("leave"))) { + if (this.rankedQueueManager.leaveQueue(player)) { + player.sendMessage(Component.text("You have left the ranked queue", NamedTextColor.YELLOW)); + } else { + player.sendMessage(Component.text("You are not in the ranked queue", NamedTextColor.GRAY)); + } + } else { + this.rankedQueueManager.joinQueue(player, false); + } + + return true; + } +} diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/RankedDao.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/RankedDao.java new file mode 100644 index 0000000000..c2be1431a1 --- /dev/null +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/RankedDao.java @@ -0,0 +1,17 @@ +package net.civmc.kitpvp.ranked; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public interface RankedDao { + int getKit(UUID player); + void setKit(UUID player, int kit); + double getElo(UUID player); + void updateElo(UUID player, UUID opponent, UUID winner); + List getTop(int n); + + Map getAll(); + + record Rank(UUID player, double elo) {} +} diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/RankedMatch.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/RankedMatch.java new file mode 100644 index 0000000000..a9cf82b4c1 --- /dev/null +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/RankedMatch.java @@ -0,0 +1,106 @@ +package net.civmc.kitpvp.ranked; + +import net.civmc.kitpvp.arena.LoadedArena; +import org.bukkit.entity.Player; +import java.time.Instant; +import java.util.Objects; + +public final class RankedMatch { + private final Player player; + private final double playerElo; + private final Player opponent; + private final double opponentElo; + private final LoadedArena arena; + private final Instant started; + private final boolean unranked; + + private double playerDamageDealt; + private double opponentDamageDealt; + + public RankedMatch(Player player, double playerElo, Player opponent, double opponentElo, LoadedArena arena, Instant started, boolean unranked) { + this.player = player; + this.playerElo = playerElo; + this.opponent = opponent; + this.opponentElo = opponentElo; + this.arena = arena; + this.started = started; + this.unranked = unranked; + } + + public Player player() { + return player; + } + + public double playerElo() { + return playerElo; + } + + public Player opponent() { + return opponent; + } + + public double opponentElo() { + return opponentElo; + } + + public LoadedArena arena() { + return arena; + } + + public Instant started() { + return started; + } + + public boolean unranked() { + return unranked; + } + + public double getPlayerDamageDealt() { + return playerDamageDealt; + } + + public double getOpponentDamageDealt() { + return opponentDamageDealt; + } + + public void addPlayerDamageDealt(double playerDamageDealt) { + this.playerDamageDealt += playerDamageDealt; + } + + public void addOpponentDamageDealt(double opponentDamageDealt) { + this.opponentDamageDealt += opponentDamageDealt; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (RankedMatch) obj; + return Objects.equals(this.player, that.player) && + Double.doubleToLongBits(this.playerElo) == Double.doubleToLongBits(that.playerElo) && + Objects.equals(this.opponent, that.opponent) && + Double.doubleToLongBits(this.opponentElo) == Double.doubleToLongBits(that.opponentElo) && + Objects.equals(this.arena, that.arena) && + Objects.equals(this.started, that.started) && + this.unranked == that.unranked; + } + + @Override + public int hashCode() { + return Objects.hash(player, playerElo, opponent, opponentElo, arena, started, unranked); + } + + @Override + public String toString() { + return "RankedMatch[" + + "player=" + player + ", " + + "playerElo=" + playerElo + ", " + + "opponent=" + opponent + ", " + + "opponentElo=" + opponentElo + ", " + + "arena=" + arena + ", " + + "started=" + started + ", " + + "unranked=" + unranked + ']'; + } + + +} diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/RankedPlaceholders.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/RankedPlaceholders.java new file mode 100644 index 0000000000..c61dc9230f --- /dev/null +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/RankedPlaceholders.java @@ -0,0 +1,51 @@ +package net.civmc.kitpvp.ranked; + +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class RankedPlaceholders extends PlaceholderExpansion { + + private RankedPlayers players; + + public RankedPlaceholders(RankedPlayers players) { + this.players = players; + } + + @Override + public @Nullable String onPlaceholderRequest(Player player, @NotNull String params) { + params = params.toLowerCase(); + + if (params.equalsIgnoreCase("elo")) { + return Long.toString(Math.round(players.getElo(player.getUniqueId()))); + } else { + return null; + } + } + + @Override + public @NotNull String getIdentifier() { + return "kitpvp"; + } + + @Override + public @NotNull String getAuthor() { + return "Okx"; + } + + @Override + public @NotNull String getVersion() { + return "1.0"; + } + + @Override + public boolean persist() { + return true; + } + + @Override + public boolean canRegister() { + return true; + } +} diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/RankedPlayers.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/RankedPlayers.java new file mode 100644 index 0000000000..a2d69b5f5f --- /dev/null +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/RankedPlayers.java @@ -0,0 +1,54 @@ +package net.civmc.kitpvp.ranked; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import net.civmc.kitpvp.KitPvpPlugin; +import org.bukkit.Bukkit; +import org.bukkit.plugin.java.JavaPlugin; + +public class RankedPlayers { + + private Map elo; + private List eloList = new ArrayList<>(); + + public RankedPlayers(RankedDao dao) { + this.elo = dao.getAll(); + this.eloList = calculateList(this.elo); + Bukkit.getScheduler().runTaskTimerAsynchronously(JavaPlugin.getPlugin(KitPvpPlugin.class), () -> { + Map all = dao.getAll(); + List list = calculateList(all); + Bukkit.getScheduler().runTask(JavaPlugin.getPlugin(KitPvpPlugin.class), () -> { + this.elo = all; + this.eloList = list; + }); + }, 20 * 60 * 5, 20 * 60 * 5); + } + + public void setElo(UUID player, double elo) { + this.elo.put(player, elo); + this.eloList = calculateList(this.elo); + } + + private List calculateList(Map all) { + return all.entrySet().stream() + .sorted(Comparator.>comparingDouble(Map.Entry::getValue).reversed()) + .map(Map.Entry::getKey) + .toList(); + } + + public double getElo(UUID player) { + return elo.getOrDefault(player, 1000D); + } + + public int getRank(UUID player) { + for (int i = 0; i < eloList.size(); i++) { + if (eloList.get(i).equals(player)) { + return i; + } + } + return -1; + } +} diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/RankedQueueListener.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/RankedQueueListener.java new file mode 100644 index 0000000000..1e98234748 --- /dev/null +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/RankedQueueListener.java @@ -0,0 +1,73 @@ +package net.civmc.kitpvp.ranked; + +import net.civmc.kitpvp.KitPvpPlugin; +import net.civmc.kitpvp.snapshot.DeathListener; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.plugin.java.JavaPlugin; + +public class RankedQueueListener implements Listener { + + private final RankedQueueManager rankedQueueManager; + private final DeathListener deathListener; + + public RankedQueueListener(RankedQueueManager rankedQueueManager, DeathListener deathListener) { + this.rankedQueueManager = rankedQueueManager; + this.deathListener = deathListener; + } + + @EventHandler + public void on(PlayerQuitEvent event) { + Player player = event.getPlayer(); + rankedQueueManager.leaveQueue(player); + rankedQueueManager.leaveUnrankedQueue(player); + rankedQueueManager.loseMatch(player); + } + + @EventHandler + public void on(PlayerDeathEvent event) { + rankedQueueManager.loseMatch(event.getPlayer()); + } + + @EventHandler + public void on(PlayerTeleportEvent event) { + if (event.getTo().getWorld().equals(event.getFrom().getWorld())) { + return; + } + if (event.getTo().getWorld().getName().startsWith("rankedarena.")) { + return; + } + if (rankedQueueManager.loseMatch(event.getPlayer())) { + JavaPlugin.getPlugin(KitPvpPlugin.class).info(event.getPlayer().getName() + " forfeited match by teleporting"); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + public void on(EntityDamageEvent event) { + if (!(event.getEntity() instanceof Player player)) { + return; + } + + RankedMatch match = rankedQueueManager.getMatch(player); + if (match == null) { + return; + } + if (match.opponent().equals(player)) { + match.addPlayerDamageDealt(event.getFinalDamage()); + } else { + match.addOpponentDamageDealt(event.getFinalDamage()); + } + + if (player.getHealth() - event.getFinalDamage() < 0) { + deathListener.die(player); + event.setCancelled(true); + rankedQueueManager.loseMatch(player); + } + } +} diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/RankedQueueManager.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/RankedQueueManager.java new file mode 100644 index 0000000000..3702c9ece6 --- /dev/null +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/RankedQueueManager.java @@ -0,0 +1,625 @@ +package net.civmc.kitpvp.ranked; + +import com.github.maxopoly.finale.Finale; +import com.github.maxopoly.finale.listeners.PearlCoolDownListener; +import java.text.DecimalFormat; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.SequencedMap; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; +import java.util.logging.Level; +import net.civmc.kitpvp.KitApplier; +import net.civmc.kitpvp.KitPvpPlugin; +import net.civmc.kitpvp.arena.ArenaManager; +import net.civmc.kitpvp.arena.data.Arena; +import net.civmc.kitpvp.kit.Kit; +import net.civmc.kitpvp.kit.KitCost; +import net.civmc.kitpvp.kit.KitPvpDao; +import net.civmc.kitpvp.spawn.SpawnProvider; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import org.bukkit.Bukkit; +import org.bukkit.GameMode; +import org.bukkit.Location; +import org.bukkit.Sound; +import org.bukkit.World; +import org.bukkit.WorldBorder; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +public class RankedQueueManager { + + private final KitPvpDao kitDao; + private final RankedDao dao; + private final ArenaManager arenaManager; + private final SpawnProvider spawnProvider; + + private final Arena arena; + + private final SequencedMap queued = new LinkedHashMap<>(); + private final SequencedMap unrankedQueued = new LinkedHashMap<>(); + + private final Map recentMatches = new HashMap<>(); + + private final List matches = new ArrayList<>(); + + private final AutoQueueSetting setting; + + private final RankedPlayers players; + + public RankedQueueManager(KitPvpDao kitDao, RankedDao dao, ArenaManager arenaManager, SpawnProvider spawnProvider, Arena arena, RankedPlayers players) { + this.kitDao = kitDao; + this.dao = dao; + this.arenaManager = arenaManager; + this.spawnProvider = spawnProvider; + + this.arena = arena; + this.players = players; + + KitPvpMenu menu = new KitPvpMenu(); + this.setting = new AutoQueueSetting(JavaPlugin.getPlugin(KitPvpPlugin.class)); + menu.registerToParentMenu(); + menu.registerSetting(this.setting); + + Bukkit.getScheduler().runTaskTimer(JavaPlugin.getPlugin(KitPvpPlugin.class), () -> { + try { + for (Iterator iterator = matches.iterator(); iterator.hasNext(); ) { + RankedMatch match = iterator.next(); + if (Instant.now().isAfter(match.started().plus(10, ChronoUnit.MINUTES))) { + iterator.remove(); + mostPotsWinsOrDraw(match); + } + } + scanQueue(); + scanQueueUnranked(); + for (RankedMatch match : matches) { + if (match.opponent().getY() > 90) { + match.opponent().addPotionEffect(new PotionEffect(PotionEffectType.WITHER, 50, 1, false, false)); + match.opponent().sendMessage(Component.text("You are too high!", NamedTextColor.RED)); + } + if (match.player().getY() > 90) { + match.player().addPotionEffect(new PotionEffect(PotionEffectType.WITHER, 50, 1, false, false)); + match.player().sendMessage(Component.text("You are too high!", NamedTextColor.RED)); + } + } + } catch (RuntimeException ex) { + JavaPlugin.getPlugin(KitPvpPlugin.class).getLogger().log(Level.WARNING, "Ticking matches", ex); + } + }, 20, 20); + Bukkit.getScheduler().runTaskTimer(JavaPlugin.getPlugin(KitPvpPlugin.class), () -> { + try { + for (Map.Entry player : queued.entrySet()) { + if (player.getValue().valid()) { + player.getKey().sendMessage(Component.text("You are queued for ranked. Type /ranked to leave the queue", NamedTextColor.YELLOW)); + } + } + for (Map.Entry player : unrankedQueued.entrySet()) { + if (player.getValue().valid()) { + player.getKey().sendMessage(Component.text("You are queued for unranked. Type /unranked to leave the queue", NamedTextColor.YELLOW)); + } + } + } catch (RuntimeException ex) { + JavaPlugin.getPlugin(KitPvpPlugin.class).getLogger().log(Level.WARNING, "Ticking queued players", ex); + } + }, 300, 300); + /*Bukkit.getScheduler().runTaskTimer(JavaPlugin.getPlugin(KitPvpPlugin.class), () -> { + try { + for (RankedDao.Rank rank : dao.getTop(10)) { + UserManager userManager = LuckPermsProvider.get().getUserManager(); + userManager.loadUser(rank.player()).thenAccept(user -> { + if (user == null) { + return; + } + user.data().add( + PermissionNode.builder() + .permission("ajqueue.priority.8") + .expiry(5, TimeUnit.MINUTES) + .build(), + TemporaryNodeMergeStrategy.REPLACE_EXISTING_IF_DURATION_LONGER); + userManager.saveUser(user); + }); + } + } catch (RuntimeException ex) { + JavaPlugin.getPlugin(KitPvpPlugin.class).getLogger().log(Level.WARNING, "Ticking queue priority", ex); + } + }, 20 * 60, 20 * 60);*/ + } + + public RankedMatch getMatch(Player player) { + for (RankedMatch match : matches) { + if (match.player().equals(player) || match.opponent().equals(player)) { + return match; + } + } + return null; + } + + private void mostPotsWinsOrDraw(RankedMatch match) { + Player player = match.player(); + Player opponent = match.opponent(); + + player.sendMessage(Component.text("The match has timed out! The player who dealt the most damage will win.", NamedTextColor.YELLOW)); + opponent.sendMessage(Component.text("The match has timed out! The player who dealt the most damage will win.", NamedTextColor.YELLOW)); + + if (match.getOpponentDamageDealt() > match.getPlayerDamageDealt()) { + endMatch(match, match.opponent().getUniqueId()); + } else if (match.getOpponentDamageDealt() < match.getPlayerDamageDealt()) { + endMatch(match, match.player().getUniqueId()); + } else { + endMatch(match, null); + } + } + + public boolean isInQueue(Player player) { + QueuedPlayer queued = this.queued.get(player); + return queued != null && queued.valid(); + } + + public boolean leaveQueue(Player player) { + if (this.queued.remove(player) != null) { + JavaPlugin.getPlugin(KitPvpPlugin.class).getLogger().info("%s left the ranked queue".formatted(player.getName())); + return true; + } + return false; + } + + public boolean isInUnrankedQueue(Player player) { + QueuedPlayer queued = this.unrankedQueued.get(player); + return queued != null && queued.valid(); + } + + public boolean leaveUnrankedQueue(Player player) { + if (this.unrankedQueued.remove(player) != null) { + JavaPlugin.getPlugin(KitPvpPlugin.class).getLogger().info("%s left the unranked queue".formatted(player.getName())); + return true; + } + return false; + } + + public boolean loseMatch(Player player) { + RankedMatch match = null; + UUID winner = null; + for (Iterator iterator = matches.iterator(); iterator.hasNext(); ) { + RankedMatch rankedMatch = iterator.next(); + if (rankedMatch.player().equals(player)) { + iterator.remove(); + winner = rankedMatch.opponent().getUniqueId(); + match = rankedMatch; + break; + } else if (rankedMatch.opponent().equals(player)) { + iterator.remove(); + winner = rankedMatch.player().getUniqueId(); + match = rankedMatch; + break; + } + } + if (match == null) { + return false; + } + + endMatch(match, winner); + return true; + } + + private String formatChange(double change) { + DecimalFormat format = new DecimalFormat("#"); + format.setPositivePrefix("+"); + return format.format(change); + } + + private void endMatch(RankedMatch match, UUID winner) { + Player player = match.player(); + Player opponent = match.opponent(); + UUID matchPlayer = player.getUniqueId(); + UUID matchOpponent = opponent.getUniqueId(); + + KitApplier.reset(player); + player.setFallDistance(0); + player.teleport(spawnProvider.getSpawn()); + KitApplier.reset(opponent); + opponent.setFallDistance(0); + opponent.teleport(spawnProvider.getSpawn()); + + double playerElo = match.playerElo(); + double opponentElo = match.opponentElo(); + + Elo.EloChange playerChange = Elo.getChange(match.playerElo(), match.opponentElo()); + Elo.EloChange opponentChange = Elo.getChange(match.opponentElo(), match.playerElo()); + + if (winner == null) { + player.sendMessage(Component.text("The match has ended in a draw because it timed out! (10 minutes)", NamedTextColor.GRAY)); + opponent.sendMessage(Component.text("The match has ended in a draw because it timed out! (10 minutes)", NamedTextColor.GRAY)); + + Bukkit.getScheduler().runTaskLater(JavaPlugin.getPlugin(KitPvpPlugin.class), () -> { + player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_TELEPORT, 1, 1); + opponent.playSound(opponent.getLocation(), Sound.ENTITY_PLAYER_TELEPORT, 1, 1); + }, 10); + playerElo += playerChange.draw(); + opponentElo += opponentChange.draw(); + if (match.unranked()) { + JavaPlugin.getPlugin(KitPvpPlugin.class).getLogger().info("%s drew against %s".formatted(player.getName(), opponent.getName())); + } else { + JavaPlugin.getPlugin(KitPvpPlugin.class).getLogger().info("%s (elo: %s, change: %s) drew against %s (elo: %s, change: %s)" + .formatted(player.getName(), Math.round(playerElo), formatChange(playerChange.draw()), + opponent.getName(), Math.round(opponentElo), formatChange(opponentChange.draw()))); + } + } else if (winner.equals(player.getUniqueId())) { + player.sendMessage(Component.text("You have won!", NamedTextColor.GREEN)); + opponent.sendMessage(Component.text("You lost", NamedTextColor.RED)); + Bukkit.getScheduler().runTaskLater(JavaPlugin.getPlugin(KitPvpPlugin.class), () -> { + player.playSound(player.getLocation(), Sound.ITEM_GOAT_HORN_SOUND_1, 1, 1); + opponent.playSound(opponent.getLocation(), Sound.ENTITY_PLAYER_TELEPORT, 1, 1); + }, 10); + playerElo += playerChange.win(); + opponentElo += opponentChange.loss(); + if (match.unranked()) { + JavaPlugin.getPlugin(KitPvpPlugin.class).getLogger().info("%s won against %s".formatted(player.getName(), opponent.getName())); + } else { + JavaPlugin.getPlugin(KitPvpPlugin.class).getLogger().info("%s (elo: %s, change: %s) won against %s (elo: %s, change: %s)" + .formatted(player.getName(), Math.round(playerElo), formatChange(playerChange.win()), + opponent.getName(), Math.round(opponentElo), formatChange(opponentChange.loss()))); + } + } else { + opponent.sendMessage(Component.text("You have won!", NamedTextColor.GREEN)); + player.sendMessage(Component.text("You lost", NamedTextColor.RED)); + Bukkit.getScheduler().runTaskLater(JavaPlugin.getPlugin(KitPvpPlugin.class), () -> { + player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_TELEPORT, 1, 1); + opponent.playSound(opponent.getLocation(), Sound.ITEM_GOAT_HORN_SOUND_1, 1, 1); + }, 10); + playerElo += playerChange.loss(); + opponentElo += opponentChange.win(); + if (match.unranked()) { + JavaPlugin.getPlugin(KitPvpPlugin.class).getLogger().info("%s lost against %s".formatted(player.getName(), opponent.getName())); + } else { + JavaPlugin.getPlugin(KitPvpPlugin.class).getLogger().info("%s (elo: %s, change: %s) lost against %s (elo: %s, change: %s)" + .formatted(player.getName(), Math.round(playerElo), formatChange(playerChange.loss()), + opponent.getName(), Math.round(opponentElo), formatChange(opponentChange.win()))); + } + } + + if (!match.unranked()) { + player.sendMessage(Component.text("Your elo is now ", NamedTextColor.GRAY) + .append(Component.text(Math.round(playerElo), NamedTextColor.WHITE)) + .append(Component.text(" (change: " + formatChange(playerElo - match.playerElo()) + ")", NamedTextColor.GRAY))); + opponent.sendMessage(Component.text("Your elo is now ", NamedTextColor.GRAY) + .append(Component.text(Math.round(opponentElo), NamedTextColor.WHITE)) + .append(Component.text(" (change: " + formatChange(opponentElo - match.opponentElo()) + ")", NamedTextColor.GRAY))); + players.setElo(player.getUniqueId(), playerElo); + players.setElo(opponent.getUniqueId(), opponentElo); + } + + Bukkit.getScheduler().runTaskAsynchronously(JavaPlugin.getPlugin(KitPvpPlugin.class), () -> { + if (!match.unranked()) { + dao.updateElo(matchPlayer, matchOpponent, winner); + } + Bukkit.getScheduler().runTask(JavaPlugin.getPlugin(KitPvpPlugin.class), () -> { + arenaManager.deleteLoadedArena(match.arena()); + + recentMatches.put(player.getUniqueId(), new RecentMatch(opponent.getUniqueId(), Instant.now())); + recentMatches.put(opponent.getUniqueId(), new RecentMatch(player.getUniqueId(), Instant.now())); + + if (match.unranked()) { + joinUnrankedQueue(player, true); + joinUnrankedQueue(opponent, true); + } else { + joinQueue(player, true); + joinQueue(opponent, true); + } + }); + }); + } + + public void joinQueue(Player player, boolean auto) { + if (getMatch(player) != null) { + return; + } + QueuedPlayer queue = this.queued.get(player); + if (queue != null && !queue.valid() && !auto) { + this.queued.put(player, new QueuedPlayer(queue.elo(), queue.joined(), false)); + player.sendMessage(Component.text("You have joined the ranked queue", NamedTextColor.YELLOW)); + return; + } + Integer value = setting.getValue(player); + if (auto && (value == null || value <= 0)) { + return; + } + JavaPlugin plugin = JavaPlugin.getPlugin(KitPvpPlugin.class); + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + int kitId = dao.getKit(player.getUniqueId()); + double elo = dao.getElo(player.getUniqueId()); + Kit kit; + if (kitId == -1) { + kit = kitDao.getKit("Ranked", null); + } else { + kit = kitDao.getKit(kitId); + } + Bukkit.getScheduler().runTask(plugin, () -> { + if (getMatch(player) != null) { + return; + } + if (kit == null) { + player.sendMessage(Component.text("You cannot join the ranked queue because you do not have a kit selected!", NamedTextColor.RED)); + player.sendMessage(Component.text("Open a kit in /kit and click on the diamond sword to select it", NamedTextColor.RED)); + return; + } + int points = KitCost.getCost(kit.items()); + if (points > KitCost.MAX_POINTS) { + player.sendMessage(Component.text("Your kit is too expensive! Kits may cost a maximum of " + KitCost.MAX_POINTS + " points, but your kit costs " + points + " points.", NamedTextColor.RED)); + return; + } + + if (player.isOnline()) { + if (auto) { + queued.put(player, new QueuedPlayer(elo, Instant.now().plusSeconds(value), true)); + } else { + queued.put(player, new QueuedPlayer(elo, Instant.now(), false)); + } + scanQueue(); + + JavaPlugin.getPlugin(KitPvpPlugin.class).getLogger().info("%s joined the ranked queue".formatted(player.getName())); + } + if (!auto) { + player.sendMessage(Component.text("You have joined the ranked queue", NamedTextColor.YELLOW)); + } else { + player.sendMessage(Component.text("You will rejoin the ranked queue in " + value + " seconds. Type /ranked leave to leave.", NamedTextColor.YELLOW)); + Bukkit.getScheduler().runTaskLater(plugin, () -> { + player.sendMessage(Component.text("Click to leave the ranked queue", NamedTextColor.RED, TextDecoration.BOLD, TextDecoration.UNDERLINED).hoverEvent(HoverEvent.showText(Component.text("Leave ranked queue"))).clickEvent(ClickEvent.runCommand("/ranked leave"))); + }, 20); + } + }); + }); + } + + public void joinUnrankedQueue(Player player, boolean auto) { + if (getMatch(player) != null) { + return; + } + QueuedPlayer queue = this.unrankedQueued.get(player); + if (queue != null && !queue.valid() && !auto) { + this.unrankedQueued.put(player, new QueuedPlayer(queue.elo(), queue.joined(), false)); + player.sendMessage(Component.text("You have joined the unranked queue", NamedTextColor.YELLOW)); + return; + } + Integer value = setting.getValue(player); + if (auto && (value == null || value <= 0)) { + return; + } + JavaPlugin plugin = JavaPlugin.getPlugin(KitPvpPlugin.class); + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + int kitId = dao.getKit(player.getUniqueId()); + Kit kit; + if (kitId == -1) { + kit = kitDao.getKit("Ranked", null); + } else { + kit = kitDao.getKit(kitId); + } + Bukkit.getScheduler().runTask(plugin, () -> { + if (kit == null) { + player.sendMessage(Component.text("You cannot join the ranked queue because you do not have a kit selected!", NamedTextColor.RED)); + player.sendMessage(Component.text("Open a kit in /kit and click on the diamond sword to select it", NamedTextColor.RED)); + return; + } + int points = KitCost.getCost(kit.items()); + if (points > KitCost.MAX_POINTS) { + player.sendMessage(Component.text("Your kit is too expensive! Kits may cost a maximum of " + KitCost.MAX_POINTS + " points, but your kit costs " + points + " points.", NamedTextColor.RED)); + return; + } + + if (player.isOnline()) { + if (auto) { + unrankedQueued.put(player, new QueuedPlayer(0, Instant.now().plusSeconds(value), true)); + } else { + unrankedQueued.put(player, new QueuedPlayer(0, Instant.now(), false)); + } + scanQueueUnranked(); + + JavaPlugin.getPlugin(KitPvpPlugin.class).getLogger().info("%s joined the unranked queue".formatted(player.getName())); + } + if (!auto) { + player.sendMessage(Component.text("You have joined the unranked queue", NamedTextColor.YELLOW)); + } else { + player.sendMessage(Component.text("You will rejoin the ranked queue in " + value + " seconds. Type /ranked to leave.", NamedTextColor.YELLOW)); + Bukkit.getScheduler().runTaskLater(plugin, () -> { + player.sendMessage(Component.text("Click to leave the unranked queue", NamedTextColor.RED, TextDecoration.BOLD, TextDecoration.UNDERLINED).hoverEvent(HoverEvent.showText(Component.text("Leave unranked queue"))).clickEvent(ClickEvent.runCommand("/unranked leave"))); + }, 20); + } + }); + }); + } + + private void scanQueue() { + // TODO make this algorithm better at preferring earlier players + List> entries = new ArrayList<>(queued.sequencedEntrySet()); + for (int i = 0; i < entries.size(); i++) { + Map.Entry playerEntry = entries.get(i); + if (!playerEntry.getValue().valid()) { + continue; + } + for (int j = i + 1; j < entries.size(); j++) { + Map.Entry opponentEntry = entries.get(j); + if (!opponentEntry.getValue().valid()) { + continue; + } + + int maxGap = 200; + Instant earlier = playerEntry.getValue().joined(); + if (opponentEntry.getValue().joined().isBefore(earlier)) { + earlier = opponentEntry.getValue().joined(); + } + double maxTime = earlier.until(Instant.now(), ChronoUnit.SECONDS); + if (maxTime > 60) { + maxGap = 10000; + } else if (maxTime > 40) { + maxGap = 400; + } else if (maxTime > 20) { + maxGap = 300; + } + + RecentMatch recent = recentMatches.get(playerEntry.getKey().getUniqueId()); + if (recent != null && recent.other().equals(opponentEntry.getKey().getUniqueId()) && recent.time().until(Instant.now(), ChronoUnit.SECONDS) < 45) { + continue; + } + + RecentMatch recent2 = recentMatches.get(opponentEntry.getKey().getUniqueId()); + if (recent2 != null && recent2.other().equals(playerEntry.getKey().getUniqueId()) && recent2.time().until(Instant.now(), ChronoUnit.SECONDS) < 45) { + continue; + } + + if (Math.abs(playerEntry.getValue().elo() - opponentEntry.getValue().elo()) < maxGap) { + if (ThreadLocalRandom.current().nextBoolean()) { + var temp = playerEntry; + playerEntry = opponentEntry; + opponentEntry = temp; + } + startMatch(playerEntry.getKey(), playerEntry.getValue().elo(), opponentEntry.getKey(), opponentEntry.getValue().elo(), false); + return; + } + } + } + } + + private void scanQueueUnranked() { + List> entries = new ArrayList<>(unrankedQueued.sequencedEntrySet()); + for (int i = 0; i < entries.size(); i++) { + Map.Entry playerEntry = entries.get(i); + if (!playerEntry.getValue().valid()) { + continue; + } + for (int j = i + 1; j < entries.size(); j++) { + Map.Entry opponentEntry = entries.get(j); + if (!opponentEntry.getValue().valid()) { + continue; + } + + RecentMatch recent = recentMatches.get(playerEntry.getKey().getUniqueId()); + if (recent != null && recent.other().equals(opponentEntry.getKey().getUniqueId()) && recent.time().until(Instant.now(), ChronoUnit.SECONDS) < 45) { + continue; + } + + RecentMatch recent2 = recentMatches.get(opponentEntry.getKey().getUniqueId()); + if (recent2 != null && recent2.other().equals(playerEntry.getKey().getUniqueId()) && recent2.time().until(Instant.now(), ChronoUnit.SECONDS) < 45) { + continue; + } + + if (ThreadLocalRandom.current().nextBoolean()) { + var temp = playerEntry; + playerEntry = opponentEntry; + opponentEntry = temp; + } + startMatch(playerEntry.getKey(), playerEntry.getValue().elo(), opponentEntry.getKey(), opponentEntry.getValue().elo(), true); + return; + } + } + } + + private void startMatch(Player player, double playerElo, Player opponent, double opponentElo, boolean unranked) { + boolean created = arenaManager.createRankedArena(arena, loaded -> { + World world = Bukkit.getWorld(arenaManager.getArenaName(loaded)); + Bukkit.getScheduler().runTaskAsynchronously(JavaPlugin.getPlugin(KitPvpPlugin.class), () -> { + int playerKitId = dao.getKit(player.getUniqueId()); + Kit playerKit; + if (playerKitId == -1) { + playerKit = kitDao.getKit("Ranked", null); + } else { + playerKit = kitDao.getKit(playerKitId); + } + int opponentKitId = dao.getKit(opponent.getUniqueId()); + Kit opponentKit; + if (opponentKitId == -1) { + opponentKit = kitDao.getKit("Ranked", null); + } else { + opponentKit = kitDao.getKit(opponentKitId); + } + Bukkit.getScheduler().runTask(JavaPlugin.getPlugin(KitPvpPlugin.class), () -> { + if (!player.isOnline() || !opponent.isOnline() || playerKit == null || opponentKit == null || KitCost.getCost(playerKit.items()) > KitCost.MAX_POINTS || KitCost.getCost(opponentKit.items()) > KitCost.MAX_POINTS || player.isDead() || opponent.isDead()) { + // TODO better error handling + player.sendMessage(Component.text("An error occurred, please requeue.", NamedTextColor.RED)); + arenaManager.deleteLoadedArena(loaded); + JavaPlugin.getPlugin(KitPvpPlugin.class).getLogger().info("Error occurred between " + player.getName() + " and " + opponent.getName()); + return; + } + + WorldBorder border = world.getWorldBorder(); + border.setCenter(72.50, 72.50); + border.setSize(143); + border.setSize(5, 8 * 60); + border.setDamageBuffer(0); + border.setDamageAmount(3); + + player.closeInventory(); + opponent.closeInventory(); + player.setFallDistance(0); + player.teleport(new Location(world, 42.5, 72, 33.5, -45, 0)); + opponent.setFallDistance(0); + opponent.teleport(new Location(world, 96.5, 72, 89.5, 135, 0)); + player.setGameMode(GameMode.SURVIVAL); + opponent.setGameMode(GameMode.SURVIVAL); + KitApplier.applyKit(playerKit, player); + KitApplier.applyKit(opponentKit, opponent); + matches.add(new RankedMatch(player, playerElo, opponent, opponentElo, loaded, Instant.now(), unranked)); + + if (unranked) { + player.sendMessage(Component.text("You have been paired with " + opponent.getName() + ". You are playing unranked", NamedTextColor.YELLOW)); + opponent.sendMessage(Component.text("You have been paired with " + player.getName() + ". You are playing unranked", NamedTextColor.YELLOW)); + + JavaPlugin.getPlugin(KitPvpPlugin.class).getLogger().info("%s started an unranked ranked fight against %s".formatted(player.getName(), opponent.getName())); + } else { + Elo.EloChange playerChange = Elo.getChange(playerElo, opponentElo); + Elo.EloChange opponentChange = Elo.getChange(opponentElo, playerElo); + + JavaPlugin.getPlugin(KitPvpPlugin.class).getLogger().info("%s (elo: %s, win: %s, loss: %s) started a ranked fight against %s (elo: %s, win: %s, loss: %s)" + .formatted(player.getName(), Math.round(playerElo), Math.round(playerChange.win()), Math.round(playerChange.loss()), + opponent.getName(), Math.round(opponentElo), Math.round(opponentChange.win()), Math.round(opponentChange.loss()))); + + player.sendMessage(Component.text("You (elo: " + ((int) Math.round(playerElo)) + ") have been paired with " + opponent.getName() + " (elo: " + ((int) Math.round(opponentElo)) + ")", NamedTextColor.YELLOW)); + player.sendMessage(Component.text("Win: +" + Math.round(playerChange.win()) + " elo, lose: " + Math.round(playerChange.loss()) + " elo", NamedTextColor.GRAY)); + + opponent.sendMessage(Component.text("You (elo: " + ((int) Math.round(opponentElo)) + ") have been paired with " + player.getName() + " (elo: " + ((int) Math.round(playerElo)) + ")", NamedTextColor.YELLOW)); + opponent.sendMessage(Component.text("Win: +" + Math.round(opponentChange.win()) + " elo, lose: " + Math.round(opponentChange.loss()) + " elo", NamedTextColor.GRAY)); + } + PearlCoolDownListener cooldown = Finale.getPlugin().getPearlCoolDownListener(); + if (cooldown != null) { + cooldown.putOnCooldown(player); + cooldown.putOnCooldown(opponent); + } + + player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1, 0f); + opponent.playSound(opponent.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1, 0f); + }); + }); + }); + if (created) { + queued.remove(player); + queued.remove(opponent); + unrankedQueued.remove(player); + unrankedQueued.remove(opponent); + } + } + + record QueuedPlayer(double elo, Instant joined, boolean auto) { + + public boolean valid() { + if (!auto) { + return true; + } else { + return !Instant.now().isBefore(joined); + } + } + } + + record RecentMatch(UUID other, Instant time) { + + } +} diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/SqlRankedDao.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/SqlRankedDao.java new file mode 100644 index 0000000000..00ce0469c2 --- /dev/null +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/SqlRankedDao.java @@ -0,0 +1,186 @@ +package net.civmc.kitpvp.ranked; + +import net.civmc.kitpvp.KitPvpPlugin; +import org.bukkit.plugin.java.JavaPlugin; +import vg.civcraft.mc.civmodcore.dao.ManagedDatasource; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.logging.Level; + +public class SqlRankedDao implements RankedDao { + + private final ManagedDatasource source; + + public SqlRankedDao(ManagedDatasource source) { + this.source = source; + source.registerMigration(7, false, """ + CREATE TABLE IF NOT EXISTS ranked_kits ( + player VARCHAR(36), + kit INT, + PRIMARY KEY (player) + ) + """, """ + CREATE TABLE IF NOT EXISTS ranked_elo ( + player VARCHAR(36), + elo DOUBLE NOT NULL, + INDEX (elo), + PRIMARY KEY (player) + ) + """, """ + CREATE TABLE IF NOT EXISTS ranked_matches ( + player VARCHAR(36) NOT NULL, + opponent VARCHAR(36) NOT NULL, + player_elo DOUBLE NOT NULL, + opponent_elo DOUBLE NOT NULL, + winner VARCHAR(36) NOT NULL, + timestamp TIMESTAMP NOT NULL, + INDEX (timestamp) + ) + """); + } + + @Override + public int getKit(UUID player) { + try (Connection connection = source.getConnection()) { + PreparedStatement statement = connection.prepareStatement("SELECT kit FROM ranked_kits WHERE player = ?"); + statement.setString(1, player.toString()); + + ResultSet resultSet = statement.executeQuery(); + if (resultSet.next()) { + return resultSet.getInt("kit"); + } else { + return -1; + } + } catch (SQLException ex) { + JavaPlugin.getPlugin(KitPvpPlugin.class).getLogger().log(Level.WARNING, "Error getting kit", ex); + return -1; + } + } + + @Override + public void setKit(UUID player, int kit) { + try (Connection connection = source.getConnection()) { + PreparedStatement statement = connection.prepareStatement("REPLACE INTO ranked_kits (player, kit) VALUES (?, ?)"); + statement.setString(1, player.toString()); + statement.setInt(2, kit); + + statement.executeUpdate(); + } catch (SQLException ex) { + JavaPlugin.getPlugin(KitPvpPlugin.class).getLogger().log(Level.WARNING, "Error setting kit", ex); + } + } + + @Override + public double getElo(UUID player) { + try (Connection connection = source.getConnection()) { + PreparedStatement statement = connection.prepareStatement("SELECT elo FROM ranked_elo WHERE player = ?"); + statement.setString(1, player.toString()); + + ResultSet resultSet = statement.executeQuery(); + if (resultSet.next()) { + return resultSet.getDouble("elo"); + } else { + return 1000; + } + } catch (SQLException ex) { + JavaPlugin.getPlugin(KitPvpPlugin.class).getLogger().log(Level.WARNING, "Error getting elo", ex); + return -1; + } + } + + @Override + public void updateElo(UUID player, UUID opponent, UUID winner) { + try (Connection connection = source.getConnection()) { + connection.setAutoCommit(false); + PreparedStatement statement = connection.prepareStatement("SELECT player, elo FROM ranked_elo WHERE player IN (?, ?) FOR UPDATE"); + statement.setString(1, player.toString()); + statement.setString(2, opponent.toString()); + + Map elos = new HashMap<>(); + ResultSet resultSet = statement.executeQuery(); + while (resultSet.next()) { + elos.put(UUID.fromString(resultSet.getString("player")), resultSet.getDouble("elo")); + } + + double playerElo = elos.getOrDefault(player, 1000D); + double opponentElo = elos.getOrDefault(opponent, 1000D); + if (winner == null) { + playerElo += Elo.getChange(playerElo, opponentElo).draw(); + opponentElo += Elo.getChange(opponentElo, playerElo).draw(); + } else if (winner.equals(player)) { + playerElo += Elo.getChange(playerElo, opponentElo).win(); + opponentElo += Elo.getChange(opponentElo, playerElo).loss(); + } else { + playerElo += Elo.getChange(playerElo, opponentElo).loss(); + opponentElo += Elo.getChange(opponentElo, playerElo).win(); + } + playerElo = Math.max(playerElo, 200); + opponentElo = Math.max(opponentElo, 200); + + PreparedStatement update = connection.prepareStatement("REPLACE INTO ranked_elo (player, elo) VALUES (?, ?)"); + update.setString(1, player.toString()); + update.setDouble(2, playerElo); + update.addBatch(); + update.setString(1, opponent.toString()); + update.setDouble(2, opponentElo); + update.addBatch(); + update.executeBatch(); + + PreparedStatement match = connection.prepareStatement("INSERT INTO ranked_matches (player, opponent, player_elo, opponent_elo, winner) VALUES (?, ?, ?, ?, ?)"); + match.setString(1, player.toString()); + match.setString(2, opponent.toString()); + match.setDouble(3, elos.getOrDefault(player, 1000D)); + match.setDouble(4, elos.getOrDefault(opponent, 1000D)); + match.setString(5, winner == null ? null : winner.toString()); + match.executeUpdate(); + + connection.commit(); + } catch (SQLException ex) { + JavaPlugin.getPlugin(KitPvpPlugin.class).getLogger().log(Level.WARNING, "Error updating elo", ex); + } + } + + @Override + public List getTop(int n) { + try (Connection connection = source.getConnection()) { + PreparedStatement statement = connection.prepareStatement("SELECT player, elo FROM ranked_elo ORDER BY elo DESC LIMIT ?"); + statement.setInt(1, n); + + ResultSet resultSet = statement.executeQuery(); + List players = new ArrayList<>(); + while (resultSet.next()) { + players.add(new Rank(UUID.fromString(resultSet.getString("player")), resultSet.getDouble("elo"))); + } + return players; + } catch (SQLException ex) { + JavaPlugin.getPlugin(KitPvpPlugin.class).getLogger().log(Level.WARNING, "Error getting top players", ex); + return Collections.emptyList(); + } + } + + @Override + public Map getAll() { + try (Connection connection = source.getConnection()) { + PreparedStatement statement = connection.prepareStatement("SELECT player, elo FROM ranked_elo"); + + ResultSet resultSet = statement.executeQuery(); + Map players = new HashMap<>(); + while (resultSet.next()) { + players.put(UUID.fromString(resultSet.getString("player")), resultSet.getDouble("elo")); + } + return players; + } catch (SQLException ex) { + JavaPlugin.getPlugin(KitPvpPlugin.class).getLogger().log(Level.WARNING, "Error getting top players", ex); + return Collections.emptyMap(); + } + } +} diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/UnrankedCommand.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/UnrankedCommand.java new file mode 100644 index 0000000000..ce55bc049e --- /dev/null +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/ranked/UnrankedCommand.java @@ -0,0 +1,37 @@ +package net.civmc.kitpvp.ranked; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +public class UnrankedCommand implements CommandExecutor { + + private final RankedQueueManager rankedQueueManager; + + public UnrankedCommand(RankedQueueManager rankedQueueManager) { + this.rankedQueueManager = rankedQueueManager; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String @NotNull [] args) { + if (!(sender instanceof Player player)) { + return false; + } + + if (this.rankedQueueManager.isInUnrankedQueue(player) || (args.length > 0 && args[0].equals("leave"))) { + if (this.rankedQueueManager.leaveUnrankedQueue(player)) { + player.sendMessage(Component.text("You have left the unranked queue", NamedTextColor.YELLOW)); + } else { + player.sendMessage(Component.text("You are not in the unranked queue", NamedTextColor.GRAY)); + } + } else { + this.rankedQueueManager.joinUnrankedQueue(player, false); + } + + return true; + } +} diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/snapshot/DeathListener.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/snapshot/DeathListener.java index b0d69594a4..e7e3078d3a 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/snapshot/DeathListener.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/snapshot/DeathListener.java @@ -19,18 +19,19 @@ public DeathListener(InventorySnapshotManager snapshotManager) { @EventHandler public void on(PlayerDeathEvent event) { - Player killer = event.getEntity().getKiller(); + die(event.getPlayer()); + event.getDrops().clear(); + } + + public void die(Player player) { + Player killer = player.getKiller(); if (killer == null) { return; } - - Player player = event.getPlayer(); - snapshotManager.putSnapshot(killer.getUniqueId(), new InventorySnapshot(killer.getInventory().getContents(), false, player.getPlayerProfile(), killer.getHealth())); snapshotManager.putSnapshot(player.getUniqueId(), new InventorySnapshot(player.getInventory().getContents(), true, killer.getPlayerProfile(), 0)); player.getInventory(); - event.getDrops().clear(); Bukkit.broadcast( Component.empty().append(createComponent(player).color(NamedTextColor.GOLD)) .append(Component.text(" was killed by ", NamedTextColor.YELLOW) diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/spawn/SpawnListener.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/spawn/SpawnListener.java index a71ea5f17e..f6366abca4 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/spawn/SpawnListener.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/spawn/SpawnListener.java @@ -43,7 +43,7 @@ public void on(PlayerSpawnLocationEvent event) { @EventHandler public void on(PlayerRespawnEvent event) { - if (event.getPlayer().getWorld().getName().equals("world")) { + if (event.getPlayer().getWorld().getName().equals("world") || event.getPlayer().getWorld().getName().startsWith("rankedarena.")) { Location spawn = this.provider.getSpawn(); if (spawn != null) { event.setRespawnLocation(spawn); diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/spawn/SqlSpawnProvider.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/spawn/SqlSpawnProvider.java index 55cf219909..69c9decf3f 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/spawn/SqlSpawnProvider.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/spawn/SqlSpawnProvider.java @@ -65,7 +65,7 @@ public boolean setSpawn(Location location) { statement.setDouble(4, location.getZ()); statement.setFloat(5, location.getYaw()); - boolean success = statement.executeUpdate() == 1; + boolean success = statement.executeUpdate() > 0; if (success) { this.spawn = location; } diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/sql/SqlKitPvpDao.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/sql/SqlKitPvpDao.java index 6660c13148..95bdd042b3 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/sql/SqlKitPvpDao.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/sql/SqlKitPvpDao.java @@ -7,8 +7,8 @@ import java.util.ArrayList; import java.util.List; import java.util.UUID; -import net.civmc.kitpvp.data.Kit; -import net.civmc.kitpvp.data.KitPvpDao; +import net.civmc.kitpvp.kit.Kit; +import net.civmc.kitpvp.kit.KitPvpDao; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import vg.civcraft.mc.civmodcore.dao.ManagedDatasource; @@ -71,6 +71,21 @@ public Kit getKit(String name, UUID player) { } } + @Override + public Kit getKit(int id) { + try (Connection connection = source.getConnection()) { + PreparedStatement statement = connection.prepareStatement("SELECT id, name, public, icon, inventory FROM kits WHERE id = ?"); + statement.setInt(1, id); + ResultSet resultSet = statement.executeQuery(); + if (resultSet.next()) { + return getKit(resultSet); + } + return null; + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + @Override public List getKits(UUID player) { try (Connection connection = source.getConnection()) { diff --git a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/warp/database/DatabaseManager.java b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/warp/database/DatabaseManager.java index 34e48bd0b3..1ddff88e03 100644 --- a/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/warp/database/DatabaseManager.java +++ b/plugins/kitpvp-paper/src/main/java/net/civmc/kitpvp/warp/database/DatabaseManager.java @@ -22,11 +22,8 @@ public class DatabaseManager { public DatabaseManager(KitPvpPlugin plugin, ManagedDatasource dataSource) { this.plugin = plugin; this.dataSource = dataSource; - this.plugin.info("Initialised a database connection"); - initialiseTables(); - dataSource.updateDatabase(); } private void initialiseTables() { diff --git a/plugins/kitpvp-paper/src/main/resources/config.yml b/plugins/kitpvp-paper/src/main/resources/config.yml index a5f4b01b69..6e55f2e112 100644 --- a/plugins/kitpvp-paper/src/main/resources/config.yml +++ b/plugins/kitpvp-paper/src/main/resources/config.yml @@ -11,3 +11,4 @@ database: max_lifetime: 7200000 max_arenas: -1 +ranked_arena: Plains diff --git a/plugins/kitpvp-paper/src/main/resources/plugin.yml b/plugins/kitpvp-paper/src/main/resources/plugin.yml index eff3e44942..933ff137cf 100644 --- a/plugins/kitpvp-paper/src/main/resources/plugin.yml +++ b/plugins/kitpvp-paper/src/main/resources/plugin.yml @@ -4,7 +4,7 @@ main: net.civmc.kitpvp.KitPvpPlugin author: Okx description: KitPvP for CivMC api-version: 1.21.3 -depend: [CivModCore, Finale] +depend: [CivModCore, Finale, LuckPerms, PlaceholderAPI] softdepend: [BreweryX] commands: @@ -29,8 +29,11 @@ commands: setspawn: permission: kitpvp.admin clear: {} + elo: {} viewinventorysnapshot: usage: / + ranked: {} + unranked: {} warp: description: Warp to a location permission: warps.use diff --git a/plugins/namecolors-paper/build.gradle.kts b/plugins/namecolors-paper/build.gradle.kts index 2aa5b2c662..8b3cb90d4e 100644 --- a/plugins/namecolors-paper/build.gradle.kts +++ b/plugins/namecolors-paper/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("io.papermc.paperweight.userdev") + alias(libs.plugins.paper.userdev) } version = "2.0.0-SNAPSHOT" @@ -13,5 +13,5 @@ dependencies { compileOnly(project(":plugins:namelayer-paper")) compileOnly(project(":plugins:civchat2-paper")) - compileOnly(files("../../ansible/src/paper-plugins/TAB v5.0.7.jar")) + compileOnly(files("../../ansible/src/paper-plugins/TAB v5.2.5.jar")) } diff --git a/plugins/namelayer-paper/build.gradle.kts b/plugins/namelayer-paper/build.gradle.kts index cb2f1612d7..95c3e83728 100644 --- a/plugins/namelayer-paper/build.gradle.kts +++ b/plugins/namelayer-paper/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("io.papermc.paperweight.userdev") + alias(libs.plugins.paper.userdev) } version = "3.0.6" diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/GroupManager.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/GroupManager.java index c79cb2c1b3..a8ff6a40d8 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/GroupManager.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/GroupManager.java @@ -10,6 +10,7 @@ import java.util.logging.Level; import org.bukkit.Bukkit; import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import vg.civcraft.mc.namelayer.database.GroupManagerDao; import vg.civcraft.mc.namelayer.events.GroupCreateEvent; @@ -63,7 +64,7 @@ public int createGroup(Group group) { public void createGroupAsync(final Group group, final RunnableOnGroup postCreate, boolean checkBeforeCreate) { if (group == null) { NameLayerPlugin.getInstance().getLogger().log(Level.INFO, "Group create failed, caller passed in null", new Exception()); - postCreate.setGroup(new Group(null, null, true, null, -1, System.currentTimeMillis())); + postCreate.setGroup(new Group(null, null, true, null, -1, System.currentTimeMillis(), null)); Bukkit.getScheduler().runTask(NameLayerPlugin.getInstance(), postCreate); } else { if (checkBeforeCreate) { @@ -83,7 +84,7 @@ public void run() { } else { // group does exist, so run postCreate with failure. NameLayerPlugin.getInstance().getLogger().log(Level.INFO, "Group create failed, group {0} already exists", group.getName()); - postCreate.setGroup(new Group(null, null, true, null, -1, System.currentTimeMillis())); + postCreate.setGroup(new Group(null, null, true, null, -1, System.currentTimeMillis(), null)); Bukkit.getScheduler().runTask(NameLayerPlugin.getInstance(), postCreate); } } @@ -102,7 +103,7 @@ private void doCreateGroupAsync(final Group group, final RunnableOnGroup postCre Bukkit.getPluginManager().callEvent(event); if (event.isCancelled()) { NameLayerPlugin.log(Level.INFO, "Group create was cancelled for group: " + group.getName()); - postCreate.setGroup(new Group(group.getName(), group.getOwner(), true, group.getPassword(), -1, System.currentTimeMillis())); + postCreate.setGroup(new Group(group.getName(), group.getOwner(), true, group.getPassword(), -1, System.currentTimeMillis(), group.getGroupColor().toString())); Bukkit.getScheduler().runTask(NameLayerPlugin.getInstance(), postCreate); } final String name = event.getGroupName(); @@ -587,7 +588,7 @@ public static String getStringOfTypes() { return ranks.toString(); } - public static void displayPlayerTypes(Player p) { + public static void displayPlayerTypes(CommandSender p) { p.sendMessage(ChatColor.RED + "That PlayerType does not exist.\n" + "The current types are: " + getStringOfTypes()); diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/MojangNames.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/MojangNames.java deleted file mode 100644 index f2a8fb1bd4..0000000000 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/MojangNames.java +++ /dev/null @@ -1,120 +0,0 @@ -package vg.civcraft.mc.namelayer; - -import com.google.common.base.Preconditions; -import com.google.common.base.Strings; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.NoSuchFileException; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.Collections; -import java.util.Map; -import java.util.TreeMap; -import java.util.UUID; -import net.minecraft.nbt.CompoundTag; -import org.bukkit.Bukkit; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.scheduler.BukkitTask; -import vg.civcraft.mc.civmodcore.nbt.NBTSerialization; -import vg.civcraft.mc.civmodcore.utilities.MoreMapUtils; -import vg.civcraft.mc.namelayer.listeners.AssociationListener; - -public final class MojangNames { - - private static final Map PROFILES = Collections.synchronizedMap( - new TreeMap<>(String.CASE_INSENSITIVE_ORDER)); - private static final String PROFILES_FILE = "mojang.dat"; - private static final long SAVE_DELAY = 20 * 60; // 60 seconds' worth of ticks - private static BukkitTask SAVE_TASK; - - public static void init(final NameLayerPlugin plugin) { - final Path mojangFile = plugin.getDataFile(PROFILES_FILE).toPath(); - // Load all the profiles that already exist - Bukkit.getScheduler().runTaskAsynchronously( - plugin, () -> load(plugin, mojangFile)); - // Set up a process of saving profiles - SAVE_TASK = Bukkit.getScheduler().runTaskTimerAsynchronously( - plugin, () -> save(plugin, mojangFile), SAVE_DELAY, SAVE_DELAY); - } - - public static void reset(final NameLayerPlugin plugin) { - if (!PROFILES.isEmpty()) { - save(plugin, plugin.getDataFile(PROFILES_FILE).toPath()); - PROFILES.clear(); - } - if (SAVE_TASK != null) { - SAVE_TASK.cancel(); - SAVE_TASK = null; - } - } - - private static void load(final NameLayerPlugin plugin, final Path file) { - PROFILES.clear(); - try { - final byte[] data = Files.readAllBytes(file); - final CompoundTag nbt = NBTSerialization.fromBytes(data); - nbt.getAllKeys().forEach(key -> PROFILES.put(key, nbt.getUUID(key))); - plugin.info("[MojangNames] Mojang profiles loaded!"); - } catch (final NoSuchFileException ignored) { - } catch (final IOException exception) { - plugin.warning("[MojangNames] Could not load Mojang profiles!", exception); - } - } - - private static void save(final NameLayerPlugin plugin, final Path file) { - final CompoundTag nbt = new CompoundTag(); - PROFILES.forEach((name, uuid) -> nbt.putUUID(name, uuid)); // Ignore highlighter - final byte[] data = NBTSerialization.toBytes(nbt); - try { - Files.write(file, data, - StandardOpenOption.CREATE, - StandardOpenOption.TRUNCATE_EXISTING, - StandardOpenOption.WRITE); - } catch (final IOException exception) { - plugin.warning("[MojangNames] Could not save Mojang profiles!", exception); - return; - } - plugin.info("[MojangNames] Mojang profiles saved!"); - } - - /** - * Returns the player's uuid based on their Mojang name. - * - * @param name The player's Mojang name. - * @return Returns the player's uuid, or null. - */ - public static UUID getMojangUuid(final String name) { - if (Strings.isNullOrEmpty(name)) { - return null; - } - return PROFILES.get(name); - } - - /** - *

Returns the player's Mojang name based on their uuid.

- * - *

Note: The name will be lowercase.

- * - * @param uuid The player's uuid. - * @return Returns the player's Mojang name, or null. - */ - public static String getMojangName(final UUID uuid) { - if (uuid == null) { - return null; - } - return MoreMapUtils.getKeyFromValue(PROFILES, uuid); - } - - /** - * DO NOT USE THIS ANYWHERE OTHER THAN {@link AssociationListener#onPlayerJoin(PlayerJoinEvent)} - * - * @param uuid The player's uuid. - * @param name The player's Mojang name. - */ - public static void declareMojangName(final UUID uuid, final String name) { - Preconditions.checkNotNull(uuid); - Preconditions.checkArgument(!Strings.isNullOrEmpty(name)); - PROFILES.put(name, uuid); - } - -} diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/NameAPI.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/NameAPI.java index 220d6cdbce..caaf1f1c5b 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/NameAPI.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/NameAPI.java @@ -3,69 +3,34 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; -import vg.civcraft.mc.namelayer.database.AssociationList; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; public class NameAPI { private static GroupManager groupManager; - private static AssociationList associations; + private static final Map uuidToPlayer = new HashMap<>(); + private static final Map playerToUuid = new HashMap<>(); - private static Map uuidsToName = new HashMap(); - private static Map nameToUUIDS = new HashMap(); - - public NameAPI(GroupManager man, AssociationList ass) { + public NameAPI(GroupManager man) { groupManager = man; - associations = ass; - loadAllPlayerInfo(); - } - - public void loadAllPlayerInfo() { - uuidsToName.clear(); - nameToUUIDS.clear(); - - boolean load = NameLayerPlugin.getInstance().getConfig().getBoolean("persistance.forceloadnamecaching", false); - if (!load) - return; - AssociationList.PlayerMappingInfo pmi = associations.getAllPlayerInfo(); - nameToUUIDS = pmi.nameMapping; - uuidsToName = pmi.uuidMapping; - + Bukkit.getScheduler().runTaskAsynchronously(NameLayerPlugin.getInstance(), () -> { + Map uuidToPlayer = new HashMap<>(); + Map playerToUuid = new HashMap<>(); + for (OfflinePlayer offlinePlayer : Bukkit.getOfflinePlayers()) { + playerToUuid.put(offlinePlayer.getName(), offlinePlayer.getUniqueId()); + uuidToPlayer.put(offlinePlayer.getUniqueId(), offlinePlayer.getName()); + } + Bukkit.getScheduler().runTask(NameLayerPlugin.getInstance(), () -> { + this.uuidToPlayer.putAll(uuidToPlayer); + this.playerToUuid.putAll(playerToUuid); + }); + }); } - public static void resetCache(UUID uuid) { - String name = getCurrentName(uuid); - uuidsToName.remove(uuid); - nameToUUIDS.remove(name); - } - - /** - * Returns the UUID of the player on the given server. - * - * @param playerName The playername. - * @return Returns the UUID of the player. - */ - public static UUID getUUID(String playerName) { - UUID uuid = nameToUUIDS.get(playerName); - if (uuid == null) { - uuid = associations.getUUID(playerName); - nameToUUIDS.put(playerName, uuid); - } - return uuid; - } - - /** - * Gets the playername from a given server from their uuid. - * - * @param uuid uuid of target player - * @return Returns the PlayerName from the UUID. - */ - public static String getCurrentName(UUID uuid) { - String name = uuidsToName.get(uuid); - if (name == null) { - name = associations.getCurrentName(uuid); - uuidsToName.put(uuid, name); - } - return name; + public static void associate(String playerName, UUID uuid) { + uuidToPlayer.put(uuid, playerName); + playerToUuid.put(playerName, uuid); } /** @@ -75,10 +40,11 @@ public static GroupManager getGroupManager() { return groupManager; } - /** - * @return Returns an instance of the AssociationList. - */ - public static AssociationList getAssociationList() { - return associations; + public static UUID getUUID(String s) { + return playerToUuid.get(s); + } + + public static String getCurrentName(UUID uuid) { + return uuidToPlayer.get(uuid); } } diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/NameLayerPlugin.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/NameLayerPlugin.java index 5b0973e819..6bac888a43 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/NameLayerPlugin.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/NameLayerPlugin.java @@ -11,20 +11,16 @@ import vg.civcraft.mc.civmodcore.dao.DatabaseCredentials; import vg.civcraft.mc.civmodcore.dao.ManagedDatasource; import vg.civcraft.mc.namelayer.command.CommandHandler; -import vg.civcraft.mc.namelayer.database.AssociationList; import vg.civcraft.mc.namelayer.database.GroupManagerDao; import vg.civcraft.mc.namelayer.group.AutoAcceptHandler; import vg.civcraft.mc.namelayer.group.BlackList; import vg.civcraft.mc.namelayer.group.DefaultGroupHandler; -import vg.civcraft.mc.namelayer.listeners.AssociationListener; import vg.civcraft.mc.namelayer.listeners.PlayerListener; import vg.civcraft.mc.namelayer.misc.ClassHandler; -import vg.civcraft.mc.namelayer.misc.NameCleanser; import vg.civcraft.mc.namelayer.permission.PermissionType; public class NameLayerPlugin extends ACivMod { - private static AssociationList associations; private static BlackList blackList; private static GroupManagerDao groupManagerDao; private static DefaultGroupHandler defaultGroupHandler; @@ -49,9 +45,7 @@ public void onEnable() { instance = this; loadDatabases(); ClassHandler.Initialize(Bukkit.getServer()); - new NameAPI(new GroupManager(), associations); - NameCleanser.load(config.getConfigurationSection("name_cleanser")); - MojangNames.init(this); + new NameAPI(new GroupManager()); registerListeners(); if (loadGroups) { PermissionType.initialize(); @@ -66,13 +60,11 @@ public void onEnable() { } public void registerListeners() { - registerListener(new AssociationListener()); registerListener(new PlayerListener()); } @Override public void onDisable() { - MojangNames.reset(this); super.onDisable(); } @@ -142,9 +134,6 @@ public void loadDatabases() { } - associations = new AssociationList(getLogger(), db); - associations.registerMigrations(); - if (loadGroups) { groupManagerDao = new GroupManagerDao(getLogger(), db); groupManagerDao.registerMigrations(); @@ -170,13 +159,6 @@ public void loadDatabases() { } - /** - * @return Returns the AssocationList. - */ - public static AssociationList getAssociationList() { - return associations; - } - /** * @return Returns the GroupManagerDatabase. */ diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/CommandHandler.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/CommandHandler.java index 23d0afeaa7..76f7294bdd 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/CommandHandler.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/CommandHandler.java @@ -2,6 +2,9 @@ import co.aikar.commands.BukkitCommandCompletionContext; import co.aikar.commands.CommandCompletions; +import java.util.Arrays; +import java.util.stream.Collectors; +import net.kyori.adventure.text.format.NamedTextColor; import org.jetbrains.annotations.NotNull; import vg.civcraft.mc.civmodcore.commands.CommandManager; import vg.civcraft.mc.namelayer.GroupManager; @@ -9,7 +12,6 @@ import vg.civcraft.mc.namelayer.command.TabCompleters.GroupTabCompleter; import vg.civcraft.mc.namelayer.command.commands.AcceptInvite; import vg.civcraft.mc.namelayer.command.commands.AddBlacklist; -import vg.civcraft.mc.namelayer.command.commands.ChangePlayerName; import vg.civcraft.mc.namelayer.command.commands.CreateGroup; import vg.civcraft.mc.namelayer.command.commands.DeleteGroup; import vg.civcraft.mc.namelayer.command.commands.DisciplineGroup; @@ -34,6 +36,7 @@ import vg.civcraft.mc.namelayer.command.commands.RemoveMember; import vg.civcraft.mc.namelayer.command.commands.RevokeInvite; import vg.civcraft.mc.namelayer.command.commands.SetDefaultGroup; +import vg.civcraft.mc.namelayer.command.commands.SetGroupColor; import vg.civcraft.mc.namelayer.command.commands.SetPassword; import vg.civcraft.mc.namelayer.command.commands.ShowBlacklist; import vg.civcraft.mc.namelayer.command.commands.ToggleAutoAcceptInvites; @@ -41,9 +44,6 @@ import vg.civcraft.mc.namelayer.command.commands.UpdateName; import vg.civcraft.mc.namelayer.permission.PermissionType; -import java.util.Arrays; -import java.util.stream.Collectors; - public class CommandHandler extends CommandManager { public CommandHandler(NameLayerPlugin plugin) { @@ -78,7 +78,6 @@ public void registerCommands() { registerCommand(new PromotePlayer()); registerCommand(new RejectInvite()); registerCommand(new RevokeInvite()); - registerCommand(new ChangePlayerName()); registerCommand(new SetDefaultGroup()); registerCommand(new GetDefaultGroup()); registerCommand(new UpdateName()); @@ -86,6 +85,7 @@ public void registerCommands() { registerCommand(new RemoveBlacklist()); registerCommand(new ShowBlacklist()); registerCommand(new NameLayerGroupGui()); + registerCommand(new SetGroupColor()); } @Override @@ -96,5 +96,6 @@ public void registerCompletions(@NotNull CommandCompletions PermissionType.getAllPermissions().stream().map(PermissionType::getName).collect(Collectors.toList())); + completions.registerCompletion("ADV_Colors", (context) -> NamedTextColor.NAMES.keys()); } } diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/ChangePlayerName.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/ChangePlayerName.java deleted file mode 100644 index 6c981ff1a6..0000000000 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/ChangePlayerName.java +++ /dev/null @@ -1,41 +0,0 @@ -package vg.civcraft.mc.namelayer.command.commands; - -import co.aikar.commands.annotation.CommandAlias; -import co.aikar.commands.annotation.CommandCompletion; -import co.aikar.commands.annotation.CommandPermission; -import co.aikar.commands.annotation.Description; -import co.aikar.commands.annotation.Syntax; -import java.util.UUID; -import org.bukkit.command.CommandSender; -import vg.civcraft.mc.namelayer.NameAPI; -import vg.civcraft.mc.namelayer.command.BaseCommandMiddle; - -/** - * Created by isaac on 2/6/15. - */ - -public class ChangePlayerName extends BaseCommandMiddle { - - @CommandAlias("nlcpn|changeplayername") - @CommandPermission("namelayer.admin") - @Syntax(" ") - @Description("Used by ops to change a players name") - @CommandCompletion("@allplayers @nothing") - public void execute(CommandSender sender, String currentName, String changedName) { - if (!sender.isOp() && !sender.hasPermission("namelayer.admin")) { - sender.sendMessage("You're not an op. "); - return; - } - UUID player = NameAPI.getUUID(currentName); - if (player == null) { - sender.sendMessage(currentName + " has never logged in"); - return; - } - - String newName = changedName.length() >= 16 ? changedName.substring(0, 16) : changedName; - NameAPI.getAssociationList().changePlayer(newName, player); - NameAPI.resetCache(player); - - sender.sendMessage(currentName + "'s name has been changed to " + newName + ". Have them relog for it to take affect"); - } -} diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/CreateGroup.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/CreateGroup.java index 62ab1e7fbd..e9372728bb 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/CreateGroup.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/CreateGroup.java @@ -70,7 +70,7 @@ public void execute(Player sender, String groupName, @Optional String userPasswo password = null; } final UUID uuid = NameAPI.getUUID(p.getName()); - Group g = new Group(name, uuid, false, password, -1, System.currentTimeMillis()); + Group g = new Group(name, uuid, false, password, -1, System.currentTimeMillis(), "GRAY"); gm.createGroupAsync(g, new RunnableOnGroup() { @Override public void run() { diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/PromotePlayer.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/PromotePlayer.java index 67a307b0d0..47180e3208 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/PromotePlayer.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/PromotePlayer.java @@ -8,6 +8,8 @@ import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Player; import vg.civcraft.mc.namelayer.GroupManager.PlayerType; import vg.civcraft.mc.namelayer.NameAPI; @@ -22,20 +24,19 @@ public class PromotePlayer extends BaseCommandMiddle { @Syntax(" ") @Description("Promote/Demote a Player in a Group") @CommandCompletion("@NL_Groups @allplayers @NL_Ranks") - public void execute(Player sender, String groupName, String playerName, String playerType) { - Player p = (Player) sender; - - UUID executor = NameAPI.getUUID(p.getName()); + public void execute(CommandSender sender, String groupName, String playerName, String playerType) { + final boolean isAdmin = sender instanceof ConsoleCommandSender || sender.hasPermission("namelayer.admin"); + UUID executor = sender instanceof Player p ? NameAPI.getUUID(p.getName()) : null; UUID promotee = NameAPI.getUUID(playerName); if (promotee == null) { - p.sendMessage(ChatColor.RED + "That player does not exist"); + sender.sendMessage(ChatColor.RED + "That player does not exist"); return; } if (promotee.equals(executor)) { - p.sendMessage(ChatColor.RED + "You cannot promote yourself"); + sender.sendMessage(ChatColor.RED + "You cannot promote yourself"); return; } @@ -44,53 +45,31 @@ public void execute(Player sender, String groupName, String playerName, String p return; } if (group.isDisciplined()) { - p.sendMessage(ChatColor.RED + "This group is disiplined."); + sender.sendMessage(ChatColor.RED + "This group is disiplined."); return; } PlayerType promoteecurrentType = group.getPlayerType(promotee); PlayerType promoteeType = PlayerType.getPlayerType(playerType); if (promoteeType == null) { - PlayerType.displayPlayerTypes(p); + PlayerType.displayPlayerTypes(sender); return; } if (promoteeType == PlayerType.NOT_BLACKLISTED) { - p.sendMessage(ChatColor.RED + "Nice try"); - return; - } - - PlayerType t = group.getPlayerType(executor); // playertype for the player running the command. - - if (t == null) { - p.sendMessage(ChatColor.RED + "You are not on that group."); + sender.sendMessage(ChatColor.RED + "Nice try"); return; } boolean allowed = false; - switch (promoteeType) { // depending on the type the executor wants to add the player to - case MEMBERS: - allowed = gm.hasAccess(group, executor, PermissionType.getPermission("MEMBERS")); - break; - case MODS: - allowed = gm.hasAccess(group, executor, PermissionType.getPermission("MODS")); - break; - case ADMINS: - allowed = gm.hasAccess(group, executor, PermissionType.getPermission("ADMINS")); - break; - case OWNER: - allowed = gm.hasAccess(group, executor, PermissionType.getPermission("OWNER")); - break; - default: - allowed = false; - break; - } + if (!isAdmin) { + PlayerType t = group.getPlayerType(executor); // playertype for the player running the command. - if (!allowed) { - p.sendMessage(ChatColor.RED + "You do not have permissions to promote to this rank"); - return; - } - if (promoteecurrentType != null) { - switch (promoteecurrentType) { // depending on the type the executor wants to add the player to + if (t == null) { + sender.sendMessage(ChatColor.RED + "You are not on that group."); + return; + } + + switch (promoteeType) { // depending on the type the executor wants to add the player to case MEMBERS: allowed = gm.hasAccess(group, executor, PermissionType.getPermission("MEMBERS")); break; @@ -107,17 +86,44 @@ public void execute(Player sender, String groupName, String playerName, String p allowed = false; break; } + + if (!allowed) { + sender.sendMessage(ChatColor.RED + "You do not have permissions to promote to this rank"); + return; + } + + if (promoteecurrentType != null) { + switch (promoteecurrentType) { // depending on the type the executor wants to add the player to + case MEMBERS: + allowed = gm.hasAccess(group, executor, PermissionType.getPermission("MEMBERS")); + break; + case MODS: + allowed = gm.hasAccess(group, executor, PermissionType.getPermission("MODS")); + break; + case ADMINS: + allowed = gm.hasAccess(group, executor, PermissionType.getPermission("ADMINS")); + break; + case OWNER: + allowed = gm.hasAccess(group, executor, PermissionType.getPermission("OWNER")); + break; + default: + allowed = false; + break; + } + } else { + allowed = false; + } } else { - allowed = false; + allowed = true; } if (!allowed || !group.isMember(promotee)) { //can't edit a player who isn't in the group - p.sendMessage(ChatColor.RED + NameAPI.getCurrentName(promotee) + " is not a member of this group or you do not have permission to edit their rank"); + sender.sendMessage(ChatColor.RED + NameAPI.getCurrentName(promotee) + " is not a member of this group or you do not have permission to edit their rank"); return; } if (group.isOwner(promotee)) { - p.sendMessage(ChatColor.RED + "That player owns the group, you cannot " + sender.sendMessage(ChatColor.RED + "That player owns the group, you cannot " + "demote the player."); return; } @@ -132,7 +138,7 @@ public void execute(Player sender, String groupName, String playerName, String p } group.removeMember(promotee); group.addMember(promotee, promoteeType); - p.sendMessage(ChatColor.GREEN + NameAPI.getCurrentName(promotee) + " has been added as (PlayerType) " + + sender.sendMessage(ChatColor.GREEN + NameAPI.getCurrentName(promotee) + " has been added as (PlayerType) " + promoteeType.toString() + " in (Group) " + group.getName()); oProm.sendMessage(ChatColor.GREEN + "You have been promoted to (PlayerType) " + promoteeType.toString() + " in (Group) " + group.getName()); @@ -140,7 +146,7 @@ public void execute(Player sender, String groupName, String playerName, String p //player is offline change their perms group.removeMember(promotee); group.addMember(promotee, promoteeType); - p.sendMessage(ChatColor.GREEN + NameAPI.getCurrentName(promotee) + " has been added as (PlayerType) " + + sender.sendMessage(ChatColor.GREEN + NameAPI.getCurrentName(promotee) + " has been added as (PlayerType) " + promoteeType.toString() + " in (Group) " + group.getName()); } } diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/SetGroupColor.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/SetGroupColor.java new file mode 100644 index 0000000000..d6eef37ad5 --- /dev/null +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/command/commands/SetGroupColor.java @@ -0,0 +1,58 @@ +package vg.civcraft.mc.namelayer.command.commands; + +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Syntax; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import org.bukkit.entity.Player; +import vg.civcraft.mc.namelayer.GroupManager; +import vg.civcraft.mc.namelayer.NameAPI; +import vg.civcraft.mc.namelayer.command.BaseCommandMiddle; +import vg.civcraft.mc.namelayer.group.Group; +import vg.civcraft.mc.namelayer.permission.PermissionType; +import java.util.UUID; + +public class SetGroupColor extends BaseCommandMiddle { + + @CommandAlias("nlsgc|setgroupcolor") + @Syntax(" ") + @Description("Set the groups color") + @CommandCompletion("@NL_Groups @ADV_Colors") + public void execute(Player player, String groupName, String targetColor) { + UUID uuid = NameAPI.getUUID(player.getName()); + Group group = gm.getGroup(groupName); + if (groupIsNull(player, groupName, group)) { + return; + } + + if (targetColor == null || targetColor.isEmpty()) { + player.sendRichMessage("You need to enter a value"); + return; + } + + TextColor color = NamedTextColor.NAMES.value(targetColor); + if (color == null) { + color = TextColor.fromHexString(targetColor); + } + if (color == null) { + player.sendRichMessage("The value you entered was not a valid hex value or color."); + return; + } + GroupManager.PlayerType playerType = group.getPlayerType(uuid); + if (playerType == null) { + player.sendRichMessage("You do not have access to that group."); + return; + } + if (!gm.hasAccess(group, uuid, PermissionType.getPermission("EDIT_COLOR"))) { + player.sendRichMessage("You do not have permission to modify " + group.getName() + " "); + return; + } + group.setGroupColor(color); + player.sendMessage(Component.text("You have changed the group color to ", NamedTextColor.GREEN) + .append(Component.text(group.getName()).color(group.getGroupColor()))); + } + +} diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/database/GroupManagerDao.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/database/GroupManagerDao.java index 3cdc08ba33..8b07fe3057 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/database/GroupManagerDao.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/database/GroupManagerDao.java @@ -40,13 +40,13 @@ public class GroupManagerDao { private static final String removeCycles = "delete a from subgroup a join faction_id a2 ON a.group_id = a2.group_id " + "JOIN subgroup b JOIN faction_id b2 on b.sub_group_id = b2.group_id where a2.group_name = b2.group_name;"; private static final String createGroup = "call createGroup(?,?,?,?)"; - private static final String getGroup = "select f.group_name, f.founder, f.password, f.discipline_flags, fi.group_id, f.last_timestamp " + + private static final String getGroup = "select f.group_name, f.founder, f.password, f.discipline_flags, fi.group_id, f.last_timestamp, f.group_color " + "from faction f " + "inner join faction_id fi on fi.group_name = f.group_name " + "where f.group_name = ?"; private static final String getGroupIDs = "SELECT f.group_id, count(DISTINCT fm.member_name) AS sz FROM faction_id f " + "LEFT JOIN faction_member fm ON f.group_id = fm.group_id WHERE f.group_name = ? GROUP BY f.group_id ORDER BY sz DESC"; - private static final String getGroupById = "select f.group_name, f.founder, f.password, f.discipline_flags, fi.group_id, f.last_timestamp " + + private static final String getGroupById = "select f.group_name, f.founder, f.password, f.discipline_flags, fi.group_id, f.last_timestamp, f.group_color " + "from faction f " + "inner join faction_id fi on fi.group_id = ? " + "where f.group_name = fi.group_name"; @@ -174,6 +174,7 @@ public class GroupManagerDao { private static final String removeBlackListMember = "delete from blacklist WHERE group_id IN (SELECT group_id FROM faction_id WHERE group_name = ?) and member_name=?;"; private static final String getBlackListMembers = "select b.member_name from blacklist b inner join faction_id fi on fi.group_name=? where b.group_id=fi.group_id;"; + private static final String setGroupColor = "update faction set faction.group_color =? where faction.group_name =?;"; private static final String getAllGroupIds = "select group_id from faction_id"; @@ -494,6 +495,10 @@ public Boolean call() { "DELETE FROM permissionByGroup " + "WHERE role='" + PlayerType.NOT_BLACKLISTED + "' " + "AND perm_id=(SELECT perm_id FROM permissionIdMapping WHERE name='BASTION_PLACE');"); + + //Adding support for groups to have a color assigned to them + db.registerMigration(15, false, + "alter table faction add group_color varchar(12) NOT NULL DEFAULT 'gray';"); } public int createGroup(String group, UUID owner, String password) { @@ -528,6 +533,7 @@ public Group getGroup(String groupName) { String password = null; int id = -1; Timestamp timeStamp = null; + String color = null; try (Connection connection = db.getConnection(); PreparedStatement getGroup = connection.prepareStatement(GroupManagerDao.getGroup)) { @@ -544,6 +550,7 @@ public Group getGroup(String groupName) { password = set.getString(3); id = set.getInt(5); timeStamp = set.getTimestamp(6); + color = set.getString(7); } catch (SQLException e) { logger.log(Level.WARNING, "Problem getting group " + groupName, e); return null; @@ -554,7 +561,7 @@ public Group getGroup(String groupName) { } Group g = null; try { - g = new Group(name, owner, discipline, password, id, timeStamp.getTime()); + g = new Group(name, owner, discipline, password, id, timeStamp.getTime(), color); } catch (Exception e) { logger.log(Level.WARNING, "Problem retrieving group " + groupName, e); } @@ -570,6 +577,7 @@ public Group getGroup(int groupId) { String password = null; int id = -1; Timestamp timeStamp = null; + String color = null; try (Connection connection = db.getConnection(); PreparedStatement getGroupById = connection.prepareStatement(GroupManagerDao.getGroupById)) { getGroupById.setInt(1, groupId); @@ -587,6 +595,7 @@ public Group getGroup(int groupId) { password = set.getString(3); id = set.getInt(5); timeStamp = set.getTimestamp(6); + color = set.getString(7); } catch (SQLException e) { logger.log(Level.WARNING, "Problem getting group " + groupId, e); return null; @@ -597,7 +606,7 @@ public Group getGroup(int groupId) { } Group g = null; try { - g = new Group(name, owner, dis, password, id, timeStamp.getTime()); + g = new Group(name, owner, dis, password, id, timeStamp.getTime(), color); } catch (Exception e) { logger.log(Level.WARNING, "Problem retrieving group " + groupId, e); } @@ -1667,5 +1676,16 @@ public List getAllIDs(String groupName) { return null; } + public void setGroupColor(String groupName, String groupColor) { + try (Connection connection = db.getConnection(); + PreparedStatement setGroupColor = connection.prepareStatement(GroupManagerDao.setGroupColor)) { + setGroupColor.setString(1, groupColor); + setGroupColor.setString(2, groupName); + setGroupColor.executeUpdate(); + } catch (SQLException e) { + logger.log(Level.WARNING, "Problem setting color for group " + groupName, e); + } + } + } diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/group/Group.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/group/Group.java index 0397cdd640..d010e6f011 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/group/Group.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/group/Group.java @@ -3,6 +3,9 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; import org.jetbrains.annotations.Nullable; import vg.civcraft.mc.namelayer.GroupManager; import vg.civcraft.mc.namelayer.GroupManager.PlayerType; @@ -35,9 +38,10 @@ public class Group { private Map players = Maps.newHashMap(); private Map invites = Maps.newHashMap(); private long activityTimestamp; + private TextColor groupColor; public Group(String name, UUID owner, boolean disciplined, - String password, int id, long activityTimestamp) { + String password, int id, long activityTimestamp, String groupColor) { if (db == null) { db = NameLayerPlugin.getGroupManagerDao(); } @@ -75,6 +79,11 @@ public Group(String name, UUID owner, boolean disciplined, for (Group subgroup : GroupManager.getSubGroups(name)) { link(this, subgroup, false); } + TextColor color = NamedTextColor.NAMES.value(groupColor); + if (color == null) { + color = TextColor.fromHexString(groupColor); + } + this.groupColor = color; } public long getActivityTimeStamp() { @@ -667,6 +676,25 @@ public void setGroupId(int id) { } } + public TextColor getGroupColor() { + return groupColor; + } + + public void setGroupColor(TextColor groupColor) { + setGroupColor(groupColor, true); + } + + public void setGroupColor(TextColor groupColor, boolean saveToDB) { + this.groupColor = groupColor; + if (saveToDB) { + db.setGroupColor(this.getName(), groupColor.toString()); + } + } + + public Component getGroupNameColored() { + return Component.text(this.name, this.groupColor); + } + /** * Updates/replaces the group id list with a new one. Clears the old one, adds these, * and ensures that the "main" id is added to the list as well. diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/gui/AdminFunctionsGUI.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/gui/AdminFunctionsGUI.java index 9eaf7e2131..4c26ef128d 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/gui/AdminFunctionsGUI.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/gui/AdminFunctionsGUI.java @@ -1,10 +1,14 @@ package vg.civcraft.mc.namelayer.gui; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.UUID; import java.util.logging.Level; import java.util.stream.Collectors; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Material; @@ -12,9 +16,12 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.util.StringUtil; import vg.civcraft.mc.civmodcore.chat.dialog.Dialog; +import vg.civcraft.mc.civmodcore.chat.dialog.DialogManager; import vg.civcraft.mc.civmodcore.inventory.gui.Clickable; import vg.civcraft.mc.civmodcore.inventory.gui.ClickableInventory; import vg.civcraft.mc.civmodcore.inventory.gui.DecorationStack; +import vg.civcraft.mc.civmodcore.inventory.gui.IClickable; +import vg.civcraft.mc.civmodcore.inventory.gui.LClickable; import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; import vg.civcraft.mc.namelayer.NameAPI; import vg.civcraft.mc.namelayer.NameLayerPlugin; @@ -42,7 +49,7 @@ public void showScreen() { + "Sorry, group linking is not a currently supported feature."); linkClick = new DecorationStack(linkStack); - ci.setSlot(linkClick, 10); +// ci.setSlot(linkClick, 10); // merging ItemStack mergeStack = new ItemStack(Material.SPONGE); ItemUtils.setDisplayName(mergeStack, ChatColor.GOLD + "Merge group"); @@ -65,7 +72,18 @@ public void showScreen() { // + "You don't have permission to do this"); // mergeClick = new DecorationStack(mergeStack); // } - ci.setSlot(mergeClick, 12); +// ci.setSlot(mergeClick, 12); + ItemStack colorChangeStack = new ItemStack(Material.WHITE_DYE); + ItemUtils.setComponentDisplayName(colorChangeStack, + Component.text("Change group color of ", NamedTextColor.GOLD).append(g.getGroupNameColored())); + Clickable colorChangeClick; + if (gm.hasAccess(g, p.getUniqueId(), PermissionType.getPermission("EDIT_COLOR"))) { + colorChangeClick = getGroupColorChangeButton(); + } else { + ItemUtils.setComponentLore(colorChangeStack, Component.text("You don't have permission to do this", NamedTextColor.RED)); + colorChangeClick = new DecorationStack(colorChangeStack); + } + ci.setSlot(colorChangeClick, 11); // transferring group ItemStack transferStack = new ItemStack(Material.PACKED_ICE); ItemUtils.setDisplayName(transferStack, ChatColor.GOLD @@ -83,7 +101,7 @@ public void clicked(Player p) { + "You don't have permission to do this"); transferClick = new DecorationStack(transferStack); } - ci.setSlot(transferClick, 14); + ci.setSlot(transferClick, 13); // deleting group ItemStack deletionStack = new ItemStack(Material.BARRIER); ItemUtils.setDisplayName(deletionStack, ChatColor.GOLD + "Delete group"); @@ -101,7 +119,7 @@ public void clicked(Player p) { + "You don't have permission to do this"); deletionClick = new DecorationStack(deletionStack); } - ci.setSlot(deletionClick, 16); + ci.setSlot(deletionClick, 15); // back button ItemStack backToOverview = goBackStack(); @@ -258,4 +276,56 @@ public void clicked(Player p) { confirmInv.showInventory(p); } + private LClickable getGroupColorChangeButton() { + return new LClickable(Material.WHITE_DYE, + Component.text("Change group color of ", NamedTextColor.GOLD).append(g.getGroupNameColored()), p -> { + ClickableInventory.forceCloseInventory(p); + p.sendMessage(Component.text("Enter the color you wish to change ", NamedTextColor.GREEN).append(g.getGroupNameColored()) + .append(Component.text(" to or type \"cancel\" to leave this prompt", NamedTextColor.GREEN))); + new Dialog(p, NameLayerPlugin.getInstance()) { + + @Override + public List onTabComplete(String wordCompleted, String[] fullMessage) { + return Collections.emptyList(); + } + + @Override + public void onReply(String[] message) { + if (message.length > 1) { + p.sendRichMessage("Colors cannot have spaces"); + showScreen(); + this.end(); + //For some reason if we don't force end the dialog after each this.end then + //tab completion for other commands will be broken? (/nl, /g etc.) + DialogManager.forceEndDialog(p.getUniqueId()); + return; + } + String cancel = message[0]; + if (cancel.equals("cancel")) { + p.sendRichMessage("Cancelled changing group color"); + showScreen(); + this.end(); + DialogManager.forceEndDialog(p.getUniqueId()); + return; + } + TextColor color = NamedTextColor.NAMES.value(cancel); + if (color == null) { + color = TextColor.fromHexString(cancel); + } + if (color == null) { + player.sendRichMessage("The value you entered was not a valid hex value or color."); + showScreen(); + this.end(); + DialogManager.forceEndDialog(p.getUniqueId()); + return; + } + g.setGroupColor(color); + p.sendMessage(Component.text("The color of " + g.getName() + " was changed to ", NamedTextColor.GREEN).append(g.getGroupNameColored())); + this.end(); + DialogManager.forceEndDialog(p.getUniqueId()); + } + }; + }); + } + } diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/gui/GUIGroupOverview.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/gui/GUIGroupOverview.java index 5244545603..d11eda4fb3 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/gui/GUIGroupOverview.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/gui/GUIGroupOverview.java @@ -314,7 +314,7 @@ public void onReply(String[] message) { } final UUID uuid = p.getUniqueId(); - Group g = new Group(groupName, uuid, false, null, -1, System.currentTimeMillis()); + Group g = new Group(groupName, uuid, false, null, -1, System.currentTimeMillis(), "GRAY"); gm.createGroupAsync(g, new RunnableOnGroup() { @Override public void run() { diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/gui/MainGroupGUI.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/gui/MainGroupGUI.java index 293e002a75..e97190fc0c 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/gui/MainGroupGUI.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/gui/MainGroupGUI.java @@ -991,7 +991,7 @@ public void clicked(Player p) { private Clickable getAdminStuffClickable() { ItemStack is = new ItemStack(Material.DIAMOND); - ItemUtils.setDisplayName(is, ChatColor.GOLD + "Owner functions"); + ItemUtils.setDisplayName(is, ChatColor.GOLD + "Admin/Owner functions"); return new Clickable(is) { @Override diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/listeners/AssociationListener.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/listeners/AssociationListener.java deleted file mode 100644 index 05f02a4424..0000000000 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/listeners/AssociationListener.java +++ /dev/null @@ -1,73 +0,0 @@ -package vg.civcraft.mc.namelayer.listeners; - -import java.util.UUID; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerJoinEvent; -import vg.civcraft.mc.namelayer.MojangNames; -import vg.civcraft.mc.namelayer.NameAPI; -import vg.civcraft.mc.namelayer.NameLayerPlugin; -import vg.civcraft.mc.namelayer.database.AssociationList; -import vg.civcraft.mc.namelayer.misc.ClassHandler; -import vg.civcraft.mc.namelayer.misc.NameCleanser; -import vg.civcraft.mc.namelayer.misc.ProfileInterface; - -public class AssociationListener implements Listener { - - private AssociationList associations; - - private ClassHandler ch; - - private ProfileInterface game; - - public AssociationListener() { - Bukkit.getScheduler().runTaskLater(NameLayerPlugin.getInstance(), new Runnable() { - - @Override - public void run() { - ch = ClassHandler.ch; - if (ClassHandler.properlyEnabled) - game = ch.getProfileClass(); - - associations = NameAPI.getAssociationList(); - } - - }, 1); - } - - @EventHandler(priority = EventPriority.LOWEST) - public void onPlayerJoin(final PlayerJoinEvent event) { - String playername = event.getPlayer().getName(); - if (NameCleanser.isDirty(playername)) { - if (NameCleanser.isAlertOps()) { - String msg = playername + " has a dirty name"; - if (NameCleanser.isCleanNames()) { - msg += ", this will be fixed"; - } - Bukkit.broadcast(msg, NameCleanser.getAlertPerm()); - } - if (NameCleanser.isCleanNames()) { - playername = NameCleanser.cleanName(playername); - } - } - UUID uuid = event.getPlayer().getUniqueId(); - associations.addPlayer(playername, uuid); - event.setJoinMessage(ChatColor.YELLOW + NameAPI.getCurrentName(uuid) + " joined the game"); - - final Player player = event.getPlayer(); - MojangNames.declareMojangName(player.getUniqueId(), player.getName()); - String name = associations.getCurrentName(player.getUniqueId()); - if (name == null) { - associations.addPlayer(player.getName(), player.getUniqueId()); - name = associations.getCurrentName(player.getUniqueId()); - } - - - if (game != null) - game.setPlayerProfile(player, name); - } -} diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/listeners/PlayerListener.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/listeners/PlayerListener.java index 96a53ad833..fef92ea8e7 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/listeners/PlayerListener.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/listeners/PlayerListener.java @@ -27,6 +27,8 @@ public void playerJoinEvent(PlayerJoinEvent event) { Player p = event.getPlayer(); UUID uuid = p.getUniqueId(); + NameAPI.associate(p.getName(), p.getUniqueId()); + if (!p.hasPlayedBefore()) { handleFirstJoin(p); } @@ -112,7 +114,7 @@ private static class NewfriendCreate extends RunnableOnGroup { public void bootstrap() { GroupManager gm = NameAPI.getGroupManager(); - gm.createGroupAsync(new Group(name, uuid, false, null, -1, System.currentTimeMillis()), this, true); + gm.createGroupAsync(new Group(name, uuid, false, null, -1, System.currentTimeMillis(), "GRAY"), this, true); } @Override @@ -128,7 +130,7 @@ public void run() { } if (inc < 20) { String newName = name + String.valueOf(inc); - gm.createGroupAsync(new Group(newName, uuid, false, null, -1, System.currentTimeMillis()), this, true); + gm.createGroupAsync(new Group(newName, uuid, false, null, -1, System.currentTimeMillis(), "GRAY"), this, true); } } else { NameLayerPlugin.log(Level.WARNING, "Newfriend automatic group creation succeeded for " + g.getName() + " " + uuid); diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/misc/NameCleanser.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/misc/NameCleanser.java deleted file mode 100644 index a82046e076..0000000000 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/misc/NameCleanser.java +++ /dev/null @@ -1,100 +0,0 @@ -package vg.civcraft.mc.namelayer.misc; - -import java.util.ArrayList; -import java.util.List; -import java.util.Random; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.bukkit.configuration.ConfigurationSection; - -public class NameCleanser { - - private static boolean enabled = true; - private static boolean cleanNames; - private static boolean alertOps; - private static String alertPerm; - private static List cleanWords; - private static Pattern wordPattern; - private static Random rng; - - public static void load(ConfigurationSection config) { - if (config == null) { - enabled = false; - return; - } - try { - List badWords = config.getStringList("bad_words"); - ConfigurationSection opts = config.getConfigurationSection("opts"); - List> options = new ArrayList<>(); - for (String key : opts.getKeys(false)) { - options.add(opts.getCharacterList(key)); - } - StringBuilder patternBuilder = new StringBuilder(); - patternBuilder.append("("); - for (String word : badWords) { - for (char c : word.toCharArray()) { - patternBuilder.append("["); - if (Character.isAlphabetic(c)) { - patternBuilder.append(Character.toUpperCase(c)); - patternBuilder.append(Character.toLowerCase(c)); - } - for (List list : options) { - if (list.contains(c)) { - for (Character other : list) { - if (!other.equals(c)) { - if (Character.isAlphabetic(other)) { - patternBuilder.append(Character.toUpperCase(other)); - patternBuilder.append(Character.toLowerCase(other)); - } else { - patternBuilder.append(other); - } - } - } - } - } - patternBuilder.append("]"); - } - patternBuilder.append("|"); - } - patternBuilder.setCharAt(patternBuilder.lastIndexOf("|"), ')'); - wordPattern = Pattern.compile(patternBuilder.toString()); - cleanWords = config.getStringList("clean_words"); - cleanNames = config.getBoolean("clean_names"); - alertOps = config.getBoolean("alert_ops"); - alertPerm = config.getString("alert_perm", "namelayer.alert_dirty_name"); - rng = new Random(); - } catch (Exception e) { - enabled = false; - } - } - - public static boolean isDirty(String name) { - return enabled && wordPattern.matcher(name).find(); - } - - public static String cleanName(String name) { - if (!enabled) return name; - Matcher matcher = wordPattern.matcher(name); - while (matcher.find()) { - name = matcher.replaceFirst(cleanWords.get(rng.nextInt(cleanWords.size()))); - matcher.reset(name); - } - return name; - } - - public static boolean isCleanNames() { - return cleanNames; - } - - public static boolean isAlertOps() { - return alertOps; - } - - public static String getAlertPerm() { - return alertPerm; - } - - public static boolean isEnabled() { - return enabled; - } -} diff --git a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/permission/PermissionType.java b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/permission/PermissionType.java index 3b7107e6be..c36890c0f4 100644 --- a/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/permission/PermissionType.java +++ b/plugins/namelayer-paper/src/main/java/vg/civcraft/mc/namelayer/permission/PermissionType.java @@ -121,6 +121,8 @@ private static void registerNameLayerPermissions() { //perm level given to members when they join with a password registerPermission("JOIN_PASSWORD", members); + //allows editing a groups color + registerPermission("EDIT_COLOR", new ArrayList<>(adminAndAbove), "Allows editing the color of the group"); } private String name; diff --git a/plugins/railswitch-paper/build.gradle.kts b/plugins/railswitch-paper/build.gradle.kts index dd8441e835..699fbacf87 100644 --- a/plugins/railswitch-paper/build.gradle.kts +++ b/plugins/railswitch-paper/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("io.papermc.paperweight.userdev") + alias(libs.plugins.paper.userdev) } version = "2.0.0-SNAPSHOT" diff --git a/plugins/randomspawn-paper/build.gradle.kts b/plugins/randomspawn-paper/build.gradle.kts index 6a92fb9c82..46f5d2ff4c 100644 --- a/plugins/randomspawn-paper/build.gradle.kts +++ b/plugins/randomspawn-paper/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("io.papermc.paperweight.userdev") + alias(libs.plugins.paper.userdev) } version = "3.0.4" diff --git a/plugins/randomspawn-paper/src/main/java/me/josvth/randomspawn/spawn/AsyncSpawnSelector.java b/plugins/randomspawn-paper/src/main/java/me/josvth/randomspawn/spawn/AsyncSpawnSelector.java index c44254ce1c..bc57172fc5 100644 --- a/plugins/randomspawn-paper/src/main/java/me/josvth/randomspawn/spawn/AsyncSpawnSelector.java +++ b/plugins/randomspawn-paper/src/main/java/me/josvth/randomspawn/spawn/AsyncSpawnSelector.java @@ -36,7 +36,7 @@ public AsyncSpawnSelector(Plugin plugin, BlockingSpawnSelector blocking, List> entry : this.randomSpawnLocations.entrySet()) { queueRandomSpawn(entry.getKey(), entry.getValue()); + } + for (Map.Entry> entry : this.spawnPointLocations.entrySet()) { queueSpawnPoint(entry.getKey(), entry.getValue()); } } @@ -75,7 +77,7 @@ private void queue(String worldName, ArrayBlockingQueue queue, BiFunct location.getChunk().addPluginChunkTicket(plugin); queue.offer(location); if (queue.remainingCapacity() > 0) { - queueSpawnPoint(worldName, queue); + queue(worldName, queue, function); } }); }); diff --git a/plugins/realisticbiomes2-paper/LICENSE.txt b/plugins/realisticbiomes2-paper/LICENSE.txt new file mode 100644 index 0000000000..fd5e2a21d9 --- /dev/null +++ b/plugins/realisticbiomes2-paper/LICENSE.txt @@ -0,0 +1,7 @@ +Copyright 2019 Civcraft + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/plugins/realisticbiomes2-paper/README.md b/plugins/realisticbiomes2-paper/README.md new file mode 100644 index 0000000000..f6ef0993ee --- /dev/null +++ b/plugins/realisticbiomes2-paper/README.md @@ -0,0 +1,31 @@ +RealisticBiomes +=============== + +![Illustration](http://i.imgur.com/sInZHEN.jpg) + +## Changes with 1.16.5 + +RealisticBiomes underwent a refactor and partial conversion to 1.16.5. It does not currently support controlling animal +reproduction and no longer supports altering fishing loot tables. + +Cactus, melons, pumpkins, sugarcane were previously non-persistent crops (you must load their chunk for them to grow). +The update to 1.16.5 made them persistent crops so that when you leave and re-enter a chunk, they will match the growth +that would have occurred in that time. However this introduced an issue with cactus causing immense server-crippling +lag. Therefore it is currently recommended to not enable persistent Cactus growth (AKA leave cactus growth totally +vanilla). + +Partially supports greenhouse growth but only with sources of light directly adjacent. + +## Description + +Fully configurable plant growth based on biome or biome category. Disallows plant growth without skylight, but allows +configuring greenhouse growth for configured light sources. Allows configuring of increased growth rates based on blocks +placed below the crop soil. + +Limits plant growth and animal reproduction to their naturally occuring biomes, or reasonable ones for plants that do +not occur naturally. Growth is persistent, so plants grow even when their chunk is not loaded. + +See [this spreadsheet](https://devotedmc.github.io/RealisticBiomes/spreadsheet/) for the default configuration of the +different plants, animals and actions. + +See [the wiki](https://github.com/DevotedMC/RealisticBiomes/wiki) for in depth explanation of the mechanics. diff --git a/plugins/realisticbiomes2-paper/build.gradle.kts b/plugins/realisticbiomes2-paper/build.gradle.kts new file mode 100644 index 0000000000..e3a5b57bb2 --- /dev/null +++ b/plugins/realisticbiomes2-paper/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + alias(libs.plugins.paper.userdev) + alias(libs.plugins.runpaper) +} + +version = "3.2.3" + +dependencies { + paperweight { + paperDevBundle(libs.versions.paper) + } + + compileOnly(project(":plugins:civmodcore-paper")) + + compileOnly(libs.worldedit) + implementation(libs.commons.math3) +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/FarmBeaconManager.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/FarmBeaconManager.java new file mode 100644 index 0000000000..6fe9e0d825 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/FarmBeaconManager.java @@ -0,0 +1,88 @@ +package com.untamedears.realisticbiomes; + +import org.bukkit.Chunk; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.World; +import org.bukkit.block.Beacon; +import org.bukkit.block.BlockState; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.persistence.PersistentDataType; + +public class FarmBeaconManager { + + private final NamespacedKey farmBeaconKey; + private final int beaconRange; + private final double farmBeaconFertility; + private final double farmBeaconGrowthMultiplier; + private final long farmBeaconWarmup; + + public FarmBeaconManager(NamespacedKey farmBeaconKey, int beaconRange, double farmBeaconFertility, double farmBeaconGrowthMultiplier, long farmBeaconWarmup) { + this.farmBeaconKey = farmBeaconKey; + this.beaconRange = beaconRange; + this.farmBeaconFertility = farmBeaconFertility; + this.farmBeaconGrowthMultiplier = farmBeaconGrowthMultiplier; + this.farmBeaconWarmup = farmBeaconWarmup; + } + + public static FarmBeaconManager fromConfiguration(ConfigurationSection section) { + return new FarmBeaconManager( + NamespacedKey.fromString(section.getString("farm_beacon_key")), + section.getInt("farm_beacon_range"), + section.getDouble("farm_beacon_fertility"), + section.getDouble("farm_beacon_growth_multiplier"), + section.getLong("farm_beacon_warmup") + ); + } + + public boolean isNearBeacon(Location location) { + World world = location.getWorld(); + + int chunkX = location.getChunk().getX(); + int chunkZ = location.getChunk().getZ(); + + int chunkRange = Math.ceilDiv(beaconRange, 16); + for (int x = -chunkRange; x <= chunkRange; x++) { + for (int z = -chunkRange; z <= chunkRange; z++) { + Chunk nearbyChunk = world.getChunkAt(x + chunkX, z + chunkZ); + for (BlockState state : nearbyChunk.getTileEntities(b -> b.getType() == Material.BEACON, false)) { + int blockX = state.getX(); + int blockY = state.getY(); + int blockZ = state.getZ(); + + if ((location.getX() - blockX) * (location.getX() - blockX) + (location.getY() - blockY) * (location.getY() - blockY) + (location.getZ() - blockZ) * (location.getZ() - blockZ) > this.beaconRange * this.beaconRange) { + continue; + } + + if (!(state instanceof Beacon beacon)) { + continue; + } + + if (beacon.getTier() <= 0) { + continue; + } + + Long value = beacon.getPersistentDataContainer().get(farmBeaconKey, PersistentDataType.LONG); + if (value == null) { + continue; + } + + if (System.currentTimeMillis() - value >= farmBeaconWarmup) { + return true; + } + } + } + } + + return false; + } + + public double getFarmBeaconGrowthMultiplier() { + return farmBeaconGrowthMultiplier; + } + + public double getFarmBeaconFertility() { + return farmBeaconFertility; + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/GrowthConfigManager.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/GrowthConfigManager.java new file mode 100644 index 0000000000..53be893c37 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/GrowthConfigManager.java @@ -0,0 +1,97 @@ +package com.untamedears.realisticbiomes; + +import com.untamedears.realisticbiomes.breaker.HandPicked; +import com.untamedears.realisticbiomes.growthconfig.PlantGrowthConfig; +import com.untamedears.realisticbiomes.model.Plant; +import com.untamedears.realisticbiomes.utils.RBUtils; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.inventory.ItemStack; + +public class GrowthConfigManager { + + private Map fallbackPlantMap; + private Map plantsById; + private Map plantsByItem; + + public GrowthConfigManager(Set plantConfigs) { + fallbackPlantMap = new EnumMap<>(Material.class); + plantsById = new HashMap<>(); + plantsByItem = new HashMap<>(); + for (PlantGrowthConfig plant : plantConfigs) { + for (Material mat : plant.getApplicableVanillaPlants()) { + fallbackPlantMap.put(mat, plant); + } + if (plant.getItem() != null) { + plantsByItem.put(plant.getItem(), plant); + } + plantsById.put(plant.getID(), plant); + } + } + + public Set getAllGrowthConfigs() { + return new HashSet<>(plantsById.values()); + } + + public PlantGrowthConfig getConfigById(short id) { + return plantsById.get(id); + } + + /** + * Gets the config based on material only, does not include remapping to account + * for fruits, any special block tracking etc. + * + * @param material Material to get config for + * @return Growth config for the given material or null if no such config exists + */ + public PlantGrowthConfig getGrowthConfigFallback(Material material) { + return fallbackPlantMap.get(material); + } + + /** + * Retrieves a growth config by the Item used to plant it + * + * @param item ItemStack used to plant the item + * @return Growth config for the given item if one exists + */ + public PlantGrowthConfig getGrowthConfigByItem(ItemStack item) { + ItemStack copy = item.clone(); + copy.setAmount(1); + if (HandPicked.isHandPicked(copy)) { + copy.lore(null); + HandPicked.removeHandPicked(copy); + } + return plantsByItem.get(copy); + } + + /** + * Gets the plant growth config responsible for further growth related to this + * plant based on its block state. For fully grown stems this will return the fruits config, for + * everything else the normal growth config + * + * @param plant Plant to get growth config for + * @return Growth config, possibly null if no config for the block of the given plant exists + */ + public PlantGrowthConfig getPlantGrowthConfigFallback(Plant plant) { + Block block = plant.getLocation().getBlock(); + PlantGrowthConfig config = getGrowthConfigFallback(block.getType()); + if (config == null) { + return null; + } + Material fruit = RBUtils.getFruit(block.getType()); + if (fruit != null && config.isFullyGrown(new Plant(block.getLocation(), config))) { + PlantGrowthConfig fruitConfig = getGrowthConfigFallback(fruit); + if (fruitConfig != null) { + plant.setGrowthConfig(fruitConfig); + return fruitConfig; + } + } + plant.setGrowthConfig(config); + return config; + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/PlantLogicManager.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/PlantLogicManager.java new file mode 100644 index 0000000000..01eb2ee846 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/PlantLogicManager.java @@ -0,0 +1,297 @@ +package com.untamedears.realisticbiomes; + +import com.untamedears.realisticbiomes.growth.ColumnPlantGrower; +import com.untamedears.realisticbiomes.growth.FruitGrower; +import com.untamedears.realisticbiomes.growth.TipGrower; +import com.untamedears.realisticbiomes.growth.VerticalGrower; +import com.untamedears.realisticbiomes.growthconfig.PlantGrowthConfig; +import com.untamedears.realisticbiomes.model.Plant; +import com.untamedears.realisticbiomes.utils.RBUtils; +import java.util.HashSet; +import java.util.Set; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.world.WorldUtils; + +public class PlantLogicManager { + + private final PlantManager plantManager; + private final GrowthConfigManager growthConfigManager; + private Set fruitBlocks; + private Set columnBlocks; + + public PlantLogicManager(PlantManager plantManager, GrowthConfigManager growthConfigManager) { + this.plantManager = plantManager; + this.growthConfigManager = growthConfigManager; + initAdjacentPlantBlocks(growthConfigManager.getAllGrowthConfigs()); + } + + public void handleCactusPhysics(Block cactusBlock) { + Plant plant = plantManager.getPlant(cactusBlock); + Block bottom = cactusBlock; + int stage = 0; + + if (plant == null) { + Block below = cactusBlock.getRelative(BlockFace.DOWN); + while (below.getType() == Material.CACTUS) { + below = below.getRelative(BlockFace.DOWN); + stage++; + } + bottom = below.getRelative(BlockFace.UP); + plant = plantManager.getPlant(bottom); + } + + if (plant == null) { + return; + } + + if (plant.getGrowthConfig() == null + || !(plant.getGrowthConfig().getGrower() instanceof ColumnPlantGrower grower)) { + plant.resetCreationTime(); + updateGrowthTime(plant, bottom); + } else { + long growthTime = plant.getGrowthConfig().getPersistentGrowthTime(bottom, true); + updateGrowthTimeOnDestruction(plant, stage, growthTime); + } + } + + public void handleBlockDestruction(Block block) { + if (plantManager == null) { + return; + } + Plant plant = plantManager.getPlant(block); + if (plant != null) { + plantManager.deletePlant(plant); + return; + } + + if (!handleColumnBlockDestruction(block)) { + handleColumnBlockingBlockDestruction(block.getRelative(BlockFace.DOWN)); + handleColumnBlockingBlockDestruction(block.getRelative(BlockFace.UP)); + } + + handleFruitBlockDestruction(block); + } + + private boolean handleColumnBlockDestruction(Block block) { + // column plants will always hold the plant object in the bottom most block, so + // we need + // to update that if we just broke one of the upper blocks of a column plant + + if (columnBlocks == null || !columnBlocks.contains(block.getType())) + return false; + + Block sourceColumn = VerticalGrower.getRelativeBlock(block, RBUtils.getGrowthDirection(block.getType()).getOppositeFace()); + Plant bottomColumnPlant = plantManager.getPlant(sourceColumn); + if (bottomColumnPlant == null) { + return false; + } + + if (bottomColumnPlant.getGrowthConfig() == null + || !(bottomColumnPlant.getGrowthConfig().getGrower() instanceof ColumnPlantGrower grower)) { + // Fallback behaviour + bottomColumnPlant.resetCreationTime(); + updateGrowthTime(bottomColumnPlant, sourceColumn); + return true; + } + + Block topColumn = VerticalGrower.getRelativeBlock(block, RBUtils.getGrowthDirection(block.getType())); + int blocksBroken = Math.abs(topColumn.getY() - block.getY()) + 1; + long growthTime = bottomColumnPlant.getGrowthConfig().getPersistentGrowthTime(sourceColumn, true); + int oldStage = grower.getStage(bottomColumnPlant); + int currentStage = Math.max(oldStage - blocksBroken, 0); + + updateGrowthTimeOnDestruction(bottomColumnPlant, currentStage, growthTime); + + return true; + } + + private void handleColumnBlockingBlockDestruction(Block block) { + if (columnBlocks == null || !columnBlocks.contains(block.getType())) + return; + + Block sourceColumn = VerticalGrower.getRelativeBlock(block, RBUtils.getGrowthDirection(block.getType()).getOppositeFace()); + Plant bottomColumnPlant = plantManager.getPlant(sourceColumn); + if (bottomColumnPlant == null || bottomColumnPlant.getNextUpdate() != Long.MAX_VALUE) + return; + + if (bottomColumnPlant.getGrowthConfig() == null + || !(bottomColumnPlant.getGrowthConfig().getGrower() instanceof ColumnPlantGrower grower)) { + bottomColumnPlant.resetCreationTime(); + return; + } + + long growthTime = bottomColumnPlant.getGrowthConfig().getPersistentGrowthTime(sourceColumn, true); + int stage = grower.getStage(bottomColumnPlant); + + updateGrowthTimeOnDestruction(bottomColumnPlant, stage, growthTime); + } + + private void updateGrowthTimeOnDestruction(Plant plant, int stage, long growthTime) { + ColumnPlantGrower grower = (ColumnPlantGrower) plant.getGrowthConfig().getGrower(); + long creationTime = System.currentTimeMillis() - growthTime * stage / grower.getMaxStage(); + + plant.setCreationTime(System.currentTimeMillis() - growthTime * stage / grower.getMaxStage()); + + double incPerStage = grower.getIncrementPerStage(); + double nextProgressStage = (stage + incPerStage) / grower.getMaxStage(); + nextProgressStage = Math.min(nextProgressStage, 1.0); + long timeFromCreationTillNextStage = (long) (growthTime * nextProgressStage); + long nextUpdateTime = creationTime + timeFromCreationTillNextStage; + + plant.setNextGrowthTime(nextUpdateTime); + } + + private void handleFruitBlockDestruction(Block block) { + if (fruitBlocks == null || !fruitBlocks.contains(block.getType())) + return; + + for (BlockFace face : WorldUtils.PLANAR_SIDES) { + Block possibleStem = block.getRelative(face); + Plant stem = plantManager.getPlant(possibleStem); + if (stem == null) { + continue; + } + if (stem.getGrowthConfig() == null || !(stem.getGrowthConfig().getGrower() instanceof FruitGrower)) { + continue; + } + FruitGrower grower = (FruitGrower) stem.getGrowthConfig().getGrower(); + if (grower.getFruitMaterial() != block.getType()) { + continue; + } + if (grower.getStage(stem) != grower.getMaxStage()) { + continue; + } + if (grower.getTurnedDirection(possibleStem) == face.getOppositeFace()) { + stem.resetCreationTime(); + grower.setStage(stem, 0); + updateGrowthTime(stem, possibleStem); + } + } + } + + private void initAdjacentPlantBlocks(Set growthConfigs) { + for (PlantGrowthConfig config : growthConfigs) { + if (config.getGrower() instanceof FruitGrower) { + FruitGrower grower = (FruitGrower) config.getGrower(); + if (fruitBlocks == null) { + fruitBlocks = new HashSet<>(); + } + fruitBlocks.add(grower.getFruitMaterial()); + continue; + } + if (config.getGrower() instanceof ColumnPlantGrower) { + ColumnPlantGrower grower = (ColumnPlantGrower) config.getGrower(); + if (columnBlocks == null) { + columnBlocks = new HashSet<>(); + } + columnBlocks.add(grower.getMaterial()); + } + if (config.getGrower() instanceof TipGrower) { + TipGrower grower = (TipGrower) config.getGrower(); + if (columnBlocks == null) { + columnBlocks = new HashSet<>(); + } + columnBlocks.add(grower.getTipMaterial()); + columnBlocks.add(grower.getStemMaterial()); + } + } + } + + public void handlePlantCreation(Block block, ItemStack itemUsed) { + if (plantManager == null) { + return; + } + PlantGrowthConfig growthConfig = growthConfigManager.getGrowthConfigByItem(itemUsed); + if (growthConfig == null) { + return; + } + if (growthConfig.getGrower() instanceof VerticalGrower verticalGrower) { + BlockFace direction = verticalGrower.getPrimaryGrowthDirection().getOppositeFace(); + Material blockMaterial = block.getType(); + Material oppositeMaterial = block.getRelative(direction).getType(); + if (oppositeMaterial == blockMaterial + || oppositeMaterial == RBUtils.getStemMaterial(blockMaterial) + || oppositeMaterial == RBUtils.getTipMaterial(blockMaterial)) { + handleColumnBlockAdded(block); + return; + } + } + Plant plant = new Plant(block.getLocation(), growthConfig); + Plant existingPlant = plantManager.getPlant(block); + if (existingPlant != null) { + plantManager.deletePlant(existingPlant); + } + plantManager.putPlant(plant); + updateGrowthTime(plant, block); + } + + private void handleColumnBlockAdded(Block block) { + if (columnBlocks == null || !columnBlocks.contains(block.getType())) + return; + + Block sourceColumn = VerticalGrower.getRelativeBlock(block, RBUtils.getGrowthDirection(block.getType()).getOppositeFace()); + Plant bottomColumnPlant = plantManager.getPlant(sourceColumn); + if (bottomColumnPlant == null) + return; + + if (bottomColumnPlant.getGrowthConfig() == null + || !(bottomColumnPlant.getGrowthConfig().getGrower() instanceof ColumnPlantGrower grower)) { + return; + } + + int stage = grower.getStage(bottomColumnPlant); + if (stage == grower.getMaxStage()) + return; + + long create = bottomColumnPlant.getCreationTime(); + long growthTime = bottomColumnPlant.getGrowthConfig().getPersistentGrowthTime(sourceColumn, true); + long newCreationTime = Math.min(System.currentTimeMillis(), create - growthTime / grower.getMaxStage()); + + bottomColumnPlant.setCreationTime(newCreationTime); + + updateGrowthTime(bottomColumnPlant, sourceColumn); + } + + public Block remapColumnBlock(Block block, PlantGrowthConfig growthConfig, Material material) { + if (!columnBlocks.contains(block.getType())) { + return block; + } + if (growthConfig.getGrower() instanceof VerticalGrower) { + BlockFace direction = ((VerticalGrower) growthConfig.getGrower()).getPrimaryGrowthDirection(); + Block below = block.getRelative(direction); + while (below.getType() == material) { + below = below.getRelative(direction); + } + return below.getRelative(direction.getOppositeFace()); + } + if (growthConfig.getGrower() instanceof TipGrower) { + TipGrower config = ((TipGrower) growthConfig.getGrower()); + BlockFace direction = config.getPrimaryGrowthDirection(); + Block adjacent = block.getRelative(direction); + while (adjacent.getType() == material || adjacent.getType() == config.getStemMaterial()) { + adjacent = adjacent.getRelative(direction); + } + return adjacent.getRelative(direction.getOppositeFace()); + } + return block; + } + + public void updateGrowthTime(Plant plant, Block block) { + PlantGrowthConfig growthConfig = plant.getGrowthConfig(); + if (growthConfig == null) { + growthConfig = growthConfigManager.getGrowthConfigFallback(block.getType()); + if (growthConfig == null) { + plantManager.deletePlant(plant); + return; + } + plant.setGrowthConfig(growthConfig); + } + long nextUpdateTime = growthConfig.updatePlant(plant, block); + if (nextUpdateTime != -1) { + plant.setNextGrowthTime(nextUpdateTime); + } + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/PlantManager.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/PlantManager.java new file mode 100644 index 0000000000..d6ade71590 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/PlantManager.java @@ -0,0 +1,50 @@ +package com.untamedears.realisticbiomes; + +import com.untamedears.realisticbiomes.model.Plant; +import com.untamedears.realisticbiomes.model.PlantLoadState; +import com.untamedears.realisticbiomes.model.RBChunkCache; +import org.bukkit.Location; +import org.bukkit.block.Block; +import vg.civcraft.mc.civmodcore.world.locations.chunkmeta.api.BlockBasedChunkMetaView; +import vg.civcraft.mc.civmodcore.world.locations.chunkmeta.block.BlockDataObjectLoadStatus; +import vg.civcraft.mc.civmodcore.world.locations.chunkmeta.block.table.TableBasedDataObject; +import vg.civcraft.mc.civmodcore.world.locations.chunkmeta.block.table.TableStorageEngine; + +public class PlantManager { + + private BlockBasedChunkMetaView> chunkMetaData; + + PlantManager(BlockBasedChunkMetaView> chunkMetaData) { + this.chunkMetaData = chunkMetaData; + } + + public void deletePlant(Plant plant) { + chunkMetaData.remove(plant); + } + + public Plant getPlant(Block block) { + return getPlant(block.getLocation()); + } + + public Plant getPlant(Location location) { + return (Plant) chunkMetaData.get(location); + } + + public PlantLoadState getPlantIfLoaded(Block block) { + return getPlantIfLoaded(block.getLocation()); + } + + public PlantLoadState getPlantIfLoaded(Location location) { + BlockDataObjectLoadStatus status = chunkMetaData.getIfLoaded(location); + return new PlantLoadState((Plant) status.data, status.isLoaded); + } + + public void putPlant(Plant plant) { + chunkMetaData.put(plant); + } + + void shutDown() { + chunkMetaData.disable(); + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/PlantProgressManager.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/PlantProgressManager.java new file mode 100644 index 0000000000..3682cbb6a5 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/PlantProgressManager.java @@ -0,0 +1,36 @@ +package com.untamedears.realisticbiomes; + +import com.untamedears.realisticbiomes.model.RBChunkCache; +import org.bukkit.Bukkit; +import vg.civcraft.mc.civmodcore.utilities.progress.ProgressTracker; + +/** + * Keeps track of which plant needs to be updated next + */ +public class PlantProgressManager { + + private ProgressTracker tracker; + + public PlantProgressManager() { + this.tracker = new ProgressTracker<>(); + Bukkit.getScheduler().scheduleSyncRepeatingTask(RealisticBiomes.getInstance(), this::processUpdates, 1L, 1L); + } + + public void addChunk(RBChunkCache cache) { + tracker.addItem(cache); + } + + public void processUpdates() { + tracker.processItems(); + } + + public void removeChunk(RBChunkCache cache) { + tracker.removeItem(cache); + } + + public void updateTime(RBChunkCache cache, long time) { + tracker.updateItem(cache, time); + cache.updateInternalProgressTime(time); + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/RBConfigManager.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/RBConfigManager.java new file mode 100644 index 0000000000..da7c0b0dbd --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/RBConfigManager.java @@ -0,0 +1,431 @@ +package com.untamedears.realisticbiomes; + +import static vg.civcraft.mc.civmodcore.config.ConfigHelper.parseMaterialList; + + +import com.untamedears.realisticbiomes.growth.AgeableGrower; +import com.untamedears.realisticbiomes.growth.BambooGrower; +import com.untamedears.realisticbiomes.growth.ColumnPlantGrower; +import com.untamedears.realisticbiomes.growth.FruitGrower; +import com.untamedears.realisticbiomes.growth.FungusGrower; +import com.untamedears.realisticbiomes.growth.HorizontalBlockSpreadGrower; +import com.untamedears.realisticbiomes.growth.IArtificialGrower; +import com.untamedears.realisticbiomes.growth.KelpGrower; +import com.untamedears.realisticbiomes.growth.SchematicGrower; +import com.untamedears.realisticbiomes.growth.SeaPickleGrower; +import com.untamedears.realisticbiomes.growth.StemGrower; +import com.untamedears.realisticbiomes.growth.TipGrower; +import com.untamedears.realisticbiomes.growth.TreeGrower; +import com.untamedears.realisticbiomes.growthconfig.BreakerConfig; +import com.untamedears.realisticbiomes.growthconfig.PlantGrowthConfig; +import com.untamedears.realisticbiomes.model.RBSchematic; +import com.untamedears.realisticbiomes.model.ltree.BlockTransformation; +import com.untamedears.realisticbiomes.model.ltree.LStepConfig; +import com.untamedears.realisticbiomes.model.ltree.LTree; +import com.untamedears.realisticbiomes.model.ltree.SpreadRule; +import com.untamedears.realisticbiomes.noise.BiomeConfiguration; +import com.untamedears.realisticbiomes.noise.Climate; +import com.untamedears.realisticbiomes.noise.CropNoise; +import com.untamedears.realisticbiomes.utils.SchematicUtils; +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.bukkit.block.Biome; +import org.bukkit.block.BlockFace; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; +import vg.civcraft.mc.civmodcore.ACivMod; +import vg.civcraft.mc.civmodcore.config.ConfigHelper; +import vg.civcraft.mc.civmodcore.config.ConfigParser; +import vg.civcraft.mc.civmodcore.dao.DatabaseCredentials; +import vg.civcraft.mc.civmodcore.dao.ManagedDatasource; +import vg.civcraft.mc.civmodcore.inventory.items.MaterialUtils; + +public class RBConfigManager extends ConfigParser { + + private ManagedDatasource database; + + private String legacyPrefix; + + private Set plantConfigs; + private BiomeConfiguration biomeConfiguration; + private Map schematics; + private List lTrees; + + private List bonemealPreventedBlocks; + + public RBConfigManager(ACivMod plugin) { + super(plugin); + } + + public ManagedDatasource getDatabase() { + return database; + } + + public String getLegacyPrefix() { + return legacyPrefix; + } + + public Set getPlantGrowthConfigs() { + return plantConfigs; + } + + private Map> loadBiomeAliases(ConfigurationSection config) { + Map> result = new HashMap<>(); + if (config == null) { + return result; + } + for (String key : config.getKeys(false)) { + if (!config.isList(key)) { + logger.warning( + "Invalid non-list entry " + key + " found at " + config.getCurrentPath() + ". It was ignored."); + continue; + } + List biomeStrings = config.getStringList(key); + List biomes = new ArrayList<>(); + Registry registry = RegistryAccess.registryAccess().getRegistry(RegistryKey.BIOME); + for (String biomeString : biomeStrings) { + try { + biomes.add(registry.get(NamespacedKey.minecraft(biomeString.toLowerCase().trim()))); + } catch (IllegalArgumentException e) { + logger.warning(biomeString + " at " + config.getCurrentPath() + "." + key + + " is not a valid biome, it was ignored"); + } + } + result.put(key, biomes); + } + return result; + } + + private void loadSchematics() { + this.schematics = new HashMap<>(); + File folder = new File(plugin.getDataFolder(), "schematics"); + for (RBSchematic schem : SchematicUtils.loadAll(folder, logger)) { + this.schematics.put(schem.getName().toLowerCase(), schem); + } + } + + @Override + protected boolean parseInternal(ConfigurationSection config) { + database = ManagedDatasource.construct((ACivMod) plugin, (DatabaseCredentials) config.get("database")); + legacyPrefix = config.getString("database_prefix", null); + Map> biomeAliases = loadBiomeAliases(config.getConfigurationSection("biome_aliases")); + loadSchematics(); + FarmBeaconManager farmBeaconManager = FarmBeaconManager.fromConfiguration(config); + biomeConfiguration = BiomeConfiguration.fromConfiguration(CropNoise.fromConfiguration(config), farmBeaconManager, config.getMapList("climate"), biomeAliases); + plantConfigs = parsePlantGrowthConfig(config.getConfigurationSection("plants"), farmBeaconManager, biomeConfiguration); + remapStemFruitConfigs(); + List rawConfigs = parseRawLStepConfigs(config.getConfigurationSection("l_steps")); + lTrees = parseLTrees(config.getConfigurationSection("l_trees"), rawConfigs); + bonemealPreventedBlocks = parseMaterialList(config, "no_bonemeal_blocks"); + return true; + } + + private void remapStemFruitConfigs() { + for (PlantGrowthConfig plantConfig : plantConfigs) { + if (!(plantConfig.getGrower() instanceof StemGrower)) { + continue; + } + StemGrower stemGrower = (StemGrower) plantConfig.getGrower(); + for (PlantGrowthConfig otherConfig : plantConfigs) { // dont care about O(n) here + if (otherConfig.getName().equalsIgnoreCase(stemGrower.getFruitConfigName())) { + stemGrower.setFruitConfig(otherConfig); + break; + } + } + if (stemGrower.getFruitConfig() == null) { + logger.severe("Stem config " + plantConfig.getName() + " specified fruit type " + + stemGrower.getFruitConfigName() + ", but no such config was found"); + } + + } + } + + private Map parseMaterialDoubleMap(ConfigurationSection parent, String identifier) { + Map result = new EnumMap<>(Material.class); + ConfigHelper.parseKeyValueMap(parent, identifier, logger, Material::valueOf, Double::parseDouble, result); + return result; + } + + private Set parsePlantGrowthConfig(ConfigurationSection config, FarmBeaconManager farmBeaconManager, BiomeConfiguration biomeConfiguration) { + Set result = new HashSet<>(); + if (config == null) { + logger.warning("No plant growth configs found"); + return result; + } + for (String key : config.getKeys(false)) { + if (!config.isConfigurationSection(key)) { + logger.warning("Ignoring invalid entry " + key + " at " + config.getCurrentPath()); + continue; + } + ConfigurationSection current = config.getConfigurationSection(key); + if (!current.isItemStack("item")) { + logger.warning("Growth config " + key + " does not have an item specified, it was ignored"); + continue; + } + ItemStack item = current.getItemStack("item", null); + List vanillaMats = parseMaterialList(current, "vanilla_materials"); + if (vanillaMats == null) { + vanillaMats = Collections.emptyList(); + } + Map greenHouseRates = parseMaterialDoubleMap(current, "greenhouse_rates"); + Map soilBoniPerLevel = parseMaterialDoubleMap(current, "soil_boni"); + if (!current.isString("persistent_growth_period")) { + logger.warning( + "persistent_growth_path not specified at " + current.getCurrentPath()); + continue; + } + long persistTime = ConfigHelper.parseTime(current.getString("persistent_growth_period"), TimeUnit.MILLISECONDS); + Climate idealClimate = new Climate( + current.getDouble("ideal_temperature"), + current.getDouble("ideal_humidity"), + current.getBoolean("saline", false), + current.getBoolean("hell", false)); + String name = current.getString("name", key); + short id = (short) current.getInt("id", 0); + if (id == 0) { + logger.warning( + "Need to specify a non 0 id for each plant type, missing at " + current.getCurrentPath()); + continue; + } + int maximumSoilLayers = current.getInt("soil_max_layers", 0); + double maximumSoilBonus = current.getDouble("max_soil_bonus", Integer.MAX_VALUE); + boolean allowBoneMeal = current.getBoolean("allow_bonemeal", false); + boolean needsLight = current.getBoolean("needs_sun_light", true); + boolean canBePlantedDirectly = current.getBoolean("can_be_planted", true); + boolean needsToBeWaterLogged = current.getBoolean("waterlog_required", false); + IArtificialGrower grower = parseGrower(current.getConfigurationSection("grower"), item); + if (grower == null) { + logger.warning("Failed to parse a grower at " + current.getCurrentPath() + ", skipped it"); + continue; + } + BreakerConfig breaker = BreakerConfig.fromConfig(current.getConfigurationSection("breaker")); + PlantGrowthConfig growthConfig = new PlantGrowthConfig(name, id, item, greenHouseRates, soilBoniPerLevel, + maximumSoilLayers, maximumSoilBonus, allowBoneMeal, idealClimate, persistTime, biomeConfiguration, breaker, needsLight, grower, vanillaMats, + canBePlantedDirectly, needsToBeWaterLogged, farmBeaconManager); + result.add(growthConfig); + } + return result; + } + + private IArtificialGrower parseGrower(ConfigurationSection section, ItemStack item) { + if (section == null) { + return null; + } + if (!section.isString("type")) { + logger.warning("No grower type specified at " + section.getCurrentPath()); + return null; + } + Material material = MaterialUtils.getMaterial(section.getString("material")); + if (material == null) { + if (item == null) { + logger.warning("Neither an item nor a material specified for grower at " + section.getCurrentPath()); + return null; + } + material = item.getType(); + } + switch (section.getString("type").toLowerCase()) { + case "bamboo": + int maxHeight = section.getInt("max_height", 12); + return new BambooGrower(maxHeight); + case "column": + int maxHeight2 = section.getInt("max_height", 3); + boolean instaBreakTouching = section.getBoolean("insta_break_touching", false); + BlockFace direction = BlockFace.valueOf(section.getString("direction", "UP")); + return new ColumnPlantGrower(maxHeight2, material, direction, instaBreakTouching); + case "fruit": + Material stemMat = MaterialUtils.getMaterial(section.getString("stem_type")); + if (stemMat == null) { + logger.warning("No stem material specified at " + section.getCurrentPath()); + return null; + } + Material attachedStemMat = MaterialUtils.getMaterial(section.getString("attached_stem_type")); + if (attachedStemMat == null) { + logger.warning("No attached stem material specified at " + section.getCurrentPath()); + return null; + } + return new FruitGrower(material, attachedStemMat, stemMat); + case "fungus": + return new FungusGrower(material); + case "tip": + Material stem = MaterialUtils.getMaterial(section.getString("stem_material")); + BlockFace growthDirection = BlockFace.valueOf(section.getString("growth_direction")); + int maxHeight3 = section.getInt("max_height", 25); + return new TipGrower(material, stem, growthDirection, maxHeight3); + case "kelp": + int maxHeight4 = section.getInt("max_height", 25); + return new KelpGrower(maxHeight4); + case "ageable": + int maxStage = section.getInt("max_stage", 7); + int increment = section.getInt("increment", 1); + return new AgeableGrower(material, maxStage, increment); + case "stem": + String fruitConfig = section.getString("fruit_config", null); + return new StemGrower(material, fruitConfig); + case "tree": + return new TreeGrower(material); + case "horizontalspread": + int maxAmount = section.getInt("max_amount"); + int horRange = section.getInt("max_range"); + List replaceableBlocks = parseMaterialList(section, "replaceable_blocks"); + List validSoil = parseMaterialList(section, "valid_soil"); + return new HorizontalBlockSpreadGrower(material, maxAmount, horRange, replaceableBlocks, validSoil); + case "seapickle": + return new SeaPickleGrower(); + case "schematic": + String name = section.getString("schematic", "default"); + RBSchematic schem = schematics.get(name.toLowerCase()); + if (schem == null) { + logger.warning( + "Schematic " + name + " specified at " + section.getCurrentPath() + " was not found"); + return null; + } + Location offSet = section.getLocation("offset", new Location(null, 0, 0, 0)); + return new SchematicGrower(schem, offSet); + default: + logger.warning(section.getString("type") + " is not a valid grower type"); + return null; + } + } + + public List parseLTrees(ConfigurationSection config, List stepConfig) { + List result = new ArrayList<>(); + if (config == null) { + return result; + } + Map configMap = stepConfig.stream().collect(Collectors.toMap(LStepConfig::getID, l -> l)); + for (String key : config.getKeys(false)) { + if (!config.isConfigurationSection(key)) { + logger.warning("Found invalid entry " + key + " at " + config.getCurrentPath()); + continue; + } + ConfigurationSection current = config.getConfigurationSection(key); + String start = current.getString("start_symbol"); + Vector defaultVector = current.getVector("start_direction"); + Map> spreadRules = new HashMap<>(); + ConfigurationSection spreadSection = current.getConfigurationSection("rules"); + if (spreadSection != null) { + for (String spreadKey : spreadSection.getKeys(false)) { + List localSpreadRules = new ArrayList<>(); + ConfigurationSection currentKeySection = spreadSection.getConfigurationSection(spreadKey); + for (String subKey : currentKeySection.getKeys(false)) { + ConfigurationSection subSection = currentKeySection.getConfigurationSection(subKey); + double chance = subSection.getDouble("chance", 1.0); + List targets = new ArrayList<>(); + List targetDirections = new ArrayList<>(); + ConfigurationSection resultSection = subSection.getConfigurationSection("results"); + if (resultSection != null) { + for (String resultKey : resultSection.getKeys(false)) { + ConfigurationSection currentResultSection = resultSection + .getConfigurationSection(resultKey); + String name = currentResultSection.getString("name"); + Vector direction = currentResultSection.getVector("direction"); + if (name == null || direction == null) { + logger.warning("Incomplete entry at " + currentResultSection.getCurrentPath() + + " was ignored"); + continue; + } + targets.add(name); + targetDirections.add(direction); + } + } + localSpreadRules.add(new SpreadRule(chance, targets, targetDirections)); + } + spreadRules.put(spreadKey, localSpreadRules); + } + } + result.add(new LTree(key, start, defaultVector, spreadRules, i -> { + LStepConfig lConfig = configMap.get(i); + if (lConfig != null) { + lConfig = lConfig.clone(); + } else { + logger.warning("No lstep config with name " + i + " was specified, but it was set as target"); + } + return lConfig; + })); + } + return result; + } + + public List getLTrees() { + return lTrees; + } + + public List getBonemealPreventedBlocks() { + return bonemealPreventedBlocks; + } + + public List parseRawLStepConfigs(ConfigurationSection config) { + List result = new ArrayList<>(); + if (config == null) { + return result; + } + for (String key : config.getKeys(false)) { + if (!config.isConfigurationSection(key)) { + logger.warning("Found invalid entry " + key + " at " + config.getCurrentPath()); + continue; + } + ConfigurationSection current = config.getConfigurationSection(key); + double directionWeight = current.getDouble("direction_weight", 1.0); + List transforms = parseBlockTransformations( + current.getConfigurationSection("transformations")); + boolean normalizeBeforeDirectionTransformation = current + .getBoolean("normalize_direction_before_transformation", true); + boolean transformInWorldCoordinates = current.getBoolean("transform_in_world_coords", false); + result.add(new LStepConfig(key, directionWeight, transforms, normalizeBeforeDirectionTransformation, + transformInWorldCoordinates)); + } + return result; + } + + private List parseBlockTransformations(ConfigurationSection config) { + List result = new ArrayList<>(); + if (config == null) { + return result; + } + for (String key : config.getKeys(false)) { + if (!config.isConfigurationSection(key)) { + logger.warning("Found invalid entry " + key + " at " + config.getCurrentPath()); + continue; + } + ConfigurationSection current = config.getConfigurationSection(key); + Material mat; + try { + mat = Material.valueOf(current.getString("material", "")); + } catch (IllegalArgumentException e) { + logger.warning("No material specified at " + current.getCurrentPath()); + continue; + } + Map blockData = new HashMap<>(); + ConfigurationSection dataSection = current.getConfigurationSection("data"); + if (dataSection != null) { + for (String dataKey : dataSection.getKeys(false)) { + String value = dataSection.getString(dataKey); + blockData.put(dataKey, value); + } + } + + result.add(new BlockTransformation(mat, blockData)); + } + return result; + } + + public BiomeConfiguration getBiomeConfiguration() { + return biomeConfiguration; + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/RealisticBiomes.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/RealisticBiomes.java new file mode 100644 index 0000000000..fb5a841fce --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/RealisticBiomes.java @@ -0,0 +1,130 @@ +package com.untamedears.realisticbiomes; + +import com.untamedears.realisticbiomes.breaker.BreakListener; +import com.untamedears.realisticbiomes.breaker.BreakManager; +import com.untamedears.realisticbiomes.breaker.TreeBlockListener; +import com.untamedears.realisticbiomes.commands.RBCommandManager; +import com.untamedears.realisticbiomes.listener.BonemealListener; +import com.untamedears.realisticbiomes.listener.MobListener; +import com.untamedears.realisticbiomes.listener.PlantListener; +import com.untamedears.realisticbiomes.listener.PlayerListener; +import com.untamedears.realisticbiomes.model.Plant; +import com.untamedears.realisticbiomes.model.RBChunkCache; +import com.untamedears.realisticbiomes.model.RBDAO; +import com.untamedears.realisticbiomes.breaker.AutoReplantListener; +import com.untamedears.realisticbiomes.noise.MeasureCommand; +import com.untamedears.realisticbiomes.noise.MeasureListener; +import com.untamedears.realisticbiomes.noise.SetYieldCommand; +import org.bukkit.Bukkit; +import org.bukkit.plugin.PluginManager; +import vg.civcraft.mc.civmodcore.ACivMod; +import vg.civcraft.mc.civmodcore.world.locations.chunkmeta.api.BlockBasedChunkMetaView; +import vg.civcraft.mc.civmodcore.world.locations.chunkmeta.api.ChunkMetaAPI; +import vg.civcraft.mc.civmodcore.world.locations.chunkmeta.block.table.TableBasedDataObject; +import vg.civcraft.mc.civmodcore.world.locations.chunkmeta.block.table.TableStorageEngine; + +public class RealisticBiomes extends ACivMod { + + private static RealisticBiomes plugin; + + public static RealisticBiomes getInstance() { + return plugin; + } + + private GrowthConfigManager growthConfigManager; + private RBConfigManager configManager; + private PlantManager plantManager; + private PlantLogicManager plantLogicManager; + private PlantProgressManager plantProgressManager; + private RBCommandManager commandManager; + + private RBDAO dao; + + public RBConfigManager getConfigManager() { + return configManager; + } + + public RBDAO getDAO() { + return dao; + } + + public GrowthConfigManager getGrowthConfigManager() { + return growthConfigManager; + } + + public PlantLogicManager getPlantLogicManager() { + return plantLogicManager; + } + + public PlantManager getPlantManager() { + return plantManager; + } + + public PlantProgressManager getPlantProgressManager() { + return plantProgressManager; + } + + @Override + public void onDisable() { + dao.setBatchMode(true); + if (plantManager != null) { + plantManager.shutDown(); + } + dao.cleanupBatches(); + } + + @Override + public void onEnable() { + super.onEnable(); + RealisticBiomes.plugin = this; + configManager = new RBConfigManager(this); + if (!configManager.parse()) { + return; + } + growthConfigManager = new GrowthConfigManager(configManager.getPlantGrowthConfigs()); + this.dao = new RBDAO(getLogger(), configManager.getDatabase()); + if (!dao.updateDatabase()) { + Bukkit.shutdown(); + return; + } + plantProgressManager = new PlantProgressManager(); + BlockBasedChunkMetaView> chunkMetaData = ChunkMetaAPI + .registerBlockBasedPlugin(this, () -> { + return new RBChunkCache(false, dao); + }, dao, false); + if (chunkMetaData == null) { + getLogger().severe("Errors setting up chunk metadata API, shutting down"); + Bukkit.shutdown(); + return; + } + plantManager = new PlantManager(chunkMetaData); + commandManager = new RBCommandManager(this); + + BreakManager breakManager = BreakManager.fromPlantConfigs(configManager.getBiomeConfiguration(), configManager.getPlantGrowthConfigs()); + if (getConfig().getBoolean("auto_replant_enabled", true)) { + getServer().getPluginManager() + .registerEvents(new AutoReplantListener(getConfig().getBoolean("auto_replant_right_click", true)), this); + } + getServer().getPluginManager().registerEvents(new BreakListener(breakManager), this); + + getServer().getPluginManager().registerEvents(MeasureListener.fromConfiguration(configManager.getBiomeConfiguration(), getConfig()), this); + + getServer().getPluginManager().registerEvents(new TreeBlockListener(), this); + + commandManager.registerCommand(new MeasureCommand(configManager.getBiomeConfiguration(), breakManager)); + commandManager.registerCommand(new SetYieldCommand(configManager.getBiomeConfiguration())); + + plantLogicManager = new PlantLogicManager(plantManager, growthConfigManager); + + registerListeners(); + } + + private void registerListeners() { + PluginManager pm = getServer().getPluginManager(); + pm.registerEvents(new PlantListener(this, plantManager, plantLogicManager), this); + pm.registerEvents(new PlayerListener(growthConfigManager, plantManager), this); + pm.registerEvents(new BonemealListener(configManager.getBonemealPreventedBlocks()), this); + pm.registerEvents(new MobListener(), this); + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/breaker/AutoReplantListener.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/breaker/AutoReplantListener.java new file mode 100644 index 0000000000..267fdf5763 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/breaker/AutoReplantListener.java @@ -0,0 +1,276 @@ +package com.untamedears.realisticbiomes.breaker; + +import com.untamedears.realisticbiomes.RealisticBiomes; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.UUID; + +import com.untamedears.realisticbiomes.utils.InfoStick; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.Ageable; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.type.Cocoa; +import org.bukkit.entity.Player; +import org.bukkit.event.Event.Result; +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.player.PlayerHarvestBlockEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import vg.civcraft.mc.civmodcore.inventory.items.MaterialUtils; +import vg.civcraft.mc.civmodcore.players.settings.PlayerSettingAPI; +import vg.civcraft.mc.civmodcore.players.settings.gui.MenuSection; +import vg.civcraft.mc.civmodcore.players.settings.impl.BooleanSetting; + +public class AutoReplantListener implements Listener { + + private final boolean rightClick; + + private BooleanSetting toggleAutoReplant; + + public AutoReplantListener(boolean rightClick) { + this.rightClick = rightClick; + initSettings(); + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onBlockBreak(BlockBreakEvent event) { + if (rightClick) { + return; + } + + Block block = event.getBlock(); + Player player = event.getPlayer(); + PlayerInventory inventory = player.getInventory(); + if (!getToggleAutoReplant(player.getUniqueId())) { + return; + } + Material seed = getSeed(block.getType()); + if (seed == null) { + return; + } + if (!isFullyGrown(block)) { + return; + } + if (!playerHasSeeds(inventory, seed)) { + return; + } + replantCrop(block, seed, inventory); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onRightClick(PlayerInteractEvent event) { + if (!rightClick + || event.getHand() != EquipmentSlot.HAND) // Process only the main hand (no need to show 'no perms' twice) + { + return; + } + + Block block = event.getClickedBlock(); + if (block == null || event.getAction() != Action.RIGHT_CLICK_BLOCK) { + return; + } + + ItemStack item = event.getItem(); + if (item != null && item.getType() == InfoStick.INFO_STICK_TYPE) { + return; + } + + Player player = event.getPlayer(); + if (!getToggleAutoReplant(player.getUniqueId())) { + return; + } + Material baseSeed = getSeed(block.getType()); + if (baseSeed == null) { + return; + } + + if (!isFullyGrown(block)) { + return; + } + + List blocks = new ArrayList<>(); + if (item != null && item.getType() == Material.NETHERITE_HOE) { + item.damage(1, player); + for (int x = -1; x <= 1; x++) { + for (int z = -1; z <= 1; z++) { + blocks.add(block.getRelative(x, 0, z)); + } + } + } else { + blocks.add(block); + } + + for (Block harvestBlock : blocks) { + Material seed = getSeed(harvestBlock.getType()); + if (seed == null) { + continue; + } + if (!isFullyGrown(harvestBlock)) { + continue; + } + + replantCropFromDrops(event, harvestBlock, seed); + } + } + + /** + * Takes a Material and checks if its a crop, returns seeds if it is, null if it isn't. + * + * @return Seed Material + */ + private Material getSeed(Material material) { + return switch (material) { + case WHEAT -> Material.WHEAT_SEEDS; + case CARROTS -> Material.CARROT; + case POTATOES -> Material.POTATO; + case BEETROOTS -> Material.BEETROOT_SEEDS; + case NETHER_WART -> Material.NETHER_WART; + case COCOA -> Material.COCOA_BEANS; + default -> null; + }; + } + + /** + * Checks a players inventory for a given seed + * + * @param inventory PlayerInventory + * @param seeds Seed we are looking for + * @return true if found + */ + private boolean playerHasSeeds(PlayerInventory inventory, Material seeds) { + ItemStack[] items = inventory.getContents(); + for (ItemStack item : items) { + if (item == null) { + continue; + } + if (item.getType() == seeds) { + return true; + } + } + return false; + } + + /** + * Runnable that replants the crop 5 ticks later, also updates PlantLogicManager to handle plant creation + */ + private void replantCrop(Block block, Material seed, PlayerInventory inventory) { + Material plant = block.getType(); + BlockData data = block.getBlockData(); + Bukkit.getScheduler().runTaskLater(RealisticBiomes.getInstance(), () -> { + if (!MaterialUtils.isAir(block.getType())) { + return; + } else if (!removeSeedFromPlayerInv(inventory, seed)) { + return; + } + resetCropState(block, data, plant); + RealisticBiomes.getInstance().getPlantLogicManager().handlePlantCreation(block, new ItemStack(seed)); + }, 5L); + } + + private void replantCropFromDrops(PlayerInteractEvent event, Block block, Material seed) { + // Subtract one seed from the drops, to be used for replanting + List drops = new ArrayList<>(block.getDrops(event.getItem())); + boolean hasSeeds = false; + for (ItemStack drop : drops) { + if (drop.getType() == seed) { + hasSeeds = true; + } + } + + if (!hasSeeds) { + return; + } + + // Citadel will pick this up and cancel if we don't have permission + PlayerHarvestBlockEvent harvestBlockEvent = new PlayerHarvestBlockEvent(event.getPlayer(), block, event.getHand(), drops); + Bukkit.getPluginManager().callEvent(harvestBlockEvent); + if (harvestBlockEvent.isCancelled()) { + event.setCancelled(true); + return; + } + + Iterator it = drops.iterator(); + boolean hasRemovedSeed = false; + while (it.hasNext()) { + ItemStack drop = it.next(); + if (drop.getType() == seed) { + int amount = drop.getAmount(); + if (amount >= 1) { + hasRemovedSeed = true; + if (amount == 1) { + it.remove(); + } else { + drop.setAmount(amount - 1); + } + break; + } + } + } + + if (!hasRemovedSeed) { + return; + } + + resetCropState(block, block.getBlockData(), block.getType()); + + for (ItemStack drop : drops) { + block.getWorld().dropItemNaturally(block.getLocation().add(0.5, 0.5, 0.5), drop); + } + RealisticBiomes.getInstance().getPlantLogicManager().handlePlantCreation(block, new ItemStack(seed)); + event.setUseItemInHand(Result.DENY); // Don't let player place a block on top of the crop + } + + private void resetCropState(Block block, BlockData previousData, Material plant) { + if (plant == Material.COCOA) { + BlockFace previousFacing = ((Cocoa) previousData).getFacing(); + block.setType(plant); + Cocoa cocoa = (Cocoa) block.getBlockData(); + cocoa.setFacing(previousFacing); + block.setBlockData(cocoa); + } else { + block.setType(plant); + } + } + + private boolean removeSeedFromPlayerInv(PlayerInventory inventory, Material seed) { + ItemStack[] items = inventory.getContents(); + for (ItemStack item : items) { + if (item == null) { + continue; + } + if (item.getType() == seed) { + item.setAmount(item.getAmount() - 1); + return true; + } + } + return false; + } + + private boolean isFullyGrown(Block block) { + Ageable crop = (Ageable) block.getBlockData(); + return crop.getAge() == crop.getMaximumAge(); + } + + private void initSettings() { + MenuSection rbMenu = PlayerSettingAPI.getMainMenu() + .createMenuSection("RealisticBiomes", "Auto replant setting", new ItemStack( + Material.WHEAT_SEEDS)); + toggleAutoReplant = new BooleanSetting(RealisticBiomes.getInstance(), true, "Use auto replant?", "autoReplant", + rightClick ? "Will automatically harvest and replant a crop when right clicked" : "Will automatically take seeds from your inventory and replant crops"); + PlayerSettingAPI.registerSetting(toggleAutoReplant, rbMenu); + } + + public boolean getToggleAutoReplant(UUID uuid) { + return toggleAutoReplant.getValue(uuid); + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/breaker/BreakListener.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/breaker/BreakListener.java new file mode 100644 index 0000000000..8787100676 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/breaker/BreakListener.java @@ -0,0 +1,219 @@ +package com.untamedears.realisticbiomes.breaker; + +import com.destroystokyo.paper.event.block.BlockDestroyEvent; +import com.destroystokyo.paper.event.server.ServerTickEndEvent; +import com.untamedears.realisticbiomes.utils.RBUtils; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import org.bukkit.ExplosionResult; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.Ageable; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockExplodeEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.block.LeavesDecayEvent; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.player.PlayerHarvestBlockEvent; +import org.bukkit.inventory.ItemStack; + +public class BreakListener implements Listener { + + private final BreakManager breakManager; + + private Set wasColumnLastTick = new HashSet<>(); + private Set wasColumnThisTick = new HashSet<>(); + + public BreakListener(BreakManager breakManager) { + this.breakManager = breakManager; + } + + @EventHandler + public void on(ServerTickEndEvent event) { + Set temp = this.wasColumnLastTick; + this.wasColumnLastTick = this.wasColumnThisTick; + temp.clear(); + this.wasColumnThisTick = temp; + } + + @EventHandler + public void on(PlayerHarvestBlockEvent event) { + Block block = event.getHarvestedBlock(); + if (!breakManager.isControlledCrop(block)) { + return; + } + + List drops = breakManager.calculateDrops(block, event.getItemsHarvested()); + event.getItemsHarvested().clear(); + event.getItemsHarvested().addAll(drops); + } + + private boolean isFarmable(Material material) { + return material == Material.BEETROOTS || material == Material.WHEAT || material == Material.POTATOES || material == Material.CARROTS || material == Material.COCOA || material == Material.NETHER_WART; + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockPlaceEvent event) { + Material type = event.getBlock().getType(); + BlockFace direction = RBUtils.getGrowthDirection(type); + if (direction == BlockFace.SELF) { + return; + } + if (!breakManager.isControlledCrop(event.getBlock())) { + return; + } + + if (type != event.getBlock().getRelative(direction.getOppositeFace()).getType()) { + return; + } + + event.setCancelled(true); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onPumpkinMelon(BlockPlaceEvent event) { + Material type = event.getBlock().getType(); + if (type == Material.PUMPKIN || type == Material.MELON) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(EntityExplodeEvent event) { + if (event.getExplosionResult() == ExplosionResult.TRIGGER_BLOCK || event.getExplosionResult() == ExplosionResult.KEEP) { + return; + } + for (Iterator iterator = event.blockList().iterator(); iterator.hasNext(); ) { + Block block = iterator.next(); + if (!breakManager.isControlledCrop(block)) { + continue; + } + + if (TreeDelegate.isTreeBlock(block.getType()) && !TreeDelegate.remove(block)) { + continue; + } + for (ItemStack item : breakManager.calculateDrops(block, block.getDrops())) { + block.getWorld().dropItemNaturally(block.getLocation().toCenterLocation(), item); + } + iterator.remove(); + block.setType(Material.AIR); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockExplodeEvent event) { + if (event.getExplosionResult() == ExplosionResult.TRIGGER_BLOCK || event.getExplosionResult() == ExplosionResult.KEEP) { + return; + } + for (Iterator iterator = event.blockList().iterator(); iterator.hasNext(); ) { + Block block = iterator.next(); + if (!breakManager.isControlledCrop(block)) { + continue; + } + + if (TreeDelegate.isTreeBlock(block.getType()) && !TreeDelegate.remove(block)) { + continue; + } + for (ItemStack item : breakManager.calculateDrops(block, block.getDrops())) { + block.getWorld().dropItemNaturally(block.getLocation().toCenterLocation(), item); + } + iterator.remove(); + block.setType(Material.AIR); + } + } + + // todo fix cactus popping off when it grows and having vanilla drops + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(LeavesDecayEvent event) { + if (!breakManager.isControlledCrop(event.getBlock())) { + return; + } + + if (TreeDelegate.isTreeBlock(event.getBlock().getType()) && !TreeDelegate.remove(event.getBlock())) { + return; + } + + for (ItemStack item : breakManager.calculateDrops(event.getBlock(), event.getBlock().getDrops())) { + event.getBlock().getWorld().dropItemNaturally(event.getBlock().getLocation().toCenterLocation(), item); + } + event.getBlock().setType(Material.AIR); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockDestroyEvent event) { + if (!event.willDrop()) { + return; + } + + Material type = event.getBlock().getType(); + if (!breakManager.isControlledCrop(event.getBlock())) { + return; + } + + if (TreeDelegate.isTreeBlock(event.getBlock().getType()) && !TreeDelegate.remove(event.getBlock())) { + return; + } + + BlockFace direction = RBUtils.getGrowthDirection(type); + if (direction != BlockFace.SELF) { + Block below = event.getBlock().getRelative(direction.getOppositeFace()); + if (!this.wasColumnLastTick.contains(below)) { + if (below.getType() != type && below.getType() != Material.AIR) { + // base blocks have normal drops + } else { + event.setWillDrop(false); + } + return; + } + this.wasColumnThisTick.add(event.getBlock()); + } + + for (ItemStack item : breakManager.calculateDrops(event.getBlock(), event.getBlock().getDrops())) { + event.getBlock().getWorld().dropItemNaturally(event.getBlock().getLocation().toCenterLocation(), item); + } + event.setWillDrop(false); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockBreakEvent event) { + if (!event.isDropItems()) { + return; + } + Block block = event.getBlock(); + + if (!breakManager.isControlledCrop(block)) { + return; + } + + if (TreeDelegate.isTreeBlock(block.getType()) && !TreeDelegate.remove(block)) { + return; + } + + BlockFace direction = RBUtils.getGrowthDirection(block.getType()); + if (direction != BlockFace.SELF) { + this.wasColumnLastTick.add(event.getBlock()); + if (event.getBlock().getType() != event.getBlock().getRelative(direction.getOppositeFace()).getType()) { + // base blocks have normal drops + return; + } + } else if (isFarmable(block.getType()) + && block.getBlockData() instanceof Ageable ageable + && ageable.getAge() != ageable.getMaximumAge()) { + return; + } + + // todo fix cactus + List drops = breakManager.calculateDrops(block, block.getDrops(event.getPlayer().getInventory().getItemInMainHand(), event.getPlayer())); + for (ItemStack item : drops) { + block.getWorld().dropItemNaturally(block.getLocation().toCenterLocation(), item); + } + event.setDropItems(false); + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/breaker/BreakManager.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/breaker/BreakManager.java new file mode 100644 index 0000000000..ce60048cbd --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/breaker/BreakManager.java @@ -0,0 +1,131 @@ +package com.untamedears.realisticbiomes.breaker; + +import com.untamedears.realisticbiomes.growthconfig.BreakerConfig; +import com.untamedears.realisticbiomes.growthconfig.PlantGrowthConfig; +import com.untamedears.realisticbiomes.noise.BiomeConfiguration; +import com.untamedears.realisticbiomes.noise.Climate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.inventory.ItemStack; + +/* + + JUNGLE_LEAVES: 0.025 + DARK_OAK_LEAVES: 0.05 + */ +public class BreakManager { + + private static final Map PRIMARY_ITEM_TYPE = Map.ofEntries( + Map.entry(Material.BEETROOTS, Material.BEETROOT), + Map.entry(Material.WHEAT, Material.WHEAT), + Map.entry(Material.POTATOES, Material.POTATO), + Map.entry(Material.CARROTS, Material.CARROT), + Map.entry(Material.COCOA, Material.COCOA_BEANS), + Map.entry(Material.PUMPKIN, Material.PUMPKIN), + Map.entry(Material.MELON, Material.MELON), + Map.entry(Material.KELP_PLANT, Material.KELP), + Map.entry(Material.OAK_LEAVES, Material.OAK_SAPLING), + Map.entry(Material.BIRCH_LEAVES, Material.BIRCH_SAPLING), + Map.entry(Material.ACACIA_LEAVES, Material.ACACIA_SAPLING), + Map.entry(Material.DARK_OAK_LEAVES, Material.DARK_OAK_SAPLING), + Map.entry(Material.MANGROVE_LEAVES, Material.MANGROVE_PROPAGULE), + Map.entry(Material.CHERRY_LEAVES, Material.CHERRY_SAPLING), + Map.entry(Material.SPRUCE_LEAVES, Material.SPRUCE_SAPLING), + Map.entry(Material.JUNGLE_LEAVES, Material.JUNGLE_SAPLING), + Map.entry(Material.PALE_OAK_LEAVES, Material.PALE_OAK_SAPLING), + Map.entry(Material.SWEET_BERRY_BUSH, Material.SWEET_BERRIES), + Map.entry(Material.CACTUS, Material.CACTUS), + Map.entry(Material.WEEPING_VINES_PLANT, Material.WEEPING_VINES), + Map.entry(Material.WEEPING_VINES, Material.WEEPING_VINES), + Map.entry(Material.TWISTING_VINES_PLANT, Material.TWISTING_VINES), + Map.entry(Material.TWISTING_VINES, Material.TWISTING_VINES), + Map.entry(Material.SUGAR_CANE, Material.SUGAR_CANE), + Map.entry(Material.BAMBOO, Material.BAMBOO), + Map.entry(Material.RED_MUSHROOM_BLOCK, Material.RED_MUSHROOM), + Map.entry(Material.BROWN_MUSHROOM_BLOCK, Material.BROWN_MUSHROOM), + Map.entry(Material.NETHER_WART, Material.NETHER_WART) + ); + + private final BiomeConfiguration biomes; + private final Map maxYield; + private final Map climates; + + public BreakManager(BiomeConfiguration biomes, Map maxYield, Map climates) { + this.biomes = biomes; + this.maxYield = maxYield; + this.climates = climates; + } + + public static BreakManager fromPlantConfigs(BiomeConfiguration biomes, Set configs) { + Map maxYield = new HashMap<>(); + Map climates = new HashMap<>(); + for (PlantGrowthConfig config : configs) { + BreakerConfig breaker = config.getBreaker(); + if (breaker != null) { + maxYield.put(breaker.type(), breaker.maxYield()); + climates.put(breaker.type(), config.getClimate()); + if (breaker.type2() != null) { + maxYield.put(breaker.type2(), breaker.maxYield()); + climates.put(breaker.type2(), config.getClimate()); + } + } + } + return new BreakManager(biomes, maxYield, climates); + } + + public Map getClimates() { + return this.climates; + } + + public Map getMaxYield() { + return maxYield; + } + + public boolean isControlledCrop(Block block) { + Climate climate = this.climates.get(block.getType()); + if (climate == null) { + return false; + } + if (!this.maxYield.containsKey(block.getType())) { + return false; + } + return true; + } + + public List calculateDrops(Block block, Collection dropsCollection) { + List drops = new ArrayList<>(dropsCollection); + if (!maxYield.containsKey(block.getType())) { + return drops; + } + + int multiplier = maxYield.get(block.getType()); + double yield = this.biomes.getYield(block, this.climates.get(block.getType()), block.getType(), multiplier); + + Material primary = PRIMARY_ITEM_TYPE.get(block.getType()); + if (primary == null) { + return drops; + } + + for (Iterator iterator = drops.iterator(); iterator.hasNext(); ) { + ItemStack drop = iterator.next(); + if (drop.getType() == primary) { + int amount = (int) ((double) multiplier * yield * drop.getAmount()); + if (amount == 0) { + iterator.remove(); + } else { + drop.setAmount(amount); + HandPicked.markHandPicked(drop); + } + } + } + + return drops; + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/breaker/HandPicked.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/breaker/HandPicked.java new file mode 100644 index 0000000000..db8effaa01 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/breaker/HandPicked.java @@ -0,0 +1,36 @@ +package com.untamedears.realisticbiomes.breaker; + +import com.untamedears.realisticbiomes.RealisticBiomes; +import net.kyori.adventure.text.Component; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.plugin.java.JavaPlugin; +import java.util.List; + +public class HandPicked { + private static final NamespacedKey KEY = new NamespacedKey(JavaPlugin.getPlugin(RealisticBiomes.class), "hand_picked"); + + public static ItemStack markHandPicked(ItemStack item) { + ItemMeta meta = item.getItemMeta(); + meta.lore(List.of( + Component.empty().append(Component.text("Hand picked")), + Component.empty().append(Component.text("Unbruised and suitable for use in XP production")) + )); + meta.getPersistentDataContainer().set(KEY, PersistentDataType.BOOLEAN, true); + item.setItemMeta(meta); + return item; + } + + public static boolean isHandPicked(ItemStack item) { + return item.getPersistentDataContainer().has(KEY); + } + + public static ItemStack removeHandPicked(ItemStack item) { + ItemMeta meta = item.getItemMeta(); + meta.getPersistentDataContainer().remove(KEY); + item.setItemMeta(meta); + return item; + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/breaker/TreeBlockListener.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/breaker/TreeBlockListener.java new file mode 100644 index 0000000000..883bdd2072 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/breaker/TreeBlockListener.java @@ -0,0 +1,56 @@ +package com.untamedears.realisticbiomes.breaker; + +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import org.bukkit.Material; +import org.bukkit.Tag; +import org.bukkit.block.Block; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import vg.civcraft.mc.civmodcore.inventory.CustomItem; + +public class TreeBlockListener implements Listener { + + private static final ItemStack WOOD_SCRAP; + + static { + WOOD_SCRAP = new ItemStack(Material.OAK_PRESSURE_PLATE); + + ItemMeta meta = WOOD_SCRAP.getItemMeta(); + meta.itemName(Component.text("Wood scrap", TextColor.color(0xbc7020))); + meta.lore(List.of(Component.text("Leftover scrap wood from cutting down a tree", NamedTextColor.WHITE), + Component.text("Convert into chests in a Carpentry Factory", NamedTextColor.WHITE))); + + WOOD_SCRAP.setItemMeta(meta); + + CustomItem.registerCustomItem("wood_scrap", WOOD_SCRAP); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(BlockBreakEvent event) { + Block block = event.getBlock(); + if (!Tag.LOGS.isTagged(block.getType())) { + return; + } + + if (!TreeDelegate.remove(block)) { + return; + } + + ItemStack item = event.getPlayer().getInventory().getItemInMainHand(); + if (item.getType() != Material.NETHERITE_AXE) { + return; + } + + if (ThreadLocalRandom.current().nextInt(5) == 0) { + block.getWorld().dropItemNaturally(block.getLocation().add(0.5, 0.5, 0.5), WOOD_SCRAP); + } + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/breaker/TreeDelegate.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/breaker/TreeDelegate.java new file mode 100644 index 0000000000..9d479ca068 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/breaker/TreeDelegate.java @@ -0,0 +1,69 @@ +package com.untamedears.realisticbiomes.breaker; + +import com.untamedears.realisticbiomes.RealisticBiomes; +import java.util.function.Consumer; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Tag; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.plugin.java.JavaPlugin; + +public class TreeDelegate implements Consumer { + + public static final NamespacedKey TREE_BLOCKS = new NamespacedKey(JavaPlugin.getPlugin(RealisticBiomes.class), "tree_blocks"); + + @Override + public void accept(BlockState blockState) { + if (!isTreeBlock(blockState.getType())) { + return; + } + + PersistentDataContainer pdc = blockState.getChunk().getPersistentDataContainer(); + int[] ints = pdc.get(TREE_BLOCKS, PersistentDataType.INTEGER_ARRAY); + IntArrayList list = ints == null ? new IntArrayList() : new IntArrayList(ints); + for (int i = 0; i < list.size(); i += 3) { + if (list.getInt(i) == blockState.getX() && list.getInt(i + 1) == blockState.getY() && list.getInt(i + 2) == blockState.getZ()) { + return; + } + } + list.ensureCapacity(3); + list.add(blockState.getX()); + list.add(blockState.getY()); + list.add(blockState.getZ()); + + pdc.set(TREE_BLOCKS, PersistentDataType.INTEGER_ARRAY, list.toIntArray()); + } + + public static boolean isTreeBlock(Material type) { + return Tag.LOGS.isTagged(type) || Tag.LEAVES.isTagged(type) || type == Material.BROWN_MUSHROOM_BLOCK || type == Material.RED_MUSHROOM_BLOCK; + } + + public static boolean remove(Block block) { + PersistentDataContainer pdc = block.getChunk().getPersistentDataContainer(); + int[] ints = pdc.get(TreeDelegate.TREE_BLOCKS, PersistentDataType.INTEGER_ARRAY); + if (ints == null) { + return false; + } + + IntList list = new IntArrayList(ints); + int index = -1; + for (int i = 0; i < list.size(); i += 3) { + if (list.getInt(i) == block.getX() && list.getInt(i + 1) == block.getY() && list.getInt(i + 2) == block.getZ()) { + index = i; + break; + } + } + if (index == -1) { + return false; + } + + list.removeElements(index, index + 3); + pdc.set(TreeDelegate.TREE_BLOCKS, PersistentDataType.INTEGER_ARRAY, list.toIntArray()); + return true; + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/commands/Menu.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/commands/Menu.java new file mode 100644 index 0000000000..0924ba8aa5 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/commands/Menu.java @@ -0,0 +1,46 @@ +package com.untamedears.realisticbiomes.commands; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandCompletion; +import co.aikar.commands.annotation.Description; +import co.aikar.commands.annotation.Optional; +import co.aikar.commands.annotation.Syntax; +import com.untamedears.realisticbiomes.utils.RealisticBiomesGUI; +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; +import org.bukkit.ChatColor; +import org.bukkit.block.Biome; +import org.bukkit.entity.Player; + +public class Menu extends BaseCommand { + + @CommandAlias("rb|rbmenu|plants") + @Syntax("[biome]") + @Description("Opens a GUI allowing you to browse RealisticBiomes growth rates for current biome") + @CommandCompletion("@RB_Biomes") + public void onCommand(Player p, @Optional String biome) { + if (p.isInsideVehicle()) { + p.sendMessage(ChatColor.RED + "You can't use this command in vehicles"); + return; + } + if (biome == null) { + RealisticBiomesGUI gui = new RealisticBiomesGUI(p); + gui.showRBOverview(null); + } else { + if (!p.hasPermission("rb.pickBiome")) { + p.sendMessage(ChatColor.RED + "You lack permission to use this command with arguments"); + return; + } + String concat = String.join(" ", biome); + for (Biome b : RegistryAccess.registryAccess().getRegistry(RegistryKey.BIOME)) { + if (b.getKey().value().equals(concat)) { + RealisticBiomesGUI gui = new RealisticBiomesGUI(p); + gui.showRBOverview(b); + return; + } + } + p.sendMessage(ChatColor.RED + "The biome " + concat + " does not exist"); + } + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/commands/RBCommandManager.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/commands/RBCommandManager.java new file mode 100644 index 0000000000..89ed5f9997 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/commands/RBCommandManager.java @@ -0,0 +1,32 @@ +package com.untamedears.realisticbiomes.commands; + +import co.aikar.commands.BukkitCommandCompletionContext; +import co.aikar.commands.CommandCompletions; +import java.util.ArrayList; +import java.util.Arrays; +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; +import org.bukkit.NamespacedKey; +import org.bukkit.block.Biome; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import vg.civcraft.mc.civmodcore.commands.CommandManager; + +public class RBCommandManager extends CommandManager { + + public RBCommandManager(@NotNull Plugin plugin) { + super(plugin); + init(); + } + + @Override + public void registerCommands() { + registerCommand(new Menu()); + } + + @Override + public void registerCompletions(@NotNull CommandCompletions completions) { + super.registerCompletions(completions); + completions.registerCompletion("RB_Biomes", (context) -> RegistryAccess.registryAccess().getRegistry(RegistryKey.BIOME).stream().map(Biome::getKey).map(NamespacedKey::value).toList()); + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/events/RealisticBiomesBlockBreakEvent.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/events/RealisticBiomesBlockBreakEvent.java new file mode 100644 index 0000000000..caef3dce36 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/events/RealisticBiomesBlockBreakEvent.java @@ -0,0 +1,33 @@ +package com.untamedears.realisticbiomes.events; + +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.block.BlockBreakEvent; + +public class RealisticBiomesBlockBreakEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + + public static HandlerList getHandlerList() { + return handlers; + } + + private BlockBreakEvent wrapped; + + public RealisticBiomesBlockBreakEvent(Block theBlock, Player player) { + super(false); + wrapped = new BlockBreakEvent(theBlock, player); + } + + public BlockBreakEvent getEvent() { + return wrapped; + } + + @Override + public HandlerList getHandlers() { + return RealisticBiomesBlockBreakEvent.handlers; + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/events/RealisticBiomesBlockGrowEvent.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/events/RealisticBiomesBlockGrowEvent.java new file mode 100644 index 0000000000..86a1c8e808 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/events/RealisticBiomesBlockGrowEvent.java @@ -0,0 +1,32 @@ +package com.untamedears.realisticbiomes.events; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.block.BlockGrowEvent; + +public final class RealisticBiomesBlockGrowEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + + public static HandlerList getHandlerList() { + return handlers; + } + + private BlockGrowEvent wrapped; + + public RealisticBiomesBlockGrowEvent(Block block, BlockState newState) { + super(false); + wrapped = new BlockGrowEvent(block, newState); + } + + public BlockGrowEvent getEvent() { + return wrapped; + } + + @Override + public HandlerList getHandlers() { + return RealisticBiomesBlockGrowEvent.handlers; + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/events/RealisticBiomesStructureGrowEvent.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/events/RealisticBiomesStructureGrowEvent.java new file mode 100644 index 0000000000..18a57e38df --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/events/RealisticBiomesStructureGrowEvent.java @@ -0,0 +1,36 @@ +package com.untamedears.realisticbiomes.events; + +import java.util.List; +import org.bukkit.Location; +import org.bukkit.TreeType; +import org.bukkit.block.BlockState; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.bukkit.event.world.StructureGrowEvent; + +public class RealisticBiomesStructureGrowEvent extends Event { + + private static final HandlerList handlers = new HandlerList(); + + public static HandlerList getHandlerList() { + return handlers; + } + + private StructureGrowEvent wrapped; + + public RealisticBiomesStructureGrowEvent(Location location, TreeType species, boolean bonemeal, Player player, + List blocks) { + super(false); + wrapped = new StructureGrowEvent(location, species, bonemeal, player, blocks); + } + + public StructureGrowEvent getEvent() { + return wrapped; + } + + @Override + public HandlerList getHandlers() { + return RealisticBiomesStructureGrowEvent.handlers; + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/events/package-info.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/events/package-info.java new file mode 100644 index 0000000000..efc60a3808 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/events/package-info.java @@ -0,0 +1,14 @@ +/** + * RB blackholes a bunch of kind of important Spigot / Bukkit events related to growth and structure growth. + * This is not good for plugins that might want to do things based on those events, after RB's rewritten the outcome. + *

+ * This package includes thin wrappers for the "real" bukkit events, which plugins seeking to work with RB can capture + * and leverage. + * + * @author ProgrammerDan + */ +/** + * @author ProgrammerDan + * + */ +package com.untamedears.realisticbiomes.events; diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/AgeableGrower.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/AgeableGrower.java new file mode 100644 index 0000000000..82ad8e948b --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/AgeableGrower.java @@ -0,0 +1,63 @@ +package com.untamedears.realisticbiomes.growth; + +import com.untamedears.realisticbiomes.model.Plant; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.data.Ageable; + +/** + * Handles growing of anything implementing Ageable (Crops, Cocoa etc.) + */ +public class AgeableGrower extends IArtificialGrower { + + protected final int maxStage; + protected final int increment; + protected final Material material; + + public AgeableGrower(Material material, int maxStage, int increment) { + this.maxStage = maxStage; + this.increment = increment; + this.material = material; + } + + @Override + public int getIncrementPerStage() { + return increment; + } + + @Override + public int getMaxStage() { + return maxStage; + } + + @Override + public int getStage(Plant plant) { + Block block = plant.getLocation().getBlock(); + if (block.getType() != material) { + return -1; + } + if (!(block.getBlockData() instanceof Ageable)) { + return -1; + } + return ((Ageable) block.getBlockData()).getAge(); + } + + @Override + public boolean setStage(Plant plant, int stage) { + Block block = plant.getLocation().getBlock(); + if (!(block.getBlockData() instanceof Ageable)) { + throw new IllegalArgumentException("Can not set age for non Ageable plant " + plant.getGrowthConfig()); + } + Ageable ageable = ((Ageable) block.getBlockData()); + ageable.setAge(stage); + block.setBlockData(ageable, true); + + return true; + } + + @Override + public boolean deleteOnFullGrowth() { + return false; + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/BambooGrower.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/BambooGrower.java new file mode 100644 index 0000000000..625e105224 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/BambooGrower.java @@ -0,0 +1,64 @@ +package com.untamedears.realisticbiomes.growth; + +import com.untamedears.realisticbiomes.model.Plant; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.type.Bamboo; + +public class BambooGrower extends ColumnPlantGrower { + + protected static final int LEAVES_AMOUNT = 3; + protected static final int LARGE_LEAVES_START_HEIGHT = 5; + + public BambooGrower(int maxHeight) { + super(maxHeight, Material.BAMBOO, BlockFace.UP, false); + } + + @Override + protected VerticalGrowResult growVertically(Plant plant, Block block, int howMany) { + // Actual growth is here: + VerticalGrowResult result = super.growVertically(plant, block, howMany); + handleProperLeafGrowth(block, result.top()); + return result; + } + + @Override + public int getStage(Plant plant) { + Block block = plant.getLocation().getBlock(); + if (block.getType() != Material.BAMBOO_SAPLING && block.getType() != Material.BAMBOO) { + return -1; + } + Block bottom = getRelativeBlock(block, getPrimaryGrowthDirection().getOppositeFace()); + if (!bottom.getLocation().equals(block.getLocation())) { + return -1; + } + int stage = getActualHeight(block) - 1; + return Math.min(stage, getMaxStage()); + } + + private void handleProperLeafGrowth(Block block, Block highestBlock) { + // according to https://minecraft.gamepedia.com/Bamboo#Appearance + int actualHeight = super.getActualHeight(block) + 1; + int leavesLeft = LEAVES_AMOUNT >= actualHeight ? actualHeight - 1 : LEAVES_AMOUNT; + Block underBlock = highestBlock; + Bamboo.Leaves leavesType = actualHeight >= LARGE_LEAVES_START_HEIGHT ? + Bamboo.Leaves.LARGE : Bamboo.Leaves.SMALL; + + // Makes Bamboo growth O(2n) but prevents forking column based growth into similar yet different code. + while (underBlock.getType() == highestBlock.getType()) { + Bamboo data = (Bamboo) underBlock.getBlockData(); + if (leavesLeft != 0) { + data.setLeaves(leavesType); + leavesLeft--; + if (leavesLeft == 1) { + leavesType = Bamboo.Leaves.SMALL; + } + } else { + data.setLeaves(Bamboo.Leaves.NONE); + } + underBlock.setBlockData(data); + underBlock = underBlock.getRelative(BlockFace.DOWN); + } + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/ColumnPlantGrower.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/ColumnPlantGrower.java new file mode 100644 index 0000000000..906492661d --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/ColumnPlantGrower.java @@ -0,0 +1,13 @@ +package com.untamedears.realisticbiomes.growth; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +public class ColumnPlantGrower extends VerticalGrower { + + public ColumnPlantGrower(int maxHeight, Material material, BlockFace direction, boolean instaBreakTouching) { + super(maxHeight, material, direction, instaBreakTouching); + } + + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/CustomTreeGrower.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/CustomTreeGrower.java new file mode 100644 index 0000000000..3f4bbc2767 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/CustomTreeGrower.java @@ -0,0 +1,41 @@ +package com.untamedears.realisticbiomes.growth; + +import com.untamedears.realisticbiomes.model.Plant; +import org.bukkit.block.Block; + +public class CustomTreeGrower extends IArtificialGrower { + + @Override + public int getIncrementPerStage() { + return 1; + } + + @Override + public int getMaxStage() { + return 1; + } + + @Override + public int getStage(Plant plant) { + Block block = plant.getLocation().getBlock(); + if (block.getType() != plant.getGrowthConfig().getItem().getType()) { + return 1; + } + return 0; + } + + @Override + public boolean setStage(Plant plant, int stage) { + if (stage < 1) { + return true; + } + + return true; + } + + @Override + public boolean deleteOnFullGrowth() { + return true; + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/DownwardsGrower.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/DownwardsGrower.java new file mode 100644 index 0000000000..5ce31dd769 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/DownwardsGrower.java @@ -0,0 +1,13 @@ +package com.untamedears.realisticbiomes.growth; + +import org.bukkit.Material; +import org.bukkit.block.BlockFace; + +public class DownwardsGrower extends VerticalGrower { + + public DownwardsGrower(int maxHeight, Material material) { + super(maxHeight, material, BlockFace.DOWN, false); + } + + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/FruitGrower.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/FruitGrower.java new file mode 100644 index 0000000000..60c9e5c722 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/FruitGrower.java @@ -0,0 +1,123 @@ +package com.untamedears.realisticbiomes.growth; + +import com.untamedears.realisticbiomes.model.Plant; +import java.util.Arrays; +import java.util.List; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.Ageable; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.Directional; +import vg.civcraft.mc.civmodcore.world.WorldUtils; + +public class FruitGrower extends IArtificialGrower { + + private List validSoil = Arrays.asList(Material.DIRT, Material.GRASS_BLOCK, Material.FARMLAND, Material.MOSS_BLOCK, Material.COARSE_DIRT, Material.PODZOL, Material.PALE_MOSS_BLOCK); + private Material attachedStem; + private Material nonAttachedStem; + private Material fruitMaterial; + + public FruitGrower(Material fruitMaterial, Material attachedStem, Material nonAttachedStem) { + this.fruitMaterial = fruitMaterial; + this.attachedStem = attachedStem; + this.nonAttachedStem = nonAttachedStem; + } + + public Material getAttachedStemMaterial() { + return attachedStem; + } + + public Material getNonattachedStemMaterial() { + return nonAttachedStem; + } + + public Material getFruitMaterial() { + return fruitMaterial; + } + + @Override + public int getIncrementPerStage() { + return 1; + } + + @Override + public int getMaxStage() { + return 1; + } + + @Override + public int getStage(Plant plant) { + Block block = plant.getLocation().getBlock(); + if (block.getType() == attachedStem) { + if (hasPlant(block)) { + return 1; + } + setStage(plant, 0); // doesn't have a fruit, but is attached type, so fix + return 0; + } + if (block.getType() != nonAttachedStem) { + return -1; + } + return 0; + } + + public BlockFace getTurnedDirection(Block block) { + if (!(block.getBlockData() instanceof Directional)) { + return null; + } + Directional data = (Directional) block.getBlockData(); + return data.getFacing(); + } + + private void growFruit(Block block) { + for (BlockFace face : WorldUtils.PLANAR_SIDES) { + Block fruitBlock = block.getRelative(face); + if (fruitBlock.getType() != Material.AIR) { + continue; + } + Block below = fruitBlock.getRelative(BlockFace.DOWN); + if (!validSoil.contains(below.getType())) { + continue; + } + fruitBlock.setType(fruitMaterial, true); + block.setType(attachedStem); + Directional stem = (Directional) block.getBlockData(); + stem.setFacing(face); + block.setBlockData(stem); + return; + } + } + + private boolean hasPlant(Block block) { + BlockData data = block.getBlockData(); + if (data instanceof Directional) { + Directional dir = (Directional) data; + return block.getRelative(dir.getFacing()).getType() == fruitMaterial; + } + return false; + } + + @Override + public boolean setStage(Plant plant, int stage) { + if (stage == 0) { + Block block = plant.getLocation().getBlock(); + block.setType(nonAttachedStem, true); + BlockData data = block.getBlockData(); + if (data instanceof Ageable) { + Ageable ageable = (Ageable) data; + ageable.setAge(ageable.getMaximumAge()); + } + block.setBlockData(data); + return true; + } + growFruit(plant.getLocation().getBlock()); + return true; + } + + @Override + public boolean deleteOnFullGrowth() { + return false; + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/FungusGrower.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/FungusGrower.java new file mode 100644 index 0000000000..926279376a --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/FungusGrower.java @@ -0,0 +1,77 @@ +package com.untamedears.realisticbiomes.growth; + +import com.untamedears.realisticbiomes.model.Plant; +import java.util.Random; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.core.registries.Registries; +import net.minecraft.data.worldgen.features.TreeFeatures; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.levelgen.feature.ConfiguredFeature; +import org.bukkit.Material; +import org.bukkit.TreeType; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.CraftWorld; +import org.bukkit.craftbukkit.util.RandomSourceWrapper; + +/** + * We need to differentiate fungus from other types of saplings thanks + * to the peculiarities with how Spigot handles tree generation. If you + * call {@code world.generateTree()} with the {@link TreeType}, it will + * generate the tree as if it were populating a newly generated chunk. + * Therefore we need to bypass that and pretend the sapling is being + * bone mealed so ensure a singular tree is generated. + */ +public class FungusGrower extends AgeableGrower { + + private final RandomSource randomSource = new RandomSourceWrapper(new Random()); + + public FungusGrower(final Material material) { + super(material, 1, 1); + } + + @Override + public int getStage(final Plant plant) { + final Block block = plant.getLocation().getBlock(); + if (block.getType() != this.material) { + return -1; + } + return 0; + } + + @Override + public boolean setStage(final Plant plant, final int stage) { + if (stage < 1) { + return true; + } + final Block block = plant.getLocation().getBlock(); + final Material material = block.getType(); + final ResourceKey> growth = + material == Material.CRIMSON_FUNGUS ? TreeFeatures.CRIMSON_FUNGUS : + material == Material.WARPED_FUNGUS ? TreeFeatures.WARPED_FUNGUS : + material == Material.FLOWERING_AZALEA ? TreeFeatures.AZALEA_TREE : + null; + if (growth == null) { + return true; + } + final ServerLevel world = ((CraftWorld) block.getWorld()).getHandle(); + final BlockPos position = new BlockPos(block.getX(), block.getY(), block.getZ()); + //Taken from CraftWorld.generateTree() + Holder> growthHolder = world.registryAccess().lookupOrThrow(Registries.CONFIGURED_FEATURE).get(growth).orElse(null); + + if (growthHolder == null) return false; + + if (!growthHolder.value().place(world, world.getChunkSource().getGenerator(), this.randomSource, position)) { + block.setType(material); + } + return true; + } + + @Override + public boolean deleteOnFullGrowth() { + return true; + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/HorizontalBlockSpreadGrower.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/HorizontalBlockSpreadGrower.java new file mode 100644 index 0000000000..de79658d76 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/HorizontalBlockSpreadGrower.java @@ -0,0 +1,144 @@ +package com.untamedears.realisticbiomes.growth; + +import com.google.common.base.Preconditions; +import com.untamedears.realisticbiomes.RealisticBiomes; +import com.untamedears.realisticbiomes.model.Plant; +import java.util.Collection; +import java.util.EnumSet; +import java.util.Random; +import java.util.Set; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; + +public class HorizontalBlockSpreadGrower extends IArtificialGrower { + + private static final Random rng = new Random(); + + private final int maximumInArea; + private final int maximumRange; + private Material material; + private Set validSoil; + private Set replaceableBlocks; + + public HorizontalBlockSpreadGrower(Material material, int maximumInArea, int maximumRange, + Collection replaceableBlocks, Collection validSoil) { + Preconditions.checkNotNull(material); + Preconditions.checkNotNull(replaceableBlocks); + Preconditions.checkNotNull(validSoil); + this.maximumInArea = maximumInArea; + this.maximumRange = maximumRange; + this.validSoil = EnumSet.copyOf(validSoil); + this.replaceableBlocks = EnumSet.copyOf(replaceableBlocks); + this.material = material; + } + + @Override + public int getIncrementPerStage() { + return 1; + } + + @Override + public int getMaxStage() { + return maximumInArea - 1; + } + + @Override + public int getStage(Plant plant) { + Block block = plant.getLocation().getBlock(); + if (block.getType() != material) { + return -1; + } + int maxX = block.getX() + maximumRange; + int maxZ = block.getZ() + maximumRange; + int count = 0; + for (int x = block.getX() - maximumRange; x < maxX; x++) { + for (int z = block.getZ() - maximumRange; z < maxZ; z++) { + if (block.getWorld().getBlockAt(x, block.getY(), z).getType() == material) { + count++; + } + } + } + // remove block itself which is always there, because stage 0 equals only the + // center block itself + return count - 1; + } + + @Override + public boolean setStage(Plant plant, int stage) { + int currentAmount = getStage(plant); + int toAdd = stage - currentAmount; + outer: + for (int i = 0; i < toAdd; i++) { + // pick a random spot in range, scan forward in lines from it, then scan + // backwards in lines from it + int xOffset = rng.nextInt(maximumRange * 2 + 1); + int zOffset = rng.nextInt(maximumRange * 2 + 1); + Location lowerCorner = plant.getLocation().clone(); + lowerCorner.subtract(maximumRange, 0, maximumRange); + int upperXBound = plant.getLocation().getBlockX() + maximumRange; + int upperZBound = plant.getLocation().getBlockZ() + maximumRange; + // starting line forward + for (int z = zOffset + lowerCorner.getBlockZ(); z <= upperZBound; z++) { + if (growAtSpot(plant, new Location(lowerCorner.getWorld(), lowerCorner.getBlockX() + xOffset, + lowerCorner.getBlockY(), z))) { + continue outer; + } + } + // scan forward + for (int x = xOffset + lowerCorner.getBlockX() + 1; x <= upperXBound; x++) { + for (int z = zOffset + lowerCorner.getBlockZ(); z <= upperZBound; z++) { + if (growAtSpot(plant, new Location(lowerCorner.getWorld(), x, lowerCorner.getBlockY(), z))) { + continue outer; + } + } + } + // starting line backward + for (int z = zOffset + lowerCorner.getBlockZ() - 1; z >= lowerCorner.getBlockZ(); z--) { + if (growAtSpot(plant, new Location(lowerCorner.getWorld(), lowerCorner.getBlockX() + xOffset, + lowerCorner.getBlockY(), z))) { + continue outer; + } + } + // scan backward + for (int x = xOffset + lowerCorner.getBlockX() - 1; x >= lowerCorner.getBlockX(); x--) { + for (int z = zOffset + lowerCorner.getBlockZ(); z >= lowerCorner.getBlockZ(); z--) { + if (growAtSpot(plant, new Location(lowerCorner.getWorld(), x, lowerCorner.getBlockY(), z))) { + continue outer; + } + } + } + break; // no valid growth spot available, cancel all further growth attempts + } + + return true; + } + + private boolean growAtSpot(Plant source, Location loc) { + Block block = loc.getBlock(); + if (!replaceableBlocks.isEmpty()) { + if (!replaceableBlocks.contains(block.getType())) { + return false; + } + } + if (!validSoil.isEmpty()) { + Block below = block.getRelative(BlockFace.DOWN); + if (!validSoil.contains(below.getType())) { + return false; + } + } + block.setType(material); + // create new plant at this location for further spread + Plant plant = new Plant(block.getLocation(), source.getGrowthConfig()); + RealisticBiomes.getInstance().getPlantManager().putPlant(plant); + RealisticBiomes.getInstance().getPlantLogicManager().updateGrowthTime(plant, block); + return true; + } + + @Override + public boolean deleteOnFullGrowth() { + return false; + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/IArtificialGrower.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/IArtificialGrower.java new file mode 100644 index 0000000000..36aaf5b5f5 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/IArtificialGrower.java @@ -0,0 +1,77 @@ +package com.untamedears.realisticbiomes.growth; + +import com.untamedears.realisticbiomes.model.Plant; + +/** + * Parent class for any block specific growth logic. Growth of a block is + * subdivided into steps, where the initial state is 0 and the final (fully + * grown) state is a positive integer. Growth happens in increments by a static + * amount in between these numbers. + *

+ * Growth stages may, but must not necessarily map to Ageable BlockStates, how + * exactly stages are interpreted is entirely up to the implementation + */ +public abstract class IArtificialGrower { + + /** + * Grows the given plant to its maximum growth stage possible + * + * @param plant Plant to grow + */ + public void fullyGrow(Plant plant) { + setStage(plant, getMaxStage()); + } + + /** + * @return How many growth stages should be progressed at once + */ + public abstract int getIncrementPerStage(); + + /** + * @return Maximum growth stage achievable + */ + public abstract int getMaxStage(); + + /** + * How far the plant has grown as a fraction from 0 to 1 + * + * @param plant Plant to check growth for + * @return Growth on a scale from 0 to 1 + */ + public double getProgressGrowthStage(Plant plant) { + return (double) getStage(plant) / getMaxStage(); + } + + /** + * Gets the current growth stage of the given plant + * + * @param plant Plant to get growth stage for + * @return Current growth stage of the plant between 0 and maxStage (inclusive). -1 if the plant is completely broken/gone + */ + public abstract int getStage(Plant plant); + + /** + * Sets the growth stage of the given plant to the given number + * + * @param plant Plant to set growth stage for + * @param stage Stage to set to + * @return True if the stage was set successfully. + * Now used only for VerticalGrower, helps to determine if CACTUS grown but was broken by adjacent blocks + */ + public abstract boolean setStage(Plant plant, int stage); + + /** + * @return Should a plant instance be deleted entirely once fully grown + */ + public abstract boolean deleteOnFullGrowth(); + + /** + * Usually we assume plants are permanently broken if a stage update fails and don't update them anymore. Settings this to true will ignore that + * + * @return + */ + public boolean ignoreGrowthFailure() { + return false; + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/KelpGrower.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/KelpGrower.java new file mode 100644 index 0000000000..f71074d1a2 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/KelpGrower.java @@ -0,0 +1,115 @@ +package com.untamedears.realisticbiomes.growth; + +import com.untamedears.realisticbiomes.model.Plant; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; + +public class KelpGrower extends TipGrower { + + private Material tip = Material.KELP; + private Material stem = Material.KELP_PLANT; + private BlockFace primaryGrowthDirection = BlockFace.UP; + private int maxHeight; + + public KelpGrower(int maxHeight) { + super(Material.KELP, Material.KELP_PLANT, BlockFace.UP, maxHeight); + this.maxHeight = maxHeight; + } + + @Override + public int getIncrementPerStage() { + return 1; + } + + @Override + public int getMaxStage() { + return this.maxHeight - 1; + } + + @Override + public int getStage(Plant plant) { + Block plantBlock = plant.getLocation().getBlock(); + Block endBlock = plantBlock.getRelative(primaryGrowthDirection.getOppositeFace()); + int count = 0; + for (int i = 0; i <= 384; i++) { + endBlock = endBlock.getRelative(getPrimaryGrowthDirection()); + if (endBlock.getType() == getTipMaterial() || endBlock.getType() == getStemMaterial()) { + count++; + continue; + } + break; + } + return count - 1; + } + + @Override + public boolean setStage(Plant plant, int stage) { + int currentStage = getStage(plant); + Block plantBlock = plant.getLocation().getBlock(); + if (stage <= currentStage) { + return false; + } + return !growVertically(plant, getRelativeBlock(plantBlock), stage - currentStage).growthLimited(); + } + + @Override + protected VerticalGrowResult growVertically(Plant plant, Block block, int howMany) { + int counter = 0; + Block onTop = block; + while (counter < getMaxStage() && howMany > 0) { + counter++; + onTop = onTop.getRelative(getPrimaryGrowthDirection()); + Material topMaterial = onTop.getType(); + if (topMaterial == Material.WATER) { + onTop.setType(getTipMaterial(), true); + howMany--; + continue; + } + if (topMaterial == getTipMaterial() || topMaterial == getStemMaterial()) { + // already existing block of the same plant + continue; + } + // neither air, nor the right plant, but something else blocking growth, so we + // stop + break; + } + + onTop = onTop.getType() != getTipMaterial() ? onTop.getRelative(getPrimaryGrowthDirection().getOppositeFace()) : onTop; + + return new VerticalGrowResult(howMany > 0, onTop); + } + + @Override + public boolean deleteOnFullGrowth() { + return super.deleteOnFullGrowth(); + } + + @Override + public Block getRelativeBlock(Block block) { + Block placeholder = block; + for (int i = 0; i < 384; i++) { + Block relative = placeholder.getRelative(getPrimaryGrowthDirection()); + if (relative.getType() != getTipMaterial() || relative.getType() != getStemMaterial()) { + break; + } + placeholder = relative; + } + return placeholder; + } + + @Override + public Material getTipMaterial() { + return Material.KELP; + } + + @Override + public Material getStemMaterial() { + return Material.KELP_PLANT; + } + + @Override + public BlockFace getPrimaryGrowthDirection() { + return BlockFace.UP; + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/SchematicGrower.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/SchematicGrower.java new file mode 100644 index 0000000000..e7d80ed15c --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/SchematicGrower.java @@ -0,0 +1,51 @@ +package com.untamedears.realisticbiomes.growth; + +import com.untamedears.realisticbiomes.model.Plant; +import com.untamedears.realisticbiomes.model.RBSchematic; +import org.bukkit.Location; +import org.bukkit.block.Block; + +public class SchematicGrower extends IArtificialGrower { + + private RBSchematic schematic; + private Location offset; + + public SchematicGrower(RBSchematic schematic, Location offSet) { + this.schematic = schematic; + this.offset = offSet; + } + + @Override + public int getIncrementPerStage() { + return 1; + } + + @Override + public int getMaxStage() { + return 1; + } + + @Override + public int getStage(Plant plant) { + Block block = plant.getLocation().getBlock(); + if (block.getType() != plant.getGrowthConfig().getItem().getType()) { + return -1; + } + return 0; + } + + @Override + public boolean setStage(Plant plant, int stage) { + if (stage < 1) { + return true; + } + schematic.spawnAt(plant.getLocation().add(offset)); + return true; + } + + @Override + public boolean deleteOnFullGrowth() { + return true; + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/SeaPickleGrower.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/SeaPickleGrower.java new file mode 100644 index 0000000000..46c076a0ce --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/SeaPickleGrower.java @@ -0,0 +1,47 @@ +package com.untamedears.realisticbiomes.growth; + +import com.untamedears.realisticbiomes.model.Plant; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.data.type.SeaPickle; + +public class SeaPickleGrower extends IArtificialGrower { + + @Override + public int getIncrementPerStage() { + return 1; + } + + @Override + public int getMaxStage() { + return 3; + } + + @Override + public int getStage(Plant plant) { + Block block = plant.getLocation().getBlock(); + if (block.getType() != Material.SEA_PICKLE) { + return -1; + } + SeaPickle data = (SeaPickle) block.getBlockData(); + return data.getPickles() - 1; + } + + @Override + public boolean setStage(Plant plant, int stage) { + Block block = plant.getLocation().getBlock(); + if (block.getType() != Material.SEA_PICKLE) { + return true; + } + SeaPickle data = (SeaPickle) block.getBlockData(); + data.setPickles(stage + 1); + block.setBlockData(data); + return true; + } + + @Override + public boolean deleteOnFullGrowth() { + return true; + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/StemGrower.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/StemGrower.java new file mode 100644 index 0000000000..a1038f3fb2 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/StemGrower.java @@ -0,0 +1,41 @@ +package com.untamedears.realisticbiomes.growth; + +import com.google.common.base.Preconditions; +import com.untamedears.realisticbiomes.growthconfig.PlantGrowthConfig; +import com.untamedears.realisticbiomes.model.Plant; +import org.bukkit.Material; + +public class StemGrower extends AgeableGrower { + + private String fruitConfigName; + private PlantGrowthConfig fruitConfig; + + public StemGrower(Material material, String fruitConfig) { + super(material, 7, 1); + Preconditions.checkNotNull(fruitConfig); + this.fruitConfigName = fruitConfig; + } + + public String getFruitConfigName() { + return fruitConfigName; + } + + public void setFruitConfig(PlantGrowthConfig fruitConfig) { + this.fruitConfig = fruitConfig; + } + + public PlantGrowthConfig getFruitConfig() { + return fruitConfig; + } + + @Override + public boolean setStage(Plant plant, int stage) { + super.setStage(plant, stage); + if (getMaxStage() == stage) { + plant.resetCreationTime(); + plant.setGrowthConfig(fruitConfig); + } + return true; + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/TipGrower.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/TipGrower.java new file mode 100644 index 0000000000..af44eb12c9 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/TipGrower.java @@ -0,0 +1,126 @@ +package com.untamedears.realisticbiomes.growth; + +import com.untamedears.realisticbiomes.model.Plant; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.Directional; + +public class TipGrower extends ColumnPlantGrower { + + private final Material tip; + private final Material stem; + private final BlockFace primaryGrowthDirection; + private final int maxHeight; + + public TipGrower(Material tip, Material stem, BlockFace primaryGrowthDirection, int maxHeight) { + super(maxHeight, tip, primaryGrowthDirection, false); + this.tip = tip; + this.stem = stem; + this.primaryGrowthDirection = primaryGrowthDirection; + this.maxHeight = maxHeight; + } + + @Override + public int getIncrementPerStage() { + return 1; + } + + @Override + public int getMaxStage() { + return this.maxHeight - 1; + } + + @Override + public int getStage(Plant plant) { + Block plantBlock = plant.getLocation().getBlock(); + Block endBlock = plantBlock.getRelative(primaryGrowthDirection.getOppositeFace()); + int count = 0; + for (int i = 0; i <= 384; i++) { + endBlock = endBlock.getRelative(getPrimaryGrowthDirection()); + if (endBlock.getType() == getTipMaterial() || endBlock.getType() == getStemMaterial()) { + count++; + continue; + } + break; + } + int stage = count - 1; + return Math.min(stage, getMaxStage()); + } + + @Override + public boolean setStage(Plant plant, int stage) { + int currentStage = getStage(plant); + Block plantBlock = plant.getLocation().getBlock(); + if (stage <= currentStage) { + return false; + } + return !growVertically(plant, getRelativeBlock(plantBlock), stage - currentStage).growthLimited(); + } + + @Override + protected VerticalGrowResult growVertically(Plant plant, Block block, int howMany) { + int counter = 0; + Block onTop = block; + BlockFace direction = null; + if (block.getBlockData() instanceof Directional dir) { + direction = dir.getFacing(); + } + while (counter < getMaxStage() && howMany > 0) { + counter++; + onTop = onTop.getRelative(getPrimaryGrowthDirection()); + Material topMaterial = onTop.getType(); + if (topMaterial.isAir()) { + onTop.setType(tip, true); + if (direction != null) { + Directional blockData = ((Directional) onTop.getBlockData()); + blockData.setFacing(direction); + onTop.setBlockData(blockData); + } + howMany--; + continue; + } + if (topMaterial == getTipMaterial() || topMaterial == getStemMaterial()) { + // already existing block of the same plant + continue; + } + // neither air, nor the right plant, but something else blocking growth, so we + // stop + break; + } + + onTop = onTop.getType() != getTipMaterial() ? onTop.getRelative(getPrimaryGrowthDirection().getOppositeFace()) : onTop; + + return new VerticalGrowResult(howMany > 0, onTop); + } + + @Override + public boolean deleteOnFullGrowth() { + return false; + } + + //Gets the bottom block of the plant in which we store the plant object + public Block getRelativeBlock(Block block) { + Block placeholder = block; + for (int i = 0; i < 384; i++) { + Block relative = placeholder.getRelative(getPrimaryGrowthDirection()); + if (relative.getType() != getTipMaterial() || relative.getType() != getStemMaterial()) { + break; + } + placeholder = relative; + } + return placeholder; + } + + public Material getTipMaterial() { + return this.tip; + } + + public Material getStemMaterial() { + return this.stem; + } + + public BlockFace getPrimaryGrowthDirection() { + return this.primaryGrowthDirection; + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/TreeGrower.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/TreeGrower.java new file mode 100644 index 0000000000..f79a8c0bba --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/TreeGrower.java @@ -0,0 +1,207 @@ +package com.untamedears.realisticbiomes.growth; + +import com.untamedears.realisticbiomes.PlantManager; +import com.untamedears.realisticbiomes.RealisticBiomes; +import com.untamedears.realisticbiomes.breaker.TreeDelegate; +import com.untamedears.realisticbiomes.model.Plant; +import org.bukkit.Material; +import org.bukkit.TreeType; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.type.Sapling; +import java.util.Random; + +public class TreeGrower extends AgeableGrower { + + private static boolean adjacentSaplingCheck(Material mat, Block northwest) { + for (Block block : new Block[]{northwest, northwest.getRelative(1, 0, 0), northwest.getRelative(0, 0, 1), + northwest.getRelative(1, 0, 1)}) { + if (block == null) { + return false; + } + if (block.getType() != mat) { + return false; + } + } + return true; + } + + private static boolean canBeBig(Material mat) { + switch (mat) { + case DARK_OAK_SAPLING: + case JUNGLE_SAPLING: + case SPRUCE_SAPLING: + case PALE_OAK_SAPLING: + return true; + default: + return false; + } + } + + /** + * Checks whether the block is part of a valid 2x2 grid + * + * @param block Block to check for + * @param mat Sapling material + * @return True if the block is part of a 2x2 sapling grid and could grow large, + * false otherwise + */ + private static boolean canGrowBig(Block block, Material mat) { + return adjacentSaplingCheck(mat, block.getRelative(-1, 0, -1)) + || adjacentSaplingCheck(mat, block.getRelative(0, 0, -1)) + || adjacentSaplingCheck(mat, block.getRelative(-1, 0, 0)) || adjacentSaplingCheck(mat, block); + } + + /** + * Checks whether the block is part of a 2x2 grid and returns the north west block + * + * @param block to check for + * @param mat Sapling material + * @return North west block; null if the block is not part of a 2x2 sapling grid + */ + private static Block findNWSapling(Block block, Material mat) { + Block northwest = null; + for (Block nwCandidate : new Block[]{block, block.getRelative(1, 0, 1), block.getRelative(0, 0, 1), + block.getRelative(1, 0, 0)}) { + if (adjacentSaplingCheck(mat, nwCandidate)) { + northwest = nwCandidate; + break; + } + } + if (northwest == null) { + return null; + } + return northwest; + } + + private static void removeSapling(Block block) { + PlantManager manager = RealisticBiomes.getInstance().getPlantManager(); + Plant plant = manager.getPlant(block); + if (plant == null) { + return; + } + manager.deletePlant(plant); + block.setType(Material.AIR); + } + + /** + * Remove a 2x2 saplings grid if the block is part of one + * + * @param block to check for + * @param mat Sapling material + */ + private static Block clearBigTreeSaplings(Block block, Material mat) { + Block northwest = null; + Block northeast, southwest, southeast; + northwest = findNWSapling(block, mat); + if (northwest == null) { + return null; + } + + northeast = northwest.getRelative(BlockFace.EAST); + southwest = northwest.getRelative(BlockFace.SOUTH); + southeast = northeast.getRelative(BlockFace.SOUTH); + + removeSapling(northwest); + removeSapling(northeast); + removeSapling(southeast); + removeSapling(southwest); + return northwest; + } + + private static TreeType remapSaplingToTree(Material mat, boolean big) { + switch (mat) { + case OAK_SAPLING: + return TreeType.TREE; + case BIRCH_SAPLING: + return TreeType.BIRCH; + case ACACIA_SAPLING: + return TreeType.ACACIA; + case FLOWERING_AZALEA: + return TreeType.AZALEA; + case CHERRY_SAPLING: + return TreeType.CHERRY; + case MANGROVE_PROPAGULE: + return big ? TreeType.TALL_MANGROVE : TreeType.MANGROVE; + case DARK_OAK_SAPLING: + return big ? TreeType.DARK_OAK : null; + case PALE_OAK_SAPLING: + return big ? TreeType.PALE_OAK : null; + case JUNGLE_SAPLING: + return big ? TreeType.JUNGLE : TreeType.SMALL_JUNGLE; + case SPRUCE_SAPLING: + return big ? TreeType.MEGA_REDWOOD : TreeType.REDWOOD; + case CHORUS_FLOWER: + return TreeType.CHORUS_PLANT; + case RED_MUSHROOM: + return TreeType.RED_MUSHROOM; + case BROWN_MUSHROOM: + return TreeType.BROWN_MUSHROOM; + default: + return null; + } + } + + + public TreeGrower(Material saplingType) { + super(saplingType, 1, 1); + } + + @Override + public int getStage(Plant plant) { + Block block = plant.getLocation().getBlock(); + if (block.getType() != this.material) { + return -1; + } + return 0; + } + + @Override + public boolean setStage(Plant plant, int stage) { + if (stage < 1) { + return true; + } + Block block = plant.getLocation().getBlock(); + // Re-Read the block data to make sure it is up to date + if (!(block.getBlockData() instanceof Sapling) && block.getType() != Material.RED_MUSHROOM && block.getType() != Material.BROWN_MUSHROOM) { + return true; + } + Material mat = block.getType(); + boolean canBeBig = canBeBig(mat); + if (canBeBig) { + canBeBig = canGrowBig(block, mat); + } + TreeType type = remapSaplingToTree(mat, canBeBig); + if (type == null) { + return true; + } + Block northwest = null; + if (canBeBig) { + northwest = clearBigTreeSaplings(block, mat); + } else { + block.setType(Material.AIR); + } + if (!block.getLocation().getWorld().generateTree(block.getLocation(), new Random(), type, new TreeDelegate())) { + //failed, so restore sapling + if (canBeBig && northwest != null) { + Block northeast = northwest.getRelative(BlockFace.EAST); + Block southwest = northwest.getRelative(BlockFace.SOUTH); + Block southeast = northeast.getRelative(BlockFace.SOUTH); + + northwest.setType(mat); + northeast.setType(mat); + southwest.setType(mat); + southeast.setType(mat); + } else { + block.setType(mat); + } + } + return true; + } + + @Override + public boolean deleteOnFullGrowth() { + return true; + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/VerticalGrower.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/VerticalGrower.java new file mode 100644 index 0000000000..aff8fd56b2 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growth/VerticalGrower.java @@ -0,0 +1,157 @@ +package com.untamedears.realisticbiomes.growth; + +import com.untamedears.realisticbiomes.model.Plant; +import com.untamedears.realisticbiomes.utils.RBUtils; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Item; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.items.MaterialUtils; +import vg.civcraft.mc.civmodcore.world.WorldUtils; + +public class VerticalGrower extends IArtificialGrower { + + public record VerticalGrowResult(boolean growthLimited, Block top) { + + } + + public static Block getRelativeBlock(Block block, BlockFace face) { + Material mat = block.getType(); + Block bottomBlock = block; + // not actually using this variable, but just having it here as a fail safe + for (int i = 0; i < 384; i++) { + Block below = bottomBlock.getRelative(face); + if (below.getType() != mat && below.getType() != RBUtils.getTipMaterial(mat) && below.getType() != RBUtils.getStemMaterial(mat)) { + break; + } + bottomBlock = below; + } + return bottomBlock; + } + + private int maxHeight; + private Material material; + private boolean instaBreakTouching; + private BlockFace primaryGrowthDirection; + + public VerticalGrower(int maxHeight, Material material, BlockFace primaryGrowthDirection, boolean instaBreakTouching) { + this.maxHeight = maxHeight; + this.material = material; + this.instaBreakTouching = instaBreakTouching; + this.primaryGrowthDirection = primaryGrowthDirection; + } + + public Material getMaterial() { + return material; + } + + @Override + public int getIncrementPerStage() { + return 1; + } + + public BlockFace getPrimaryGrowthDirection() { + return primaryGrowthDirection; + } + + public boolean isInstaBreakTouching() { + return instaBreakTouching; + } + + @Override + public int getMaxStage() { + return maxHeight - 1; + } + + @Override + public int getStage(Plant plant) { + Block block = plant.getLocation().getBlock(); + if (material != block.getType()) { + return -1; + } + Block bottom = getRelativeBlock(block, primaryGrowthDirection.getOppositeFace()); + if (!bottom.getLocation().equals(block.getLocation())) { + return -1; + } + int stage = getActualHeight(block) - 1; + return Math.min(stage, getMaxStage()); + } + + protected int getActualHeight(Block block) { + Block bottom = getRelativeBlock(block, BlockFace.DOWN); + Block top = getRelativeBlock(block, BlockFace.UP); + + return top.getY() - bottom.getY() + 1; + } + + /** + * Handles the growth of a column plant ( i.e sugarcane, cactus ) + * + * @param block Block of the corresponding plant + * @param howMany How tall should the growth be + * @return highest plant block + */ + protected VerticalGrowResult growVertically(Plant plant, Block block, int howMany) { + if (material != null && block.getType() != material) { + block.setType(material); + } + + int counter = 1; + Block onTop = block; + while (counter < maxHeight && howMany > 0) { + counter++; + onTop = onTop.getRelative(primaryGrowthDirection); + Material topMaterial = onTop.getType(); + if (topMaterial == Material.AIR) { + if (instaBreakTouching) { + for (BlockFace face : WorldUtils.PLANAR_SIDES) { + Block side = onTop.getRelative(face); + if (!MaterialUtils.isAir(side.getType())) { + ItemStack toDrop = plant.getGrowthConfig().getItem().clone(); + toDrop.setAmount(howMany); + Location loc = block.getLocation(); + loc.add(0.5, 0.5, 0.5); + Item item = block.getWorld().dropItemNaturally(loc, toDrop); + item.setVelocity(item.getVelocity().multiply(1.2)); + plant.resetCreationTime(); + onTop = onTop.getRelative(primaryGrowthDirection.getOppositeFace()); + + return new VerticalGrowResult(false, onTop); + } + } + } + onTop.setType(material, true); + howMany--; + continue; + } + if (topMaterial == block.getType()) { + // already existing block of the same plant + continue; + } + // neither air, nor the right plant, but something else blocking growth, so we + // stop + break; + } + + onTop = onTop.getType() != material ? onTop.getRelative(primaryGrowthDirection.getOppositeFace()) : onTop; + + return new VerticalGrowResult(howMany > 0, onTop); + } + + @Override + public boolean setStage(Plant plant, int stage) { + int currentStage = getStage(plant); + if (stage <= currentStage) { + return false; + } + Block block = plant.getLocation().getBlock(); + return !growVertically(plant, block, stage - currentStage).growthLimited; + } + + @Override + public boolean deleteOnFullGrowth() { + return false; + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growthconfig/AbstractGrowthConfig.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growthconfig/AbstractGrowthConfig.java new file mode 100644 index 0000000000..e71478c460 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growthconfig/AbstractGrowthConfig.java @@ -0,0 +1,17 @@ +package com.untamedears.realisticbiomes.growthconfig; + +public abstract class AbstractGrowthConfig { + + protected final String name; + + public AbstractGrowthConfig(String name) { + this.name = name; + } + + /** + * @return Identifiying unique name of this config + */ + public String getName() { + return name; + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growthconfig/BreakerConfig.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growthconfig/BreakerConfig.java new file mode 100644 index 0000000000..736103f826 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growthconfig/BreakerConfig.java @@ -0,0 +1,18 @@ +package com.untamedears.realisticbiomes.growthconfig; + +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import java.util.Objects; + +public record BreakerConfig(Material type, Material type2, int maxYield) { + + public static BreakerConfig fromConfig(ConfigurationSection section) { + if (section == null) { + return null; + } + String type = section.getString("type"); + String type2 = section.getString("type2"); + Material material = Objects.requireNonNull(Material.matchMaterial(type), "Unknown material: " + type); + return new BreakerConfig(material, type2 == null ? null : Objects.requireNonNull(Material.matchMaterial(type2), "Unknown material: " + type2), section.getInt("max_yield")); + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growthconfig/PlantGrowthConfig.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growthconfig/PlantGrowthConfig.java new file mode 100644 index 0000000000..59e36333b7 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/growthconfig/PlantGrowthConfig.java @@ -0,0 +1,478 @@ +package com.untamedears.realisticbiomes.growthconfig; + +import com.untamedears.realisticbiomes.FarmBeaconManager; +import com.untamedears.realisticbiomes.RealisticBiomes; +import com.untamedears.realisticbiomes.growth.IArtificialGrower; +import com.untamedears.realisticbiomes.growth.VerticalGrower; +import com.untamedears.realisticbiomes.model.Plant; +import com.untamedears.realisticbiomes.noise.BiomeConfiguration; +import com.untamedears.realisticbiomes.noise.Climate; +import com.untamedears.realisticbiomes.utils.RBUtils; +import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.BlockData; +import org.bukkit.block.data.Waterlogged; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; +import vg.civcraft.mc.civmodcore.utilities.TextUtil; +import vg.civcraft.mc.civmodcore.world.WorldUtils; + +public class PlantGrowthConfig extends AbstractGrowthConfig { + + private record SetStageResult(int currentStage, int intendedStage, long creationTime, Long nextUpdateTime) { + + } + + private static final SetStageResult NO_NEXT_UPDATE = new SetStageResult(0, 0, 0, Long.MAX_VALUE); + + private static final byte MAX_LIGHT = 15; + private static final long INFINITE_TIME = TimeUnit.DAYS.toMillis(365L * 1000L); + private static final DecimalFormat decimalFormat = new DecimalFormat("0.00"); + + private ItemStack item; + private short id; + + private List applicableVanillaPlants; + + private Map greenHouseRates; + + private int maximumSoilLayers; + private double maximumSoilBonus; + private Map soilBoniPerLevel; + + private boolean allowBoneMeal; + private boolean needsLight; + + private final Climate climate; + private final long growthTime; + private final BiomeConfiguration biomeConfiguration; + private BreakerConfig breaker; + + private IArtificialGrower grower; + private boolean canBePlantedDirectly; + private boolean needsToBeWaterlogged; + + private final FarmBeaconManager farmBeaconManager; + + public PlantGrowthConfig(String name, short id, ItemStack item, Map greenHouseRates, + Map soilBoniPerLevel, int maximumSoilLayers, double maximumSoilBonus, + boolean allowBoneMeal, Climate climate, long growthTime, BiomeConfiguration biomeConfiguration, BreakerConfig breaker, boolean needsLight, IArtificialGrower grower, + List applicableVanillaPlants, boolean canBePlantedDirectly, boolean needsToBeWaterlogged, FarmBeaconManager farmBeaconManager) { + super(name); + this.id = id; + this.item = item; + this.greenHouseRates = greenHouseRates; + this.soilBoniPerLevel = soilBoniPerLevel; + this.maximumSoilLayers = maximumSoilLayers; + this.maximumSoilBonus = maximumSoilBonus; + this.allowBoneMeal = allowBoneMeal; + this.climate = climate; + this.growthTime = growthTime; + this.biomeConfiguration = biomeConfiguration; + this.breaker = breaker; + this.grower = grower; + this.needsLight = needsLight; + this.canBePlantedDirectly = canBePlantedDirectly; + this.applicableVanillaPlants = applicableVanillaPlants; + this.needsToBeWaterlogged = needsToBeWaterlogged; + this.farmBeaconManager = farmBeaconManager; + } + + /** + * Gets an information string to show to as growth information at a specific + * block + * + * @param b Block to get growth information for + * @return Info string ready for output to a player + */ + public String getInfoString(Block b) { + double soilMultiplier = 1.0 + getSoilBonus(b); + double lightMultiplier = getLightMultiplier(b); + StringBuilder sb = new StringBuilder(); + sb.append(ChatColor.GOLD); + sb.append(ItemUtils.getItemName(item)); + + long time = getPersistentGrowthTime(b); + if (time == -1) { + sb.append(" does not grow here"); + return sb.toString(); + } else { + sb.append(" will grow here within " + TextUtil.formatDuration(time, TimeUnit.MILLISECONDS)); + } + + if (this.breaker != null) { + int maxYield = this.breaker.maxYield(); + double yield = biomeConfiguration.getYield(b, climate, this.breaker.type(), maxYield); + DecimalFormat format = new DecimalFormat("#.##"); + format.setRoundingMode(RoundingMode.DOWN); + sb.append(", yield: ").append(format.format(yield * maxYield)).append("x"); + } + + if (soilMultiplier != 1.0) { + sb.append("\n" + ChatColor.AQUA + "Soil multiplier: " + soilMultiplier); + } + if (lightMultiplier != 1.0) { + sb.append("\n" + ChatColor.GOLD + "Light multiplier: " + lightMultiplier); + } + if (this.farmBeaconManager.isNearBeacon(b.getLocation())) { + sb.append("\n" + ChatColor.LIGHT_PURPLE + "Farm beacon nearby"); + } + return sb.toString(); + } + + public short getID() { + return id; + } + + public List getApplicableVanillaPlants() { + return applicableVanillaPlants; + } + + /** + * Multiplier to apply to the plants growth based on surrounding light + * conditions + * + * @param block Block to check light conditions for + * @return Multplier to apply to growth rate + */ + private double getLightMultiplier(Block block) { + if (!needsLight) { + return 1.0; + } + byte naturalLight = block.getLightFromSky(); + if (naturalLight == MAX_LIGHT) { + return 1.0; + } + double greenHouseRate = 0.0; + if (block.getLightFromBlocks() >= 12) { + for (BlockFace face : WorldUtils.ALL_SIDES) { + Block adjacent = block.getRelative(face); + Double multiplier = greenHouseRates.get(adjacent.getType()); + if (multiplier != null) { + greenHouseRate = Math.max(greenHouseRate, multiplier); + } + } + } + return Math.max(greenHouseRate, 0); + } + + // affects growth time + private double getBeaconMultiplier(Block block) { + return this.farmBeaconManager.isNearBeacon(block.getLocation()) ? this.farmBeaconManager.getFarmBeaconGrowthMultiplier() : 1.0; + } + + /** + * @return Item/Plant for which this config applies + */ + public ItemStack getItem() { + return item; + } + + public Map getGreenHouseRates() { + return greenHouseRates; + } + + public int getMaximumSoilLayers() { + return maximumSoilLayers; + } + + public double getMaximumSoilBonus() { + return maximumSoilBonus; + } + + public boolean needsToBeWaterLogged() { + return needsToBeWaterlogged; + } + + public Map getSoilBoniPerLevel() { + return soilBoniPerLevel; + } + + public boolean getNeedsLight() { + return needsLight; + } + + public boolean getAllowBoneMeal() { + return allowBoneMeal; + } + + public IArtificialGrower getGrower() { + return grower; + } + + public BreakerConfig getBreaker() { + return breaker; + } + + /** + * @return Whether a plant of this config can be created by placing its matching item down. Will be false for melons or pumpkins for example + */ + public boolean canBePlantedDirectly() { + return canBePlantedDirectly; + } + + /** + * Looks at all surrounding factors to calculate how long a plant would take to + * grow at the given block with persistent growth + * + * @param block Block to check growth condition for + * @return Total milli seconds needed to fully grow a plant + * @see PlantGrowthConfig#getPersistentGrowthTime(Block, boolean) + */ + public long getPersistentGrowthTime(Block block) { + return getPersistentGrowthTime(block, false); + } + + /** + * Looks at all surrounding factors to calculate how long a plant would take to + * grow at the given block with persistent growth + * + * @param block Block to check growth condition for + * @param ignoreLight Whether to ignore light, if true will assume there is full light + * @return Total milli seconds needed to fully grow a plant + */ + public long getPersistentGrowthTime(Block block, boolean ignoreLight) { + double baseTime = this.growthTime / (1.0 + getSoilBonus(block)); + if (!ignoreLight) { + double lightMultiplier = getLightMultiplier(block); + if (lightMultiplier == 0.0) { + baseTime = INFINITE_TIME; + } else { + baseTime /= lightMultiplier; + } + } + baseTime *= getBeaconMultiplier(block); + return (long) baseTime; + } + + public long getGrowthTime() { + return growthTime; + } + + /** + * Gets an info string regarding the growth progress of a single plant + * + * @param block Block at which the plant is + * @param plant Plant growing there, may be null for non-persistent growth + * @return Info string describing the plants growth progress + */ + public String getPlantInfoString(Block block, Plant plant) { + StringBuilder sb = new StringBuilder(); + sb.append(ChatColor.GOLD); + sb.append(this.name); + if (plant == null) { + // non-persistent growth + double progress = grower.getProgressGrowthStage(new Plant(block.getLocation(), this)); + sb.append(" is "); + sb.append(decimalFormat.format(progress * 100)); + sb.append(" % grown"); + } else { + if (isFullyGrown(plant)) { + sb.append(" is fully grown "); + return sb.toString(); + } + appendPlantProgress(block, plant, sb); + } + return sb.toString(); + } + + private void appendPlantProgress(Block block, Plant plant, StringBuilder sb) { + long totalTime = getPersistentGrowthTime(block); + long passedTime = System.currentTimeMillis() - plant.getCreationTime(); + long timeRemaining = Math.max(0, totalTime - passedTime); + + if (timeRemaining >= INFINITE_TIME || totalTime == -1) { + sb.append(" will never grow here"); + return; + } + + if (plant.getNextUpdate() == Long.MAX_VALUE) { + sb.append(" does not grow"); + return; + } + + sb.append(" will grow to full size "); + if (timeRemaining <= 0) { + sb.append("now"); + } else { + sb.append("in "); + sb.append(TextUtil.formatDuration(timeRemaining, TimeUnit.MILLISECONDS)); + } + } + + /** + * Calculates the soil bonus multiplier for the given plant at the given + * location. A soil bonus multiplier of 0.0 means no bonus and a growth rate of + * 1.0 for the plant, a soil bonus of 1.0 means a 2.0 multiplier for the plant + * etc. + * + * @param block Block where the plant is + * @return Soil bonus applied for the location + */ + private double getSoilBonus(Block block) { + Block soilBlock = block.getRelative(0, RBUtils.getVerticalSoilOffset(block.getType()), 0); + double totalRate = 0.0; + for (int i = 0; i < maximumSoilLayers; i++) { + Double blockRate = soilBoniPerLevel.get(soilBlock.getType()); + if (blockRate == null) { + break; + } + totalRate += blockRate; + soilBlock = soilBlock.getRelative(BlockFace.DOWN); + } + if (needsToBeWaterlogged) { + boolean hasWater = false; + if (block.getType() == Material.WATER) { + hasWater = true; + } else { + BlockData data = block.getBlockData(); + if (data instanceof Waterlogged) { + hasWater = ((Waterlogged) data).isWaterlogged(); + } + } + if (!hasWater) { + return 0.0; + } + } + return Math.min(totalRate, maximumSoilBonus); + } + + public boolean isBonemealAllowed() { + return allowBoneMeal; + } + + /** + * Checks whether the given plant is fully grown + * + * @param plant the plant + * @return True if the plant has reached its maximum growth stage, false + * otherwise + */ + public boolean isFullyGrown(Plant plant) { + return grower.getMaxStage() == grower.getStage(plant); + } + + public long updatePlant(Plant plant) { + Block block = plant.getLocation().getBlock(); + return updatePlant(plant, block); + } + + /** + * Updates the world state of the plant to match its intended state based on its + * creation time and calculates the next time stamp at which the plant should be + * updated + * + * @param plant Plant to update + * @param block Block the plant is at + * @return UNIX time stamp at which the plant needs to be updated next if it is + * still growing or Long.MAX_VALUE if it will never grow or if it is already + * fully grown + */ + public long updatePlant(Plant plant, Block block) { + if (plant.getGrowthConfig() == null) { + plant.setGrowthConfig(this); + } + if (plant.getGrowthConfig() != this) { + throw new IllegalStateException("Can not grow plant with different growth config, at " + plant.getLocation() + + " with " + plant.getGrowthConfig().getName() + ", but this is " + getName()); + } + long totalTime = getPersistentGrowthTime(block); + if (totalTime == -1) { + return Long.MAX_VALUE; + } + long creationTime = plant.getCreationTime(); + long now = System.currentTimeMillis(); + long timeElapsed = now - creationTime; + double progress = (double) timeElapsed / (double) totalTime; + + SetStageResult setStageResult = setStage(plant, block, totalTime, creationTime, progress); + if (setStageResult.nextUpdateTime != null) { + return setStageResult.nextUpdateTime; + } + + int currentStage = setStageResult.currentStage; + int intendedStage = setStageResult.intendedStage; + creationTime = setStageResult.creationTime; + + if (plant.getGrowthConfig() != this) { + //happens for example when a stem fully grows + return plant.getGrowthConfig().updatePlant(plant, block); + } + + if (currentStage == grower.getMaxStage()) { + if (grower.deleteOnFullGrowth()) { + plant.getOwningCache().remove(plant); + } + return Long.MAX_VALUE; + } + double incPerStage = grower.getIncrementPerStage(); + double nextProgressStage = (intendedStage + incPerStage) / grower.getMaxStage(); + nextProgressStage = Math.min(nextProgressStage, 1.0); + long timeFromCreationTillNextStage = (long) (totalTime * nextProgressStage); + return creationTime + timeFromCreationTillNextStage; + } + + private SetStageResult setStage(Plant plant, Block block, long totalTime, long creationTime, double progress) { + int currentStage = grower.getStage(plant); + if (currentStage < 0) { + plant.getOwningCache().remove(plant); + return NO_NEXT_UPDATE; + } + + int intendedStage = Math.min((int) (grower.getMaxStage() * progress), grower.getMaxStage()); + if (intendedStage == currentStage) { + return new SetStageResult(currentStage, intendedStage, creationTime, null); + } + + boolean stageSet; + try { + stageSet = grower.setStage(plant, intendedStage); + } catch (IllegalArgumentException e) { + RealisticBiomes.getInstance().getLogger().warning("Failed to update stage for " + block.toString()); + //delete + plant.getOwningCache().remove(plant); + return NO_NEXT_UPDATE; + } + + currentStage = grower.getStage(plant); + if (intendedStage == currentStage) { + return new SetStageResult(currentStage, intendedStage, creationTime, null); + } + + if (stageSet + && grower instanceof VerticalGrower verticalGrower + && verticalGrower.isInstaBreakTouching()) { + creationTime = System.currentTimeMillis() - totalTime * currentStage / grower.getMaxStage(); + plant.setCreationTime(creationTime); + return new SetStageResult(currentStage, currentStage, creationTime, null); + } + + if (!grower.ignoreGrowthFailure()) { + //setting the state failed due to some external condition, we assume this wont change any time soon + return NO_NEXT_UPDATE; + } + + if (creationTime != plant.getCreationTime() && plant.getGrowthConfig() != this) { + long nextUpdateTime = updatePlant(plant, block); + return new SetStageResult(0, 0, 0, nextUpdateTime); + } + + return new SetStageResult(currentStage, intendedStage, creationTime, null); + } + + public Climate getClimate() { + return this.climate; + } + + public String toString() { + return getName(); + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/listener/BonemealListener.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/listener/BonemealListener.java new file mode 100644 index 0000000000..46c0e62d70 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/listener/BonemealListener.java @@ -0,0 +1,48 @@ +package com.untamedears.realisticbiomes.listener; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.Set; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.data.type.Dispenser; +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.BlockDispenseEvent; +import org.bukkit.event.player.PlayerInteractEvent; + +public class BonemealListener implements Listener { + + private Set preventedMaterials; + + public BonemealListener(Collection preventedMaterials) { + this.preventedMaterials = EnumSet.copyOf(preventedMaterials); + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onInteract(PlayerInteractEvent event) { + if (event.getAction() != Action.RIGHT_CLICK_BLOCK || event.getClickedBlock() == null) { + return; + } + if (event.getItem() == null || event.getItem().getType() != Material.BONE_MEAL) { + return; + } + if (preventedMaterials.contains(event.getClickedBlock().getType())) { + event.setCancelled(true); + } + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onDispenser(BlockDispenseEvent event) { + if (event.getItem() == null || event.getItem().getType() != Material.BONE_MEAL) { + return; + } + Dispenser dispenser = (Dispenser) event.getBlock().getBlockData(); + Block dispensedInto = event.getBlock().getRelative(dispenser.getFacing()); + if (preventedMaterials.contains(dispensedInto.getType())) { + event.setCancelled(true); + } + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/listener/MobListener.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/listener/MobListener.java new file mode 100644 index 0000000000..77e3b04487 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/listener/MobListener.java @@ -0,0 +1,48 @@ +package com.untamedears.realisticbiomes.listener; + +import java.util.ArrayList; +import java.util.List; +import org.bukkit.Material; +import org.bukkit.entity.EntityType; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityChangeBlockEvent; +import org.bukkit.event.entity.EntityPickupItemEvent; + +public class MobListener implements Listener { + + private final static List crops = new ArrayList<>(); + + public MobListener() { + crops.add(Material.POTATOES); + crops.add(Material.BEETROOTS); + crops.add(Material.CARROTS); + crops.add(Material.WHEAT); + crops.add(Material.SWEET_BERRIES); + crops.add(Material.SWEET_BERRY_BUSH); + } + + @EventHandler + public void onEntityChangeBlock(EntityChangeBlockEvent e) { + if (e.getEntityType() == EntityType.VILLAGER) { + if (crops.contains(e.getBlock().getType()) || crops.contains(e.getTo())) { + e.setCancelled(true); + } + } + if (e.getEntityType() == EntityType.FOX) { + if (crops.contains(e.getBlock().getType()) || crops.contains(e.getTo())) { + e.setCancelled(true); + } + } + } + + @EventHandler + public void onEntityPickupItem(EntityPickupItemEvent e) { + if (e.getEntityType() == EntityType.VILLAGER) { + e.setCancelled(true); + } + if (e.getEntityType() == EntityType.FOX) { + e.setCancelled(true); + } + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/listener/PlantListener.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/listener/PlantListener.java new file mode 100644 index 0000000000..a5292fef54 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/listener/PlantListener.java @@ -0,0 +1,168 @@ +package com.untamedears.realisticbiomes.listener; + +import com.untamedears.realisticbiomes.PlantLogicManager; +import com.untamedears.realisticbiomes.PlantManager; +import com.untamedears.realisticbiomes.RealisticBiomes; +import com.untamedears.realisticbiomes.growthconfig.PlantGrowthConfig; +import com.untamedears.realisticbiomes.model.Plant; +import com.untamedears.realisticbiomes.model.PlantLoadState; +import com.untamedears.realisticbiomes.utils.RBUtils; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.event.Cancellable; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockExplodeEvent; +import org.bukkit.event.block.BlockGrowEvent; +import org.bukkit.event.block.BlockPhysicsEvent; +import org.bukkit.event.block.BlockPistonExtendEvent; +import org.bukkit.event.block.BlockPistonRetractEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.block.BlockSpreadEvent; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.world.StructureGrowEvent; + +public class PlantListener implements Listener { + + private final RealisticBiomes plugin; + private PlantManager plantManager; + private PlantLogicManager plantLogicManager; + + public PlantListener(RealisticBiomes plugin, PlantManager plantManager, PlantLogicManager plantLogicManager) { + this.plugin = plugin; + this.plantManager = plantManager; + this.plantLogicManager = plantLogicManager; + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockPistonExtendEvent event) { + for (Block block : event.getBlocks()) { + plugin.getPlantLogicManager().handleBlockDestruction(block); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(BlockPistonRetractEvent event) { + plugin.getPlantLogicManager().handleBlockDestruction(event.getBlock()); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onBlockBreak(BlockBreakEvent event) { + plugin.getPlantLogicManager().handleBlockDestruction(event.getBlock()); + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onExplosion(EntityExplodeEvent event) { + for (Block block : event.blockList()) { + plugin.getPlantLogicManager().handleBlockDestruction(block); + } + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onExplosion(BlockExplodeEvent event) { + for (Block block : event.blockList()) { + plugin.getPlantLogicManager().handleBlockDestruction(block); + } + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onBlockGrow(BlockGrowEvent event) { + handleGrowEvent(event, event.getBlock(), event.getNewState().getType()); + } + + private void handleGrowEvent(Cancellable event, Block sourceBlock, Material material) { + PlantLoadState state = plantManager.getPlantIfLoaded(RBUtils.getRealPlantBlock(sourceBlock)); + if (!state.isLoaded) { + // RB cannot handle it now and therefore waiting for the next tick + // but for now cancel it to prevent vanilla mechanics + event.setCancelled(true); + + return; + } + + Plant plant = state.plant; + + PlantGrowthConfig growthConfig; + if (plant == null) { + growthConfig = plugin.getGrowthConfigManager().getGrowthConfigFallback(material); + if (growthConfig == null) { + // vanilla + return; + } + if (RBUtils.isFruit(material)) { + event.setCancelled(true); + return; + } + event.setCancelled(true); + sourceBlock = plantLogicManager.remapColumnBlock(sourceBlock, growthConfig, material); + plant = plantManager.getPlant(sourceBlock); + if (plant == null) { + // a plant should be here, but isn't + plant = new Plant(sourceBlock.getLocation(), growthConfig); + plantManager.putPlant(plant); + } + } else { + growthConfig = plant.getGrowthConfig(); + if (growthConfig == null) { + growthConfig = plugin.getGrowthConfigManager().getGrowthConfigFallback(material); + } + if (growthConfig == null) { + plantManager.deletePlant(plant); + return; + } else { + plant.setGrowthConfig(growthConfig); + } + } + plantLogicManager.updateGrowthTime(plant, sourceBlock); + event.setCancelled(true); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onBlockPlace(BlockPlaceEvent event) { + plugin.getPlantLogicManager().handlePlantCreation(event.getBlock(), event.getItemInHand()); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onStructureGrow(StructureGrowEvent event) { + // disable bonemeal + if (event.isFromBonemeal()) { + if (plugin.getConfigManager().getBonemealPreventedBlocks().contains( + event.getLocation().getBlock().getType())) { + event.setCancelled(true); + } + } + // handle trees etc. + Block block = event.getLocation().getBlock(); + handleGrowEvent(event, block, block.getType()); + } + + /* + * If Bamboo and Kelp stop answering to these events, this spigot bug might have + * been solved and should contain more info on how to update accordingly: + * https://hub.spigotmc.org/jira/browse/SPIGOT-5312 + */ + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onBlockSpread(BlockSpreadEvent event) { + handleGrowEvent(event, event.getSource(), event.getNewState().getType()); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void cactusBreak(BlockPhysicsEvent event) { + // - sourceBlock is affecting the other block + // If it is AIR then this means that the CACTUS block was replaced by AIR + // - changedType is the block the was affected + // Must be CACTUS since we handling here CACTUS break event + // Growth time should be recalculated if the CACTUS was broken + + if (event.getChangedType() != Material.CACTUS + || event.getSourceBlock().getType() != Material.AIR + || event.getSourceBlock().getY() <= event.getBlock().getLocation().getY() + ) { + return; + } + + plugin.getPlantLogicManager().handleCactusPhysics(event.getBlock()); + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/listener/PlayerListener.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/listener/PlayerListener.java new file mode 100644 index 0000000000..8faaf42bc9 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/listener/PlayerListener.java @@ -0,0 +1,156 @@ +package com.untamedears.realisticbiomes.listener; + +import com.untamedears.realisticbiomes.GrowthConfigManager; +import com.untamedears.realisticbiomes.PlantManager; +import com.untamedears.realisticbiomes.RealisticBiomes; +import com.untamedears.realisticbiomes.growthconfig.PlantGrowthConfig; +import com.untamedears.realisticbiomes.model.Plant; +import com.untamedears.realisticbiomes.utils.InfoStick; +import com.untamedears.realisticbiomes.utils.RBUtils; +import java.text.DecimalFormat; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Item; +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.player.PlayerFishEvent; +import org.bukkit.event.player.PlayerHarvestBlockEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; + +public class PlayerListener implements Listener { + + private record InteractPlant(Plant plant, PlantGrowthConfig config) { + + } + + private GrowthConfigManager growthConfigs; + private PlantManager plantManager; + private DecimalFormat decimalFormat = new DecimalFormat("0.####"); + + public PlayerListener(GrowthConfigManager growthConfigs, PlantManager plantManager) { + this.growthConfigs = growthConfigs; + this.plantManager = plantManager; + } + + // show plant progress when right clicking it with stick + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onPlayerInteractCrop(PlayerInteractEvent event) { + if (event.getAction() != Action.RIGHT_CLICK_BLOCK + || event.getItem() == null + || event.getItem().getType() != InfoStick.INFO_STICK_TYPE) { + return; + } + + Block block = RBUtils.getRealPlantBlock(event.getClickedBlock()); + if (RBUtils.isFruit(block.getType())) { + return; + } + + InteractPlant interactPlant = event.getPlayer().isSneaking() && event.getPlayer().hasPermission("rb.op") + ? getInteractPlant(block) + : getInteractPlantAndUpdate(block); + + if (interactPlant == null) { + return; + } + + event.getPlayer().sendMessage(interactPlant.config.getPlantInfoString(block, interactPlant.plant)); + if (event.getPlayer().hasPermission("rb.op")) { + event.getPlayer().sendMessage(interactPlant.plant.toString()); + } + } + + private InteractPlant getInteractPlant(Block block) { + Plant plant = plantManager.getPlant(block); + return plant != null && plant.getGrowthConfig() != null + ? new InteractPlant(plant, plant.getGrowthConfig()) + : null; + } + + private InteractPlant getInteractPlantAndUpdate(Block block) { + Plant plant = plantManager.getPlant(block); + if (plant == null) { + PlantGrowthConfig growthConfig = growthConfigs.getGrowthConfigFallback(block.getType()); + if (growthConfig == null + || RBUtils.isFruit(block.getType())) { + return null; + } + // a plant should be here, but isn't + plant = new Plant(block.getLocation(), growthConfig); + plantManager.putPlant(plant); + } + PlantGrowthConfig plantConfig = plant.getGrowthConfig(); + if (plantConfig == null) { + plantConfig = growthConfigs.getPlantGrowthConfigFallback(plant); + if (plantConfig == null) { + return null; + } + plant.setGrowthConfig(plantConfig); + } + + RealisticBiomes.getInstance().getPlantLogicManager().updateGrowthTime(plant, block); + + return new InteractPlant(plant, plantConfig); + } + + // show growth rates when hitting floor with crop + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onPlayerInteractEvent(PlayerInteractEvent event) { + if (event.getAction() != Action.LEFT_CLICK_BLOCK) { + return; + } + if (event.getItem() == null) { + return; + } + ItemStack item = event.getItem(); + PlantGrowthConfig plantConfig = growthConfigs.getGrowthConfigByItem(item); + if (plantConfig == null) { + return; + } + event.getPlayer() + .sendMessage(plantConfig.getInfoString(event.getClickedBlock().getRelative(event.getBlockFace()))); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onPlayerHarvest(PlayerHarvestBlockEvent event) { + if (event.getHarvestedBlock().getType() != Material.SWEET_BERRY_BUSH) { + return; + } + Block block = event.getHarvestedBlock(); + Plant plant = RealisticBiomes.getInstance().getPlantManager().getPlant(block); + if (plant == null) { + return; + } + PlantGrowthConfig config = plant.getGrowthConfig(); + if (config == null) { + return; + } + config.getGrower().setStage(plant, 0); + + plant.setCreationTime(System.currentTimeMillis()); + Bukkit.getScheduler().runTask(RealisticBiomes.getInstance(), () -> { + RealisticBiomes.getInstance().getPlantLogicManager().updateGrowthTime(plant, block); + }); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onFish(PlayerFishEvent event) { + //disable prot 4 fishing + if (event.getCaught() == null) { + return; + } + if (event.getCaught() instanceof Item item) { + Material mat = item.getItemStack().getType(); + if (mat == Material.BOW || + mat == Material.ENCHANTED_BOOK || + mat == Material.FISHING_ROD) { + + event.setCancelled(true); + } + } + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/Plant.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/Plant.java new file mode 100644 index 0000000000..71679970e9 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/Plant.java @@ -0,0 +1,113 @@ +package com.untamedears.realisticbiomes.model; + +import com.untamedears.realisticbiomes.RealisticBiomes; +import com.untamedears.realisticbiomes.growthconfig.PlantGrowthConfig; +import org.bukkit.Location; +import org.bukkit.block.Block; +import vg.civcraft.mc.civmodcore.utilities.BukkitComparators; +import vg.civcraft.mc.civmodcore.utilities.progress.ProgressTrackable; +import vg.civcraft.mc.civmodcore.world.locations.chunkmeta.CacheState; +import vg.civcraft.mc.civmodcore.world.locations.chunkmeta.block.table.TableBasedDataObject; + +import java.util.Date; + +public class Plant extends TableBasedDataObject implements ProgressTrackable { + + private long creationTime; + private long nextUpdate; + private PlantGrowthConfig growthConfig; + + public Plant(Location location, PlantGrowthConfig plantType) { + this(System.currentTimeMillis(), location, true, plantType); + } + + public Plant(long creationTime, Location location, boolean isNew, PlantGrowthConfig growthConfig) { + super(location, isNew); + this.creationTime = creationTime; + this.growthConfig = growthConfig; + } + + public PlantGrowthConfig getGrowthConfig() { + return growthConfig; + } + + public void setGrowthConfig(PlantGrowthConfig growthConfig) { + if (growthConfig != this.growthConfig) { + this.growthConfig = growthConfig; + setDirty(); + } + } + + @Override + public int compareTo(ProgressTrackable o) { + return BukkitComparators.getLocation().compare(getLocation(), ((Plant) o).getLocation()); + } + + /** + * @return Creation time as unix time stamp + */ + public long getCreationTime() { + return creationTime; + } + + @Override + public long getNextUpdate() { + return nextUpdate; + } + + public void resetCreationTime() { + setCreationTime(System.currentTimeMillis()); + } + + public void setCreationTime(long creationTime) { + this.creationTime = creationTime; + setDirty(); + } + + /** + * Use this method to set the next update, not setNextUpdate() + * + * @param time + */ + public void setNextGrowthTime(long time) { + ((RBChunkCache) getOwningCache()).updateGrowthTime(this, time); + } + + /** + * Internal method, don't use this + */ + @Override + public void updateInternalProgressTime(long update) { + this.nextUpdate = update; + } + + @Override + public void updateState() { + if (getCacheState() == CacheState.DELETED) { + nextUpdate = Long.MAX_VALUE; + return; + } + if (growthConfig != null) { + nextUpdate = growthConfig.updatePlant(this); + } else { + Block block = location.getBlock(); + PlantGrowthConfig newConfig = RealisticBiomes.getInstance().getGrowthConfigManager() + .getGrowthConfigFallback(block.getType()); + if (newConfig != null) { + setGrowthConfig(newConfig); + nextUpdate = newConfig.updatePlant(this); + } else { + nextUpdate = Long.MAX_VALUE; + } + } + } + + public String toString() { + return String.format("Created: %s, Loc: %s, Next update in: %s, config: %s", + new Date(creationTime), + "[" + getLocation().getBlockX() + " " + getLocation().getBlockY() + " " + getLocation().getBlockZ() + "]", + nextUpdate != Long.MAX_VALUE ? (nextUpdate - System.currentTimeMillis()) + " ms" : "Never", + growthConfig + ); + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/PlantLoadState.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/PlantLoadState.java new file mode 100644 index 0000000000..fb0e555afc --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/PlantLoadState.java @@ -0,0 +1,12 @@ +package com.untamedears.realisticbiomes.model; + +public class PlantLoadState { + + public final Plant plant; + public final boolean isLoaded; + + public PlantLoadState(Plant plant, boolean isLoaded) { + this.plant = plant; + this.isLoaded = isLoaded; + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/RBChunkCache.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/RBChunkCache.java new file mode 100644 index 0000000000..456ad3dc0e --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/RBChunkCache.java @@ -0,0 +1,88 @@ +package com.untamedears.realisticbiomes.model; + +import com.untamedears.realisticbiomes.RealisticBiomes; +import org.bukkit.Bukkit; +import vg.civcraft.mc.civmodcore.utilities.progress.ProgressTrackable; +import vg.civcraft.mc.civmodcore.utilities.progress.ProgressTracker; +import vg.civcraft.mc.civmodcore.world.WorldUtils; +import vg.civcraft.mc.civmodcore.world.locations.chunkmeta.block.table.TableBasedBlockChunkMeta; +import vg.civcraft.mc.civmodcore.world.locations.chunkmeta.block.table.TableBasedDataObject; +import vg.civcraft.mc.civmodcore.world.locations.chunkmeta.block.table.TableStorageEngine; + +public class RBChunkCache extends TableBasedBlockChunkMeta implements ProgressTrackable { + + private ProgressTracker tracker; + private long nextUpdate; + + public RBChunkCache(boolean isNew, TableStorageEngine storage) { + super(isNew, storage); + tracker = new ProgressTracker<>(); + this.nextUpdate = Long.MAX_VALUE; + } + + @Override + public int compareTo(ProgressTrackable o) { + return getChunkCoord().compareTo(((RBChunkCache) o).getChunkCoord()); + } + + @Override + public long getNextUpdate() { + return nextUpdate; + } + + @Override + protected Plant remove(int x, int y, int z) { + Plant data = (Plant) super.remove(x, y, z); + tracker.removeItem(data); + return data; + } + + @Override + public void remove(TableBasedDataObject blockData) { + super.remove(blockData); + tracker.removeItem((Plant) blockData); + } + + /** + * Updates the growth time for the given plant in the scheduled tracking + * + * @param plant Plant to update time for + * @param time Time when the plant should next be updated + */ + public void updateGrowthTime(Plant plant, long time) { + if (time < nextUpdate) { + RealisticBiomes.getInstance().getPlantProgressManager().updateTime(this, time); + } + tracker.updateItem(plant, time); + } + + @Override + public void updateInternalProgressTime(long update) { + this.nextUpdate = update; + } + + @Override + public void updateState() { + if (!WorldUtils.isChunkLoaded(getWorld(), chunkCoord.getX(), chunkCoord.getZ())) { + this.nextUpdate = Long.MAX_VALUE; + return; + } + this.nextUpdate = tracker.processItems(); + } + + @Override + public void handleChunkCacheReuse() { + // update all plants in the chunk and reinsert them into the growth updating + // cache + Bukkit.getScheduler().runTask(RealisticBiomes.getInstance(), () -> iterateAll(p -> RealisticBiomes.getInstance() + .getPlantLogicManager().updateGrowthTime((Plant) p, p.getLocation().getBlock()))); + } + + @Override + public void handleChunkUnload() { + Bukkit.getScheduler().runTask(RealisticBiomes.getInstance(), () -> { + RealisticBiomes.getInstance().getPlantProgressManager().removeChunk(this); + updateInternalProgressTime(Long.MAX_VALUE); + }); + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/RBDAO.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/RBDAO.java new file mode 100644 index 0000000000..ac99816133 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/RBDAO.java @@ -0,0 +1,308 @@ +package com.untamedears.realisticbiomes.model; + +import com.untamedears.realisticbiomes.GrowthConfigManager; +import com.untamedears.realisticbiomes.PlantLogicManager; +import com.untamedears.realisticbiomes.RealisticBiomes; +import com.untamedears.realisticbiomes.growthconfig.PlantGrowthConfig; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import vg.civcraft.mc.civmodcore.dao.ManagedDatasource; +import vg.civcraft.mc.civmodcore.world.locations.chunkmeta.CacheState; +import vg.civcraft.mc.civmodcore.world.locations.chunkmeta.XZWCoord; +import vg.civcraft.mc.civmodcore.world.locations.chunkmeta.block.BlockBasedChunkMeta; +import vg.civcraft.mc.civmodcore.world.locations.chunkmeta.block.table.TableBasedBlockChunkMeta; +import vg.civcraft.mc.civmodcore.world.locations.chunkmeta.block.table.TableBasedDataObject; +import vg.civcraft.mc.civmodcore.world.locations.chunkmeta.block.table.TableStorageEngine; + +public class RBDAO extends TableStorageEngine { + + private boolean batchMode; + private List> batches; + + public RBDAO(Logger logger, ManagedDatasource db) { + super(logger, db); + this.batchMode = false; + } + + public void setBatchMode(boolean batch) { + this.batchMode = batch; + batches = new ArrayList<>(); + for (int i = 0; i < 3; i++) { + batches.add(new ArrayList<>()); + } + } + + public void cleanupBatches() { + long currentTime = System.currentTimeMillis(); + try (Connection conn = db.getConnection(); + PreparedStatement deletePlant = conn.prepareStatement( + "delete from rb_plants where chunk_x = ? and chunk_z = ? and world_id = ? and " + + "x_offset = ? and y = ? and z_offset = ?;");) { + conn.setAutoCommit(false); + for (PlantTuple tuple : batches.get(2)) { + setDeleteDataStatement(deletePlant, tuple.plant, tuple.coord); + deletePlant.addBatch(); + } + logger.info("Batch 2: " + (System.currentTimeMillis() - currentTime) + " ms"); + logger.info("Batch 2 Size: " + batches.get(2).size()); + batches.get(2).clear(); + deletePlant.executeBatch(); + conn.setAutoCommit(true); + logger.info("Batch 2 Finish: " + (System.currentTimeMillis() - currentTime) + " ms"); + } catch (SQLException e) { + logger.log(Level.SEVERE, "Failed to delete plant from db: ", e); + } + try (Connection conn = db.getConnection(); + PreparedStatement insertPlant = conn.prepareStatement( + "insert ignore into rb_plants (chunk_x, chunk_z, world_id, x_offset, y, z_offset, creation_time, type) " + + "values(?,?,?, ?,?,?, ?,?);");) { + conn.setAutoCommit(false); + for (PlantTuple tuple : batches.get(0)) { + setInsertDataStatement(insertPlant, tuple.plant, tuple.coord); + insertPlant.addBatch(); + } + logger.info("Batch 0: " + (System.currentTimeMillis() - currentTime) + " ms"); + logger.info("Batch 0 Size: " + batches.get(0).size()); + batches.get(0).clear(); + insertPlant.executeBatch(); + conn.setAutoCommit(true); + logger.info("Batch 0 Finish: " + (System.currentTimeMillis() - currentTime) + " ms"); + } catch (SQLException e) { + logger.log(Level.SEVERE, "Failed to insert plant into db: ", e); + } + try (Connection conn = db.getConnection(); + PreparedStatement updatePlant = conn.prepareStatement("update rb_plants set creation_time = ?, type = ? where " + + "chunk_x = ? and chunk_z = ? and world_id = ? and x_offset = ? and y = ? and z_offset = ?;");) { + conn.setAutoCommit(false); + for (PlantTuple tuple : batches.get(1)) { + setUpdateDataStatement(updatePlant, tuple.plant, tuple.coord); + updatePlant.addBatch(); + } + logger.info("Batch 1: " + (System.currentTimeMillis() - currentTime) + " ms"); + logger.info("Batch 1 Size: " + batches.get(1).size()); + batches.get(1).clear(); + updatePlant.executeBatch(); + conn.setAutoCommit(true); + logger.info("Batch 1 Finish: " + (System.currentTimeMillis() - currentTime) + " ms"); + } catch (SQLException e) { + logger.log(Level.SEVERE, "Failed to update plant in db: ", e); + } + } + + @Override + public void delete(Plant data, XZWCoord coord) { + if (batchMode) { + batches.get(2).add(new PlantTuple(data, coord)); + return; + } + try (Connection conn = db.getConnection(); + PreparedStatement deletePlant = conn.prepareStatement( + "delete from rb_plants where chunk_x = ? and chunk_z = ? and world_id = ? and " + + "x_offset = ? and y = ? and z_offset = ?;");) { + setDeleteDataStatement(deletePlant, data, coord); + deletePlant.execute(); + } catch (SQLException e) { + logger.log(Level.SEVERE, "Failed to delete plant from db: ", e); + } + } + + private static void setDeleteDataStatement(PreparedStatement deletePlant, Plant data, XZWCoord coord) throws SQLException { + deletePlant.setInt(1, coord.getX()); + deletePlant.setInt(2, coord.getZ()); + deletePlant.setShort(3, coord.getWorldID()); + deletePlant.setByte(4, (byte) BlockBasedChunkMeta.modulo(data.getLocation().getBlockX())); + deletePlant.setShort(5, (short) data.getLocation().getBlockY()); + deletePlant.setByte(6, (byte) BlockBasedChunkMeta.modulo(data.getLocation().getBlockZ())); + } + + @Override + public void fill(TableBasedBlockChunkMeta chunkData, Consumer insertFunction) { + int preMultipliedX = chunkData.getChunkCoord().getX() * 16; + int preMultipliedZ = chunkData.getChunkCoord().getZ() * 16; + List toUpdate = new ArrayList<>(); + PlantLogicManager logicMan = RealisticBiomes.getInstance().getPlantLogicManager(); + GrowthConfigManager growthConfigMan = RealisticBiomes.getInstance().getGrowthConfigManager(); + World world = chunkData.getChunkCoord().getWorld(); + try (Connection insertConn = db.getConnection(); + PreparedStatement selectPlant = insertConn + .prepareStatement("select x_offset, y, z_offset, creation_time, type " + + "from rb_plants where chunk_x = ? and chunk_z = ? and world_id = ?;");) { + selectPlant.setInt(1, chunkData.getChunkCoord().getX()); + selectPlant.setInt(2, chunkData.getChunkCoord().getZ()); + selectPlant.setShort(3, chunkData.getChunkCoord().getWorldID()); + try (ResultSet rs = selectPlant.executeQuery()) { + while (rs.next()) { + int xOffset = rs.getByte(1); + int x = xOffset + preMultipliedX; + int y = rs.getShort(2); + int zOffset = rs.getByte(3); + int z = zOffset + preMultipliedZ; + Location location = new Location(world, x, y, z); + long creationTime = rs.getTimestamp(4).getTime(); + short configId = rs.getShort(5); + PlantGrowthConfig growthConfig = null; + if (configId != 0) { + growthConfig = growthConfigMan.getConfigById(configId); + } + Plant plant = new Plant(creationTime, location, false, growthConfig); + toUpdate.add(plant); + insertFunction.accept(plant); + } + } + } catch (SQLException e) { + logger.log(Level.SEVERE, "Failed to load plant from db: ", e); + } + Bukkit.getScheduler().runTask(RealisticBiomes.getInstance(), () -> { + for (Plant plant : toUpdate) { + if (plant.getCacheState() == CacheState.DELETED) { + continue; + } + logicMan.updateGrowthTime(plant, plant.getLocation().getBlock()); + } + }); + } + + @Override + public void insert(Plant data, XZWCoord coord) { + if (batchMode) { + batches.get(0).add(new PlantTuple(data, coord)); + return; + } + try (Connection conn = db.getConnection(); + PreparedStatement insertPlant = conn.prepareStatement( + "insert into rb_plants (chunk_x, chunk_z, world_id, x_offset, y, z_offset, creation_time, type) " + + "values(?,?,?, ?,?,?, ?,?);");) { + setInsertDataStatement(insertPlant, data, coord); + insertPlant.execute(); + } catch (SQLException e) { + logger.log(Level.SEVERE, "Failed to insert plant into db: ", e); + } + } + + private static void setInsertDataStatement(PreparedStatement insertPlant, Plant data, XZWCoord coord) throws SQLException { + insertPlant.setInt(1, coord.getX()); + insertPlant.setInt(2, coord.getZ()); + insertPlant.setShort(3, coord.getWorldID()); + insertPlant.setByte(4, (byte) BlockBasedChunkMeta.modulo(data.getLocation().getBlockX())); + insertPlant.setShort(5, (short) data.getLocation().getBlockY()); + insertPlant.setByte(6, (byte) BlockBasedChunkMeta.modulo(data.getLocation().getBlockZ())); + insertPlant.setTimestamp(7, new Timestamp(data.getCreationTime())); + if (data.getGrowthConfig() == null) { + insertPlant.setNull(8, Types.SMALLINT); + } else { + insertPlant.setShort(8, data.getGrowthConfig().getID()); + } + } + + @Override + public void registerMigrations() { + db.registerMigration(1, false, + "CREATE TABLE IF NOT EXISTS rb_plant (chunkId bigint(20) DEFAULT NULL, w int(11) DEFAULT NULL," + + "x int(11) DEFAULT NULL, y int(11) DEFAULT NULL, z int(11) DEFAULT NULL, date int(10) unsigned DEFAULT NULL," + + "growth double DEFAULT NULL, fruitGrowth double DEFAULT NULL)", + "CREATE TABLE IF NOT EXISTS rb_chunk (id bigint(20) NOT NULL AUTO_INCREMENT, w int(11) DEFAULT NULL, x int(11) DEFAULT NULL, " + + "z int(11) DEFAULT NULL, PRIMARY KEY (id), KEY chunk_coords_idx (w,x,z))"); + db.registerMigration(2, false, + "create table if not exists rb_plants (chunk_x int not null, chunk_z int not null, world_id smallint unsigned not null, " + + "x_offset tinyint unsigned not null, y tinyint unsigned not null, z_offset tinyint unsigned not null," + + "creation_time timestamp not null default now(), index plantChunkLookUp(chunk_x, chunk_z, world_id)," + + "index plantCoordLookUp (x_offset, y, z_offset, world_id), " + + "constraint plantUniqueLocation unique (chunk_x,chunk_z,x_offset,y,z_offset,world_id));", + "delete from rb_plants", + "insert into rb_plants (chunk_x, chunk_z, world_id, x_offset, y, z_offset, creation_time) " + + "select c.x,c.z,c.w + 1, if(mod(p.x,16)<0,mod(p.x,16)+16,mod(p.x,16)), p.y, " + + "if(mod(p.z,16)<0,mod(p.z,16)+16,mod(p.z,16)),FROM_UNIXTIME(p.date) " + + "from rb_plant p inner join rb_chunk c on p.chunkId=c.id;"); + db.registerMigration(3, false, + "alter table rb_plants add type smallint"); + db.registerMigration(4, false, + "alter table rb_plants modify y tinyint signed not null"); + db.registerMigration(5, false, + "alter table rb_plants modify y smallint not null"); + } + + @Override + public void update(Plant data, XZWCoord coord) { + if (batchMode) { + batches.get(1).add(new PlantTuple(data, coord)); + return; + } + try (Connection conn = db.getConnection(); + PreparedStatement updatePlant = conn.prepareStatement("update rb_plants set creation_time = ?, type = ? where " + + "chunk_x = ? and chunk_z = ? and world_id = ? and x_offset = ? and y = ? and z_offset = ?;");) { + setUpdateDataStatement(updatePlant, data, coord); + updatePlant.execute(); + } catch (SQLException e) { + logger.log(Level.SEVERE, "Failed to update plant in db: ", e); + } + } + + private static void setUpdateDataStatement(PreparedStatement updatePlant, Plant data, XZWCoord coord) throws SQLException { + updatePlant.setTimestamp(1, new Timestamp(data.getCreationTime())); + if (data.getGrowthConfig() == null) { + updatePlant.setNull(2, Types.SMALLINT); + } else { + updatePlant.setShort(2, data.getGrowthConfig().getID()); + } + updatePlant.setInt(3, coord.getX()); + updatePlant.setInt(4, coord.getZ()); + updatePlant.setShort(5, coord.getWorldID()); + updatePlant.setByte(6, (byte) BlockBasedChunkMeta.modulo(data.getLocation().getBlockX())); + updatePlant.setShort(7, (short) data.getLocation().getBlockY()); + updatePlant.setByte(8, (byte) BlockBasedChunkMeta.modulo(data.getLocation().getBlockZ())); + } + + @Override + public TableBasedDataObject getForLocation(int x, int y, int z, short worldID, short pluginID) { + throw new IllegalStateException("Can not load plant when chunk is not loaded"); + } + + @Override + public Collection getAllDataChunks() { + List result = new ArrayList<>(); + try (Connection insertConn = db.getConnection(); + PreparedStatement selectChunks = insertConn.prepareStatement( + "select chunk_x, chunk_z, world_id from rb_plants group by chunk_x, chunk_z, world_id"); + ResultSet rs = selectChunks.executeQuery()) { + while (rs.next()) { + int chunkX = rs.getInt(1); + int chunkZ = rs.getInt(2); + short worldID = rs.getShort(3); + result.add(new XZWCoord(chunkX, chunkZ, worldID)); + } + } catch (SQLException e) { + logger.log(Level.SEVERE, "Failed to select populated chunks from db: ", e); + } + return result; + } + + @Override + public boolean stayLoaded() { + return false; + } + + private class PlantTuple { + + private Plant plant; + private XZWCoord coord; + + PlantTuple(Plant plant, XZWCoord coord) { + this.plant = plant; + this.coord = coord; + } + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/RBSchematic.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/RBSchematic.java new file mode 100644 index 0000000000..50957fe6c1 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/RBSchematic.java @@ -0,0 +1,57 @@ +package com.untamedears.realisticbiomes.model; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.math.BlockVector3; +import org.bukkit.Location; +import org.bukkit.block.data.BlockData; + +public class RBSchematic { + + private BlockData[][][] blockData; + private int xOffset; + private int zOffset; + private String name; + + public RBSchematic(String name, Clipboard weClipBoard) { + this.blockData = convertWEClipBoard(weClipBoard); + this.name = name; + this.xOffset = blockData.length / 2; + this.zOffset = blockData[0][0].length / 2; + } + + public String getName() { + return name; + } + + public void spawnAt(Location loc) { + int xCap = blockData.length; + int yCap = blockData[0].length; + int zCap = blockData[0][0].length; + int localxOffset = loc.getBlockX() - xOffset; + int localyOffset = loc.getBlockY(); + int localzOffset = loc.getBlockZ() - zOffset; + for (int x = 0; x < xCap; x++) { + for (int y = 0; y < yCap; y++) { + for (int z = 0; z < zCap; z++) { + loc.getWorld().getBlockAt(x + localxOffset, y + localyOffset, z + localzOffset) + .setBlockData(blockData[x][y][z]); + } + } + } + } + + private static BlockData[][][] convertWEClipBoard(Clipboard weClipBoard) { + BlockVector3 size = weClipBoard.getDimensions(); + BlockData[][][] result = new BlockData[size.getX()][size.getY()][size.getZ()]; + for (int x = 0; x < size.getX(); x++) { + for (int y = 0; y < size.getY(); y++) { + for (int z = 0; z < size.getZ(); z++) { + result[x][y][z] = BukkitAdapter.adapt(weClipBoard.getBlock(BlockVector3.at(x, y, z))); + } + } + } + return result; + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/gauss/DeviatingDouble.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/gauss/DeviatingDouble.java new file mode 100644 index 0000000000..d05bb77746 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/gauss/DeviatingDouble.java @@ -0,0 +1,40 @@ +package com.untamedears.realisticbiomes.model.gauss; + +import java.util.Random; + +public class DeviatingDouble { + + private static Random rng = new Random(); + + private double lowerCap; + private double upperCap; + private double deviation; + + public DeviatingDouble(double lowerCap, double upperCap, double deviation) { + this.lowerCap = lowerCap; + this.upperCap = upperCap; + this.deviation = deviation; + } + + public double deviate(double baseValue) { + // random value within baseValue - deviation and baseValue + deviation + baseValue += rng.nextDouble() * 2 * deviation; + baseValue -= deviation; + if (upperCap > baseValue) { + return upperCap; + } + if (lowerCap < baseValue) { + return lowerCap; + } + return baseValue; + } + + private int randomRound(double d) { + double floor = Math.floor(d); + double leftOver = d - Math.floor(d); + if (rng.nextDouble() > leftOver) { + return (int) floor; + } + return (int) floor + 1; + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/gauss/GaussProperty.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/gauss/GaussProperty.java new file mode 100644 index 0000000000..a1cb006679 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/gauss/GaussProperty.java @@ -0,0 +1,48 @@ +package com.untamedears.realisticbiomes.model.gauss; + +import java.util.Random; + +public final class GaussProperty { + + private static Random rng = new Random(); + + private DeviatingDouble valueDeviation; + private double sigma; + private double value; + + private GaussProperty(DeviatingDouble valueDeviation, double sigma, double baseValue) { + this.valueDeviation = valueDeviation; + this.sigma = sigma; + this.value = baseValue; + } + + public static GaussProperty constructProperty(DeviatingDouble valueDeviation, double sigma) { + return new GaussProperty(valueDeviation, sigma, 0); + } + + public GaussProperty getDeviatedCopy() { + return getDeviatedCopy(value); + } + + public GaussProperty getDeviatedCopy(double baseValue) { + return new GaussProperty(valueDeviation, sigma, valueDeviation.deviate(baseValue)); + } + + public double getRandomValue() { + return rng.nextGaussian() * sigma * value + value; + } + + public int getRoundedRandomValue() { + return randomRound(getRandomValue()); + } + + private static int randomRound(double d) { + double floor = Math.floor(d); + double leftOver = d - Math.floor(d); + if (rng.nextDouble() > leftOver) { + return (int) floor; + } + return (int) floor + 1; + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/gauss/GaussTree.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/gauss/GaussTree.java new file mode 100644 index 0000000000..c0f9208c53 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/gauss/GaussTree.java @@ -0,0 +1,172 @@ +package com.untamedears.realisticbiomes.model.gauss; + +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldedit.math.Vector2; +import com.sk89q.worldedit.regions.CylinderRegion; +import com.sk89q.worldedit.regions.Region; +import com.untamedears.realisticbiomes.model.ltree.BlockTransformation; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import net.minecraft.nbt.CompoundTag; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; +import vg.civcraft.mc.civmodcore.world.WorldUtils; + +public class GaussTree { + + private GaussProperty preCanopyHeight; + private GaussProperty stemAngleStep; + private double stemAngleCap; + private GaussProperty stemRadius; + private BlockTransformation stemTransform; + private BlockTransformation leafTransform; + + private GaussProperty canopyHeight; + private GaussProperty leafAmount; + private double branchChance; + private GaussProperty branchLength; + private GaussProperty branchLogInterval; + private GaussProperty branchAngle; + + private Random rng; + + public GaussTree(ItemStack sapling) { + + } + + public GaussTree(GaussProperty preCanopyHeight, GaussProperty stemAngleStep, double stemAngleCap, + GaussProperty stemRadius, BlockTransformation stemTransform, GaussProperty canopyHeight, + GaussProperty leafAmount, double branchChance, GaussProperty branchLength, GaussProperty branchLogInterval, + GaussProperty branchAngle, BlockTransformation leafTransform) { + this.preCanopyHeight = preCanopyHeight; + this.stemAngleStep = stemAngleStep; + this.stemAngleCap = stemAngleCap; + this.stemRadius = stemRadius; + this.stemTransform = stemTransform; + this.canopyHeight = canopyHeight; + this.leafAmount = leafAmount; + this.branchChance = branchChance; + this.branchLength = branchLength; + this.branchLogInterval = branchLogInterval; + this.branchAngle = branchAngle; + this.leafTransform = leafTransform; + rng = new Random(); + } + + public void genAt(Location loc) { + loc = new Location(loc.getWorld(), loc.getBlockX() + 0.5, loc.getBlockY() + 0.5, loc.getBlockZ() + 0.5); + loc = genTreeSection(loc, preCanopyHeight, false); + loc = genTreeSection(loc, canopyHeight, true); + } + + private Location genTreeSection(Location startingLocation, GaussProperty sectionHeight, boolean branch) { + int localHeight = sectionHeight.getRoundedRandomValue(); + Vector up = new Vector(0, 1, 0); + Vector direction = up.clone(); + double radius = stemRadius.getRandomValue(); + Location currentLocation = startingLocation.clone(); + for (int i = 0; i < localHeight; i++) { + // first set blocks for the current location + List logs = setNearby(currentLocation, direction, radius, stemTransform); + + if (branch) { + // second generate eventual branches + logs.addAll(generateBranches(currentLocation, branchChance)); + + // third generate foliage around it + for (Block log : logs) { + double localChance = leafAmount.getRandomValue(); + recursiveLeafGeneration(log, localChance); + } + } + + // fourth apply a random directional deviation to the upwards movement + + // calculate length of the direction vector added + double deviation = stemAngleStep.getRandomValue(); + // vector in random x z direction + Vector randomDirection = new Vector(randomNumber(-1, 1), 0, randomNumber(-1, 1)).normalize() + .multiply(deviation); + Vector newDirection = direction.clone().add(randomDirection).normalize(); + double dotProductUp = up.dot(newDirection); + if (dotProductUp > stemAngleCap) { + // stemAngleCap works as lower cap on this dot product, which is the cosine of + // the angle to the y-axis + direction = newDirection; + } + currentLocation.add(direction); + } + return currentLocation; + } + + private Vector getRandomXZVector() { + return new Vector(randomNumber(-1, 1), 0, randomNumber(-1, 1)); + } + + private void recursiveLeafGeneration(Block sourceBlock, double chance) { + if (chance >= 1.0 || Math.random() < chance) { + for (Block innerBlock : WorldUtils.getAllBlockSides(sourceBlock, true)) { + if (leafTransform.applyAt(innerBlock.getLocation())) { + recursiveLeafGeneration(innerBlock, chance - 1); + } + } + } + } + + private List generateBranches(Location sourceLocation, double chance) { + List result = new ArrayList<>(); + while (chance >= 1.0 || rng.nextDouble() <= branchChance) { + generateBranch(sourceLocation, result); + chance--; + if (chance <= 0) { + break; + } + } + return result; + } + + private void generateBranch(Location sourceLocation, List result) { + Vector direction = getRandomXZVector(); + Vector oppositeDirection = direction.multiply(-1); + Vector yModifier = new Vector(0, branchAngle.getRandomValue(), 0); + direction.add(yModifier); + oppositeDirection.add(yModifier); + direction.normalize(); + oppositeDirection.normalize(); + double length = branchLength.getRandomValue(); + double logInterval = branchLogInterval.getRandomValue(); + int blockAmount = (int) (length / logInterval); + Location currentLocation = sourceLocation.clone(); + Location oppositeLocation = sourceLocation.clone(); + Vector perStepOffset = direction.clone().multiply(logInterval); + Vector oppositeStepOffset = oppositeDirection.clone().multiply(logInterval); + for (int i = 0; i < blockAmount; i++) { + currentLocation.add(perStepOffset); + oppositeLocation.add(oppositeStepOffset); + stemTransform.applyAt(currentLocation); + stemTransform.applyAt(oppositeLocation); + result.add(currentLocation.getBlock()); + result.add(oppositeLocation.getBlock()); + } + } + + private List setNearby(Location center, Vector direction, double radius, BlockTransformation transformer) { + List result = new ArrayList<>(); + Region region = new CylinderRegion(BukkitAdapter.adapt(center.getWorld()), BukkitAdapter.asBlockVector(center), + Vector2.at(radius, radius), center.getBlockY(), center.getBlockY()); + region.forEach(b -> { + Block block = BukkitAdapter.adapt(center.getWorld(), b).getBlock(); + transformer.applyAt(block); + result.add(block); + }); + return result; + } + + private double randomNumber(double lowerBound, double upperBound) { + double range = upperBound - lowerBound; + return rng.nextDouble() * range + lowerBound; + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/gauss/GaussTreeConfig.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/gauss/GaussTreeConfig.java new file mode 100644 index 0000000000..6b2cb47905 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/gauss/GaussTreeConfig.java @@ -0,0 +1,57 @@ +package com.untamedears.realisticbiomes.model.gauss; + +import com.untamedears.realisticbiomes.model.ltree.BlockTransformation; +import org.bukkit.configuration.ConfigurationSection; + +public class GaussTreeConfig { + + private String identifier; + + private GaussProperty preCanopyHeight; + private GaussProperty stemAngleStep; + private double stemAngleCap; + private GaussProperty stemRadius; + private BlockTransformation stemTransform; + private BlockTransformation leafTransform; + + private GaussProperty canopyHeight; + private GaussProperty leafAmount; + private GaussProperty branchChance; + private GaussProperty branchLength; + private GaussProperty branchLogInterval; + private GaussProperty branchAngle; + + public GaussTreeConfig(ConfigurationSection config) { + if (!config.isString("id")) { + throw new IllegalArgumentException("Config did not have an id"); + } + this.identifier = config.getString("id"); + this.preCanopyHeight = parseGaussProperty(config, "stem_height"); + this.stemAngleStep = parseGaussProperty(config, "stem_angle_step"); + this.stemRadius = parseGaussProperty(config, "stem_radius"); + this.canopyHeight = parseGaussProperty(config, "canopy_height"); + this.leafAmount = parseGaussProperty(config, "leaf_amount"); + this.branchLength = parseGaussProperty(config, "branch_length"); + this.branchLogInterval = parseGaussProperty(config, "branch_log_interval"); + this.branchAngle = parseGaussProperty(config, "stem_height"); + this.branchChance = parseGaussProperty(config, "branch_chance"); + } + + private GaussProperty parseGaussProperty(ConfigurationSection config, String key) { + if (!config.isConfigurationSection(key)) { + return GaussProperty.constructProperty(new DeviatingDouble(0.0, 1.0, 0.1), 0.15); + } + config = config.getConfigurationSection(key); + if (!config.isConfigurationSection("deviation")) { + throw new IllegalArgumentException(config.getCurrentPath() + " did not specify deviation"); + } + double lowerCap = config.getDouble("lower_cap", 0); + double upperCap = config.getDouble("upper_cap"); + double deviation = config.getDouble("deviation", 0.1); + DeviatingDouble deviatingDouble = new DeviatingDouble(lowerCap, upperCap, deviation); + double sigma = config.getDouble("sigma", 0.15); + return GaussProperty.constructProperty(deviatingDouble, sigma); + } + + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/gauss/drop/BlockDrop.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/gauss/drop/BlockDrop.java new file mode 100644 index 0000000000..ebd3517b06 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/gauss/drop/BlockDrop.java @@ -0,0 +1,38 @@ +package com.untamedears.realisticbiomes.model.gauss.drop; + +import java.util.Arrays; +import java.util.List; +import org.bukkit.Location; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.world.locations.chunkmeta.block.auto.YamlDataObject; + +public class BlockDrop extends YamlDataObject { + + private List drops; + + public BlockDrop(Location location, boolean isNew, ItemStack drop) { + this(location, isNew, Arrays.asList(drop)); + } + + public BlockDrop(Location location, boolean isNew, List drops) { + super(location, isNew); + this.drops = drops; + } + + public List getDrops() { + return drops; + } + + @Override + protected void concreteSerialize(YamlConfiguration config) { + config.set("drops", drops); + } + + public static BlockDrop deserialize(Location location, YamlConfiguration config) { + @SuppressWarnings("unchecked") + List drops = (List) config.getList("drops"); + return new BlockDrop(location, false, drops); + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/ltree/BlockTransformation.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/ltree/BlockTransformation.java new file mode 100644 index 0000000000..599d84f6a3 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/ltree/BlockTransformation.java @@ -0,0 +1,51 @@ +package com.untamedears.realisticbiomes.model.ltree; + +import java.util.Map; +import java.util.Map.Entry; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Tag; +import org.bukkit.block.Block; +import org.bukkit.block.data.type.Leaves; +import vg.civcraft.mc.civmodcore.inventory.items.MaterialUtils; +import vg.civcraft.mc.civmodcore.world.BlockProperties; + +public class BlockTransformation { + + private Material material; + private Map blockData; + + public BlockTransformation(Material material, Map blockData) { + this.material = material; + this.blockData = blockData; + } + + /** + * Applies this transformation at the given location + * + * @param loc Location the current LStep is at + * @return Whether the transformation could be applied + */ + public boolean applyAt(Location loc) { + return applyAt(loc.getBlock()); + } + + public boolean applyAt(Block block) { + if (block.getY() > 255) { + return false; + } + if (!MaterialUtils.isAir(block.getType())) { + return false; + } + block.setType(material); + if (Tag.LEAVES.isTagged(material)) { + Leaves leaves = (Leaves) block.getBlockData(); + leaves.setPersistent(true); + } + for (Entry data : blockData.entrySet()) { + BlockProperties.setBlockProperty(block, data.getKey(), data.getValue()); + } + return true; + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/ltree/LStep.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/ltree/LStep.java new file mode 100644 index 0000000000..710f0f6dfd --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/ltree/LStep.java @@ -0,0 +1,42 @@ +package com.untamedears.realisticbiomes.model.ltree; + +import java.util.List; +import org.bukkit.Location; +import org.bukkit.util.Vector; + +public class LStep { + + private LStepConfig config; + private Location location; + private Vector direction; + private int depth; + + public LStep(LStepConfig config, Location location, Vector direction, int depth) { + this.location = location; + this.config = config; + this.direction = direction; + this.depth = depth; + + } + + public List apply() { + return config.progress(this); + } + + public Vector getDirection() { + return direction; + } + + public Location getLocation() { + return location; + } + + public LStepConfig getConfig() { + return config; + } + + public int getDepth() { + return depth; + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/ltree/LStepConfig.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/ltree/LStepConfig.java new file mode 100644 index 0000000000..e2bce3b82f --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/ltree/LStepConfig.java @@ -0,0 +1,146 @@ +package com.untamedears.realisticbiomes.model.ltree; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.bukkit.Location; +import org.bukkit.util.Vector; + +public class LStepConfig { + + private List results; + private double directionWeight; + private List transformations; + private boolean normalizeBeforeDirectionTransformation; + private boolean transformInWorldCoordinates; + private double chanceSum; + private String id; + + public LStepConfig(String id, double directionWeight, List transformations, + boolean normalizeBeforeDirectionTransformation, boolean transformInWorldCoordinates) { + this.directionWeight = directionWeight; + this.transformations = transformations; + this.normalizeBeforeDirectionTransformation = normalizeBeforeDirectionTransformation; + this.transformInWorldCoordinates = transformInWorldCoordinates; + this.results = new ArrayList<>(); + this.chanceSum = 0; + this.id = id; + } + + public String getID() { + return id; + } + + public void addNextStep(List step, double chance, List directions) { + this.results.add(new LStepConfigChance(step, chance, directions)); + chanceSum += chance; + } + + public LStep toStep(Location loc, Vector direction, int depth) { + return new LStep(this, loc, direction, depth); + } + + public Vector getNewDirection(Vector oldDirection, Vector newDirection) { + System.out.println("New before: " + newDirection); + System.out.println("Old: " + oldDirection); + if (!transformInWorldCoordinates) { + newDirection = getInRelativeCoordinateSystem(oldDirection, newDirection); + } + System.out.println("New after: " + newDirection); + return oldDirection.clone().multiply(1 - directionWeight).add(newDirection.clone().multiply(directionWeight)); + } + + /** + * Transform the toTransform vector into one relative to an ongoing directional + * movement targetDirection. This is done by assuming that targetDirection is + * the up vector of the target coordinate system, the normal of 0,1,0 and + * targetDirection is the x component and z is the cross product of the x + * component and targetDirection. If the target direction is already the up + * vector, the vector toTransform is left untouched as in this case our target + * coordinate system is the world coordinate system + * + * @param targetDirection Direction based on which the target coordinate system + * is defined + * @param toTransform Relative direction to transform + * @return Transformed vector in target coordinate system + */ + private Vector getInRelativeCoordinateSystem(Vector targetDirection, Vector toTransform) { + if (Math.abs(targetDirection.getX()) < 1E-10 && Math.abs(targetDirection.getZ()) < 1E-10) { + // standard up vector, meaning our default world coordinate system + return toTransform; + } + // at this point we know the targetDirection vector is not the up vector, so the + // cross product will give us a valid normal on the plane defined by + // targetDirection and the up vector in world coordinates + Vector xComponent = new Vector(0, 1, 0).crossProduct(targetDirection); + Vector zComponent = xComponent.getCrossProduct(targetDirection); + System.out.println("x: " + xComponent); + System.out.println("z: " + zComponent); + return dirtyMatrixMultiplication(xComponent, targetDirection, zComponent, toTransform); + } + + private static Vector dirtyMatrixMultiplication(Vector matrixComp1, Vector matrixComp2, Vector matrixComp3, Vector vector) { + double firstComp = matrixComp1.getX() * vector.getX() + matrixComp2.getX() * vector.getY() + matrixComp3.getX() * vector.getZ(); + double secondComp = matrixComp1.getY() * vector.getX() + matrixComp2.getY() * vector.getY() + matrixComp3.getY() * vector.getZ(); + double thirdComp = matrixComp1.getZ() * vector.getX() + matrixComp2.getZ() * vector.getY() + matrixComp3.getZ() * vector.getZ(); + return new Vector(firstComp, secondComp, thirdComp); + } + + public List progress(LStep step) { + Location currentLocation = step.getLocation(); + Vector normalizedDirection = step.getDirection().clone().normalize(); + for (BlockTransformation transform : transformations) { + transform.applyAt(currentLocation); + } + if (results.isEmpty()) { + return Collections.emptyList(); + } + List nextSteps = null; + List nextStepDirection = null; + if (results.size() == 1) { + nextSteps = results.get(0).step; + nextStepDirection = results.get(0).directions; + } else { + double random = Math.random() * chanceSum; + for (LStepConfigChance chance : results) { + random -= chance.chance; + if (random <= 0) { + nextSteps = chance.step; + nextStepDirection = chance.directions; + break; + } + } + } + List result = new ArrayList<>(); + Vector oldDirection = step.getDirection(); + if (normalizeBeforeDirectionTransformation) { + oldDirection = normalizedDirection; + } + for (int i = 0; i < nextSteps.size(); i++) { + LStepConfig subStep = nextSteps.get(i); + Vector subDirection = nextStepDirection.get(i); + Vector localDirection = subStep.getNewDirection(oldDirection, subDirection); + Location updatedLocation = step.getLocation().clone().add(localDirection); + result.add(subStep.toStep(updatedLocation, localDirection, step.getDepth() + 1)); + } + return result; + } + + public LStepConfig clone() { + return new LStepConfig(id, directionWeight, transformations, normalizeBeforeDirectionTransformation, transformInWorldCoordinates); + } + + private static final class LStepConfigChance { + + private final double chance; + private final List step; + private final List directions; + + public LStepConfigChance(List step, double chance, List directions) { + this.step = step; + this.chance = chance; + this.directions = directions; + } + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/ltree/LTree.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/ltree/LTree.java new file mode 100644 index 0000000000..66d7f5ef2e --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/ltree/LTree.java @@ -0,0 +1,66 @@ +package com.untamedears.realisticbiomes.model.ltree; + +import java.util.Deque; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.bukkit.Location; +import org.bukkit.util.Vector; + +public class LTree { + + private LStepConfig startSymbol; + private Vector intialVector; + private String name; + private int DEPTH_CAP = 100; + + public LTree(String name, String startingSymbol, Vector initialVector, Map> spreadRules, + Function configGenerator) { + this.intialVector = initialVector; + this.name = name; + Map configMap = new HashMap<>(); + configMap.put(startingSymbol, configGenerator.apply(startingSymbol)); + for (List ruleList : spreadRules.values()) { + for (SpreadRule rule : ruleList) { + for (String key : rule.getResultSteps()) { + configMap.put(key, configGenerator.apply(key)); + } + } + } + this.startSymbol = configMap.get(startingSymbol); + if (this.startSymbol == null) { + throw new IllegalArgumentException("Starting symbol can not be null"); + } + for (Entry> entry : spreadRules.entrySet()) { + LStepConfig currentStep = configMap.get(entry.getKey()); + for (SpreadRule rule : entry.getValue()) { + List followupSteps = rule.getResultSteps().stream().map(configMap::get).collect(Collectors.toList()); + currentStep.addNextStep(followupSteps, rule.getChance(), rule.getDirections()); + } + } + } + + public String getName() { + return name; + } + + public void genAt(Location loc) { + LStep startingStep = new LStep(startSymbol, loc, intialVector, 0); + Deque todo = new LinkedList<>(); + todo.add(startingStep); + while (!todo.isEmpty()) { + LStep item = todo.poll(); + System.out.println("Processing " + item.getConfig().getID()); + for (LStep next : item.apply()) { + if (next.getDepth() <= DEPTH_CAP) { + todo.push(next); + } + } + } + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/ltree/SpreadRule.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/ltree/SpreadRule.java new file mode 100644 index 0000000000..e66ab5a95d --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/model/ltree/SpreadRule.java @@ -0,0 +1,30 @@ +package com.untamedears.realisticbiomes.model.ltree; + +import java.util.List; +import org.bukkit.util.Vector; + +public class SpreadRule { + + private final double chance; + private final List resultSteps; + private final List directions; + + public SpreadRule(double chance, List resultSteps, List directions) { + this.chance = chance; + this.resultSteps = resultSteps; + this.directions = directions; + } + + public double getChance() { + return chance; + } + + public List getDirections() { + return directions; + } + + public List getResultSteps() { + return resultSteps; + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/noise/BiomeConfiguration.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/noise/BiomeConfiguration.java new file mode 100644 index 0000000000..5671b90d31 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/noise/BiomeConfiguration.java @@ -0,0 +1,87 @@ +package com.untamedears.realisticbiomes.noise; + +import com.untamedears.realisticbiomes.FarmBeaconManager; +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.block.Biome; +import org.bukkit.block.Block; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class BiomeConfiguration { + + private final CropNoise noise; + private final FarmBeaconManager farmBeaconManager; + private final Map biomeClimates; + private double yieldOverride = -1; + + private BiomeConfiguration(CropNoise noise, FarmBeaconManager farmBeaconManager, Map biomeClimates) { + this.noise = noise; + this.farmBeaconManager = farmBeaconManager; + this.biomeClimates = biomeClimates; + } + + @SuppressWarnings("unchecked") + public static BiomeConfiguration fromConfiguration(CropNoise noise, FarmBeaconManager farmBeaconManager, List> climatesList, Map> biomeAliases) { + Map biomeClimates = new HashMap<>(); + for (Map climateMap : climatesList) { + Climate climate = new Climate(((Number) climateMap.get("temperature")).doubleValue(), ((Number) climateMap.get("humidity")).doubleValue(), (boolean) ((Map) climateMap).getOrDefault("saline", false), (boolean) ((Map) climateMap).getOrDefault("hell", false)); + + List biomes = (List) climateMap.get("biomes"); + for (String biome : biomes) { + List aliases = biomeAliases.get(biome); + if (aliases == null) { + biomeClimates.put(RegistryAccess.registryAccess().getRegistry(RegistryKey.BIOME).get(NamespacedKey.minecraft(biome.toLowerCase())), climate); + } else { + for (Biome alias : aliases) { + biomeClimates.put(alias, climate); + } + } + } + } + return new BiomeConfiguration(noise, farmBeaconManager, biomeClimates); + } + + public Climate getClimate(Biome biome) { + return biomeClimates.get(biome); + } + + public double getFertility(Block block) { + return noise.getFertility(block.getX(), block.getZ()); + } + + public double getTemperature(Block block) { + return noise.getTemperature(block.getX(), block.getZ()); + } + + public double getHumidity(Block block) { + return noise.getHumidity(block.getX(), block.getZ()); + } + + public double getHumidityScale() { + return noise.getHumidityScale(); + } + + public void setYieldOverride(double yieldOverride) { + this.yieldOverride = yieldOverride; + } + + public double getYield(Block block, Climate climate, Material mat, int maxYield) { + if (yieldOverride != -1) { + return yieldOverride; + } + Climate biomeClimate = biomeClimates.get(block.getBiome()); + if (biomeClimate == null || biomeClimate.saline() != climate.saline() || biomeClimate.hell() != climate.hell()) { + return 0; + } + double yield = noise.getYield(block.getX(), block.getZ(), biomeClimate.temperature(), biomeClimate.humidity(), climate.temperature(), climate.humidity(), + farmBeaconManager.isNearBeacon(block.getLocation()) ? farmBeaconManager.getFarmBeaconFertility() : 0); + if (mat == Material.WHEAT && yield < 1.0 / maxYield) { + yield = (1.00001 / maxYield); + } + return yield; + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/noise/Climate.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/noise/Climate.java new file mode 100644 index 0000000000..6e91c8c6b2 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/noise/Climate.java @@ -0,0 +1,5 @@ +package com.untamedears.realisticbiomes.noise; + +public record Climate(double temperature, double humidity, boolean saline, boolean hell) { + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/noise/CropNoise.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/noise/CropNoise.java new file mode 100644 index 0000000000..a693ee7ee0 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/noise/CropNoise.java @@ -0,0 +1,90 @@ +package com.untamedears.realisticbiomes.noise; + +import org.bukkit.configuration.ConfigurationSection; + +public class CropNoise { + + private static final double SCALE = 64; + + private final SimplexNoise temperatureNoise; + private final NoiseConfiguration temperatureConfiguration; + private final SimplexNoise humidityNoise; + private final NoiseConfiguration humidityConfiguration; + private final SimplexNoise fertilitySeed; + private final SimplexNoise fertilitySeed2; + + private final double yieldPowerFactor; + + private final double fertilityPower; + private final double fertilityScale; + + private final double fertilityScale2; + private final double fertilityMul2; + + private CropNoise(NoiseConfiguration temperature, NoiseConfiguration humidity, long fertilitySeed, long fertilitySeed2, double fertilityScale2, double fertilityMul2, double yieldPowerFactor, double fertilityPower, double fertilityScale) { + this.temperatureNoise = new SimplexNoise(temperature.seed()); + this.temperatureConfiguration = temperature; + this.humidityNoise = new SimplexNoise(humidity.seed()); + this.humidityConfiguration = humidity; + this.fertilitySeed = new SimplexNoise(fertilitySeed); + this.fertilitySeed2 = new SimplexNoise(fertilitySeed2); + this.fertilityScale2 = fertilityScale2; + this.fertilityMul2 = fertilityMul2; + this.yieldPowerFactor = yieldPowerFactor; + this.fertilityPower = fertilityPower; + this.fertilityScale = fertilityScale; + } + + public static CropNoise fromConfiguration(ConfigurationSection section) { + return new CropNoise( + NoiseConfiguration.fromConfiguration(section.getConfigurationSection("temperature")), + NoiseConfiguration.fromConfiguration(section.getConfigurationSection("humidity")), + section.getLong("fertility_seed"), + section.getLong("fertility_seed2"), + section.getDouble("fertility2_scale"), + section.getDouble("fertility2_mul"), + section.getDouble("yield_power_factor"), + section.getDouble("fertility_power"), + section.getDouble("fertility_scale") + ); + } + + public double getFertility(int x, int z) { + return Math.pow((Math.max(this.fertilitySeed2.noise(x / fertilityScale2, z / fertilityScale2) * fertilityMul2, this.fertilitySeed.noise(x / fertilityScale, z / fertilityScale)) + 1) / 2, this.fertilityPower); + } + + public double getHumidity(int x, int z) { + return this.humidityConfiguration.scale() * (this.humidityNoise.fractal(this.humidityConfiguration.octaves(), + x / SCALE, + z / SCALE, + this.humidityConfiguration.frequency(), + this.humidityConfiguration.amplitude(), + this.humidityConfiguration.lacunarity(), + this.humidityConfiguration.persistence())); + } + + public double getHumidityScale() { + return this.humidityConfiguration.scale(); + } + + public double getTemperature(int x, int z) { + return this.temperatureConfiguration.scale() * (this.temperatureNoise.fractal(this.temperatureConfiguration.octaves(), + x / SCALE, + z / SCALE, + this.temperatureConfiguration.frequency(), + this.temperatureConfiguration.amplitude(), + this.temperatureConfiguration.lacunarity(), + this.temperatureConfiguration.persistence())); + } + + public double getYield(int x, int z, double biomeTemperature, double biomeHumidity, double cropTemperature, double cropHumidity, double extraFertility) { + double temperature = getTemperature(x, z) + biomeTemperature; + double humidity = getHumidity(x, z) + biomeHumidity; + double fertility = getFertility(x, z) + extraFertility; + + double dt = temperature - cropTemperature; + double dh = humidity - cropHumidity; + + return Math.pow(fertility * (1 - Math.min(1, Math.sqrt(dt * dt + dh * dh))), this.yieldPowerFactor); + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/noise/MeasureCommand.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/noise/MeasureCommand.java new file mode 100644 index 0000000000..287e5020df --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/noise/MeasureCommand.java @@ -0,0 +1,57 @@ +package com.untamedears.realisticbiomes.noise; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import com.untamedears.realisticbiomes.breaker.BreakManager; +import net.kyori.adventure.text.Component; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Map; + +public class MeasureCommand extends BaseCommand { + + private final BiomeConfiguration biomes; + private final BreakManager breakManager; + + public MeasureCommand(BiomeConfiguration biomes, BreakManager breakManager) { + this.biomes = biomes; + this.breakManager = breakManager; + } + + @CommandAlias("rbmeasure") + @CommandPermission("realisticbiomes.measure") + public void measure(Player player) { + Block block = player.getLocation().getBlock(); + + Climate climate = biomes.getClimate(block.getBiome()); + if (climate == null) { + player.sendMessage(Component.text("Invalid biome")); + return; + } + + + player.sendMessage(Component.text("Biome temperature: " + (int) (100 * climate.temperature()))); + player.sendMessage(Component.text("Biome humidity: " + (int) (100 * climate.humidity()))); + + player.sendMessage(Component.text("Soil fertility: " + NumberFormat.getPercentInstance().format(biomes.getFertility(block)))); + player.sendMessage(Component.text("Ambient temperature: " + (int) (100 * (biomes.getTemperature(block) + climate.temperature())))); + player.sendMessage(Component.text("Ambient humidity: " + (int) (100 * (biomes.getHumidity(block) + climate.humidity())))); + + NumberFormat f = new DecimalFormat("#.##%"); + for (Map.Entry entry : breakManager.getClimates().entrySet()) { + double yield = this.biomes.getYield(block, entry.getValue(), entry.getKey(), breakManager.getMaxYield().getOrDefault(entry.getKey(), 0)); + if (yield > 0.001) { + String format = f.format(yield); + + int temp = (int) (entry.getValue().temperature() * 100); + int hum = (int) (entry.getValue().humidity() * 100); + + player.sendMessage(Component.text(entry.getKey() + ": t=" + temp + " h=" + hum + " " + format)); + } + } + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/noise/MeasureListener.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/noise/MeasureListener.java new file mode 100644 index 0000000000..37a8e907cb --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/noise/MeasureListener.java @@ -0,0 +1,92 @@ +package com.untamedears.realisticbiomes.noise; + +import com.untamedears.realisticbiomes.RealisticBiomes; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +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; + +public class MeasureListener implements Listener { + + private final BiomeConfiguration biomes; + + private final Material temperatureMeasure; + private final Material humidityMeasure; + private final Material fertilityMeasure; + + public MeasureListener(BiomeConfiguration biomes, Material temperatureMeasure, Material humidityMeasure, Material fertilityMeasure) { + this.biomes = biomes; + this.temperatureMeasure = temperatureMeasure; + this.humidityMeasure = humidityMeasure; + this.fertilityMeasure = fertilityMeasure; + } + + public static MeasureListener fromConfiguration(BiomeConfiguration configuration, ConfigurationSection section) { + return new MeasureListener( + configuration, + Material.matchMaterial(section.getString("temperature_measure")), + Material.matchMaterial(section.getString("humidity_measure")), + Material.matchMaterial(section.getString("fertility_measure")) + ); + } + + @EventHandler + public void on(PlayerInteractEvent event) { + Block block = event.getClickedBlock(); + if (event.getAction() != Action.RIGHT_CLICK_BLOCK || block == null) { + return; + } + + Player player = event.getPlayer(); + if (!player.isSneaking()) { + return; + } + + ItemStack item = event.getItem(); + if (item == null || item.isEmpty()) { + return; + } + + Material type = item.getType(); + + Climate biomeClimate = biomes.getClimate(block.getBiome()); + if (type == this.temperatureMeasure) { + double lt = biomes.getTemperature(block) + biomeClimate.temperature(); + int temperature = (int) Math.round(lt * 100); + player.sendMessage(Component.empty().color(TextColor.color(232, 41, 41)) + .append(Component.text("Temperature: ")) + .append(Component.text(temperature + "°T", NamedTextColor.RED))); + event.setUseItemInHand(Event.Result.DENY); + RealisticBiomes.getInstance().getLogger().info(player.getName() + " found " + lt + " temperature at " + block.getX() + " " + block.getY() + " " + block.getZ()); + } else if (type == this.humidityMeasure) { + double lh = biomes.getHumidity(block) + biomeClimate.humidity(); + // rescale humidity between 0% and 100% because it makes more sense that way + lh += biomes.getHumidityScale(); + lh /= 1.0 + biomes.getHumidityScale() * 2.0; + int humidity = (int) Math.round(lh * 100); + humidity = Math.max(0, Math.min(100, humidity)); + player.sendMessage(Component.empty().color(TextColor.color(55, 159, 163)) + .append(Component.text("Humidity: ")) + .append(Component.text(humidity + "%", NamedTextColor.AQUA))); + event.setUseItemInHand(Event.Result.DENY); + RealisticBiomes.getInstance().getLogger().info(player.getName() + " found " + lh + " humidity at " + block.getX() + " " + block.getY() + " " + block.getZ()); + } else if (type == this.fertilityMeasure) { + double lf = biomes.getFertility(block); + int fertility = (int) Math.round(lf * 100); + player.sendMessage(Component.empty().color(NamedTextColor.GOLD) + .append(Component.text("Soil fertility: ")) + .append(Component.text(fertility + "%", NamedTextColor.YELLOW))); + event.setUseItemInHand(Event.Result.DENY); + RealisticBiomes.getInstance().getLogger().info(player.getName() + " found " + lf + " humidity at " + block.getX() + " " + block.getY() + " " + block.getZ()); + } + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/noise/NoiseConfiguration.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/noise/NoiseConfiguration.java new file mode 100644 index 0000000000..5292b012b8 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/noise/NoiseConfiguration.java @@ -0,0 +1,20 @@ +package com.untamedears.realisticbiomes.noise; + +import org.bukkit.configuration.ConfigurationSection; + +public record NoiseConfiguration(int octaves, double frequency, double amplitude, double lacunarity, double persistence, + double scale, long seed) { + + public static NoiseConfiguration fromConfiguration(ConfigurationSection section) { + return new NoiseConfiguration( + section.getInt("octaves", 1), + section.getDouble("frequency", 1), + section.getDouble("amplitude", 1), + section.getDouble("lacunarity", 2), + section.getDouble("persistence", 2), + section.getDouble("scale", 1), + section.getLong("seed") + ); + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/noise/SetYieldCommand.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/noise/SetYieldCommand.java new file mode 100644 index 0000000000..f0f4ecbd40 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/noise/SetYieldCommand.java @@ -0,0 +1,29 @@ +package com.untamedears.realisticbiomes.noise; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import com.untamedears.realisticbiomes.breaker.BreakManager; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Map; +import net.kyori.adventure.text.Component; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; + +public class SetYieldCommand extends BaseCommand { + + private final BiomeConfiguration biomes; + + public SetYieldCommand(BiomeConfiguration biomes) { + this.biomes = biomes; + } + + @CommandAlias("rbsetyield") + @CommandPermission("realisticbiomes.rbsetyield") + public void measure(Player player, double yield) { + biomes.setYieldOverride(yield); + player.sendMessage("Set yield to " + yield); + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/noise/SimplexNoise.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/noise/SimplexNoise.java new file mode 100644 index 0000000000..bf4d9c0c86 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/noise/SimplexNoise.java @@ -0,0 +1,152 @@ +package com.untamedears.realisticbiomes.noise; + +import java.util.Random; + +public class SimplexNoise { + + private static Grad grad3[] = {new Grad(1, 1, 0), new Grad(-1, 1, 0), new Grad(1, -1, 0), new Grad(-1, -1, 0), + new Grad(1, 0, 1), new Grad(-1, 0, 1), new Grad(1, 0, -1), new Grad(-1, 0, -1), + new Grad(0, 1, 1), new Grad(0, -1, 1), new Grad(0, 1, -1), new Grad(0, -1, -1)}; + + private static int NUMBEROFSWAPS = 400; + private static short p_supply[] = {151, 160, 137, 91, 90, 15, //this contains all the numbers between 0 and 255, these are put in a random order depending upon the seed + 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, + 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, + 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, + 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, + 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, + 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, + 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, + 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, + 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, + 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, + 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, + 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180}; + + private short p[]; + private short perm[] = new short[512]; + private short permMod12[] = new short[512]; + + private static final double F2 = 0.5 * (Math.sqrt(3.0) - 1.0); + private static final double G2 = (3.0 - Math.sqrt(3.0)) / 6.0; + + public SimplexNoise(long seed) { + p = p_supply.clone(); + //the random for the swaps + Random rand = new Random(seed); + + //the seed determines the swaps that occur between the default order and the order we're actually going to use + for (int i = 0; i < NUMBEROFSWAPS; i++) { + int swapFrom = rand.nextInt(p.length); + int swapTo = rand.nextInt(p.length); + + short temp = p[swapFrom]; + p[swapFrom] = p[swapTo]; + p[swapTo] = temp; + } + + for (int i = 0; i < 512; i++) { + perm[i] = p[i & 255]; + permMod12[i] = (short) (perm[i] % 12); + } + } + + // This method is a *lot* faster than using (int)Math.floor(x) + private static int fastfloor(double x) { + int xi = (int) x; + return x < xi ? xi - 1 : xi; + } + + + private static double dot(Grad g, double x, double y) { + return g.x * x + g.y * y; + } + + // 2D simplex noise + public double noise(double xin, double yin) { + double n0, n1, n2; // Noise contributions from the three corners + // Skew the input space to determine which simplex cell we're in + double s = (xin + yin) * F2; // Hairy factor for 2D + int i = fastfloor(xin + s); + int j = fastfloor(yin + s); + double t = (i + j) * G2; + double X0 = i - t; // Unskew the cell origin back to (x,y) space + double Y0 = j - t; + double x0 = xin - X0; // The x,y distances from the cell origin + double y0 = yin - Y0; + // For the 2D case, the simplex shape is an equilateral triangle. + // Determine which simplex we are in. + int i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords + if (x0 > y0) { + i1 = 1; + j1 = 0; + } // lower triangle, XY order: (0,0)->(1,0)->(1,1) + else { + i1 = 0; + j1 = 1; + } // upper triangle, YX order: (0,0)->(0,1)->(1,1) + // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and + // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where + // c = (3-sqrt(3))/6 + double x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords + double y1 = y0 - j1 + G2; + double x2 = x0 - 1.0 + 2.0 * G2; // Offsets for last corner in (x,y) unskewed coords + double y2 = y0 - 1.0 + 2.0 * G2; + int ii = i & 255; + int jj = j & 255; + // Work out the hashed gradient indices of the three simplex corners + int gi0 = permMod12[ii + perm[jj]]; + int gi1 = permMod12[ii + i1 + perm[jj + j1]]; + int gi2 = permMod12[ii + 1 + perm[jj + 1]]; + // Calculate the contribution from the three corners + double t0 = 0.5 - x0 * x0 - y0 * y0; + if (t0 < 0) n0 = 0.0; + else { + t0 *= t0; + n0 = t0 * t0 * dot(grad3[gi0], x0, y0); // (x,y) of grad3 used for 2D gradient + } + double t1 = 0.5 - x1 * x1 - y1 * y1; + if (t1 < 0) n1 = 0.0; + else { + t1 *= t1; + n1 = t1 * t1 * dot(grad3[gi1], x1, y1); + } + double t2 = 0.5 - x2 * x2 - y2 * y2; + if (t2 < 0) n2 = 0.0; + else { + t2 *= t2; + n2 = t2 * t2 * dot(grad3[gi2], x2, y2); + } + // Add contributions from each corner to get the final noise value. + // The result is scaled to return values in the interval [-1,1]. + return 70.0 * (n0 + n1 + n2); + } + + + public double fractal(int octaves, double x, double y, double frequency, double amplitude, double lacunarity, double persistence) { + double output = 0.f; + double denom = 0.f; + + for (int i = 0; i < octaves; i++) { + output += (amplitude * noise(x * frequency, y * frequency)); + denom += amplitude; + + frequency *= lacunarity; + amplitude *= persistence; + } + + return (output / denom); + } + + // Inner class to speed upp gradient computations + // (array access is a lot slower than member access) + private static class Grad { + double x, y, z; + + Grad(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/utils/InfoStick.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/utils/InfoStick.java new file mode 100644 index 0000000000..0a5b5fe524 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/utils/InfoStick.java @@ -0,0 +1,8 @@ +package com.untamedears.realisticbiomes.utils; + +import org.bukkit.Material; + +public class InfoStick { + + public static Material INFO_STICK_TYPE = Material.STICK; +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/utils/RBUtils.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/utils/RBUtils.java new file mode 100644 index 0000000000..b51d49faa8 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/utils/RBUtils.java @@ -0,0 +1,113 @@ +package com.untamedears.realisticbiomes.utils; + +import com.untamedears.realisticbiomes.growth.ColumnPlantGrower; +import org.bukkit.Material; +import org.bukkit.TreeType; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; + +public class RBUtils { + + public static Material getFruit(Material mat) { + switch (mat) { + case MELON_SEEDS: + case MELON_STEM: + return Material.MELON; + case PUMPKIN_SEEDS: + case PUMPKIN_STEM: + return Material.PUMPKIN; + default: + return null; + } + } + + public static Block getRealPlantBlock(Block block) { + if (isColumnPlant(block.getType())) { + return ColumnPlantGrower.getRelativeBlock(block, RBUtils.getGrowthDirection(block.getType()).getOppositeFace()); + } + return block; + } + + public static int getVerticalSoilOffset(Material mat) { + if (mat == Material.COCOA) { + return -1; + } + return -2; + } + + public static boolean isColumnPlant(Material mat) { + return mat == Material.CACTUS || mat == Material.SUGAR_CANE || mat == Material.BAMBOO + || mat == Material.TWISTING_VINES || mat == Material.WEEPING_VINES + || mat == Material.TWISTING_VINES_PLANT || mat == Material.WEEPING_VINES_PLANT + || mat == Material.KELP || mat == Material.KELP_PLANT + || mat == Material.CAVE_VINES || mat == Material.CAVE_VINES_PLANT + || mat == Material.BIG_DRIPLEAF || mat == Material.BIG_DRIPLEAF_STEM; + } + + public static Material getStemMaterial(Material material) { + switch (material) { + case KELP: + return Material.KELP_PLANT; + case TWISTING_VINES: + return Material.TWISTING_VINES_PLANT; + case WEEPING_VINES: + return Material.WEEPING_VINES_PLANT; + case BAMBOO_SAPLING: + return Material.BAMBOO; + case CAVE_VINES: + return Material.CAVE_VINES_PLANT; + case BIG_DRIPLEAF: + return Material.BIG_DRIPLEAF_STEM; + default: + return material; + } + } + + public static Material getTipMaterial(Material material) { + switch (material) { + case KELP_PLANT: + return Material.KELP; + case TWISTING_VINES_PLANT: + return Material.TWISTING_VINES; + case WEEPING_VINES_PLANT: + return Material.WEEPING_VINES; + case CAVE_VINES_PLANT: + return Material.CAVE_VINES; + case BIG_DRIPLEAF_STEM: + return Material.BIG_DRIPLEAF; + default: + return material; + } + } + + public static boolean isFruit(Material mat) { + return mat == Material.PUMPKIN || mat == Material.MELON; + } + + public static BlockFace getGrowthDirection(Material material) { + switch (material) { + case TWISTING_VINES: + case TWISTING_VINES_PLANT: + case KELP: + case KELP_PLANT: + case BAMBOO_SAPLING: + case BAMBOO: + case SUGAR_CANE: + case CACTUS: + case BIG_DRIPLEAF: + case BIG_DRIPLEAF_STEM: + return BlockFace.UP; + case WEEPING_VINES: + case WEEPING_VINES_PLANT: + case CAVE_VINES: + case CAVE_VINES_PLANT: + return BlockFace.DOWN; + default: + return BlockFace.SELF; + } + } + + private RBUtils() { + + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/utils/RealisticBiomesGUI.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/utils/RealisticBiomesGUI.java new file mode 100644 index 0000000000..2caabe0e51 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/utils/RealisticBiomesGUI.java @@ -0,0 +1,128 @@ +package com.untamedears.realisticbiomes.utils; + +import com.untamedears.realisticbiomes.RealisticBiomes; +import com.untamedears.realisticbiomes.growthconfig.PlantGrowthConfig; +import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import org.apache.commons.lang3.StringUtils; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.block.Biome; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import vg.civcraft.mc.civmodcore.inventory.gui.DecorationStack; +import vg.civcraft.mc.civmodcore.inventory.gui.IClickable; +import vg.civcraft.mc.civmodcore.inventory.gui.components.ComponableInventory; +import vg.civcraft.mc.civmodcore.inventory.gui.components.ContentAligners; +import vg.civcraft.mc.civmodcore.inventory.gui.components.Scrollbar; +import vg.civcraft.mc.civmodcore.inventory.gui.components.SlotPredicates; +import vg.civcraft.mc.civmodcore.inventory.gui.components.StaticDisplaySection; +import vg.civcraft.mc.civmodcore.inventory.items.ItemUtils; +import vg.civcraft.mc.civmodcore.utilities.TextUtil; + +public class RealisticBiomesGUI { + + private final Player player; + private ComponableInventory inventory; + private static final DecimalFormat decimalFormat = new DecimalFormat("0.00"); + + public RealisticBiomesGUI(Player player) { + this.player = player; + } + + /** + * Show GUI containing all RB effected crops, sorted by their biome multiplier in current biome. + */ + public void showRBOverview(Biome biome) { + Biome currentBiome; + if (biome == null) { + currentBiome = player.getLocation().getBlock().getBiome(); + } else { + currentBiome = biome; + } + if (inventory == null) { + String biomeText = (currentBiome.toString().toLowerCase()).replace("_", " "); + biomeText = StringUtils.capitalize(biomeText); + biomeText = StringUtils.abbreviate(biomeText, 30); + inventory = new ComponableInventory(ChatColor.DARK_GRAY + biomeText, 6, player); + } else { + inventory.clear(); + } + List clicks = new LinkedList<>(); + List plantConfigs = new ArrayList<>( + RealisticBiomes.getInstance().getConfigManager().getPlantGrowthConfigs()); + plantConfigs.sort(Comparator.comparingDouble(p -> { + if (p.getBreaker() == null) { + return -1; + } else { + int maxYield = p.getBreaker().maxYield(); + return maxYield * RealisticBiomes.getInstance().getConfigManager().getBiomeConfiguration().getYield(player.getLocation().getBlock(), p.getClimate(), p.getBreaker().type(), maxYield); + } + }).reversed().thenComparing(p -> p.getItem().getType())); + for (PlantGrowthConfig plant : plantConfigs) { + Material representation = plant.getItem().getType(); + if (representation == Material.COCOA) { + representation = Material.COCOA_BEANS; + } else if (!representation.isItem()) { + representation = Material.BARRIER; + } + ItemStack is = new ItemStack(representation); + ItemUtils.setDisplayName(is, ChatColor.DARK_GREEN + plant.getName()); + List lore = new ArrayList<>(); + long timeNeeded = plant.getGrowthTime(); + lore.add(ChatColor.DARK_AQUA + "Time: " + + ChatColor.GRAY + TextUtil.formatDuration(timeNeeded, TimeUnit.MILLISECONDS)); + if (plant.getBreaker() != null) { + DecimalFormat format = new DecimalFormat("#.##"); + format.setRoundingMode(RoundingMode.DOWN); + int maxYield = plant.getBreaker().maxYield(); + double yield = RealisticBiomes.getInstance().getConfigManager().getBiomeConfiguration().getYield(player.getLocation().getBlock(), plant.getClimate(), plant.getBreaker().type(), maxYield); + lore.add(ChatColor.DARK_AQUA + "Yield: " + ChatColor.GRAY + format.format(yield * maxYield) + "x"); + } + + is.editMeta(itemMeta -> itemMeta.setEnchantmentGlintOverride(true)); + if (plant.getAllowBoneMeal() || !plant.getNeedsLight() + || !plant.getSoilBoniPerLevel().isEmpty() || plant.getMaximumSoilLayers() != 0 + || plant.getMaximumSoilBonus() != Integer.MAX_VALUE || !plant.getGreenHouseRates().isEmpty()) { + lore.add(ChatColor.DARK_GREEN + "---"); + } + if (plant.getAllowBoneMeal()) { + lore.add(ChatColor.DARK_AQUA + "Allow Bone Meal: " + ChatColor.GRAY + "true"); + } + if (!plant.getNeedsLight()) { + lore.add(ChatColor.DARK_AQUA + "Needs Light: " + ChatColor.GRAY + "false"); + } + for (Map.Entry entry : plant.getSoilBoniPerLevel().entrySet()) { + lore.add(String.format("%sSoil Bonus: %s%s (%.2f)", ChatColor.DARK_AQUA, + ChatColor.GRAY, ItemUtils.getItemName(entry.getKey()), entry.getValue())); + } + if (plant.getMaximumSoilLayers() != 0) { + lore.add(String.format("%sMax Soil Layers: %s%d", ChatColor.DARK_AQUA, + ChatColor.GRAY, plant.getMaximumSoilLayers())); + } + if (plant.getMaximumSoilBonus() < Integer.MAX_VALUE) { + lore.add(String.format("%sMax Soil Bonus: %s%.2f", ChatColor.DARK_AQUA, + ChatColor.GRAY, plant.getMaximumSoilBonus())); + } + for (Map.Entry entry : plant.getGreenHouseRates().entrySet()) { + lore.add(String.format("%sGreen House Rate: %s%s (%.2f)", ChatColor.DARK_AQUA, + ChatColor.GRAY, ItemUtils.getItemName(entry.getKey()), entry.getValue())); + } + ItemUtils.addLore(is, lore); + IClickable click = new DecorationStack(is); + clicks.add(click); + } + Scrollbar middleBar = new Scrollbar( + clicks, 45, 5, ContentAligners.getCenteredInOrder(clicks.size(), 45, 9)); + inventory.addComponent(middleBar, SlotPredicates.rows(5)); + StaticDisplaySection bottomLine = new StaticDisplaySection(9); + inventory.addComponent(bottomLine, SlotPredicates.rows(1)); + inventory.show(); + } +} diff --git a/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/utils/SchematicUtils.java b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/utils/SchematicUtils.java new file mode 100644 index 0000000000..76b2c7df32 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/java/com/untamedears/realisticbiomes/utils/SchematicUtils.java @@ -0,0 +1,51 @@ +package com.untamedears.realisticbiomes.utils; + +import com.sk89q.worldedit.extent.clipboard.Clipboard; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats; +import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader; +import com.untamedears.realisticbiomes.model.RBSchematic; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +public class SchematicUtils { + + public static List loadAll(File folder, Logger logger) { + List result = new ArrayList<>(); + if (!folder.isDirectory()) { + logger.warning("No schematic directory provided"); + return result; + } + for (File subFile : folder.listFiles()) { + if (subFile.isDirectory()) { + result.addAll(loadAll(subFile, logger)); + continue; + } + if (!subFile.getName().endsWith(".schematic")) { + continue; + } + Clipboard board; + try { + board = loadSchematic(subFile); + } catch (IOException e) { + logger.severe("Failed to load schematic " + e.toString()); + continue; + } + String name = subFile.getName().replace(".schematic", ""); + result.add(new RBSchematic(name, board)); + } + return result; + } + + public static Clipboard loadSchematic(File file) throws IOException { + ClipboardFormat format = ClipboardFormats.findByFile(file); + try (ClipboardReader reader = format.getReader(new FileInputStream(file))) { + return reader.read(); + } + } + +} diff --git a/plugins/realisticbiomes2-paper/src/main/resources/config.yml b/plugins/realisticbiomes2-paper/src/main/resources/config.yml new file mode 100644 index 0000000000..6701808571 --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/resources/config.yml @@ -0,0 +1,885 @@ +# Set to true if you want tapping a plant with bonemeal to double it. +allow_tallplant_replication: true + +#Enable auto replant? +auto_replant_enabled: true +# true for right click auto replant, false for left click +auto_replant_right_click: true + +no_bonemeal_blocks: + - SEA_PICKLE + - KELP + - TWISTING_VINES + - WEEPING_VINES + - CAVE_VINES + - BAMBOO + - KELP_PLANT + - TWISTING_VINES_PLANT + - WEEPING_VINES_PLANT + - CAVE_VINES_PLANT + - BAMBOO_SAPLING + - SWEET_BERRY_BUSH + - BIG_DRIPLEAF + - BIG_DRIPLEAF_STEM + - RED_MUSHROOM + - BROWN_MUSHROOM + - WHEAT + - CARROTS + - POTATOES + - BEETROOTS + - NETHER_WART + - COCOA + - MELON_STEM + - PUMPKIN_STEM + - CACTUS + - SUGAR_CANE + - OAK_SAPLING + - BIRCH_SAPLING + - SPRUCE_SAPLING + - JUNGLE_SAPLING + - DARK_OAK_SAPLING + - ACACIA_SAPLING + - FLOWERING_AZALEA + + # categories of biomes +biome_aliases: + water: + - DEEP_OCEAN + - OCEAN + - STONE_SHORE + freshwater: + - RIVER + - BEACH + cold_water: + - SNOWY_BEACH + - FROZEN_OCEAN + - FROZEN_RIVER + temperate: + - PLAINS + - SUNFLOWER_PLAINS + - SAVANNA + - SAVANNA_PLATEAU + - SHATTERED_SAVANNA + - SHATTERED_SAVANNA_PLATEAU + foresty: + - BIRCH_FOREST + - BIRCH_FOREST_HILLS + - DARK_FOREST + - DARK_FOREST_HILLS + - FLOWER_FOREST + - FOREST + - TALL_BIRCH_FOREST + - TALL_BIRCH_HILLS + - WOODED_HILLS + cold: + - ICE_SPIKES + - GIANT_SPRUCE_TAIGA + - GIANT_SPRUCE_TAIGA_HILLS + - GIANT_TREE_TAIGA + - GIANT_TREE_TAIGA_HILLS + - TAIGA + - TAIGA_HILLS + - TAIGA_MOUNTAINS + - SNOWY_TUNDRA + - SNOWY_MOUNTAINS + - SNOWY_TAIGA + - SNOWY_TAIGA_HILLS + - SNOWY_TAIGA_MOUNTAINS + cold_forest: + - GIANT_SPRUCE_TAIGA + - GIANT_SPRUCE_TAIGA_HILLS + - GIANT_TREE_TAIGA + - GIANT_TREE_TAIGA_HILLS + - TAIGA + - TAIGA_HILLS + - TAIGA_MOUNTAINS + hot_dry: + - DESERT + - DESERT_HILLS + - DESERT_LAKES + - BADLANDS + - BADLANDS_PLATEAU + - ERODED_BADLANDS + - WOODED_BADLANDS_PLATEAU + hot_rainy: + - JUNGLE + - JUNGLE_EDGE + - JUNGLE_HILLS + - MODIFIED_JUNGLE + - MODIFIED_JUNGLE_EDGE + mountain: + - MOUNTAINS + - MOUNTAIN_EDGE + - GRAVELLY_MOUNTAINS + - MODIFIED_GRAVELLY_MOUNTAINS + - WOODED_MOUNTAINS + mesa: + - BADLANDS + - BADLANDS_PLATEAU + - ERODED_BADLANDS + - WOODED_BADLANDS_PLATEAU + - MODIFIED_BADLANDS_PLATEAU + - MODIFIED_WOODED_BADLANDS_PLATEAU + mushroom: + - MUSHROOM_FIELDS + - MUSHROOM_FIELD_SHORE + swampy: + - SWAMP + - SWAMP_HILLS + nether: + - NETHER_WASTES + - SOUL_SAND_VALLEY + - CRIMSON_FOREST + - WARPED_FOREST + - BASALT_DELTAS + end: + - END_BARRENS + - END_HIGHLANDS + - END_MIDLANDS + - THE_VOID + - THE_END + +temperature: + octaves: 3 + frequency: 0.5 + amplitude: 1 + lacunarity: 2 + persistence: 0.5 + scale: 0.5 + seed: 1 + +humidity: + octaves: 3 + frequency: 1 + amplitude: 1 + lacunarity: 2 + persistence: 0.5 + scale: 0.5 + seed: 2 + +fertility_seed: 3 +fertility_power: 1 +fertility_scale: 1 + +temperature_measure: CLOCK +humidity_measure: LIGHTNING_ROD +fertility_measure: BRUSH + +yield_power_factor: 3 + +climate: + - biomes: + - temperate + - foresty + temperature: 0.5 + humidity: 0.5 + - biomes: + - hot_dry + - mesa + temperature: 0.75 + humidity: 0.25 + - biomes: + - mountain + - cold + temperature: 0.25 + humidity: 0.25 + +farm_beacon_key: "heliodor:farm_beacon" +farm_beacon_range: 20 +farm_beacon_fertility: 0.05 +farm_beacon_growth_multiplier: 1 +farm_beacon_warmup: 518400000 + +plants: + + # formerly inheriting CROP properties + WHEAT: + persistent_growth_period: 3d + name: Wheat + id: 1 + item: + ==: org.bukkit.inventory.ItemStack + type: WHEAT_SEEDS + v: 1 + grower: + type: ageable + material: WHEAT + max_stage: 7 + increment: 1 + vanilla_materials: + - WHEAT + soil_max_layers: 8 + max_soil_bonus: 2 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: WHEAT + max_yield: 64 + soil_boni: + CLAY: 0.5 + DIRT: 0.1 + + CARROT: + persistent_growth_period: 3d + name: Carrots + id: 2 + item: + ==: org.bukkit.inventory.ItemStack + type: CARROT + v: 1 + grower: + type: ageable + material: CARROTS + max_stage: 7 + increment: 3 + vanilla_materials: + - CARROTS + soil_max_layers: 4 + soil_boni: + CLAY: 0.5 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: CARROT + max_yield: 64 + + POTATO: + persistent_growth_period: 3d + name: Potatoes + id: 3 + item: + ==: org.bukkit.inventory.ItemStack + type: POTATO + v: 1 + grower: + type: ageable + material: POTATOES + max_stage: 7 + increment: 3 + vanilla_materials: + - POTATOES + soil_max_layers: 4 + soil_boni: + CLAY: 0.5 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: POTATO + max_yield: 64 + + BEETROOT: + persistent_growth_period: 3d + name: Beetroots + id: 4 + item: + ==: org.bukkit.inventory.ItemStack + type: BEETROOT_SEEDS + v: 1 + grower: + type: ageable + material: BEETROOTS + max_stage: 3 + increment: 1 + vanilla_materials: + - BEETROOTS + soil_max_layers: 4 + soil_boni: + CLAY: 0.5 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: BEETROOT + max_yield: 64 + + NETHER_WART: + persistent_growth_period: 16d + name: Nether wart + id: 5 + item: + ==: org.bukkit.inventory.ItemStack + type: NETHER_WART + v: 1 + grower: + type: ageable + material: NETHER_WART + max_stage: 3 + increment: 1 + vanilla_materials: + - NETHER_WART + needs_sun_light: false + soil_max_layers: 4 + soil_boni: + SOUL_SAND: 0.5 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: NETHER_WART + max_yield: 64 + + COCOA: + persistent_growth_period: 24d + name: Cocoa + id: 6 + item: + ==: org.bukkit.inventory.ItemStack + type: COCOA_BEANS + v: 1 + grower: + type: ageable + material: COCOA + max_stage: 2 + increment: 1 + vanilla_materials: + - COCOA + soil_max_layers: 25 + soil_boni: + VINE: 0.3 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: COCOA + max_yield: 64 + + MELON: + persistent_growth_period: 0 + name: Melon + id: 7 + item: + ==: org.bukkit.inventory.ItemStack + type: MELON + v: 2584 + can_be_planted: false + soil_max_layers: 4 + soil_boni: + CLAY: 0.5 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: MELON + max_yield: 64 + grower: + type: fruit + material: MELON + stem_type: MELON_STEM + attached_stem_type: ATTACHED_MELON_STEM + vanilla_materials: + - ATTACHED_MELON_STEM + - MELON_STEM + + MELON_STEM: + persistent_growth_period: 0 + name: Melon stem + id: 8 + item: + ==: org.bukkit.inventory.ItemStack + type: MELON_SEEDS + v: 1 + grower: + type: stem + material: MELON_STEM + fruit_config: Melon + soil_max_layers: 4 + soil_boni: + CLAY: 0.5 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + + PUMPKIN: + persistent_growth_period: 0 + name: Pumpkin + id: 9 + can_be_planted: false + item: + ==: org.bukkit.inventory.ItemStack + type: PUMPKIN + v: 1 + grower: + type: fruit + material: PUMPKIN + stem_type: PUMPKIN_STEM + attached_stem_type: ATTACHED_PUMPKIN_STEM + vanilla_materials: + - ATTACHED_PUMPKIN_STEM + - PUMPKIN_STEM + soil_max_layers: 4 + soil_boni: + CLAY: 0.5 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + max_yield: 64 + + PUMPKIN_STEM: + persistent_growth_period: 0 + name: Pumpkin stem + grower: + type: stem + material: PUMPKIN_STEM + fruit_config: Pumpkin + id: 10 + item: + ==: org.bukkit.inventory.ItemStack + type: PUMPKIN_SEEDS + v: 1 + soil_max_layers: 4 + soil_boni: + CLAY: 0.5 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + + # formerly inheriting Column properties + + CACTUS: + persistent_growth_period: 6h + name: Cactus + grower: + type: column + material: CACTUS + max_height: 3 + insta_break_touching: false + vanilla_materials: + - CACTUS + id: 11 + item: + ==: org.bukkit.inventory.ItemStack + type: CACTUS + v: 1 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: CACTUS + max_yield: 64 + + SUGAR_CANE: + persistent_growth_period: 6d + name: Sugar cane + id: 12 + grower: + type: column + material: SUGAR_CANE + max_height: 3 + vanilla_materials: + - SUGAR_CANE + item: + ==: org.bukkit.inventory.ItemStack + type: SUGAR_CANE + v: 1 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: SUGAR_CANE + max_yield: 64 + + # formerly inheriting "basetree" properties + + OAK_SAPLING: + persistent_growth_period: 6d + name: Oak Tree + id: 13 + grower: + type: tree + material: OAK_SAPLING + vanilla_materials: + - OAK_SAPLING + item: + ==: org.bukkit.inventory.ItemStack + type: OAK_SAPLING + v: 1 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: OAK_LEAVES + max_yield: 64 + + BIRCH_SAPLING: + persistent_growth_period: 6d + name: Birch Tree + id: 14 + grower: + type: tree + material: BIRCH_SAPLING + vanilla_materials: + - BIRCH_SAPLING + item: + ==: org.bukkit.inventory.ItemStack + type: BIRCH_SAPLING + v: 1 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: BIRCH_LEAVES + max_yield: 64 + + SPRUCE_SAPLING: + persistent_growth_period: 6d + name: Spruce Tree + id: 15 + grower: + type: tree + material: SPRUCE_SAPLING + vanilla_materials: + - SPRUCE_SAPLING + item: + ==: org.bukkit.inventory.ItemStack + type: SPRUCE_SAPLING + v: 1 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: SPRUCE_LEAVES + max_yield: 64 + + JUNGLE_SAPLING: + persistent_growth_period: 6d + name: Jungle Tree + id: 16 + grower: + type: tree + material: JUNGLE_SAPLING + vanilla_materials: + - JUNGLE_SAPLING + item: + ==: org.bukkit.inventory.ItemStack + type: JUNGLE_SAPLING + v: 1 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: JUNGLE_LEAVES + max_yield: 128 + + ACACIA_SAPLING: + persistent_growth_period: 6d + name: Acacia Tree + grower: + type: tree + material: ACACIA_SAPLING + vanilla_materials: + - ACACIA_SAPLING + id: 17 + item: + ==: org.bukkit.inventory.ItemStack + type: ACACIA_SAPLING + v: 1 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: ACACIA_LEAVES + max_yield: 128 + + DARK_OAK_SAPLING: + persistent_growth_period: 6d + name: Dark Oak Tree + grower: + type: tree + material: DARK_OAK_SAPLING + vanilla_materials: + - DARK_OAK_SAPLING + id: 18 + item: + ==: org.bukkit.inventory.ItemStack + type: DARK_OAK_SAPLING + v: 1 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: DARK_OAK_LEAVES + max_yield: 192 + + PALE_OAK_SAPLING: + persistent_growth_period: 6d + name: Pale Oak Tree + grower: + type: tree + material: PALE_OAK_SAPLING + vanilla_materials: + - PALE_OAK_SAPLING + id: 19 + item: + ==: org.bukkit.inventory.ItemStack + type: PALE_OAK_SAPLING + v: 1 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: PALE_OAK_LEAVES + max_yield: 64 + + CHERRY_SAPLING: + persistent_growth_period: 0h + name: Cherry Tree + grower: + type: tree + material: CHERRY_SAPLING + vanilla_materials: + - CHERRY_SAPLING + id: 20 + item: + ==: org.bukkit.inventory.ItemStack + type: CHERRY_SAPLING + v: 1 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: CHERRY_LEAVES + max_yield: 64 + + MANGROVE_PROPAGULE: + persistent_growth_period: 0h + name: Mangrove Tree + grower: + type: tree + material: MANGROVE_PROPAGULE + vanilla_materials: + - MANGROVE_PROPAGULE + id: 21 + item: + ==: org.bukkit.inventory.ItemStack + type: MANGROVE_PROPAGULE + v: 1 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: MANGROVE_LEAVES + max_yield: 64 + + GLOW_BERRIES: + persistent_growth_period: 6d + name: Glow Berries + grower: + type: tip + material: CAVE_VINES + stem_material: CAVE_VINES_PLANT + growth_direction: DOWN + max_height: 25 + id: 22 + item: + ==: org.bukkit.inventory.ItemStack + type: GLOW_BERRIES + v: 2730 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: GLOW_BERRIES + max_yield: 64 + + TWISTING_VINES: + persistent_growth_period: 6d + name: Twisting Vines + grower: + type: tip + material: TWISTING_VINES + stem_material: TWISTING_VINES_PLANT + growth_direction: UP + max_height: 25 + id: 23 + item: + ==: org.bukkit.inventory.ItemStack + type: TWISTING_VINES + v: 2730 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: TWISTING_VINES + max_yield: 64 + + WEEPING_VINES: + persistent_growth_period: 6d + name: Weeping Vines + grower: + type: tip + material: WEEPING_VINES + stem_material: WEEPING_VINES_PLANT + growth_direction: DOWN + max_height: 25 + id: 24 + item: + ==: org.bukkit.inventory.ItemStack + type: WEEPING_VINES + v: 2730 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: WEEPING_VINES + max_yield: 64 + + KELP: + persistent_growth_period: 6d + name: Kelp + grower: + type: kelp + max_height: 25 + id: 25 + item: + ==: org.bukkit.inventory.ItemStack + type: KELP + v: 2730 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: KELP + max_yield: 64 + + RED_MUSHROOM: + persistent_growth_period: 12d + name: Red Mushroom + grower: + type: horizontalspread + max_amount: 81 + max_range: 9 + replaceable_blocks: + - AIR + - CAVE_AIR + - VOID_AIR + valid_soil: + - STONE + - MYCELIUM + - PODZOL + id: 26 + item: + ==: org.bukkit.inventory.ItemStack + type: RED_MUSHROOM + v: 2730 + needs_sun_light: false + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: RED_MUSHROOM + max_yield: 64 + + BROWN_MUSHROOM: + persistent_growth_period: 12d + name: Brown Mushroom + grower: + type: horizontalspread + max_amount: 81 + max_range: 9 + replaceable_blocks: + - AIR + - CAVE_AIR + - VOID_AIR + valid_soil: + - STONE + - MYCELIUM + - PODZOL + id: 27 + item: + ==: org.bukkit.inventory.ItemStack + type: BROWN_MUSHROOM + v: 2730 + needs_sun_light: false + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: BROWN_MUSHROOM + max_yield: 64 + + CRIMSON_FUNGUS: + persistent_growth_period: 12d + name: Crimson Fungus + grower: + type: fungus + material: CRIMSON_FUNGUS + vanilla_materials: + - CRIMSON_FUNGUS + id: 28 + item: + ==: org.bukkit.inventory.ItemStack + type: CRIMSON_FUNGUS + v: 2730 + needs_sun_light: false + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: CRIMSON_FUNGUS + max_yield: 64 + + WARPED_FUNGUS: + persistent_growth_period: 12d + name: Warped Fungus + grower: + type: fungus + material: WARPED_FUNGUS + vanilla_materials: + - WARPED_FUNGUS + id: 29 + item: + ==: org.bukkit.inventory.ItemStack + type: WARPED_FUNGUS + v: 2730 + needs_sun_light: false + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: WARPED_FUNGUS + max_yield: 64 + + BAMBOO: + persistent_growth_period: 12d + name: Bamboo + grower: + type: bamboo + max_height: 12 + id: 30 + item: + ==: org.bukkit.inventory.ItemStack + type: BAMBOO + v: 2730 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: BAMBOO + max_yield: 64 + + SWEET_BERRY: + persistent_growth_period: 12d + name: Sweet Berry + id: 31 + item: + ==: org.bukkit.inventory.ItemStack + type: SWEET_BERRIES + v: 2730 + grower: + type: ageable + material: SWEET_BERRY_BUSH + max_stage: 3 + increment: 1 + vanilla_materials: + - SWEET_BERRIES + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: SWEET_BERRY_BUSH + max_yield: 64 + + BIG_DRIPLEAF: + persistent_growth_period: 12d + name: Big Dripleaf + grower: + type: tip + material: BIG_DRIPLEAF + stem_material: BIG_DRIPLEAF_STEM + growth_direction: UP + max_height: 25 + id: 32 + item: + ==: org.bukkit.inventory.ItemStack + type: BIG_DRIPLEAF + v: 2730 + ideal_temperature: 0.5 + ideal_humidity: 0.5 + breaker: + type: BIG_DRIPLEAF + max_yield: 64 + +database: + ==: vg.civcraft.mc.civmodcore.dao.DatabaseCredentials + plugin: RealisticBiomes + user: 'root' + password: 'root' + host: 'localhost' + port: 3306 + database: 'database' + poolsize: 5 + connection_timeout: 10000 + idle_timeout: 600000 + max_lifetime: 7200000 diff --git a/plugins/realisticbiomes2-paper/src/main/resources/plugin.yml b/plugins/realisticbiomes2-paper/src/main/resources/plugin.yml new file mode 100644 index 0000000000..9fe57a48dd --- /dev/null +++ b/plugins/realisticbiomes2-paper/src/main/resources/plugin.yml @@ -0,0 +1,26 @@ +name: RealisticBiomes +main: com.untamedears.realisticbiomes.RealisticBiomes +depend: + - CivModCore +version: ${version} +authors: + - mgrandi + - gispy-king + - jacobcole2000 + - WildWeazel + - ProgrammerDan + - Numrufus + - Erocs + - danquist + - pruby + - Maxopoly + - BlackXnt +api-version: 1.21.3 +description: RealisticBiomes makes farming harder by limiting growth rates of crops and making them only grow well in certain biomes. +permissions: + rb.public: + default: true + rb.op: + default: op + rb.pickBiome: + default: op diff --git a/plugins/simpleadminhacks-paper/build.gradle.kts b/plugins/simpleadminhacks-paper/build.gradle.kts index 53a11e5f7e..dc4276170d 100644 --- a/plugins/simpleadminhacks-paper/build.gradle.kts +++ b/plugins/simpleadminhacks-paper/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("io.papermc.paperweight.userdev") + alias(libs.plugins.paper.userdev) } version = "2.3.2" @@ -16,6 +16,7 @@ dependencies { compileOnly(project(":plugins:banstick-paper")) compileOnly(project(":plugins:bastion-paper")) compileOnly(project(":plugins:exilepearl-paper")) + compileOnly("me.clip:placeholderapi:2.11.6") compileOnly(libs.protocollib) } diff --git a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/configs/InvControlConfig.java b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/configs/InvControlConfig.java deleted file mode 100644 index 1f4d775e96..0000000000 --- a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/configs/InvControlConfig.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.programmerdan.minecraft.simpleadminhacks.configs; - -import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks; -import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHackConfig; -import org.bukkit.configuration.ConfigurationSection; - -/** - * Stub; this hack just turns on and off, and has some commands. - * - * @author ProgrammerDan - */ -public class InvControlConfig extends SimpleHackConfig { - - public InvControlConfig(SimpleAdminHacks plugin, ConfigurationSection base) { - super(plugin, base); - } - - @Override - protected void wireup(ConfigurationSection config) { - } -} diff --git a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/CreeperDiscHack.java b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/CreeperDiscHack.java index babdc9f3d6..96c50ce91d 100644 --- a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/CreeperDiscHack.java +++ b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/CreeperDiscHack.java @@ -12,7 +12,6 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.inventory.ItemStack; -import vg.civcraft.mc.civmodcore.inventory.items.SpawnEggUtils; import java.util.Iterator; import java.util.List; diff --git a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/DisableAI.java b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/DisableAI.java deleted file mode 100644 index 563b9481e0..0000000000 --- a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/DisableAI.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.programmerdan.minecraft.simpleadminhacks.hacks; - -import com.destroystokyo.paper.event.entity.EntityAddToWorldEvent; -import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks; -import com.programmerdan.minecraft.simpleadminhacks.configs.DisableAIConfig; -import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHack; -import net.minecraft.nbt.CompoundTag; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer; -import org.bukkit.entity.LivingEntity; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.HandlerList; -import org.bukkit.event.Listener; - -public final class DisableAI extends SimpleHack implements Listener { - - private static final String HAS_BEEN_DISABLED_KEY = "SAH_AI_DISABLED"; - - public DisableAI(final SimpleAdminHacks plugin, final DisableAIConfig config) { - super(plugin, config); - } - - public static DisableAIConfig generate(final SimpleAdminHacks plugin, final ConfigurationSection config) { - return new DisableAIConfig(plugin, config); - } - - @Override - public void onEnable() { - plugin().registerListener(this); - } - - @Override - public void onDisable() { - HandlerList.unregisterAll(this); - config().reset(); - } - - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void disableModAI(final EntityAddToWorldEvent event) { - final var entity = event.getEntity(); - if (!(entity instanceof LivingEntity)) { - return; - } - final var livingEntity = (LivingEntity) entity; - final var nbt = new CompoundTag(((CraftPersistentDataContainer) entity.getPersistentDataContainer()).getRaw()) { - }; - if (!config().isLimitingEntityAI(entity.getType(), entity.getEntitySpawnReason())) { - // If the entity was disabled before, re-enable it - if (nbt.contains(HAS_BEEN_DISABLED_KEY)) { - livingEntity.setAI(nbt.getBoolean(HAS_BEEN_DISABLED_KEY)); - nbt.remove(HAS_BEEN_DISABLED_KEY); - } - return; - } - // If the entity has already been disabled, leave alone - if (nbt.contains(HAS_BEEN_DISABLED_KEY)) { - return; - } - nbt.putBoolean(HAS_BEEN_DISABLED_KEY, livingEntity.hasAI()); - livingEntity.setAI(false); - } - -} diff --git a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/InvControl.java b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/InvControl.java deleted file mode 100644 index 81b05558e7..0000000000 --- a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/InvControl.java +++ /dev/null @@ -1,224 +0,0 @@ -package com.programmerdan.minecraft.simpleadminhacks.hacks; - -import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks; -import com.programmerdan.minecraft.simpleadminhacks.configs.InvControlConfig; -import com.programmerdan.minecraft.simpleadminhacks.framework.SimpleHack; -import java.util.HashSet; -import java.util.Set; -import java.util.UUID; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.world.level.storage.PlayerDataStorage; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Material; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.craftbukkit.CraftServer; -import org.bukkit.craftbukkit.inventory.CraftInventoryPlayer; -import org.bukkit.entity.Player; -import org.bukkit.event.Listener; -import org.bukkit.event.inventory.InventoryCloseEvent; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; -import org.bukkit.inventory.meta.ItemMeta; -import vg.civcraft.mc.namelayer.NameAPI; - -public class InvControl extends SimpleHack implements CommandExecutor, Listener { - - public static final String NAME = "InvControl"; - - private boolean hasNameAPI; - - private Set adminsWithInv; - - public InvControl(SimpleAdminHacks plugin, InvControlConfig config) { - super(plugin, config); - } - - @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { - if (args.length < 1) { - return false; - } - String playername = args[0]; - UUID playerUID = null; - Player player = null; - if (this.hasNameAPI) { - plugin().debug("Using NameAPI to look up {0}", playername); - playerUID = NameAPI.getUUID(playername); - plugin().debug("Found UUID match: {0}", playerUID); - player = Bukkit.getPlayer(playerUID); - } - if (player == null) { // no NameAPI or failure. - plugin().debug("Using Bukkit byname to look up {0}", playername); - player = Bukkit.getPlayer(playername); - } - if (player == null) { // By name failed... is it a UUID? - try { - plugin().debug("Using Bukkit by UUID to look up {0}", playername); - player = Bukkit.getPlayer(UUID.fromString(playername)); - playerUID = UUID.fromString(playername); - } catch (IllegalArgumentException iae) { - player = null; - } - } - if (player == null && playerUID != null) { // Go deep into NBT. - @SuppressWarnings("resource") - PlayerDataStorage storage = ((CraftServer) plugin().getServer()).getServer().playerDataStorage; - CompoundTag rawPlayer = storage.load(playername, playerUID.toString()).orElse(null); - - if (rawPlayer != null) { - plugin().debug("Player {0} found in NBT data, read-only access enabled.", playername); - sender.sendMessage("Player found via alternate lookup, read-only access enabled."); - } else { - sender.sendMessage("Player " + playername + " does not exist or cannot be opened."); - return false; - } - - float health = rawPlayer.getFloat("Health"); - int food = rawPlayer.getInt("foodLevel"); - - // Fun NMS inventory reconstruction from file data. - net.minecraft.world.entity.player.Inventory nms_pl_inv = new net.minecraft.world.entity.player.Inventory(null); - ListTag inv = rawPlayer.getList("Inventory", rawPlayer.getId()); - nms_pl_inv.load(inv); // We use this to bypass the Craft code which requires a player object, unlike NMS. - PlayerInventory pl_inv = new CraftInventoryPlayer(nms_pl_inv); - - invSee(sender, pl_inv, health, food, playername); - return true; - } - if (player == null) { - sender.sendMessage("Player " + playername + " does not exist or cannot be opened."); - return false; - } - if (command.getName().equalsIgnoreCase("invsee")) { // see - if (player.equals(sender)) { - sender.sendMessage(ChatColor.RED + " Just open your inventory, silly."); - return true; - } - final PlayerInventory pl_inv = player.getInventory(); - invSee(sender, pl_inv, player.getHealth(), player.getFoodLevel(), playername); - } else if (command.getName().equalsIgnoreCase("invmod")) { // mod - if (!(sender instanceof Player)) { // send text only. - sender.sendMessage(ChatColor.RED + "Apologies, this is only for in-game operators"); - } else { - Player admin = (Player) sender; - - if (admin.equals(player)) { - sender.sendMessage(ChatColor.RED + "You cannot modify your own inventory in this manner."); - } else { - sender.sendMessage(ChatColor.RED + "Feature not yet implemented"); - //this.adminsWithInv.add(admin.getUniqueId()); - } - } - } else { - return false; - } - return true; - } - - public void adminCloseInventory(InventoryCloseEvent event) { - if (this.adminsWithInv.contains(event.getPlayer().getUniqueId())) { - this.adminsWithInv.remove(event.getPlayer().getUniqueId()); - } - } - - private void invSee(CommandSender sender, PlayerInventory pl_inv, double health, int food, String playername) { - if (!(sender instanceof Player)) { // send text only. - StringBuilder sb = new StringBuilder(); - sb.append(playername).append("'s\n Health: ").append((int) health * 2); - sb.append("\n Food: ").append(food); - sb.append("\n Inventory: "); - sb.append("\n Offhand: ").append(pl_inv.getItemInOffHand()); - sb.append("\n Helmet: ").append(pl_inv.getHelmet()); - sb.append("\n Chest: ").append(pl_inv.getChestplate()); - sb.append("\n Legs: ").append(pl_inv.getLeggings()); - sb.append("\n Feet: ").append(pl_inv.getBoots()); - for (int slot = 0; slot < 36; slot++) { - final ItemStack it = pl_inv.getItem(slot); - sb.append("\n ").append(slot).append(":").append(it).append(ChatColor.RESET); - } - sender.sendMessage(sb.toString()); - } else { - Player admin = (Player) sender; - Inventory inv = Bukkit.createInventory( - admin, 45, playername + "'s Inventory"); - for (int slot = 0; slot < 36; slot++) { - final ItemStack it = pl_inv.getItem(slot); - inv.setItem(slot, it); - } - inv.setItem(36, pl_inv.getItemInOffHand()); - inv.setItem(38, pl_inv.getHelmet()); - inv.setItem(39, pl_inv.getChestplate()); - inv.setItem(40, pl_inv.getLeggings()); - inv.setItem(41, pl_inv.getBoots()); - ItemStack ihealth = new ItemStack(Material.APPLE, (int) health * 2); - ItemMeta hdata = ihealth.getItemMeta(); - hdata.setDisplayName("Player Health"); - ihealth.setItemMeta(hdata); - inv.setItem(43, ihealth); - ItemStack hunger = new ItemStack(Material.COOKED_BEEF, food); - hdata = hunger.getItemMeta(); - hdata.setDisplayName("Player Hunger"); - hunger.setItemMeta(hdata); - inv.setItem(44, hunger); - admin.openInventory(inv); - admin.updateInventory(); - } - } - - @Override - public void registerListeners() { - - } - - @Override - public void registerCommands() { - if (config.isEnabled()) { - plugin().log("Registering invsee and invmod commands"); - plugin().registerCommand("invsee", this); - plugin().registerCommand("invmod", this); - } - } - - @Override - public void dataBootstrap() { - if (config.isEnabled()) { - if (plugin().serverHasPlugin("NameLayer")) { - this.hasNameAPI = true; - } else { - this.hasNameAPI = false; - } - this.adminsWithInv = new HashSet(); - } - } - - @Override - public void unregisterListeners() { - } - - @Override - public void unregisterCommands() { - } - - @Override - public void dataCleanup() { - } - - @Override - public String status() { - if (config.isEnabled()) { - return "Listening for invsee and invmod commands"; - } else { - return "Inventory Control disabled."; - } - } - - public static InvControlConfig generate(SimpleAdminHacks plugin, ConfigurationSection config) { - return new InvControlConfig(plugin, config); - } -} diff --git a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/ArthropodEggHack.java b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/ArthropodEggHack.java index 156a3a2d49..acc8469770 100644 --- a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/ArthropodEggHack.java +++ b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/ArthropodEggHack.java @@ -6,13 +6,14 @@ import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad; import java.util.List; import java.util.Map; +import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Ageable; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.entity.EntityDeathEvent; import org.bukkit.inventory.ItemStack; -import vg.civcraft.mc.civmodcore.inventory.items.SpawnEggUtils; public class ArthropodEggHack extends BasicHack { @@ -73,7 +74,12 @@ public void onEntityDeath(EntityDeathEvent event) { // Check if egg should be spawned if (randomNum < targetPercentage) { - ItemStack item = new ItemStack(SpawnEggUtils.getSpawnEgg(event.getEntityType()), 1); + final Material spawnEggMaterial = Bukkit.getItemFactory().getSpawnEgg(event.getEntityType()); + if (spawnEggMaterial == null) { + this.logger.warning("Could not get the spawn-egg type for [" + event.getEntityType() + "]!"); + return; + } + ItemStack item = ItemStack.of(spawnEggMaterial, 1); if (removeDrops) { event.getDrops().clear(); event.setDroppedExp(0); diff --git a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/BeeKeeping.java b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/BeeKeeping.java index c43fcc63d6..a6b16d04fb 100644 --- a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/BeeKeeping.java +++ b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/BeeKeeping.java @@ -113,7 +113,7 @@ public void attackedByBee(final EntityDamageByEntityEvent event) { // Reset the above values bee.setLastHurtByMob(lastDamageCause); bee.setPersistentAngerTarget(angerTarget); - bee.setTarget(goalTarget, EntityTargetEvent.TargetReason.FORGOT_TARGET, false); // Params: target, cause, emit event + bee.setTarget(goalTarget, EntityTargetEvent.TargetReason.FORGOT_TARGET); // Params: target, cause, emit event bee.setRemainingPersistentAngerTime(angerLevel); }); } @@ -196,7 +196,7 @@ private static BeehiveBlockEntity getBeeHive(@NotNull final Block block) { } private static List getBeesFromHive(@NotNull final BeehiveBlockEntity hive) { - List bees = hive.components().get(DataComponents.BEES); + List bees = hive.components().get(DataComponents.BEES).bees(); return bees == null ? null : bees.stream().map(bee -> bee.entityData().copyTag()).map(BeeData::new).collect(Collectors.toCollection(ArrayList::new)); } @@ -207,7 +207,7 @@ private static final class BeeData { public BeeData(@NotNull final CompoundTag nbt) { // Parse name - final String rawName = nbt.getString(BEE_NAME_KEY); + final String rawName = nbt.getString(BEE_NAME_KEY).get(); if (Strings.isNullOrEmpty(rawName)) { this.name = null; } else { diff --git a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/CopperRail.java b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/CopperRail.java index 0db3634468..bb0d1a912c 100644 --- a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/CopperRail.java +++ b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/CopperRail.java @@ -108,7 +108,7 @@ public void on(VehicleMoveEvent event) { copper.getNext(state).ifPresent((iblockdata2) -> { try { formingBlock = true; - CraftEventFactory.handleBlockFormEvent(level, craftBlock.getPosition(), iblockdata2); + CraftEventFactory.handleBlockFormEvent(level, craftBlock.getPosition(), iblockdata2, 3); } finally { formingBlock = false; } diff --git a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/DebugWand.java b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/DebugWand.java index e9e4ceefa3..6a949c09d9 100644 --- a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/DebugWand.java +++ b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/DebugWand.java @@ -8,7 +8,8 @@ import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack; import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig; import java.util.Objects; -import net.minecraft.nbt.CompoundTag; +import net.minecraft.util.ProblemReporter; +import net.minecraft.world.level.storage.TagValueOutput; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.Material; @@ -118,10 +119,10 @@ public void onWandUsageOnEntity(final PlayerInteractEntityEvent event) { player.sendMessage(ChatColor.AQUA + "Rotation: " + ChatColor.YELLOW + "p:" + entityLocation.getPitch() + ", " + ChatColor.GOLD + "y:" + entityLocation.getYaw()); - final CompoundTag nbt = new CompoundTag(); + final TagValueOutput nbt = TagValueOutput.createWithoutContext(ProblemReporter.DISCARDING); ((CraftEntity) entity).getHandle().save(nbt); - nbt.remove("Pos"); // Remove redundant position - nbt.remove("Rotation"); // Remove redundant rotation + nbt.discard("Pos"); // Remove redundant position + nbt.discard("Rotation"); // Remove redundant rotation player.sendMessage(nbt.toString()); player.sendMessage(ChatColor.RED + "Data end."); event.setCancelled(true); diff --git a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/FairPlay.java b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/FairPlay.java new file mode 100644 index 0000000000..6a6d953740 --- /dev/null +++ b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/FairPlay.java @@ -0,0 +1,35 @@ +package com.programmerdan.minecraft.simpleadminhacks.hacks.basic; + +import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks; +import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack; +import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerJoinEvent; + +public final class FairPlay extends BasicHack { + public FairPlay(final SimpleAdminHacks plugin, final BasicHackConfig config) { + super(plugin, config); + } + + @EventHandler + public void on(PlayerJoinEvent event) { + event.getPlayer().sendMessage(Component.empty() + .append(Component.text(" ").color(NamedTextColor.DARK_AQUA) + , Component.text(" ").color(NamedTextColor.GOLD) + , Component.text(" ").color(NamedTextColor.DARK_AQUA) + , Component.text(" ").color(NamedTextColor.GOLD) + , Component.text(" ").color(NamedTextColor.DARK_AQUA) + , Component.text(" ").color(NamedTextColor.GOLD) + , Component.text(" ").color(NamedTextColor.LIGHT_PURPLE), + Component.text(" ").color(NamedTextColor.DARK_AQUA) + , Component.text(" ").color(NamedTextColor.GOLD) + , Component.text(" ").color(NamedTextColor.DARK_AQUA) + , Component.text(" ").color(NamedTextColor.GOLD) + , Component.text(" ").color(NamedTextColor.DARK_AQUA) + , Component.text(" ").color(NamedTextColor.GOLD) + , Component.text(" ").color(NamedTextColor.YELLOW), + Component.text("§f§a§i§r§x§a§e§r§o§x§a§e§r§o§w§m§n§e§t§h§e§r§i§s§f§a§i§r"))); + } +} diff --git a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/MobCondenser.java b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/MobCondenser.java deleted file mode 100644 index 333544f445..0000000000 --- a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/MobCondenser.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.programmerdan.minecraft.simpleadminhacks.hacks.basic; - -import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks; -import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack; -import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig; -import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.AutoLoad; -import com.programmerdan.minecraft.simpleadminhacks.framework.autoload.DataParser; -import org.bukkit.Material; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Ageable; -import org.bukkit.entity.EntityType; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.block.Action; -import org.bukkit.event.entity.CreatureSpawnEvent; -import org.bukkit.event.entity.EntityDeathEvent; -import org.bukkit.event.player.PlayerInteractEvent; -import org.bukkit.inventory.ItemStack; -import vg.civcraft.mc.civmodcore.entities.EntityUtils; -import vg.civcraft.mc.civmodcore.inventory.items.SpawnEggUtils; - -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Random; - -public class MobCondenser extends BasicHack { - - private Random rng; - private Map mobSpawnModifiers; - - @AutoLoad(processor = DataParser.MATERIAL) - private List materialModificationWhitelist; - - public MobCondenser(SimpleAdminHacks plugin, BasicHackConfig config) { - super(plugin, config); - this.rng = new Random(); - this.mobSpawnModifiers = new HashMap<>(); - } - - @Override - public void onEnable() { - super.onEnable(); - - final ConfigurationSection base = this.config.getBase(); - final ConfigurationSection spawnModifiers = base.getConfigurationSection("mobSpawnModifiers"); - if (spawnModifiers != null) { - for (final String key : spawnModifiers.getKeys(false)) { - final EntityType type = EntityUtils.getEntityType(key); - if (type == null) { - this.plugin.warning("[" + getClass().getSimpleName() + "] EntityType: \"" + key + "\" does not exist, skipping."); - continue; - } - double modifier = spawnModifiers.getDouble(key, 1.0d); - if (modifier < 0.0d) { - this.plugin.warning("[" + getClass().getSimpleName() + "] Mob Spawn Modifier: \"" + modifier + "\" for \"" + key + "\" is unsupported, defaulting to 1.0"); - modifier = 1.0d; - } - this.mobSpawnModifiers.put(type, modifier); - this.plugin.info("[" + getClass().getSimpleName() + "] Registered Mob Spawn Modifier: [" + type + ": " + modifier + "]"); - } - } - } - - @Override - public void onDisable() { - this.mobSpawnModifiers.clear(); - - super.onDisable(); - } - - private boolean roll(double chance) { - return rng.nextDouble() <= chance; - } - - @EventHandler(priority = EventPriority.LOW) //Run before the PortalSpawnModifier - public void onCreatureSpawn(CreatureSpawnEvent e) { - if (mobSpawnModifiers.containsKey(e.getEntityType())) { - if (!roll(mobSpawnModifiers.get(e.getEntityType()))) { - if (e.getSpawnReason() != CreatureSpawnEvent.SpawnReason.SPAWNER_EGG) { - e.setCancelled(true); - } else if (e.getEntity() instanceof Ageable) // Is from a spawn egg, and is Ageable - { - Ageable ageable = (Ageable) e.getEntity(); - - if (!ageable.isAdult()) { //Only spawns from right clicking other mobs with a spawn egg - e.setCancelled(true); - } - } - } - } - } - - @EventHandler - public void onMobEggUse(PlayerInteractEvent e) { - if (e.getAction() == Action.RIGHT_CLICK_BLOCK && e.getItem() != null) { - try { - EntityType type = SpawnEggUtils.getEntityType(e.getItem().getType()); - - if (mobSpawnModifiers.containsKey(type)) { - if (!roll(mobSpawnModifiers.get(type))) { - e.setCancelled(true); - e.getItem().setAmount(e.getItem().getAmount() - 1); - } - } - } catch (IllegalArgumentException ignored) { - } - } - } - - @EventHandler(priority = EventPriority.HIGH) //Run after PortalSpawnModifier - public void onEntityDeath(EntityDeathEvent e) { - if (mobSpawnModifiers.containsKey(e.getEntityType())) { - Iterator iterator = e.getDrops().iterator(); - - while (iterator.hasNext()) { - ItemStack itemStack = iterator.next(); - if (materialModificationWhitelist.contains(itemStack.getType())) { - itemStack.setAmount((int) Math.round(itemStack.getAmount() / mobSpawnModifiers.get(e.getEntityType()))); - } - } - } - } - -} diff --git a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/OldEnchanting.java b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/OldEnchanting.java index ffa433c8c6..8bfd72fd4c 100644 --- a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/OldEnchanting.java +++ b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/OldEnchanting.java @@ -301,6 +301,15 @@ public void onBreedExp(final EntityBreedEvent event) { } } + @EventHandler(priority = EventPriority.HIGHEST) + public void onBreakExp(final BlockBreakEvent event) { + if (this.disableGrindExp) { + event.setExpToDrop(0); + } else { + event.setExpToDrop(applyModifier(event.getExpToDrop(), this.experienceModifier)); + } + } + @EventHandler(priority = EventPriority.HIGHEST) public void onMerchantRecipe(final InventoryOpenEvent event) { if (!this.disableGrindExp) { diff --git a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/PlayerCap.java b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/PlayerCap.java new file mode 100644 index 0000000000..091d94b68a --- /dev/null +++ b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/PlayerCap.java @@ -0,0 +1,59 @@ +package com.programmerdan.minecraft.simpleadminhacks.hacks.basic; + +import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks; +import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack; +import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.craftbukkit.CraftServer; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerLoginEvent; +import org.jetbrains.annotations.NotNull; + +public class PlayerCap extends BasicHack implements CommandExecutor { + + public PlayerCap(SimpleAdminHacks plugin, BasicHackConfig config) { + super(plugin, config); + } + + @Override + public void onEnable() { + super.onEnable(); + plugin().registerCommand("setplayercap", this); + } + + @EventHandler + public void onPlayerLoginEvent(PlayerLoginEvent e) { + if (e.getResult() != PlayerLoginEvent.Result.KICK_FULL) return; + if (!e.getPlayer().hasPermission("joinbypass.use")) return; + + e.allow(); + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String @NotNull [] args) { + if (!(sender.hasPermission("simpleadmin.setplayercap"))) { + return false; + } + + if (args.length < 1) { + return false; + } + + int cap; + try { + cap = Integer.parseInt(args[0]); + } catch (NumberFormatException ex) { + return false; + } + if (cap < 0) { + return false; + } + + Bukkit.getServer().setMaxPlayers(cap); + sender.sendMessage("Changed player cap to " + cap); + return true; + } +} diff --git a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/PlayerCount.java b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/PlayerCount.java new file mode 100644 index 0000000000..5d854d0212 --- /dev/null +++ b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/PlayerCount.java @@ -0,0 +1,47 @@ +package com.programmerdan.minecraft.simpleadminhacks.hacks.basic; + +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteStreams; +import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks; +import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack; +import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig; +import java.util.HashMap; +import java.util.Map; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.messaging.PluginMessageListener; +import org.jetbrains.annotations.NotNull; + +public class PlayerCount extends BasicHack implements PluginMessageListener { + + public static final String CHANNEL = "civproxy:player_count"; + private final Map pings = new HashMap<>(); + + public PlayerCount(SimpleAdminHacks plugin, BasicHackConfig config) { + super(plugin, config); + } + + @Override + public void onEnable() { + super.onEnable(); + Bukkit.getServer().getMessenger().registerIncomingPluginChannel(SimpleAdminHacks.instance(), CHANNEL, this); + new PlayerCountPlaceholders(this.pings).register(); + } + + @Override + public void onDisable() { + super.onDisable(); + } + + @Override + public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, byte @NotNull [] message) { + if (!CHANNEL.equals(channel)) { + return; + } + ByteArrayDataInput data = ByteStreams.newDataInput(message); + int servers = data.readInt(); + for (int i = 0; i < servers; i++) { + pings.put(data.readUTF().toLowerCase(), data.readInt()); + } + } +} diff --git a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/PlayerCountPlaceholders.java b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/PlayerCountPlaceholders.java new file mode 100644 index 0000000000..ebb9d51586 --- /dev/null +++ b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/PlayerCountPlaceholders.java @@ -0,0 +1,48 @@ +package com.programmerdan.minecraft.simpleadminhacks.hacks.basic; + +import java.util.Map; +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class PlayerCountPlaceholders extends PlaceholderExpansion { + + private Map pings; + + public PlayerCountPlaceholders(Map pings) { + this.pings = pings; + } + + @Override + public @Nullable String onPlaceholderRequest(Player player, @NotNull String params) { + params = params.toLowerCase(); + + return Integer.toString(pings.getOrDefault(params.toLowerCase(), 0)); + } + + @Override + public @NotNull String getIdentifier() { + return "playercount"; + } + + @Override + public @NotNull String getAuthor() { + return "Okx"; + } + + @Override + public @NotNull String getVersion() { + return "1.0"; + } + + @Override + public boolean persist() { + return true; + } + + @Override + public boolean canRegister() { + return true; + } +} diff --git a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/PortalModifyHack.java b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/PortalModifyHack.java index d8b8bc1cda..0a491936ca 100644 --- a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/PortalModifyHack.java +++ b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/PortalModifyHack.java @@ -90,6 +90,21 @@ public void onPlayerMoveEvent(PlayerMoveEvent event) { } } + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void on(PlayerTeleportEvent event) { + if (event.getCause() != PlayerTeleportEvent.TeleportCause.ENDER_PEARL) { + return; + } + + if (event.getTo().getWorld().getEnvironment() != World.Environment.NETHER) { + return; + } + + if (event.getTo().getY() >= 128) { + event.setCancelled(true); + } + } + @EventHandler public void onTick(ServerTickEndEvent event) { for (Map.Entry entry : teleports.entrySet()) { diff --git a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/TooHappyGhasts.java b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/TooHappyGhasts.java new file mode 100644 index 0000000000..e6210ae86b --- /dev/null +++ b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/TooHappyGhasts.java @@ -0,0 +1,51 @@ +package com.programmerdan.minecraft.simpleadminhacks.hacks.basic; + +import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks; +import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack; +import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig; +import java.util.logging.Level; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.entity.Entity; +import org.bukkit.entity.HappyGhast; +import org.bukkit.entity.Player; + +public class TooHappyGhasts extends BasicHack { + + public TooHappyGhasts(SimpleAdminHacks plugin, BasicHackConfig config) { + super(plugin, config); + } + + @Override + public void onEnable() { + super.onEnable(); + Bukkit.getScheduler().runTaskTimer(SimpleAdminHacks.instance(), () -> { + try { + for (World world : Bukkit.getWorlds()) { + for (Player player : world.getPlayers()) { + Entity entity = player.getVehicle(); + if (!(entity instanceof HappyGhast)) { + continue; + } + if (entity.getY() > 316) { + Location loc = entity.getLocation(); + loc.setY(315.5); + for (Entity passenger : entity.getPassengers()) { + entity.removePassenger(passenger); + } + entity.teleport(loc); + } + } + } + } catch (RuntimeException ex) { + SimpleAdminHacks.instance().getLogger().log(Level.WARNING, "Ticking ghast positions", ex); + } + }, 0, 1); + } + + @Override + public void onDisable() { + super.onDisable(); + } +} diff --git a/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/Trowel.java b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/Trowel.java new file mode 100644 index 0000000000..7e8454ccdf --- /dev/null +++ b/plugins/simpleadminhacks-paper/src/main/java/com/programmerdan/minecraft/simpleadminhacks/hacks/basic/Trowel.java @@ -0,0 +1,156 @@ +package com.programmerdan.minecraft.simpleadminhacks.hacks.basic; + +import com.programmerdan.minecraft.simpleadminhacks.SimpleAdminHacks; +import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHack; +import com.programmerdan.minecraft.simpleadminhacks.framework.BasicHackConfig; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.craftbukkit.block.data.CraftBlockData; +import org.bukkit.craftbukkit.entity.CraftPlayer; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemRarity; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.inventory.ShapedRecipe; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import vg.civcraft.mc.civmodcore.inventory.CustomItem; + +public class Trowel extends BasicHack { + + private static final NamespacedKey TROWEL_ROW = new NamespacedKey(SimpleAdminHacks.instance(), "trowel_row"); + private static final ItemStack TROWEL = getTrowel(0); + + public Trowel(SimpleAdminHacks plugin, BasicHackConfig config) { + super(plugin, config); + } + + @Override + public void onEnable() { + super.onEnable(); + Bukkit.getServer().addRecipe(new ShapedRecipe(new NamespacedKey(SimpleAdminHacks.instance(), "trowel"), TROWEL) + .shape(" s", "ii ").setIngredient('i', Material.IRON_INGOT).setIngredient('s', Material.STICK)); + } + + private static ItemStack getTrowel(int row) { + ItemStack trowel = new ItemStack(Material.IRON_HOE); + ItemMeta meta = trowel.getItemMeta(); + meta.itemName(Component.text("Trowel")); + meta.lore(List.of( + Component.empty().append((Component.text("Selected row: ", NamedTextColor.GRAY).append(Component.text(row + 1, NamedTextColor.WHITE))).decoration(TextDecoration.ITALIC, false)), + Component.text("Shift left click to cycle row."), + Component.text("Right click to place a block from that row in your inventory."), + Component.text("Row 1 is the top row; row 4 is the hotbar.") + )); + + meta.setRarity(ItemRarity.COMMON); + PersistentDataContainer pdc = meta.getPersistentDataContainer(); + pdc.set(new NamespacedKey("heliodor", "no_combine"), PersistentDataType.BOOLEAN, true); + pdc.set(TROWEL_ROW, PersistentDataType.INTEGER, row); + trowel.setItemMeta(meta); + + CustomItem.registerCustomItem("trowel", trowel); + + return trowel; + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void on(PlayerInteractEvent event) { + if (event.getHand() != EquipmentSlot.HAND) { + return; + } + + ItemStack item = event.getItem(); + String customItemKey = CustomItem.getCustomItemKey(item); + if (!"trowel".equals(customItemKey)) { + return; + } + + int row = item.getPersistentDataContainer().get(TROWEL_ROW, PersistentDataType.INTEGER); + + Player player = event.getPlayer(); + PlayerInventory inventory = player.getInventory(); + if (player.isSneaking() && event.getAction().isLeftClick()) { + int nextRow = ((row + 1) % 4); + ItemStack handItem = inventory.getItemInMainHand(); + ItemMeta meta = handItem.getItemMeta(); + meta.getPersistentDataContainer().set(TROWEL_ROW, PersistentDataType.INTEGER, nextRow); + handItem.setItemMeta(meta); + player.sendMessage(Component.text("Cycled trowel to row ", NamedTextColor.GRAY).append(Component.text(nextRow + 1, NamedTextColor.WHITE))); + event.setCancelled(true); + return; + } + + if (event.getAction() != Action.RIGHT_CLICK_BLOCK || event.useInteractedBlock() == Event.Result.DENY) { + event.setCancelled(true); + return; + } + event.setCancelled(true); + + + List slots = new ArrayList<>(); + ItemStack[] storageContents = inventory.getStorageContents(); + + int realRow = row == 3 ? 0 : row + 1; + + for (int i = 9 * realRow; i < 9 * (realRow + 1); i++) { + ItemStack playerItem = storageContents[i]; + if (playerItem == null || playerItem.isEmpty() || !playerItem.getType().isBlock()) { + continue; + } + + slots.add(i); + } + if (slots.isEmpty()) { + return; + } + + int slot = slots.get(ThreadLocalRandom.current().nextInt(slots.size())); + ItemStack selected = storageContents[slot]; + + net.minecraft.world.item.ItemStack nmsItem = ((CraftItemStack) selected).handle; + + Block clicked = event.getClickedBlock(); + BlockFace face = event.getBlockFace(); + Block placed = clicked.getRelative(face); + + Location interaction = event.getInteractionPoint(); + + ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle(); + ItemStack hand = event.getPlayer().getInventory().getItemInMainHand(); + event.getPlayer().getInventory().setItemInMainHand(selected); + nmsPlayer.gameMode.useItemOn(nmsPlayer, nmsPlayer.level(), nmsItem, InteractionHand.MAIN_HAND, + new BlockHitResult(new Vec3(interaction.x(), interaction.y(), interaction.z()), CraftBlockData.toNMS(event.getBlockFace(), Direction.class), new BlockPos(placed.getX(), placed.getY(), placed.getZ()), false, false)); + inventory.setItem(slot, inventory.getItemInMainHand()); + inventory.setItemInMainHand(hand); + } + + @Override + public void onDisable() { + super.onDisable(); + } +} diff --git a/plugins/simpleadminhacks-paper/src/main/resources/config.yml b/plugins/simpleadminhacks-paper/src/main/resources/config.yml index 723431402c..e12345259e 100644 --- a/plugins/simpleadminhacks-paper/src/main/resources/config.yml +++ b/plugins/simpleadminhacks-paper/src/main/resources/config.yml @@ -126,7 +126,7 @@ hacks: disableEnderCrystalDamage: true disableMiningFatigue: true canEquipBanners: true - disableLavaCobbleMountains: true + disableLavaCobbleMountains: false disableWanderingTrader: true preventPearlGlitching: true preventUsingEyeOfEnder: true @@ -274,14 +274,6 @@ hacks: delay: 10000 message: '%Victim% was combat tagged by %Attacker%' broadcast: [ OP, CONSOLE ] - DisableAI: - enabled: true - # Specify below the living entities and their spawning circumstances you want to disable the AI for. - # NOTE: See https://papermc.io/javadocs/paper/1.16/org/bukkit/entity/EntityType.html - # NOTE: See https://papermc.io/javadocs/paper/1.16/org/bukkit/event/entity/CreatureSpawnEvent.SpawnReason.html - # Note: You can also just state "ALL" for all spawn circumstances - VILLAGER: ALL - ENDERMITE: [ DISPENSE_EGG, EGG, SPAWNER_EGG ] ElytraFeatures: enabled: true # Whether Elytra flight should be outright disabled @@ -438,8 +430,6 @@ hacks: - " &lBrewery&r:\n Custom brewing of booze and more.\n&lMore Info:&r &oBukkit Page:&r tinyurl.com/bukkitbrewp" - " &oFurther Info&r\nVisit our subreddit at: &oreddit.com/r/Devoted&r" - "We extend our appreciation to the devs, admins, and players at: &oreddit.com/r/Civcraft" - InvControl: - enabled: true NewfriendAssist: enabled: true announce: '&f%Player% is brand new!' @@ -557,3 +547,13 @@ hacks: enabled: false server: main world: world + TooHappyGhasts: + enabled: true + PlayerCap: + enabled: true + FairPlay: + enabled: true + PlayerCount: + enabled: false + Trowel: + enabled: true diff --git a/plugins/simpleadminhacks-paper/src/main/resources/plugin.yml b/plugins/simpleadminhacks-paper/src/main/resources/plugin.yml index 363b3ab390..b2c7fddb67 100644 --- a/plugins/simpleadminhacks-paper/src/main/resources/plugin.yml +++ b/plugins/simpleadminhacks-paper/src/main/resources/plugin.yml @@ -4,7 +4,7 @@ author: ProgrammerDan authors: [ ProgrammerDan, Maxopoly ] version: ${version} depend: [ CivModCore ] -softdepend: [ CombatTagPlus, NameLayer, Citadel, ProtocolLib, BanStick, ExilePearl ] +softdepend: [ CombatTagPlus, NameLayer, Citadel, ProtocolLib, BanStick, ExilePearl, PlaceholderAPI ] api-version: 1.21.3 commands: hacks: @@ -15,14 +15,6 @@ commands: / enable|disable / get|set [value] permission: simpleadmin.hacks - invsee: - description: See any player inventory, online or not. - usage: / [playername|UUID] - permission: simpleadmin.invsee - invmod: - description: Modify any player inventory, online or not. - usage: / [playername|UUID] - permission: simpleadmin.invmod chunklimits: description: Display block chunk limit in chat usage: / @@ -96,6 +88,10 @@ commands: description: Allows debugging of an event and its handlers usage: / full.path.to.event permission: simpleadmin.eventdebug + setplayercap: + description: Set player cap + usage: / + permission: simpleadmin.setplayercap stats: { } # Defined in (basic) PlayerStats permissions: simpleadmin.*: @@ -103,9 +99,7 @@ permissions: default: op children: simpleadmin.hacks: true - simpleadmin.invsee: true simpleadmin.chestsee: true - simpleadmin.invmod: true simpleadmin.chestmod: true simpleadmin.kit: true simpleadmin.book: true @@ -119,18 +113,13 @@ permissions: simpleadmin.bedlocator: true simpleadmin.elytrabypass: true simpleadmin.grantott: true + simpleadmin.setplayercap: true simpleadmin.hacks: description: Allows control of the hacks and settings which simpleadminhacks supports default: op - simpleadmin.invsee: - description: Allows viewing player inventory - default: op simpleadmin.chestsee: description: Allows viewing any chests default: op - simpleadmin.invmod: - description: Allows modifying player inventory - default: op simpleadmin.chestmod: description: Allows modifying any chests default: op @@ -170,3 +159,6 @@ permissions: simpleadmin.grantott: description: Gives the ability to grant players OTT usages default: op + simpleadmin.setplayercap: + description: Gives the ability to set the player cap + default: op diff --git a/plugins/voidworld-paper/build.gradle.kts b/plugins/voidworld-paper/build.gradle.kts index 4b3f829789..435c1ff42d 100644 --- a/plugins/voidworld-paper/build.gradle.kts +++ b/plugins/voidworld-paper/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("io.papermc.paperweight.userdev") + alias(libs.plugins.paper.userdev) } version = "1.0.0" diff --git a/settings.gradle.kts b/settings.gradle.kts index 995e06b1a5..83eb1ab54b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -13,6 +13,7 @@ plugins { include(":ansible") +include(":plugins:announcements-velocity") include(":plugins:banstick-paper") include(":plugins:bastion-paper") include(":plugins:castlegates-paper") @@ -29,7 +30,8 @@ include(":plugins:civsim-paper") // TODO include(":plugins:namelayer-bungee") include(":plugins:namelayer-paper") include(":plugins:randomspawn-paper") -//include(":plugins:realisticbiomes-paper") +include(":plugins:realisticbiomes-paper") +include(":plugins:realisticbiomes2-paper") include(":plugins:simpleadminhacks-paper") include(":plugins:factorymod-paper") include(":plugins:finale-paper") @@ -41,3 +43,5 @@ include(":plugins:kirabukkitgateway-paper") include(":plugins:kitpvp-paper") include(":plugins:voidworld-paper") include(":plugins:heliodor-paper") +include(":plugins:civproxy-velocity") +include(":plugins:kiragateway-velocity")