diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 8596b0ac..997898b2 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -18,7 +18,7 @@ We expect all contributors to follow our [code of conduct](CODE_OF_CONDUCT.md). If you find an issue with the plugin, please report it in the [Issues tab](https://github.com/EternalCodeTeam/EternalCombat/issues). Please provide as much information as -possible, including the version of Minecraft and the plugin you are using, as well as any error messages or logs. +possible, including the version of Minecraft and the plugin you are using, as well as any error messagesSettings or logs. ### Compatibility diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 7004f0c3..c1ea6e48 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -4,7 +4,6 @@ on: push: branches: [ "master" ] pull_request: - branches: [ "master" ] permissions: contents: read @@ -23,7 +22,7 @@ jobs: java-version: '17' distribution: 'temurin' - name: Cache Gradle - uses: actions/cache@v4.0.2 + uses: actions/cache@v4 with: path: ~/.gradle/caches key: >- @@ -37,7 +36,7 @@ jobs: with: arguments: shadowJar - name: Upload a Build Artifact - uses: actions/upload-artifact@v4.3.6 + uses: actions/upload-artifact@v4 with: name: 'Successfully build EternalCombat' path: eternalcombat-plugin/build/libs/*.jar diff --git a/README.md b/README.md index 5b0f03cd..0628dd5c 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ with Maven: with Gradle: ```kts -compileOnly("com.eternalcode:eternalcombat-api:1.3.3") +compileOnly("com.eternalcode:eternalcombat-api:1.3.2") ``` with Maven: @@ -80,7 +80,7 @@ with Maven: com.eternalcode eternalcombat-api - 1.3.3 + 1.3.2 provided ``` @@ -94,4 +94,4 @@ more information on how to contribute and our [code of conduct](./.github/CODE_O If you find an issue with the plugin, please report it in the [Issues tab](https://github.com/EternalCodeTeam/EternalCombat/issues). Please provide as much information as -possible, including the version of Minecraft and the plugin you are using, as well as any error messages or logs +possible, including the version of Minecraft and the plugin you are using, as well as any error messagesSettings or logs diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 2acc891c..a9ffc502 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -6,13 +6,16 @@ object Versions { const val JUNIT_JUPITER_PARAMS = "5.10.3" const val JUNIT_JUPITER_ENGINE = "5.10.3" + const val JETBRAINS_ANNOTATIONS = "24.1.0" + const val ETERNALCODE_COMMONS = "1.1.3" - // TODO: Multification. + const val MULTIFICATION = "1.1.4" + const val PACKETS_EVENTS = "2.7.0" const val ADVENTURE_PLATFORM_BUKKIT = "4.3.4" - const val ADVENTURE_TEXT_MINIMESSAGE = "4.17.0" + const val ADVENTURE_API = "4.17.0" - const val LITE_COMMANDS = "2.8.9" + const val LITE_COMMANDS = "3.7.0" const val OKAERI_CONFIGS_YAML_BUKKIT = "5.0.3" const val OKAERI_CONFIGS_SERDES_COMMONS = "5.0.3" const val OKAERI_CONFIGS_SERDES_BUKKIT = "5.0.3" @@ -27,6 +30,7 @@ object Versions { const val WORLD_GUARD_BUKKIT = "7.0.9" const val PLACEHOLDER_API = "2.11.6" + const val PAPERLIB = "1.0.8" } diff --git a/buildSrc/src/main/kotlin/eternalcombat-java.gradle.kts b/buildSrc/src/main/kotlin/eternalcombat-java.gradle.kts index 30f3992a..ef36bda0 100644 --- a/buildSrc/src/main/kotlin/eternalcombat-java.gradle.kts +++ b/buildSrc/src/main/kotlin/eternalcombat-java.gradle.kts @@ -1,36 +1,15 @@ plugins { `java-library` - checkstyle } group = "com.eternalcode" -version = "1.3.3" +version = "2.0.0-SNAPSHOT" java { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } -checkstyle { - toolVersion = "10.17.0" - - configFile = file("${rootDir}/config/checkstyle/checkstyle.xml") - - maxErrors = 0 - maxWarnings = 0 -} - -// https://github.com/JabRef/jabref/pull/10812/files#diff-49a96e7eea8a94af862798a45174e6ac43eb4f8b4bd40759b5da63ba31ec3ef7R267 -configurations.named("checkstyle") { - resolutionStrategy { - capabilitiesResolution { - withCapability("com.google.collections:google-collections") { - select("com.google.guava:guava:33.2.1-jre") - } - } - } -} - tasks.compileJava { options.compilerArgs = listOf("-Xlint:deprecation", "-parameters") options.encoding = "UTF-8" diff --git a/buildSrc/src/main/kotlin/eternalcombat-publish.gradle.kts b/buildSrc/src/main/kotlin/eternalcombat-publish.gradle.kts index f88362a7..54b28952 100644 --- a/buildSrc/src/main/kotlin/eternalcombat-publish.gradle.kts +++ b/buildSrc/src/main/kotlin/eternalcombat-publish.gradle.kts @@ -4,7 +4,7 @@ plugins { } group = "com.eternalcode" -version = "1.3.3" +version = "2.0.0-SNAPSHOT" java { withSourcesJar() diff --git a/buildSrc/src/main/kotlin/eternalcombat-repositories.gradle.kts b/buildSrc/src/main/kotlin/eternalcombat-repositories.gradle.kts index bc88cc57..d3bb41cf 100644 --- a/buildSrc/src/main/kotlin/eternalcombat-repositories.gradle.kts +++ b/buildSrc/src/main/kotlin/eternalcombat-repositories.gradle.kts @@ -5,11 +5,12 @@ plugins { repositories { mavenCentral() - maven { url = uri("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") } - maven { url = uri("https://papermc.io/repo/repository/maven-public/") } - maven { url = uri("https://repo.eternalcode.pl/releases") } - maven { url = uri("https://storehouse.okaeri.eu/repository/maven-public/") } - maven { url = uri("https://repo.panda-lang.org/releases") } - maven { url = uri("https://maven.enginehub.org/repo/") } - maven { url = uri("https://repo.extendedclip.com/content/repositories/placeholderapi/")} + maven("https://hub.spigotmc.org/nexus/content/repositories/snapshots/") + maven("https://papermc.io/repo/repository/maven-public/") + maven("https://repo.eternalcode.pl/releases") + maven("https://storehouse.okaeri.eu/repository/maven-public/") + maven("https://repo.panda-lang.org/releases") + maven("https://maven.enginehub.org/repo/") + maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") + maven("https://repo.codemc.io/repository/maven-releases/") } diff --git a/eternalcombat-api/build.gradle.kts b/eternalcombat-api/build.gradle.kts index efbd0420..b6911a26 100644 --- a/eternalcombat-api/build.gradle.kts +++ b/eternalcombat-api/build.gradle.kts @@ -8,41 +8,5 @@ plugins { dependencies { // Spigot api compileOnlyApi("org.spigotmc:spigot-api:${Versions.SPIGOT_API}") - - // kyori - api("net.kyori:adventure-platform-bukkit:${Versions.ADVENTURE_PLATFORM_BUKKIT}") - api("net.kyori:adventure-text-minimessage:${Versions.ADVENTURE_TEXT_MINIMESSAGE}") - - // litecommands - api("dev.rollczi.litecommands:bukkit-adventure:${Versions.LITE_COMMANDS}") - - // Okaeri configs - api("eu.okaeri:okaeri-configs-yaml-bukkit:${Versions.OKAERI_CONFIGS_YAML_BUKKIT}") - api("eu.okaeri:okaeri-configs-serdes-commons:${Versions.OKAERI_CONFIGS_SERDES_COMMONS}") - api("eu.okaeri:okaeri-configs-serdes-bukkit:${Versions.OKAERI_CONFIGS_SERDES_BUKKIT}") - - // Panda utilities - api("org.panda-lang:panda-utilities:${Versions.PANDA_UTILITIES}") - - // GitCheck - api("com.eternalcode:gitcheck:${Versions.GIT_CHECK}") - - // commons - api("commons-io:commons-io:${Versions.APACHE_COMMONS}") - - // bstats - api("org.bstats:bstats-bukkit:${Versions.B_STATS_BUKKIT}") - - // caffeine - api("com.github.ben-manes.caffeine:caffeine:${Versions.CAFFEINE}") - - api("com.eternalcode:eternalcode-commons-bukkit:${Versions.ETERNALCODE_COMMONS}") - api("com.eternalcode:eternalcode-commons-adventure:${Versions.ETERNALCODE_COMMONS}") - - // worldguard - compileOnly("com.sk89q.worldguard:worldguard-bukkit:${Versions.WORLD_GUARD_BUKKIT}") - - // PlaceholderAPI - compileOnlyApi("me.clip:placeholderapi:${Versions.PLACEHOLDER_API}") - + api("org.jetbrains:annotations:${Versions.JETBRAINS_ANNOTATIONS}") } diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/CombatCommand.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/CombatCommand.java deleted file mode 100644 index e6e7db61..00000000 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/CombatCommand.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.eternalcode.combat; - -import com.eternalcode.combat.config.ConfigService; -import com.eternalcode.combat.config.implementation.PluginConfig; -import com.eternalcode.combat.fight.FightManager; -import com.eternalcode.combat.fight.event.CauseOfTag; -import com.eternalcode.combat.fight.event.CauseOfUnTag; -import com.eternalcode.combat.fight.event.FightTagEvent; -import com.eternalcode.combat.fight.event.FightUntagEvent; -import com.eternalcode.combat.notification.NotificationAnnouncer; -import dev.rollczi.litecommands.argument.Arg; -import dev.rollczi.litecommands.command.async.Async; -import dev.rollczi.litecommands.command.execute.Execute; -import dev.rollczi.litecommands.command.permission.Permission; -import dev.rollczi.litecommands.command.route.Route; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import panda.utilities.text.Formatter; - -import java.time.Duration; -import java.util.UUID; - -@Route(name = "combatlog", aliases = "combat") -public class CombatCommand { - - private final FightManager fightManager; - private final ConfigService configService; - private final NotificationAnnouncer announcer; - private final PluginConfig config; - - public CombatCommand(FightManager fightManager, ConfigService configService, NotificationAnnouncer announcer, PluginConfig config) { - this.fightManager = fightManager; - this.configService = configService; - this.announcer = announcer; - this.config = config; - } - - @Execute(route = "status", required = 1) - @Permission("eternalcombat.status") - void status(CommandSender sender, @Arg Player target) { - UUID targetUniqueId = target.getUniqueId(); - PluginConfig.Messages messages = this.config.messages; - - Formatter formatter = new Formatter() - .register("{PLAYER}", target.getName()); - - this.announcer.sendMessage(sender, this.fightManager.isInCombat(targetUniqueId) - ? formatter.format(messages.admin.playerInCombat) - : formatter.format(messages.admin.playerNotInCombat)); - } - - @Execute(route = "tag", required = 1) - @Permission("eternalcombat.tag") - void tag(CommandSender sender, @Arg Player target) { - UUID targetUniqueId = target.getUniqueId(); - Duration time = this.config.settings.combatDuration; - - Formatter formatter = new Formatter() - .register("{PLAYER}", target.getName()); - - FightTagEvent event = this.fightManager.tag(targetUniqueId, time, CauseOfTag.COMMAND); - - if (event.isCancelled()) { - this.announcer.sendMessage(sender, event.getCancelMessage()); - return; - } - - String format = formatter.format(this.config.messages.admin.adminTagPlayer); - this.announcer.sendMessage(sender, format); - } - - @Execute(route = "tag", required = 2) - @Permission("eternalcombat.tag") - void tagMultiple(CommandSender sender, @Arg Player firstTarget, @Arg Player secondTarget) { - Duration combatTime = this.config.settings.combatDuration; - PluginConfig.Messages messages = this.config.messages; - - if (sender.equals(firstTarget) || sender.equals(secondTarget)) { - this.announcer.sendMessage(sender, messages.admin.adminCannotTagSelf); - return; - } - - FightTagEvent firstTagEvent = this.fightManager.tag(firstTarget.getUniqueId(), combatTime, CauseOfTag.COMMAND); - FightTagEvent secondTagEvent = this.fightManager.tag(secondTarget.getUniqueId(), combatTime, CauseOfTag.COMMAND); - - Formatter formatter = new Formatter() - .register("{FIRST_PLAYER}", firstTarget.getName()) - .register("{SECOND_PLAYER}", secondTarget.getName()); - - String format = formatter.format(messages.admin.adminTagMultiplePlayers); - - if (firstTagEvent.isCancelled()) { - this.announcer.sendMessage(sender, firstTagEvent.getCancelMessage()); - } - - if (secondTagEvent.isCancelled()) { - this.announcer.sendMessage(sender, firstTagEvent.getCancelMessage()); - } - - if (firstTagEvent.isCancelled() && secondTagEvent.isCancelled()) { - return; - } - - this.announcer.sendMessage(sender, format); - } - - @Async - @Execute(route = "reload") - @Permission("eternalcombat.reload") - void execute(CommandSender player) { - this.configService.reload(); - this.announcer.sendMessage(player, this.config.messages.admin.reload); - } - - @Execute(route = "untag", required = 1) - @Permission("eternalcombat.untag") - void untag(Player sender, @Arg Player target) { - UUID targetUniqueId = target.getUniqueId(); - - if (!this.fightManager.isInCombat(targetUniqueId)) { - this.announcer.sendMessage(sender, this.config.messages.admin.adminPlayerNotInCombat); - return; - } - - FightUntagEvent event = this.fightManager.untag(targetUniqueId, CauseOfUnTag.COMMAND); - if (event.isCancelled()) { - return; - } - - Formatter formatter = new Formatter() - .register("{PLAYER}", target.getName()); - - String format = formatter.format(this.config.messages.admin.adminUntagPlayer); - - this.announcer.sendMessage(sender, format); - } -} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/EternalCombatApi.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/EternalCombatApi.java index bdd26e26..a45af55d 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/EternalCombatApi.java +++ b/eternalcombat-api/src/main/java/com/eternalcode/combat/EternalCombatApi.java @@ -1,13 +1,12 @@ package com.eternalcode.combat; -import com.eternalcode.combat.config.implementation.PluginConfig; -import com.eternalcode.combat.drop.DropKeepInventoryManager; -import com.eternalcode.combat.drop.DropManager; +import com.eternalcode.combat.fight.drop.DropKeepInventoryService; import com.eternalcode.combat.fight.FightManager; +import com.eternalcode.combat.fight.drop.DropService; import com.eternalcode.combat.fight.effect.FightEffectService; -import com.eternalcode.combat.fight.pearl.FightPearlManager; -import com.eternalcode.combat.fight.tagout.FightTagOutService; +import com.eternalcode.combat.fight.pearl.FightPearlService; import com.eternalcode.combat.region.RegionProvider; +import com.eternalcode.combat.fight.tagout.FightTagOutService; public interface EternalCombatApi { @@ -15,16 +14,14 @@ public interface EternalCombatApi { RegionProvider getRegionProvider(); - FightPearlManager getFightPearlManager(); + FightPearlService getFightPearlService(); FightTagOutService getFightTagOutService(); FightEffectService getFightEffectService(); - DropManager getDropManager(); - - DropKeepInventoryManager getDropKeepInventoryManager(); + DropService getDropService(); - PluginConfig getPluginConfig(); + DropKeepInventoryService getDropKeepInventoryService(); } diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/command/InvalidUsage.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/command/InvalidUsage.java deleted file mode 100644 index a8a6e622..00000000 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/command/InvalidUsage.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.eternalcode.combat.command; - - -import com.eternalcode.combat.config.implementation.PluginConfig; -import com.eternalcode.combat.notification.NotificationAnnouncer; -import dev.rollczi.litecommands.command.LiteInvocation; -import dev.rollczi.litecommands.handle.InvalidUsageHandler; -import dev.rollczi.litecommands.schematic.Schematic; -import org.bukkit.command.CommandSender; -import panda.utilities.text.Formatter; - -import java.util.List; - -public class InvalidUsage implements InvalidUsageHandler { - - private final PluginConfig config; - private final NotificationAnnouncer announcer; - - public InvalidUsage(PluginConfig config, NotificationAnnouncer announcer) { - this.config = config; - this.announcer = announcer; - } - - @Override - public void handle(CommandSender commandSender, LiteInvocation invocation, Schematic scheme) { - List schematics = scheme.getSchematics(); - - for (String schematic : schematics) { - Formatter formatter = new Formatter() - .register("{USAGE}", schematic); - - this.announcer.sendMessage(commandSender, formatter.format(this.config.messages.invalidCommandUsage)); - } - } -} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/command/PermissionMessage.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/command/PermissionMessage.java deleted file mode 100644 index d9343b5a..00000000 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/command/PermissionMessage.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.eternalcode.combat.command; - -import com.eternalcode.combat.config.implementation.PluginConfig; -import com.eternalcode.combat.notification.NotificationAnnouncer; -import dev.rollczi.litecommands.command.LiteInvocation; -import dev.rollczi.litecommands.command.permission.RequiredPermissions; -import dev.rollczi.litecommands.handle.PermissionHandler; -import org.bukkit.command.CommandSender; -import panda.utilities.text.Formatter; -import panda.utilities.text.Joiner; - -public class PermissionMessage implements PermissionHandler { - - private final PluginConfig config; - private final NotificationAnnouncer announcer; - - public PermissionMessage(PluginConfig config, NotificationAnnouncer announcer) { - this.config = config; - this.announcer = announcer; - } - - @Override - public void handle(CommandSender commandSender, LiteInvocation invocation, RequiredPermissions requiredPermissions) { - String value = Joiner.on(", ") - .join(requiredPermissions.getPermissions()) - .toString(); - - Formatter formatter = new Formatter() - .register("{PERMISSION}", value); - - this.announcer.sendMessage(commandSender, formatter.format(this.config.messages.noPermission)); - } - -} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/config/ConfigService.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/config/ConfigService.java deleted file mode 100644 index 53bfb06a..00000000 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/config/ConfigService.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.eternalcode.combat.config; - -import com.eternalcode.combat.notification.serializer.NotificationSerializer; -import eu.okaeri.configs.ConfigManager; -import eu.okaeri.configs.OkaeriConfig; -import eu.okaeri.configs.serdes.commons.SerdesCommons; -import eu.okaeri.configs.yaml.bukkit.YamlBukkitConfigurer; -import eu.okaeri.configs.yaml.bukkit.serdes.SerdesBukkit; - -import java.io.File; -import java.util.HashSet; -import java.util.Set; - -public class ConfigService { - - private final Set configs = new HashSet<>(); - - public T create(Class config, File file) { - T configFile = ConfigManager.create(config); - - configFile.withConfigurer(new YamlBukkitConfigurer(), new SerdesCommons(), new SerdesBukkit()); - configFile.withSerdesPack(registry -> { - registry.register(new NotificationSerializer()); - }); - - configFile.withBindFile(file); - configFile.withRemoveOrphans(true); - configFile.saveDefaults(); - configFile.load(true); - - this.configs.add(configFile); - - return configFile; - } - - public void reload() { - for (OkaeriConfig config : this.configs) { - config.load(); - } - } -} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java deleted file mode 100644 index a192f99f..00000000 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java +++ /dev/null @@ -1,255 +0,0 @@ -package com.eternalcode.combat.config.implementation; - -import com.eternalcode.combat.WhitelistBlacklistMode; -import com.eternalcode.combat.drop.DropSettings; -import com.eternalcode.combat.fight.effect.FightEffectSettings; -import com.eternalcode.combat.fight.pearl.FightPearlSettings; -import com.eternalcode.combat.notification.Notification; -import com.eternalcode.combat.notification.implementation.ActionBarNotification; -import eu.okaeri.configs.OkaeriConfig; -import eu.okaeri.configs.annotation.Comment; -import org.bukkit.Material; -import org.bukkit.entity.EntityType; -import org.bukkit.event.entity.EntityDamageEvent; - -import java.time.Duration; -import java.util.Collections; -import java.util.List; - -public class PluginConfig extends OkaeriConfig { - - @Comment("# Do you want to change the plugin settings?") - public Settings settings = new Settings(); - - @Comment({" ", "# Ender pearl settings"}) - public FightPearlSettings pearl = new FightPearlSettings(); - - @Comment({" ", "# Custom effects settings"}) - public FightEffectSettings effect = new FightEffectSettings(); - - @Comment({" ", "# Set a custom way for a player's items to drop on death (if in combat)"}) - public DropSettings dropSettings = new DropSettings(); - - public static class Settings extends OkaeriConfig { - - @Comment("# Whether the player should receive information about new plugin updates upon joining the server") - public boolean shouldReceivePluginUpdates = true; - - @Comment("# The duration of combat in seconds") - public Duration combatDuration = Duration.ofSeconds(20); - - @Comment("# List of worlds to ignore") - public List worldsToIgnore = List.of( - "your_world" - ); - - @Comment("# List of regions to block") - public List blockedRegions = Collections.singletonList("your_region"); - - @Comment({ - "# Set the knock multiplier for the blocked region", - "# Values can be decimal. Do NOT use negative values.", - "# Setting it around 1 knocks the player around 2-4 blocks away." - }) - public double blockedRegionKnockMultiplier = 1; - - @Comment({"# Should the player be prevented from entering regions with WorldGuard flag PVP set to DENY "}) - public boolean shouldPreventPvpRegions = true; - - @Comment("# Set the radius of the blocked region if you aren't using WorldGuard basen on default spawn region!") - public int blockedRegionRadius = 10; - - @Comment("# Release attacker after victim dies?") - public boolean shouldReleaseAttacker = true; - - @Comment({"# If you want to exclude admins from combat, ", - "# Setting this to true - admins cannot be tagged and will not tag other players on hit", - "# Setting this to false - admins can be tagged and can tag other players on hit" - }) - public boolean excludeAdminFromCombat = false; - - @Comment("# Command blocking mode, available modes: WHITELIST, BLACKLIST") - public WhitelistBlacklistMode commandBlockingMode = WhitelistBlacklistMode.BLACKLIST; - - @Comment({ - "# List of commands based on the mode above", - "# Based on BLACKLIST mode, all commands in the list are blocked, and all others are allowed", - "# Based on WHITELIST mode, all commands in the list are allowed, and all others are blocked", - }) - public List blockedCommands = List.of( - "gamemode", - "spawn", - "tp" - ); - - @Comment("# Block the use of elytra?") - public boolean shouldPreventElytraUsage = true; - - @Comment("# Block flying? (flying players will fall to the ground)") - public boolean shouldPreventFlying = true; - - @Comment("# Disable the use of elytra on damage?") - public boolean shouldElytraDisableOnDamage = true; - - @Comment("# Block the opening of inventory?") - public boolean shouldPreventInventoryOpening = true; - - @Comment("# Whether to block the placement of blocks?") - public boolean shouldPreventBlockPlacing = true; - - @Comment({ "# Block the placement of blocks above or below a certain Y coordinate", - "# Select the mode for block placing, available modes: ABOVE, BELOW" - }) - public BlockPlacingMode blockPlacingMode = BlockPlacingMode.ABOVE; - - @Comment("# Block placing mode custom name used if messages") - public String blockPlacingModeName = "above"; - - @Comment("# Set the Y coordinate for block placing relative to mode selected above") - public int blockPlacingYCoordinate = 40; - - public enum BlockPlacingMode { - ABOVE, - BELOW - } - - @Comment({ - "# Disable placing specific blocks?", - "# If you want to block all blocks, enable shouldPreventBlockPlacing and make this list empty", - "# If you want to disable placing only specific blocks, enable shouldPreventBlockPlacing and add blocks to this list above", - "# If you want to disable this feature completely, disable shouldPreventBlockPlacing option above", - }) - public List specificBlocksToPreventPlacing = List.of(); - - @Comment("# Do You want to enable combat log for non-player causes of damage? - Set to false to disable") - public boolean shouldEnableDamageCauses = false; - - @Comment("# Select the mode for damage causes, available modes: WHITELIST, BLACKLIST") - public WhitelistBlacklistMode damageCausesMode = WhitelistBlacklistMode.WHITELIST; - - @Comment({ - "# After selecting the mode above, select the causes of damage to be logged", - "# You can find a list of all causes here: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/EntityDamageEvent.DamageCause.html", - "# While using Whitelist mode the player will get a combat log after the damage from the list below", - "# While using Blacklist mode the player will get a combat log after any damage non-listed below", - }) - public List damageCausesToLog = List.of( - EntityDamageEvent.DamageCause.LAVA, - EntityDamageEvent.DamageCause.CONTACT, - EntityDamageEvent.DamageCause.FIRE, - EntityDamageEvent.DamageCause.FIRE_TICK - ); - - @Comment({ - "# After what type of projectile entity should not tag the player as fighter?", - "# You can find a list of all entity types here: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/entity/EntityType.html" - }) - public List disabledProjectileEntities = List.of( - EntityType.ENDER_PEARL - ); - - } - - @Comment({" ", "# Do you want to change the plugin messages?"}) - public Messages messages = new Messages(); - - public static class Messages extends OkaeriConfig { - - @Comment("# Do you want to change the admin messages?") - public AdminMessages admin = new AdminMessages(); - - @Comment({ - " ", - "# Combat log notification", - "# You can use {TIME} variable to display the time left in combat", - "# Notification types: CHAT, ACTION_BAR, TITLE, SUB_TITLE, BOSS_BAR", - " ", - "# BossBar progress: This is the value of the progress bar. Set it to -1.0 to show the remaining combat time", - "# BossBar colors: https://javadoc.io/static/net.kyori/adventure-api/4.14.0/net/kyori/adventure/bossbar/BossBar.Color.html", - "# BossBar overlays: https://javadoc.io/static/net.kyori/adventure-api/4.14.0/net/kyori/adventure/bossbar/BossBar.Overlay.html" - }) - public Notification combatNotification = new ActionBarNotification("&dCombat ends in: &f{TIME}"); - - @Comment("# Message sent when the player does not have permission to perform a command") - public String noPermission = "&cYou don't have permission to perform this command!"; - - @Comment("# Message sent when the specified player could not be found") - public String playerNotFound = "&cThe specified player could not be found!"; - - @Comment("# Message sent when the player enters combat") - public String playerTagged = "&cYou are in combat, do not leave the server!"; - - @Comment("# Message sent when the player leaves combat") - public String playerUntagged = "&aYou are no longer in combat! You can safely leave the server."; - - @Comment("# This is broadcast when the player is in combat and logs out") - public String playerLoggedOutDuringCombat = "&c{PLAYER} logged off during the fight!"; - - @Comment({ - "# Message sent when the player is in combat and tries to use a disabled command", - "# you can configure the list of disabled commands in the blockedCommands section of the config.yml file" - }) - public String commandDisabledDuringCombat = "&cUsing this command during combat is prohibited!"; - - @Comment("# Message sent when player tries to use a command with invalid arguments") - public String invalidCommandUsage = "&7Correct usage: &e{USAGE}"; - - @Comment("# Message sent when player tries to open inventory, but the inventory open is blocked") - public String inventoryBlockedDuringCombat = "&cYou cannot open this inventory during combat!"; - - @Comment({"# Message sent when player tries to place a block, but the block place is blocked", - "# Placeholder {Y} is replaced with the Y coordinate set in the config", - "# Placeholder {MODE} is replaced with the mode set in the config"}) - public String blockPlacingBlockedDuringCombat = "&cYou cannot place {MODE} {Y} coordinate during combat!"; - - @Comment("# Message sent when player tries to enter a region") - public String cantEnterOnRegion = "&cYou can't enter this region during combat!"; - - public static class AdminMessages extends OkaeriConfig { - @Comment("# Message sent when the configuration is reloaded") - public String reload = "&aConfiguration has been successfully reloaded!"; - - @Comment("# Message sent when console tries to use a command that is only for players") - public String onlyForPlayers = "&cThis command is only available to players!"; - - @Comment("# Message sent to admin when they tag a player") - public String adminTagPlayer = "&7You have tagged &e{PLAYER}"; - - @Comment("# Message sent when a player is tagged by an admin") - public String adminTagMultiplePlayers = "&7You have tagged &e{FIRST_PLAYER}&7 and &e{SECOND_PLAYER}&7."; - - @Comment("# Message sent to admin when they remove a player from combat") - public String adminUntagPlayer = "&7You have removed &e{PLAYER}&7 from the fight."; - - @Comment("# Message sent when the player is not in combat") - public String adminPlayerNotInCombat = "&cThis player is not in combat!"; - - @Comment("# Message sent when the player is in combat") - public String playerInCombat = "&c{PLAYER} is currently in combat!"; - - @Comment("# Message sent when a player is not in combat") - public String playerNotInCombat = "&a{PLAYER} is not currently in combat."; - - @Comment("# Message sent when an admin tries to tag themselves") - public String adminCannotTagSelf = "&cYou cannot tag yourself!"; - - @Comment("# Message sent when an admin disables the ability to get tagged for some time") - public String adminTagOutSelf = "&7Successfully disabled tag for Yourself! You will be taggable after &e{TIME} "; - - @Comment("# Message sent when an admin disables the ability to get tagged for some time for other player") - public String adminTagOut = "&7Successfully disabled tag for &e{PLAYER}! They will be taggable after &e{TIME} "; - - @Comment("# Message sent to the player whom the ability to get tagged for some time has been disabled") - public String playerTagOut = "&7You will be taggable in &e{TIME} !"; - - @Comment("# Message sent when an admin reenables the ability to get tagged for the player") - public String adminTagOutOff = "&7Successfully enabled tag for &e{PLAYER}!"; - - @Comment("# Message sent to the player whom the ability to get tagged has been reenabled") - public String playerTagOutOff = "&7You are now taggable!"; - - @Comment("# Message sent when player cannot be tagged because they have enabled tag-out") - public String adminTagOutCanceled = "&cCannot tag this player due to tag-out!"; - } - } -} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/FightManager.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/FightManager.java index ffb636c4..49a58967 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/FightManager.java +++ b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/FightManager.java @@ -4,76 +4,26 @@ import com.eternalcode.combat.fight.event.CauseOfUnTag; import com.eternalcode.combat.fight.event.FightTagEvent; import com.eternalcode.combat.fight.event.FightUntagEvent; -import com.eternalcode.combat.event.EventCaller; - import java.time.Duration; -import java.time.Instant; import java.util.Collection; -import java.util.Collections; -import java.util.Map; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; -public class FightManager { - - private final Map fights = new ConcurrentHashMap<>(); - private final EventCaller eventCaller; - - public FightManager(EventCaller eventCaller) { - this.eventCaller = eventCaller; - } - - public boolean isInCombat(UUID player) { - if (!this.fights.containsKey(player)) { - return false; - } - - FightTag fightTag = this.fights.get(player); +public interface FightManager { - return !fightTag.isExpired(); - } + boolean isInCombat(UUID player); - public FightUntagEvent untag(UUID player, CauseOfUnTag causeOfUnTag) { - FightUntagEvent event = this.eventCaller.publishEvent(new FightUntagEvent(player, causeOfUnTag)); - if (event.isCancelled()) { - return event; - } + FightTag getTag(UUID target); - this.fights.remove(player); - return event; - } - - public FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag) { - return this.tag(target, delay, causeOfTag, null); - } + Collection getFights(); @ApiStatus.Experimental - public FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag, @Nullable UUID tagger) { - FightTagEvent event = this.eventCaller.publishEvent(new FightTagEvent(target, causeOfTag)); - - if (event.isCancelled()) { - return event; - } - Instant now = Instant.now(); - Instant endOfCombatLog = now.plus(delay); - - FightTag fightTag = new FightTag(target, endOfCombatLog, tagger); - - this.fights.put(target, fightTag); - return event; - } + FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag, @Nullable UUID tagger); - public Collection getFights() { - return Collections.unmodifiableCollection(this.fights.values()); - } + FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag); - public FightTag getTag(UUID target) { - return this.fights.get(target); - } + FightUntagEvent untag(UUID player, CauseOfUnTag causeOfUnTag); - public void untagAll() { - this.fights.clear(); - } + void untagAll(); } diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/FightTag.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/FightTag.java index c28bbcb8..f16103f0 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/FightTag.java +++ b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/FightTag.java @@ -6,44 +6,17 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; -public class FightTag { +public interface FightTag { - private final UUID taggedPlayer; - private final Instant endOfCombatLog; - private final @Nullable UUID tagger; - - FightTag(UUID personToAddCombat, Instant endOfCombatLog, @Nullable UUID tagger) { - this.taggedPlayer = personToAddCombat; - this.endOfCombatLog = endOfCombatLog; - this.tagger = tagger; - } - - public UUID getTaggedPlayer() { - return this.taggedPlayer; - } - - public Instant getEndOfCombatLog() { - return this.endOfCombatLog; - } - - public boolean isExpired() { - return Instant.now().isAfter(this.endOfCombatLog); - } - - public Duration getRemainingDuration() { - Duration between = Duration.between(Instant.now(), this.endOfCombatLog); + @Nullable + @ApiStatus.Experimental + UUID getTagger(); - if (between.isNegative()) { - return Duration.ZERO; - } + Duration getRemainingDuration(); - return between; - } + boolean isExpired(); - @Nullable - @ApiStatus.Experimental - public UUID getTagger() { - return this.tagger; - } + Instant getEndOfCombatLog(); + UUID getTaggedPlayer(); } diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/bossbar/FightBossBar.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/bossbar/FightBossBar.java deleted file mode 100644 index 430cca22..00000000 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/bossbar/FightBossBar.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.eternalcode.combat.fight.bossbar; - -import net.kyori.adventure.audience.Audience; -import net.kyori.adventure.bossbar.BossBar; - -import java.time.Duration; - -public record FightBossBar(Audience audience, BossBar bossBar, float progress, Duration combatDuration) { -} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/bossbar/FightBossBarRegistry.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/bossbar/FightBossBarRegistry.java deleted file mode 100644 index b113a317..00000000 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/bossbar/FightBossBarRegistry.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.eternalcode.combat.fight.bossbar; - -import java.util.Map; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; - -class FightBossBarRegistry { - - private final Map fightBossBars = new ConcurrentHashMap<>(); - - void add(UUID uuid, FightBossBar fightBossBar) { - this.fightBossBars.put(uuid, fightBossBar); - } - - void remove(UUID uuid) { - this.fightBossBars.remove(uuid); - } - - boolean hasFightBossBar(UUID uuid) { - return this.fightBossBars.containsKey(uuid); - } - - Optional getFightBossBar(UUID uuid) { - return Optional.ofNullable(this.fightBossBars.get(uuid)); - } -} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/bossbar/FightBossBarService.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/bossbar/FightBossBarService.java deleted file mode 100644 index e7fcb6de..00000000 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/bossbar/FightBossBarService.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.eternalcode.combat.fight.bossbar; - -import com.eternalcode.combat.config.implementation.PluginConfig; -import com.eternalcode.combat.fight.FightTag; -import com.eternalcode.combat.notification.implementation.BossBarNotification; -import net.kyori.adventure.audience.Audience; -import net.kyori.adventure.bossbar.BossBar; -import net.kyori.adventure.platform.AudienceProvider; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.minimessage.MiniMessage; -import org.bukkit.entity.Player; -import panda.utilities.text.Formatter; - -import java.time.Duration; -import java.util.Optional; -import java.util.UUID; - -public class FightBossBarService { - - private final PluginConfig pluginConfig; - private final FightBossBarRegistry bossBarRegistry = new FightBossBarRegistry(); - private final AudienceProvider audienceProvider; - private final MiniMessage miniMessage; - - public FightBossBarService(PluginConfig pluginConfig, AudienceProvider audienceProvider, MiniMessage miniMessage) { - this.pluginConfig = pluginConfig; - this.audienceProvider = audienceProvider; - this.miniMessage = miniMessage; - } - - public void hide(FightTag fightTag, FightBossBar fightBossBar) { - Audience audience = fightBossBar.audience(); - BossBar bossBar = fightBossBar.bossBar(); - UUID taggedPlayer = fightTag.getTaggedPlayer(); - - audience.hideBossBar(bossBar); - this.bossBarRegistry.remove(taggedPlayer); - } - - public void hide(UUID playerUuid) { - Optional bossBar = this.bossBarRegistry.getFightBossBar(playerUuid); - - if (bossBar.isPresent()) { - FightBossBar fightBossBar = bossBar.get(); - Audience audience = fightBossBar.audience(); - BossBar bar = fightBossBar.bossBar(); - - audience.hideBossBar(bar); - this.bossBarRegistry.remove(playerUuid); - } - } - - public void send(Player player, FightTag fightTag, BossBarNotification bossBarNotification, Formatter formatter) { - UUID playerUniqueId = player.getUniqueId(); - - FightBossBar fightBossBar = this.bossBarRegistry.getFightBossBar(playerUniqueId) - .orElseGet(() -> this.create(player, bossBarNotification, formatter)); - - if (!this.bossBarRegistry.hasFightBossBar(playerUniqueId)) { - this.show(player, fightBossBar); - return; - } - - String message = formatter.format(bossBarNotification.message()); - - this.update(fightTag, fightBossBar, message); - } - - private void update(FightTag fightTag, FightBossBar fightBossBar, String message) { - if (fightTag.isExpired()) { - this.hide(fightTag, fightBossBar); - return; - } - - BossBar bossBar = fightBossBar.bossBar(); - - long combatDurationMillis = this.pluginConfig.settings.combatDuration.toMillis(); - long remainingDurationMillis = fightTag.getRemainingDuration().toMillis(); - - float progress = (float) remainingDurationMillis / combatDurationMillis; - - if (progress > 1.0F) { - progress = 1.0F; - } - - Component name = this.miniMessage.deserialize(message); - bossBar.name(name); - bossBar.progress(progress); - } - - private void show(Player player, FightBossBar fightBossBar) { - UUID playerUniqueId = player.getUniqueId(); - - Audience audience = this.audienceProvider.player(playerUniqueId); - BossBar bossBar = fightBossBar.bossBar(); - - audience.showBossBar(bossBar); - this.bossBarRegistry.add(playerUniqueId, fightBossBar); - } - - private FightBossBar create(Player player, BossBarNotification bossBarNotification, Formatter formatter) { - Audience audience = this.audienceProvider.player(player.getUniqueId()); - - Component name = this.miniMessage.deserialize(formatter.format(bossBarNotification.message())); - BossBar bossBar = bossBarNotification.create(name); - - float progress = bossBarNotification.progress(); - Duration combatDuration = this.pluginConfig.settings.combatDuration; - - return new FightBossBar(audience, bossBar, progress, combatDuration); - } -} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/drop/Drop.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/drop/Drop.java similarity index 97% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/drop/Drop.java rename to eternalcombat-api/src/main/java/com/eternalcode/combat/fight/drop/Drop.java index 92839d4e..b8d75438 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/drop/Drop.java +++ b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/drop/Drop.java @@ -1,4 +1,4 @@ -package com.eternalcode.combat.drop; +package com.eternalcode.combat.fight.drop; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/drop/DropKeepInventoryService.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/drop/DropKeepInventoryService.java new file mode 100644 index 00000000..9b98816e --- /dev/null +++ b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/drop/DropKeepInventoryService.java @@ -0,0 +1,16 @@ +package com.eternalcode.combat.fight.drop; + +import java.util.List; +import java.util.UUID; +import org.bukkit.inventory.ItemStack; + +public interface DropKeepInventoryService { + + List nextItems(UUID uuid); + + boolean hasItems(UUID uuid); + + void addItems(UUID uuid, List item); + + void addItem(UUID uuid, ItemStack item); +} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/drop/DropModifier.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/drop/DropModifier.java similarity index 70% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/drop/DropModifier.java rename to eternalcombat-api/src/main/java/com/eternalcode/combat/fight/drop/DropModifier.java index 7c126601..47ec6524 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/drop/DropModifier.java +++ b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/drop/DropModifier.java @@ -1,4 +1,4 @@ -package com.eternalcode.combat.drop; +package com.eternalcode.combat.fight.drop; public interface DropModifier { diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/drop/DropResult.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/drop/DropResult.java similarity index 91% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/drop/DropResult.java rename to eternalcombat-api/src/main/java/com/eternalcode/combat/fight/drop/DropResult.java index 15dd56ec..74963bc0 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/drop/DropResult.java +++ b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/drop/DropResult.java @@ -1,4 +1,4 @@ -package com.eternalcode.combat.drop; +package com.eternalcode.combat.fight.drop; import org.bukkit.inventory.ItemStack; diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/drop/DropService.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/drop/DropService.java new file mode 100644 index 00000000..77528cb7 --- /dev/null +++ b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/drop/DropService.java @@ -0,0 +1,8 @@ +package com.eternalcode.combat.fight.drop; + +public interface DropService { + + DropResult modify(DropType dropType, Drop drop); + + void registerModifier(DropModifier dropModifier); +} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/drop/DropType.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/drop/DropType.java similarity index 63% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/drop/DropType.java rename to eternalcombat-api/src/main/java/com/eternalcode/combat/fight/drop/DropType.java index 1e0b9153..46abeed7 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/drop/DropType.java +++ b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/drop/DropType.java @@ -1,4 +1,4 @@ -package com.eternalcode.combat.drop; +package com.eternalcode.combat.fight.drop; public enum DropType { diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/effect/FightEffectService.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/effect/FightEffectService.java index 13910444..5ad1ae8f 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/effect/FightEffectService.java +++ b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/effect/FightEffectService.java @@ -1,75 +1,25 @@ package com.eternalcode.combat.fight.effect; +import java.util.List; import org.bukkit.entity.Player; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import java.util.List; -import java.util.ArrayList; - -public class FightEffectService { - - private final Map> activeEffects = new HashMap<>(); - private static final int INFINITE_DURATION = -1; - - public void storeActiveEffect(Player player, PotionEffect effect) { - List effects = this.activeEffects.computeIfAbsent(player.getUniqueId(), k -> new ArrayList<>()); - - effects.add(effect); - } - - public void restoreActiveEffects(Player player) { - List currentEffects = this.getCurrentEffects(player); - - for (PotionEffect effect : currentEffects) { - player.addPotionEffect(effect); - } - - this.clearStoredEffects(player); - } - - public void clearStoredEffects(Player player) { - this.activeEffects.remove(player.getUniqueId()); - } - - public List getCurrentEffects(Player player) { - return this.activeEffects.getOrDefault(player.getUniqueId(), new ArrayList<>()); - } - - public void applyCustomEffect(Player player, PotionEffectType type, Integer amplifier) { - PotionEffect activeEffect = player.getPotionEffect(type); - - if (activeEffect == null) { - player.addPotionEffect(new PotionEffect(type, INFINITE_DURATION, amplifier)); - return; - } - - if (activeEffect.getAmplifier() > amplifier) { - return; - } +/** + * Manages custom potion effects on players during combat. + * Active effects before combat are stored and restored after combat ends. + */ +public interface FightEffectService { - if (activeEffect.getDuration() == -1) { - return; - } + void removeCustomEffect(Player player, PotionEffectType type, Integer amplifier); - this.storeActiveEffect(player, activeEffect); - player.addPotionEffect(new PotionEffect(type, INFINITE_DURATION, amplifier)); - } + void applyCustomEffect(Player player, PotionEffectType type, Integer amplifier); - public void removeCustomEffect(Player player, PotionEffectType type, Integer amplifier) { - PotionEffect activeEffect = player.getPotionEffect(type); + List getCurrentEffects(Player player); - if (activeEffect == null) { - return; - } + void clearStoredEffects(Player player); - if (activeEffect.getAmplifier() != amplifier) { - return; - } + void restoreActiveEffects(Player player); - player.removePotionEffect(type); - } + void storeActiveEffect(Player player, PotionEffect effect); } diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/CancelTagReason.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/CancelTagReason.java new file mode 100644 index 00000000..3bd1c7c4 --- /dev/null +++ b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/CancelTagReason.java @@ -0,0 +1,7 @@ +package com.eternalcode.combat.fight.event; + +public enum CancelTagReason { + + TAGOUT + +} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/CauseOfTag.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/CauseOfTag.java index 5e62c959..940117c6 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/CauseOfTag.java +++ b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/CauseOfTag.java @@ -1,8 +1,25 @@ package com.eternalcode.combat.fight.event; public enum CauseOfTag { + + /** + * The player was tagged in combat as a result of an attack by another player. + */ PLAYER, + + /** + * The player was tagged in combat due to an interaction with a non-player entity + * (e.g., mobs or environmental damage). + */ NON_PLAYER, + + /** + * A command was executed to apply a combat tag to the player. + */ COMMAND, + + /** + * A custom cause, typically defined by external plugins or systems, applied the combat tag. + */ CUSTOM } diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/CauseOfUnTag.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/CauseOfUnTag.java index 6cf7958b..52e331cf 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/CauseOfUnTag.java +++ b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/CauseOfUnTag.java @@ -1,11 +1,38 @@ package com.eternalcode.combat.fight.event; public enum CauseOfUnTag { + /** + * The combat tag expired after a set duration. + */ TIME_EXPIRED, + + /** + * The player died, which resulted in the removal of the combat tag. + */ DEATH, + + /** + * The player was killed by another player, causing the combat tag to be removed. + */ DEATH_BY_PLAYER, + + /** + * The player logged out, triggering the removal of the combat tag. + */ LOGOUT, + + /** + * A command was executed to remove the player's combat tag. + */ COMMAND, + + /** + * The attacker released the player, ending the combat and removing the tag. + */ ATTACKER_RELEASE, + + /** + * A custom cause, typically defined by external plugins or systems. + */ CUSTOM } diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/FightTagEvent.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/FightTagEvent.java index 0775d9b3..0871c195 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/FightTagEvent.java +++ b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/FightTagEvent.java @@ -5,59 +5,80 @@ import org.bukkit.event.HandlerList; import java.util.UUID; +import org.jetbrains.annotations.NotNull; +/** + * This event is triggered when a player is tagged in a fight. + */ public class FightTagEvent extends Event implements Cancellable { - private static final HandlerList handlers = new HandlerList(); + private static final HandlerList HANDLER_LIST = new HandlerList(); private final UUID player; private final CauseOfTag cause; - private boolean isCancelled = false; - private String cancelMessage = ""; + private boolean cancelled = false; + private CancelTagReason cancelReason; public FightTagEvent(UUID player, CauseOfTag cause) { + super(false); + this.player = player; this.cause = cause; } + /** + * Gets the UUID of the player who was tagged. + * + * @return The UUID of the tagged player. + */ public UUID getPlayer() { return this.player; } + /** + * Gets the reason why the player was tagged. + * + * @return The cause of the combat tag as a {@link CauseOfTag}. + */ public CauseOfTag getCause() { return this.cause; } @Override public boolean isCancelled() { - return this.isCancelled; + return this.cancelled; } @Override public void setCancelled(boolean cancelled) { - throw new UnsupportedOperationException("Use #cancel(String) instead"); - } - - public void cancel(String cancelMessage) { - this.isCancelled = true; - this.cancelMessage = cancelMessage; + this.cancelled = cancelled; } - public void allow() { - this.isCancelled = false; - this.cancelMessage = ""; + /** + * Gets the reason why the event was cancelled. + * + * @return The cancel reason. + */ + public CancelTagReason getCancelReason() { + return this.cancelReason; } - public String getCancelMessage() { - return this.cancelMessage; + /** + * Sets the reason why the event was cancelled. + * + * @param cancelReason The cancel reason. + */ + public void setCancelReason(CancelTagReason cancelReason) { + this.cancelReason = cancelReason; } + @NotNull @Override public HandlerList getHandlers() { - return handlers; + return HANDLER_LIST; } public static HandlerList getHandlerList() { - return handlers; + return HANDLER_LIST; } } diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/FightUntagEvent.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/FightUntagEvent.java index 18fdfd11..f9913767 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/FightUntagEvent.java +++ b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/event/FightUntagEvent.java @@ -5,43 +5,61 @@ import org.bukkit.event.HandlerList; import java.util.UUID; +import org.jetbrains.annotations.NotNull; +/** + * This event is triggered when a player is untagged during a fight. + */ public class FightUntagEvent extends Event implements Cancellable { - private static final HandlerList handlers = new HandlerList(); + private static final HandlerList HANDLER_LIST = new HandlerList(); + private final UUID player; private final CauseOfUnTag cause; - private boolean isCancelled = false; + private boolean cancelled; public FightUntagEvent(UUID player, CauseOfUnTag cause) { + super(false); + this.player = player; this.cause = cause; } + /** + * Gets the UUID of the player who was untagged. + * + * @return The UUID of the untagged player. + */ public UUID getPlayer() { return this.player; } - + + /** + * Gets the reason for why the player was untagged. + * + * @return The cause of untagging as a {@link CauseOfUnTag}. + */ public CauseOfUnTag getCause() { return this.cause; } @Override public boolean isCancelled() { - return this.isCancelled; + return this.cancelled; } @Override public void setCancelled(boolean cancelled) { - this.isCancelled = cancelled; + this.cancelled = cancelled; } + @NotNull @Override public HandlerList getHandlers() { - return handlers; + return HANDLER_LIST; } public static HandlerList getHandlerList() { - return handlers; + return HANDLER_LIST; } } diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlService.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlService.java new file mode 100644 index 00000000..18d2b84f --- /dev/null +++ b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlService.java @@ -0,0 +1,16 @@ +package com.eternalcode.combat.fight.pearl; + +import java.time.Duration; +import java.time.Instant; +import java.util.UUID; + +public interface FightPearlService { + + Instant getDelay(UUID uuid); + + Duration getRemainingDelay(UUID uuid); + + boolean hasDelay(UUID uuid); + + void markDelay(UUID uuid); +} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutService.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutService.java index 9acec882..1565afa2 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutService.java +++ b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutService.java @@ -1,34 +1,13 @@ package com.eternalcode.combat.fight.tagout; import java.time.Duration; -import java.time.Instant; -import java.util.HashMap; -import java.util.Map; import java.util.UUID; -public class FightTagOutService { +public interface FightTagOutService { - private final Map tagOuts = new HashMap<>(); + boolean isTaggedOut(UUID player); - public void tagOut(UUID player, Duration duration) { - Instant endTime = Instant.now().plus(duration); - - this.tagOuts.put(player, endTime); - } - - public void unTagOut(UUID player) { - this.tagOuts.remove(player); - } - - public boolean isTaggedOut(UUID player) { - Instant endTime = this.tagOuts.get(player); - - if (endTime == null) { - return false; - } - Instant now = Instant.now(); - - return now.isBefore(endTime); - } + void unTagOut(UUID player); + void tagOut(UUID player, Duration duration); } diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/tagout/TagOutCommand.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/tagout/TagOutCommand.java deleted file mode 100644 index 02989310..00000000 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/tagout/TagOutCommand.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.eternalcode.combat.fight.tagout; - -import com.eternalcode.combat.config.implementation.PluginConfig; -import com.eternalcode.combat.notification.NotificationAnnouncer; -import com.eternalcode.combat.util.DurationUtil; -import dev.rollczi.litecommands.argument.Arg; -import dev.rollczi.litecommands.command.execute.Execute; -import dev.rollczi.litecommands.command.permission.Permission; -import dev.rollczi.litecommands.command.route.Route; -import org.bukkit.entity.Player; -import panda.utilities.text.Formatter; - -import java.time.Duration; -import java.time.Instant; -import java.util.UUID; - -@Route(name = "tagout", aliases = "tagimmunity") -public class TagOutCommand { - - private final FightTagOutService fightTagOutService; - private final NotificationAnnouncer announcer; - private final PluginConfig config; - - public TagOutCommand(FightTagOutService fightTagOutService, NotificationAnnouncer announcer, PluginConfig config) { - this.fightTagOutService = fightTagOutService; - this.announcer = announcer; - this.config = config; - } - - @Execute(required = 1) - @Permission("eternalcombat.tagout") - void tagout(Player sender, @Arg Duration time) { - UUID targetUniqueId = sender.getUniqueId(); - - Formatter formatter = new Formatter() - .register("{PLAYER}", sender.getName()) - .register("{TIME}", DurationUtil.format(time)); - - this.fightTagOutService.tagOut(targetUniqueId, time); - - String format = formatter.format(this.config.messages.admin.adminTagOutSelf); - this.announcer.sendMessage(sender, format); - } - - @Execute(required = 2) - @Permission("eternalcombat.tagout") - void tagout(Player sender, @Arg Player target, @Arg Duration time) { - UUID targetUniqueId = target.getUniqueId(); - - Instant now = Instant.now(); - Duration remaining = Duration.between(now, now.plus(time)); - - Formatter formatter = new Formatter() - .register("{PLAYER}", target.getName()) - .register("{TIME}", DurationUtil.format(remaining)); - - this.fightTagOutService.tagOut(targetUniqueId, time); - - String adminTagOutFormat = formatter.format(this.config.messages.admin.adminTagOut); - this.announcer.sendMessage(sender, adminTagOutFormat); - - String playerTagOutFormat = formatter.format(this.config.messages.admin.playerTagOut); - this.announcer.sendMessage(target, playerTagOutFormat); - } - - @Execute(route = "remove", required = 1) - @Permission("eternalcombat.tagout") - void untagout(Player sender, @Arg Player target) { - UUID targetUniqueId = target.getUniqueId(); - - this.fightTagOutService.unTagOut(targetUniqueId); - - Formatter formatter = new Formatter() - .register("{PLAYER}", target.getName()); - - if (!targetUniqueId.equals(sender.getUniqueId())) { - String adminUnTagOutFormat = formatter.format(this.config.messages.admin.adminTagOutOff); - this.announcer.sendMessage(sender, adminUnTagOutFormat); - } - - this.announcer.sendMessage(target, this.config.messages.admin.playerTagOutOff); - } - - @Execute(route = "remove", required = 0) - @Permission("eternalcombat.tagout") - void untagout(Player sender) { - UUID senderUniqueId = sender.getUniqueId(); - - this.fightTagOutService.unTagOut(senderUniqueId); - - this.announcer.sendMessage(sender, this.config.messages.admin.playerTagOutOff); - } -} - diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/Notification.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/Notification.java deleted file mode 100644 index a6bd16e1..00000000 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/Notification.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.eternalcode.combat.notification; - -public interface Notification { - - String message(); - - NotificationType type(); -} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/NotificationAnnouncer.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/NotificationAnnouncer.java deleted file mode 100644 index 47de9228..00000000 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/NotificationAnnouncer.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.eternalcode.combat.notification; - -import com.eternalcode.combat.notification.implementation.BossBarNotification; -import net.kyori.adventure.audience.Audience; -import net.kyori.adventure.bossbar.BossBar; -import net.kyori.adventure.platform.AudienceProvider; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.minimessage.MiniMessage; -import net.kyori.adventure.title.Title; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import panda.utilities.text.Formatter; - -public final class NotificationAnnouncer { - - private final AudienceProvider audienceProvider; - private final MiniMessage miniMessage; - - public NotificationAnnouncer(AudienceProvider audienceProvider, MiniMessage miniMessage) { - this.audienceProvider = audienceProvider; - this.miniMessage = miniMessage; - } - - public void send(CommandSender sender, Notification notification, Formatter formatter) { - Audience audience = this.audience(sender); - Component message = this.miniMessage.deserialize(formatter.format(notification.message())); - - NotificationType type = notification.type(); - - switch (type) { - case CHAT -> audience.sendMessage(message); - case ACTION_BAR -> audience.sendActionBar(message); - - case TITLE -> { - Title title = Title.title(message, Component.empty()); - - audience.showTitle(title); - } - - case SUB_TITLE -> { - Title subTitle = Title.title(Component.empty(), message); - - audience.showTitle(subTitle); - } - - case BOSS_BAR -> { - BossBarNotification bossBarNotification = (BossBarNotification) notification; - BossBar bossBar = bossBarNotification.create(message); - - audience.showBossBar(bossBar); - } - - default -> throw new IllegalStateException("Unknown notification type: " + type); - } - } - - public void sendMessage(CommandSender commandSender, String text) { - Audience audience = this.audience(commandSender); - Component message = this.miniMessage.deserialize(text); - - audience.sendMessage(message); - } - - public void broadcast(String text) { - Audience audience = this.audienceProvider.all(); - Component message = this.miniMessage.deserialize(text); - - audience.sendMessage(message); - } - - private Audience audience(CommandSender sender) { - if (sender instanceof Player player) { - return this.audienceProvider.player(player.getUniqueId()); - } - - return this.audienceProvider.console(); - } -} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/NotificationType.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/NotificationType.java deleted file mode 100644 index b4fff54d..00000000 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/NotificationType.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.eternalcode.combat.notification; - -public enum NotificationType { - CHAT, - ACTION_BAR, - TITLE, - SUB_TITLE, - BOSS_BAR -} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/implementation/ActionBarNotification.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/implementation/ActionBarNotification.java deleted file mode 100644 index 03fc6318..00000000 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/implementation/ActionBarNotification.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.eternalcode.combat.notification.implementation; - -import com.eternalcode.combat.notification.Notification; -import com.eternalcode.combat.notification.NotificationType; - -public record ActionBarNotification(String message) implements Notification { - - @Override - public NotificationType type() { - return NotificationType.ACTION_BAR; - } -} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/implementation/BossBarNotification.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/implementation/BossBarNotification.java deleted file mode 100644 index b9881d73..00000000 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/implementation/BossBarNotification.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.eternalcode.combat.notification.implementation; - -import com.eternalcode.combat.notification.Notification; -import com.eternalcode.combat.notification.NotificationType; -import net.kyori.adventure.bossbar.BossBar; -import net.kyori.adventure.text.Component; - -public record BossBarNotification(String message, float progress, BossBar.Color color, BossBar.Overlay overlay) implements Notification { - - public BossBar create(Component name) { - float replacedProgress = this.progress < 0.0F ? BossBar.MAX_PROGRESS : this.progress; - - return BossBar.bossBar(name, replacedProgress, this.color, this.overlay); - } - - @Override - public NotificationType type() { - return NotificationType.BOSS_BAR; - } -} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/implementation/ChatNotification.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/implementation/ChatNotification.java deleted file mode 100644 index 51fbc937..00000000 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/implementation/ChatNotification.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.eternalcode.combat.notification.implementation; - -import com.eternalcode.combat.notification.Notification; -import com.eternalcode.combat.notification.NotificationType; - -public record ChatNotification(String message) implements Notification { - - @Override - public NotificationType type() { - return NotificationType.CHAT; - } -} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/implementation/SubTitleNotification.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/implementation/SubTitleNotification.java deleted file mode 100644 index 350a2075..00000000 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/implementation/SubTitleNotification.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.eternalcode.combat.notification.implementation; - -import com.eternalcode.combat.notification.Notification; -import com.eternalcode.combat.notification.NotificationType; - -public record SubTitleNotification(String message) implements Notification { - - @Override - public NotificationType type() { - return NotificationType.SUB_TITLE; - } -} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/implementation/TitleNotification.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/implementation/TitleNotification.java deleted file mode 100644 index cae763db..00000000 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/implementation/TitleNotification.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.eternalcode.combat.notification.implementation; - -import com.eternalcode.combat.notification.Notification; -import com.eternalcode.combat.notification.NotificationType; - -public record TitleNotification(String message) implements Notification { - - @Override - public NotificationType type() { - return NotificationType.TITLE; - } -} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/serializer/NotificationSerializer.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/serializer/NotificationSerializer.java deleted file mode 100644 index 252c6c2b..00000000 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/notification/serializer/NotificationSerializer.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.eternalcode.combat.notification.serializer; - -import com.eternalcode.combat.notification.Notification; -import com.eternalcode.combat.notification.NotificationType; -import com.eternalcode.combat.notification.implementation.ActionBarNotification; -import com.eternalcode.combat.notification.implementation.BossBarNotification; -import com.eternalcode.combat.notification.implementation.ChatNotification; -import com.eternalcode.combat.notification.implementation.SubTitleNotification; -import com.eternalcode.combat.notification.implementation.TitleNotification; -import eu.okaeri.configs.schema.GenericsDeclaration; -import eu.okaeri.configs.serdes.DeserializationData; -import eu.okaeri.configs.serdes.ObjectSerializer; -import eu.okaeri.configs.serdes.SerializationData; -import net.kyori.adventure.bossbar.BossBar; -import org.checkerframework.checker.nullness.qual.NonNull; - -import java.util.Optional; - -public class NotificationSerializer implements ObjectSerializer { - - @Override - public boolean supports(@NonNull Class type) { - return Notification.class.isAssignableFrom(type); - } - - @Override - public void serialize(@NonNull Notification notification, @NonNull SerializationData data, @NonNull GenericsDeclaration generics) { - data.add("type", notification.type(), NotificationType.class); - data.add("message", notification.message(), String.class); - - if (notification instanceof BossBarNotification bossBarNotification) { - data.add("progress", bossBarNotification.progress(), float.class); - data.add("color", bossBarNotification.color(), BossBar.Color.class); - data.add("overlay", bossBarNotification.overlay(), BossBar.Overlay.class); - } - } - - @Override - public Notification deserialize(@NonNull DeserializationData data, @NonNull GenericsDeclaration generics) { - NotificationType type = data.get("type", NotificationType.class); - String message = data.get("message", String.class); - - return switch (type) { - case CHAT -> new ChatNotification(message); - case ACTION_BAR -> new ActionBarNotification(message); - - case TITLE -> new TitleNotification(message); - case SUB_TITLE -> new SubTitleNotification(message); - - case BOSS_BAR -> { - float progress = Optional.ofNullable(data.get("progress", float.class)) - .orElse(BossBar.MAX_PROGRESS); - - BossBar.Color color = Optional.ofNullable(data.get("color", BossBar.Color.class)) - .orElse(BossBar.Color.RED); - - BossBar.Overlay overlay = Optional.ofNullable(data.get("overlay", BossBar.Overlay.class)) - .orElse(BossBar.Overlay.PROGRESS); - - yield new BossBarNotification(message, progress, color, overlay); - } - }; - } -} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/region/DefaultRegionProvider.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/region/DefaultRegionProvider.java deleted file mode 100644 index 8f948ce3..00000000 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/region/DefaultRegionProvider.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.eternalcode.combat.region; - -import java.util.Optional; -import org.bukkit.Location; -import org.bukkit.util.BlockVector; - -public class DefaultRegionProvider implements RegionProvider { - - private final int radius; - - public DefaultRegionProvider(int radius) { - this.radius = radius; - } - - @Override - public Optional getRegion(Location location) { - Location spawnLocation = location.getWorld().getSpawnLocation(); - double x = spawnLocation.getX(); - double z = spawnLocation.getZ(); - - BlockVector min = new BlockVector(x - this.radius, 0, z - this.radius); - BlockVector max = new BlockVector(x + this.radius, 0, z + this.radius); - - if (this.contains(min, max, location.getX(), location.getZ())) { - return Optional.of(() -> new Location(location.getWorld(), x, location.getY(), z)); - } - - return Optional.empty(); - } - - private boolean contains(BlockVector min, BlockVector max, double x, double z) { - return x >= min.getX() && x < max.getX() - && z >= min.getZ() && z < max.getZ(); - } - -} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/region/Region.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/region/Region.java index fecd119a..58ab7ea8 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/region/Region.java +++ b/eternalcombat-api/src/main/java/com/eternalcode/combat/region/Region.java @@ -6,4 +6,20 @@ public interface Region { Location getCenter(); + Location getMin(); + + Location getMax(); + + default boolean contains(Location location) { + return this.contains(location.getX(), location.getY(), location.getZ()); + } + + default boolean contains(double x, double y, double z) { + Location min = this.getMin(); + Location max = this.getMax(); + return x >= min.getX() && x < max.getX() + && y >= min.getY() && y < max.getY() + && z >= min.getZ() && z < max.getZ(); + } + } diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/region/RegionController.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/region/RegionController.java deleted file mode 100644 index 9039be19..00000000 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/region/RegionController.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.eternalcode.combat.region; - -import com.eternalcode.combat.config.implementation.PluginConfig; -import com.eternalcode.combat.fight.FightManager; -import com.eternalcode.combat.notification.NotificationAnnouncer; -import java.util.Optional; -import org.bukkit.Location; -import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerMoveEvent; -import org.bukkit.event.player.PlayerTeleportEvent; -import org.bukkit.util.Vector; - -public class RegionController implements Listener { - - private final NotificationAnnouncer announcer; - private final RegionProvider regionProvider; - private final FightManager fightManager; - private final PluginConfig pluginConfig; - - public RegionController(NotificationAnnouncer announcer, RegionProvider regionProvider, FightManager fightManager, PluginConfig pluginConfig) { - this.announcer = announcer; - this.regionProvider = regionProvider; - this.fightManager = fightManager; - this.pluginConfig = pluginConfig; - } - - @EventHandler - void onPlayerMove(PlayerMoveEvent event) { - Player player = event.getPlayer(); - - if (!this.fightManager.isInCombat(player.getUniqueId())) { - return; - } - - Location locationTo = event.getTo(); - int xTo = locationTo.getBlockX(); - int yTo = locationTo.getBlockY(); - int zTo = locationTo.getBlockZ(); - - Location locationFrom = event.getFrom(); - int xFrom = locationFrom.getBlockX(); - int yFrom = locationFrom.getBlockY(); - int zFrom = locationFrom.getBlockZ(); - - if (xTo != xFrom || yTo != yFrom || zTo != zFrom) { - Optional regionOptional = regionProvider.getRegion(locationTo); - if (regionOptional.isEmpty()) { - return; - } - - Region region = regionOptional.get(); - Location centerOfRegion = region.getCenter(); - Location subtract = player.getLocation().subtract(centerOfRegion); - - Vector knockbackVector = new Vector(subtract.getX(), 0, subtract.getZ()).normalize(); - Vector configuredVector = new Vector( - this.pluginConfig.settings.blockedRegionKnockMultiplier, - 0.5, - this.pluginConfig.settings.blockedRegionKnockMultiplier); - - player.setVelocity(knockbackVector.multiply(configuredVector)); - - this.announcer.sendMessage(player, this.pluginConfig.messages.cantEnterOnRegion); - } - } - - @EventHandler - public void onPlayerTeleport(PlayerTeleportEvent event) { - Player player = event.getPlayer(); - - if (!this.fightManager.isInCombat(player.getUniqueId())) { - return; - } - - Location targetLocation = event.getTo(); - - if (this.regionProvider.isInRegion(targetLocation)) { - event.setCancelled(true); - this.announcer.sendMessage(player, this.pluginConfig.messages.cantEnterOnRegion); - } - } -} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/region/RegionProvider.java b/eternalcombat-api/src/main/java/com/eternalcode/combat/region/RegionProvider.java index a60c7fe9..25051e96 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/region/RegionProvider.java +++ b/eternalcombat-api/src/main/java/com/eternalcode/combat/region/RegionProvider.java @@ -1,14 +1,18 @@ package com.eternalcode.combat.region; +import java.util.Collection; import java.util.Optional; import org.bukkit.Location; +import org.bukkit.World; public interface RegionProvider { Optional getRegion(Location location); default boolean isInRegion(Location location) { - return getRegion(location).isPresent(); + return this.getRegion(location).isPresent(); } + Collection getRegions(World world); + } diff --git a/eternalcombat-plugin/build.gradle.kts b/eternalcombat-plugin/build.gradle.kts index 9143031e..612c09a8 100644 --- a/eternalcombat-plugin/build.gradle.kts +++ b/eternalcombat-plugin/build.gradle.kts @@ -9,6 +9,53 @@ plugins { dependencies { implementation(project(":eternalcombat-api")) + + // kyori + implementation("net.kyori:adventure-platform-bukkit:${Versions.ADVENTURE_PLATFORM_BUKKIT}") + implementation("net.kyori:adventure-text-minimessage:${Versions.ADVENTURE_API}") + implementation("net.kyori:adventure-api") { + version { + strictly(Versions.ADVENTURE_API) + } + } + + // litecommands + implementation("dev.rollczi:litecommands-bukkit:${Versions.LITE_COMMANDS}") + + // Okaeri configs + implementation("eu.okaeri:okaeri-configs-yaml-bukkit:${Versions.OKAERI_CONFIGS_YAML_BUKKIT}") + implementation("eu.okaeri:okaeri-configs-serdes-commons:${Versions.OKAERI_CONFIGS_SERDES_COMMONS}") + implementation("eu.okaeri:okaeri-configs-serdes-bukkit:${Versions.OKAERI_CONFIGS_SERDES_BUKKIT}") + + // Panda utilities + implementation("org.panda-lang:panda-utilities:${Versions.PANDA_UTILITIES}") + + // GitCheck + implementation("com.eternalcode:gitcheck:${Versions.GIT_CHECK}") + + // commons + implementation("commons-io:commons-io:${Versions.APACHE_COMMONS}") + + // bstats + implementation("org.bstats:bstats-bukkit:${Versions.B_STATS_BUKKIT}") + + // caffeine + implementation("com.github.ben-manes.caffeine:caffeine:${Versions.CAFFEINE}") + + implementation("com.eternalcode:eternalcode-commons-bukkit:${Versions.ETERNALCODE_COMMONS}") + implementation("com.eternalcode:eternalcode-commons-adventure:${Versions.ETERNALCODE_COMMONS}") + + // worldguard + compileOnly("com.sk89q.worldguard:worldguard-bukkit:${Versions.WORLD_GUARD_BUKKIT}") + + // PlaceholderAPI + compileOnly("me.clip:placeholderapi:${Versions.PLACEHOLDER_API}") + + // Multification + implementation("com.eternalcode:multification-bukkit:${Versions.MULTIFICATION}") + implementation("com.eternalcode:multification-okaeri:${Versions.MULTIFICATION}") + implementation("com.github.retrooper:packetevents-spigot:${Versions.PACKETS_EVENTS}") + implementation("io.papermc:paperlib:${Versions.PAPERLIB}") } bukkit { @@ -23,7 +70,7 @@ bukkit { tasks { runServer { - minecraftVersion("1.21.1") + minecraftVersion("1.21.4") } } @@ -54,7 +101,8 @@ tasks.shadowJar { "org.apache.commons", "javassist", "com.github.benmanes.caffeine", - "com.eternalcode.commons" + "com.eternalcode.commons", + "com.eternalcode.multification" ).forEach { pack -> relocate(pack, "$prefix.$pack") } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java index 8451463f..d0593a72 100644 --- a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/CombatPlugin.java @@ -1,51 +1,66 @@ package com.eternalcode.combat; +import com.eternalcode.combat.border.BorderTriggerController; +import com.eternalcode.combat.border.BorderService; +import com.eternalcode.combat.border.BorderServiceImpl; +import com.eternalcode.combat.border.animation.block.BorderBlockController; +import com.eternalcode.combat.border.animation.particle.ParticleController; import com.eternalcode.combat.bridge.BridgeService; -import com.eternalcode.combat.command.InvalidUsage; -import com.eternalcode.combat.command.PermissionMessage; +import com.eternalcode.combat.fight.drop.DropKeepInventoryService; +import com.eternalcode.combat.fight.FightManager; +import com.eternalcode.combat.fight.drop.DropService; +import com.eternalcode.combat.fight.effect.FightEffectService; +import com.eternalcode.combat.fight.knockback.KnockbackService; +import com.eternalcode.combat.fight.tagout.FightTagOutService; +import com.eternalcode.combat.fight.pearl.FightPearlService; +import com.eternalcode.combat.handler.InvalidUsageHandlerImpl; +import com.eternalcode.combat.handler.MissingPermissionHandlerImpl; import com.eternalcode.combat.config.ConfigService; import com.eternalcode.combat.config.implementation.PluginConfig; -import com.eternalcode.combat.drop.DropController; -import com.eternalcode.combat.drop.DropKeepInventoryManager; -import com.eternalcode.combat.drop.DropManager; -import com.eternalcode.combat.drop.impl.PercentDropModifier; -import com.eternalcode.combat.drop.impl.PlayersHealthDropModifier; +import com.eternalcode.combat.fight.drop.DropController; +import com.eternalcode.combat.fight.drop.DropKeepInventoryServiceImpl; +import com.eternalcode.combat.fight.drop.DropServiceImpl; +import com.eternalcode.combat.fight.drop.impl.PercentDropModifier; +import com.eternalcode.combat.fight.drop.impl.PlayersHealthDropModifier; +import com.eternalcode.combat.fight.FightTagCommand; import com.eternalcode.combat.fight.controller.FightActionBlockerController; import com.eternalcode.combat.fight.controller.FightMessageController; import com.eternalcode.combat.fight.controller.FightTagController; import com.eternalcode.combat.fight.controller.FightUnTagController; import com.eternalcode.combat.fight.effect.FightEffectController; import com.eternalcode.combat.event.EventCaller; -import com.eternalcode.combat.fight.FightManager; +import com.eternalcode.combat.fight.FightManagerImpl; import com.eternalcode.combat.fight.FightTask; -import com.eternalcode.combat.fight.bossbar.FightBossBarService; -import com.eternalcode.combat.fight.effect.FightEffectService; +import com.eternalcode.combat.fight.effect.FightEffectServiceImpl; import com.eternalcode.combat.fight.logout.LogoutController; import com.eternalcode.combat.fight.logout.LogoutService; import com.eternalcode.combat.fight.pearl.FightPearlController; -import com.eternalcode.combat.fight.pearl.FightPearlManager; +import com.eternalcode.combat.fight.pearl.FightPearlServiceImpl; import com.eternalcode.combat.fight.tagout.FightTagOutController; -import com.eternalcode.combat.fight.tagout.FightTagOutService; -import com.eternalcode.combat.fight.tagout.TagOutCommand; -import com.eternalcode.combat.notification.NotificationAnnouncer; -import com.eternalcode.combat.region.RegionController; +import com.eternalcode.combat.fight.tagout.FightTagOutServiceImpl; +import com.eternalcode.combat.fight.tagout.FightTagOutCommand; +import com.eternalcode.combat.notification.NoticeService; +import com.eternalcode.combat.fight.knockback.KnockbackRegionController; import com.eternalcode.combat.region.RegionProvider; import com.eternalcode.combat.updater.UpdaterNotificationController; import com.eternalcode.combat.updater.UpdaterService; import com.eternalcode.commons.adventure.AdventureLegacyColorPostProcessor; import com.eternalcode.commons.adventure.AdventureLegacyColorPreProcessor; +import com.eternalcode.commons.bukkit.scheduler.BukkitSchedulerImpl; +import com.eternalcode.commons.scheduler.Scheduler; +import com.eternalcode.multification.notice.Notice; +import com.github.retrooper.packetevents.PacketEvents; import com.google.common.base.Stopwatch; import dev.rollczi.litecommands.LiteCommands; -import dev.rollczi.litecommands.bukkit.adventure.platform.LiteBukkitAdventurePlatformFactory; -import dev.rollczi.litecommands.bukkit.tools.BukkitOnlyPlayerContextual; -import dev.rollczi.litecommands.bukkit.tools.BukkitPlayerArgument; +import dev.rollczi.litecommands.bukkit.LiteBukkitFactory; +import dev.rollczi.litecommands.bukkit.LiteBukkitMessages; +import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder; import net.kyori.adventure.platform.AudienceProvider; import net.kyori.adventure.platform.bukkit.BukkitAudiences; import net.kyori.adventure.text.minimessage.MiniMessage; import org.bstats.bukkit.Metrics; import org.bukkit.Server; import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; import java.io.File; @@ -58,14 +73,14 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi { private static final int BSTATS_METRICS_ID = 17803; private FightManager fightManager; - private FightPearlManager fightPearlManager; + private FightPearlService fightPearlService; private FightTagOutService fightTagOutService; private FightEffectService fightEffectService; private LogoutService logoutService; - private DropManager dropManager; - private DropKeepInventoryManager dropKeepInventoryManager; + private DropService dropService; + private DropKeepInventoryService dropKeepInventoryService; private RegionProvider regionProvider; @@ -74,6 +89,12 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi { private AudienceProvider audienceProvider; private LiteCommands liteCommands; + @Override + public void onLoad() { + PacketEvents.setAPI(SpigotPacketEventsBuilder.build(this)); + PacketEvents.getAPI().load(); + } + @Override public void onEnable() { Stopwatch started = Stopwatch.createStarted(); @@ -84,16 +105,17 @@ public void onEnable() { ConfigService configService = new ConfigService(); EventCaller eventCaller = new EventCaller(server); + Scheduler scheduler = new BukkitSchedulerImpl(this); this.pluginConfig = configService.create(PluginConfig.class, new File(dataFolder, "config.yml")); - this.fightManager = new FightManager(eventCaller); - this.fightPearlManager = new FightPearlManager(this.pluginConfig.pearl); - this.fightTagOutService = new FightTagOutService(); - this.fightEffectService = new FightEffectService(); + this.fightManager = new FightManagerImpl(eventCaller); + this.fightPearlService = new FightPearlServiceImpl(this.pluginConfig.pearl); + this.fightTagOutService = new FightTagOutServiceImpl(); + this.fightEffectService = new FightEffectServiceImpl(); this.logoutService = new LogoutService(); - this.dropManager = new DropManager(); - this.dropKeepInventoryManager = new DropKeepInventoryManager(); + this.dropService = new DropServiceImpl(); + this.dropKeepInventoryService = new DropKeepInventoryServiceImpl(); UpdaterService updaterService = new UpdaterService(this.getDescription()); @@ -103,49 +125,60 @@ public void onEnable() { .preProcessor(new AdventureLegacyColorPreProcessor()) .build(); - FightBossBarService fightBossBarService = new FightBossBarService(this.pluginConfig, this.audienceProvider, miniMessage); - BridgeService bridgeService = new BridgeService(this.pluginConfig, server.getPluginManager(), this.getLogger(), this); bridgeService.init(this.fightManager, server); this.regionProvider = bridgeService.getRegionProvider(); + BorderService borderService = new BorderServiceImpl(scheduler, server, regionProvider, eventCaller, pluginConfig.border); + KnockbackService knockbackService = new KnockbackService(this.pluginConfig, scheduler); - NotificationAnnouncer notificationAnnouncer = new NotificationAnnouncer(this.audienceProvider, miniMessage); + NoticeService noticeService = new NoticeService(this.audienceProvider, this.pluginConfig, miniMessage); - this.liteCommands = LiteBukkitAdventurePlatformFactory.builder(server, FALLBACK_PREFIX, this.audienceProvider) - .argument(Player.class, new BukkitPlayerArgument<>(this.getServer(), this.pluginConfig.messages.playerNotFound)) - .contextualBind(Player.class, new BukkitOnlyPlayerContextual<>(this.pluginConfig.messages.admin.onlyForPlayers)) + this.liteCommands = LiteBukkitFactory.builder(FALLBACK_PREFIX, this, server) + .message(LiteBukkitMessages.PLAYER_NOT_FOUND, this.pluginConfig.messagesSettings.playerNotFound) + .message(LiteBukkitMessages.PLAYER_ONLY, this.pluginConfig.messagesSettings.admin.onlyForPlayers) - .invalidUsageHandler(new InvalidUsage(this.pluginConfig, notificationAnnouncer)) - .permissionHandler(new PermissionMessage(this.pluginConfig, notificationAnnouncer)) + .invalidUsage(new InvalidUsageHandlerImpl(this.pluginConfig, noticeService)) + .missingPermission(new MissingPermissionHandlerImpl(this.pluginConfig, noticeService)) - .commandInstance(new CombatCommand(this.fightManager, configService, notificationAnnouncer, this.pluginConfig)) - .commandInstance(new TagOutCommand(this.fightTagOutService, notificationAnnouncer, this.pluginConfig)) + .commands( + new FightTagCommand(this.fightManager, noticeService, this.pluginConfig), + new FightTagOutCommand(this.fightTagOutService, noticeService, this.pluginConfig), + new EternalCombatReloadCommand(configService, noticeService) + ) - .register(); + .result(Notice.class, (invocation, result, chain) -> noticeService.create() + .viewer(invocation.sender()) + .notice(result) + .send()) + + .build(); - FightTask fightTask = new FightTask(server, this.pluginConfig, this.fightManager, fightBossBarService, notificationAnnouncer); + FightTask fightTask = new FightTask(server, this.pluginConfig, this.fightManager, noticeService); this.getServer().getScheduler().runTaskTimer(this, fightTask, 20L, 20L); new Metrics(this, BSTATS_METRICS_ID); Stream.of( - new PercentDropModifier(this.pluginConfig.dropSettings), - new PlayersHealthDropModifier(this.pluginConfig.dropSettings, this.logoutService) - ).forEach(this.dropManager::registerModifier); + new PercentDropModifier(this.pluginConfig.drop), + new PlayersHealthDropModifier(this.pluginConfig.drop, this.logoutService) + ).forEach(this.dropService::registerModifier); Stream.of( - new DropController(this.dropManager, this.dropKeepInventoryManager, this.pluginConfig.dropSettings, this.fightManager), + new DropController(this.dropService, this.dropKeepInventoryService, this.pluginConfig.drop, this.fightManager), new FightTagController(this.fightManager, this.pluginConfig), - new LogoutController(this.fightManager, this.logoutService, notificationAnnouncer, this.pluginConfig), + new LogoutController(this.fightManager, this.logoutService, noticeService, this.pluginConfig), new FightUnTagController(this.fightManager, this.pluginConfig, this.logoutService), - new FightActionBlockerController(this.fightManager, notificationAnnouncer, this.pluginConfig), - new FightPearlController(this.pluginConfig.pearl, notificationAnnouncer, this.fightManager, this.fightPearlManager), + new FightActionBlockerController(this.fightManager, noticeService, this.pluginConfig, server), + new FightPearlController(this.pluginConfig.pearl, noticeService, this.fightManager, this.fightPearlService), new UpdaterNotificationController(updaterService, this.pluginConfig, this.audienceProvider, miniMessage), - new RegionController(notificationAnnouncer, this.regionProvider, this.fightManager, this.pluginConfig), + new KnockbackRegionController(noticeService, this.regionProvider, this.fightManager, knockbackService, server), new FightEffectController(this.pluginConfig.effect, this.fightEffectService, this.fightManager, this.getServer()), - new FightTagOutController(this.fightTagOutService, this.pluginConfig), - new FightMessageController(this.fightManager, notificationAnnouncer, fightBossBarService, this.pluginConfig, this.getServer()) + new FightTagOutController(this.fightTagOutService), + new FightMessageController(this.fightManager, noticeService, this.pluginConfig, this.getServer()), + new BorderTriggerController(borderService, pluginConfig.border, fightManager, server), + new ParticleController(borderService, pluginConfig.border.particle, scheduler, server), + new BorderBlockController(borderService, pluginConfig.border.block, scheduler, server) ).forEach(listener -> this.getServer().getPluginManager().registerEvents(listener, this)); EternalCombatProvider.initialize(this); @@ -159,7 +192,7 @@ public void onDisable() { EternalCombatProvider.deinitialize(); if (this.liteCommands != null) { - this.liteCommands.getPlatform().unregisterAll(); + this.liteCommands.unregister(); } if (this.audienceProvider != null) { @@ -167,6 +200,8 @@ public void onDisable() { } this.fightManager.untagAll(); + + PacketEvents.getAPI().terminate(); } @Override @@ -180,8 +215,8 @@ public RegionProvider getRegionProvider() { } @Override - public FightPearlManager getFightPearlManager() { - return this.fightPearlManager; + public FightPearlService getFightPearlService() { + return this.fightPearlService; } @Override @@ -195,17 +230,12 @@ public FightEffectService getFightEffectService() { } @Override - public DropManager getDropManager() { - return this.dropManager; - } - - @Override - public DropKeepInventoryManager getDropKeepInventoryManager() { - return this.dropKeepInventoryManager; + public DropService getDropService() { + return this.dropService; } @Override - public PluginConfig getPluginConfig() { - return this.pluginConfig; + public DropKeepInventoryService getDropKeepInventoryService() { + return this.dropKeepInventoryService; } } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/EternalCombatReloadCommand.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/EternalCombatReloadCommand.java new file mode 100644 index 00000000..c47a3aa2 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/EternalCombatReloadCommand.java @@ -0,0 +1,46 @@ +package com.eternalcode.combat; + +import com.eternalcode.combat.config.ConfigService; +import com.eternalcode.combat.notification.NoticeService; +import com.eternalcode.multification.bukkit.notice.BukkitNotice; +import com.eternalcode.multification.notice.Notice; +import com.google.common.base.Stopwatch; +import dev.rollczi.litecommands.annotations.async.Async; +import dev.rollczi.litecommands.annotations.command.Command; +import dev.rollczi.litecommands.annotations.context.Context; +import dev.rollczi.litecommands.annotations.execute.Execute; +import dev.rollczi.litecommands.annotations.permission.Permission; +import java.time.Duration; +import org.bukkit.command.CommandSender; + +@Command(name = "combatlog", aliases = "combat") +@Permission("eternalcombat.reload") +public class EternalCombatReloadCommand { + + private static final Notice RELOAD_MESSAGE = BukkitNotice.builder() + .chat("EternalCombat: Reloaded EternalCombat in {TIME}ms!") + .build(); + + private final ConfigService configService; + private final NoticeService announcer; + + public EternalCombatReloadCommand(ConfigService configService, NoticeService announcer) { + this.configService = configService; + this.announcer = announcer; + } + + @Async + @Execute(name = "reload") + void execute(@Context CommandSender sender) { + Stopwatch stopwatch = Stopwatch.createStarted(); + this.configService.reload(); + + Duration elapsed = stopwatch.elapsed(); + this.announcer.create() + .viewer(sender) + .notice(RELOAD_MESSAGE) + .placeholder("{TIME}", String.valueOf(elapsed.toMillis())) + .send(); + + } +} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/WhitelistBlacklistMode.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/WhitelistBlacklistMode.java similarity index 100% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/WhitelistBlacklistMode.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/WhitelistBlacklistMode.java diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderActivePointsIndex.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderActivePointsIndex.java new file mode 100644 index 00000000..085e0154 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderActivePointsIndex.java @@ -0,0 +1,66 @@ +package com.eternalcode.combat.border; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +class BorderActivePointsIndex { + + private final Map>> registry = new ConcurrentHashMap<>(); + + boolean hasPoints(String world, UUID player) { + Map> worldRegistry = this.registry.get(world); + if (worldRegistry == null) { + return false; + } + + return worldRegistry.containsKey(player); + } + + Set putPoints(String world, UUID player, Set points) { + Map> worldRegistry = this.registry.computeIfAbsent(world, k -> new ConcurrentHashMap<>()); + Set oldPoints = worldRegistry.put(player, points); + if (oldPoints != null) { + return calculateRemovedPoints(points, oldPoints); + } + + return Set.of(); + } + + private static Set calculateRemovedPoints(Set points, Set oldPoints) { + Set removed = new HashSet<>(); + for (BorderPoint oldPoint : oldPoints) { + if (!points.contains(oldPoint)) { + removed.add(oldPoint); + } + } + return Collections.unmodifiableSet(removed); + } + + Set getPoints(String world, UUID player) { + Map> worldRegistry = this.registry.get(world); + if (worldRegistry == null) { + return Set.of(); + } + + return worldRegistry.getOrDefault(player, Set.of()); + } + + Set removePoints(String world, UUID player) { + Map> worldRegistry = this.registry.get(world); + if (worldRegistry == null) { + return Set.of(); + } + + Set remove = worldRegistry.remove(player); + if (remove == null) { + return Set.of(); + } + + return remove; + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderLazyResult.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderLazyResult.java new file mode 100644 index 00000000..150a68c6 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderLazyResult.java @@ -0,0 +1,24 @@ +package com.eternalcode.combat.border; + +import dev.rollczi.litecommands.shared.Lazy; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +class BorderLazyResult implements BorderResult { + + private final List>> borderPoints = new ArrayList<>(); + + void addLazyBorderPoints(Lazy> supplier) { + borderPoints.add(supplier); + } + + @Override + public Set collect() { + return borderPoints.stream() + .flatMap(result -> result.get().stream()) + .collect(Collectors.toUnmodifiableSet()); + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderPoint.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderPoint.java new file mode 100644 index 00000000..b76dec0b --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderPoint.java @@ -0,0 +1,19 @@ +package com.eternalcode.combat.border; + +import org.jetbrains.annotations.Nullable; + +public record BorderPoint(int x, int y, int z, @Nullable BorderPoint inclusive) { + + public BorderPoint(int x, int y, int z) { + this(x, y, z, null); + } + + public BorderPoint toInclusive() { + if (inclusive == null) { + return this; + } + + return inclusive; + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderResult.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderResult.java new file mode 100644 index 00000000..8b991673 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderResult.java @@ -0,0 +1,9 @@ +package com.eternalcode.combat.border; + +import java.util.Set; + +public interface BorderResult { + + Set collect(); + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderService.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderService.java new file mode 100644 index 00000000..bb62251d --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderService.java @@ -0,0 +1,15 @@ +package com.eternalcode.combat.border; + +import java.util.Set; +import org.bukkit.Location; +import org.bukkit.entity.Player; + +public interface BorderService { + + void updateBorder(Player player, Location to); + + void clearBorder(Player player); + + Set getActiveBorder(Player player); + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderServiceImpl.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderServiceImpl.java new file mode 100644 index 00000000..0d88d928 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderServiceImpl.java @@ -0,0 +1,182 @@ +package com.eternalcode.combat.border; + +import com.eternalcode.combat.border.event.BorderHideAsyncEvent; +import com.eternalcode.combat.border.event.BorderShowAsyncEvent; +import com.eternalcode.combat.event.EventCaller; +import com.eternalcode.combat.region.RegionProvider; +import com.eternalcode.commons.scheduler.Scheduler; +import dev.rollczi.litecommands.shared.Lazy; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import org.bukkit.Location; +import org.bukkit.Server; +import org.bukkit.World; +import org.bukkit.entity.Player; + +public class BorderServiceImpl implements BorderService { + + private final Scheduler scheduler; + private final EventCaller eventCaller; + + private final BorderSettings settings; + + private final BorderTriggerIndex borderIndexes; + private final BorderActivePointsIndex activeBorderIndex = new BorderActivePointsIndex(); + + public BorderServiceImpl(Scheduler scheduler, Server server, RegionProvider provider, EventCaller eventCaller, BorderSettings settings) { + this.scheduler = scheduler; + this.eventCaller = eventCaller; + this.settings = settings; + this.borderIndexes = BorderTriggerIndex.started(server, scheduler, provider, settings); + } + + @Override + public void updateBorder(Player player, Location location) { + Optional result = resolveBorder(location); + String world = player.getWorld().getName(); + + if (result.isEmpty()) { + if (!this.activeBorderIndex.hasPoints(world, player.getUniqueId())) { + return; + } + + this.clearBorder(player); + return; + } + + scheduler.async(() -> { + BorderResult borderResult = result.get(); + Set points = borderResult.collect(); + + if (!points.isEmpty()) { + BorderShowAsyncEvent event = eventCaller.publishEvent(new BorderShowAsyncEvent(player, points)); + points = event.getPoints(); + } + + Set removed = this.activeBorderIndex.putPoints(world, player.getUniqueId(), points); + + if (!removed.isEmpty()) { + eventCaller.publishEvent(new BorderHideAsyncEvent(player, removed)); + } + }); + } + + @Override + public void clearBorder(Player player) { + World world = player.getWorld(); + UUID uniqueId = player.getUniqueId(); + + scheduler.async(() -> { + Set removed = this.activeBorderIndex.removePoints(world.getName(), uniqueId); + if (!removed.isEmpty()) { + eventCaller.publishEvent(new BorderHideAsyncEvent(player, removed)); + } + }); + } + + @Override + public Set getActiveBorder(Player player) { + return this.activeBorderIndex.getPoints(player.getWorld().getName(), player.getUniqueId()); + } + + private Optional resolveBorder(Location location) { + List triggered = borderIndexes.getTriggered(location); + + if (triggered.isEmpty()) { + return Optional.empty(); + } + + BorderLazyResult result = new BorderLazyResult(); + for (BorderTrigger trigger : triggered) { + result.addLazyBorderPoints(new Lazy<>(() -> this.resolveBorderPoints(trigger, location))); + } + + return Optional.of(result); + } + + /* this code is ugly but is fast */ + private List resolveBorderPoints(BorderTrigger trigger, Location playerLocation) { + BorderPoint borderMin = trigger.min(); + BorderPoint borderMax = trigger.max(); + + int x = (int) Math.round(playerLocation.getX()); + int y = (int) Math.round(playerLocation.getY()); + int z = (int) Math.round(playerLocation.getZ()); + + int realMinX = Math.max(borderMin.x(), x - settings.distanceRounded()); + int realMaxX = Math.min(borderMax.x(), x + settings.distanceRounded()); + int realMinY = Math.max(borderMin.y(), y - settings.distanceRounded()); + int realMaxY = Math.min(borderMax.y(), y + settings.distanceRounded()); + int realMinZ = Math.max(borderMin.z(), z - settings.distanceRounded()); + int realMaxZ = Math.min(borderMax.z(), z + settings.distanceRounded()); + + List points = new ArrayList<>(); + + if (borderMin.y() >= realMinY) { // Bottom wall + for (int currentX = realMinX; currentX <= realMaxX - 1; currentX++) { + for (int currentZ = realMinZ; currentZ <= realMaxZ - 1; currentZ++) { + addPoint(points, currentX, realMinY, currentZ, playerLocation, null); + } + } + } + + if (borderMax.y() <= realMaxY) { // Top wall + for (int currentX = realMinX; currentX <= realMaxX; currentX++) { + for (int currentZ = realMinZ; currentZ <= realMaxZ; currentZ++) { + BorderPoint inclusive = new BorderPoint(Math.max(realMinX, currentX - 1), realMaxY - 1, Math.max(realMinZ, currentZ - 1)); + addPoint(points, currentX, realMaxY, currentZ, playerLocation, inclusive); + } + } + } + + if (borderMin.x() >= realMinX) { // West wall (left) + for (int currentY = realMinY; currentY <= realMaxY - 1; currentY++) { + for (int currentZ = realMinZ; currentZ <= realMaxZ - 1; currentZ++) { + addPoint(points, realMinX, currentY, currentZ, playerLocation, null); + } + } + } + + if (borderMax.x() <= realMaxX) { // East wall (right) + for (int currentY = realMinY; currentY <= realMaxY; currentY++) { + for (int currentZ = realMinZ; currentZ <= realMaxZ; currentZ++) { + BorderPoint inclusive = new BorderPoint(realMaxX - 1, Math.max(realMinY, currentY - 1), Math.max(realMinZ, currentZ - 1)); + addPoint(points, realMaxX, currentY, currentZ, playerLocation, inclusive); + } + } + } + + if (borderMin.z() >= realMinZ) { // North wall (front) + for (int currentX = realMinX; currentX <= realMaxX - 1; currentX++) { + for (int currentY = realMinY; currentY <= realMaxY - 1; currentY++) { + addPoint(points, currentX, currentY, realMinZ, playerLocation, null); + } + } + } + + if (borderMax.z() <= realMaxZ) { // South wall (back) + for (int currentX = realMinX; currentX <= realMaxX; currentX++) { + for (int currentY = realMinY; currentY <= realMaxY; currentY++) { + BorderPoint inclusive = new BorderPoint(Math.max(realMinX, currentX - 1), Math.max(realMinY, currentY - 1), realMaxZ - 1); + addPoint(points, currentX, currentY, realMaxZ, playerLocation, inclusive); + } + } + } + + return points; + } + + private void addPoint(List points, int x, int y, int z, Location playerLocation, BorderPoint inclusive) { + if (isVisible(x, y, z, playerLocation)) { + points.add(new BorderPoint(x, y, z, inclusive)); + } + } + + private boolean isVisible(int x, int y, int z, Location player) { + return Math.sqrt(Math.pow(x - player.getX(), 2) + Math.pow(y - player.getY(), 2) + Math.pow(z - player.getZ(), 2)) <= this.settings.distance; + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderSettings.java new file mode 100644 index 00000000..cda0af18 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderSettings.java @@ -0,0 +1,40 @@ +package com.eternalcode.combat.border; + +import com.eternalcode.combat.border.animation.block.BlockSettings; +import com.eternalcode.combat.border.animation.particle.ParticleSettings; +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.annotation.Comment; +import java.time.Duration; + +public class BorderSettings extends OkaeriConfig { + + @Comment("# Border view distance in blocks") + public double distance = 6.5; + + @Comment({ + " ", + "# Border block animation settings", + "# Configure the block animation that appears during combat." + }) + public BlockSettings block = new BlockSettings(); + + @Comment({ + " ", + "# Border particle animation settings", + "# Configure the particle animation that appears during combat." + }) + public ParticleSettings particle = new ParticleSettings(); + + public Duration indexRefreshDelay() { + return Duration.ofSeconds(1); + } + + public int distanceRounded() { + return (int) Math.ceil(this.distance); + } + + public boolean isEnabled() { + return this.block.enabled || this.particle.enabled; + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTrigger.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTrigger.java new file mode 100644 index 00000000..aacc9458 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTrigger.java @@ -0,0 +1,20 @@ +package com.eternalcode.combat.border; + +record BorderTrigger(BorderPoint min, BorderPoint max, BorderPoint triggerMin, BorderPoint triggerMax) { + + BorderTrigger(int minX, int minY, int minZ, int maxX, int maxY, int maxZ, int distance) { + this( + new BorderPoint(minX, minY, minZ), + new BorderPoint(maxX, maxY, maxZ), + new BorderPoint(minX - distance, minY - distance, minZ - distance), + new BorderPoint(maxX + distance, maxY + distance, maxZ + distance) + ); + } + + public boolean isTriggered(int x, int y, int z) { + return x >= triggerMin.x() && x <= triggerMax.x() + && y >= triggerMin.y() && y <= triggerMax.y() + && z >= triggerMin.z() && z <= triggerMax.z(); + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTriggerController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTriggerController.java new file mode 100644 index 00000000..32e808f1 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTriggerController.java @@ -0,0 +1,76 @@ +package com.eternalcode.combat.border; + +import com.eternalcode.combat.fight.FightManager; +import com.eternalcode.combat.fight.event.FightUntagEvent; +import org.bukkit.Location; +import org.bukkit.Server; +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.PlayerMoveEvent; +import org.bukkit.event.player.PlayerTeleportEvent; + +public class BorderTriggerController implements Listener { + + private final BorderService borderService; + private final BorderSettings border; + private final FightManager fightManager; + private final Server server; + + public BorderTriggerController(BorderService borderService, BorderSettings border, FightManager fightManager, Server server) { + this.borderService = borderService; + this.border = border; + this.fightManager = fightManager; + this.server = server; + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + void onMove(PlayerMoveEvent event) { + if (!border.isEnabled()) { + return; + } + + Location to = event.getTo(); + Location from = event.getFrom(); + if (to.getBlockX() == from.getBlockX() && to.getBlockY() == from.getBlockY() && to.getBlockZ() == from.getBlockZ()) { + return; + } + + Player player = event.getPlayer(); + if (!fightManager.isInCombat(player.getUniqueId())) { + return; + } + + borderService.updateBorder(player, to); + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + void onTeleport(PlayerTeleportEvent event) { + if (!border.isEnabled()) { + return; + } + + Player player = event.getPlayer(); + if (!fightManager.isInCombat(player.getUniqueId())) { + return; + } + + borderService.updateBorder(player, event.getTo()); + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + void onFightEnd(FightUntagEvent event) { + if (!border.isEnabled()) { + return; + } + + Player player = server.getPlayer(event.getPlayer()); + if (player == null) { + return; + } + + borderService.clearBorder(player); + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTriggerIndex.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTriggerIndex.java new file mode 100644 index 00000000..561ffd19 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTriggerIndex.java @@ -0,0 +1,79 @@ +package com.eternalcode.combat.border; + +import com.eternalcode.combat.region.Region; +import com.eternalcode.combat.region.RegionProvider; +import com.eternalcode.commons.scheduler.Scheduler; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.bukkit.Location; +import org.bukkit.Server; +import org.bukkit.World; + +class BorderTriggerIndex { + + private final Server server; + private final Scheduler scheduler; + private final RegionProvider provider; + private final BorderSettings settings; + + private final Map borderIndexes = new HashMap<>(); + + private BorderTriggerIndex(Server server, Scheduler scheduler, RegionProvider provider, BorderSettings settings) { + this.server = server; + this.scheduler = scheduler; + this.provider = provider; + this.settings = settings; + } + + private void updateWorlds() { + for (World world : server.getWorlds()) { + this.updateWorld(world.getName(), provider.getRegions(world)); + } + } + + private void updateWorld(String world, Collection regions) { + this.scheduler.async(() -> { + List triggers = new ArrayList<>(); + for (Region region : regions) { + Location min = region.getMin(); + Location max = region.getMax(); + + triggers.add(new BorderTrigger( + min.getBlockX(), min.getBlockY(), min.getBlockZ(), + max.getBlockX() + 1, max.getBlockY() + 1, max.getBlockZ() + 1, + settings.distanceRounded() + )); + } + + BorderTriggerIndexBucket index = BorderTriggerIndexBucket.create(triggers); + this.borderIndexes.put(world, index); + }); + } + + static BorderTriggerIndex started(Server server, Scheduler scheduler, RegionProvider provider, BorderSettings settings) { + BorderTriggerIndex index = new BorderTriggerIndex(server, scheduler, provider, settings); + scheduler.timerSync(() -> index.updateWorlds(), Duration.ZERO, settings.indexRefreshDelay()); + return index; + } + + public List getTriggered(Location location) { + BorderTriggerIndexBucket index = borderIndexes.get(location.getWorld().getName()); + if (index == null) { + return List.of(); + } + + int x = (int) Math.round(location.getX()); + int y = (int) Math.round(location.getY()); + int z = (int) Math.round(location.getZ()); + + return index.getTriggers(x, z) + .stream() + .filter(trigger -> trigger.isTriggered(x, y, z)) + .toList(); + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTriggerIndexBucket.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTriggerIndexBucket.java new file mode 100644 index 00000000..8605754f --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/BorderTriggerIndexBucket.java @@ -0,0 +1,84 @@ +package com.eternalcode.combat.border; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +class BorderTriggerIndexBucket { + + /** + * Represents "00000000 00000000 00000000 00000000 11111111 11111111 11111111 11111111" bit mask. + * This allows removing left bits when used with AND bit operator. + * */ + private static final long LEFT_INT_MASK = 0xFFFFFFFFL; + + /** + * Allows packing the coordination on one axis from 256 into 1 + * Why? - We divide the world into fragments of size 256x256 + * Why it is 8? Because when you shift bits, then the value will be smaller / bigger in scale 2^n + * E.g `2^4 = 16` (standard minecraft chunk size) in our case, it is `2^8 = 256` + */ + private static final int CHUNK_SHIFT = 8; + + private final Map> index = new HashMap<>(); + + private BorderTriggerIndexBucket() { + } + + Set getTriggers(int x, int z) { + long position = packChunk(x >> CHUNK_SHIFT, z >> CHUNK_SHIFT); + return this.index.getOrDefault(position, Set.of()); + } + + static BorderTriggerIndexBucket create(Collection triggers) { + return new BorderTriggerIndexBucket().with(triggers); + } + + private BorderTriggerIndexBucket with(Collection triggers) { + for (BorderTrigger trigger : triggers) { + withTrigger(trigger); + } + return this; + } + + private void withTrigger(BorderTrigger trigger) { + int minX = trigger.min().x() >> CHUNK_SHIFT; + int minZ = trigger.min().z() >> CHUNK_SHIFT; + int maxX = trigger.max().x() >> CHUNK_SHIFT; + int maxZ = trigger.max().z() >> CHUNK_SHIFT; + + int startX = Math.min(minX, maxX); + int startZ = Math.min(minZ, maxZ); + int endX = Math.max(minX, maxX); + int endZ = Math.max(minZ, maxZ); + + for (int chunkX = startX; chunkX <= endX; chunkX++) { + for (int chunkZ = startZ; chunkZ <= endZ; chunkZ++) { + long packed = packChunk(chunkX, chunkZ); + this.index.computeIfAbsent(packed, key -> new HashSet<>()) + .add(trigger); + } + } + } + + /** + * Packs two ints into long value. + *

+ * For example for values: + *

    + *
  • X - 00000000 00000000 00000000 00000001
  • + *
  • Z - 00000000 00000000 00000000 00000111
  • + *
+ * This method will return:
+ * 00000000 00000000 00000000 00000111 00000000 00000000 00000000 00000001 + *

+ * @param bigChunkX right int to pack + * @param bigChunkZ left int to pack + */ + private static long packChunk(int bigChunkX, int bigChunkZ) { + return (long) bigChunkX & LEFT_INT_MASK | ((long) bigChunkZ & LEFT_INT_MASK) << Integer.SIZE; + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/BorderColorUtil.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/BorderColorUtil.java new file mode 100644 index 00000000..e635e889 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/BorderColorUtil.java @@ -0,0 +1,18 @@ +package com.eternalcode.combat.border.animation; + +import java.awt.Color; + +public final class BorderColorUtil { + + private BorderColorUtil() { + } + + public static Color xyzToRainbow(int x, int y, int z) { + float hue = (float) ((Math.sin(x * 0.05) + Math.cos(z * 0.05)) * 0.5 + 0.5); + float saturation = 1.0f; + float brightness = 0.8f + 0.2f * Math.max(0.0f, Math.min(1.0f, (float) y / 255)); + + return Color.getHSBColor(hue, saturation, brightness); + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BlockSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BlockSettings.java new file mode 100644 index 00000000..615e19cd --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BlockSettings.java @@ -0,0 +1,33 @@ +package com.eternalcode.combat.border.animation.block; + +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.annotation.Comment; +import java.time.Duration; + +public class BlockSettings extends OkaeriConfig { + + @Comment("# Enable block animation?") + public boolean enabled = true; + + @Comment({ + "# Block type used for rendering the border", + "# Custom: RAINBOW_GLASS, RAINBOW_WOOL, RAINBOW_TERRACOTTA, RAINBOW_CONCRETE", + "# Vanilla: https://javadocs.packetevents.com/com/github/retrooper/packetevents/protocol/world/states/type/StateTypes.html" + }) + public BlockType type = BlockType.RAINBOW_GLASS; + + @Comment({ + "# Delay between each async animation update", + "# Lower values will decrease performance but will make the animation smoother", + "# Higher values will increase performance" + }) + public Duration updateDelay = Duration.ofMillis(250); + + @Comment({ + "# Delay between each chunk cache update", + "# Lower values will decrease performance", + "# Higher values will increase performance but may cause overlapping existing blocks (this does not modify the world)" + }) + public Duration chunkCacheDelay = Duration.ofMillis(300); + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BlockType.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BlockType.java new file mode 100644 index 00000000..3043a44b --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BlockType.java @@ -0,0 +1,49 @@ +package com.eternalcode.combat.border.animation.block; + +import com.eternalcode.combat.border.BorderPoint; +import static com.eternalcode.combat.border.animation.block.BorderBlockRainbowUtil.xyzToConcrete; +import static com.eternalcode.combat.border.animation.block.BorderBlockRainbowUtil.xyzToGlass; +import static com.eternalcode.combat.border.animation.block.BorderBlockRainbowUtil.xyzToTerracotta; +import static com.eternalcode.combat.border.animation.block.BorderBlockRainbowUtil.xyzToWool; +import com.github.retrooper.packetevents.protocol.world.states.type.StateType; +import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes; +import java.util.Locale; + +public class BlockType { + + public static final BlockType RAINBOW_GLASS = new BlockType("RAINBOW_GLASS", point -> xyzToGlass(point)); + public static final BlockType RAINBOW_TERRACOTTA = new BlockType("RAINBOW_TERRACOTTA", point -> xyzToTerracotta(point)); + public static final BlockType RAINBOW_WOOL = new BlockType("RAINBOW_WOOL", point -> xyzToWool(point)); + public static final BlockType RAINBOW_CONCRETE = new BlockType("RAINBOW_CONCRETE", point -> xyzToConcrete(point)); + + private final String name; + private final TypeProvider type; + + private BlockType(String name, TypeProvider type) { + this.name = name; + this.type = type; + } + + public String getName() { + return name; + } + + public StateType getStateType(BorderPoint point) { + return type.provide(point); + } + + public static BlockType fromName(String name) { + return switch (name) { + case "RAINBOW_GLASS" -> RAINBOW_GLASS; + case "RAINBOW_WOOL" -> RAINBOW_WOOL; + case "RAINBOW_TERRACOTTA" -> RAINBOW_TERRACOTTA; + case "RAINBOW_CONCRETE" -> RAINBOW_CONCRETE; + default -> new BlockType(name, point -> StateTypes.getByName(name.toLowerCase(Locale.ROOT))); + }; + } + + private interface TypeProvider { + StateType provide(BorderPoint point); + } + +} \ No newline at end of file diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BlockTypeTransformer.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BlockTypeTransformer.java new file mode 100644 index 00000000..da07f289 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BlockTypeTransformer.java @@ -0,0 +1,30 @@ +package com.eternalcode.combat.border.animation.block; + +import eu.okaeri.configs.schema.GenericsPair; +import eu.okaeri.configs.serdes.BidirectionalTransformer; +import eu.okaeri.configs.serdes.SerdesContext; +import java.util.Locale; + +public class BlockTypeTransformer extends BidirectionalTransformer { + + @Override + public GenericsPair getPair() { + return this.genericsPair(String.class, BlockType.class); + } + + @Override + public BlockType leftToRight(String data, SerdesContext serdesContext) { + BlockType blockType = BlockType.fromName(data); + if (blockType == null) { + throw new IllegalArgumentException("Unknown block type: " + data); + } + + return blockType; + } + + @Override + public String rightToLeft(BlockType data, SerdesContext serdesContext) { + return data.getName().toUpperCase(Locale.ROOT); + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java new file mode 100644 index 00000000..45e40984 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockController.java @@ -0,0 +1,196 @@ +package com.eternalcode.combat.border.animation.block; + +import com.eternalcode.combat.border.BorderPoint; +import com.eternalcode.combat.border.BorderService; +import com.eternalcode.combat.border.event.BorderHideAsyncEvent; +import com.eternalcode.combat.border.event.BorderShowAsyncEvent; +import com.eternalcode.commons.scheduler.Scheduler; +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.PacketEventsAPI; +import com.github.retrooper.packetevents.manager.player.PlayerManager; +import com.github.retrooper.packetevents.protocol.player.ClientVersion; +import com.github.retrooper.packetevents.protocol.world.states.WrappedBlockState; +import com.github.retrooper.packetevents.protocol.world.states.type.StateType; +import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes; +import com.github.retrooper.packetevents.util.Vector3i; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerMultiBlockChange; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerMultiBlockChange.EncodedBlock; +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.bukkit.ChunkSnapshot; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +public class BorderBlockController implements Listener { + + public static final PacketEventsAPI PACKET_EVENTS = PacketEvents.getAPI(); + public static final PlayerManager PLAYER_MANAGER = PACKET_EVENTS.getPlayerManager(); + public static final ClientVersion SERVER_VERSION = PACKET_EVENTS.getServerManager().getVersion().toClientVersion(); + + public static final int AIR_ID = WrappedBlockState.getDefaultState(SERVER_VERSION, StateTypes.AIR).getGlobalId(); + public static final int MINECRAFT_CHUNK_SHIFT = 4; + + private final BorderService borderService; + private final BlockSettings settings; + private final Server server; + + private final Set playersToUpdate = ConcurrentHashMap.newKeySet(); + private final Map lockedPlayers = new ConcurrentHashMap<>(); + private final ChunkCache chunkCache; + + public BorderBlockController(BorderService borderService, BlockSettings settings, Scheduler scheduler, Server server) { + this.borderService = borderService; + this.settings = settings; + this.server = server; + this.chunkCache = new ChunkCache(settings); + + scheduler.timerAsync(() -> this.updatePlayers(), settings.updateDelay, settings.updateDelay); + } + + @EventHandler + void onBorderShowAsyncEvent(BorderShowAsyncEvent event) { + if (!settings.enabled) { + return; + } + + Player player = event.getPlayer(); + Set borderPoints = this.getPointsWithoutAir(player, event.getPoints()); + + event.setPoints(borderPoints); + this.showBlocks(player, borderPoints); + this.playersToUpdate.add(player.getUniqueId()); + } + + @EventHandler + void onBorderHideAsyncEvent(BorderHideAsyncEvent event) { + if (!settings.enabled) { + return; + } + + Object lock = lockedPlayers.computeIfAbsent(event.getPlayer().getUniqueId(), k -> new Object()); + synchronized (lock) { + this.hideBlocks(event.getPlayer(), event.getPoints()); + + Set border = this.borderService.getActiveBorder(event.getPlayer()); + if (border.isEmpty()) { + this.playersToUpdate.remove(event.getPlayer().getUniqueId()); + } + } + } + + private void updatePlayers() { + if (!settings.enabled) { + return; + } + + for (UUID uuid : this.playersToUpdate) { + Player player = this.server.getPlayer(uuid); + if (player == null) { + this.playersToUpdate.remove(uuid); + continue; + } + + this.updatePlayer(uuid, player); + } + } + + private void updatePlayer(UUID uuid, Player player) { + Object lock = lockedPlayers.computeIfAbsent(uuid, k -> new Object()); + synchronized (lock) { + Set border = this.borderService.getActiveBorder(player); + + if (border.isEmpty()) { + this.playersToUpdate.remove(uuid); + return; + } + + this.showBlocks(player, border); + } + } + + private void showBlocks(Player player, Collection blocks) { + this.splitByChunks(blocks).entrySet().stream() + .map(chunkBlocks -> toMultiBlockChangePacket(chunkBlocks)) + .forEach(change -> PLAYER_MANAGER.sendPacket(player, change)); + } + + private WrapperPlayServerMultiBlockChange toMultiBlockChangePacket(Entry> chunkBlocks) { + EncodedBlock[] encodedBlocks = chunkBlocks.getValue().stream() + .map(borderPoint -> this.toEncodedBlock(borderPoint)) + .toArray(value -> new EncodedBlock[value]); + + return new WrapperPlayServerMultiBlockChange(chunkBlocks.getKey(), true, encodedBlocks); + } + + private void hideBlocks(Player player, Collection blocks) { + this.splitByChunks(blocks).entrySet().stream() + .map(chunkBlocks -> toMultiAirChangePacket(chunkBlocks)) + .forEach(change -> PLAYER_MANAGER.sendPacket(player, change)); + } + + private WrapperPlayServerMultiBlockChange toMultiAirChangePacket(Entry> chunkBlocks) { + EncodedBlock[] encodedBlocks = chunkBlocks.getValue().stream() + .map(point -> new EncodedBlock(AIR_ID, point.x(), point.y(), point.z())) + .toArray(value -> new EncodedBlock[value]); + + return new WrapperPlayServerMultiBlockChange(chunkBlocks.getKey(), true, encodedBlocks); + } + + private Set getPointsWithoutAir(Player player, Collection blocks) { + Map> chunksToProcess = blocks.stream() + .map(borderPoint -> borderPoint.toInclusive()) + .collect(Collectors.groupingBy( + inclusive -> new ChunkLocation(inclusive.x() >> MINECRAFT_CHUNK_SHIFT, inclusive.z() >> MINECRAFT_CHUNK_SHIFT), + Collectors.toSet() + )); + + return chunksToProcess.entrySet().stream() + .flatMap(entry -> getPointsWithoutAirOnChunk(player, entry)) + .collect(Collectors.toSet()); + } + + private Stream getPointsWithoutAirOnChunk(Player player, Entry> entry) { + ChunkSnapshot snapshot = this.chunkCache.loadSnapshot(player, entry.getKey()); + if (snapshot == null) { + return Stream.empty(); + } + + return entry.getValue().stream() + .filter(point -> isAir(entry.getKey(), point, snapshot)); + } + + private static boolean isAir(ChunkLocation location, BorderPoint point, ChunkSnapshot chunk) { + int xInsideChunk = point.x() - (location.x() << MINECRAFT_CHUNK_SHIFT); + int zInsideChunk = point.z() - (location.z() << MINECRAFT_CHUNK_SHIFT); + Material material = chunk.getBlockType(xInsideChunk, point.y(), zInsideChunk); + + return material.isAir(); + } + + private Map> splitByChunks(Collection blocks) { + return blocks.stream().collect(Collectors.groupingBy( + block -> new Vector3i( + block.x() >> MINECRAFT_CHUNK_SHIFT, + block.y() >> MINECRAFT_CHUNK_SHIFT, + block.z() >> MINECRAFT_CHUNK_SHIFT + ), + Collectors.toSet() + )); + } + + private EncodedBlock toEncodedBlock(BorderPoint point) { + StateType type = settings.type.getStateType(point); + WrappedBlockState state = WrappedBlockState.getDefaultState(SERVER_VERSION, type); + return new EncodedBlock(state.getGlobalId(), point.x(), point.y(), point.z()); + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockRainbowUtil.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockRainbowUtil.java new file mode 100644 index 00000000..f66f5a09 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/BorderBlockRainbowUtil.java @@ -0,0 +1,122 @@ +package com.eternalcode.combat.border.animation.block; + +import com.eternalcode.combat.border.BorderPoint; +import com.eternalcode.combat.border.animation.BorderColorUtil; +import com.github.retrooper.packetevents.protocol.world.states.type.StateType; +import com.github.retrooper.packetevents.protocol.world.states.type.StateTypes; +import com.google.common.collect.ImmutableMap; +import java.awt.Color; +import java.util.EnumMap; +import java.util.Map; +import org.bukkit.DyeColor; + +final class BorderBlockRainbowUtil { + + private final static Map GLASSES = new EnumMap<>(ImmutableMap.builder() + .put(DyeColor.WHITE, StateTypes.WHITE_STAINED_GLASS) + .put(DyeColor.ORANGE, StateTypes.ORANGE_STAINED_GLASS) + .put(DyeColor.MAGENTA, StateTypes.MAGENTA_STAINED_GLASS) + .put(DyeColor.LIGHT_BLUE, StateTypes.LIGHT_BLUE_STAINED_GLASS) + .put(DyeColor.YELLOW, StateTypes.YELLOW_STAINED_GLASS) + .put(DyeColor.LIME, StateTypes.LIME_STAINED_GLASS) + .put(DyeColor.PINK, StateTypes.PINK_STAINED_GLASS) + .put(DyeColor.GRAY, StateTypes.GRAY_STAINED_GLASS) + .put(DyeColor.LIGHT_GRAY, StateTypes.LIGHT_GRAY_STAINED_GLASS) + .put(DyeColor.CYAN, StateTypes.CYAN_STAINED_GLASS) + .put(DyeColor.PURPLE, StateTypes.PURPLE_STAINED_GLASS) + .put(DyeColor.BLUE, StateTypes.BLUE_STAINED_GLASS) + .put(DyeColor.BROWN, StateTypes.BROWN_STAINED_GLASS) + .put(DyeColor.GREEN, StateTypes.GREEN_STAINED_GLASS) + .put(DyeColor.RED, StateTypes.RED_STAINED_GLASS) + .build() + ); + + private final static Map TERRACOTTA = new EnumMap<>(ImmutableMap.builder() + .put(DyeColor.WHITE, StateTypes.WHITE_TERRACOTTA) + .put(DyeColor.ORANGE, StateTypes.ORANGE_TERRACOTTA) + .put(DyeColor.MAGENTA, StateTypes.MAGENTA_TERRACOTTA) + .put(DyeColor.LIGHT_BLUE, StateTypes.LIGHT_BLUE_TERRACOTTA) + .put(DyeColor.YELLOW, StateTypes.YELLOW_TERRACOTTA) + .put(DyeColor.LIME, StateTypes.LIME_TERRACOTTA) + .put(DyeColor.PINK, StateTypes.PINK_TERRACOTTA) + .put(DyeColor.GRAY, StateTypes.GRAY_TERRACOTTA) + .put(DyeColor.LIGHT_GRAY, StateTypes.LIGHT_GRAY_TERRACOTTA) + .put(DyeColor.CYAN, StateTypes.CYAN_TERRACOTTA) + .put(DyeColor.PURPLE, StateTypes.PURPLE_TERRACOTTA) + .put(DyeColor.BLUE, StateTypes.BLUE_TERRACOTTA) + .put(DyeColor.BROWN, StateTypes.BROWN_TERRACOTTA) + .put(DyeColor.GREEN, StateTypes.GREEN_TERRACOTTA) + .put(DyeColor.RED, StateTypes.RED_TERRACOTTA) + .build() + ); + + private final static Map WOOLS = new EnumMap<>(ImmutableMap.builder() + .put(DyeColor.WHITE, StateTypes.WHITE_WOOL) + .put(DyeColor.ORANGE, StateTypes.ORANGE_WOOL) + .put(DyeColor.MAGENTA, StateTypes.MAGENTA_WOOL) + .put(DyeColor.LIGHT_BLUE, StateTypes.LIGHT_BLUE_WOOL) + .put(DyeColor.YELLOW, StateTypes.YELLOW_WOOL) + .put(DyeColor.LIME, StateTypes.LIME_WOOL) + .put(DyeColor.PINK, StateTypes.PINK_WOOL) + .put(DyeColor.GRAY, StateTypes.GRAY_WOOL) + .put(DyeColor.LIGHT_GRAY, StateTypes.LIGHT_GRAY_WOOL) + .put(DyeColor.CYAN, StateTypes.CYAN_WOOL) + .put(DyeColor.PURPLE, StateTypes.PURPLE_WOOL) + .put(DyeColor.BLUE, StateTypes.BLUE_WOOL) + .put(DyeColor.BROWN, StateTypes.BROWN_WOOL) + .put(DyeColor.GREEN, StateTypes.GREEN_WOOL) + .put(DyeColor.RED, StateTypes.RED_WOOL) + .build() + ); + + private final static Map CONCRETE = new EnumMap<>(ImmutableMap.builder() + .put(DyeColor.WHITE, StateTypes.WHITE_CONCRETE) + .put(DyeColor.ORANGE, StateTypes.ORANGE_CONCRETE) + .put(DyeColor.MAGENTA, StateTypes.MAGENTA_CONCRETE) + .put(DyeColor.LIGHT_BLUE, StateTypes.LIGHT_BLUE_CONCRETE) + .put(DyeColor.YELLOW, StateTypes.YELLOW_CONCRETE) + .put(DyeColor.LIME, StateTypes.LIME_CONCRETE) + .put(DyeColor.PINK, StateTypes.PINK_CONCRETE) + .put(DyeColor.GRAY, StateTypes.GRAY_CONCRETE) + .put(DyeColor.LIGHT_GRAY, StateTypes.LIGHT_GRAY_CONCRETE) + .put(DyeColor.CYAN, StateTypes.CYAN_CONCRETE) + .put(DyeColor.PURPLE, StateTypes.PURPLE_CONCRETE) + .put(DyeColor.BLUE, StateTypes.BLUE_CONCRETE) + .put(DyeColor.BROWN, StateTypes.BROWN_CONCRETE) + .put(DyeColor.GREEN, StateTypes.GREEN_CONCRETE) + .put(DyeColor.RED, StateTypes.RED_CONCRETE) + .build() + ); + + static StateType xyzToGlass(BorderPoint point) { + return GLASSES.get(xyzToDye(point)); + } + + static StateType xyzToTerracotta(BorderPoint point) { + return TERRACOTTA.get(xyzToDye(point)); + } + + public static StateType xyzToWool(BorderPoint point) { + return WOOLS.get(xyzToDye(point)); + } + + public static StateType xyzToConcrete(BorderPoint point) { + return CONCRETE.get(xyzToDye(point)); + } + + private static DyeColor xyzToDye(BorderPoint point) { + Color rgb = BorderColorUtil.xyzToRainbow(point.x(), point.y(), point.z()); + int distance = Integer.MAX_VALUE; + DyeColor dye = DyeColor.WHITE; + + for (DyeColor currentDye : DyeColor.values()) { + org.bukkit.Color color = currentDye.getColor(); + int currentDistance = Math.abs(color.getRed() - rgb.getRed()) + Math.abs(color.getGreen() - rgb.getGreen()) + Math.abs(color.getBlue() - rgb.getBlue()); + if (currentDistance < distance) { + distance = currentDistance; + dye = currentDye; + } + } + return dye; + } +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/ChunkCache.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/ChunkCache.java new file mode 100644 index 00000000..b8f099c6 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/ChunkCache.java @@ -0,0 +1,33 @@ +package com.eternalcode.combat.border.animation.block; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import io.papermc.lib.PaperLib; +import org.bukkit.ChunkSnapshot; +import org.bukkit.entity.Player; + +class ChunkCache { + + private final Cache chunkCache; + + ChunkCache(BlockSettings settings) { + this.chunkCache = CacheBuilder.newBuilder() + .expireAfterWrite(settings.chunkCacheDelay) + .build(); + } + + public ChunkSnapshot loadSnapshot(Player player, ChunkLocation location) { + ChunkSnapshot snapshot = chunkCache.getIfPresent(location); + if (snapshot != null) { + return snapshot; + } + + ChunkSnapshot chunkSnapshot = PaperLib.getChunkAtAsync(player.getWorld(), location.x(), location.z(), false) + .thenApply(chunk -> chunk.getChunkSnapshot()) + .join(); + + chunkCache.put(location, chunkSnapshot); + return chunkSnapshot; + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/ChunkLocation.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/ChunkLocation.java new file mode 100644 index 00000000..eaed29d6 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/block/ChunkLocation.java @@ -0,0 +1,4 @@ +package com.eternalcode.combat.border.animation.block; + +record ChunkLocation(int x, int z) { +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleColor.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleColor.java new file mode 100644 index 00000000..20665a81 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleColor.java @@ -0,0 +1,40 @@ +package com.eternalcode.combat.border.animation.particle; + +import com.eternalcode.combat.border.BorderPoint; +import com.eternalcode.combat.border.animation.BorderColorUtil; +import java.awt.Color; + +public class ParticleColor { + + public static final ParticleColor RAINBOW = new ParticleColor("RAINBOW", point -> BorderColorUtil.xyzToRainbow(point.x(), point.y(), point.z())); + + private final String name; + private final ColorProvider color; + + private ParticleColor(String name, ColorProvider color) { + this.name = name; + this.color = color; + } + + public String getName() { + return name; + } + + public Color getColor(BorderPoint point) { + return color.provide(point); + } + + public static ParticleColor fromName(String name) { + if (name.equals(RAINBOW.name)) { + return RAINBOW; + } + + Color decoded = Color.decode(name); + return new ParticleColor(name, point -> decoded); + } + + private interface ColorProvider { + Color provide(BorderPoint point); + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleColorTransformer.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleColorTransformer.java new file mode 100644 index 00000000..f912c2c7 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleColorTransformer.java @@ -0,0 +1,24 @@ +package com.eternalcode.combat.border.animation.particle; + +import eu.okaeri.configs.schema.GenericsPair; +import eu.okaeri.configs.serdes.BidirectionalTransformer; +import eu.okaeri.configs.serdes.SerdesContext; + +public class ParticleColorTransformer extends BidirectionalTransformer { + + @Override + public GenericsPair getPair() { + return this.genericsPair(String.class, ParticleColor.class); + } + + @Override + public ParticleColor leftToRight(String data, SerdesContext serdesContext) { + return ParticleColor.fromName(data); + } + + @Override + public String rightToLeft(ParticleColor data, SerdesContext serdesContext) { + return data.getName(); + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleController.java new file mode 100644 index 00000000..e94a5293 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleController.java @@ -0,0 +1,89 @@ +package com.eternalcode.combat.border.animation.particle; + +import com.eternalcode.combat.border.BorderPoint; +import com.eternalcode.combat.border.BorderService; +import com.eternalcode.combat.border.event.BorderHideAsyncEvent; +import com.eternalcode.combat.border.event.BorderShowAsyncEvent; +import com.eternalcode.commons.scheduler.Scheduler; +import com.github.retrooper.packetevents.PacketEvents; +import com.github.retrooper.packetevents.PacketEventsAPI; +import com.github.retrooper.packetevents.manager.player.PlayerManager; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerParticle; +import java.time.Duration; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import org.bukkit.Server; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +public class ParticleController implements Listener { + + private static final PacketEventsAPI PACKET_EVENTS_API = PacketEvents.getAPI(); + private static final PlayerManager PLAYER_MANAGER = PACKET_EVENTS_API.getPlayerManager(); + + private final BorderService borderService; + private final ParticleSettings particleSettings; + private final Server server; + private final Set playersToUpdate = ConcurrentHashMap.newKeySet(); + + public ParticleController(BorderService borderService, ParticleSettings particleSettings, Scheduler scheduler, Server server) { + this.borderService = borderService; + this.particleSettings = particleSettings; + this.server = server; + scheduler.timerAsync(() -> this.updatePlayers(), Duration.ofMillis(200), Duration.ofMillis(200)); + } + + @EventHandler + void onBorderShowAsyncEvent(BorderShowAsyncEvent event) { + if (!particleSettings.enabled) { + return; + } + + this.playersToUpdate.add(event.getPlayer().getUniqueId()); + + for (BorderPoint point : event.getPoints()) { + this.playParticle(event.getPlayer(), point); + } + } + + @EventHandler + void onBorderHideAsyncEvent(BorderHideAsyncEvent event) { + if (!particleSettings.enabled) { + return; + } + + Set border = this.borderService.getActiveBorder(event.getPlayer()); + if (border.isEmpty()) { + this.playersToUpdate.remove(event.getPlayer().getUniqueId()); + } + } + + private void updatePlayers() { + if (!particleSettings.enabled) { + return; + } + + for (UUID uuid : this.playersToUpdate) { + Player player = this.server.getPlayer(uuid); + Set border = this.borderService.getActiveBorder(player); + + if (border.isEmpty()) { + this.playersToUpdate.remove(uuid); + continue; + } + + for (BorderPoint point : border) { + this.playParticle(player, point); + } + } + } + + private void playParticle(Player player, BorderPoint point) { + WrapperPlayServerParticle particle = particleSettings.getParticle(point); + + PLAYER_MANAGER.sendPacket(player, particle); + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleSettings.java new file mode 100644 index 00000000..89f138ed --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleSettings.java @@ -0,0 +1,76 @@ +package com.eternalcode.combat.border.animation.particle; + +import com.eternalcode.combat.border.BorderPoint; +import com.github.retrooper.packetevents.protocol.color.AlphaColor; +import com.github.retrooper.packetevents.protocol.particle.Particle; +import com.github.retrooper.packetevents.protocol.particle.data.ParticleColorData; +import com.github.retrooper.packetevents.protocol.particle.data.ParticleData; +import com.github.retrooper.packetevents.protocol.particle.data.ParticleDustData; +import com.github.retrooper.packetevents.protocol.particle.type.ParticleType; +import com.github.retrooper.packetevents.protocol.particle.type.ParticleTypes; +import com.github.retrooper.packetevents.util.Vector3d; +import com.github.retrooper.packetevents.util.Vector3f; +import com.github.retrooper.packetevents.wrapper.play.server.WrapperPlayServerParticle; +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.annotation.Comment; +import java.awt.Color; + +@SuppressWarnings("unchecked") +public class ParticleSettings extends OkaeriConfig { + + @Comment("# Enable particle animation?") + public boolean enabled = true; + + @Comment("# Particle type - https://javadocs.packetevents.com/com/github/retrooper/packetevents/protocol/particle/type/ParticleTypes.html") + public ParticleType type = ParticleTypes.DUST; + @Comment({ + "# Particle color (used only for DUST or ENTITY_EFFECT particle type)", + "# You can set hex color e.g. \"#ca4c45\" or use \"RAINBOW\" to generate rainbow gradient based on x and z coordinates." + }) + public ParticleColor color = ParticleColor.RAINBOW; + public int count = 5; + public float scale = 1.0F; + public float maxSpeed = 0.0F; + public float offsetX = 0.2F; + public float offsetY = 0.2F; + public float offsetZ = 0.2F; + + public WrapperPlayServerParticle getParticle(BorderPoint point) { + return getParticle(point, type); + } + + private WrapperPlayServerParticle getParticle(BorderPoint point, ParticleType type) { + T particleData = this.createData(type, point); + Particle dust = new Particle<>(type, particleData); + return new WrapperPlayServerParticle( + dust, + true, + new Vector3d(point.x(), point.y(), point.z()), + new Vector3f(orElse(offsetX, 0.1F), orElse(offsetY, 0.1F), orElse(offsetZ, 0.1F)), + orElse(maxSpeed, 0.0F), + count, + true + ); + } + + private T orElse(T nullable, T or) { + return nullable != null ? nullable : or; + } + + @SuppressWarnings("unchecked") + private T createData(ParticleType type, BorderPoint point) { + if (type.equals(ParticleTypes.DUST)) { + Color color = this.color.getColor(point); + return (T) new ParticleDustData(orElse(scale, 1.0F), color.getRed(), color.getGreen(), color.getBlue()); + } + + if (type.equals(ParticleTypes.ENTITY_EFFECT)) { + Color color = this.color.getColor(point); + AlphaColor alphaColor = new AlphaColor(color.getAlpha(), color.getRed(), color.getGreen(), color.getBlue()); + return (T) new ParticleColorData(alphaColor); + } + + return ParticleData.emptyData(); + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleTypeTransformer.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleTypeTransformer.java new file mode 100644 index 00000000..ec7a8a4c --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/animation/particle/ParticleTypeTransformer.java @@ -0,0 +1,43 @@ +package com.eternalcode.combat.border.animation.particle; + +import com.github.retrooper.packetevents.protocol.particle.type.ParticleType; +import com.github.retrooper.packetevents.protocol.particle.type.ParticleTypes; +import com.github.retrooper.packetevents.resources.ResourceLocation; +import eu.okaeri.configs.schema.GenericsDeclaration; +import eu.okaeri.configs.schema.GenericsPair; +import eu.okaeri.configs.serdes.BidirectionalTransformer; +import eu.okaeri.configs.serdes.SerdesContext; +import java.util.Locale; + +public class ParticleTypeTransformer extends BidirectionalTransformer> { + + @Override + public GenericsPair> getPair() { + return this.generics(GenericsDeclaration.of(String.class), GenericsDeclaration.of(ParticleType.class)); + } + + @Override + public ParticleType leftToRight(String data, SerdesContext serdesContext) { + if (!data.contains(":")) { + data = ResourceLocation.normString(data); + } + + ParticleType type = ParticleTypes.getByName(data.toLowerCase(Locale.ROOT)); + if (type == null) { + throw new IllegalArgumentException("Unknown particle type: " + data); + } + + return type; + } + + @Override + public String rightToLeft(ParticleType data, SerdesContext serdesContext) { + ResourceLocation location = data.getName(); + if (location.getNamespace().equals(ResourceLocation.VANILLA_NAMESPACE)) { + return location.getKey().toUpperCase(Locale.ROOT); + } + + return location.toString(); + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/event/BorderHideAsyncEvent.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/event/BorderHideAsyncEvent.java new file mode 100644 index 00000000..3c983ded --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/event/BorderHideAsyncEvent.java @@ -0,0 +1,41 @@ +package com.eternalcode.combat.border.event; + +import com.eternalcode.combat.border.BorderPoint; +import java.util.Set; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +public class BorderHideAsyncEvent extends Event { + + private static final HandlerList HANDLER_LIST = new HandlerList(); + + private final Player player; + private final Set points; + + public BorderHideAsyncEvent(Player player, Set points) { + super(true); + this.player = player; + this.points = points; + } + + public Player getPlayer() { + return this.player; + } + + public Set getPoints() { + return this.points; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return HANDLER_LIST; + } + + public static HandlerList getHandlerList() { + return HANDLER_LIST; + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/event/BorderShowAsyncEvent.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/event/BorderShowAsyncEvent.java new file mode 100644 index 00000000..7fb2e29f --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/border/event/BorderShowAsyncEvent.java @@ -0,0 +1,46 @@ +package com.eternalcode.combat.border.event; + +import com.eternalcode.combat.border.BorderPoint; +import java.util.Set; +import java.util.UUID; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +public class BorderShowAsyncEvent extends Event { + + private static final HandlerList HANDLER_LIST = new HandlerList(); + + private final Player player; + private Set points; + + public BorderShowAsyncEvent(Player player, Set points) { + super(true); + this.player = player; + this.points = points; + } + + public Player getPlayer() { + return player; + } + + public Set getPoints() { + return points; + } + + public void setPoints(Set points) { + this.points = points; + } + + @NotNull + @Override + public HandlerList getHandlers() { + return HANDLER_LIST; + } + + public static HandlerList getHandlerList() { + return HANDLER_LIST; + } + +} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/bridge/BridgeInitializer.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/BridgeInitializer.java similarity index 100% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/bridge/BridgeInitializer.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/BridgeInitializer.java diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/bridge/BridgeService.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/BridgeService.java similarity index 92% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/bridge/BridgeService.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/BridgeService.java index 665362bd..a1eeaa31 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/bridge/BridgeService.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/BridgeService.java @@ -30,15 +30,16 @@ public BridgeService(PluginConfig pluginConfig, PluginManager pluginManager, Log public void init(FightManager fightManager, Server server) { this.initialize("WorldGuard", - () -> this.regionProvider = new WorldGuardRegionProvider(this.pluginConfig.settings.blockedRegions, this.pluginConfig), + () -> this.regionProvider = new WorldGuardRegionProvider(this.pluginConfig.regions.blockedRegions, + this.pluginConfig), () -> { - this.regionProvider = new DefaultRegionProvider(this.pluginConfig.settings.blockedRegionRadius); + this.regionProvider = new DefaultRegionProvider(this.pluginConfig.regions.restrictedRegionRadius); this.logger.warning("WorldGuard is not installed, set to default region provider."); }); this.initialize("PlaceholderAPI", - () -> new FightTagPlaceholder(fightManager, server, plugin).register(), + () -> new FightTagPlaceholder(fightManager, server, this.plugin).register(), () -> this.logger.warning("PlaceholderAPI is not installed, placeholders will not be registered.") ); } diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/bridge/placeholder/FightTagPlaceholder.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/placeholder/FightTagPlaceholder.java similarity index 87% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/bridge/placeholder/FightTagPlaceholder.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/placeholder/FightTagPlaceholder.java index f605c356..b4e9a051 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/bridge/placeholder/FightTagPlaceholder.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/bridge/placeholder/FightTagPlaceholder.java @@ -38,19 +38,19 @@ public boolean canRegister() { @Override public @NotNull String getAuthor() { - return plugin.getDescription().getAuthors().get(0); + return this.plugin.getDescription().getAuthors().get(0); } @Override public @NotNull String getVersion() { - return plugin.getDescription().getVersion(); + return this.plugin.getDescription().getVersion(); } @Override public String onRequest(OfflinePlayer player, String identifier) { if (identifier.equals("remaining_seconds")) { return this.getFightTag(player) - .map(fightTag -> DurationUtil.format(fightTag.getRemainingDuration())) + .map(fightTagInter -> DurationUtil.format(fightTagInter.getRemainingDuration())) .orElse(""); } @@ -61,13 +61,13 @@ public String onRequest(OfflinePlayer player, String identifier) { } if (identifier.equals("opponent")) { - return getTagger(player) + return this.getTagger(player) .map(tagger -> tagger.getName()) .orElse(""); } if (identifier.equals("opponent_health")) { - return getTagger(player) + return this.getTagger(player) .map(tagger -> String.format("%.2f", tagger.getHealth())) .orElse(""); } @@ -77,7 +77,7 @@ public String onRequest(OfflinePlayer player, String identifier) { private @NotNull Optional getTagger(OfflinePlayer player) { return this.getFightTag(player) - .map(fightTag -> fightTag.getTagger()) + .map(fightTagInter -> fightTagInter.getTagger()) .map(taggerId -> this.server.getPlayer(taggerId)); } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/ConfigService.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/ConfigService.java new file mode 100644 index 00000000..4418433b --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/ConfigService.java @@ -0,0 +1,64 @@ +package com.eternalcode.combat.config; + +import com.eternalcode.combat.border.animation.block.BlockTypeTransformer; +import com.eternalcode.combat.border.animation.particle.ParticleColorTransformer; +import com.eternalcode.combat.border.animation.particle.ParticleTypeTransformer; +import com.eternalcode.multification.bukkit.notice.resolver.sound.SoundBukkitResolver; +import com.eternalcode.multification.notice.resolver.NoticeResolverDefaults; +import com.eternalcode.multification.notice.resolver.NoticeResolverRegistry; +import com.eternalcode.multification.okaeri.MultificationSerdesPack; +import eu.okaeri.configs.ConfigManager; +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.serdes.OkaeriSerdesPack; +import eu.okaeri.configs.serdes.SerdesRegistry; +import eu.okaeri.configs.serdes.commons.SerdesCommons; +import eu.okaeri.configs.yaml.bukkit.YamlBukkitConfigurer; +import eu.okaeri.configs.yaml.bukkit.serdes.SerdesBukkit; + +import java.io.File; +import java.util.HashSet; +import java.util.Set; + +public class ConfigService { + + private final Set configs = new HashSet<>(); + + public T create(Class config, File file) { + T configFile = ConfigManager.create(config); + + YamlBukkitConfigurer yamlBukkitConfigurer = new YamlBukkitConfigurer(); + NoticeResolverRegistry noticeRegistry = NoticeResolverDefaults.createRegistry() + .registerResolver(new SoundBukkitResolver()); + + configFile.withConfigurer(yamlBukkitConfigurer, + new SerdesCommons(), + new SerdesBukkit(), + new MultificationSerdesPack(noticeRegistry), + new DefaultSerdesPack() + ); + + configFile.withBindFile(file); + configFile.withRemoveOrphans(true); + configFile.saveDefaults(); + configFile.load(true); + + this.configs.add(configFile); + + return configFile; + } + + public void reload() { + for (OkaeriConfig config : this.configs) { + config.load(); + } + } + + private static class DefaultSerdesPack implements OkaeriSerdesPack { + @Override + public void register(SerdesRegistry config) { + config.register(new ParticleColorTransformer()); + config.register(new ParticleTypeTransformer()); + config.register(new BlockTypeTransformer()); + } + } +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/AdminSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/AdminSettings.java new file mode 100644 index 00000000..d87cc058 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/AdminSettings.java @@ -0,0 +1,20 @@ +package com.eternalcode.combat.config.implementation; + +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.annotation.Comment; + +public class AdminSettings extends OkaeriConfig { + @Comment({ + "# Exclude server administrators from combat tagging and being tagged.", + "# Set to 'true' to prevent admins from being tagged or tagging others.", + "# Set to 'false' to allow admins to participate in combat." + }) + public boolean excludeAdminsFromCombat = false; + + @Comment({ + "# Exclude players in creative mode from combat tagging and being tagged.", + "# Set to 'true' to prevent creative mode players from being tagged or tagging others.", + "# Set to 'false' to allow creative mode players to participate in combat." + }) + public boolean excludeCreativePlayersFromCombat = false; +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/BlockPlacementSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/BlockPlacementSettings.java new file mode 100644 index 00000000..acde4b83 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/BlockPlacementSettings.java @@ -0,0 +1,43 @@ +package com.eternalcode.combat.config.implementation; + +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.annotation.Comment; +import java.util.List; +import org.bukkit.Material; + +public class BlockPlacementSettings extends OkaeriConfig { + @Comment({ + "# Prevent players from placing blocks during combat.", + "# Set to 'true' to block block placement while in combat." + }) + public boolean disableBlockPlacing = true; + + @Comment({ + "# Restrict block placement above or below a specific Y coordinate.", + "# Available modes: ABOVE (blocks cannot be placed above the Y coordinate), BELOW (blocks cannot be placed below the Y coordinate)." + }) + public BlockPlacingMode blockPlacementMode = BlockPlacingMode.ABOVE; + + @Comment({ + "# Custom name for the block placement mode used in messages.", + "# This name will be displayed in notifications related to block placement restrictions." + }) + public String blockPlacementModeDisplayName = "above"; + + @Comment({ + "# Define the Y coordinate for block placement restrictions.", + "# This value is relative to the selected block placement mode (ABOVE or BELOW)." + }) + public int blockPlacementYCoordinate = 40; + @Comment({ + "# Restrict the placement of specific blocks during combat.", + "# Add blocks to this list to prevent their placement. Leave the list empty to block all blocks.", + "# Note: This feature requires 'disableBlockPlacing' to be enabled." + }) + public List restrictedBlockTypes = List.of(); + + public enum BlockPlacingMode { + ABOVE, + BELOW + } +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/CombatSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/CombatSettings.java new file mode 100644 index 00000000..5064c10e --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/CombatSettings.java @@ -0,0 +1,75 @@ +package com.eternalcode.combat.config.implementation; + +import com.eternalcode.combat.WhitelistBlacklistMode; +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.annotation.Comment; +import java.util.List; +import org.bukkit.entity.EntityType; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityDamageEvent.DamageCause; + +public class CombatSettings extends OkaeriConfig { + @Comment({ + "# Automatically release the attacker from combat when the victim dies.", + "# Set to 'true' to enable this feature, or 'false' to keep the attacker in combat." + }) + public boolean releaseAttackerOnVictimDeath = true; + + @Comment({ + "# Disable the use of elytra during combat.", + "# Set to 'true' to prevent players from using elytra while in combat." + }) + public boolean disableElytraUsage = true; + + @Comment({ + "# Disable the use of elytra when a player takes damage.", + "# Set to 'true' to disable elytra usage upon taking damage, even when the player is mid-air." + }) + public boolean disableElytraOnDamage = true; + + @Comment({ + "# Prevent players from flying during combat.", + "# Flying players will fall to the ground if this is enabled." + }) + public boolean disableFlying = true; + + @Comment({ + "# Prevent players from opening their inventory during combat.", + "# Set to 'true' to block inventory access while in combat." + }) + public boolean disableInventoryAccess = false; + + @Comment({ + "# Enable or disable combat logging for damage caused by non-player entities.", + "# Set to 'true' to log damage from non-player sources, or 'false' to disable this feature." + }) + public boolean enableDamageCauseLogging = false; + + @Comment({ + "# Set the mode for logging damage causes.", + "# Available modes: WHITELIST (only listed causes are logged), BLACKLIST (all causes except listed ones are logged)." + }) + public WhitelistBlacklistMode damageCauseRestrictionMode = WhitelistBlacklistMode.WHITELIST; + + @Comment({ + "# List of damage causes to be logged based on the selected mode.", + "# In WHITELIST mode, only these causes are logged. In BLACKLIST mode, all causes except these are logged.", + "# For a full list of damage causes, visit: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/EntityDamageEvent.DamageCause.html" + }) + public List loggedDamageCauses = List.of( + DamageCause.LAVA, + DamageCause.CONTACT, + DamageCause.FIRE, + DamageCause.FIRE_TICK + ); + + @Comment({ + "# List of projectile types that do not trigger combat tagging.", + "# Players hit by these projectiles will not be tagged as in combat.", + "# For a full list of entity types, visit: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/entity/EntityType.html" + }) + public List ignoredProjectileTypes = List.of( + EntityType.ENDER_PEARL, + EntityType.EGG + ); +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/CommandSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/CommandSettings.java new file mode 100644 index 00000000..d0819515 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/CommandSettings.java @@ -0,0 +1,26 @@ +package com.eternalcode.combat.config.implementation; + +import com.eternalcode.combat.WhitelistBlacklistMode; +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.annotation.Comment; +import java.util.List; + +public class CommandSettings extends OkaeriConfig { + @Comment({ + "# Set the mode for command restrictions during combat.", + "# Available modes: WHITELIST (only listed commands are allowed), BLACKLIST (listed commands are blocked)." + }) + public WhitelistBlacklistMode commandRestrictionMode = WhitelistBlacklistMode.BLACKLIST; + + @Comment({ + "# List of commands affected by the command restriction mode.", + "# In BLACKLIST mode, these commands are blocked. In WHITELIST mode, only these commands are allowed." + }) + public List restrictedCommands = List.of( + "gamemode", + "spawn", + "tp", + "tpa", + "tpaccept" + ); +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/MessagesSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/MessagesSettings.java new file mode 100644 index 00000000..6b5c65c9 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/MessagesSettings.java @@ -0,0 +1,200 @@ +package com.eternalcode.combat.config.implementation; + +import com.eternalcode.multification.bukkit.notice.BukkitNotice; +import com.eternalcode.multification.notice.Notice; +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.annotation.Comment; +import org.bukkit.Sound; +import org.bukkit.SoundCategory; + +public class MessagesSettings extends OkaeriConfig { + + @Comment({ + "# Customize messages related to admin commands and notifications.", + "# These messages are displayed to server administrators." + }) + public AdminMessages admin = new AdminMessages(); + + @Comment({ + " ", + "# Configure the combat log notification displayed to players.", + "# You can use the {TIME} variable to display the remaining combat time.", + " ", + "# BossBar progress: Set to -1.0 to show the remaining combat time as a progress bar.", + "# BossBar colors: https://javadoc.io/static/net.kyori/adventure-api/4.14.0/net/kyori/adventure/bossbar/BossBar.Color.html", + "# BossBar overlays: https://javadoc.io/static/net.kyori/adventure-api/4.14.0/net/kyori/adventure/bossbar/BossBar.Overlay.html" + }) + public Notice combatNotification = BukkitNotice.builder() + .actionBar("Combat ends in: {TIME}") + .sound(Sound.ENTITY_EXPERIENCE_ORB_PICKUP, SoundCategory.PLAYERS, 2.0F, 1.0F) + .build(); + + @Comment({ + "# Message displayed when a player lacks permission to execute a command.", + "# The {PERMISSION} placeholder is replaced with the required permission." + }) + public Notice noPermission = Notice.chat( + "You don't have permission ({PERMISSION}) to perform this command!"); + + @Comment({ + "# Message displayed when a specified player is not found.", + "# This message is shown when a command targets a player who is not online or does not exist." + }) + public Notice playerNotFound = + Notice.chat("The specified player could not be found!"); + + @Comment({ + "# Message displayed when a player enters combat.", + "# This message warns the player not to leave the server while in combat." + }) + public Notice playerTagged = Notice.chat( + "You are in combat! Do not leave the server!"); + + @Comment({ + "# Message displayed when a player leaves combat.", + "# This message informs the player that they can safely leave the server." + }) + public Notice playerUntagged = Notice.chat( + "Combat ended! You can now safely leave!"); + + @Comment({ + "# Broadcast message displayed when a player logs out during combat.", + "# The {PLAYER} placeholder is replaced with the player's name." + }) + public Notice playerLoggedOutDuringCombat = + Notice.chat("{PLAYER} logged off during combat!"); + + @Comment({ + "# Message displayed when a player attempts to use a disabled command during combat.", + "# This message informs the player that the command is prohibited while in combat." + }) + public Notice commandDisabledDuringCombat = Notice.chat( + "Command blocked! Cannot use this during combat!"); + + @Comment({ + "# Message displayed when a player uses a command with incorrect arguments.", + "# The {USAGE} placeholder is replaced with the correct command syntax." + }) + public Notice invalidCommandUsage = Notice.chat("Usage: {USAGE}"); + + @Comment({ + "# Message displayed when a player attempts to open their inventory during combat.", + "# This message informs the player that inventory access is blocked while in combat." + }) + public Notice inventoryBlockedDuringCombat = Notice.chat( + "Inventory access is restricted during combat!"); + + @Comment({ + "# Message displayed when a player attempts to place a block during combat.", + "# The {MODE} placeholder is replaced with the block placement mode (ABOVE/BELOW).", + "# The {Y} placeholder is replaced with the Y coordinate set in the config." + }) + public Notice blockPlacingBlockedDuringCombat = + Notice.chat("⚠ Block placement {MODE} Y:{Y} is restricted!"); + + @Comment({ + "# Message displayed when a player attempts to enter a restricted region during combat.", + "# This message informs the player that they cannot enter the region while in combat." + }) + public Notice cantEnterOnRegion = Notice.chat( + "⚠ Restricted area! Cannot enter during combat!"); + + public static class AdminMessages extends OkaeriConfig { + @Comment({ + "# Message displayed when the console attempts to use a player-only command.", + "# This message informs the console that the command is not available for non-players." + }) + public Notice onlyForPlayers = + Notice.chat("❌ This command is player-only!"); + + @Comment({ + "# Message displayed to an admin when they tag a player.", + "# The {PLAYER} placeholder is replaced with the tagged player's name." + }) + public Notice adminTagPlayer = + Notice.chat("Tagged player: {PLAYER}"); + + @Comment({ + "# Message displayed when an admin tags multiple players.", + "# The {FIRST_PLAYER} and {SECOND_PLAYER} placeholders are replaced with the players' names." + }) + public Notice adminTagMultiplePlayers = Notice.chat( + "Tagged: {FIRST_PLAYER} and {SECOND_PLAYER}"); + + @Comment({ + "# Message displayed to an admin when they remove a player from combat.", + "# The {PLAYER} placeholder is replaced with the player's name." + }) + public Notice adminUntagPlayer = Notice.chat( + "Removed {PLAYER} from combat"); + + @Comment({ + "# Message displayed when an admin attempts to untag a player who is not in combat.", + "# This message informs the admin that the player is not currently in combat." + }) + public Notice adminPlayerNotInCombat = + Notice.chat("{PLAYER} is not in combat!"); + + @Comment({ + "# Message displayed when a player is in combat.", + "# The {PLAYER} placeholder is replaced with the player's name." + }) + public Notice playerInCombat = + Notice.chat("{PLAYER} is in combat!"); + + @Comment({ + "# Message displayed when a player is not in combat.", + "# The {PLAYER} placeholder is replaced with the player's name." + }) + public Notice playerNotInCombat = + Notice.chat("{PLAYER} is safe"); + + @Comment({ + "# Message displayed when an admin attempts to tag themselves.", + "# This message informs the admin that they cannot tag themselves." + }) + public Notice adminCannotTagSelf = Notice.chat("❌ Cannot tag yourself!"); + + @Comment({ + "# Message displayed when an admin disables combat tagging for themselves.", + "# The {TIME} placeholder is replaced with the remaining fight time." + }) + public Notice adminTagOutSelf = Notice.chat( + "🛡 Self-protection active for {TIME}"); + + @Comment({ + "# Message displayed when an admin disables combat tagging for another player.", + "# The {PLAYER} placeholder is replaced with the player's name.", + "# The {TIME} placeholder is replaced with the remaining fight time." + }) + public Notice adminTagOut = Notice.chat( + "🛡 Protected {PLAYER} for {TIME}"); + + @Comment({ + "# Message displayed to a player when their combat tagging is disabled.", + "# The {TIME} placeholder is replaced with the remaining fight time." + }) + public Notice playerTagOut = Notice.chat( + "🛡 Protection active for {TIME}"); + + @Comment({ + "# Message displayed when an admin reenables combat tagging for a player.", + "# The {PLAYER} placeholder is replaced with the player's name." + }) + public Notice adminTagOutOff = Notice.chat( + "Re-enabled tagging for {PLAYER}"); + + @Comment({ + "# Message displayed to a player when their combat tagging is reenabled.", + "# This message informs the player that they can now be tagged again." + }) + public Notice playerTagOutOff = Notice.chat(""); + + @Comment({ + "# Message displayed when an admin attempts to tag a player who has tag-out enabled.", + "# This message informs the admin that the player cannot be tagged at this time." + }) + public Notice adminTagOutCanceled = + Notice.chat("❌ Player has tag-out protection!"); + } +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java new file mode 100644 index 00000000..f183b036 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java @@ -0,0 +1,129 @@ +package com.eternalcode.combat.config.implementation; + +import com.eternalcode.combat.border.BorderSettings; +import com.eternalcode.combat.fight.drop.DropSettings; +import com.eternalcode.combat.fight.effect.FightEffectSettings; +import com.eternalcode.combat.fight.knockback.KnockbackSettings; +import com.eternalcode.combat.fight.pearl.FightPearlSettings; +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.annotation.Comment; +import java.time.Duration; +import java.util.List; + +public class PluginConfig extends OkaeriConfig { + + @Comment(" ") + @Comment("# _____ _ EternalCombat _ _____ _ _ ") + @Comment("# | ___| | o()xxx[{:::::::::> | / __ \\ | | | | ") + @Comment("# | |__ | |_ ___ _ __ _ __ __ _| | / \\/ ___ _ __ ___ | |__ __ _| |_ ") + @Comment("# | __|| __/ _ \\ '__| '_ \\ / _` | | | / _ \\| '_ ` _ \\| '_ \\ / _` | __| ") + @Comment("# | |___| || __/ | | | | | (_| | | \\__/\\ (_) | | | | | | |_) | (_| | |_ ") + @Comment("# \\____/ \\__\\___|_| |_| |_|\\__,_|_|\\____/\\___/|_| |_| |_|_.__/ \\__,_|\\__| ") + @Comment(" ") + + @Comment({ + " ", + "# Settings for the plugin.", + "# Modify these to customize the plugin's behavior." + }) + public Settings settings = new Settings(); + + @Comment({ + " ", + "# Settings related to Ender Pearls.", + "# Configure cooldowns, restrictions, and other behaviors for Ender Pearls during combat." + }) + public FightPearlSettings pearl = new FightPearlSettings(); + + @Comment({ + " ", + "# Custom effects applied during combat.", + "# Configure effects like blindness, slowness, or other debuffs that are applied to players in combat." + }) + public FightEffectSettings effect = new FightEffectSettings(); + + @Comment({ + " ", + "# Customize how items are dropped when a player dies during combat.", + "# Configure whether items drop, how they drop, and any additional rules for item drops." + }) + public DropSettings drop = new DropSettings(); + + @Comment({ + " ", + "# Settings related to knockback during combat.", + "# Configure knockback settings and behaviors for players in combat." + }) + public KnockbackSettings knockback = new KnockbackSettings(); + + @Comment({ + " ", + "# Border Settings", + "# Configure the border that appears during combat.", + }) + public BorderSettings border = new BorderSettings(); + + @Comment({ + " ", + "# Settings related to block placement during combat.", + "# Configure restrictions and behaviors for block placement while players are in combat." + }) + public BlockPlacementSettings blockPlacement = new BlockPlacementSettings(); + + @Comment({ + " ", + "# Settings related to commands during combat.", + "# Configure command restrictions and behaviors for players in combat." + }) + public CommandSettings commands = new CommandSettings(); + + @Comment({ + " ", + "# Settings related to the plugin's admin commands and features.", + "# Configure admin-specific settings and behaviors for the plugin." + }) + public AdminSettings admin = new AdminSettings(); + + @Comment({ + " ", + "# Settings related to regions.", + "# Configure region-specific settings and behaviors for combat." + }) + public RegionSettings regions = new RegionSettings(); + + @Comment({ + " ", + "# Settings related to combat and player tagging.", + "# Configure combat rules, and behaviors for player tagging." + }) + public CombatSettings combat = new CombatSettings(); + + @Comment({ + " ", + "# Customize the messages displayed by the plugin.", + "# Modify these to change the text and formatting of notifications and alerts." + }) + public MessagesSettings messagesSettings = new MessagesSettings(); + + public static class Settings extends OkaeriConfig { + @Comment({ + "# Notify players about new plugin updates when they join the server.", + "# Set to 'true' to enable update notifications, or 'false' to disable them." + }) + public boolean notifyAboutUpdates = true; + + @Comment({ + "# The duration (in seconds) that a player remains in combat after being attacked.", + "# After this time expires, the player will no longer be considered in combat." + }) + public Duration combatTimerDuration = Duration.ofSeconds(20); + + @Comment({ + "# List of worlds where combat logging is disabled.", + "# Players in these worlds will not be tagged or affected by combat rules." + }) + public List ignoredWorlds = List.of( + "your_world" + ); + } +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/RegionSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/RegionSettings.java new file mode 100644 index 00000000..d5551cbb --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/config/implementation/RegionSettings.java @@ -0,0 +1,26 @@ +package com.eternalcode.combat.config.implementation; + +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.annotation.Comment; +import java.util.Collections; +import java.util.List; + +public class RegionSettings extends OkaeriConfig { + @Comment({ + "# List of regions where combat is restricted.", + "# Players in these regions will not be able to engage in combat." + }) + public List blockedRegions = Collections.singletonList("your_region"); + + @Comment({ + "# Prevent players from entering regions where PVP is disabled by WorldGuard.", + "# Set to 'true' to enforce this restriction, or 'false' to allow PVP in all regions." + }) + public boolean preventPvpInRegions = true; + + @Comment({ + "# Define the radius of restricted regions if WorldGuard is not used.", + "# This setting is based on the default spawn region." + }) + public int restrictedRegionRadius = 10; +} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/event/EventCaller.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/event/EventCaller.java similarity index 100% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/event/EventCaller.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/event/EventCaller.java diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightManagerImpl.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightManagerImpl.java new file mode 100644 index 00000000..67ad0643 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightManagerImpl.java @@ -0,0 +1,86 @@ +package com.eternalcode.combat.fight; + +import com.eternalcode.combat.event.EventCaller; + +import com.eternalcode.combat.fight.event.CauseOfTag; +import com.eternalcode.combat.fight.event.CauseOfUnTag; +import com.eternalcode.combat.fight.event.FightTagEvent; +import com.eternalcode.combat.fight.event.FightUntagEvent; +import java.time.Duration; +import java.time.Instant; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +public class FightManagerImpl implements FightManager { + + private final Map fights = new ConcurrentHashMap<>(); + private final EventCaller eventCaller; + + public FightManagerImpl(EventCaller eventCaller) { + this.eventCaller = eventCaller; + } + + @Override + public boolean isInCombat(UUID player) { + if (!this.fights.containsKey(player)) { + return false; + } + + FightTag fightTag = this.fights.get(player); + + return !fightTag.isExpired(); + } + + @Override + public FightUntagEvent untag(UUID player, CauseOfUnTag causeOfUnTag) { + FightUntagEvent event = this.eventCaller.publishEvent(new FightUntagEvent(player, causeOfUnTag)); + if (event.isCancelled()) { + return event; + } + + this.fights.remove(player); + return event; + } + + @Override + public FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag) { + return this.tag(target, delay, causeOfTag, null); + } + + @ApiStatus.Experimental + @Override + public FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag, @Nullable UUID tagger) { + FightTagEvent event = this.eventCaller.publishEvent(new FightTagEvent(target, causeOfTag)); + + if (event.isCancelled()) { + return event; + } + Instant now = Instant.now(); + Instant endOfCombatLog = now.plus(delay); + + FightTag fightTag = new FightTagImpl(target, endOfCombatLog, tagger); + + this.fights.put(target, fightTag); + return event; + } + + @Override + public Collection getFights() { + return Collections.unmodifiableCollection(this.fights.values()); + } + + @Override + public FightTag getTag(UUID target) { + return this.fights.get(target); + } + + @Override + public void untagAll() { + this.fights.clear(); + } +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTagCommand.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTagCommand.java new file mode 100644 index 00000000..f94749bc --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTagCommand.java @@ -0,0 +1,160 @@ +package com.eternalcode.combat.fight; + +import com.eternalcode.combat.config.implementation.MessagesSettings; +import com.eternalcode.combat.config.implementation.PluginConfig; +import com.eternalcode.combat.fight.event.CancelTagReason; +import com.eternalcode.combat.fight.event.CauseOfTag; +import com.eternalcode.combat.fight.event.CauseOfUnTag; +import com.eternalcode.combat.fight.event.FightTagEvent; +import com.eternalcode.combat.fight.event.FightUntagEvent; +import com.eternalcode.combat.notification.NoticeService; +import dev.rollczi.litecommands.annotations.argument.Arg; +import dev.rollczi.litecommands.annotations.command.Command; +import dev.rollczi.litecommands.annotations.context.Context; +import dev.rollczi.litecommands.annotations.execute.Execute; +import dev.rollczi.litecommands.annotations.permission.Permission; +import dev.rollczi.litecommands.annotations.priority.Priority; +import dev.rollczi.litecommands.annotations.priority.PriorityValue; +import java.time.Duration; +import java.util.UUID; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +@Command(name = "combatlog", aliases = "combat") +public class FightTagCommand { + + private final FightManager fightManager; + private final NoticeService announcer; + private final PluginConfig config; + + public FightTagCommand(FightManager fightManager, NoticeService announcer, PluginConfig config) { + this.fightManager = fightManager; + this.announcer = announcer; + this.config = config; + } + + @Execute(name = "status") + @Permission("eternalcombat.status") + void status(@Context CommandSender sender, @Arg Player target) { + UUID targetUniqueId = target.getUniqueId(); + + this.announcer.create() + .notice(this.fightManager.isInCombat(targetUniqueId) + ? this.config.messagesSettings.admin.playerInCombat + : this.config.messagesSettings.admin.playerNotInCombat + ) + .placeholder("{PLAYER}", target.getName()) + .viewer(sender) + .send(); + + } + + @Execute(name = "tag") + @Permission("eternalcombat.tag") + @Priority(PriorityValue.HIGH) + void tag(@Context CommandSender sender, @Arg Player target) { + UUID targetUniqueId = target.getUniqueId(); + Duration time = this.config.settings.combatTimerDuration; + + FightTagEvent event = this.fightManager.tag(targetUniqueId, time, CauseOfTag.COMMAND); + + if (event.isCancelled()) { + CancelTagReason cancelReason = event.getCancelReason(); + + this.tagoutReasonHandler(sender, cancelReason, this.config.messagesSettings); + + return; + } + + this.announcer.create() + .notice(this.config.messagesSettings.admin.adminTagPlayer) + .placeholder("{PLAYER}", target.getName()) + .viewer(sender) + .send(); + + } + + @Execute(name = "tag") + @Permission("eternalcombat.tag") + void tagMultiple(@Context CommandSender sender, @Arg Player firstTarget, @Arg Player secondTarget) { + Duration combatTime = this.config.settings.combatTimerDuration; + MessagesSettings messagesSettings = this.config.messagesSettings; + + if (sender.equals(firstTarget) || sender.equals(secondTarget)) { + + this.announcer.create() + .notice(messagesSettings.admin.adminCannotTagSelf) + .viewer(sender) + .send(); + + return; + } + + FightTagEvent firstTagEvent = this.fightManager.tag(firstTarget.getUniqueId(), combatTime, CauseOfTag.COMMAND); + FightTagEvent secondTagEvent = this.fightManager.tag(secondTarget.getUniqueId(), combatTime, CauseOfTag.COMMAND); + + if (firstTagEvent.isCancelled()) { + CancelTagReason cancelReason = firstTagEvent.getCancelReason(); + + this.tagoutReasonHandler(sender, cancelReason, messagesSettings); + + return; + } + + if (secondTagEvent.isCancelled()) { + CancelTagReason cancelReason = secondTagEvent.getCancelReason(); + + this.tagoutReasonHandler(sender, cancelReason, messagesSettings); + + return; + } + + if (firstTagEvent.isCancelled() && secondTagEvent.isCancelled()) { + return; + } + + this.announcer.create() + .notice(messagesSettings.admin.adminTagMultiplePlayers) + .placeholder("{FIRST_PLAYER}", firstTarget.getName()) + .placeholder("{SECOND_PLAYER}", secondTarget.getName()) + .viewer(sender) + .send(); + + } + + @Execute(name = "untag") + @Permission("eternalcombat.untag") + void untag(@Context Player sender, @Arg Player target) { + UUID targetUniqueId = target.getUniqueId(); + + if (!this.fightManager.isInCombat(targetUniqueId)) { + this.announcer.create() + .viewer(sender) + .notice(this.config.messagesSettings.admin.adminPlayerNotInCombat) + .send(); + return; + } + + FightUntagEvent event = this.fightManager.untag(targetUniqueId, CauseOfUnTag.COMMAND); + if (event.isCancelled()) { + return; + } + + + this.announcer.create() + .notice(this.config.messagesSettings.admin.adminUntagPlayer) + .placeholder("{PLAYER}", target.getName()) + .viewer(sender) + .send(); + } + + private void tagoutReasonHandler(CommandSender sender, CancelTagReason cancelReason, MessagesSettings messagesSettings) { + if (cancelReason == CancelTagReason.TAGOUT) { + this.announcer.create() + .notice(messagesSettings.admin.adminTagOutCanceled) + .viewer(sender) + .send(); + + } + } +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTagImpl.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTagImpl.java new file mode 100644 index 00000000..3e71b3f2 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTagImpl.java @@ -0,0 +1,53 @@ +package com.eternalcode.combat.fight; + +import java.time.Duration; +import java.time.Instant; +import java.util.UUID; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +public class FightTagImpl implements FightTag { + + private final UUID taggedPlayer; + private final Instant endOfCombatLog; + private final @Nullable UUID tagger; + + FightTagImpl(UUID personToAddCombat, Instant endOfCombatLog, @Nullable UUID tagger) { + this.taggedPlayer = personToAddCombat; + this.endOfCombatLog = endOfCombatLog; + this.tagger = tagger; + } + + @Override + public UUID getTaggedPlayer() { + return this.taggedPlayer; + } + + @Override + public Instant getEndOfCombatLog() { + return this.endOfCombatLog; + } + + @Override + public boolean isExpired() { + return Instant.now().isAfter(this.endOfCombatLog); + } + + @Override + public Duration getRemainingDuration() { + Duration between = Duration.between(Instant.now(), this.endOfCombatLog); + + if (between.isNegative()) { + return Duration.ZERO; + } + + return between; + } + + @ApiStatus.Experimental + @Override + public @Nullable UUID getTagger() { + return this.tagger; + } + +} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/FightTask.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTask.java similarity index 50% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/fight/FightTask.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTask.java index 6b4fa79c..b7fe6461 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/FightTask.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/FightTask.java @@ -1,15 +1,11 @@ package com.eternalcode.combat.fight; -import com.eternalcode.combat.fight.event.CauseOfUnTag; import com.eternalcode.combat.config.implementation.PluginConfig; -import com.eternalcode.combat.fight.bossbar.FightBossBarService; -import com.eternalcode.combat.notification.Notification; -import com.eternalcode.combat.notification.NotificationAnnouncer; -import com.eternalcode.combat.notification.implementation.BossBarNotification; +import com.eternalcode.combat.fight.event.CauseOfUnTag; +import com.eternalcode.combat.notification.NoticeService; import com.eternalcode.combat.util.DurationUtil; import org.bukkit.Server; import org.bukkit.entity.Player; -import panda.utilities.text.Formatter; import java.time.Duration; import java.util.UUID; @@ -19,14 +15,12 @@ public class FightTask implements Runnable { private final Server server; private final PluginConfig config; private final FightManager fightManager; - private final FightBossBarService bossBarService; - private final NotificationAnnouncer announcer; + private final NoticeService announcer; - public FightTask(Server server, PluginConfig config, FightManager fightManager, FightBossBarService bossBarService, NotificationAnnouncer announcer) { + public FightTask(Server server, PluginConfig config, FightManager fightManager, NoticeService announcer) { this.server = server; this.config = config; this.fightManager = fightManager; - this.bossBarService = bossBarService; this.announcer = announcer; } @@ -47,21 +41,13 @@ public void run() { } Duration remaining = fightTag.getRemainingDuration(); - Formatter formatter = new Formatter() - .register("{TIME}", DurationUtil.format(remaining)); - - Notification combatNotification = this.config.messages.combatNotification; - this.sendFightNotification(player, fightTag, combatNotification, formatter); - } - } + this.announcer.create() + .player(player.getUniqueId()) + .notice(this.config.messagesSettings.combatNotification) + .placeholder("{TIME}", DurationUtil.format(remaining)) + .send(); - private void sendFightNotification(Player player, FightTag fightTag, Notification notification, Formatter formatter) { - if (notification instanceof BossBarNotification bossBarNotification) { - this.bossBarService.send(player, fightTag, bossBarNotification, formatter); - return; } - - this.announcer.send(player, notification, formatter); } } diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/controller/FightActionBlockerController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightActionBlockerController.java similarity index 58% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/fight/controller/FightActionBlockerController.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightActionBlockerController.java index 9432b6ce..9aeb8637 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/controller/FightActionBlockerController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightActionBlockerController.java @@ -2,9 +2,13 @@ import com.eternalcode.combat.config.implementation.PluginConfig; import com.eternalcode.combat.WhitelistBlacklistMode; +import com.eternalcode.combat.config.implementation.BlockPlacementSettings; import com.eternalcode.combat.fight.FightManager; -import com.eternalcode.combat.notification.NotificationAnnouncer; +import com.eternalcode.combat.fight.event.FightUntagEvent; +import com.eternalcode.combat.notification.NoticeService; +import org.bukkit.GameMode; import org.bukkit.Material; +import org.bukkit.Server; import org.bukkit.block.Block; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -18,23 +22,24 @@ import java.util.List; import java.util.UUID; -import panda.utilities.text.Formatter; public class FightActionBlockerController implements Listener { private final FightManager fightManager; - private final NotificationAnnouncer announcer; + private final NoticeService announcer; private final PluginConfig config; + private final Server server; - public FightActionBlockerController(FightManager fightManager, NotificationAnnouncer announcer, PluginConfig config) { + public FightActionBlockerController(FightManager fightManager, NoticeService announcer, PluginConfig config, Server server) { this.fightManager = fightManager; this.announcer = announcer; this.config = config; + this.server = server; } @EventHandler void onPlace(BlockPlaceEvent event) { - if (!this.config.settings.shouldPreventBlockPlacing) { + if (!this.config.blockPlacement.disableBlockPlacing) { return; } @@ -48,36 +53,45 @@ void onPlace(BlockPlaceEvent event) { Block block = event.getBlock(); int level = block.getY(); - List specificBlocksToPreventPlacing = this.config.settings.specificBlocksToPreventPlacing; + List specificBlocksToPreventPlacing = this.config.blockPlacement.restrictedBlockTypes; - boolean isPlacementBlocked = isPlacementBlocked(level); - - Formatter formatter = new Formatter() - .register("{Y}", this.config.settings.blockPlacingYCoordinate) - .register("{MODE}", this.config.settings.blockPlacingModeName); + boolean isPlacementBlocked = this.isPlacementBlocked(level); if (isPlacementBlocked && specificBlocksToPreventPlacing.isEmpty()) { event.setCancelled(true); - this.announcer.sendMessage(player, formatter.format(this.config.messages.blockPlacingBlockedDuringCombat)); + this.announcer.create() + .player(uniqueId) + .notice(this.config.messagesSettings.blockPlacingBlockedDuringCombat) + .placeholder("{Y}", String.valueOf(this.config.blockPlacement.blockPlacementYCoordinate)) + .placeholder("{MODE}", this.config.blockPlacement.blockPlacementModeDisplayName) + .send(); + } Material blockMaterial = block.getType(); boolean isBlockInDisabledList = specificBlocksToPreventPlacing.contains(blockMaterial); if (isPlacementBlocked && isBlockInDisabledList) { event.setCancelled(true); - this.announcer.sendMessage(player, formatter.format(this.config.messages.blockPlacingBlockedDuringCombat)); + + this.announcer.create() + .player(uniqueId) + .notice(this.config.messagesSettings.blockPlacingBlockedDuringCombat) + .placeholder("{Y}", String.valueOf(this.config.blockPlacement.blockPlacementYCoordinate)) + .placeholder("{MODE}", this.config.blockPlacement.blockPlacementModeDisplayName) + .send(); + } } private boolean isPlacementBlocked(int level) { - return this.config.settings.blockPlacingMode == PluginConfig.Settings.BlockPlacingMode.ABOVE - ? level > this.config.settings.blockPlacingYCoordinate - : level < this.config.settings.blockPlacingYCoordinate; + return this.config.blockPlacement.blockPlacementMode == BlockPlacementSettings.BlockPlacingMode.ABOVE + ? level > this.config.blockPlacement.blockPlacementYCoordinate + : level < this.config.blockPlacement.blockPlacementYCoordinate; } @EventHandler void onToggleGlide(EntityToggleGlideEvent event) { - if (!this.config.settings.shouldPreventElytraUsage) { + if (!this.config.combat.disableElytraUsage) { return; } @@ -97,7 +111,7 @@ void onToggleGlide(EntityToggleGlideEvent event) { @EventHandler void onFly(PlayerToggleFlightEvent event) { - if (!this.config.settings.shouldPreventFlying) { + if (!this.config.combat.disableFlying) { return; } @@ -115,9 +129,27 @@ void onFly(PlayerToggleFlightEvent event) { } } + @EventHandler + void onUnTag(FightUntagEvent event) { + if (!this.config.combat.disableFlying) { + return; + } + + UUID uniqueId = event.getPlayer(); + Player player = this.server.getPlayer(uniqueId); + + if (player == null) { + return; + } + GameMode playerGameMode = player.getGameMode(); + if (playerGameMode == GameMode.CREATIVE || playerGameMode == GameMode.SPECTATOR) { + player.setAllowFlight(true); + } + } + @EventHandler void onDamage(EntityDamageEvent event) { - if (!this.config.settings.shouldElytraDisableOnDamage) { + if (!this.config.combat.disableElytraOnDamage) { return; } @@ -133,7 +165,7 @@ void onDamage(EntityDamageEvent event) { @EventHandler void onOpenInventory(InventoryOpenEvent event) { - if (!this.config.settings.shouldPreventInventoryOpening) { + if (!this.config.combat.disableInventoryAccess) { return; } @@ -146,7 +178,11 @@ void onOpenInventory(InventoryOpenEvent event) { event.setCancelled(true); - this.announcer.sendMessage(player, this.config.messages.inventoryBlockedDuringCombat); + this.announcer.create() + .player(uniqueId) + .notice(this.config.messagesSettings.inventoryBlockedDuringCombat) + .send(); + } @EventHandler @@ -160,16 +196,20 @@ void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) { String command = event.getMessage().split(" ")[0].substring(1).toLowerCase(); - boolean isMatchCommand = this.config.settings.blockedCommands.stream() + boolean isMatchCommand = this.config.commands.restrictedCommands.stream() .anyMatch(command::startsWith); - WhitelistBlacklistMode mode = this.config.settings.commandBlockingMode; + WhitelistBlacklistMode mode = this.config.commands.commandRestrictionMode; boolean shouldCancel = mode.shouldBlock(isMatchCommand); if (shouldCancel) { event.setCancelled(true); - this.announcer.sendMessage(player, this.config.messages.commandDisabledDuringCombat); + this.announcer.create() + .player(playerUniqueId) + .notice(this.config.messagesSettings.commandDisabledDuringCombat) + .send(); + } } } diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/controller/FightMessageController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightMessageController.java similarity index 72% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/fight/controller/FightMessageController.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightMessageController.java index 99d5af57..dae9b365 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/controller/FightMessageController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightMessageController.java @@ -2,10 +2,9 @@ import com.eternalcode.combat.config.implementation.PluginConfig; import com.eternalcode.combat.fight.FightManager; -import com.eternalcode.combat.fight.bossbar.FightBossBarService; import com.eternalcode.combat.fight.event.FightTagEvent; import com.eternalcode.combat.fight.event.FightUntagEvent; -import com.eternalcode.combat.notification.NotificationAnnouncer; +import com.eternalcode.combat.notification.NoticeService; import org.bukkit.Server; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -15,15 +14,13 @@ public class FightMessageController implements Listener { private final FightManager fightManager; - private final NotificationAnnouncer announcer; - private final FightBossBarService bossBarService; + private final NoticeService announcer; private final PluginConfig config; private final Server server; - public FightMessageController(FightManager fightManager, NotificationAnnouncer announcer, FightBossBarService bossBarService, PluginConfig config, Server server) { + public FightMessageController(FightManager fightManager, NoticeService announcer, PluginConfig config, Server server) { this.fightManager = fightManager; this.announcer = announcer; - this.bossBarService = bossBarService; this.config = config; this.server = server; } @@ -40,7 +37,10 @@ void onTag(FightTagEvent event) { return; } - this.announcer.sendMessage(player, this.config.messages.playerTagged); + this.announcer.create() + .player(player.getUniqueId()) + .notice(this.config.messagesSettings.playerTagged) + .send(); } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @@ -51,7 +51,10 @@ void onUnTag(FightUntagEvent event) { throw new IllegalStateException("Player cannot be null!"); } - this.announcer.sendMessage(player, this.config.messages.playerUntagged); - this.bossBarService.hide(event.getPlayer()); + this.announcer.create() + .player(player.getUniqueId()) + .notice(this.config.messagesSettings.playerUntagged) + .send(); + } } diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/controller/FightTagController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightTagController.java similarity index 78% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/fight/controller/FightTagController.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightTagController.java index c7c78642..5fe8d995 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/controller/FightTagController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightTagController.java @@ -4,6 +4,7 @@ import com.eternalcode.combat.config.implementation.PluginConfig; import com.eternalcode.combat.fight.FightManager; import com.eternalcode.combat.fight.event.CauseOfTag; +import org.bukkit.GameMode; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; import org.bukkit.entity.Projectile; @@ -34,7 +35,7 @@ void onEntityDamageByEntity(EntityDamageByEntityEvent event) { return; } - List disabledProjectileEntities = this.config.settings.disabledProjectileEntities; + List disabledProjectileEntities = this.config.combat.ignoredProjectileTypes; if (event.getDamager() instanceof Projectile projectile && disabledProjectileEntities.contains(projectile.getType())) { return; @@ -50,19 +51,19 @@ void onEntityDamageByEntity(EntityDamageByEntityEvent event) { return; } - Duration combatTime = this.config.settings.combatDuration; + Duration combatTime = this.config.settings.combatTimerDuration; UUID attackedUniqueId = attackedPlayerByPerson.getUniqueId(); UUID attackerUniqueId = attacker.getUniqueId(); - if (attacker.isOp() && this.config.settings.excludeAdminFromCombat) { + if (this.cannotBeTagged(attacker)) { return; } - if (attackedPlayerByPerson.isOp() && this.config.settings.excludeAdminFromCombat) { + if (this.cannotBeTagged(attackedPlayerByPerson)) { return; } - if (this.config.settings.shouldPreventFlying) { + if (this.config.combat.disableFlying) { if (attackedPlayerByPerson.isFlying()) { attackedPlayerByPerson.setFlying(false); attackedPlayerByPerson.setAllowFlight(false); @@ -80,7 +81,7 @@ void onEntityDamageByEntity(EntityDamageByEntityEvent event) { @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) void onEntityDamage(EntityDamageEvent event) { - if (!this.config.settings.shouldEnableDamageCauses) { + if (!this.config.combat.enableDamageCauseLogging) { return; } @@ -92,12 +93,16 @@ void onEntityDamage(EntityDamageEvent event) { return; } - Duration combatTime = this.config.settings.combatDuration; + if (this.cannotBeTagged(player)) { + return; + } + + Duration combatTime = this.config.settings.combatTimerDuration; UUID uuid = player.getUniqueId(); - List damageCauses = this.config.settings.damageCausesToLog; - WhitelistBlacklistMode mode = this.config.settings.damageCausesMode; + List damageCauses = this.config.combat.loggedDamageCauses; + WhitelistBlacklistMode mode = this.config.combat.damageCauseRestrictionMode; EntityDamageEvent.DamageCause cause = event.getCause(); @@ -126,7 +131,18 @@ Player getDamager(EntityDamageByEntityEvent event) { private boolean isPlayerInDisabledWorld(Player player) { String worldName = player.getWorld().getName(); - return this.config.settings.worldsToIgnore.contains(worldName); + return this.config.settings.ignoredWorlds.contains(worldName); } + private boolean cannotBeTagged(Player player) { + if (player.getGameMode().equals(GameMode.CREATIVE) && this.config.admin.excludeCreativePlayersFromCombat) { + return true; + } + + if (player.isOp() && this.config.admin.excludeAdminsFromCombat) { + return true; + } + + return false; + } } diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/controller/FightUnTagController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightUnTagController.java similarity index 95% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/fight/controller/FightUnTagController.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightUnTagController.java index 1e0157b1..aaa8000b 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/controller/FightUnTagController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/controller/FightUnTagController.java @@ -1,8 +1,8 @@ package com.eternalcode.combat.fight.controller; +import com.eternalcode.combat.fight.FightManager; import com.eternalcode.combat.fight.event.CauseOfUnTag; import com.eternalcode.combat.config.implementation.PluginConfig; -import com.eternalcode.combat.fight.FightManager; import com.eternalcode.combat.fight.logout.LogoutService; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -34,7 +34,7 @@ void onPlayerDeath(PlayerDeathEvent event) { this.fightManager.untag(player.getUniqueId(), cause); - if (killer != null && this.config.settings.shouldReleaseAttacker) { + if (killer != null && this.config.combat.releaseAttackerOnVictimDeath) { this.fightManager.untag(killer.getUniqueId(), CauseOfUnTag.ATTACKER_RELEASE); } } diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/drop/DropController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/DropController.java similarity index 85% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/drop/DropController.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/DropController.java index d1fb05ad..1c3ce344 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/drop/DropController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/DropController.java @@ -1,4 +1,4 @@ -package com.eternalcode.combat.drop; +package com.eternalcode.combat.fight.drop; import com.eternalcode.combat.fight.FightManager; import org.bukkit.entity.Player; @@ -15,13 +15,13 @@ public class DropController implements Listener { - private final DropManager dropManager; - private final DropKeepInventoryManager keepInventoryManager; + private final DropService dropService; + private final DropKeepInventoryService keepInventoryManager; private final DropSettings dropSettings; private final FightManager fightManager; - public DropController(DropManager dropManager, DropKeepInventoryManager keepInventoryManager, DropSettings dropSettings, FightManager fightManager) { - this.dropManager = dropManager; + public DropController(DropService dropService, DropKeepInventoryService keepInventoryManager, DropSettings dropSettings, FightManager fightManager) { + this.dropService = dropService; this.keepInventoryManager = keepInventoryManager; this.dropSettings = dropSettings; this.fightManager = fightManager; @@ -45,7 +45,7 @@ void onPlayerDeath(PlayerDeathEvent event) { .droppedExp(player.getTotalExperience()) .build(); - DropResult result = this.dropManager.modify(dropType, drop); + DropResult result = this.dropService.modify(dropType, drop); if (result == null) { return; diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/drop/DropKeepInventoryManager.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/DropKeepInventoryServiceImpl.java similarity index 84% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/drop/DropKeepInventoryManager.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/DropKeepInventoryServiceImpl.java index 093b710e..98a1fb2d 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/drop/DropKeepInventoryManager.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/DropKeepInventoryServiceImpl.java @@ -1,4 +1,4 @@ -package com.eternalcode.combat.drop; +package com.eternalcode.combat.fight.drop; import org.bukkit.inventory.ItemStack; @@ -9,22 +9,26 @@ import java.util.Map; import java.util.UUID; -public class DropKeepInventoryManager { +public class DropKeepInventoryServiceImpl implements DropKeepInventoryService { private final Map> itemsToGiveAfterRespawn = new HashMap<>(); + @Override public void addItem(UUID uuid, ItemStack item) { this.itemsToGiveAfterRespawn.computeIfAbsent(uuid, k -> new ArrayList<>()).add(item); } + @Override public void addItems(UUID uuid, List item) { item.forEach(i -> this.addItem(uuid, i)); } + @Override public boolean hasItems(UUID uuid) { return this.itemsToGiveAfterRespawn.containsKey(uuid); } + @Override public List nextItems(UUID uuid) { List itemStacks = this.itemsToGiveAfterRespawn.remove(uuid); diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/drop/DropManager.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/DropServiceImpl.java similarity index 86% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/drop/DropManager.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/DropServiceImpl.java index 1e924ca2..2df9fc5b 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/drop/DropManager.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/DropServiceImpl.java @@ -1,16 +1,17 @@ -package com.eternalcode.combat.drop; +package com.eternalcode.combat.fight.drop; import java.util.HashMap; import java.util.Map; -public class DropManager { +public class DropServiceImpl implements DropService { private final Map modifiers; - public DropManager() { + public DropServiceImpl() { this.modifiers = new HashMap<>(); } + @Override public void registerModifier(DropModifier dropModifier) { DropType dropType = dropModifier.getDropType(); @@ -25,6 +26,7 @@ public void registerModifier(DropModifier dropModifier) { this.modifiers.put(dropType, dropModifier); } + @Override public DropResult modify(DropType dropType, Drop drop) { if (!this.modifiers.containsKey(dropType)) { throw new RuntimeException("No drop modifier found for type '%s'".formatted(dropType.name())); diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/drop/DropSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/DropSettings.java similarity index 96% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/drop/DropSettings.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/DropSettings.java index f373ee50..088f290d 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/drop/DropSettings.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/DropSettings.java @@ -1,4 +1,4 @@ -package com.eternalcode.combat.drop; +package com.eternalcode.combat.fight.drop; import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.annotation.Comment; diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/drop/impl/PercentDropModifier.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/impl/PercentDropModifier.java similarity index 78% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/drop/impl/PercentDropModifier.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/impl/PercentDropModifier.java index 81774fbf..f891e937 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/drop/impl/PercentDropModifier.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/impl/PercentDropModifier.java @@ -1,10 +1,10 @@ -package com.eternalcode.combat.drop.impl; +package com.eternalcode.combat.fight.drop.impl; -import com.eternalcode.combat.drop.Drop; -import com.eternalcode.combat.drop.DropModifier; -import com.eternalcode.combat.drop.DropResult; -import com.eternalcode.combat.drop.DropSettings; -import com.eternalcode.combat.drop.DropType; +import com.eternalcode.combat.fight.drop.Drop; +import com.eternalcode.combat.fight.drop.DropModifier; +import com.eternalcode.combat.fight.drop.DropResult; +import com.eternalcode.combat.fight.drop.DropSettings; +import com.eternalcode.combat.fight.drop.DropType; import com.eternalcode.combat.util.InventoryUtil; import com.eternalcode.combat.util.MathUtil; import com.eternalcode.combat.util.RemoveItemResult; diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/drop/impl/PlayersHealthDropModifier.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/impl/PlayersHealthDropModifier.java similarity index 86% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/drop/impl/PlayersHealthDropModifier.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/impl/PlayersHealthDropModifier.java index d6f4ba15..e0ccfdbf 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/drop/impl/PlayersHealthDropModifier.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/drop/impl/PlayersHealthDropModifier.java @@ -1,10 +1,10 @@ -package com.eternalcode.combat.drop.impl; +package com.eternalcode.combat.fight.drop.impl; -import com.eternalcode.combat.drop.Drop; -import com.eternalcode.combat.drop.DropModifier; -import com.eternalcode.combat.drop.DropResult; -import com.eternalcode.combat.drop.DropSettings; -import com.eternalcode.combat.drop.DropType; +import com.eternalcode.combat.fight.drop.Drop; +import com.eternalcode.combat.fight.drop.DropModifier; +import com.eternalcode.combat.fight.drop.DropResult; +import com.eternalcode.combat.fight.drop.DropSettings; +import com.eternalcode.combat.fight.drop.DropType; import com.eternalcode.combat.fight.logout.Logout; import com.eternalcode.combat.fight.logout.LogoutService; import com.eternalcode.combat.util.InventoryUtil; diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/effect/FightEffectController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/effect/FightEffectController.java similarity index 100% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/fight/effect/FightEffectController.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/effect/FightEffectController.java diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/effect/FightEffectServiceImpl.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/effect/FightEffectServiceImpl.java new file mode 100644 index 00000000..f9d4ab02 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/effect/FightEffectServiceImpl.java @@ -0,0 +1,81 @@ +package com.eternalcode.combat.fight.effect; + +import org.bukkit.entity.Player; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.List; +import java.util.ArrayList; + +public class FightEffectServiceImpl implements FightEffectService { + + private final Map> activeEffects = new HashMap<>(); + private static final int INFINITE_DURATION = -1; + + @Override + public void storeActiveEffect(Player player, PotionEffect effect) { + List effects = this.activeEffects.computeIfAbsent(player.getUniqueId(), k -> new ArrayList<>()); + + effects.add(effect); + } + + @Override + public void restoreActiveEffects(Player player) { + List currentEffects = this.getCurrentEffects(player); + + for (PotionEffect effect : currentEffects) { + player.addPotionEffect(effect); + } + + this.clearStoredEffects(player); + } + + @Override + public void clearStoredEffects(Player player) { + this.activeEffects.remove(player.getUniqueId()); + } + + @Override + public List getCurrentEffects(Player player) { + return this.activeEffects.getOrDefault(player.getUniqueId(), new ArrayList<>()); + } + + @Override + public void applyCustomEffect(Player player, PotionEffectType type, Integer amplifier) { + PotionEffect activeEffect = player.getPotionEffect(type); + + if (activeEffect == null) { + player.addPotionEffect(new PotionEffect(type, INFINITE_DURATION, amplifier)); + return; + } + + if (activeEffect.getAmplifier() > amplifier) { + return; + } + + if (activeEffect.getDuration() == -1) { + return; + } + + this.storeActiveEffect(player, activeEffect); + player.addPotionEffect(new PotionEffect(type, INFINITE_DURATION, amplifier)); + } + + @Override + public void removeCustomEffect(Player player, PotionEffectType type, Integer amplifier) { + PotionEffect activeEffect = player.getPotionEffect(type); + + if (activeEffect == null) { + return; + } + + if (activeEffect.getAmplifier() != amplifier) { + return; + } + + player.removePotionEffect(type); + } +} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/effect/FightEffectSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/effect/FightEffectSettings.java similarity index 100% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/fight/effect/FightEffectSettings.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/effect/FightEffectSettings.java diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackOutsideRegionGenerator.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackOutsideRegionGenerator.java new file mode 100644 index 00000000..13c48872 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackOutsideRegionGenerator.java @@ -0,0 +1,98 @@ +package com.eternalcode.combat.fight.knockback; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.NavigableMap; +import java.util.Set; +import java.util.TreeMap; +import org.bukkit.Location; +import org.bukkit.World; + +class KnockbackOutsideRegionGenerator { + + private record Point2D(int x, int z) { + + Location toLocation(Location location) { + World world = location.getWorld(); + int y = world.getHighestBlockYAt(x, z) + 1; + + return new Location(world, x, y, z, location.getYaw(), location.getPitch()); + } + + private static Point2D from(Location location) { + return new Point2D(location.getBlockX(), location.getBlockZ()); + } + + } + + static Location generate(Location min, Location max, Location playerLocation) { + NavigableMap> points = generatePoints(Point2D.from(min), Point2D.from(max), Point2D.from(playerLocation)); + NavigableMap distances = new TreeMap<>(); + double totalWeight = 0; + + Double maxDistance = points.lastKey(); + + for (double distance : points.keySet()) { + double weight = createWeight(distance, maxDistance); + distances.put(distance, weight); + totalWeight += weight; + } + + double rand = Math.random() * totalWeight; + double cumulativeWeight = 0; + + for (Map.Entry entry : distances.entrySet()) { + double distance = entry.getKey(); + double weight = entry.getValue(); + + cumulativeWeight += weight; + if (rand <= cumulativeWeight) { + return getRandom(points.get(distance)) + .toLocation(playerLocation); + } + } + + return getRandom(points.firstEntry().getValue()) + .toLocation(playerLocation); + } + + private static Point2D getRandom(List points) { + return points.get((int) (Math.random() * points.size())); + } + + private static double createWeight(double distance, double maxDistance) { + double last = Math.log(maxDistance); + double weight = last - Math.log(distance); + return Math.pow(weight, 10); + } + + private static NavigableMap> generatePoints(Point2D min, Point2D max, Point2D location) { + Set points = new HashSet<>(); + + for (int i = min.x() - 1; i <= max.x() + 1; i++) { + points.add(new Point2D(i, min.z() - 1)); + points.add(new Point2D(i, max.z() + 1)); + } + for (int i = min.z(); i <= max.z(); i++) { + points.add(new Point2D(min.x() - 1, i)); + points.add(new Point2D(max.x() + 1, i)); + } + + TreeMap> result = new TreeMap<>(); + + for (Point2D point : points) { + double distance = distance(location, point); + result.computeIfAbsent(distance, k -> new ArrayList<>()).add(point); + } + + return result; + } + + private static double distance(Point2D p1, Point2D p2) { + return Math.sqrt(Math.pow(p2.x() - p1.x(), 2) + Math.pow(p2.z() - p1.z(), 2)); + } + + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackRegionController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackRegionController.java new file mode 100644 index 00000000..ae092a2f --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackRegionController.java @@ -0,0 +1,114 @@ +package com.eternalcode.combat.fight.knockback; + +import com.eternalcode.combat.fight.FightManager; +import com.eternalcode.combat.fight.event.FightTagEvent; +import com.eternalcode.combat.notification.NoticeService; +import com.eternalcode.combat.region.Region; +import com.eternalcode.combat.region.RegionProvider; +import java.time.Duration; +import java.util.Optional; +import org.bukkit.Location; +import org.bukkit.Server; +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.PlayerMoveEvent; +import org.bukkit.event.player.PlayerTeleportEvent; + +public class KnockbackRegionController implements Listener { + + private final NoticeService announcer; + private final RegionProvider regionProvider; + private final FightManager fightManager; + private final KnockbackService knockbackService; + private final Server server; + + public KnockbackRegionController(NoticeService announcer, RegionProvider regionProvider, FightManager fightManager, KnockbackService knockbackService, Server server) { + this.announcer = announcer; + this.regionProvider = regionProvider; + this.fightManager = fightManager; + this.knockbackService = knockbackService; + this.server = server; + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) + void onPlayerMove(PlayerMoveEvent event) { + Player player = event.getPlayer(); + if (!this.fightManager.isInCombat(player.getUniqueId())) { + return; + } + + Location locationTo = event.getTo(); + int xTo = locationTo.getBlockX(); + int yTo = locationTo.getBlockY(); + int zTo = locationTo.getBlockZ(); + + Location locationFrom = event.getFrom(); + int xFrom = locationFrom.getBlockX(); + int yFrom = locationFrom.getBlockY(); + int zFrom = locationFrom.getBlockZ(); + + if (xTo != xFrom || yTo != yFrom || zTo != zFrom) { + Optional regionOptional = this.regionProvider.getRegion(locationTo); + if (regionOptional.isEmpty()) { + return; + } + + Region region = regionOptional.get(); + if (region.contains(locationFrom)) { + this.knockbackService.knockback(region, player); + this.knockbackService.forceKnockbackLater(player, region); + } else { + event.setCancelled(true); + this.knockbackService.knockbackLater(region, player, Duration.ofMillis(50)); + } + + this.announcer.create() + .player(player.getUniqueId()) + .notice(config -> config.messagesSettings.cantEnterOnRegion) + .send(); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) + void onPlayerTeleport(PlayerTeleportEvent event) { + Player player = event.getPlayer(); + if (!this.fightManager.isInCombat(player.getUniqueId())) { + return; + } + + Location targetLocation = event.getTo(); + + if (this.regionProvider.isInRegion(targetLocation)) { + event.setCancelled(true); + this.announcer.create() + .player(player.getUniqueId()) + .notice(config -> config.messagesSettings.cantEnterOnRegion) + .send(); + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) + void onTag(FightTagEvent event) { + Player player = this.server.getPlayer(event.getPlayer()); + if (player == null) { + throw new IllegalStateException("Player cannot be null!"); + } + + Optional regionOptional = this.regionProvider.getRegion(player.getLocation()); + if (regionOptional.isEmpty()) { + return; + } + + Region region = regionOptional.get(); + this.knockbackService.knockback(region, player); + this.knockbackService.forceKnockbackLater(player, region); + + this.announcer.create() + .player(player.getUniqueId()) + .notice(config -> config.messagesSettings.cantEnterOnRegion) + .send(); + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackService.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackService.java new file mode 100644 index 00000000..5aeb4742 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackService.java @@ -0,0 +1,60 @@ +package com.eternalcode.combat.fight.knockback; + +import com.eternalcode.combat.config.implementation.PluginConfig; +import com.eternalcode.combat.region.Region; +import com.eternalcode.commons.scheduler.Scheduler; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerTeleportEvent; +import org.bukkit.util.Vector; + +public final class KnockbackService { + + private final PluginConfig config; + private final Scheduler scheduler; + + private final Map insideRegion = new HashMap<>(); + + public KnockbackService(PluginConfig config, Scheduler scheduler) { + this.config = config; + this.scheduler = scheduler; + } + + public void knockbackLater(Region region, Player player, Duration duration) { + this.scheduler.laterSync(() -> this.knockback(region, player), duration); + } + + public void forceKnockbackLater(Player player, Region region) { + if (insideRegion.containsKey(player.getUniqueId())) { + return; + } + + insideRegion.put(player.getUniqueId(), region); + scheduler.laterSync(() -> { + insideRegion.remove(player.getUniqueId()); + Location playerLocation = player.getLocation(); + if (!region.contains(playerLocation)) { + return; + } + + Location location = KnockbackOutsideRegionGenerator.generate(region.getMin(), region.getMax(), playerLocation); + player.teleport(location, PlayerTeleportEvent.TeleportCause.PLUGIN); + }, this.config.knockback.forceDelay); + } + + public void knockback(Region region, Player player) { + Location centerOfRegion = region.getCenter(); + Location subtract = player.getLocation().subtract(centerOfRegion); + + Vector knockbackVector = new Vector(subtract.getX(), 0, subtract.getZ()).normalize(); + double multiplier = this.config.knockback.multiplier; + Vector configuredVector = new Vector(multiplier, 0.5, multiplier); + + player.setVelocity(knockbackVector.multiply(configuredVector)); + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackSettings.java new file mode 100644 index 00000000..8b722435 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/knockback/KnockbackSettings.java @@ -0,0 +1,19 @@ +package com.eternalcode.combat.fight.knockback; + +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.annotation.Comment; +import java.time.Duration; + +public class KnockbackSettings extends OkaeriConfig { + + @Comment({ + "# Adjust the knockback multiplier for restricted regions.", + "# Higher values increase the knockback distance. Avoid using negative values.", + "# A value of 1.0 typically knocks players 2-4 blocks away." + }) + public double multiplier = 1; + + @Comment({ "# Time after which the player will be force knocked back outside the safe zone" }) + public Duration forceDelay = Duration.ofSeconds(1); + +} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/logout/Logout.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/logout/Logout.java similarity index 100% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/fight/logout/Logout.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/logout/Logout.java diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/logout/LogoutController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/logout/LogoutController.java similarity index 69% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/fight/logout/LogoutController.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/logout/LogoutController.java index c870fa38..44f33f89 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/logout/LogoutController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/logout/LogoutController.java @@ -2,23 +2,22 @@ import com.eternalcode.combat.config.implementation.PluginConfig; import com.eternalcode.combat.fight.FightManager; -import com.eternalcode.combat.notification.NotificationAnnouncer; +import com.eternalcode.combat.notification.NoticeService; 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.PlayerQuitEvent; -import panda.utilities.text.Formatter; public class LogoutController implements Listener { private final FightManager fightManager; private final LogoutService logoutService; - private final NotificationAnnouncer announcer; + private final NoticeService announcer; private final PluginConfig config; - public LogoutController(FightManager fightManager, LogoutService logoutService, NotificationAnnouncer announcer, PluginConfig config) { + public LogoutController(FightManager fightManager, LogoutService logoutService, NoticeService announcer, PluginConfig config) { this.fightManager = fightManager; this.logoutService = logoutService; this.announcer = announcer; @@ -36,11 +35,11 @@ void onQuit(PlayerQuitEvent event) { this.logoutService.punishForLogout(player); player.setHealth(0.0); - Formatter formatter = new Formatter() - .register("{PLAYER}", player.getName()); - - String format = formatter.format(this.config.messages.playerLoggedOutDuringCombat); - this.announcer.broadcast(format); + this.announcer.create() + .notice(this.config.messagesSettings.playerLoggedOutDuringCombat) + .placeholder("{PLAYER}", player.getName()) + .all() + .send(); } diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/logout/LogoutService.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/logout/LogoutService.java similarity index 100% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/fight/logout/LogoutService.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/logout/LogoutService.java diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlController.java similarity index 71% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlController.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlController.java index 28282b91..56a8c28d 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlController.java @@ -1,7 +1,7 @@ package com.eternalcode.combat.fight.pearl; import com.eternalcode.combat.fight.FightManager; -import com.eternalcode.combat.notification.NotificationAnnouncer; +import com.eternalcode.combat.notification.NoticeService; import com.eternalcode.combat.util.DurationUtil; import org.bukkit.Material; import org.bukkit.entity.EnderPearl; @@ -14,7 +14,6 @@ import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.event.player.PlayerInteractEvent; import org.bukkit.inventory.ItemStack; -import panda.utilities.text.Formatter; import java.time.Duration; import java.util.UUID; @@ -22,15 +21,15 @@ public class FightPearlController implements Listener { private final FightPearlSettings settings; - private final NotificationAnnouncer announcer; + private final NoticeService announcer; private final FightManager fightManager; - private final FightPearlManager fightPearlManager; + private final FightPearlService fightPearlService; - public FightPearlController(FightPearlSettings settings, NotificationAnnouncer announcer, FightManager fightManager, FightPearlManager fightPearlManager) { + public FightPearlController(FightPearlSettings settings, NoticeService announcer, FightManager fightManager, FightPearlService fightPearlService) { this.settings = settings; this.announcer = announcer; this.fightManager = fightManager; - this.fightPearlManager = fightPearlManager; + this.fightPearlService = fightPearlService; } @EventHandler @@ -58,24 +57,29 @@ void onInteract(PlayerInteractEvent event) { if (this.settings.pearlThrowDelay.isZero()) { event.setCancelled(true); - this.announcer.sendMessage(player, this.settings.pearlThrowBlockedDuringCombat); + this.announcer.create() + .player(uniqueId) + .notice(this.settings.pearlThrowBlockedDuringCombat) + .send(); + return; } - if (this.fightPearlManager.hasDelay(uniqueId)) { + if (this.fightPearlService.hasDelay(uniqueId)) { event.setCancelled(true); - Duration remainingPearlDelay = this.fightPearlManager.getRemainingDelay(uniqueId); + Duration remainingPearlDelay = this.fightPearlService.getRemainingDelay(uniqueId); - Formatter formatter = new Formatter() - .register("{TIME}", DurationUtil.format(remainingPearlDelay)); + this.announcer.create() + .player(uniqueId) + .notice(this.settings.pearlThrowBlockedDelayDuringCombat) + .placeholder("{TIME}", DurationUtil.format(remainingPearlDelay)) + .send(); - String format = formatter.format(this.settings.pearlThrowBlockedDelayDuringCombat); - this.announcer.sendMessage(player, format); return; } - this.fightPearlManager.markDelay(uniqueId); + this.fightPearlService.markDelay(uniqueId); } @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlManager.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlServiceImpl.java similarity index 84% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlManager.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlServiceImpl.java index 5cdaf423..4101a87b 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlManager.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlServiceImpl.java @@ -7,30 +7,34 @@ import java.time.Instant; import java.util.UUID; -public class FightPearlManager { +public class FightPearlServiceImpl implements FightPearlService { private final FightPearlSettings pearlSettings; private final Cache pearlDelays; - public FightPearlManager(FightPearlSettings pearlSettings) { + public FightPearlServiceImpl(FightPearlSettings pearlSettings) { this.pearlSettings = pearlSettings; this.pearlDelays = Caffeine.newBuilder() .expireAfterWrite(pearlSettings.pearlThrowDelay) .build(); } + @Override public void markDelay(UUID uuid) { this.pearlDelays.put(uuid, Instant.now().plus(this.pearlSettings.pearlThrowDelay)); } + @Override public boolean hasDelay(UUID uuid) { return Instant.now().isBefore(this.getDelay(uuid)); } + @Override public Duration getRemainingDelay(UUID uuid) { return Duration.between(Instant.now(), this.getDelay(uuid)); } + @Override public Instant getDelay(UUID uuid) { return this.pearlDelays.asMap().getOrDefault(uuid, Instant.MIN); } diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlSettings.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlSettings.java similarity index 67% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlSettings.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlSettings.java index 62fa6e04..5042ee04 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlSettings.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/pearl/FightPearlSettings.java @@ -1,5 +1,7 @@ package com.eternalcode.combat.fight.pearl; +import com.eternalcode.multification.bukkit.notice.BukkitNotice; +import com.eternalcode.multification.notice.Notice; import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.annotation.Comment; @@ -21,9 +23,13 @@ public class FightPearlSettings extends OkaeriConfig { public Duration pearlThrowDelay = Duration.ofSeconds(3); @Comment("# Message sent when player tries to throw ender pearl, but are disabled") - public String pearlThrowBlockedDuringCombat = "&cThrowing ender pearls is prohibited during combat!"; + public Notice pearlThrowBlockedDuringCombat = BukkitNotice.builder() + .chat("Throwing ender pearls is prohibited during combat!") + .build(); @Comment("# Message sent when player tries to throw ender pearl, but has delay") - public String pearlThrowBlockedDelayDuringCombat = "&cYou must wait {TIME} before next throw!"; + public Notice pearlThrowBlockedDelayDuringCombat = BukkitNotice.builder() + .chat("You must wait {TIME} before next throw!") + .build(); } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutCommand.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutCommand.java new file mode 100644 index 00000000..09ea32f9 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutCommand.java @@ -0,0 +1,102 @@ +package com.eternalcode.combat.fight.tagout; + +import com.eternalcode.combat.config.implementation.PluginConfig; +import com.eternalcode.combat.notification.NoticeService; +import com.eternalcode.combat.util.DurationUtil; +import dev.rollczi.litecommands.annotations.argument.Arg; +import dev.rollczi.litecommands.annotations.command.Command; +import dev.rollczi.litecommands.annotations.context.Context; +import dev.rollczi.litecommands.annotations.execute.Execute; +import dev.rollczi.litecommands.annotations.permission.Permission; +import java.time.Duration; +import java.util.UUID; +import org.bukkit.entity.Player; + +@Permission("eternalcombat.tagout") +@Command(name = "tagout", aliases = "tagimmunity") +public class FightTagOutCommand { + + private final FightTagOutService fightTagOutService; + private final NoticeService announcer; + private final PluginConfig config; + + public FightTagOutCommand( + FightTagOutService fightTagOutService, + NoticeService announcer, + PluginConfig config + ) { + this.fightTagOutService = fightTagOutService; + this.announcer = announcer; + this.config = config; + } + + @Execute + void tagout(@Context Player sender, @Arg Duration time) { + UUID targetUniqueId = sender.getUniqueId(); + + this.fightTagOutService.tagOut(targetUniqueId, time); + + this.announcer.create() + .notice(this.config.messagesSettings.admin.adminTagOutSelf) + .placeholder("{TIME}", DurationUtil.format(time)) + .viewer(sender) + .send(); + + } + + @Execute + void tagout(@Context Player sender, @Arg Player target, @Arg Duration time) { + UUID targetUniqueId = target.getUniqueId(); + + this.fightTagOutService.tagOut(targetUniqueId, time); + + this.announcer.create() + .notice(this.config.messagesSettings.admin.adminTagOut) + .placeholder("{PLAYER}", target.getName()) + .placeholder("{TIME}", DurationUtil.format(time)) + .viewer(sender) + .send(); + + this.announcer.create() + .notice(this.config.messagesSettings.admin.playerTagOut) + .placeholder("{TIME}", DurationUtil.format(time)) + .player(target.getUniqueId()) + .send(); + + } + + @Execute(name = "remove") + void untagout(@Context Player sender, @Arg Player target) { + UUID targetUniqueId = target.getUniqueId(); + + this.fightTagOutService.unTagOut(targetUniqueId); + + + if (!targetUniqueId.equals(sender.getUniqueId())) { + this.announcer.create() + .notice(this.config.messagesSettings.admin.adminTagOutOff) + .placeholder("{PLAYER}", target.getName()) + .viewer(sender) + .send(); + } + + this.announcer.create() + .notice(this.config.messagesSettings.admin.playerTagOutOff) + .player(targetUniqueId) + .send(); + } + + @Execute(name = "remove") + void untagout(@Context Player sender) { + UUID senderUniqueId = sender.getUniqueId(); + + this.fightTagOutService.unTagOut(senderUniqueId); + + this.announcer.create() + .notice(this.config.messagesSettings.admin.playerTagOutOff) + .viewer(sender) + .send(); + + } +} + diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutController.java similarity index 71% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutController.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutController.java index 1815583f..dd520444 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutController.java @@ -1,6 +1,6 @@ package com.eternalcode.combat.fight.tagout; -import com.eternalcode.combat.config.implementation.PluginConfig; +import com.eternalcode.combat.fight.event.CancelTagReason; import com.eternalcode.combat.fight.event.FightTagEvent; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -10,11 +10,9 @@ public class FightTagOutController implements Listener { private final FightTagOutService tagOutService; - private final PluginConfig config; - public FightTagOutController(FightTagOutService tagOutService, PluginConfig config) { + public FightTagOutController(FightTagOutService tagOutService) { this.tagOutService = tagOutService; - this.config = config; } @EventHandler @@ -22,7 +20,8 @@ void onTagOut(FightTagEvent event) { UUID uniqueId = event.getPlayer(); if (this.tagOutService.isTaggedOut(uniqueId)) { - event.cancel(this.config.messages.admin.adminTagOutCanceled); + event.setCancelReason(CancelTagReason.TAGOUT); + event.setCancelled(true); } } diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutServiceImpl.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutServiceImpl.java new file mode 100644 index 00000000..548e6d1a --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/tagout/FightTagOutServiceImpl.java @@ -0,0 +1,37 @@ +package com.eternalcode.combat.fight.tagout; + +import java.time.Duration; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class FightTagOutServiceImpl implements FightTagOutService { + + private final Map tagOuts = new HashMap<>(); + + @Override + public void tagOut(UUID player, Duration duration) { + Instant endTime = Instant.now().plus(duration); + + this.tagOuts.put(player, endTime); + } + + @Override + public void unTagOut(UUID player) { + this.tagOuts.remove(player); + } + + @Override + public boolean isTaggedOut(UUID player) { + Instant endTime = this.tagOuts.get(player); + + if (endTime == null) { + return false; + } + Instant now = Instant.now(); + + return now.isBefore(endTime); + } + +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/handler/InvalidUsageHandlerImpl.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/handler/InvalidUsageHandlerImpl.java new file mode 100644 index 00000000..4d60fadd --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/handler/InvalidUsageHandlerImpl.java @@ -0,0 +1,39 @@ +package com.eternalcode.combat.handler; + +import com.eternalcode.combat.config.implementation.PluginConfig; +import com.eternalcode.combat.notification.NoticeService; +import dev.rollczi.litecommands.handler.result.ResultHandlerChain; +import dev.rollczi.litecommands.invalidusage.InvalidUsage; +import dev.rollczi.litecommands.invalidusage.InvalidUsageHandler; +import dev.rollczi.litecommands.invocation.Invocation; +import dev.rollczi.litecommands.schematic.Schematic; +import org.bukkit.command.CommandSender; + +public class InvalidUsageHandlerImpl implements InvalidUsageHandler { + + private final PluginConfig config; + private final NoticeService announcer; + + public InvalidUsageHandlerImpl(PluginConfig config, NoticeService announcer) { + this.config = config; + this.announcer = announcer; + } + + @Override + public void handle( + Invocation invocation, + InvalidUsage commandSenderInvalidUsage, + ResultHandlerChain resultHandlerChain + ) { + Schematic schematic = commandSenderInvalidUsage.getSchematic(); + + for (String usage : schematic.all()) { + this.announcer.create() + .viewer(invocation.sender()) + .notice(this.config.messagesSettings.invalidCommandUsage) + .placeholder("{USAGE}", usage) + .send(); + } + + } +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/handler/MissingPermissionHandlerImpl.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/handler/MissingPermissionHandlerImpl.java new file mode 100644 index 00000000..f5e6862b --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/handler/MissingPermissionHandlerImpl.java @@ -0,0 +1,38 @@ +package com.eternalcode.combat.handler; + +import com.eternalcode.combat.config.implementation.PluginConfig; +import com.eternalcode.combat.notification.NoticeService; +import dev.rollczi.litecommands.handler.result.ResultHandlerChain; +import dev.rollczi.litecommands.invocation.Invocation; +import dev.rollczi.litecommands.permission.MissingPermissions; +import dev.rollczi.litecommands.permission.MissingPermissionsHandler; +import org.bukkit.command.CommandSender; + +public class MissingPermissionHandlerImpl implements MissingPermissionsHandler { + + private final PluginConfig config; + private final NoticeService announcer; + + public MissingPermissionHandlerImpl(PluginConfig config, NoticeService announcer) { + this.config = config; + this.announcer = announcer; + } + + @Override + public void handle( + Invocation invocation, + MissingPermissions missingPermissions, + ResultHandlerChain resultHandlerChain + ) { + String joinedText = missingPermissions.asJoinedText(); + + + this.announcer.create() + .viewer(invocation.sender()) + .notice(this.config.messagesSettings.noPermission) + .placeholder("{PERMISSION}", joinedText) + .send(); + + } +} + diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/notification/NoticeService.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/notification/NoticeService.java new file mode 100644 index 00000000..2ada96b8 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/notification/NoticeService.java @@ -0,0 +1,48 @@ +package com.eternalcode.combat.notification; + +import com.eternalcode.combat.config.implementation.PluginConfig; +import com.eternalcode.multification.adventure.AudienceConverter; +import com.eternalcode.multification.bukkit.BukkitMultification; +import com.eternalcode.multification.translation.TranslationProvider; +import net.kyori.adventure.platform.AudienceProvider; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.serializer.ComponentSerializer; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +public final class NoticeService extends BukkitMultification { + + private final AudienceProvider audienceProvider; + private final PluginConfig pluginConfig; + private final MiniMessage miniMessage; + + public NoticeService(AudienceProvider audienceProvider, PluginConfig pluginConfig, MiniMessage miniMessage) { + this.audienceProvider = audienceProvider; + this.pluginConfig = pluginConfig; + this.miniMessage = miniMessage; + } + + @Override + protected @NotNull TranslationProvider translationProvider() { + return locale -> this.pluginConfig; + } + + @Override + protected @NotNull ComponentSerializer serializer() { + return this.miniMessage; + } + + @Override + protected @NotNull AudienceConverter audienceConverter() { + return commandSender -> { + if (commandSender instanceof Player player) { + return this.audienceProvider.player(player.getUniqueId()); + } + + return this.audienceProvider.console(); + }; + + } +} diff --git a/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/DefaultRegionProvider.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/DefaultRegionProvider.java new file mode 100644 index 00000000..481ec8d3 --- /dev/null +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/DefaultRegionProvider.java @@ -0,0 +1,63 @@ +package com.eternalcode.combat.region; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; + +public class DefaultRegionProvider implements RegionProvider { + + private final int radius; + + public DefaultRegionProvider(int radius) { + this.radius = radius; + } + + @Override + public Optional getRegion(Location location) { + World world = location.getWorld(); + Region spawnRegion = this.createSpawnRegion(world); + if (spawnRegion.contains(location.getX(), location.getY(), location.getZ())) { + return Optional.of(spawnRegion); + } + + return Optional.empty(); + } + + @Override + public Collection getRegions(World world) { + Region spawnRegion = this.createSpawnRegion(world); + return List.of(spawnRegion); + } + + private Region createSpawnRegion(World world) { + Location spawnLocation = world.getSpawnLocation(); + double x = spawnLocation.getX(); + double z = spawnLocation.getZ(); + + Location min = new Location(world, x - this.radius, world.getMinHeight(), z - this.radius); + Location max = new Location(world, x + this.radius - 1, world.getMaxHeight() - 1, z + this.radius - 1); + + return new DefaultSpawnRegion(min, max, spawnLocation); + } + + private record DefaultSpawnRegion(Location min, Location max, Location center) implements Region { + @Override + public Location getCenter() { + return this.center; + } + + @Override + public Location getMin() { + return this.min; + } + + @Override + public Location getMax() { + return this.max; + } + } + +} diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/region/WorldGuardRegionProvider.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/WorldGuardRegionProvider.java similarity index 57% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/region/WorldGuardRegionProvider.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/WorldGuardRegionProvider.java index 96ecef4b..62279cab 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/region/WorldGuardRegionProvider.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/region/WorldGuardRegionProvider.java @@ -7,14 +7,18 @@ import com.sk89q.worldguard.protection.ApplicableRegionSet; import com.sk89q.worldguard.protection.flags.Flags; import com.sk89q.worldguard.protection.flags.StateFlag; +import com.sk89q.worldguard.protection.managers.RegionManager; import com.sk89q.worldguard.protection.regions.ProtectedRegion; import com.sk89q.worldguard.protection.regions.RegionContainer; import com.sk89q.worldguard.protection.regions.RegionQuery; +import java.util.Collection; +import java.util.Collections; import java.util.Optional; import java.util.TreeSet; import org.bukkit.Location; import java.util.List; +import org.bukkit.World; public class WorldGuardRegionProvider implements RegionProvider { @@ -33,22 +37,37 @@ public Optional getRegion(Location location) { ApplicableRegionSet applicableRegions = regionQuery.getApplicableRegions(BukkitAdapter.adapt(location)); for (ProtectedRegion region : applicableRegions.getRegions()) { - if (!isCombatRegion(region)) { + if (!this.isCombatRegion(region)) { continue; } - return Optional.of(new RegionImpl(location, region)); + return Optional.of(new WorldGuardRegion(location.getWorld(), region)); } return Optional.empty(); } + @Override + public Collection getRegions(World world) { + RegionManager regionManager = this.regionContainer.get(BukkitAdapter.adapt(world)); + if (regionManager == null) { + return Collections.emptyList(); + } + + return regionManager.getRegions() + .values() + .stream() + .filter(region -> this.isCombatRegion(region)) + .map(region -> (Region) new WorldGuardRegion(world, region)) + .toList(); + } + private boolean isCombatRegion(ProtectedRegion region) { if (this.regions.contains(region.getId())) { return true; } - if (this.pluginConfig.settings.shouldPreventPvpRegions) { + if (this.pluginConfig.regions.preventPvpInRegions) { StateFlag.State flag = region.getFlag(Flags.PVP); if (flag != null) { @@ -59,16 +78,29 @@ private boolean isCombatRegion(ProtectedRegion region) { return false; } - private record RegionImpl(Location contextLocation, ProtectedRegion region) implements Region { + private record WorldGuardRegion(World context, ProtectedRegion region) implements Region { @Override public Location getCenter() { - BlockVector3 min = region.getMinimumPoint(); - BlockVector3 max = region.getMaximumPoint(); + BlockVector3 min = this.region.getMinimumPoint(); + BlockVector3 max = this.region.getMaximumPoint(); double x = (double) (min.getX() + max.getX()) / 2; double z = (double) (min.getZ() + max.getZ()) / 2; + double y = (double) (min.getY() + max.getY()) / 2; - return new Location(contextLocation.getWorld(), x, contextLocation.getY(), z); + return new Location(this.context, x, y, z); + } + + @Override + public Location getMin() { + BlockVector3 min = this.region.getMinimumPoint(); + return new Location(this.context, min.getX(), min.getY(), min.getZ()); + } + + @Override + public Location getMax() { + BlockVector3 max = this.region.getMaximumPoint(); + return new Location(this.context, max.getX(), max.getY(), max.getZ()); } } diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/updater/UpdaterNotificationController.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/updater/UpdaterNotificationController.java similarity index 97% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/updater/UpdaterNotificationController.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/updater/UpdaterNotificationController.java index d110f2ad..5300481b 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/updater/UpdaterNotificationController.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/updater/UpdaterNotificationController.java @@ -33,7 +33,7 @@ void onJoin(PlayerJoinEvent event) { Player player = event.getPlayer(); Audience audience = this.audienceProvider.player(player.getUniqueId()); - if (!player.hasPermission(RECEIVE_UPDATES_PERMISSION) || !this.pluginConfig.settings.shouldReceivePluginUpdates) { + if (!player.hasPermission(RECEIVE_UPDATES_PERMISSION) || !this.pluginConfig.settings.notifyAboutUpdates) { return; } diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/updater/UpdaterService.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/updater/UpdaterService.java similarity index 96% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/updater/UpdaterService.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/updater/UpdaterService.java index bf5d665c..e5700829 100644 --- a/eternalcombat-api/src/main/java/com/eternalcode/combat/updater/UpdaterService.java +++ b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/updater/UpdaterService.java @@ -4,8 +4,8 @@ import com.eternalcode.gitcheck.GitCheckResult; import com.eternalcode.gitcheck.git.GitRepository; import com.eternalcode.gitcheck.git.GitTag; +import dev.rollczi.litecommands.shared.Lazy; import org.bukkit.plugin.PluginDescriptionFile; -import panda.std.Lazy; import java.util.concurrent.CompletableFuture; diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/util/DurationUtil.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/DurationUtil.java similarity index 100% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/util/DurationUtil.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/DurationUtil.java diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/util/InventoryUtil.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/InventoryUtil.java similarity index 100% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/util/InventoryUtil.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/InventoryUtil.java diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/util/MathUtil.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/MathUtil.java similarity index 100% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/util/MathUtil.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/MathUtil.java diff --git a/eternalcombat-api/src/main/java/com/eternalcode/combat/util/RemoveItemResult.java b/eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/RemoveItemResult.java similarity index 100% rename from eternalcombat-api/src/main/java/com/eternalcode/combat/util/RemoveItemResult.java rename to eternalcombat-plugin/src/main/java/com/eternalcode/combat/util/RemoveItemResult.java