Compare commits

...
Author SHA1 Message Date
Martin Sulikowski 3dd3ae9012 Fix head drop logic 2026-09-22 01:11:08 +02:00
Martin Sulikowski e3508f3be6 Bump version to 2.6.0-SNAPSHOT 2026-09-22 00:55:15 +02:00
Martin Sulikowski fdd5147f18 [ci skip] Release 2.5.2 2026-09-22 00:40:18 +02:00
Martin Sulikowski 2265b66dfc Update dependencies for Minecraft 26.2 compatibility and improve README 2026-09-22 00:38:38 +02:00
Martin Sulikowski 07821a62f0 Rename crystalpvp classes and update package structure for clarity 2026-09-22 00:30:27 +02:00
Martin Sulikowski 78db24b23f Fix README for improved formatting and clarity 2026-09-22 00:27:19 +02:00
Martin Sulikowski 9ea261517a GH-180 Add GriefPrevention region support for combat restrictions (#423) 2026-09-22 00:26:08 +02:00
renovate[bot] 7363d57b0e dependency: Update dependency java-jdk to v25 (#432)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-09-22 00:19:08 +02:00
renovate[bot] 0951ef3b97 dependency: Update dependency com.github.ben-manes.caffeine:caffeine to v3.3.0 (#435)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-09-22 00:18:55 +02:00
renovate[bot] 8a48eb55bf dependency: Update Gradle to v9.7.1 (#426)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-09-20 11:54:58 +02:00
renovate[bot] dd1688af28 dependency: Update dependency org.jetbrains.kotlin:kotlin-stdlib-jdk8 to v2.4.20 (#428)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-09-20 11:54:48 +02:00
renovate[bot] fbce3c2c0b dependency: Update dependency com.modrinth.minotaur:com.modrinth.minotaur.gradle.plugin to v2.10.0 (#433)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-09-20 11:54:36 +02:00
Norbert Dejlich 2de95627d5 Fix PacketEvents version in gradle Versions declaration (#434) 2026-09-17 20:50:20 +02:00
Michał Wojtas f31a459c13 GH-334 Folia pearl fix (#334)
* Move FoliaChecker to final class - used it in FightTagController, add runFolia to runPlugin configuration

* Make it configurable

* Follow Gemini review

* Follow jakubek's review

* Remove unnecessary configuration

* Adjust to @imDMK review

* Follow Rollczi and imDMK reviews - remove synchronized and cache

Signed-off-by: Michał Wojtas <wojtas.michal90@gmail.com>

* Follow Rollczi overdue review

Signed-off-by: Michał Wojtas <wojtas.michal90@gmail.com>

---------

Signed-off-by: Michał Wojtas <wojtas.michal90@gmail.com>
2026-09-14 21:04:14 +02:00
Michał WojtasandJakub Kędziora 9fe7a1d067 GH-414 feat: add spear with lunge cooldown (#414)
* add spear cooldown and lunge support based on new api handling without building against it due to support for older versions

Signed-off-by: Michał Wojtas <wojtas.michal90@gmail.com>

* Resolve AI review. Add gif to readme

Signed-off-by: Michał Wojtas <wojtas.michal90@gmail.com>

* Optimise SpearLungeController

Signed-off-by: Michał Wojtas <wojtas.michal90@gmail.com>

* Update eternalcombat-plugin/src/main/java/com/eternalcode/combat/fight/spear/SpearLungeController.java

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

* Update eternalcombat-api/src/main/java/com/eternalcode/combat/fight/spear/SpearService.java

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

---------

Signed-off-by: Michał Wojtas <wojtas.michal90@gmail.com>
Co-authored-by: Jakub Kędziora <77227023+Jakubk15@users.noreply.github.com>
2026-09-14 21:03:14 +02:00
33 changed files with 429 additions and 55 deletions

No files matched your search

+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
- name: Set up JDK 17
uses: actions/setup-java@v5
with:
java-version: '17'
java-version: '25'
distribution: 'temurin'
- name: Setup Gradle
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
- name: Set up JDK 21
uses: actions/setup-java@v5
with:
java-version: '21'
java-version: '25'
distribution: 'temurin'
- name: Setup Gradle
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
- name: Set up JDK 21
uses: actions/setup-java@v5
with:
java-version: '21'
java-version: '25'
distribution: 'temurin'
- name: Setup Gradle
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: 17
java-version: 25
cache: 'gradle'
- name: Grant execute permission for gradlew
+11 -7
View File
@@ -25,7 +25,7 @@
## EternalCombat requires PacketEvents plugin to work.
You can download it from [here](https://modrinth.com/plugin/packetevents)
EternalCombat 2.0 has been tested on Minecraft versions **1.17.1 to 26.1.2**, but it should work seamlessly on most
EternalCombat 2.0 has been tested on Minecraft versions **1.17.1 to 26.2**, but it should work seamlessly on most
other versions too.
If you run into any compatibility issues, please let us know in
the [Issues tab](https://github.com/EternalCodeTeam/EternalCombat/issues).
@@ -45,9 +45,13 @@ fair, heart-pounding battles that keep players on their toes. Here’s a rundown
in action:
<img alt="Combat log anti logout feature" src="https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/combatlog.gif?raw=true" width="420">
- **Customize combat experience**
Add custom effects to players in combat or death. Everything should be configurable and user-friendly:
<img alt="Lightning strikes when players die" src="https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/lightning.gif?raw=true" width="420">
- **Support for newest features**
Our plugin features cooldown and delays for tridents, spears and much more. With **out of the box** configurable feats.
<img alt="Trident and spear cooldown feature" src="https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/spear.gif?raw=true" width="420">
- **Customize combat experience**
Add custom effects to players in combat or death. Everything should be configurable and user-friendly:
<img alt="Lightning strikes when players die" src="https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/lightning.gif?raw=true" width="420">
<img alt="Flare indicates players death location" src="https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/flare.gif?raw=true" width="420">
- **Spawn Protection (Configurable)**
@@ -57,7 +61,7 @@ fair, heart-pounding battles that keep players on their toes. Here’s a rundown
- **Crystal PvP support**
Engage in intense Crystal PvP battles without worrying about players logging out mid-fight! EternalCombat keeps
everyone in the game until the last anchor hit. Check it out:
everyone in the game until the last anchor hit. Check it out:
<img alt="Crystal PvP showcase" src="https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/crystals.gif?raw=true" width="420">
- **Fully Customizable Combat**
@@ -139,7 +143,7 @@ With Maven:
With Gradle:
```kts
compileOnly("com.eternalcode:eternalcombat-api:2.5.2-SNAPSHOT")
compileOnly("com.eternalcode:eternalcombat-api:2.6.0-SNAPSHOT")
```
With Maven:
@@ -149,7 +153,7 @@ With Maven:
<dependency>
<groupId>com.eternalcode</groupId>
<artifactId>eternalcombat-api</artifactId>
<version>2.5.2-SNAPSHOT</version>
<version>2.6.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
```
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 MiB

+2 -2
View File
@@ -9,9 +9,9 @@ repositories {
dependencies {
implementation("com.gradleup.shadow:com.gradleup.shadow.gradle.plugin:9.6.1")
implementation("com.modrinth.minotaur:com.modrinth.minotaur.gradle.plugin:2.9.0")
implementation("com.modrinth.minotaur:com.modrinth.minotaur.gradle.plugin:2.10.0")
implementation("io.papermc.hangar-publish-plugin:io.papermc.hangar-publish-plugin.gradle.plugin:0.1.4")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.4.10")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.4.20")
implementation("net.minecrell:plugin-yml:0.6.0")
implementation("xyz.jpenilla:run-task:3.1.0")
}
+3 -4
View File
@@ -19,17 +19,16 @@ object Versions {
const val XSERIES = "13.8.0"
const val CAFFEINE = "3.2.4"
const val CAFFEINE = "3.3.0"
const val B_STATS_BUKKIT = "3.2.1"
const val WORLD_GUARD_BUKKIT = "7.0.17"
const val PLACEHOLDER_API = "2.12.3"
const val LANDS_API = "7.25.4"
const val GRIEF_PREVENTION = "16.18.7"
const val WORLDEDIT = "3ISh7ADm" //cannot use numeric version bc of duplicated version on modrinth
const val PACKETEVENTS = "2.11.1"
const val WORLDGUARD = "7.0.15-beta-01"
const val LUCKPERMS = "5.5.17"
const val PACKETEVENTS = "2.13.0"
}
@@ -3,7 +3,7 @@ plugins {
}
group = "com.eternalcode"
version = "2.5.2-SNAPSHOT"
version = "2.6.0-SNAPSHOT"
tasks.compileJava {
options.compilerArgs = listOf("-Xlint:deprecation", "-parameters")
@@ -4,7 +4,7 @@ plugins {
}
group = "com.eternalcode"
version = "2.5.2-SNAPSHOT"
version = "2.6.0-SNAPSHOT"
java {
withSourcesJar()
@@ -13,10 +13,11 @@ tasks {
languageVersion.set(JavaLanguageVersion.of(25))
})
minecraftVersion("26.1.2")
downloadPlugins.modrinth("FastAsyncWorldEdit", "2.15.2")
downloadPlugins.modrinth("PacketEvents", "2.12.2+spigot")
downloadPlugins.modrinth("WorldGuard", "7.0.17")
minecraftVersion("26.2")
downloadPlugins.modrinth("WorldEdit", "7.4.6-beta-01")
downloadPlugins.modrinth("PacketEvents", "2.13.0+spigot")
downloadPlugins.modrinth("WorldGuard", "7.0.19")
downloadPlugins.modrinth("LuckPerms", "v5.5.53-bukkit")
downloadPlugins.modrinth("GriefPrevention", Versions.GRIEF_PREVENTION)
}
}
@@ -0,0 +1,11 @@
package com.eternalcode.combat.fight.spear;
import java.time.Duration;
import java.util.UUID;
public interface SpearService {
boolean isOnCooldown(UUID player);
void saveCooldown(UUID player);
Duration getRemainingCooldown(UUID player);
void removeCooldown(UUID player);
}
+16
View File
@@ -62,6 +62,11 @@ dependencies {
// Lands
compileOnly("com.github.angeschossen:LandsAPI:${Versions.LANDS_API}")
// GriefPrevention
compileOnly("com.github.GriefPrevention:GriefPrevention:${Versions.GRIEF_PREVENTION}") {
isTransitive = false
}
// Multification
implementation("com.eternalcode:multification-paper:${Versions.MULTIFICATION}")
implementation("com.eternalcode:multification-okaeri:${Versions.MULTIFICATION}")
@@ -96,6 +101,11 @@ paper {
load = PaperPluginDescription.RelativeLoadOrder.BEFORE
}
register("GriefPrevention") {
required = false
load = PaperPluginDescription.RelativeLoadOrder.BEFORE
}
register("PlaceholderAPI") {
required = false
load = PaperPluginDescription.RelativeLoadOrder.BEFORE
@@ -107,6 +117,12 @@ tasks {
named("generatePaperPluginDescription") {
notCompatibleWithConfigurationCache("The plugin-yml paper generator reads Task.project during execution.")
}
runPaper.folia.registerTask {
minecraftVersion("1.21.11")
downloadPlugins.modrinth("PacketEvents", "${Versions.PACKETEVENTS}+spigot")
}
}
tasks.shadowJar {
@@ -8,8 +8,8 @@ import com.eternalcode.combat.border.animation.particle.ParticleController;
import com.eternalcode.combat.bridge.BridgeService;
import com.eternalcode.combat.config.ConfigService;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.crystalpvp.EndCrystalListener;
import com.eternalcode.combat.crystalpvp.RespawnAnchorListener;
import com.eternalcode.combat.fight.crystalpvp.EndCrystalController;
import com.eternalcode.combat.fight.crystalpvp.RespawnAnchorController;
import com.eternalcode.combat.event.EventManager;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.FightManagerImpl;
@@ -49,6 +49,9 @@ import com.eternalcode.combat.fight.logout.LogoutService;
import com.eternalcode.combat.fight.pearl.PearlController;
import com.eternalcode.combat.fight.pearl.PearlService;
import com.eternalcode.combat.fight.pearl.PearlServiceImpl;
import com.eternalcode.combat.fight.spear.SpearLungeController;
import com.eternalcode.combat.fight.spear.SpearService;
import com.eternalcode.combat.fight.spear.SpearServiceImpl;
import com.eternalcode.combat.fight.tagout.FightTagOutCommand;
import com.eternalcode.combat.fight.tagout.FightTagOutController;
import com.eternalcode.combat.fight.tagout.FightTagOutService;
@@ -151,6 +154,8 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
BorderService borderService = new BorderServiceImpl(scheduler, server, regionProvider, eventManager, () -> pluginConfig.border);
KnockbackService knockbackService = new KnockbackService(pluginConfig, scheduler, regionProvider);
SpearService spearService = new SpearServiceImpl(pluginConfig.spear);
this.liteCommands = LiteBukkitFactory.builder(FALLBACK_PREFIX, this, server)
.message(LiteBukkitMessages.PLAYER_NOT_FOUND, pluginConfig.messagesSettings.playerNotFound)
.message(LiteBukkitMessages.PLAYER_ONLY, pluginConfig.messagesSettings.admin.onlyForPlayers)
@@ -202,8 +207,8 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
new BorderTriggerController(borderService, () -> pluginConfig.border, fightManager, server, scheduler),
new ParticleController(borderService, () -> pluginConfig.border.particle, scheduler, server),
new BorderBlockController(borderService, () -> pluginConfig.border.block, scheduler, server),
new EndCrystalListener(this, this.fightManager, pluginConfig),
new RespawnAnchorListener(this, this.fightManager, pluginConfig),
new EndCrystalController(this, this.fightManager, pluginConfig),
new RespawnAnchorController(this, this.fightManager, pluginConfig),
new FireworkController(this.fightManager, pluginConfig, noticeService),
new InventoryContainersBlocker(this.fightManager, pluginConfig, noticeService),
new SignEditingBlocker(this.fightManager, pluginConfig),
@@ -216,6 +221,8 @@ public final class CombatPlugin extends JavaPlugin implements EternalCombatApi {
new KnockbackMountController(noticeService, this.regionProvider, this.fightManager).register(this);
new SpearLungeController(this, fightManager, spearService, pluginConfig.spear, noticeService);
eventManager.subscribe(
PlayerDeathEvent.class,
pluginConfig.drop.dropEventPriority,
@@ -8,8 +8,6 @@ import org.bukkit.plugin.Plugin;
public final class CombatSchedulerAdapter {
private static final String FOLIA_CLASS = "io.papermc.paper.threadedregions.RegionizedServer";
private CombatSchedulerAdapter() {
throw new UnsupportedOperationException("This is an utility class and cannot be instantiated");
}
@@ -17,14 +15,12 @@ public final class CombatSchedulerAdapter {
public static MinecraftScheduler getAdaptiveScheduler(Plugin plugin) {
Logger logger = plugin.getLogger();
try {
Class.forName(FOLIA_CLASS);
if (FoliaChecker.isFolia()) {
logger.info("» Detected Folia environment. Using FoliaScheduler.");
return new FoliaSchedulerImpl(plugin);
}
catch (ClassNotFoundException exception) {
logger.info("» Detected Bukkit/Paper environment. Using BukkitScheduler.");
return new BukkitSchedulerImpl(plugin);
}
logger.info("» Detected Bukkit/Paper environment. Using BukkitScheduler.");
return new BukkitSchedulerImpl(plugin);
}
}
@@ -0,0 +1,25 @@
package com.eternalcode.combat;
public final class FoliaChecker {
private static final String FOLIA_CLASS = "io.papermc.paper.threadedregions.RegionizedServer";
private static final boolean IS_FOLIA = detectFoliaEnvironment();
private FoliaChecker() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
public static boolean isFolia() {
return IS_FOLIA;
}
private static boolean detectFoliaEnvironment() {
try {
Class.forName(FOLIA_CLASS);
return true;
}
catch (ClassNotFoundException exception) {
return false;
}
}
}
@@ -7,6 +7,7 @@ import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.region.CompositeRegionProvider;
import com.eternalcode.combat.region.bukkit.DefaultRegionProvider;
import com.eternalcode.combat.region.RegionProvider;
import com.eternalcode.combat.region.griefprevention.GriefPreventionRegionProvider;
import com.eternalcode.combat.region.worldguard.WorldGuardRegionProvider;
import java.util.ArrayList;
import java.util.List;
@@ -55,6 +56,12 @@ public class BridgeService {
() -> this.logger.warning("WorldGuard not found; skipping WorldGuardRegionProvider.")
);
this.initialize(
"GriefPrevention",
() -> providers.add(new GriefPreventionRegionProvider(this.config)),
() -> this.logger.warning("GriefPrevention not found; skipping GriefPreventionRegionProvider.")
);
if (providers.isEmpty()) {
providers.add(new DefaultRegionProvider(this.config.regions.restrictedRegionRadius));
this.logger.warning("No region plugin found; using DefaultRegionProvider.");
@@ -6,6 +6,7 @@ import com.eternalcode.combat.fight.drop.DropSettings;
import com.eternalcode.combat.fight.effect.FightEffectSettings;
import com.eternalcode.combat.fight.knockback.KnockbackSettings;
import com.eternalcode.combat.fight.pearl.PearlSettings;
import com.eternalcode.combat.fight.spear.SpearSettings;
import com.eternalcode.combat.fight.trident.TridentSettings;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
@@ -44,6 +45,13 @@ public class PluginConfig extends OkaeriConfig {
})
public TridentSettings trident = new TridentSettings();
@Comment({
" ",
"# Settings related to Spears with lunge",
"# Set cooldown for spear lunging"
})
public SpearSettings spear = new SpearSettings();
@Comment({
" ",
"# Custom effects applied during combat.",
@@ -13,7 +13,8 @@ public class RegionSettings extends OkaeriConfig {
public List<String> blockedRegions = Collections.singletonList("your_region");
@Comment({
"# Prevent players from entering regions where PVP is disabled by WorldGuard.",
"# Prevent players from entering regions where PVP is disabled by the region plugin",
"# (WorldGuard PVP flag, GriefPrevention no-combat-in-claims settings).",
"# Set to 'true' to enforce this restriction, or 'false' to allow PVP in all regions."
})
public boolean preventPvpInRegions = true;
@@ -1,5 +1,6 @@
package com.eternalcode.combat.fight.controller;
import com.eternalcode.combat.FoliaChecker;
import com.eternalcode.combat.WhitelistBlacklistMode;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
@@ -55,6 +56,16 @@ public class FightTagController implements Listener {
return;
}
UUID targetId = attackedPlayerByPerson.getUniqueId();
UUID attackerId = attacker.getUniqueId();
if (
targetId.equals(attackerId)
&& FoliaChecker.isFolia()
) {
return;
}
if (this.config.combat.disableFlying) {
if (attackedPlayerByPerson.isFlying()) {
attackedPlayerByPerson.setFlying(false);
@@ -68,11 +79,9 @@ public class FightTagController implements Listener {
}
Duration combatTime = this.config.settings.combatTimerDuration;
UUID attackedUniqueId = attackedPlayerByPerson.getUniqueId();
UUID attackerUniqueId = attacker.getUniqueId();
this.fightManager.tag(attackedUniqueId, combatTime, CauseOfTag.PLAYER, attackerUniqueId);
this.fightManager.tag(attackerUniqueId, combatTime, CauseOfTag.PLAYER, attackedUniqueId);
this.fightManager.tag(targetId, combatTime, CauseOfTag.PLAYER, attackerId);
this.fightManager.tag(attackerId, combatTime, CauseOfTag.PLAYER, targetId);
}
@@ -1,6 +1,6 @@
package com.eternalcode.combat.fight.controller;
import com.eternalcode.combat.crystalpvp.CrystalPvpConstants;
import com.eternalcode.combat.fight.crystalpvp.CrystalPvpConstants;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.fight.event.CauseOfUnTag;
import com.eternalcode.combat.config.implementation.PluginConfig;
@@ -1,4 +1,4 @@
package com.eternalcode.combat.crystalpvp;
package com.eternalcode.combat.fight.crystalpvp;
import java.util.Optional;
import java.util.UUID;
@@ -1,4 +1,4 @@
package com.eternalcode.combat.crystalpvp;
package com.eternalcode.combat.fight.crystalpvp;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
@@ -1,4 +1,4 @@
package com.eternalcode.combat.crystalpvp;
package com.eternalcode.combat.fight.crystalpvp;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
@@ -12,15 +12,15 @@ import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.plugin.Plugin;
import static com.eternalcode.combat.crystalpvp.CrystalPvpConstants.CRYSTAL_METADATA;
import static com.eternalcode.combat.fight.crystalpvp.CrystalPvpConstants.CRYSTAL_METADATA;
public class EndCrystalListener implements Listener {
public class EndCrystalController implements Listener {
private final Plugin plugin;
private final FightManager fightManager;
private final PluginConfig pluginConfig;
public EndCrystalListener(Plugin plugin, FightManager fightManager, PluginConfig pluginConfig) {
public EndCrystalController(Plugin plugin, FightManager fightManager, PluginConfig pluginConfig) {
this.plugin = plugin;
this.fightManager = fightManager;
this.pluginConfig = pluginConfig;
@@ -1,4 +1,4 @@
package com.eternalcode.combat.crystalpvp;
package com.eternalcode.combat.fight.crystalpvp;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.FightManager;
@@ -16,16 +16,16 @@ import org.bukkit.event.entity.EntityDamageByBlockEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;
import static com.eternalcode.combat.crystalpvp.CrystalPvpConstants.ANCHOR_METADATA;
import static com.eternalcode.combat.fight.crystalpvp.CrystalPvpConstants.ANCHOR_METADATA;
public class RespawnAnchorListener implements Listener {
public class RespawnAnchorController implements Listener {
private final Plugin plugin;
private final FightManager fightManager;
private final PluginConfig pluginConfig;
public RespawnAnchorListener(Plugin plugin, FightManager fightManager, PluginConfig pluginConfig) {
public RespawnAnchorController(Plugin plugin, FightManager fightManager, PluginConfig pluginConfig) {
this.plugin = plugin;
this.fightManager = fightManager;
this.pluginConfig = pluginConfig;
@@ -85,8 +85,8 @@ public class DropController implements DynamicListener<PlayerDeathEvent> {
return false;
}
if (this.dropSettings.headDropOnlyInCombat && inCombat) {
return true;
if (this.dropSettings.headDropOnlyInCombat) {
return inCombat;
}
if (this.dropSettings.headDropChance <= 0.0) {
@@ -0,0 +1,85 @@
package com.eternalcode.combat.fight.spear;
import com.eternalcode.combat.fight.FightManager;
import com.eternalcode.combat.notification.NoticeService;
import com.eternalcode.combat.util.DurationUtil;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.Plugin;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.UUID;
public class SpearLungeController implements Listener {
private final SpearService spearService;
public SpearLungeController(Plugin plugin, FightManager fightManager, SpearService spearService, SpearSettings settings, NoticeService noticeService) {
this.spearService = spearService;
try {
Class<? extends Event> lungeEventClass = Class.forName("io.papermc.paper.event.entity.EntityLungeEvent").asSubclass(Event.class);
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle getEntityHandle = lookup.findVirtual(lungeEventClass, "getEntity", MethodType.methodType(Entity.class));
MethodHandle setCancelledHandle = lookup.findVirtual(lungeEventClass, "setCancelled", MethodType.methodType(void.class, boolean.class));
Bukkit.getPluginManager().registerEvent(
lungeEventClass,
this,
EventPriority.NORMAL,
(listener, event) -> {
if (!settings.lungeCooldown || !lungeEventClass.isInstance(event)) {
return;
}
try {
Entity entity = (Entity) getEntityHandle.invoke(event);
if (!(entity instanceof Player player)) {
return;
}
UUID uuid = player.getUniqueId();
if (settings.onlyForFight && !fightManager.isInCombat(uuid)) {
return;
}
if (this.spearService.isOnCooldown(uuid)) {
setCancelledHandle.invoke(event, true);
noticeService.create()
.player(uuid)
.notice(settings.lungeOnCooldown)
.placeholder("{TIME}", DurationUtil.format(spearService.getRemainingCooldown(uuid), !settings.useMillis))
.send();
return;
}
spearService.saveCooldown(uuid);
} catch (Throwable e) {
plugin.getLogger().warning("Failed to handle EntityLungeEvent: " + e.getMessage());
}
},
plugin
);
} catch (ClassNotFoundException e) {
// Silently ignore: Server version is too old to have EntityLungeEvent
} catch (Throwable e) {
plugin.getLogger().warning("Failed to hook EntityLungeEvent: " + e.getMessage());
}
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
this.spearService.removeCooldown(event.getPlayer().getUniqueId());
}
}
@@ -0,0 +1,49 @@
package com.eternalcode.combat.fight.spear;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class SpearServiceImpl implements SpearService {
private final SpearSettings settings;
private final Map<UUID, Instant> cooldowns = new ConcurrentHashMap<>();
public SpearServiceImpl(SpearSettings settings) {
this.settings = settings;
}
@Override
public boolean isOnCooldown(UUID uuid) {
return !this.getRemainingCooldown(uuid).isZero();
}
@Override
public void saveCooldown(UUID uuid) {
this.cooldowns.put(uuid, Instant.now().plus(this.settings.lungeCooldownDuration));
}
@Override
public Duration getRemainingCooldown(UUID uuid) {
Instant expiration = this.cooldowns.get(uuid);
if (expiration == null) {
return Duration.ZERO;
}
Instant now = Instant.now();
if (now.isAfter(expiration)) {
this.cooldowns.remove(uuid);
return Duration.ZERO;
}
return Duration.between(now, expiration);
}
@Override
public void removeCooldown(UUID uuid) {
this.cooldowns.remove(uuid);
}
}
@@ -0,0 +1,29 @@
package com.eternalcode.combat.fight.spear;
import com.eternalcode.multification.notice.Notice;
import eu.okaeri.configs.OkaeriConfig;
import eu.okaeri.configs.annotation.Comment;
import java.time.Duration;
public class SpearSettings extends OkaeriConfig {
@Comment("# Should spears have lunge cooldown or delay between uses? True for cooldown, False for vanilla mechanics")
public boolean lungeCooldown = false;
@Comment("# Should cooldown be applied only during fights")
public boolean onlyForFight = true;
@Comment("# Duration of cooldown")
public Duration lungeCooldownDuration = Duration.ofSeconds(5);
@Comment("# Should milliseconds be used for last second of cooldown for more precise time")
public boolean useMillis = true;
@Comment({
"# Notice sent to the players that try to use spears before cooldown ends.",
"# Placeholder: {TIME} - time left of cooldown"
})
public Notice lungeOnCooldown = Notice.builder().actionBar("<dark_red>Spear cannot be used for next <red>{TIME}</red></dark_red>")
.build();
}
@@ -0,0 +1,34 @@
package com.eternalcode.combat.region.griefprevention;
import com.eternalcode.combat.region.Point;
import com.eternalcode.combat.region.Region;
import me.ryanhamshire.GriefPrevention.Claim;
import org.bukkit.Location;
import org.bukkit.World;
record GriefPreventionRegion(World world, Claim claim) implements Region {
@Override
public Point getCenter() {
Location min = this.claim.getLesserBoundaryCorner();
Location max = this.claim.getGreaterBoundaryCorner();
double x = (min.getX() + max.getX()) / 2.0;
double z = (min.getZ() + max.getZ()) / 2.0;
return new Point(this.world, x, z);
}
@Override
public Location getMin() {
Location min = this.claim.getLesserBoundaryCorner();
return new Location(this.world, min.getBlockX(), this.world.getMinHeight(), min.getBlockZ());
}
@Override
public Location getMax() {
Location max = this.claim.getGreaterBoundaryCorner();
return new Location(this.world, max.getBlockX(), this.world.getMaxHeight() - 1, max.getBlockZ());
}
}
@@ -0,0 +1,93 @@
package com.eternalcode.combat.region.griefprevention;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.region.Region;
import com.eternalcode.combat.region.RegionProvider;
import java.util.ArrayList;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.Optional;
import java.util.TreeSet;
import me.ryanhamshire.GriefPrevention.Claim;
import me.ryanhamshire.GriefPrevention.GriefPrevention;
import org.bukkit.Location;
import org.bukkit.World;
public class GriefPreventionRegionProvider implements RegionProvider {
private final TreeSet<String> claims = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
private final PluginConfig pluginConfig;
public GriefPreventionRegionProvider(PluginConfig pluginConfig) {
this.claims.addAll(pluginConfig.regions.blockedRegions);
this.pluginConfig = pluginConfig;
}
@Override
public Optional<Region> getRegion(Location location) {
Claim claim = GriefPrevention.instance.dataStore.getClaimAt(location, true, null);
if (claim == null || !this.isCombatClaim(claim)) {
return Optional.empty();
}
return Optional.of(new GriefPreventionRegion(location.getWorld(), claim));
}
@Override
public Collection<Region> getRegions(World world) {
List<Region> regions = new ArrayList<>();
for (Claim claim : this.snapshotClaims()) {
if (!world.equals(claim.getLesserBoundaryCorner().getWorld())) {
continue;
}
if (this.isCombatClaim(claim)) {
regions.add(new GriefPreventionRegion(world, claim));
}
}
return regions;
}
private boolean isCombatClaim(Claim claim) {
if (this.claims.contains(String.valueOf(claim.getID()))) {
return true;
}
if (!this.pluginConfig.regions.preventPvpInRegions) {
return false;
}
World world = claim.getLesserBoundaryCorner().getWorld();
GriefPrevention griefPrevention = GriefPrevention.instance;
if (world == null || !griefPrevention.pvpRulesApply(world)) {
return false;
}
if (!claim.isAdminClaim()) {
return griefPrevention.config_pvp_noCombatInPlayerLandClaims;
}
if (claim.parent == null) {
return griefPrevention.config_pvp_noCombatInAdminLandClaims;
}
return griefPrevention.config_pvp_noCombatInAdminSubdivisions;
}
// GriefPrevention returns a live view of its claim list, and the border index reads regions asynchronously.
// A failed snapshot is retried by the next border index refresh, so an empty result is safe here.
private List<Claim> snapshotClaims() {
try {
return List.copyOf(GriefPrevention.instance.dataStore.getClaims());
}
catch (ConcurrentModificationException exception) {
return List.of();
}
}
}
+1 -1
View File
@@ -4,4 +4,4 @@ org.gradle.configuration-cache=true
org.gradle.configuration-cache.parallel=true
# Platforms
paperVersion=1.19.3,1.19.4,1.20,1.20.1,1.20.2,1.20.4,1.20.5,1.20.6,1.21,1.21.1,1.21.3,1.21.4,1.21.5,1.21.6,1.21.7,1.21.8,1.21.9,1.21.10,1.21.11,26.1.1,26.1.2
paperVersion=1.19.3,1.19.4,1.20,1.20.1,1.20.2,1.20.4,1.20.5,1.20.6,1.21,1.21.1,1.21.3,1.21.4,1.21.5,1.21.6,1.21.7,1.21.8,1.21.9,1.21.10,1.21.11,26.1.1,26.1.2,26.2
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500