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 super Notification> 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