Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,8 @@ public void onEnable() {
new TridentController(pluginConfig, noticeService, this.fightManager, this.tridentService, server),
new DeathFlareController(pluginConfig, server, scheduler, this),
new DeathLightningController(pluginConfig, server),
new UpdaterNotificationController(updaterService, pluginConfig, miniMessage),
new KnockbackRegionController(noticeService, this.regionProvider, this.fightManager, knockbackService, server),
new UpdaterNotificationController(updaterService, pluginConfig, miniMessage, scheduler),
new KnockbackRegionController(noticeService, this.regionProvider, this.fightManager, knockbackService, server, this),
new FightEffectController(pluginConfig.effect, this.fightEffectService, this.fightManager, server),
new FightTagOutController(this.fightTagOutService),
new FightMessageController(this.fightManager, noticeService, pluginConfig, server),
Expand All @@ -207,8 +207,7 @@ public void onEnable() {
new CommandsBlocker(this.fightManager, noticeService, pluginConfig),
new ElytraBlocker(this.fightManager, pluginConfig),
new ElytraEquipBlocker(this.fightManager, noticeService, pluginConfig, server),
new FlyingBlocker(this.fightManager, pluginConfig, server),
new PlaceBlockBlocker(this.fightManager, noticeService, pluginConfig)
new FlyingBlocker(this.fightManager, pluginConfig, server)
);

eventManager.subscribe(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import com.eternalcode.combat.notification.NoticeService;
import com.eternalcode.multification.notice.Notice;
import com.google.common.base.Stopwatch;
import dev.rollczi.litecommands.annotations.async.Async;
import dev.rollczi.litecommands.annotations.command.Command;
import dev.rollczi.litecommands.annotations.context.Context;
import dev.rollczi.litecommands.annotations.execute.Execute;
Expand All @@ -27,7 +26,6 @@ public EternalCombatReloadCommand(ConfigService configService, NoticeService not
this.noticeService = noticeService;
}

@Async
@Execute(name = "reload")
@Permission("eternalcombat.reload")
void execute(@Context CommandSender sender) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
import com.eternalcode.combat.region.Region;
import com.eternalcode.combat.region.RegionProvider;
import com.eternalcode.commons.bukkit.scheduler.MinecraftScheduler;
import com.eternalcode.commons.scheduler.Scheduler;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import org.bukkit.Location;
import org.bukkit.Server;
Expand All @@ -22,7 +21,7 @@ class BorderTriggerIndex {
private final RegionProvider provider;
private final Supplier<BorderSettings> settings;

private final Map<String, BorderTriggerIndexBucket> borderIndexes = new HashMap<>();
private final Map<String, BorderTriggerIndexBucket> borderIndexes = new ConcurrentHashMap<>();

private BorderTriggerIndex(Server server, MinecraftScheduler scheduler, RegionProvider provider, Supplier<BorderSettings> settings) {
this.server = server;
Expand All @@ -38,28 +37,26 @@ private void updateWorlds() {
}

private void updateWorld(String world, Collection<Region> regions) {
this.scheduler.runAsync(() -> {
int distanceRounded = settings.get().distanceRounded();
List<BorderTrigger> triggers = new ArrayList<>();
for (Region region : regions) {
Location min = region.getMin();
Location max = region.getMax();
int distanceRounded = settings.get().distanceRounded();
List<BorderTrigger> triggers = new ArrayList<>();
for (Region region : regions) {
Location min = region.getMin();
Location max = region.getMax();

triggers.add(new BorderTrigger(
min.getBlockX(), min.getBlockY(), min.getBlockZ(),
max.getBlockX() + 1, max.getBlockY() + 1, max.getBlockZ() + 1,
distanceRounded
));
}
triggers.add(new BorderTrigger(
min.getBlockX(), min.getBlockY(), min.getBlockZ(),
max.getBlockX() + 1, max.getBlockY() + 1, max.getBlockZ() + 1,
distanceRounded
));
}

BorderTriggerIndexBucket index = BorderTriggerIndexBucket.create(triggers);
this.borderIndexes.put(world, index);
});
BorderTriggerIndexBucket index = BorderTriggerIndexBucket.create(triggers);
this.borderIndexes.put(world, index);
}

static BorderTriggerIndex started(Server server, MinecraftScheduler scheduler, RegionProvider provider, Supplier<BorderSettings> settings) {
BorderTriggerIndex index = new BorderTriggerIndex(server, scheduler, provider, settings);
scheduler.timerAsync(() -> index.updateWorlds(), Duration.ZERO, settings.get().indexRefreshDelay());
scheduler.timer(() -> index.updateWorlds(), Duration.ZERO, settings.get().indexRefreshDelay());
return index;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.combat.fight.event.CauseOfUnTag;
import com.eternalcode.combat.fight.event.FightUntagEvent;
import com.eternalcode.commons.scheduler.Scheduler;
import com.eternalcode.commons.bukkit.scheduler.MinecraftScheduler;
import java.time.Duration;
import java.util.UUID;
import org.bukkit.Color;
Expand All @@ -27,10 +27,10 @@ public class DeathFlareController implements Listener {

private final PluginConfig pluginConfig;
private final Server server;
private final Scheduler scheduler;
private final MinecraftScheduler scheduler;
private final NamespacedKey key;

public DeathFlareController(PluginConfig pluginConfig, Server server, Scheduler scheduler, Plugin plugin) {
public DeathFlareController(PluginConfig pluginConfig, Server server, MinecraftScheduler scheduler, Plugin plugin) {
this.pluginConfig = pluginConfig;
this.server = server;
this.scheduler = scheduler;
Expand Down Expand Up @@ -65,7 +65,7 @@ public void onPlayerDeathEventFlare(PlayerDeathEvent event) {
}
}

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onEntityDamageByEntity(EntityDamageByEntityEvent event) {
if (event.getDamager() instanceof Firework firework && firework.getPersistentDataContainer().has(key, PersistentDataType.STRING)) {
event.setCancelled(true);
Expand Down Expand Up @@ -102,7 +102,8 @@ private void spawnFlare(Player player) {
}

private void scheduleParticles(Firework flare, World world) {
this.scheduler.runLaterAsync(
this.scheduler.runLater(
flare.getLocation(),
() -> {
if (flare.isDead() || !flare.isValid()) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.SkullMeta;

import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
Expand Down Expand Up @@ -85,8 +86,8 @@ private boolean shouldHeadDrop(boolean inCombat) {
return false;
}

if (this.dropSettings.headDropOnlyInCombat && inCombat) {
return true;
if (this.dropSettings.headDropOnlyInCombat && !inCombat) {
return false;
}

if (this.dropSettings.headDropChance <= 0.0) {
Expand Down Expand Up @@ -139,7 +140,8 @@ public void onPlayerRespawn(PlayerRespawnEvent event) {
ItemStack[] itemsToGive = this.keepInventoryManager.nextItems(playerUniqueId)
.toArray(new ItemStack[0]);

playerInventory.addItem(itemsToGive);
HashMap<Integer, ItemStack> leftover = playerInventory.addItem(itemsToGive);
leftover.values().forEach(item -> event.getRespawnLocation().getWorld().dropItemNaturally(event.getRespawnLocation(), item));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ public DropType getDropType() {

@Override
public DropResult modifyDrop(Drop drop) {
int dropItemPercent = 100 - MathUtil.clamp(this.settings.dropItemPercent, 0, 100);
int dropItemPercent = MathUtil.clamp(this.settings.dropItemPercent, 0, 100);
int keepItemPercent = 100 - dropItemPercent;
List<ItemStack> droppedItems = drop.getDroppedItems();

int itemsToDelete = InventoryUtil.calculateItemsToDelete(dropItemPercent, droppedItems, ItemStack::getAmount);
int itemsToDelete = InventoryUtil.calculateItemsToDelete(keepItemPercent, droppedItems, ItemStack::getAmount);
int droppedExp = MathUtil.getRoundedCountFromPercentage(dropItemPercent, drop.getDroppedExp());

RemoveItemResult result = InventoryUtil.removeRandomItems(droppedItems, itemsToDelete);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.eternalcode.combat.util.MathUtil;
import com.eternalcode.combat.util.RemoveItemResult;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeInstance;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;

Expand Down Expand Up @@ -46,14 +47,17 @@ public DropResult modifyDrop(Drop drop) {

List<ItemStack> droppedItems = drop.getDroppedItems();

double maxHealth = player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getBaseValue();
double maxHealth = Optional.ofNullable(player.getAttribute(Attribute.GENERIC_MAX_HEALTH))
.map(AttributeInstance::getValue)
.orElse(20.0);
double health = logout.health();

int percentHealth = MathUtil.getRoundedCountPercentage(health, maxHealth);
int reversedPercent = MathUtil.clamp(100 - percentHealth, this.settings.playersHealthPercentClamp, 100);
int dropPercent = MathUtil.clamp(100 - percentHealth, this.settings.playersHealthPercentClamp, 100);
int keepPercent = 100 - dropPercent;
Comment on lines +56 to +57

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using player.getAttribute(Attribute.GENERIC_MAX_HEALTH).getBaseValue() retrieves the base maximum health (usually 20.0), which ignores any active modifiers such as health boost effects, absorption, or attribute modifiers from equipment. Since logout.health() represents the player's actual health at logout (which can exceed the base max health), this can result in incorrect percentage calculations (e.g., greater than 100%). Use getValue() instead of getBaseValue(), and safely handle potential null values.

Suggested change
int dropPercent = MathUtil.clamp(100 - percentHealth, this.settings.playersHealthPercentClamp, 100);
int keepPercent = 100 - dropPercent;
double correctedMaxHealth = Optional.ofNullable(player.getAttribute(org.bukkit.attribute.Attribute.GENERIC_MAX_HEALTH))
.map(org.bukkit.attribute.AttributeInstance::getValue)
.orElse(20.0);
int dropPercent = MathUtil.clamp(100 - MathUtil.getRoundedCountPercentage(health, correctedMaxHealth), this.settings.playersHealthPercentClamp, 100);
int keepPercent = 100 - dropPercent;


int itemsToDelete = InventoryUtil.calculateItemsToDelete(reversedPercent, droppedItems, ItemStack::getAmount);
int droppedExp = MathUtil.getRoundedCountFromPercentage(reversedPercent, drop.getDroppedExp());
int itemsToDelete = InventoryUtil.calculateItemsToDelete(keepPercent, droppedItems, ItemStack::getAmount);
int droppedExp = MathUtil.getRoundedCountFromPercentage(dropPercent, drop.getDroppedExp());

RemoveItemResult result = InventoryUtil.removeRandomItems(droppedItems, itemsToDelete);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,48 @@
import com.eternalcode.combat.notification.NoticeService;
import com.eternalcode.combat.region.Region;
import com.eternalcode.combat.region.RegionProvider;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.Optional;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.Event;
import org.bukkit.event.entity.EntityEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.vehicle.VehicleMoveEvent;
import org.spigotmc.event.entity.EntityMountEvent;
import org.bukkit.plugin.EventExecutor;
import org.bukkit.plugin.Plugin;

public class KnockbackRegionController implements Listener {

private static final String[] ENTITY_MOUNT_EVENT_CLASSES = {
"org.bukkit.event.entity.EntityMountEvent",
"org.spigotmc.event.entity.EntityMountEvent"
};
Comment on lines +30 to +33

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To avoid performing expensive reflection lookups (getMethod) on every single EntityMountEvent execution, we should resolve and cache the getMount method once during initialization.

Suggested change
private static final String[] ENTITY_MOUNT_EVENT_CLASSES = {
"org.bukkit.event.entity.EntityMountEvent",
"org.spigotmc.event.entity.EntityMountEvent"
};
private static final String[] ENTITY_MOUNT_EVENT_CLASSES = {
"org.bukkit.event.entity.EntityMountEvent",
"org.spigotmc.event.entity.EntityMountEvent"
};
private Method getMountMethod;


private final NoticeService noticeService;
private final RegionProvider regionProvider;
private final FightManager fightManager;
private final KnockbackService knockbackService;
private final Server server;
private Method getMountMethod;

public KnockbackRegionController(NoticeService noticeService, RegionProvider regionProvider, FightManager fightManager, KnockbackService knockbackService, Server server) {
public KnockbackRegionController(NoticeService noticeService, RegionProvider regionProvider, FightManager fightManager, KnockbackService knockbackService, Server server, Plugin plugin) {
this.noticeService = noticeService;
this.regionProvider = regionProvider;
this.fightManager = fightManager;
this.knockbackService = knockbackService;
this.server = server;

this.registerEntityMountEvent(plugin);
}

@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
Expand Down Expand Up @@ -132,27 +146,82 @@ void onVehicleMove(VehicleMoveEvent event) {
}
}

@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
void onEntityMount(EntityMountEvent event) {
if (!(event.getEntity() instanceof Player player)) {
private void registerEntityMountEvent(Plugin plugin) {
Optional<Class<? extends Event>> eventClass = this.findEntityMountEventClass();
if (eventClass.isEmpty()) {
plugin.getLogger().fine("EntityMountEvent is not available. Mount region protection will be disabled.");
return;
}

try {
this.getMountMethod = eventClass.get().getMethod("getMount");
} catch (NoSuchMethodException exception) {
plugin.getLogger().warning("Failed to find getMount method on " + eventClass.get().getName());
return;
}

EventExecutor executor = this::onEntityMount;
plugin.getServer().getPluginManager().registerEvent(eventClass.get(), this, EventPriority.HIGHEST, executor, plugin, true);
}
Comment on lines +149 to +165

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Resolve and cache the getMount method during event registration so it can be reused efficiently.

    private void registerEntityMountEvent(Plugin plugin) {
        Optional<Class<? extends Event>> eventClass = this.findEntityMountEventClass();
        if (eventClass.isEmpty()) {
            plugin.getLogger().fine("EntityMountEvent is not available. Mount region protection will be disabled.");
            return;
        }

        try {
            this.getMountMethod = eventClass.get().getMethod("getMount");
        } catch (NoSuchMethodException exception) {
            plugin.getLogger().warning("Failed to find getMount method on " + eventClass.get().getName());
            return;
        }

        EventExecutor executor = this::onEntityMount;
        plugin.getServer().getPluginManager().registerEvent(eventClass.get(), this, EventPriority.HIGHEST, executor, plugin, true);
    }


private Optional<Class<? extends Event>> findEntityMountEventClass() {
for (String eventClassName : ENTITY_MOUNT_EVENT_CLASSES) {
try {
return Optional.of(Class.forName(eventClassName).asSubclass(Event.class));
} catch (ClassNotFoundException ignored) {
// Try the next API package.
}
}

return Optional.empty();
}

private void onEntityMount(Listener listener, Event event) {
if (!(event instanceof EntityEvent entityEvent)) {
return;
}

if (!(entityEvent.getEntity() instanceof Player player)) {
return;
}

if (!this.fightManager.isInCombat(player.getUniqueId())) {
return;
}

if (!this.regionProvider.isInRegion(event.getMount().getLocation())) {
Entity mount = this.getMount(event);
if (mount == null || !this.regionProvider.isInRegion(mount.getLocation())) {
return;
}

event.setCancelled(true);
if (event instanceof Cancellable cancellable) {
cancellable.setCancelled(true);
}

this.noticeService.create()
.player(player.getUniqueId())
.notice(config -> config.messagesSettings.cantEnterOnRegion)
.send();
}

private Entity getMount(Event event) {
if (this.getMountMethod == null) {
return null;
}

try {
Object mount = this.getMountMethod.invoke(event);

if (mount instanceof Entity entity) {
return entity;
}

return null;
} catch (IllegalAccessException | InvocationTargetException exception) {
return null;
}
}
Comment on lines +207 to +223

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Use the cached getMountMethod to invoke the method. Additionally, handle any reflection exceptions gracefully by returning null instead of throwing an IllegalStateException which would crash the event handler and spam the console.

Suggested change
private Entity getMount(Event event) {
try {
Method getMount = event.getClass().getMethod("getMount");
Object mount = getMount.invoke(event);
if (mount instanceof Entity entity) {
return entity;
}
return null;
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException exception) {
throw new IllegalStateException("Cannot read mount from " + event.getClass().getName(), exception);
}
}
private Entity getMount(Event event) {
if (this.getMountMethod == null) {
return null;
}
try {
Object mount = this.getMountMethod.invoke(event);
if (mount instanceof Entity entity) {
return entity;
}
return null;
} catch (IllegalAccessException | InvocationTargetException exception) {
return null;
}
}


@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
void onTag(FightTagEvent event) {
Player player = this.server.getPlayer(event.getPlayer());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.eternalcode.combat.config.implementation.PluginConfig;
import com.eternalcode.commons.concurrent.FutureHandler;
import com.eternalcode.commons.scheduler.Scheduler;
import java.time.Duration;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
Expand All @@ -15,11 +17,13 @@ public class UpdaterNotificationController implements Listener {
private final UpdaterService updaterService;
private final PluginConfig pluginConfig;
private final MiniMessage miniMessage;
private final Scheduler scheduler;

public UpdaterNotificationController(UpdaterService updaterService, PluginConfig pluginConfig, MiniMessage miniMessage) {
public UpdaterNotificationController(UpdaterService updaterService, PluginConfig pluginConfig, MiniMessage miniMessage, Scheduler scheduler) {
this.updaterService = updaterService;
this.pluginConfig = pluginConfig;
this.miniMessage = miniMessage;
this.scheduler = scheduler;
}

@EventHandler
Expand All @@ -33,7 +37,11 @@ void onJoin(PlayerJoinEvent event) {
this.updaterService.checkForUpdate()
.thenAccept(result -> {
if (result.isUpdateAvailable()) {
player.sendMessage(this.miniMessage.deserialize(NEW_VERSION_AVAILABLE));
this.scheduler.runLater(() -> {
if (player.isOnline()) {
player.sendMessage(this.miniMessage.deserialize(NEW_VERSION_AVAILABLE));
}
}, Duration.ZERO);
}
})
.exceptionally(FutureHandler::handleException);
Expand Down