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 @@ -30,6 +30,12 @@ public class BattleArenaConfig {
@ArenaOption(name = "randomized-arena-join", description = "Whether players should be randomly placed in an Arena when joining without specifying a map.", required = true)
private boolean randomizedArenaJoin;

@ArenaOption(name = "use-schematic", description = "Whether creating a dynamic arena should try to use a schematic if one is available first.", required = true)
private boolean schematicUsage;

@ArenaOption(name = "center-dynamic-arena", description = "If true, pastes the dynamic arena centered at 0,0,0.", required = true)
private boolean centerDynamicArena;

@ArenaOption(name = "disabled-modules", description = "Modules that are disabled by default.")
private List<String> disabledModules;

Expand Down Expand Up @@ -59,6 +65,14 @@ public boolean isRandomizedArenaJoin() {
return this.randomizedArenaJoin;
}

public boolean isSchematicUsage() {
return this.schematicUsage;
}

public boolean centerDynamicArena() {
return this.centerDynamicArena;
}

public List<String> getDisabledModules() {
return this.disabledModules == null ? List.of() : List.copyOf(this.disabledModules);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import net.kyori.adventure.util.TriState;
import org.battleplugins.arena.Arena;
import org.battleplugins.arena.ArenaLike;
import org.battleplugins.arena.competition.Competition;
import org.battleplugins.arena.competition.LiveCompetition;
import org.battleplugins.arena.competition.map.options.Bounds;
import org.battleplugins.arena.competition.map.options.Spawns;
Expand All @@ -14,6 +13,7 @@
import org.battleplugins.arena.util.BlockUtil;
import org.battleplugins.arena.util.Util;
import org.battleplugins.arena.util.VoidChunkGenerator;
import org.battleplugins.arena.BattleArenaConfig;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.WorldCreator;
Expand Down Expand Up @@ -255,8 +255,14 @@ public final LiveCompetition<?> createDynamicCompetition(Arena arena) {
return null;
}

if (!BlockUtil.copyToWorld(this.mapWorld, world, this.bounds)) {
return null; // Failed to copy
BattleArenaConfig config = this.getArena().getPlugin().getMainConfig();

// If schematic usage is disabled in the config OR schematic pasting fails,
// then attempt to fall back to copying the map directly from the map world.
// If that also fails, return null to indicate map setup failure.
if ((!config.isSchematicUsage() || !BlockUtil.pasteSchematic(this, world))
&& !BlockUtil.copyToWorld(this.mapWorld, world, this.bounds, config.centerDynamicArena())) {
return null;
}

LiveCompetitionMap copy = arena.getMapFactory().create(this.name, arena, this.type, worldName, this.bounds, this.spawns);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import org.battleplugins.arena.ArenaPlayer;
import org.battleplugins.arena.competition.Competition;
import org.battleplugins.arena.competition.map.options.Bounds;
import org.bukkit.util.Vector;
import org.battleplugins.arena.competition.map.MapType;
import org.battleplugins.arena.competition.map.options.TeamSpawns;
import org.battleplugins.arena.event.action.EventAction;
import org.battleplugins.arena.resolver.Resolvable;
Expand Down Expand Up @@ -31,6 +34,15 @@ public void call(ArenaPlayer arenaPlayer, Resolvable resolvable) {
TeleportLocation location = TeleportLocation.valueOf(this.get(LOCATION_KEY).toUpperCase(Locale.ROOT));
boolean randomized = Boolean.parseBoolean(this.getOrDefault(RANDOM, "false"));

boolean centerDynamicArena = arenaPlayer.getCompetition().getArena().getPlugin().getMainConfig().centerDynamicArena()
&& arenaPlayer.getCompetition().getMap().getType() == MapType.DYNAMIC;
Bounds bounds = arenaPlayer.getCompetition().getMap().getBounds();
Vector center = new Vector(
(bounds.getMaxX() + bounds.getMinX()) / 2.0,
bounds.getMinY(),
(bounds.getMaxZ() + bounds.getMinZ()) / 2.0
);

PositionWithRotation pos = switch (location) {
case LAST_LOCATION:
Location lastLocation = arenaPlayer.getStorage().getLastLocation();
Expand All @@ -40,9 +52,11 @@ public void call(ArenaPlayer arenaPlayer, Resolvable resolvable) {
yield null;
}
case WAITROOM:
yield arenaPlayer.getCompetition().getMap().getSpawns().getWaitroomSpawn();
PositionWithRotation waitroomSpawn = arenaPlayer.getCompetition().getMap().getSpawns().getWaitroomSpawn();
yield centerDynamicArena ? centerOffsetPosition(waitroomSpawn, center) : waitroomSpawn;
case SPECTATOR:
yield arenaPlayer.getCompetition().getMap().getSpawns().getSpectatorSpawn();
PositionWithRotation spectatorSpawn = arenaPlayer.getCompetition().getMap().getSpawns().getSpectatorSpawn();
yield centerDynamicArena ? centerOffsetPosition(spectatorSpawn, center) : spectatorSpawn;
case TEAM_SPAWN:
Map<String, TeamSpawns> teamSpawns = arenaPlayer.getCompetition().getMap().getSpawns().getTeamSpawns();
if (teamSpawns == null) {
Expand All @@ -62,19 +76,22 @@ public void call(ArenaPlayer arenaPlayer, Resolvable resolvable) {

// Fast track if there is only one spawn
if (spawns.size() == 1) {
yield spawns.get(0);
PositionWithRotation spawn = spawns.get(0);
yield centerDynamicArena ? centerOffsetPosition(spawn, center) : spawn;
}

if (randomized) {
yield spawns.get(ThreadLocalRandom.current().nextInt(spawns.size()));
PositionWithRotation spawn = spawns.get(ThreadLocalRandom.current().nextInt(spawns.size()));
yield centerDynamicArena ? centerOffsetPosition(spawn, center) : spawn;
}

// Get the spawn index for the team and increment it
int spawnIndex = this.spawnTeleportIndexQueue.getOrDefault(arenaPlayer.getCompetition(), 0);
this.spawnTeleportIndexQueue.put(arenaPlayer.getCompetition(), spawnIndex + 1);

// Get the spawn at the index
yield spawns.get(spawnIndex % spawns.size());
PositionWithRotation spawn = spawns.get(spawnIndex % spawns.size());
yield centerDynamicArena ? centerOffsetPosition(spawn, center) : spawn;
};

if (pos == null) {
Expand All @@ -90,4 +107,14 @@ public enum TeleportLocation {
TEAM_SPAWN,
LAST_LOCATION
}

private static PositionWithRotation centerOffsetPosition(PositionWithRotation base, Vector offset) {
return new PositionWithRotation(
base.getX() - offset.getX(),
base.getY(),
base.getZ() - offset.getZ(),
base.getYaw(),
base.getPitch()
);
}
}
77 changes: 75 additions & 2 deletions plugin/src/main/java/org/battleplugins/arena/util/BlockUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,30 @@
import com.sk89q.worldedit.WorldEditException;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import com.sk89q.worldedit.extent.clipboard.io.BuiltInClipboardFormat;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardReader;
import com.sk89q.worldedit.function.operation.ForwardExtentCopy;
import com.sk89q.worldedit.function.operation.Operation;
import com.sk89q.worldedit.function.operation.Operations;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.session.ClipboardHolder;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Locale;
import org.battleplugins.arena.BattleArena;
import org.battleplugins.arena.competition.map.options.Bounds;
import org.battleplugins.arena.competition.map.LiveCompetitionMap;
import org.bukkit.Bukkit;
import org.bukkit.World;

public final class BlockUtil {

public static boolean copyToWorld(World oldWorld, World newWorld, Bounds bounds) {
public static boolean copyToWorld(World oldWorld, World newWorld, Bounds bounds, boolean center) {
CuboidRegion region = new CuboidRegion(BlockVector3.at(bounds.getMinX(), bounds.getMinY(), bounds.getMinZ()), BlockVector3.at(bounds.getMaxX(), bounds.getMaxY(), bounds.getMaxZ()));
BlockArrayClipboard clipboard = new BlockArrayClipboard(region);
ForwardExtentCopy copy = new ForwardExtentCopy(BukkitAdapter.adapt(oldWorld), region, clipboard, region.getMinimumPoint());
Expand All @@ -30,9 +41,11 @@ public static boolean copyToWorld(World oldWorld, World newWorld, Bounds bounds)
return false;
}

BlockVector3 pasteLocation = getPasteLocation(bounds, center);

try (EditSession session = WorldEdit.getInstance().newEditSession(BukkitAdapter.adapt(newWorld))) {
Operation operation = new ClipboardHolder(clipboard).createPaste(session)
.to(BlockVector3.at(bounds.getMinX(), bounds.getMinY(), bounds.getMinZ()))
.to(pasteLocation)
.build();

Operations.complete(operation);
Expand All @@ -44,4 +57,64 @@ public static boolean copyToWorld(World oldWorld, World newWorld, Bounds bounds)

return true;
}

public static boolean pasteSchematic(LiveCompetitionMap map, World world) {
Path path = map.getArena().getPlugin().getDataFolder().toPath()
.resolve("schematics")
.resolve(map.getArena().getName().toLowerCase(Locale.ROOT))
.resolve(map.getName().toLowerCase(Locale.ROOT) + "." +
BuiltInClipboardFormat.SPONGE_SCHEMATIC.getPrimaryFileExtension()
);

if (Files.notExists(path)) {
Bukkit.getLogger().warning("Schematic not found: " + path);
return false;
}

ClipboardFormat format = ClipboardFormats.findByFile(path.toFile());
if (format == null) {
Bukkit.getLogger().warning("Unknown schematic format: " + path.getFileName());
return false;
}

Clipboard clipboard;
try (ClipboardReader reader = format.getReader(Files.newInputStream(path))) {
clipboard = reader.read();
} catch (IOException e) {
Bukkit.getLogger().severe("Failed to read schematic: " + e.getMessage());
return false;
}

BlockVector3 pasteLocation = getPasteLocation(map.getBounds(), map.getArena().getPlugin().getMainConfig().centerDynamicArena());

try (EditSession session = WorldEdit.getInstance().newEditSession(BukkitAdapter.adapt(world))) {
Operation operation = new ClipboardHolder(clipboard)
.createPaste(session)
.to(pasteLocation)
.ignoreAirBlocks(true)
.build();

Operations.complete(operation);
return true;
} catch (WorldEditException e) {
Bukkit.getLogger().severe("Failed to paste schematic: " + e.getMessage());
return false;
}
}

private static BlockVector3 getPasteLocation(Bounds bounds, boolean center) {
if (center) {
int widthX = bounds.getMaxX() - bounds.getMinX();
int y = bounds.getMinY();
int widthZ = bounds.getMaxZ() - bounds.getMinZ();

return BlockVector3.at(
-widthX / 2,
y,
-widthZ / 2
);
} else {
return BlockVector3.at(bounds.getMinX(), bounds.getMinY(), bounds.getMinZ());
}
}
}
11 changes: 11 additions & 0 deletions plugin/src/main/resources/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ max-dynamic-maps: 5
# enabled.
randomized-arena-join: false

# Whether dynamic arenas should try to use a schematic file for the map if one is available.
# If enabled and a schematic exists for the arena, it will be pasted into the world automatically.
# If no schematic is found or pasting fails, the plugin will fall back to copying the map directly
# from the source world. Disabling this option will skip the schematic step entirely and always
# use world copying instead.
use-schematic: false

# If true, dynamic arenas (schematic or copied) will be pasted centered around 0,0,0.
# If false, they will be pasted at their original saved coordinates.
center-dynamic-arena: false

# Modules that are disabled by default. BattleArena comes pre-installed with
# multiple modules that can be disabled below if their behavior is not desired
# Example for disabling the parties module:
Expand Down