Compare commits

..
18 Commits
Author SHA1 Message Date
Norbert Dejlich 16c40319f4 [ci skip] Fix a publish workflow. 2023-10-14 21:44:55 +02:00
Michał WojtasandDMK a88c9e74fe GH-120 Add suport for event API. Add tagout - command to prevent combat log for a set duration (#120)
* Add causes for untag and tag of the player

* Add Cancellable implementation to Events.

* Extend uses of FightTagEvent in FightEffectController, add FightExtendTagEvent

* Small fixup (add this. to isCancelled variable)

* Add sketch for TagOut Commands, add listener for custom events, setup base for event api

* Code fix up. Add EventHandlers in EffectController

* Remove unnecessary file

* Add support for untagout command

* Follow DMK review suggestions

* Extract tag-out commands our of CombatCommand. Add one more user case scenario of removing tag-out

* codestyle fix

* Fix warn

* Follow DMK suggestions

* revert `assert` changes

* add space

* Update src/main/java/com/eternalcode/combat/fight/FightManager.java

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>

* Follow another set of issues from DMK

* remove unused imports

* fix compilation error

* remove formatter where not needed

* Update src/main/java/com/eternalcode/combat/fight/tagout/TagOutCommand.java

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>

* Add FightMessageController

* Small fixes

* Rename enum in CauseOfUnTag.java, fix block placement ban message

* Setup Api, setup Maven Repository - ready to publish

* Add module pointer before execute publish task.

* rename command from tag-out to tagout

* command aliases fix

* change "publish on push" to "publish on release"

* fix release types

---------

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>
2023-10-14 21:42:54 +02:00
cceeb82fef GH-116 Add Effects applied during Combat (#116)
* Add inCombatEffects based on custom Events

* Improve give back of potion effects.

* Fix merge conflicts and add okaeri bukkit serdes.

* Simplification of loops, new methods and optimization

* Change HashMap based on Player to UUID for better security

* Fix issues

* Update src/main/java/com/eternalcode/combat/config/implementation/PluginConfig.java

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/effect/EffectService.java

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/effect/EffectService.java

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/effect/EffectService.java

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/event/FightDeathEvent.java

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/event/FightTagEvent.java

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/event/FightDeathEvent.java

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/FightManager.java

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/FightManager.java

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/effect/EffectService.java

Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>

* Codestyle fixes

* Codestyle fixes

* Remove unnesesary FightDeathEvent.java, refactor class names to match project style, fixes in codestyle, move to UUID event pass down instead of bukkit player.

* Add brackets

* Follow Rollczi's review

* Follow DMK's review.

* simple rename

---------

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>
Co-authored-by: DMK <81445555+imDMK@users.noreply.github.com>
2023-10-02 11:42:04 +02:00
DMKandRollczi 92535dd7a3 GH-86 Add a bossbar implementation (#113)
* Initial commit.

* Fix checkstyle.

* Follow rollczi review suggestions.

* Encapsulate FightBossBarService and FightBossBarRegistry.

* Added hiding boss bar after untagging player by command.

* Added the ability to customize the progress bar on the boss bar.

---------

Co-authored-by: Rollczi <ndejlich5@gmail.com>
2023-09-28 00:28:31 +02:00
DMK 3390534d98 GH-94 Fix item drop 2023-09-16 13:37:09 +02:00
DMK d45ce7406f GH-114 Fix eternal combat status message 2023-09-16 13:35:29 +02:00
Martin SulikowskiandDominik 5861f70010 GH-109 Migrate configuration to use the okaeri configuration (#111)
* Initial commit.

* Follow rollczi suggestions.

* Fixed a bug with file configuration creation.

* Jar file size reduced.

* Added relocating javassist and org.apache.commons.

---------

Co-authored-by: Dominik <dominiks8318@gmail.com>
2023-09-15 18:28:07 +02:00
Martin Sulikowski 7940a2663b Release v1.1.1 2023-08-25 00:20:21 +02:00
Martin Sulikowski 9e9e2956b9 GH-105 Add option to release attacker when victim dies (Fix after mistkate) (#105)
* Add option to release attacker when victim dies (Fix after mistkate)

* Follow @failutee suggestion
2023-08-25 00:07:16 +02:00
Michał Wojtas 7bfb4c22a4 GH-100 Add blacklist option to damage causes (#100)
* Add blacklist option to damage causes

* Small fix

* Resolve description to better match features

* Create WhitelistBlacklistMode enum, make consistent Whitelist and Blacklist uses throughout plugin.
2023-08-22 22:53:34 +02:00
failuteeandJakubk15 1d7a652f23 GH-93 Add PlayersHealthDropModifier (#97)
* Move to feature to separate class

* Register FightEscapeController

* PlayersHealthDropModifier impl & Some changes

* Add FightDeathCasue

* Fix

* Update src/main/java/com/eternalcode/combat/fight/FightTag.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/FightTag.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/FightTag.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/FightDeathCasue.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/FightTag.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/FightTag.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/controller/FightDeathCasueController.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/controller/FightDeathCasueController.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/drop/Drop.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/controller/FightDeathCasueController.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/drop/Drop.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/controller/FightDeathCasueController.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Update src/main/java/com/eternalcode/combat/fight/controller/FightDeathCasueController.java

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

* Resolve errors

---------

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>
2023-08-22 15:31:21 +02:00
renovate[bot] d9dfa5396a GH-102 Update gradle/gradle-build-action digest to e3426b1 (#102)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-22 13:47:45 +02:00
renovate[bot] 630b8bc23f GH-90 Update all dependencies (#90)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2023-08-19 18:16:53 +02:00
failutee 302fc3ed64 GH-92 Release attacker when victim dies. (#96)
* Init

* Add untag message

* Untag player in PlayerDeathEvent instead of EntityDamageByEntityEvent

* Add setting to config

* Follow Rollczi's suggestions

* Remove unused imports
2023-08-19 18:16:00 +02:00
DMKandNorbert Dejlich fb51b062f6 GH-98 Pearl throw control (#99)
* Initial commit.

* Follow rollczi suggestions.

* Change entityTypesToLog to disabledProjectileEntities.

Co-authored-by: Norbert Dejlich <49173834+Rollczi@users.noreply.github.com>

* Change entityTypesToLog to disabledProjectileEntities.

Co-authored-by: Norbert Dejlich <49173834+Rollczi@users.noreply.github.com>

* Follow rollczi suggestions.

---------

Co-authored-by: Norbert Dejlich <49173834+Rollczi@users.noreply.github.com>
2023-08-19 18:10:26 +02:00
NotAlexNoyleandJakubk15 bfd7557e6d GH-91 Add build instructions
* Add build instructions

* Update README.md

Follow suggestion

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>

---------

Co-authored-by: Jakubk15 <77227023+Jakubk15@users.noreply.github.com>
2023-07-23 14:14:55 +02:00
failutee ba2e55fa84 GH-89 Add DropModifier implementation. (#89)
* Init

* Fix compile error

* Follow suggestions

* Follow suggestions
2023-07-10 18:32:58 +02:00
Martin Sulikowski d1659ac5dd Release v1.1.0 2023-07-01 19:55:30 +02:00
91 changed files with 2641 additions and 777 deletions

No files matched your search

+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
- name: Make gradlew executable
run: chmod +x gradlew
- name: Build with Gradle
uses: gradle/gradle-build-action@bde650d6f1426615a60165575bc9cdad3e54d975
uses: gradle/gradle-build-action@e3426b1f20c7262e0fed7b855aae1325633af3b4
with:
arguments: shadowJar
- name: Upload a Build Artifact
+28
View File
@@ -0,0 +1,28 @@
name: Publish EternalCombat API
on:
release:
branches: [ master ]
types: [ published ]
jobs:
publish:
runs-on: ubuntu-latest
environment: deployment
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: 17
cache: 'gradle'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Publish with Gradle
run: ./gradlew clean build eternalcombat-api:publish
env:
ETERNAL_CODE_MAVEN_USERNAME: ${{ secrets.ETERNAL_CODE_MAVEN_USERNAME }}
ETERNAL_CODE_MAVEN_PASSWORD: ${{ secrets.ETERNAL_CODE_MAVEN_PASSWORD }}
+6 -1
View File
@@ -15,11 +15,16 @@
### Information
EternalCombat has been tested on Minecraft versions 1.17.1 to 1.19.3, but it should work on most other versions. If you
EternalCombat has been tested on Minecraft versions 1.17.1 to 1.20.1, but it should work on most other versions. If you
encounter any compatibility issues, please report them in the
[Issues tab](https://github.com/EternalCodeTeam/EternalCombat/issues). The plugin requires Java 17 or later, so please
make sure it is installed on your server.
## Building
Build EternalCombat using:
`./gradlew shadowJar`
### Permissions for EternalCombat
- `eternalcombat.status` - Allows to check if the player is in combat `/combatlog status <player>`
+109
View File
@@ -0,0 +1,109 @@
plugins {
`java-library`
checkstyle
id("maven-publish")
}
group = "com.eternalcode"
version = "1.1.1"
checkstyle {
toolVersion = "10.12.2"
configFile = file("${rootDir}/config/checkstyle/checkstyle.xml")
maxErrors = 0
maxWarnings = 0
}
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/") }
}
dependencies {
// Spigot api
compileOnlyApi("org.spigotmc:spigot-api:1.19.3-R0.1-SNAPSHOT")
// kyori
api("net.kyori:adventure-platform-bukkit:4.3.0")
api("net.kyori:adventure-text-minimessage:4.14.0")
// litecommands
api("dev.rollczi.litecommands:bukkit-adventure:2.8.9")
// Okaeri configs
api("eu.okaeri:okaeri-configs-yaml-bukkit:5.0.0-beta.5")
api("eu.okaeri:okaeri-configs-serdes-commons:5.0.0-beta.5")
api("eu.okaeri:okaeri-configs-serdes-bukkit:5.0.0-beta.5")
// Panda utilities
api("org.panda-lang:panda-utilities:0.5.2-alpha")
// GitCheck
api("com.eternalcode:gitcheck:1.0.0")
// commons
api("commons-io:commons-io:2.13.0")
// bstats
api("org.bstats:bstats-bukkit:3.0.2")
// worldguard
compileOnly("com.sk89q.worldguard:worldguard-bukkit:7.0.9")
// tests
testImplementation("org.spigotmc:spigot-api:1.19.3-R0.1-SNAPSHOT")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.0")
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
tasks.getByName<Test>("test") {
useJUnitPlatform()
}
// Publish
java {
withSourcesJar()
withJavadocJar()
}
publishing {
publications {
create<MavenPublication>("maven") {
artifactId = "eternalcombat-api"
from(project.components["java"])
}
}
}
publishing {
repositories {
mavenLocal()
maven {
name = "eternalcodeReleases"
url = uri("https://repo.eternalcode.pl/releases")
credentials {
username = System.getenv("ETERNAL_CODE_MAVEN_USERNAME")
password = System.getenv("ETERNAL_CODE_MAVEN_PASSWORD")
}
}
}
}
@@ -1,8 +1,12 @@
package com.eternalcode.combat;
import com.eternalcode.combat.config.ConfigManager;
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;
@@ -20,101 +24,114 @@ import java.util.UUID;
public class CombatCommand {
private final FightManager fightManager;
private final ConfigManager configManager;
private final ConfigService configService;
private final NotificationAnnouncer announcer;
private final PluginConfig config;
public CombatCommand(FightManager fightManager, ConfigManager configManager, NotificationAnnouncer announcer, PluginConfig config) {
public CombatCommand(FightManager fightManager, ConfigService configService, NotificationAnnouncer announcer, PluginConfig config) {
this.fightManager = fightManager;
this.configManager = configManager;
this.configService = configService;
this.announcer = announcer;
this.config = config;
}
@Execute(route = "status", required = 1)
@Permission("eternalcombat.status")
void status(CommandSender player, @Arg Player target) {
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(player, this.fightManager.isInCombat(targetUniqueId)
this.announcer.sendMessage(sender, this.fightManager.isInCombat(targetUniqueId)
? formatter.format(messages.admin.playerInCombat)
: formatter.format(messages.admin.adminPlayerNotInCombat));
: formatter.format(messages.admin.playerNotInCombat));
}
@Execute(route = "tag", required = 1)
@Permission("eternalcombat.tag")
void tag(Player player, @Arg Player target) {
UUID targetUniqueId = target.getUniqueId();
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());
this.fightManager.tag(targetUniqueId, time);
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(player, format);
this.announcer.sendMessage(sender, format);
}
@Execute(route = "tag", required = 2)
@Permission("eternalcombat.tag")
public void tagMultiple(Player player, @Arg Player firstTarget, @Arg Player secondTarget) {
void tagMultiple(CommandSender sender, @Arg Player firstTarget, @Arg Player secondTarget) {
Duration combatTime = this.config.settings.combatDuration;
PluginConfig.Messages messages = this.config.messages;
UUID firstTargetUniqueId = firstTarget.getUniqueId();
UUID secondTargetUniqueId = secondTarget.getUniqueId();
UUID playerUniqueId = player.getUniqueId();
if (playerUniqueId.equals(firstTargetUniqueId) || playerUniqueId.equals(secondTargetUniqueId)) {
this.announcer.sendMessage(player, messages.admin.adminCannotTagSelf);
if (sender.equals(firstTarget) || sender.equals(secondTarget)) {
this.announcer.sendMessage(sender, messages.admin.adminCannotTagSelf);
return;
}
this.fightManager.tag(firstTargetUniqueId, combatTime);
this.fightManager.tag(secondTargetUniqueId, combatTime);
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);
this.announcer.sendMessage(player, format);
this.announcer.sendMessage(firstTarget, messages.playerTagged);
this.announcer.sendMessage(secondTarget, messages.playerTagged);
}
if (firstTagEvent.isCancelled()) {
this.announcer.sendMessage(sender, firstTagEvent.getCancelMessage());
}
@Execute(route = "untag", required = 1)
@Permission("eternalcombat.untag")
void untag(Player player, @Arg Player target) {
UUID targetUniqueId = target.getUniqueId();
if (secondTagEvent.isCancelled()) {
this.announcer.sendMessage(sender, firstTagEvent.getCancelMessage());
}
if (!this.fightManager.isInCombat(targetUniqueId)) {
this.announcer.sendMessage(player, this.config.messages.admin.adminPlayerNotInCombat);
if (firstTagEvent.isCancelled() && secondTagEvent.isCancelled()) {
return;
}
this.announcer.sendMessage(target, this.config.messages.playerUntagged);
this.fightManager.untag(targetUniqueId);
Formatter formatter = new Formatter()
.register("{PLAYER}", target.getName());
String format = formatter.format(this.config.messages.admin.adminUntagPlayer);
this.announcer.sendMessage(player, format);
this.announcer.sendMessage(sender, format);
}
@Async
@Execute(route = "reload")
@Permission("eternalcombat.reload")
void execute(CommandSender player) {
this.configManager.reload();
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);
}
}
@@ -0,0 +1,30 @@
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.FightManager;
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.region.RegionProvider;
public interface EternalCombatApi {
FightManager getFightManager();
RegionProvider getRegionProvider();
FightPearlManager getFightPearlManager();
FightTagOutService getFightTagOutService();
FightEffectService getFightEffectService();
DropManager getDropManager();
DropKeepInventoryManager getDropKeepInventoryManager();
PluginConfig getPluginConfig();
}
@@ -0,0 +1,31 @@
package com.eternalcode.combat;
public final class EternalCombatProvider {
private static EternalCombatApi ETERNAL_COMBAT_API;
public static EternalCombatApi provide() {
if (ETERNAL_COMBAT_API == null) {
throw new IllegalStateException("EternalCombatApi has not been initialized yet!");
}
return ETERNAL_COMBAT_API;
}
static void initialize(EternalCombatApi eternalCombatApi) {
if (ETERNAL_COMBAT_API != null) {
throw new IllegalStateException("EternalCombatApi has already been initialized!");
}
ETERNAL_COMBAT_API = eternalCombatApi;
}
static void deinitialize() {
if (ETERNAL_COMBAT_API == null) {
throw new IllegalStateException("EternalCombatApi has not been initialized yet!");
}
ETERNAL_COMBAT_API = null;
}
}
@@ -1,6 +1,6 @@
package com.eternalcode.combat.fight;
package com.eternalcode.combat;
public enum FightCommandMode {
public enum WhitelistBlacklistMode {
WHITELIST,
BLACKLIST;
@@ -0,0 +1,49 @@
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 ConfigBackupService backupService;
private final Set<OkaeriConfig> configs = new HashSet<>();
public ConfigService(ConfigBackupService backupService) {
this.backupService = backupService;
}
public <T extends OkaeriConfig> T create(Class<T> config, File file) {
this.backupService.createBackup();
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();
}
}
}
@@ -0,0 +1,224 @@
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<String> worldsToIgnore = List.of(
"your_world"
);
@Comment("# List of regions to block")
public List<String> blockedRegions = Collections.singletonList("your_region");
@Comment("# Set the knock multiplier for the blocked region")
public double blockedRegionKnockMultiplier = 1.2;
@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("# 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<String> blockedCommands = List.of(
"gamemode",
"spawn",
"tp"
);
@Comment("# Block the use of elytra?")
public boolean shouldPreventElytraUsage = 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("# The minimum Y level at which block placing is blocked")
public int minBlockPlacingLevel = 40;
@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<Material> 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<EntityDamageEvent.DamageCause> 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<EntityType> 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")
public String blockPlacingBlockedDuringCombat = "&cYou cannot place above 40Y coordinate during combat!";
@Comment("# Message sent when player tries to enter a region")
public String cantEnterOnRegion = "&cYou can't enter on 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!";
}
}
}
@@ -0,0 +1,116 @@
package com.eternalcode.combat.drop;
import com.eternalcode.combat.fight.FightDeathCause;
import com.eternalcode.combat.fight.FightTag;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Drop {
private final Player player;
private final Player killer;
private final FightTag fightTag;
private final FightDeathCause deathCause;
private final List<ItemStack> droppedItems;
private final int droppedExp;
private Drop(Player player, Player killer, FightTag fightTag, FightDeathCause deathCause, List<ItemStack> droppedItems, int droppedExp) {
this.player = player;
this.killer = killer;
this.fightTag = fightTag;
this.deathCause = deathCause;
this.droppedItems = droppedItems;
this.droppedExp = droppedExp;
}
public List<ItemStack> getDroppedItems() {
return Collections.unmodifiableList(this.droppedItems);
}
public Player getPlayer() {
return this.player;
}
public @Nullable Player getKiller() {
return this.killer;
}
public FightTag getFightTag() {
return this.fightTag;
}
public FightDeathCause getDeathCause() {
return this.deathCause;
}
public boolean hasKiller() {
return this.killer != null;
}
public int getDroppedExp() {
return this.droppedExp;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private Player player;
private Player killer;
private FightTag fightTag;
private FightDeathCause deathCause;
private List<ItemStack> droppedItems;
private int droppedExp;
public Builder player(@NotNull Player player) {
this.player = player;
return this;
}
public Builder killer(@Nullable Player killer) {
this.killer = killer;
return this;
}
public Builder fightTag(@NotNull FightTag fightTag) {
this.fightTag = fightTag;
return this;
}
public Builder deathCause(@NotNull FightDeathCause deathCause) {
this.deathCause = deathCause;
return this;
}
public Builder droppedItems(@NotNull List<ItemStack> droppedItems) {
this.droppedItems = new ArrayList<>(droppedItems);
return this;
}
public Builder droppedExp(int droppedExp) {
this.droppedExp = droppedExp;
return this;
}
public Drop build() {
return new Drop(
this.player,
this.killer,
this.fightTag,
this.deathCause,
this.droppedItems,
this.droppedExp
);
}
}
}
@@ -0,0 +1,80 @@
package com.eternalcode.combat.drop;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.FightTag;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import java.util.List;
import java.util.UUID;
public class DropController implements Listener {
private final DropManager dropManager;
private final DropKeepInventoryManager keepInventoryManager;
private final DropSettings dropSettings;
private final FightManager fightManager;
public DropController(DropManager dropManager, DropKeepInventoryManager keepInventoryManager, DropSettings dropSettings, FightManager fightManager) {
this.dropManager = dropManager;
this.keepInventoryManager = keepInventoryManager;
this.dropSettings = dropSettings;
this.fightManager = fightManager;
}
@EventHandler
void onPlayerDeath(PlayerDeathEvent event) {
Player player = event.getEntity();
DropType dropType = this.dropSettings.dropType;
if (dropType == DropType.UNCHANGED || !this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
FightTag fightTag = this.fightManager.getTag(player.getUniqueId());
List<ItemStack> drops = event.getDrops();
Drop drop = Drop.builder()
.player(player)
.killer(player.getKiller())
.fightTag(fightTag)
.deathCause(fightTag.getDeathCause())
.droppedItems(drops)
.droppedExp(player.getTotalExperience())
.build();
DropResult result = this.dropManager.modify(dropType, drop);
drops.clear();
drops.addAll(result.droppedItems());
this.keepInventoryManager.addItems(player.getUniqueId(), result.removedItems());
if (this.dropSettings.affectExperience) {
event.setDroppedExp(drop.getDroppedExp());
}
}
@EventHandler
void onPlayerRespawn(PlayerRespawnEvent event) {
Player player = event.getPlayer();
UUID playerUniqueId = player.getUniqueId();
if (this.keepInventoryManager.hasItems(playerUniqueId)) {
PlayerInventory playerInventory = player.getInventory();
ItemStack[] itemsToGive = this.keepInventoryManager.nextItems(playerUniqueId)
.toArray(new ItemStack[0]);
playerInventory.addItem(itemsToGive);
}
}
}
@@ -0,0 +1,38 @@
package com.eternalcode.combat.drop;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class DropKeepInventoryManager {
private final Map<UUID, List<ItemStack>> itemsToGiveAfterRespawn = new HashMap<>();
public void addItem(UUID uuid, ItemStack item) {
this.itemsToGiveAfterRespawn.computeIfAbsent(uuid, k -> new ArrayList<>()).add(item);
}
public void addItems(UUID uuid, List<ItemStack> item) {
item.forEach(i -> this.addItem(uuid, i));
}
public boolean hasItems(UUID uuid) {
return this.itemsToGiveAfterRespawn.containsKey(uuid);
}
public List<ItemStack> nextItems(UUID uuid) {
List<ItemStack> itemStacks = this.itemsToGiveAfterRespawn.remove(uuid);
if (itemStacks == null) {
return Collections.emptyList();
}
return Collections.unmodifiableList(itemStacks);
}
}
@@ -0,0 +1,36 @@
package com.eternalcode.combat.drop;
import java.util.HashMap;
import java.util.Map;
public class DropManager {
private final Map<DropType, DropModifier> modifiers;
public DropManager() {
this.modifiers = new HashMap<>();
}
public void registerModifier(DropModifier dropModifier) {
DropType dropType = dropModifier.getDropType();
if (dropType == null) {
throw new RuntimeException("Drop type cannot be null! '%s'".formatted(dropModifier.getClass().getSimpleName()));
}
if (dropType == DropType.UNCHANGED) {
throw new RuntimeException("You cannot register DropModifier for this type '%s'".formatted(dropType.name()));
}
this.modifiers.put(dropType, dropModifier);
}
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()));
}
return this.modifiers.get(dropType).modifyDrop(drop);
}
}
@@ -0,0 +1,9 @@
package com.eternalcode.combat.drop;
public interface DropModifier {
DropType getDropType();
DropResult modifyDrop(Drop drop);
}
@@ -0,0 +1,24 @@
package com.eternalcode.combat.drop;
import org.bukkit.inventory.ItemStack;
import java.util.Collections;
import java.util.List;
public record DropResult(
List<ItemStack> droppedItems,
List<ItemStack> removedItems,
int droppedExp
) {
@Override
public List<ItemStack> droppedItems() {
return Collections.unmodifiableList(this.droppedItems);
}
@Override
public List<ItemStack> removedItems() {
return Collections.unmodifiableList(this.removedItems);
}
}
@@ -0,0 +1,23 @@
package com.eternalcode.combat.drop;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
public class DropSettings extends OkaeriConfig {
@Comment({
"# UNCHANGED - The default way of item drop defined by the engine",
"# PERCENT - Drops a fixed percentage of items",
"# PLAYERS_HEALTH - Drops inverted percentage of the player's health (i.e. if the player has, for example, 80% HP, he will drop 20% of items. Only works when the player escapes from combat by quiting game)"
})
public DropType dropType = DropType.UNCHANGED;
@Comment("# What percentage of items should drop from the player? (Only if Drop Type is set to PERCENT)")
public int dropItemPercent = 100;
@Comment("# This option is responsible for the lowest percentage of the player that can drop (i.e. if the player leaves the game while he has 100% of his HP, the percentage of items that is set in this option will drop, if you set this option to 0, then nothing will drop from such a player)")
public int playersHealthPercentClamp = 20;
@Comment("# Does the drop modification affect the experience drop?")
public boolean affectExperience = false;
}
@@ -0,0 +1,9 @@
package com.eternalcode.combat.drop;
public enum DropType {
UNCHANGED,
PERCENT,
PLAYERS_HEALTH
}
@@ -0,0 +1,40 @@
package com.eternalcode.combat.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.util.InventoryUtil;
import com.eternalcode.combat.util.MathUtil;
import com.eternalcode.combat.util.RemoveItemResult;
import org.bukkit.inventory.ItemStack;
import java.util.List;
public class PercentDropModifier implements DropModifier {
private final DropSettings settings;
public PercentDropModifier(DropSettings settings) {
this.settings = settings;
}
@Override
public DropType getDropType() {
return DropType.PERCENT;
}
@Override
public DropResult modifyDrop(Drop drop) {
int dropItemPercent = 100 - MathUtil.clamp(this.settings.dropItemPercent, 0, 100);
List<ItemStack> droppedItems = drop.getDroppedItems();
int itemsToDelete = InventoryUtil.calculateItemsToDelete(dropItemPercent, droppedItems, ItemStack::getAmount);
int droppedExp = MathUtil.getRoundedCountFromPercentage(dropItemPercent, drop.getDroppedExp());
RemoveItemResult result = InventoryUtil.removeRandomItems(droppedItems, itemsToDelete);
return new DropResult(result.restItems(), result.removedItems(), droppedExp);
}
}
@@ -0,0 +1,56 @@
package com.eternalcode.combat.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.FightDeathCause;
import com.eternalcode.combat.fight.FightTag;
import com.eternalcode.combat.util.InventoryUtil;
import com.eternalcode.combat.util.MathUtil;
import com.eternalcode.combat.util.RemoveItemResult;
import org.bukkit.attribute.Attribute;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import java.util.List;
public class PlayersHealthDropModifier implements DropModifier {
private final DropSettings settings;
public PlayersHealthDropModifier(DropSettings settings) {
this.settings = settings;
}
@Override
public DropType getDropType() {
return DropType.PLAYERS_HEALTH;
}
@Override
public DropResult modifyDrop(Drop drop) {
if (drop.getDeathCause() != FightDeathCause.ESCAPE) {
return null;
}
Player player = drop.getPlayer();
FightTag fightTag = drop.getFightTag();
List<ItemStack> droppedItems = drop.getDroppedItems();
double maxHealth = player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getBaseValue();
double health = fightTag.getHealthBeforeDeath();
int percentHealth = MathUtil.getRoundedCountPercentage(health, maxHealth);
int reversedPercent = MathUtil.clamp(100 - percentHealth, this.settings.playersHealthPercentClamp, 100);
int itemsToDelete = InventoryUtil.calculateItemsToDelete(reversedPercent, droppedItems, ItemStack::getAmount);
int droppedExp = MathUtil.getRoundedCountFromPercentage(reversedPercent, drop.getDroppedExp());
RemoveItemResult result = InventoryUtil.removeRandomItems(droppedItems, itemsToDelete);
return new DropResult(result.restItems(), result.removedItems(), droppedExp);
}
}
@@ -0,0 +1,20 @@
package com.eternalcode.combat.event;
import org.bukkit.Server;
import org.bukkit.event.Event;
public class EventCaller {
private final Server server;
public EventCaller(Server server) {
this.server = server;
}
public <T extends Event> T publishEvent(T event) {
this.server.getPluginManager().callEvent(event);
return event;
}
}
@@ -0,0 +1,10 @@
package com.eternalcode.combat.fight;
public enum FightDeathCause {
UNKNOWN,
ESCAPE,
KILLED_BY_PLAYER,
KILLED_BY_ENTITY
}
@@ -0,0 +1,72 @@
package com.eternalcode.combat.fight;
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.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;
public class FightManager {
private final Map<UUID, FightTag> 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);
return !fightTag.isExpired();
}
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;
}
public FightTagEvent tag(UUID target, Duration delay, CauseOfTag causeOfTag) {
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);
this.fights.put(target, fightTag);
return event;
}
public Collection<FightTag> getFights() {
return Collections.unmodifiableCollection(this.fights.values());
}
public FightTag getTag(UUID target) {
return this.fights.get(target);
}
public void untagAll() {
this.fights.clear();
}
}
@@ -9,15 +9,25 @@ public class FightTag {
private final UUID taggedPlayer;
private final Instant endOfCombatLog;
private double healthBeforeDeath;
private FightDeathCause deathCause;
FightTag(UUID personToAddCombat, Instant endOfCombatLog) {
this.taggedPlayer = personToAddCombat;
this.endOfCombatLog = endOfCombatLog;
this.healthBeforeDeath = 0;
this.deathCause = FightDeathCause.UNKNOWN;
}
public UUID getTaggedPlayer() {
return this.taggedPlayer;
}
public Instant getEndOfCombatLog() {
return this.endOfCombatLog;
}
public boolean isExpired() {
return Instant.now().isAfter(this.endOfCombatLog);
}
@@ -31,4 +41,20 @@ public class FightTag {
return between;
}
public double getHealthBeforeDeath() {
return this.healthBeforeDeath;
}
public void setHealthBeforeDeath(double health) {
this.healthBeforeDeath = health;
}
public FightDeathCause getDeathCause() {
return this.deathCause;
}
public void setDeathCause(FightDeathCause deathCause) {
this.deathCause = deathCause;
}
}
@@ -0,0 +1,67 @@
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.util.DurationUtil;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import panda.utilities.text.Formatter;
import java.time.Duration;
import java.util.UUID;
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;
public FightTask(Server server, PluginConfig config, FightManager fightManager, FightBossBarService bossBarService, NotificationAnnouncer announcer) {
this.server = server;
this.config = config;
this.fightManager = fightManager;
this.bossBarService = bossBarService;
this.announcer = announcer;
}
@Override
public void run() {
for (FightTag fightTag : this.fightManager.getFights()) {
Player player = this.server.getPlayer(fightTag.getTaggedPlayer());
if (player == null) {
continue;
}
UUID playerUniqueId = player.getUniqueId();
if (fightTag.isExpired()) {
this.fightManager.untag(playerUniqueId, CauseOfUnTag.TIME_EXPIRED);
return;
}
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);
}
}
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);
}
}
@@ -0,0 +1,9 @@
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) {
}
@@ -0,0 +1,27 @@
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<UUID, FightBossBar> 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<FightBossBar> getFightBossBar(UUID uuid) {
return Optional.ofNullable(this.fightBossBars.get(uuid));
}
}
@@ -0,0 +1,119 @@
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.time.Instant;
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<FightBossBar> 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();
float fightBossBarProgress = fightBossBar.progress();
if (fightBossBarProgress < 0.0F) {
Instant now = Instant.now();
Instant endOfCombatLog = fightTag.getEndOfCombatLog();
Duration between = Duration.between(now, endOfCombatLog);
Duration combatDuration = fightBossBar.combatDuration();
float difference = (float) between.toMillis() / combatDuration.toMillis();
float progress = Math.max(0.0F, Math.min(1.0F, difference));
bossBar.progress(progress);
}
Component name = this.miniMessage.deserialize(message);
bossBar.name(name);
}
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);
}
}
@@ -1,7 +1,7 @@
package com.eternalcode.combat.fight.controller;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightCommandMode;
import com.eternalcode.combat.WhitelistBlacklistMode;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import org.bukkit.Material;
@@ -130,7 +130,7 @@ public class FightActionBlockerController implements Listener {
boolean isMatchCommand = this.config.settings.blockedCommands.stream()
.anyMatch(command::startsWith);
FightCommandMode mode = this.config.settings.commandBlockingMode;
WhitelistBlacklistMode mode = this.config.settings.commandBlockingMode;
boolean shouldCancel = mode.shouldBlock(isMatchCommand);
@@ -0,0 +1,59 @@
package com.eternalcode.combat.fight.controller;
import com.eternalcode.combat.fight.FightDeathCause;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.FightTag;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.player.PlayerQuitEvent;
public class FightDeathCauseController implements Listener {
private final FightManager fightManager;
public FightDeathCauseController(FightManager fightManager) {
this.fightManager = fightManager;
}
@EventHandler
public void onEntityDamage(EntityDamageByEntityEvent event) {
Entity victimEntity = event.getEntity();
Entity damagerEntity = event.getDamager();
if (!(victimEntity instanceof Player victim)) {
return;
}
if (!this.fightManager.isInCombat(victim.getUniqueId())) {
return;
}
if (victim.getHealth() > event.getFinalDamage()) {
return;
}
FightTag fightTag = this.fightManager.getTag(damagerEntity.getUniqueId());
if (damagerEntity instanceof Player) {
fightTag.setDeathCause(FightDeathCause.KILLED_BY_PLAYER);
return;
}
fightTag.setDeathCause(FightDeathCause.KILLED_BY_ENTITY);
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
FightTag fightTag = this.fightManager.getTag(player.getUniqueId());
fightTag.setDeathCause(FightDeathCause.ESCAPE);
}
}
@@ -2,38 +2,26 @@ package com.eternalcode.combat.fight.controller;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.FightTag;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import panda.utilities.text.Formatter;
public class FightUnTagController implements Listener {
public class FightEscapeController implements Listener {
private final FightManager fightManager;
private final PluginConfig config;
private final NotificationAnnouncer announcer;
public FightUnTagController(FightManager fightManager, PluginConfig config, NotificationAnnouncer announcer) {
public FightEscapeController(FightManager fightManager, PluginConfig config, NotificationAnnouncer announcer) {
this.fightManager = fightManager;
this.config = config;
this.announcer = announcer;
}
@EventHandler
void onPlayerDeath(PlayerDeathEvent event) {
Player player = event.getEntity();
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
this.announcer.sendMessage(player, this.config.messages.playerUntagged);
this.fightManager.untag(player.getUniqueId());
}
@EventHandler
void onPlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
@@ -47,9 +35,11 @@ public class FightUnTagController implements Listener {
String format = formatter.format(this.config.messages.playerLoggedOutDuringCombat);
this.fightManager.untag(player.getUniqueId());
this.announcer.broadcast(player, format);
player.setHealth(0.0);
}
this.announcer.broadcast(format);
FightTag fightTag = this.fightManager.getTag(player.getUniqueId());
fightTag.setHealthBeforeDeath(player.getHealth());
player.setHealth(0.0); // Untagged in PlayerDeathEvent
}
}
@@ -0,0 +1,61 @@
package com.eternalcode.combat.fight.controller;
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.notification.NotificationAnnouncer;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
public class FightMessageController implements Listener {
private final FightManager fightManager;
private final NotificationAnnouncer announcer;
private final FightBossBarService bossBarService;
private final PluginConfig config;
private final Server server;
public FightMessageController(FightManager fightManager, NotificationAnnouncer announcer, FightBossBarService bossBarService, PluginConfig config, Server server) {
this.fightManager = fightManager;
this.announcer = announcer;
this.bossBarService = bossBarService;
this.config = config;
this.server = server;
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
void onTag(FightTagEvent event) {
Player player = this.server.getPlayer(event.getPlayer());
if (player == null) {
throw new IllegalStateException("Player cannot be null!");
}
if (this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
this.announcer.sendMessage(player, this.config.messages.playerTagged);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
void onUnTag(FightTagEvent event) {
Player player = this.server.getPlayer(event.getPlayer());
if (player == null) {
throw new IllegalStateException("Player cannot be null!");
}
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
this.announcer.sendMessage(player, this.config.messages.playerUntagged);
this.bossBarService.hide(event.getPlayer());
}
}
@@ -1,8 +1,11 @@
package com.eternalcode.combat.fight.controller;
import com.eternalcode.combat.WhitelistBlacklistMode;
import com.eternalcode.combat.fight.event.CauseOfTag;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.event.EventHandler;
@@ -34,6 +37,12 @@ public class FightTagController implements Listener {
return;
}
List<EntityType> disabledProjectileEntities = this.config.settings.disabledProjectileEntities;
if (event.getDamager() instanceof Projectile projectile && disabledProjectileEntities.contains(projectile.getType())) {
return;
}
if (this.isPlayerInDisabledWorld(attackedPlayerByPerson)) {
return;
}
@@ -49,16 +58,8 @@ public class FightTagController implements Listener {
UUID attackedUniqueId = attackedPlayerByPerson.getUniqueId();
UUID personToAddCombatTimeUniqueId = personToAddCombatTime.getUniqueId();
if (!this.fightManager.isInCombat(attackedUniqueId)) {
this.announcer.sendMessage(personToAddCombatTime, this.config.messages.playerTagged);
}
if (!this.fightManager.isInCombat(personToAddCombatTimeUniqueId)) {
this.announcer.sendMessage(attackedPlayerByPerson, this.config.messages.playerTagged);
}
this.fightManager.tag(attackedUniqueId, combatTime);
this.fightManager.tag(personToAddCombatTimeUniqueId, combatTime);
this.fightManager.tag(attackedUniqueId, combatTime, CauseOfTag.PLAYER);
this.fightManager.tag(personToAddCombatTimeUniqueId, combatTime, CauseOfTag.PLAYER);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@@ -80,17 +81,17 @@ public class FightTagController implements Listener {
UUID uuid = player.getUniqueId();
List<EntityDamageEvent.DamageCause> damageCauses = this.config.settings.damageCausesToLog;
WhitelistBlacklistMode mode = this.config.settings.damageCausesMode;
EntityDamageEvent.DamageCause cause = event.getCause();
if (!damageCauses.contains(cause)) {
boolean shouldLog = mode.shouldBlock(damageCauses.contains(cause));
if (shouldLog) {
return;
}
if (!this.fightManager.isInCombat(uuid)) {
this.announcer.sendMessage(player, this.config.messages.playerTagged);
}
this.fightManager.tag(uuid, combatTime);
this.fightManager.tag(uuid, combatTime, CauseOfTag.NON_PLAYER);
}
@Nullable
@@ -0,0 +1,38 @@
package com.eternalcode.combat.fight.controller;
import com.eternalcode.combat.fight.event.CauseOfUnTag;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
public class FightUnTagController implements Listener {
private final FightManager fightManager;
private final PluginConfig config;
public FightUnTagController(FightManager fightManager, PluginConfig config) {
this.fightManager = fightManager;
this.config = config;
}
@EventHandler
void onPlayerDeath(PlayerDeathEvent event) {
Player player = event.getEntity();
Player killer = player.getKiller();
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
CauseOfUnTag causeOfUnTag = (killer == null ? CauseOfUnTag.DEATH_BY_PLAYER : CauseOfUnTag.DEATH);
this.fightManager.untag(player.getUniqueId(), causeOfUnTag);
if (killer != null && this.config.settings.shouldReleaseAttacker) {
this.fightManager.untag(killer.getUniqueId(), CauseOfUnTag.ATTACKER_RELEASE);
}
}
}
@@ -0,0 +1,124 @@
package com.eternalcode.combat.fight.effect;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.event.CauseOfUnTag;
import com.eternalcode.combat.fight.event.FightTagEvent;
import com.eternalcode.combat.fight.event.FightUntagEvent;
import org.bukkit.Bukkit;
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.entity.EntityPotionEffectEvent;
import org.bukkit.potion.PotionEffect;
public class FightEffectController implements Listener {
private final FightEffectService effectService;
private final FightEffectSettings effectSettings;
private final FightManager fightManager;
private final Server server;
public FightEffectController(FightEffectSettings settings, FightEffectService effectService, FightManager fightManager, Server server) {
this.effectSettings = settings;
this.effectService = effectService;
this.fightManager = fightManager;
this.server = server;
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onTag(FightTagEvent event) {
if (!this.effectSettings.customEffectsEnabled) {
return;
}
Player player = this.server.getPlayer(event.getPlayer());
if (player == null) {
return;
}
this.effectSettings.customEffects.forEach((key, value) ->
this.effectService.applyCustomEffect(player, key, value));
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onQuit(FightUntagEvent event) {
if (!this.effectSettings.customEffectsEnabled) {
return;
}
if (event.getCause() == CauseOfUnTag.LOGOUT) {
Player player = Bukkit.getPlayer(event.getPlayer());
assert player != null;
this.effectService.clearStoredEffects(player);
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onUntag(FightUntagEvent event) {
if (!this.effectSettings.customEffectsEnabled) {
return;
}
Player player = this.server.getPlayer(event.getPlayer());
if (player == null) {
return;
}
this.effectSettings.customEffects.forEach((key, value) -> this.effectService.removeCustomEffect(player, key, value));
this.effectService.restoreActiveEffects(player);
}
@EventHandler
public void onDeath(FightUntagEvent event) {
if (!this.effectSettings.customEffectsEnabled) {
return;
}
if (event.getCause() == CauseOfUnTag.DEATH_BY_PLAYER || event.getCause() == CauseOfUnTag.DEATH) {
Player player = Bukkit.getPlayer(event.getPlayer());
assert player != null;
this.effectService.clearStoredEffects(player);
}
}
@EventHandler
public void onEffectChange(EntityPotionEffectEvent event) {
if (!this.effectSettings.customEffectsEnabled) {
return;
}
if (!(event.getEntity() instanceof Player player)) {
return;
}
if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}
PotionEffect newEffect = event.getNewEffect();
PotionEffect oldEffect = event.getOldEffect();
if (!this.isRemovedEffect(newEffect, oldEffect)) {
return;
}
Integer customAmplifier = this.effectSettings.customEffects.get(oldEffect.getType());
if (customAmplifier == null) {
return;
}
player.addPotionEffect(new PotionEffect(oldEffect.getType(), -1, customAmplifier));
}
private boolean isRemovedEffect(PotionEffect newEffect, PotionEffect oldEffect) {
return newEffect == null && oldEffect != null;
}
}
@@ -0,0 +1,75 @@
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 FightEffectService {
private final Map<UUID, List<PotionEffect>> activeEffects = new HashMap<>();
private static final int INFINITE_DURATION = -1;
public void storeActiveEffect(Player player, PotionEffect effect) {
List<PotionEffect> effects = this.activeEffects.computeIfAbsent(player.getUniqueId(), k -> new ArrayList<>());
effects.add(effect);
}
public void restoreActiveEffects(Player player) {
List<PotionEffect> 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<PotionEffect> 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;
}
if (activeEffect.getDuration() == -1) {
return;
}
this.storeActiveEffect(player, activeEffect);
player.addPotionEffect(new PotionEffect(type, INFINITE_DURATION, amplifier));
}
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);
}
}
@@ -0,0 +1,26 @@
package com.eternalcode.combat.fight.effect;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import org.bukkit.potion.PotionEffectType;
import java.util.Map;
public class FightEffectSettings extends OkaeriConfig {
@Comment({"# Do you want to add effects to players in combat?"})
public boolean customEffectsEnabled = true;
@Comment({
"# If the option above is set to true, you can add effects to players in combat below",
"# You can find a list of all potion effects here: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/potion/PotionEffectType.html",
"# Correct format: 'EFFECT_TYPE:AMPLIFIER' Amplifier strength starts from 0, so level 1 gives effect strength 2",
"# Example: SPEED:1, DAMAGE_RESISTANCE:0",
})
public Map<PotionEffectType, Integer> customEffects = Map.of(
PotionEffectType.SPEED, 1,
PotionEffectType.DAMAGE_RESISTANCE, 0
);
}
@@ -0,0 +1,7 @@
package com.eternalcode.combat.fight.event;
public enum CauseOfTag {
PLAYER,
NON_PLAYER,
COMMAND
}
@@ -0,0 +1,10 @@
package com.eternalcode.combat.fight.event;
public enum CauseOfUnTag {
TIME_EXPIRED,
DEATH,
DEATH_BY_PLAYER,
LOGOUT,
COMMAND,
ATTACKER_RELEASE
}
@@ -0,0 +1,63 @@
package com.eternalcode.combat.fight.event;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import java.util.UUID;
public class FightTagEvent extends Event implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private final UUID player;
private final CauseOfTag cause;
private boolean isCancelled = false;
private String cancelMessage = "";
public FightTagEvent(UUID player, CauseOfTag cause) {
this.player = player;
this.cause = cause;
}
public UUID getPlayer() {
return this.player;
}
public CauseOfTag getCause() {
return this.cause;
}
@Override
public boolean isCancelled() {
return this.isCancelled;
}
@Override
public void setCancelled(boolean cancelled) {
throw new UnsupportedOperationException("Use #cancel(String) instead");
}
public void cancel(String cancelMessage) {
this.isCancelled = true;
this.cancelMessage = cancelMessage;
}
public void allow() {
this.isCancelled = false;
this.cancelMessage = "";
}
public String getCancelMessage() {
return this.cancelMessage;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}
@@ -0,0 +1,47 @@
package com.eternalcode.combat.fight.event;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import java.util.UUID;
public class FightUntagEvent extends Event implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private final UUID player;
private final CauseOfUnTag cause;
private boolean isCancelled = false;
public FightUntagEvent(UUID player, CauseOfUnTag cause) {
this.player = player;
this.cause = cause;
}
public UUID getPlayer() {
return this.player;
}
public CauseOfUnTag getCause() {
return this.cause;
}
@Override
public boolean isCancelled() {
return this.isCancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.isCancelled = cancelled;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}
@@ -4,10 +4,14 @@ import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import com.eternalcode.combat.util.DurationUtil;
import org.bukkit.Material;
import org.bukkit.entity.EnderPearl;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.Action;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.entity.EntityDamageEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import panda.utilities.text.Formatter;
@@ -34,6 +38,10 @@ public class FightPearlController implements Listener {
Player player = event.getPlayer();
UUID uniqueId = player.getUniqueId();
if (!this.settings.pearlThrowBlocked) {
return;
}
if (!this.fightManager.isInCombat(uniqueId)) {
return;
}
@@ -48,11 +56,7 @@ public class FightPearlController implements Listener {
return;
}
if (!this.settings.enabled) {
return;
}
if (this.settings.delay.isZero()) {
if (this.settings.pearlThrowDelay.isZero()) {
event.setCancelled(true);
this.announcer.sendMessage(player, this.settings.pearlThrowBlockedDuringCombat);
return;
@@ -73,4 +77,25 @@ public class FightPearlController implements Listener {
this.fightPearlManager.markDelay(uniqueId);
}
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
void onEntityDamage(EntityDamageByEntityEvent event) {
if (this.settings.pearlThrowDamageEnabled) {
return;
}
if (!(event.getEntity() instanceof Player)) {
return;
}
if (!(event.getDamager() instanceof EnderPearl)) {
return;
}
if (event.getCause() != EntityDamageEvent.DamageCause.FALL) {
return;
}
event.setDamage(0.0D);
}
}
@@ -15,12 +15,12 @@ public class FightPearlManager {
public FightPearlManager(FightPearlSettings pearlSettings) {
this.pearlSettings = pearlSettings;
this.pearlDelays = CacheBuilder.newBuilder()
.expireAfterWrite(pearlSettings.delay)
.expireAfterWrite(pearlSettings.pearlThrowDelay)
.build();
}
public void markDelay(UUID uuid) {
this.pearlDelays.put(uuid, Instant.now().plus(this.pearlSettings.delay));
this.pearlDelays.put(uuid, Instant.now().plus(this.pearlSettings.pearlThrowDelay));
}
public boolean hasDelay(UUID uuid) {
@@ -0,0 +1,29 @@
package com.eternalcode.combat.fight.pearl;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import java.time.Duration;
public class FightPearlSettings extends OkaeriConfig {
@Comment({ "# Is pearl damage to be enabled?", "# This will work globally" })
public boolean pearlThrowDamageEnabled = true;
@Comment("# Set true, If you want to lock pearls during the combat")
public boolean pearlThrowBlocked = false;
@Comment({
"# Block throwing pearls with delay?",
"# If you set this to for example 3s, player will have to wait 3 seconds before throwing another pearl",
"# Set to 0 to disable"
})
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!";
@Comment("# Message sent when player tries to throw ender pearl, but has delay")
public String pearlThrowBlockedDelayDuringCombat = "&cYou must wait {TIME} before next throw!";
}
@@ -0,0 +1,29 @@
package com.eternalcode.combat.fight.tagout;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.event.FightTagEvent;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import java.util.UUID;
public class FightTagOutController implements Listener {
private final FightTagOutService tagOutService;
private final PluginConfig config;
public FightTagOutController(FightTagOutService tagOutService, PluginConfig config) {
this.tagOutService = tagOutService;
this.config = config;
}
@EventHandler
void onTagOut(FightTagEvent event) {
UUID uniqueId = event.getPlayer();
if (this.tagOutService.isTaggedOut(uniqueId)) {
event.cancel(this.config.messages.admin.adminTagOutCanceled);
}
}
}
@@ -0,0 +1,34 @@
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 {
private final Map<UUID, Instant> tagOuts = new HashMap<>();
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);
}
}
@@ -0,0 +1,94 @@
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);
}
}
@@ -0,0 +1,8 @@
package com.eternalcode.combat.notification;
public interface Notification {
String message();
NotificationType type();
}
@@ -0,0 +1,79 @@
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();
}
}
@@ -1,9 +1,9 @@
package com.eternalcode.combat.notification;
public enum NotificationType {
ACTION_BAR,
CHAT,
ACTION_BAR,
TITLE,
SUBTITLE,
SUB_TITLE,
BOSS_BAR
}
@@ -0,0 +1,12 @@
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;
}
}
@@ -0,0 +1,20 @@
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;
}
}
@@ -0,0 +1,12 @@
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;
}
}
@@ -0,0 +1,12 @@
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;
}
}
@@ -0,0 +1,12 @@
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;
}
}
@@ -0,0 +1,64 @@
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<Notification> {
@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);
}
};
}
}
@@ -49,7 +49,7 @@ public class RegionController implements Listener {
Location playerLocation = player.getLocation().subtract(player.getWorld().getSpawnLocation());
double distance = playerLocation.distance(player.getWorld().getSpawnLocation());
Vector knockback = new Vector(0, 3, 0).multiply(this.pluginConfig.settings.blockedRegionMultiplier / distance);
Vector knockback = new Vector(0, 3, 0).multiply(this.pluginConfig.settings.blockedRegionKnockMultiplier / distance);
Vector vector = playerLocation.toVector().add(knockback);
player.setVelocity(vector);
@@ -0,0 +1,71 @@
package com.eternalcode.combat.util;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.function.ToIntFunction;
public class InventoryUtil {
private static final Random RANDOM = new Random();
private InventoryUtil() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
public static <T> int calculateItemsToDelete(int percent, Collection<T> objectList, ToIntFunction<? super T> mapper) {
return MathUtil.getRoundedCountFromPercentage(percent, MathUtil.sum(objectList, mapper));
}
public static RemoveItemResult removeRandomItems(List<ItemStack> list, int itemsToDelete) {
List<ItemStack> restItems = new ArrayList<>(list);
Map<Integer, ItemStack> removedItems = new HashMap<>();
for (int i = 0; i < itemsToDelete; i++) {
if (restItems.isEmpty()) {
break;
}
int randomIndex = RANDOM.nextInt(restItems.size());
ItemStack item = restItems.get(randomIndex);
int newAmount = item.getAmount() - 1;
if (newAmount <= 0) {
restItems.remove(randomIndex);
incrementItem(removedItems, randomIndex, item);
continue;
}
item.setAmount(newAmount);
incrementItem(removedItems, randomIndex, item);
}
return new RemoveItemResult(restItems, new ArrayList<>(removedItems.values()));
}
// TODO: kinda shitty, but no time for a proper solution now
private static void incrementItem(Map<Integer, ItemStack> items, int index, ItemStack itemStack) {
ItemStack itemInList = items.get(index);
if (itemInList == null) {
ItemStack cloned = itemStack.clone();
cloned.setAmount(1);
items.put(index, cloned);
return;
}
if (!itemInList.isSimilar(itemStack)) {
throw new IllegalStateException("Items are not similar!");
}
itemInList.setAmount(itemInList.getAmount() + 1);
}
}
@@ -0,0 +1,35 @@
package com.eternalcode.combat.util;
import java.util.Collection;
import java.util.function.ToIntFunction;
public class MathUtil {
private MathUtil() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
public static double getCountFromPercentage(int percentage, int totalCount) {
return percentage / 100D * totalCount;
}
public static double getCountPercentage(double value, double base) {
return value / base * 100;
}
public static int getRoundedCountPercentage(double value, double base) {
return (int) Math.round(getCountPercentage(value, base));
}
public static int getRoundedCountFromPercentage(int percentage, int baseNumber) {
return (int) Math.round(getCountFromPercentage(percentage, baseNumber));
}
public static <T> int sum(Collection<T> collection, ToIntFunction<? super T> mapper) {
return collection.stream().mapToInt(mapper).sum();
}
public static int clamp(int value, int min, int max) {
return Math.max(min, Math.min(max, value));
}
}
@@ -0,0 +1,22 @@
package com.eternalcode.combat.util;
import org.bukkit.inventory.ItemStack;
import java.util.Collections;
import java.util.List;
public record RemoveItemResult(
List<ItemStack> restItems,
List<ItemStack> removedItems
) {
public List<ItemStack> restItems() {
return Collections.unmodifiableList(this.restItems);
}
@Override
public List<ItemStack> removedItems() {
return Collections.unmodifiableList(this.removedItems);
}
}
@@ -9,11 +9,8 @@ plugins {
id("xyz.jpenilla.run-paper") version "2.1.0"
}
group = "com.eternalcode"
version = "1.0.3"
checkstyle {
toolVersion = "10.12.1"
toolVersion = "10.12.2"
configFile = file("${rootDir}/config/checkstyle/checkstyle.xml")
@@ -23,48 +20,17 @@ checkstyle {
repositories {
mavenCentral()
mavenLocal()
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/") }
}
dependencies {
// Spigot api
compileOnly("org.spigotmc:spigot-api:1.19.3-R0.1-SNAPSHOT")
// kyori
implementation("net.kyori:adventure-platform-bukkit:4.3.0")
implementation("net.kyori:adventure-text-minimessage:4.14.0")
// litecommands
implementation("dev.rollczi.litecommands:bukkit-adventure:2.8.8")
// cdn configs
implementation("net.dzikoysk:cdn:1.14.4") {
exclude(group = "org.jetbrains.kotlin")
}
// GitCheck
implementation("com.eternalcode:gitcheck:1.0.0")
// commons
implementation("commons-io:commons-io:2.13.0")
// bstats
implementation("org.bstats:bstats-bukkit:3.0.2")
// worldguard
compileOnly("com.sk89q.worldguard:worldguard-bukkit:7.0.8")
// tests
testImplementation("org.spigotmc:spigot-api:1.19.3-R0.1-SNAPSHOT")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.9.3")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.9.3")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.9.3")
implementation(project(":eternalcombat-api"))
}
bukkit {
@@ -82,10 +48,6 @@ java {
targetCompatibility = JavaVersion.VERSION_17
}
tasks.getByName<Test>("test") {
useJUnitPlatform()
}
tasks.withType<JavaCompile> {
options.compilerArgs = listOf("-Xlint:deprecation", "-parameters")
options.encoding = "UTF-8"
@@ -98,7 +60,7 @@ tasks {
}
tasks.withType<ShadowJar> {
archiveFileName.set("EternalCombat v${project.version} (MC 1.8.8-1.19x).jar")
archiveFileName.set("EternalCombat v${project.version}.jar")
dependsOn("checkstyleMain")
dependsOn("checkstyleTest")
@@ -108,6 +70,7 @@ tasks.withType<ShadowJar> {
"org/intellij/lang/annotations/**",
"org/jetbrains/annotations/**",
"META-INF/**",
"kotlin/**",
"javax/**"
)
@@ -116,13 +79,14 @@ tasks.withType<ShadowJar> {
"panda.std",
"panda.utilities",
"org.panda-lang",
"net.dzikoysk",
"eu.okaeri",
"net.kyori",
"org.bstats",
"dev.rollczi.litecommands",
"com.eternalcode.gitcheck",
"org.json.simple",
"commons-io"
"org.apache.commons",
"javassist"
).forEach { pack ->
relocate(pack, "$prefix.$pack")
}
@@ -0,0 +1,203 @@
package com.eternalcode.combat;
import com.eternalcode.combat.bridge.BridgeService;
import com.eternalcode.combat.command.InvalidUsage;
import com.eternalcode.combat.command.PermissionMessage;
import com.eternalcode.combat.config.ConfigBackupService;
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.controller.FightActionBlockerController;
import com.eternalcode.combat.fight.controller.FightDeathCauseController;
import com.eternalcode.combat.fight.controller.FightEscapeController;
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.FightTask;
import com.eternalcode.combat.fight.bossbar.FightBossBarService;
import com.eternalcode.combat.fight.effect.FightEffectService;
import com.eternalcode.combat.fight.pearl.FightPearlController;
import com.eternalcode.combat.fight.pearl.FightPearlManager;
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.region.RegionProvider;
import com.eternalcode.combat.updater.UpdaterNotificationController;
import com.eternalcode.combat.updater.UpdaterService;
import com.eternalcode.combat.util.legacy.LegacyColorProcessor;
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 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;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
private FightManager fightManager;
private FightPearlManager fightPearlManager;
private FightTagOutService fightTagOutService;
private FightEffectService fightEffectService;
private DropManager dropManager;
private DropKeepInventoryManager dropKeepInventoryManager;
private RegionProvider regionProvider;
private PluginConfig pluginConfig;
private AudienceProvider audienceProvider;
private LiteCommands<CommandSender> liteCommands;
@Override
public void onEnable() {
Stopwatch started = Stopwatch.createStarted();
Server server = this.getServer();
File dataFolder = this.getDataFolder();
ConfigBackupService backupService = new ConfigBackupService(dataFolder);
ConfigService configService = new ConfigService(backupService);
EventCaller eventCaller = new EventCaller(server);
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.dropManager = new DropManager();
this.dropKeepInventoryManager = new DropKeepInventoryManager();
UpdaterService updaterService = new UpdaterService(this.getDescription());
this.audienceProvider = BukkitAudiences.create(this);
MiniMessage miniMessage = MiniMessage.builder()
.postProcessor(new LegacyColorProcessor())
.build();
FightBossBarService fightBossBarService = new FightBossBarService(this.pluginConfig, this.audienceProvider, miniMessage);
BridgeService bridgeService = new BridgeService(this.pluginConfig, server.getPluginManager(), this.getLogger());
bridgeService.init();
this.regionProvider = bridgeService.getRegionProvider();
NotificationAnnouncer notificationAnnouncer = new NotificationAnnouncer(this.audienceProvider, miniMessage);
this.liteCommands = LiteBukkitAdventurePlatformFactory.builder(server, "eternalcombat", this.audienceProvider)
.argument(Player.class, new BukkitPlayerArgument<>(this.getServer(), this.pluginConfig.messages.playerNotFound))
.contextualBind(Player.class, new BukkitOnlyPlayerContextual<>(this.pluginConfig.messages.admin.onlyForPlayers))
.invalidUsageHandler(new InvalidUsage(this.pluginConfig, notificationAnnouncer))
.permissionHandler(new PermissionMessage(this.pluginConfig, notificationAnnouncer))
.commandInstance(new CombatCommand(this.fightManager, configService, notificationAnnouncer, this.pluginConfig))
.commandInstance(new TagOutCommand(this.fightTagOutService, notificationAnnouncer, this.pluginConfig))
.register();
FightTask fightTask = new FightTask(server, this.pluginConfig, this.fightManager, fightBossBarService, notificationAnnouncer);
this.getServer().getScheduler().runTaskTimer(this, fightTask, 20L, 20L);
new Metrics(this, 17803);
Stream.of(
new PercentDropModifier(this.pluginConfig.dropSettings),
new PlayersHealthDropModifier(this.pluginConfig.dropSettings)
).forEach(this.dropManager::registerModifier);
Stream.of(
new FightDeathCauseController(this.fightManager),
new DropController(this.dropManager, this.dropKeepInventoryManager, this.pluginConfig.dropSettings, this.fightManager),
new FightTagController(this.fightManager, this.pluginConfig, notificationAnnouncer),
new FightUnTagController(this.fightManager, this.pluginConfig),
new FightEscapeController(this.fightManager, this.pluginConfig, notificationAnnouncer),
new FightActionBlockerController(this.fightManager, notificationAnnouncer, this.pluginConfig),
new FightPearlController(this.pluginConfig.pearl, notificationAnnouncer, this.fightManager, this.fightPearlManager),
new UpdaterNotificationController(updaterService, this.pluginConfig, this.audienceProvider, miniMessage),
new RegionController(notificationAnnouncer, this.regionProvider, this.fightManager, this.pluginConfig),
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())
).forEach(listener -> this.getServer().getPluginManager().registerEvents(listener, this));
EternalCombatProvider.initialize(this);
long millis = started.elapsed(TimeUnit.MILLISECONDS);
this.getLogger().info("Successfully loaded EternalCombat in " + millis + "ms");
}
@Override
public void onDisable() {
EternalCombatProvider.deinitialize();
if (this.liteCommands != null) {
this.liteCommands.getPlatform().unregisterAll();
}
if (this.audienceProvider != null) {
this.audienceProvider.close();
}
this.fightManager.untagAll();
}
@Override
public FightManager getFightManager() {
return this.fightManager;
}
@Override
public RegionProvider getRegionProvider() {
return this.regionProvider;
}
@Override
public FightPearlManager getFightPearlManager() {
return this.fightPearlManager;
}
@Override
public FightTagOutService getFightTagOutService() {
return this.fightTagOutService;
}
@Override
public FightEffectService getFightEffectService() {
return this.fightEffectService;
}
@Override
public DropManager getDropManager() {
return this.dropManager;
}
@Override
public DropKeepInventoryManager getDropKeepInventoryManager() {
return this.dropKeepInventoryManager;
}
@Override
public PluginConfig getPluginConfig() {
return this.pluginConfig;
}
}
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Vendored
+2 -1
View File
@@ -83,7 +83,8 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
+2
View File
@@ -1 +1,3 @@
rootProject.name = "EternalCombat"
include("eternalcombat-api")
include("eternalcombat-plugin")
@@ -1,112 +0,0 @@
package com.eternalcode.combat;
import com.eternalcode.combat.bridge.BridgeService;
import com.eternalcode.combat.command.InvalidUsage;
import com.eternalcode.combat.command.PermissionMessage;
import com.eternalcode.combat.config.ConfigBackupService;
import com.eternalcode.combat.config.ConfigManager;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.FightTask;
import com.eternalcode.combat.fight.controller.FightActionBlockerController;
import com.eternalcode.combat.fight.controller.FightTagController;
import com.eternalcode.combat.fight.controller.FightUnTagController;
import com.eternalcode.combat.fight.pearl.FightPearlController;
import com.eternalcode.combat.fight.pearl.FightPearlManager;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import com.eternalcode.combat.region.RegionController;
import com.eternalcode.combat.updater.UpdaterNotificationController;
import com.eternalcode.combat.updater.UpdaterService;
import com.eternalcode.combat.util.legacy.LegacyColorProcessor;
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 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;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
public final class CombatPlugin extends JavaPlugin {
private FightManager fightManager;
private FightPearlManager fightPearlManager;
private AudienceProvider audienceProvider;
private LiteCommands<CommandSender> liteCommands;
@Override
public void onEnable() {
Stopwatch started = Stopwatch.createStarted();
Server server = this.getServer();
File dataFolder = this.getDataFolder();
ConfigBackupService backupService = new ConfigBackupService(dataFolder);
ConfigManager configManager = new ConfigManager(backupService, dataFolder);
PluginConfig pluginConfig = configManager.load(new PluginConfig());
this.fightManager = new FightManager();
this.fightPearlManager = new FightPearlManager(pluginConfig.pearl);
UpdaterService updaterService = new UpdaterService(this.getDescription());
this.audienceProvider = BukkitAudiences.create(this);
MiniMessage miniMessage = MiniMessage.builder()
.postProcessor(new LegacyColorProcessor())
.build();
BridgeService bridgeService = new BridgeService(pluginConfig, server.getPluginManager(), this.getLogger());
bridgeService.init();
NotificationAnnouncer notificationAnnouncer = new NotificationAnnouncer(this.audienceProvider, miniMessage);
this.liteCommands = LiteBukkitAdventurePlatformFactory.builder(server, "eternalcombat", this.audienceProvider)
.argument(Player.class, new BukkitPlayerArgument<>(this.getServer(), pluginConfig.messages.playerNotFound))
.contextualBind(Player.class, new BukkitOnlyPlayerContextual<>(pluginConfig.messages.admin.onlyForPlayers))
.invalidUsageHandler(new InvalidUsage(pluginConfig, notificationAnnouncer))
.permissionHandler(new PermissionMessage(pluginConfig, notificationAnnouncer))
.commandInstance(new CombatCommand(this.fightManager, configManager, notificationAnnouncer, pluginConfig))
.register();
FightTask fightTask = new FightTask(this.fightManager, pluginConfig, server, notificationAnnouncer);
this.getServer().getScheduler().runTaskTimerAsynchronously(this, fightTask, 20L, 20L);
new Metrics(this, 17803);
Stream.of(
new FightTagController(this.fightManager, pluginConfig, notificationAnnouncer),
new FightUnTagController(this.fightManager, pluginConfig, notificationAnnouncer),
new FightActionBlockerController(this.fightManager, notificationAnnouncer, pluginConfig),
new FightPearlController(pluginConfig.pearl, notificationAnnouncer, this.fightManager, this.fightPearlManager),
new UpdaterNotificationController(updaterService, pluginConfig, this.audienceProvider, miniMessage),
new RegionController(notificationAnnouncer, bridgeService.getRegionProvider(), this.fightManager, pluginConfig)
).forEach(listener -> this.getServer().getPluginManager().registerEvents(listener, this));
long millis = started.elapsed(TimeUnit.MILLISECONDS);
this.getLogger().info("Successfully loaded EternalCombat in " + millis + "ms");
}
@Override
public void onDisable() {
if (this.liteCommands != null) {
this.liteCommands.getPlatform().unregisterAll();
}
if (this.audienceProvider != null) {
this.audienceProvider.close();
}
this.fightManager.untagAll();
}
}
@@ -1,50 +0,0 @@
package com.eternalcode.combat.config;
import com.eternalcode.combat.config.composer.DurationComposer;
import net.dzikoysk.cdn.Cdn;
import net.dzikoysk.cdn.CdnFactory;
import java.io.File;
import java.time.Duration;
import java.util.HashSet;
import java.util.Set;
public class ConfigManager {
private final ConfigBackupService backupService;
private final Cdn cdn = CdnFactory
.createYamlLike()
.getSettings()
.withComposer(Duration.class, new DurationComposer())
.build();
private final Set<ReloadableConfig> configs = new HashSet<>();
private final File dataFolder;
public ConfigManager(ConfigBackupService backupService, File dataFolder) {
this.backupService = backupService;
this.dataFolder = dataFolder;
}
public <T extends ReloadableConfig> T load(T config) {
this.backupService.createBackup();
this.cdn.load(config.resource(this.dataFolder), config)
.orThrow(RuntimeException::new);
this.cdn.render(config, config.resource(this.dataFolder))
.orThrow(RuntimeException::new);
this.configs.add(config);
return config;
}
public void reload() {
for (ReloadableConfig config : this.configs) {
this.load(config);
}
}
}
@@ -1,12 +0,0 @@
package com.eternalcode.combat.config;
import net.dzikoysk.cdn.source.Resource;
import java.io.File;
public interface ReloadableConfig {
Resource resource(File folder);
}
@@ -1,24 +0,0 @@
package com.eternalcode.combat.config.composer;
import dev.rollczi.litecommands.shared.EstimatedTemporalAmountParser;
import panda.std.Result;
import java.time.Duration;
public class DurationComposer implements SimpleComposer<Duration> {
@Override
public Result<Duration, Exception> deserialize(String source) {
if (source.isEmpty() || source.equals("0")) {
return Result.ok(Duration.ZERO);
}
return Result.supplyThrowing(IllegalArgumentException.class, () -> EstimatedTemporalAmountParser.TIME_UNITS.parse(source));
}
@Override
public Result<String, Exception> serialize(Duration entity) {
return Result.ok(EstimatedTemporalAmountParser.TIME_UNITS.format(entity));
}
}
@@ -1,9 +0,0 @@
package com.eternalcode.combat.config.composer;
import net.dzikoysk.cdn.serdes.Composer;
import net.dzikoysk.cdn.serdes.SimpleDeserializer;
import net.dzikoysk.cdn.serdes.SimpleSerializer;
public interface SimpleComposer<T> extends Composer<T>, SimpleDeserializer<T>, SimpleSerializer<T> {
}
@@ -1,188 +0,0 @@
package com.eternalcode.combat.config.implementation;
import com.eternalcode.combat.config.ReloadableConfig;
import com.eternalcode.combat.fight.FightCommandMode;
import com.eternalcode.combat.fight.pearl.FightPearlSettings;
import com.eternalcode.combat.notification.NotificationType;
import net.dzikoysk.cdn.entity.Contextual;
import net.dzikoysk.cdn.entity.Description;
import net.dzikoysk.cdn.source.Resource;
import net.dzikoysk.cdn.source.Source;
import org.bukkit.Material;
import org.bukkit.event.entity.EntityDamageEvent;
import java.io.File;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
public class PluginConfig implements ReloadableConfig {
@Description("# Do you want to change the plugin settings?")
public Settings settings = new Settings();
@Description({" ", "# Block the use of ender pearls"})
public FightPearlSettings pearl = new FightPearlSettings();
@Contextual
public static class Settings {
@Description("# Whether the player should receive information about new plugin updates upon joining the server")
public boolean shouldReceivePluginUpdates = true;
@Description("# The duration of combat in seconds")
public Duration combatDuration = Duration.ofSeconds(20);
@Description("# List of worlds to ignore")
public List<String> worldsToIgnore = List.of(
"your_world"
);
@Description("# List of regions to block")
public List<String> blockedRegions = Collections.singletonList("your_region");
@Description("# Set the knock multiplier for the blocked region")
public double blockedRegionMultiplier = 1.2;
@Description("# Set the radius of the blocked region if you aren't using WorldGuard!")
public int blockedRegionRadius = 10;
@Description("# Combat log notification type, available types: ACTION_BAR, CHAT, TITLE, SUBTITLE")
public NotificationType notificationType = NotificationType.ACTION_BAR;
@Description("# Command blocking mode, available modes: WHITELIST, BLACKLIST")
public FightCommandMode commandBlockingMode = FightCommandMode.BLACKLIST;
@Description({
"# 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<String> blockedCommands = List.of(
"gamemode",
"spawn",
"tp"
);
@Description("# Block the use of elytra?")
public boolean shouldPreventElytraUsage = true;
@Description("# Disable the use of elytra on damage?")
public boolean shouldElytraDisableOnDamage = true;
@Description("# Block the opening of inventory?")
public boolean shouldPreventInventoryOpening = true;
@Description("# Whether to block the placement of blocks?")
public boolean shouldPreventBlockPlacing = true;
@Description("# The minimum Y level at which block placing is blocked")
public int minBlockPlacingLevel = 40;
@Description({
"# 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<Material> specificBlocksToPreventPlacing = List.of();
@Description("# Should the option below be enabled?")
public boolean shouldEnableDamageCauses = true;
@Description({
"# After what type of damage the player should get a combat log?",
"# You can find a list of all causes here: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/event/entity/EntityDamageEvent.DamageCause.html",
"# If you don't want the combat log to be given to players for a certain damage type, simply remove it from this list"
})
public List<EntityDamageEvent.DamageCause> damageCausesToLog = List.of(
EntityDamageEvent.DamageCause.LAVA,
EntityDamageEvent.DamageCause.CONTACT,
EntityDamageEvent.DamageCause.FIRE,
EntityDamageEvent.DamageCause.FIRE_TICK
);
}
@Description({ " ", "# Do you want to change the plugin messages?" })
public Messages messages = new Messages();
@Contextual
public static class Messages {
@Description("# Do you want to change the admin messages?")
public AdminMessages admin = new AdminMessages();
@Description({ " ", "# Combat log message format, e.g. on the actionbar (you can use {TIME} variable to display the time left in combat)" })
public String combatFormat = "&dCombat ends in: &f{TIME}";
@Description("# 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!";
@Description("# Message sent when the specified player could not be found")
public String playerNotFound = "&cThe specified player could not be found!";
@Description("# Message sent when the player enters combat")
public String playerTagged = "&cYou are in combat, do not leave the server!";
@Description("# Message sent when the player leaves combat")
public String playerUntagged = "&aYou are no longer in combat! You can safely leave the server.";
@Description("# This is broadcast when the player is in combat and logs out")
public String playerLoggedOutDuringCombat = "&c{PLAYER} logged off during the fight!";
@Description({
"# 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!";
@Description("# Message sent when player tries to use a command with invalid arguments")
public String invalidCommandUsage = "&7Correct usage: &e{USAGE}";
@Description("# Message sent when player tries to open inventory, but the inventory open is blocked")
public String inventoryBlockedDuringCombat = "&cYou cannot open this inventory during combat!";
@Description("# Message sent when player tries to place a block, but the block place is blocked")
public String blockPlacingBlockedDuringCombat = "&cYou cannot place below 40Y coordinate during combat!";
@Description("# Message sent when player tries to enter a region")
public String cantEnterOnRegion = "&cYou can't enter on this region during combat!";
@Contextual
public static class AdminMessages {
@Description("# Message sent when the configuration is reloaded")
public String reload = "&aConfiguration has been successfully reloaded!";
@Description("# Message sent when console tries to use a command that is only for players")
public String onlyForPlayers = "&cThis command is only available to players!";
@Description("# Message sent to admin when they tag a player")
public String adminTagPlayer = "&7You have tagged &e{PLAYER}";
@Description("# 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.";
@Description("# Message sent to admin when they remove a player from combat")
public String adminUntagPlayer = "&7You have removed &e{PLAYER}&7 from the fight.";
@Description("# Message sent when the player is not in combat")
public String adminPlayerNotInCombat = "&cThis player is not in combat!";
@Description("# Message sent when the player is in combat")
public String playerInCombat = "&c{PLAYER} is currently in combat!";
@Description("# Message sent when a player is not in combat")
public String playerNotInCombat = "&a{PLAYER} is not currently in combat.";
@Description("# Message sent when an admin tries to tag themselves")
public String adminCannotTagSelf = "&cYou cannot tag yourself!";
}
}
@Override
public Resource resource(File folder) {
return Source.of(folder, "config.yml");
}
}
@@ -1,51 +0,0 @@
package com.eternalcode.combat.fight;
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;
public class FightManager {
private final Map<UUID, FightTag> fights = new ConcurrentHashMap<>();
public boolean isInCombat(UUID player) {
if (!this.fights.containsKey(player)) {
return false;
}
FightTag fightTag = this.fights.get(player);
if (fightTag.isExpired()) {
this.fights.remove(player);
return false;
}
return true;
}
public void untag(UUID player) {
this.fights.remove(player);
}
public void tag(UUID target, Duration delay) {
Instant now = Instant.now();
Instant endOfCombatLog = now.plus(delay);
FightTag fightTag = new FightTag(target, endOfCombatLog);
this.fights.put(target, fightTag);
}
public Collection<FightTag> getFights() {
return Collections.unmodifiableCollection(this.fights.values());
}
public void untagAll() {
this.fights.clear();
}
}
@@ -1,51 +0,0 @@
package com.eternalcode.combat.fight;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.notification.NotificationAnnouncer;
import com.eternalcode.combat.util.DurationUtil;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import panda.utilities.text.Formatter;
import java.time.Duration;
public class FightTask implements Runnable {
private final FightManager fightManager;
private final PluginConfig config;
private final Server server;
private final NotificationAnnouncer announcer;
public FightTask(FightManager fightManager, PluginConfig config, Server server, NotificationAnnouncer announcer) {
this.fightManager = fightManager;
this.config = config;
this.server = server;
this.announcer = announcer;
}
@Override
public void run() {
for (FightTag fightTag : this.fightManager.getFights()) {
Player player = this.server.getPlayer(fightTag.getTaggedPlayer());
if (player == null) {
return;
}
if (!fightTag.isExpired()) {
Duration remaining = fightTag.getRemainingDuration();
Formatter formatter = new Formatter()
.register("{TIME}", DurationUtil.format(remaining));
String format = formatter.format(this.config.messages.combatFormat);
this.announcer.send(player, this.config.settings.notificationType, format);
continue;
}
this.fightManager.untag(fightTag.getTaggedPlayer());
this.announcer.send(player, this.config.settings.notificationType, this.config.messages.playerUntagged);
}
}
}
@@ -1,27 +0,0 @@
package com.eternalcode.combat.fight.pearl;
import net.dzikoysk.cdn.entity.Contextual;
import net.dzikoysk.cdn.entity.Description;
import java.time.Duration;
@Contextual
public class FightPearlSettings {
@Description("# If you want to completely disable this option, set this to false")
public boolean enabled = true;
@Description({
"# Block throwing pearls with delay?",
"# If you set this to for example 3s, player will have to wait 3 seconds before throwing another pearl",
"# Set to 0 to disable"
})
public Duration delay = Duration.ofSeconds(3);
@Description("# Message sent when player tries to throw ender pearl, but are disabled")
public String pearlThrowBlockedDuringCombat = "&cThrowing ender pearls is prohibited during combat!";
@Description("# Message sent when player tries to throw ender pearl, but has delay")
public String pearlThrowBlockedDelayDuringCombat = "&cYou must wait {TIME} before next throw!";
}
@@ -1,61 +0,0 @@
package com.eternalcode.combat.notification;
import net.kyori.adventure.audience.Audience;
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 java.util.Map;
import java.util.function.BiConsumer;
public final class NotificationAnnouncer {
private final static Map<NotificationType, BiConsumer<Audience, Component>> NOTIFICATION_HANDLERS = Map.of(
NotificationType.CHAT, Audience::sendMessage,
NotificationType.ACTION_BAR, Audience::sendActionBar,
NotificationType.TITLE, (audience, component) -> audience.showTitle(Title.title(component, Component.empty())),
NotificationType.SUBTITLE, (audience, component) -> audience.showTitle(Title.title(Component.empty(), component))
);
private final AudienceProvider audienceProvider;
private final MiniMessage miniMessage;
public NotificationAnnouncer(AudienceProvider audienceProvider, MiniMessage miniMessage) {
this.audienceProvider = audienceProvider;
this.miniMessage = miniMessage;
}
public void send(CommandSender commandSender, NotificationType notificationType, String text) {
Audience audience = this.audience(commandSender);
Component component = this.miniMessage.deserialize(text);
BiConsumer<Audience, Component> handler = NOTIFICATION_HANDLERS.get(notificationType);
if (handler == null) {
return;
}
handler.accept(audience, component);
}
public void sendMessage(CommandSender commandSender, String text) {
Audience audience = this.audience(commandSender);
audience.sendMessage(this.miniMessage.deserialize(text));
}
public void broadcast(CommandSender commandSender, String text) {
this.audienceProvider.all().sendMessage(this.miniMessage.deserialize(text));
}
private Audience audience(CommandSender sender) {
if (sender instanceof Player player) {
return this.audienceProvider.player(player.getUniqueId());
}
return this.audienceProvider.console();
}
}
@@ -1,54 +0,0 @@
package com.eternalcode.combat.config.composer;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import panda.std.Result;
import java.time.Duration;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class DurationComposerTest {
DurationComposer composer = new DurationComposer();
@CsvSource({
"5400, 1h30m",
"3600, 1h",
"1800, 30m",
"60, 1m",
"1, 1s",
"5401, 1h30m1s",
"3601, 1h1s",
"61, 1m1s",
"3661, 1h1m1s",
})
@ParameterizedTest
void testDeserialize(long expected, String source) {
Result<Duration, Exception> deserialize = this.composer.deserialize(source);
assertTrue(deserialize.isOk());
assertEquals(expected, deserialize.get().getSeconds());
}
@CsvSource({
"1h30m, 5400",
"1h, 3600",
"30m, 1800",
"1m, 60",
"1s, 1",
"1h30m1s, 5401",
"1h1s, 3601",
"1m1s, 61",
"1h1m1s, 3661",
})
@ParameterizedTest
void testSerialize(String expected, long source) {
Result<String, Exception> serialize = this.composer.serialize(Duration.ofSeconds(source));
assertTrue(serialize.isOk());
assertEquals(expected, serialize.get());
}
}