Skip to content

Commit

Permalink
Further cleanups and add /ba reload along with memory leak fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Redned235 committed Jun 29, 2024
1 parent 99780b8 commit 38ae967
Show file tree
Hide file tree
Showing 17 changed files with 587 additions and 258 deletions.
363 changes: 116 additions & 247 deletions plugin/src/main/java/org/battleplugins/arena/BattleArena.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.battleplugins.arena.messages.Messages;
import org.battleplugins.arena.util.InventoryBackup;
import org.battleplugins.arena.util.OptionSelector;
import org.battleplugins.arena.util.UnitUtil;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;

Expand All @@ -20,6 +21,7 @@
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

public class BACommandExecutor extends BaseCommandExecutor {

Expand Down Expand Up @@ -162,4 +164,21 @@ public void debug(Player player) {
BattleArena.getInstance().setDebugMode(!BattleArena.getInstance().isDebugMode());
Messages.DEBUG_MODE_SET_TO.send(player, Boolean.toString(BattleArena.getInstance().isDebugMode()));
}

@ArenaCommand(commands = "reload", description = "Reloads the plugin.", permissionNode = "reload")
public void reload(Player player) {
Messages.STARTING_RELOAD.send(player);
long start = System.currentTimeMillis();

try {
BattleArena.getInstance().reload();
} catch (Exception e) {
Messages.RELOAD_FAILED.send(player);
BattleArena.getInstance().error("Failed to reload plugin", e);
return;
}

long end = System.currentTimeMillis();
Messages.RELOAD_COMPLETE.send(player, UnitUtil.toUnitString(player, end - start, TimeUnit.MILLISECONDS));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
package org.battleplugins.arena.competition;

import org.battleplugins.arena.Arena;
import org.battleplugins.arena.ArenaPlayer;
import org.battleplugins.arena.BattleArena;
import org.battleplugins.arena.competition.map.LiveCompetitionMap;
import org.battleplugins.arena.competition.map.MapType;
import org.battleplugins.arena.competition.phase.CompetitionPhaseType;
import org.battleplugins.arena.competition.phase.phases.VictoryPhase;
import org.battleplugins.arena.event.arena.ArenaCreateCompetitionEvent;
import org.battleplugins.arena.event.player.ArenaLeaveEvent;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;

public class CompetitionManager {
private final Map<Arena, List<Competition<?>>> competitions = new HashMap<>();

private final BattleArena plugin;

public CompetitionManager(BattleArena plugin) {
this.plugin = plugin;
}

public List<Competition<?>> getCompetitions(Arena arena) {
List<Competition<?>> competitions = this.competitions.get(arena);
return competitions == null ? List.of() : List.copyOf(competitions);
}

public List<Competition<?>> getCompetitions(Arena arena, String name) {
List<Competition<?>> competitions = this.getCompetitions(arena);
return competitions.stream()
.filter(competition -> competition.getMap().getName().equals(name))
.toList();
}

public CompletableFuture<CompetitionResult> getOrCreateCompetition(Arena arena, Player player, PlayerRole role, @Nullable String name) {
// See if we can join any already open competitions
List<Competition<?>> openCompetitions = this.getCompetitions(arena, name);
CompletableFuture<CompetitionResult> joinableCompetition = this.findJoinableCompetition(openCompetitions, player, role);
return joinableCompetition.thenApplyAsync(result -> {
if (result.competition() != null) {
return result;
}

CompetitionResult invalidResult = new CompetitionResult(null, !result.result().canJoin() ? result.result() : JoinResult.NOT_JOINABLE);
if (arena.getType() == CompetitionType.EVENT) {
// Cannot create non-requested dynamic competitions for events
return invalidResult;
}

List<LiveCompetitionMap<?>> maps = this.plugin.getMaps(arena);
if (maps == null) {
// No maps, return
return invalidResult;
}

// Ensure we have WorldEdit installed
if (this.plugin.getServer().getPluginManager().getPlugin("WorldEdit") == null) {
this.plugin.error("WorldEdit is required to create dynamic competitions! Not proceeding with creating a new dynamic competition.");
return invalidResult;
}

// Check if we have exceeded the maximum number of dynamic maps
List<Competition<?>> allCompetitions = this.getCompetitions(arena);
long dynamicMaps = allCompetitions.stream()
.map(Competition::getMap)
.filter(map -> map.getType() == MapType.DYNAMIC)
.count();

if (dynamicMaps >= this.plugin.getMainConfig().getMaxDynamicMaps() && this.plugin.getMainConfig().getMaxDynamicMaps() != -1) {
this.plugin.warn("Exceeded maximum number of dynamic maps for arena {}! Not proceeding with creating a new dynamic competition.", arena.getName());
return invalidResult;
}

// Create a new competition if possible

if (name == null) {
// Shuffle results if map name is not requested
maps = new ArrayList<>(maps);
Collections.shuffle(maps);
}

for (LiveCompetitionMap<?> map : maps) {
if (map.getType() != MapType.DYNAMIC) {
continue;
}

if ((name == null || map.getName().equals(name))) {
Competition<?> competition = map.createDynamicCompetition(arena);
if (competition == null) {
this.plugin.warn("Failed to create dynamic competition for map {} in arena {}!", map.getName(), arena.getName());
continue;
}

this.addCompetition(arena, competition);
return new CompetitionResult(competition, JoinResult.SUCCESS);
}
}

// No open competitions found or unable to create a new one
return invalidResult;
}, Bukkit.getScheduler().getMainThreadExecutor(this.plugin));
}

public CompletableFuture<CompetitionResult> findJoinableCompetition(List<Competition<?>> competitions, Player player, PlayerRole role) {
return this.findJoinableCompetition(competitions, player, role, null);
}

public CompletableFuture<CompetitionResult> findJoinableCompetition(List<Competition<?>> competitions, Player player, PlayerRole role, @Nullable JoinResult lastResult) {
if (competitions.isEmpty()) {
return CompletableFuture.completedFuture(new CompetitionResult(null, lastResult == null ? JoinResult.NOT_JOINABLE : lastResult));
}

Competition<?> competition = competitions.get(0);
CompletableFuture<JoinResult> result = competition.canJoin(player, role);
JoinResult joinResult = result.join();
if (joinResult == JoinResult.SUCCESS) {
return CompletableFuture.completedFuture(new CompetitionResult(competition, JoinResult.SUCCESS));
} else {
List<Competition<?>> remainingCompetitions = new ArrayList<>(competitions);
remainingCompetitions.remove(competition);

return this.findJoinableCompetition(remainingCompetitions, player, role, joinResult);
}
}

public void addCompetition(Arena arena, Competition<?> competition) {
this.competitions.computeIfAbsent(arena, k -> new ArrayList<>()).add(competition);
this.plugin.getServer().getPluginManager().callEvent(new ArenaCreateCompetitionEvent(arena, competition));
}

@SuppressWarnings("unchecked")
public void removeCompetition(Arena arena, Competition<?> competition) {
List<Competition<?>> competitions = this.competitions.get(arena);
if (competitions == null) {
return;
}

Set<CompetitionPhaseType<?, ?>> phases = arena.getPhases();

// Check if we have a victory phase
CompetitionPhaseType<?, VictoryPhase<?>> victoryPhase = null;
for (CompetitionPhaseType<?, ?> phase : phases) {
if (VictoryPhase.class.isAssignableFrom(phase.getPhaseType())) {
victoryPhase = (CompetitionPhaseType<?, VictoryPhase<?>>) phase;
break;
}
}

boolean removed = competitions.remove(competition);
if (removed && competition instanceof LiveCompetition<?> liveCompetition) {
// De-reference any remaining resources
liveCompetition.getVictoryManager().end(true);

if (victoryPhase != null && !(VictoryPhase.class.isAssignableFrom(liveCompetition.getPhase().getPhaseType()))) {
liveCompetition.getPhaseManager().setPhase(victoryPhase);

VictoryPhase<?> phase = (VictoryPhase<?>) liveCompetition.getPhaseManager().getCurrentPhase();
phase.onDraw(); // Mark as a draw

// End the victory phase
liveCompetition.getPhaseManager().end(true);
} else {
// No victory phase - just forcefully kick every player
for (ArenaPlayer player : liveCompetition.getPlayers()) {
liveCompetition.leave(player, ArenaLeaveEvent.Cause.SHUTDOWN);
}
}

liveCompetition.onDestroy();
}

competitions.remove(competition);
if (competition.getMap().getType() == MapType.DYNAMIC && competition.getMap() instanceof LiveCompetitionMap<?> map) {
this.clearDynamicMap(map);
}
}

public void completeAllActiveCompetitions() {
for (Map.Entry<Arena, List<Competition<?>>> entry : Map.copyOf(this.competitions).entrySet()) {
for (Competition<?> competition : List.copyOf(entry.getValue())) {
this.removeCompetition(entry.getKey(), competition);
}
}
}

private void clearDynamicMap(LiveCompetitionMap<?> map) {
if (map.getType() != MapType.DYNAMIC) {
return;
}

Bukkit.unloadWorld(map.getWorld(), false);
if (!map.getWorld().getWorldFolder().exists()) {
return;
}

try {
try (Stream<Path> pathsToDelete = Files.walk(map.getWorld().getWorldFolder().toPath())) {
for (Path path : pathsToDelete.sorted(Comparator.reverseOrder()).toList()) {
Files.deleteIfExists(path);
}
}
} catch (IOException e) {
this.plugin.error("Failed to delete dynamic map {}", map.getName(), e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ public abstract class LiveCompetition<T extends Competition<T>> implements Arena
private final TeamManager teamManager;
private final VictoryManager<T> victoryManager;

private final CompetitionListener<T> competitionListener;
private final OptionsListener<T> optionsListener;
private final StatListener<T> statListener;

public LiveCompetition(Arena arena, LiveCompetitionMap<T> map) {
this.arena = arena;
this.map = map;
Expand All @@ -55,15 +59,21 @@ public LiveCompetition(Arena arena, LiveCompetitionMap<T> map) {
this.teamManager = new TeamManager(this);
this.victoryManager = new VictoryManager<>(arena, (T) this);

arena.getEventManager().registerEvents(new CompetitionListener<>(this));
arena.getEventManager().registerEvents(new OptionsListener<>(this));
arena.getEventManager().registerEvents(new StatListener<>(this));
arena.getEventManager().registerEvents(this.competitionListener = new CompetitionListener<>(this));
arena.getEventManager().registerEvents(this.optionsListener = new OptionsListener<>(this));
arena.getEventManager().registerEvents(this.statListener = new StatListener<>(this));

// Set the initial phase
CompetitionPhaseType<?, ?> initialPhase = arena.getInitialPhase();
this.phaseManager.setPhase(initialPhase);
}

protected void onDestroy() {
this.arena.getEventManager().unregisterEvents(this.competitionListener);
this.arena.getEventManager().unregisterEvents(this.optionsListener);
this.arena.getEventManager().unregisterEvents(this.statListener);
}

private ArenaPlayer createPlayer(Player player) {
return new ArenaPlayer(player, this.arena, this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.battleplugins.arena.event.player.ArenaDeathEvent;
import org.battleplugins.arena.event.player.ArenaKillEvent;
import org.battleplugins.arena.event.player.ArenaLifeDepleteEvent;
import org.battleplugins.arena.event.player.ArenaLivesExhaustEvent;
import org.battleplugins.arena.event.player.ArenaStatChangeEvent;
import org.battleplugins.arena.stat.ArenaStats;
import org.bukkit.event.EventPriority;
Expand All @@ -31,10 +32,19 @@ public void onKill(ArenaKillEvent event) {
}

@ArenaEventHandler(priority = EventPriority.LOWEST)
public void onLifeDeplete(ArenaStatChangeEvent<?> event) {
public void onStatChange(ArenaStatChangeEvent<?> event) {
if (event.getStat() == ArenaStats.LIVES && event.getStatHolder() instanceof ArenaPlayer player) {
ArenaLifeDepleteEvent lifeDepleteEvent = new ArenaLifeDepleteEvent(this.competition.getArena(), player, (int) event.getNewValue());
this.competition.getArena().getEventManager().callEvent(lifeDepleteEvent);
int newValue = (int) event.getNewValue();
if (event.getOldValue() != null && (int) event.getOldValue() < newValue) {
return;
}

if (newValue == 0) {
this.competition.getArena().getEventManager().callEvent(new ArenaLivesExhaustEvent(this.competition.getArena(), player));
return;
}

this.competition.getArena().getEventManager().callEvent(new ArenaLifeDepleteEvent(this.competition.getArena(), player, newValue));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,13 @@ public void eventEnded(Arena arena, Competition<?> competition) {
arena.getPlugin().info("Event in arena {} has ended. Rescheduling event at interval.", arena.getName());
}

public void stopAllScheduledEvents() {
public void stopAllEvents() {
for (ScheduledEvent task : this.scheduledEvents.values()) {
task.task().cancel();
}

this.scheduledEvents.clear();
this.activeEvents.clear();
}

public Set<Arena> getScheduledEvents() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.battleplugins.arena.Arena;
import org.battleplugins.arena.competition.Competition;
import org.battleplugins.arena.competition.victory.VictoryCondition;

public class PhaseManager<T extends Competition<T>> {
private final Arena arena;
Expand All @@ -19,16 +20,20 @@ public void setPhase(CompetitionPhaseType<?, ?> phaseType) {
}

public void setPhase(CompetitionPhaseType<?, ?> phaseType, boolean complete) {
this.end(complete);

this.currentPhase = this.arena.createPhase(phaseType, this.competition);
this.arena.getEventManager().registerEvents(this.currentPhase);
this.currentPhase.start();
}

public void end(boolean complete) {
if (this.currentPhase != null) {
if (complete) {
this.currentPhase.complete();
}
this.arena.getEventManager().unregisterEvents(this.currentPhase);
}

this.currentPhase = this.arena.createPhase(phaseType, this.competition);
this.arena.getEventManager().registerEvents(this.currentPhase);
this.currentPhase.start();
}

public CompetitionPhase<T> getCurrentPhase() {
Expand Down
Loading

0 comments on commit 38ae967

Please sign in to comment.