Release EternalCombat-OG

This commit is contained in:
NotAlexNoyle committed 2026-05-27 07:33:11 -04:00
1 parent cc85e0ad0c
commit 1f3b4a7215
9 files changed
+212 -317

No files matched your search

+22 -160
View File
@@ -1,168 +1,30 @@
<div align="center">
# EternalCombat-OG
![Readme banner](https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/readme-banner.png?raw=true)
A [TrueOG Network](https://true-og.net) fork of [EternalCombat](https://github.com/EternalCodeTeam/EternalCombat)
by the EternalCode Team — a combat-tag / combat-logging plugin for Paper.
[![Available on SpigotMC](https://raw.githubusercontent.com/vLuckyyy/badges/main/available-on-spigotmc.svg)](https://www.spigotmc.org/resources/eternalcombat-%E2%9C%94%EF%B8%8F-enchance-your-combat-system-with-eternalcombat.109056/)
[![Available on Modrinth](https://raw.githubusercontent.com/vLuckyyy/badges/main/avaiable-on-modrinth.svg)](https://modrinth.com/plugin/eternalcombat)
[![Available on Hangar](https://raw.githubusercontent.com/vLuckyyy/badges/main/avaiable-on-hangar.svg)](https://hangar.papermc.io/EternalCodeTeam/eternalcombat)
## License & attribution
[![Chat on Discord](https://raw.githubusercontent.com/vLuckyyy/badges/main//chat-with-us-on-discord.svg)](https://discord.com/invite/FQ7jmGBd6c)
[![Read the Docs](https://raw.githubusercontent.com/vLuckyyy/badges/main/read-the-documentation.svg)](https://docs.eternalcode.pl/eternalcombat/introduction)
[![Available on BStats](https://raw.githubusercontent.com/vLuckyyy/badges/main/available-on-bstats.svg)](https://bstats.org/plugin/bukkit/EternalCombat/17803)
EternalCombat-OG is a derivative work of **EternalCombat** by the **EternalCode Team**, distributed
under the **Apache License 2.0**. The original copyright and license are retained; see the
[`LICENSE`](LICENSE) file. Upstream project: https://github.com/EternalCodeTeam/EternalCombat
[Report Bug](https://github.com/EternalCodeTeam/EternalCombat/issues) • [Request Feature](https://github.com/EternalCodeTeam/EternalCombat/issues) • [Join Discord](https://discord.com/invite/FQ7jmGBd6c)
## Changes from upstream
<br>
This fork diverges from upstream EternalCombat in the following ways:
<a href="https://ko-fi.com/eternalcodeteam">
<img src="https://github.com/intergrav/devins-badges/blob/v3/assets/cozy/donate/kofi-plural-alt_64h.png?raw=true" height="64" alt="Support us on Ko-fi">
</a>
- Renamed the project and plugin to `EternalCombat-OG`.
- Fixed combat knockback flinging players across the map or into the sky near safe zones.
- Players in combat can move between the warzone and wilderness, but not into safe areas.
- Mounted players are pushed along with their mount instead of being thrown off.
- Closed gaps that let combat-tagged players slip into shops and the market.
- Removed chat gradients.
- Added a command whitelist for "The Herobrine" minigames.
- Dropped Lands support.
- Removed the upstream GitHub Actions, plus build and dependency cleanup.
</div>
## Requirements
### Information
## 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 1.21.11**, 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).
The plugin requires **Java 21 or later**, so
ensure your server is ready.
Ready for action?
Install EternalCombat and dive in now!
### How Does EternalCombat Work?
Take your server’s PvP experience to epic heights with **EternalCombat 2.0**! Our robust combat logging system ensures
fair, heart-pounding battles that keep players on their toes. Here’s a rundown of what makes it stand out:
- **Combat Logging**
No more dodging fights by logging out! Once players are in combat, they’re committed until the showdown ends. Watch it
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">
<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)**
Stop players from fleeing to safety! Block access to spawn or safe zones during combat – tweak it to fit your server’s
rules. See how it works:
<img alt="Border around protected region" src="https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/border.gif?raw=true" width="420">
- **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:
<img alt="Crystal PvP showcase" src="https://github.com/EternalCodeTeam/EternalCombat/blob/master/assets/crystals.gif?raw=true" width="420">
- **Fully Customizable Combat**
Tailor the combat experience to your liking with a ton of options! From disabling elytra to setting drop rates for
defeated players, you’re in control. Here’s what you can tweak:
| Feature | Description |
|----------------------|-----------------------------------------------------------------|
| Elytra & Inventory | Disable elytra or inventory access during combat. |
| Commands | Whitelist or blacklist specific commands in combat. |
| Damage & Projectiles | Customize damage causes and projectile tagging settings. |
| Ender Pearls | Add cooldowns to pearl usage in fights. |
| Block Placement | Enable or disable block placement during combat. |
| Drop Rates | Set a percentage of items dropped by defeated players. |
| Temporary Effects | Apply special effects to players in combat for added intensity. |
| And More! | Dive into the config for even more fine-tuning options! |
Curious for more? Check out our quick and exciting YouTube presentation [here](https://youtu.be/5pELO5B0Hhk) to see
EternalCombat in full swing and learn why it’s a game-changer for your server!
### Permissions for EternalCombat
Control who can use EternalCombat’s powerful features with these permissions:
| Permission | Description |
|--------------------------------|-------------------------------------------------------------------------------------------------------|
| `eternalcombat.status` | Check a player’s combat status with `/combatlog status <player>`. |
| `eternalcombat.tag` | Start a fight between players using `/combatlog tag <player> [player2]`. |
| `eternalcombat.untag` | Remove a player from combat with `/combatlog untag <player>`. |
| `eternalcombat.reload` | Reload the plugin with `/combatlog reload`. |
| `eternalcombat.receiveupdates` | Receive notifications about new plugin versions on join. |
| `eternalcombat.bypass` | When exclude `excludeAdminsFromCombat` is set to `true` users with this permission will not be tagged |
## PlaceholderAPI
EternalCombat 2.0 fully supports **PlaceholderAPI**, letting you integrate placeholders into other compatible plugins.
Follow the [PlaceholderAPI instructions](https://wiki.placeholderapi.com/users/) to get started.
Here are the available
placeholders:
| Placeholder | Description |
|-------------------------------------|---------------------------------------------------------------|
| `%eternalcombat_opponent%` | Returns the name of the player you’re fighting. |
| `%eternalcombat_opponent_health%` | Returns the opponent’s health in `00.00` format. |
| `%eternalcombat_remaining_seconds%` | Returns seconds remaining until the player exits combat. |
| `%eternalcombat_remaining_millis%` | Returns milliseconds remaining until the player exits combat. |
If a player isn’t in combat, placeholders return an empty string.
If combat wasn’t triggered by another player,
opponent-related placeholders will also return empty.
### Developer API
#### 1. Add repository:
With Gradle:
```kts
maven {
url = uri("https://repo.eternalcode.pl/releases")
}
```
With Maven:
```xml
<repository>
<id>eternalcode-reposilite-releases</id>
<name>EternalCode Repository</name>
<url>https://repo.eternalcode.pl/releases</url>
</repository>
```
#### 2. Add dependency:
With Gradle:
```kts
compileOnly("com.eternalcode:eternalcombat-api:2.5.0-SNAPSHOT")
```
With Maven:
```xml
<dependency>
<groupId>com.eternalcode</groupId>
<artifactId>eternalcombat-api</artifactId>
<version>2.5.0-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
```
### Contributing
We’d love your help to make EternalCombat even better!
Check out our [contributing guide](https://github.com/EternalCodeTeam/EternalCombat/blob/master/.github/CONTRIBUTING.md) for
details on how to get involved and our [code of conduct](https://github.com/EternalCodeTeam/EternalCombat/blob/master/.github/CODE_OF_CONDUCT.md).
### Reporting Issues
Found a bug?
Report it in the [Issues tab](https://github.com/eternalcodeteam/eternalcombat/issues).
Please include as much detail as possible, like your Minecraft and plugin
versions, along with any error messages or logs.
Ready to transform your server’s combat experience?
Download EternalCombat 2.0 now and let the battles begin!
- Paper (Java 17+ toolchain)
- [PacketEvents](https://modrinth.com/plugin/packetevents) (required)
- WorldGuard (optional, for region-based combat restrictions)
+1 -1
View File
@@ -11,7 +11,7 @@ val pluginProject = project(":eternalcombat-plugin")
val pluginJar by tasks.registering(Copy::class) {
dependsOn(":eternalcombat-plugin:shadowJar")
from(pluginProject.provider {
pluginProject.layout.buildDirectory.file("libs/EternalCombat-${pluginProject.version}.jar").get().asFile
pluginProject.layout.buildDirectory.file("libs/EternalCombat-OG-${pluginProject.version}.jar").get().asFile
})
into(layout.buildDirectory.dir("libs"))
}
+4 -5
View File
@@ -155,13 +155,12 @@ dependencies {
}
bukkit {
name = "EternalCombat"
name = "EternalCombat-OG"
main = "com.eternalcode.combat.CombatPlugin"
version = project.version.toString()
apiVersion = "1.19"
authors = listOf("EternalCodeTeam")
prefix = "EternalCombat"
name = "EternalCombat"
authors = listOf("EternalCodeTeam", "true-og")
prefix = "EternalCombat-OG"
load = BukkitPluginDescription.PluginLoadOrder.POSTWORLD
softDepend = listOf(
"WorldGuard"
@@ -185,7 +184,7 @@ tasks {
}
tasks.shadowJar {
archiveFileName.set("EternalCombat-${project.version}.jar")
archiveFileName.set("EternalCombat-OG-${project.version}.jar")
exclude(
"org/intellij/lang/annotations/**",
@@ -1,82 +0,0 @@
package com.eternalcode.combat.fight.knockback;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Set;
import java.util.TreeMap;
import org.bukkit.Location;
class KnockbackOutsideRegionGenerator {
static Location generate(Point2D min, Point2D max, Location playerLocation) {
NavigableMap<Double, List<Point2D>> points = generatePoints(min, max, Point2D.from(playerLocation));
NavigableMap<Double, Double> distances = new TreeMap<>();
double totalWeight = 0;
Double maxDistance = points.lastKey();
for (double distance : points.keySet()) {
double weight = createWeight(distance, maxDistance);
distances.put(distance, weight);
totalWeight += weight;
}
double rand = Math.random() * totalWeight;
double cumulativeWeight = 0;
for (Map.Entry<Double, Double> entry : distances.entrySet()) {
double distance = entry.getKey();
double weight = entry.getValue();
cumulativeWeight += weight;
if (rand <= cumulativeWeight) {
return getRandom(points.get(distance))
.toLocation(playerLocation);
}
}
return getRandom(points.firstEntry().getValue())
.toLocation(playerLocation);
}
private static Point2D getRandom(List<Point2D> points) {
return points.get((int) (Math.random() * points.size()));
}
private static double createWeight(double distance, double maxDistance) {
double last = Math.log(maxDistance);
double weight = last - Math.log(distance);
return Math.pow(weight, 10);
}
private static NavigableMap<Double, List<Point2D>> generatePoints(Point2D min, Point2D max, Point2D location) {
Set<Point2D> points = new HashSet<>();
for (int i = min.x() - 1; i <= max.x() + 1; i++) {
points.add(new Point2D(i, min.z() - 1));
points.add(new Point2D(i, max.z() + 1));
}
for (int i = min.z(); i <= max.z(); i++) {
points.add(new Point2D(min.x() - 1, i));
points.add(new Point2D(max.x() + 1, i));
}
TreeMap<Double, List<Point2D>> result = new TreeMap<>();
for (Point2D point : points) {
double distance = distance(location, point);
result.computeIfAbsent(distance, k -> new ArrayList<>()).add(point);
}
return result;
}
private static double distance(Point2D p1, Point2D p2) {
return Math.sqrt(Math.pow(p2.x() - p1.x(), 2) + Math.pow(p2.z() - p1.z(), 2));
}
}
@@ -17,6 +17,7 @@ import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.vehicle.VehicleMoveEvent;
import org.bukkit.util.Vector;
import org.spigotmc.event.entity.EntityMountEvent;
public class KnockbackRegionController implements Listener {
@@ -55,16 +56,22 @@ public class KnockbackRegionController implements Listener {
if (xTo != xFrom || yTo != yFrom || zTo != zFrom) {
Optional<Region> regionOptional = this.regionProvider.getRegion(locationTo);
if (regionOptional.isEmpty()) {
// Destination is not a safe zone (wilderness or warzone) -> allow the move.
return;
}
Region region = regionOptional.get();
if (region.contains(locationFrom)) {
if (this.regionProvider.isInRegion(locationFrom)) {
// Origin is itself a safe zone (player was already inside it, e.g. tagged there)
// -> eject them out through the nearest wall.
this.knockbackService.knockback(region, player);
this.knockbackService.forceKnockbackLater(player, region);
} else {
// Origin is a legal area (wilderness or warzone) and they tried to step into a safe
// zone -> cancel the move and bounce them straight back the way they came.
event.setCancelled(true);
this.knockbackService.knockbackLater(region, player, Duration.ofMillis(50));
Vector back = locationFrom.toVector().subtract(locationTo.toVector());
this.knockbackService.knockbackDirectionLater(player, back, Duration.ofMillis(50));
}
this.noticeService.create()
@@ -118,11 +125,16 @@ public class KnockbackRegionController implements Listener {
}
Region region = regionOptional.get();
if (region.contains(locationFrom)) {
if (this.regionProvider.isInRegion(locationFrom)) {
this.knockbackService.knockback(region, player);
this.knockbackService.forceKnockbackLater(player, region);
} else {
this.knockbackService.knockbackLater(region, player, Duration.ofMillis(50));
// VehicleMoveEvent cannot be cancelled, so bounce the mount back the way it came and
// schedule an eject as a fallback if it is still inside after the delay. The vehicle
// is moved with its rider rather than dismounting.
Vector back = locationFrom.toVector().subtract(locationTo.toVector());
this.knockbackService.knockbackDirectionLater(player, back, Duration.ofMillis(50));
this.knockbackService.forceKnockbackLater(player, region);
}
this.noticeService.create()
@@ -1,7 +1,6 @@
package com.eternalcode.combat.fight.knockback;
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.region.Point;
import com.eternalcode.combat.region.Region;
import com.eternalcode.combat.region.RegionProvider;
import com.eternalcode.commons.bukkit.scheduler.MinecraftScheduler;
@@ -9,15 +8,18 @@ import io.papermc.lib.PaperLib;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause;
import org.bukkit.util.Vector;
public final class KnockbackService {
// Pushed just past the nearest region wall so the player lands outside the cuboid.
private static final double EJECT_MARGIN = 1.5;
private final PluginConfig config;
private final MinecraftScheduler scheduler;
private final RegionProvider regionProvider;
@@ -34,6 +36,25 @@ public final class KnockbackService {
this.scheduler.runLater(() -> this.knockback(region, player), duration);
}
// Push the player along the given horizontal direction. Used when a move into a safe zone is
// cancelled: the direction is (from - to), i.e. straight back the way the player came, so they
// bounce back into the legal area (wilderness or warzone) regardless of the safe region's shape
// or size. Applied a tick later so it survives the move cancellation.
public void knockbackDirectionLater(Player player, Vector horizontalDirection, Duration duration) {
Vector direction = new Vector(horizontalDirection.getX(), 0, horizontalDirection.getZ());
if (direction.lengthSquared() < 1.0E-6) {
return;
}
double multiplier = this.config.knockback.multiplier;
Vector velocity = direction.normalize().multiply(multiplier);
this.scheduler.runLater(
() -> applyVelocity(player, new Vector(velocity.getX(), 0.5, velocity.getZ())),
duration
);
}
public void forceKnockbackLater(Player player, Region region) {
if (insideRegion.containsKey(player.getUniqueId())) {
return;
@@ -49,39 +70,113 @@ public final class KnockbackService {
return;
}
if (player.isInsideVehicle()) {
player.leaveVehicle();
// Eject through the nearest wall, keeping the player's own Y. No highest-block lookup
// (which launched players onto walls/treetops) and no bounding-box perimeter teleport
// (which threw them dozens of blocks across large regions). A mounted player is moved
// by teleporting their vehicle, so the mount travels with them instead of dismounting.
Location target = ejectLocation(region, playerLocation);
Entity vehicle = topVehicle(player);
if (vehicle != null) {
PaperLib.teleportAsync(vehicle, target, TeleportCause.PLUGIN);
} else {
PaperLib.teleportAsync(player, target, TeleportCause.PLUGIN);
}
Location location = generate(playerLocation, Point2D.from(region.getMin()), Point2D.from(region.getMax()));
PaperLib.teleportAsync(player, location, TeleportCause.PLUGIN);
}, this.config.knockback.forceDelay);
}
private Location generate(Location playerLocation, Point2D minX, Point2D maxX) {
Location location = KnockbackOutsideRegionGenerator.generate(minX, maxX, playerLocation);
Optional<Region> otherRegion = regionProvider.getRegion(location);
if (otherRegion.isPresent()) {
Region region = otherRegion.get();
return generate(playerLocation, minX.min(region.getMin()), maxX.max(region.getMax()));
}
return location;
}
public void knockback(Region region, Player player) {
if (player.isInsideVehicle()) {
player.leaveVehicle();
Vector outward = outwardDirection(region, player.getLocation());
double multiplier = this.config.knockback.multiplier;
applyVelocity(player, new Vector(outward.getX() * multiplier, 0.5, outward.getZ() * multiplier));
}
// Apply velocity to the player, or to the vehicle they are riding so the mount (and its rider)
// move together. Never dismounts the player.
private static void applyVelocity(Player player, Vector velocity) {
Entity vehicle = topVehicle(player);
if (vehicle != null) {
vehicle.setVelocity(velocity);
} else {
player.setVelocity(velocity);
}
}
// The root of the player's mount stack (the entity actually being moved), or null when the
// player is on foot.
private static Entity topVehicle(Player player) {
Entity vehicle = player.getVehicle();
if (vehicle == null) {
return null;
}
while (vehicle.getVehicle() != null) {
vehicle = vehicle.getVehicle();
}
return vehicle;
}
// Unit vector pointing straight out of the closest vertical face of the region. For a player
// crossing a wall this is the wall they crossed, so the push always sends them back the way
// they came regardless of how large the region is.
private static Vector outwardDirection(Region region, Location location) {
Face face = nearestFace(region, location);
return switch (face.side) {
case WEST -> new Vector(-1, 0, 0);
case EAST -> new Vector(1, 0, 0);
case NORTH -> new Vector(0, 0, -1);
case SOUTH -> new Vector(0, 0, 1);
};
}
private static Location ejectLocation(Region region, Location location) {
Face face = nearestFace(region, location);
double minX = region.getMin().getX();
double maxX = region.getMax().getX() + 1;
double minZ = region.getMin().getZ();
double maxZ = region.getMax().getZ() + 1;
double x = location.getX();
double z = location.getZ();
switch (face.side) {
case WEST -> x = minX - EJECT_MARGIN;
case EAST -> x = maxX + EJECT_MARGIN;
case NORTH -> z = minZ - EJECT_MARGIN;
case SOUTH -> z = maxZ + EJECT_MARGIN;
}
Point point = region.getCenter();
Location subtract = player.getLocation().subtract(point.x(), 0, point.z());
Vector knockbackVector = new Vector(subtract.getX(), 0, subtract.getZ()).normalize();
double multiplier = this.config.knockback.multiplier;
Vector configuredVector = new Vector(multiplier, 0.5, multiplier);
player.setVelocity(knockbackVector.multiply(configuredVector));
return new Location(location.getWorld(), x, location.getY(), z, location.getYaw(), location.getPitch());
}
private static Face nearestFace(Region region, Location location) {
double minX = region.getMin().getX();
double maxX = region.getMax().getX() + 1;
double minZ = region.getMin().getZ();
double maxZ = region.getMax().getZ() + 1;
double px = location.getX();
double pz = location.getZ();
double west = px - minX;
double east = maxX - px;
double north = pz - minZ;
double south = maxZ - pz;
double nearest = Math.min(Math.min(west, east), Math.min(north, south));
if (nearest == west) {
return new Face(Side.WEST, west);
}
if (nearest == east) {
return new Face(Side.EAST, east);
}
if (nearest == north) {
return new Face(Side.NORTH, north);
}
return new Face(Side.SOUTH, south);
}
private enum Side { WEST, EAST, NORTH, SOUTH }
private record Face(Side side, double distance) {}
}
@@ -1,27 +0,0 @@
package com.eternalcode.combat.fight.knockback;
import org.bukkit.Location;
import org.bukkit.World;
record Point2D(int x, int z) {
Location toLocation(Location location) {
World world = location.getWorld();
int y = world.getHighestBlockYAt(x, z) + 1;
return new Location(world, x, y, z, location.getYaw(), location.getPitch());
}
static Point2D from(Location location) {
return new Point2D(location.getBlockX(), location.getBlockZ());
}
public Point2D min(Location min) {
return new Point2D(Math.min(this.x, min.getBlockX()), Math.min(this.z, min.getBlockZ()));
}
public Point2D max(Location max) {
return new Point2D(Math.max(this.x, max.getBlockX()), Math.max(this.z, max.getBlockZ()));
}
}
@@ -38,17 +38,24 @@ public class WorldGuardRegionProvider implements RegionProvider {
RegionQuery regionQuery = this.regionContainer.createQuery();
ApplicableRegionSet applicableRegions = regionQuery.getApplicableRegions(BukkitAdapter.adapt(location));
ProtectedRegion highestPriorityRegion = this.highestPriorityRegion(applicableRegions);
if (highestPriorityRegion == null) {
if (applicableRegions.size() == 0) {
return Optional.empty();
}
if (this.isCombatRegion(highestPriorityRegion)) {
return Optional.of(new WorldGuardRegion(location.getWorld(), highestPriorityRegion));
if (!this.isRestricted(applicableRegions)) {
return Optional.empty();
}
return Optional.empty();
// Eject relative to the innermost (highest-priority) region the player is standing in, not
// whichever region happens to set the PVP flag. The innermost region is the tightest box, so
// the knockback/teleport distance stays local instead of being measured against a giant
// world-spanning spawn region.
ProtectedRegion bounds = this.innermostRegion(applicableRegions);
if (bounds == null) {
return Optional.empty();
}
return Optional.of(new WorldGuardRegion(location.getWorld(), bounds));
}
@Override
@@ -61,11 +68,40 @@ public class WorldGuardRegionProvider implements RegionProvider {
return regionManager.getRegions()
.values()
.stream()
.filter(region -> this.isCombatRegion(region))
.filter(this::isCombatRegion)
.map(region -> (Region) new WorldGuardRegion(world, region))
.toList();
}
// Whether the player's current location is a no-PvP / restricted spot. Uses WorldGuard's own flag
// resolution (queryState) so inherited and priority-overridden PVP flags are honoured. This closes
// the holes where a child region (e.g. a shop) without its own PVP flag was treated as unprotected.
private boolean isRestricted(ApplicableRegionSet applicableRegions) {
StateFlag.State pvpState = this.pluginConfig.regions.preventPvpInRegions
? applicableRegions.queryState(null, Flags.PVP)
: null;
// Effective PvP is denied here (inheritance/priority aware) -> safe zone, block entry.
if (pvpState == StateFlag.State.DENY) {
return true;
}
// A region in the blockedRegions list applies here, but never treat a spot as restricted when
// a higher-priority region explicitly allows PvP. Otherwise a large umbrella region (e.g. a
// world-spanning "spawn" listed in blockedRegions) would also block the warzone/arenas nested
// inside it.
if (pvpState != StateFlag.State.ALLOW) {
for (ProtectedRegion region : applicableRegions.getRegions()) {
if (this.regions.contains(region.getId())) {
return true;
}
}
}
return false;
}
// Per-region check used only for border rendering, where there is no single query point.
private boolean isCombatRegion(ProtectedRegion region) {
if (this.regions.contains(region.getId())) {
return true;
@@ -83,7 +119,7 @@ public class WorldGuardRegionProvider implements RegionProvider {
}
@Nullable
private ProtectedRegion highestPriorityRegion(ApplicableRegionSet applicableRegions) {
private ProtectedRegion innermostRegion(ApplicableRegionSet applicableRegions) {
return applicableRegions.getRegions().stream()
.max(Comparator.comparingInt(ProtectedRegion::getPriority))
.orElse(null);
+1 -1
View File
@@ -1,4 +1,4 @@
rootProject.name = "EternalCombat"
rootProject.name = "EternalCombat-OG"
include("eternalcombat-api")
include("eternalcombat-plugin")