Focus on bow spleef and classic spleef

This commit is contained in:
NotAlexNoyle committed 2026-05-20 09:53:26 -04:00
1 parent 63acbc99d5
commit 07433ba005
20 files changed
+429 -651

No files matched your search

+8 -6
View File
@@ -3,12 +3,14 @@
## 2.0.1-SNAPSHOT:
- First beta-ready snapshot of the Spleef-OG fork. Targets Purpur / Paper 1.19.4.
- **Editor layer lookup**: `SpleefMap.positionToLayers` is now maintained on `addLayer`, `removeLayer`, and `clearLayers`. Layers created via `/spleef layer add` are immediately usable; no server restart required. Previously the lookup map was only populated in `postProcess`, so runtime-added layers were invisible to block-break, decay, and projectile-hit logic.
- **Decay scheduler crash**: `SpleefCompetition.beginLayerDecay` now guards against empty layers and clamps the per-block interval to `Math.max(1, totalTicks / blocks)`. Prevents `ArithmeticException` on empty layers and runaway 0-tick-period timers on very large layers.
- **Splegg phantom eggs**: `SpleefArena.onInteract` now returns early when the held item is not a tagged spleef item. Previously an egg was launched on every right-click while in SPLEGG mode, regardless of the item in hand.
- **Mode scope**: Spleef-OG now supports only Classic Spleef and Bow Spleef. Unsupported projectile-cannon and TNT Run code paths and templates were removed.
- **Classic snowballs**: Breaking configured snow floor layers now grants throwable snowballs, and thrown snowballs break configured Spleef layer blocks.
- **WorldGuard region flow**: Arenas are intended to run in WorldGuard regions in the main spawn world. The default `world-whitelist` now targets `world`, and `/spleef worldguardregion <map> <region>` stores the map region ID.
- **Region whitelist**: `config.yml` now supports `region-whitelist` for limiting Spleef arena creation, joining, resets, floor breaking, projectile floor breaking, and Bow Spleef TNT handling to specific WorldGuard regions.
- **Editor layer lookup**: `SpleefMap.positionToLayers` is now maintained on `addLayer`, `removeLayer`, and `clearLayers`. Layers created via `/spleef layer add` are immediately usable; no server restart required.
- **NPE on layer removal**: `SpleefMap.removeLayer` and `clearLayers` null-guard `this.layers`, matching the lazy init in `addLayer`. `/spleef layer clear` on a fresh map no longer throws.
- **Bow Spleef broken**: `SpleefEventResolvers.PROJECTILE_HIT` now accepts any `Projectile`, with dedicated branches for `Egg` (splegg or `projectiles-break-blocks`) and `AbstractArrow` (BOW_SPLEEF or `projectiles-break-blocks`). Arrows now break layer blocks in bow-spleef mode.
- **Bow Spleef projectiles**: Arrows break layer blocks in bow-spleef mode.
- **`SpleefConfig.getShovel` NPE**: Now null-safe when the configured shovel name is unknown, honoring the `@Nullable` contract. Prevents crash on typos in arena YAML (e.g. `give-shovel{shovel=clasic}`).
- **PlayerMoveEvent hot path**: `SpleefArena.onMove` now returns early when `from` and `to` share block coordinates, cutting per-tick overhead for the death-region and decay-trigger checks by a large margin.
- **Scheduler leak on disable**: `ArenaSpleef.onDisable` cancels all scheduler tasks owned by the plugin, preventing leaked decay tasks from firing against stale map references after reload.
- **PlayerMoveEvent hot path**: `SpleefArena.onMove` now returns early when `from` and `to` share block coordinates, cutting per-tick overhead for death-region checks by a large margin.
- **Scheduler cleanup on disable**: `ArenaSpleef.onDisable` cancels all scheduler tasks owned by the plugin, preventing delayed tasks from firing against stale map references after reload.
- Gradle build updated to use the TrueOG bootstrap maven-local repository when available, falling back to `~/.m2`.
+64 -108
View File
@@ -1,54 +1,83 @@
# Spleef-OG
A spleef plugin using [BattleArena](https://github.com/BattlePlugins/BattleArena).
A BattleArena-backed Spleef plugin for Purpur / Paper 1.19.4.
Spleef-OG is a fork of BattlePlugins' ArenaSpleef, maintained for Purpur 1.19.4. It keeps the classic spleef experience alongside multiple modes and addresses critical stability bugs that blocked beta release of upstream 2.0.x.
Spleef-OG is maintained for TrueOG-style server-spawn minigames. It runs arenas in WorldGuard regions in the main world, not in MyWorlds world copies.
## Spleef Modes
- **Classic**: Players receive a shovel to break blocks and knock other players into the void.
- **Splegg**: Players receive an egg cannon to shoot eggs that break blocks.
- **Decay**: Blocks decay beneath the player. Commonly played as "TNT Run", but any block type works.
- **Bow Spleef**: Players receive a bow to shoot arrows that break blocks. TNT layers prime on hit.
## Supported Modes
- **Classic Spleef**: players receive a shovel and break configured snow floor layers. Breaking `snow_block` floors grants 4 snowballs; breaking `snow` layers grants snowballs equal to the layer count. Thrown snowballs break configured Spleef layer blocks.
- **Bow Spleef**: players receive a bow and arrow. Arrows break configured layer blocks, and TNT projectile handling is gated by a configured WorldGuard region.
For example configurations of these modes, see the [templates](./templates) folder.
For example configurations, see the [templates](./templates) folder.
## Region Model
- Spleef arenas are intended to live in WorldGuard regions in the main `world` near server spawn.
- `config.yml` defaults `world-whitelist` to `world`. Add your real spawn-world name there if it differs.
- `region-whitelist` can restrict Spleef to specific WorldGuard region IDs. Leave it empty to allow any region in an allowed world.
- Arena creation requires standing in a WorldGuard region with `allow-spleef` set to `allow`.
- Maps can set `worldguard-region` to their region ID. Bow Spleef maps should set it to the region that covers the TNT/layer area.
- When `region-whitelist` is set, joins, layer resets, block breaks, snowball/arrow block hits, and Bow Spleef TNT handling are limited to whitelisted regions.
- Spleef-OG does not depend on MyWorlds and does not create per-match world copies.
Example setup flow:
```text
/rg define spleef_classic
/rg flag spleef_classic allow-spleef allow
/spleef create classic
/spleef layer add classic
/spleef deathregion classic
```
For Bow Spleef, also run:
```text
/spleef worldguardregion bowspleef spleef_bow
```
Example `config.yml` whitelist:
```yaml
world-whitelist:
- world
region-whitelist:
- spleef_classic
- spleef_bow
```
## Compatibility
- **Server**: Purpur / Paper 1.19.4
- **Java**: 17+
- **Depends**: BattleArena 4.0.0-SNAPSHOT, WorldGuard
- **Soft-depends**: HorseTp-OG, PetTeleport-OG, PlayerBounties-OG, WorldEdit
## Notes
- **Arena reset**: Layer pasting now hard-resets configured layer volumes each round instead of only filling air blocks.
- **Bow Spleef TNT**: TNT priming is now gated by WorldGuard. Set `worldguard-region: <region-id>` on the map used by a Bow Spleef arena, and make sure that region covers the TNT layer.
- **Soft-depends**: GameModeInventories-OG, HorseTp-OG, PetTeleport-OG, PlayerBounties-OG, WorldEdit
## Integrations
- **[PetTeleport-OG](https://github.com/true-og/PetTeleport-OG)**: pet teleports are suppressed while a player is in a Spleef match. No configuration required — PetTeleport-OG reads `SpleefAPI.isInSpleef`.
- **[HorseTp-OG](https://github.com/true-og/HorseTp-OG)**: horse teleports are suppressed while a player is in a Spleef match. No configuration required — HorseTp-OG reads `SpleefAPI.isInSpleef`.
- **[PlayerBounties-OG](https://github.com/true-og/PlayerBounties-OG)**: bounty claims and new bounty placements are cancelled whenever the claimant, victim, or target is in a Spleef match. Active automatically when PlayerBounties-OG is installed.
- **PetTeleport-OG**: pet teleports are suppressed while a player is in a Spleef match. No configuration required; PetTeleport-OG reads `SpleefAPI.isInSpleef`.
- **HorseTp-OG**: horse teleports are suppressed while a player is in a Spleef match. No configuration required; HorseTp-OG reads `SpleefAPI.isInSpleef`.
- **PlayerBounties-OG**: bounty claims and new bounty placements are cancelled whenever the claimant, victim, or target is in a Spleef match.
- **GameModeInventories-OG**: gamemode-specific inventories are preserved while players enter and leave Spleef.
## Commands
| Command | Description |
|---------------------------------------|------------------------------------------------|
| /spleef deathregion <map> <region> | Sets the death region for a spleef arena. |
| /spleef layer add <map> | Adds a layer to a spleef arena. |
| /spleef layer remove <map> <index> | Removes a layer from a spleef arena. |
| /spleef layer clear <map> | Clears all layers from a spleef arena. |
| /spleef layer index <map> <from> <to> | Changes the index of a layer. |
| /spleef layer list <map> | Lists all layers in a spleef arena. |
| Command | Description |
|-----------------------------------------------|-----------------------------------------------------|
| `/spleef deathregion <map>` | Sets the death region for a Spleef arena. |
| `/spleef layer add <map>` | Adds a floor layer to a Spleef arena. |
| `/spleef layer remove <map> <index>` | Removes a layer from a Spleef arena. |
| `/spleef layer clear <map>` | Clears all layers from a Spleef arena. |
| `/spleef layer index <map> <from> <to>` | Changes the index of a layer. |
| `/spleef layer list <map>` | Lists all layers in a Spleef arena. |
| `/spleef worldguardregion <map> <region>` | Sets the map WorldGuard region ID. |
## Permissions
| Permission | Command |
|-----------------------------------------|----------------------|
| battlearena.command.spleef.deathregion | /spleef deathregion |
| battlearena.command.spleef.layer.add | /spleef layer add |
| battlearena.command.spleef.layer.remove | /spleef layer remove |
| battlearena.command.spleef.layer.clear | /spleef layer clear |
| battlearena.command.spleef.layer.index | /spleef layer index |
| battlearena.command.spleef.layer.list | /spleef layer list |
| Permission | Command |
|----------------------------------------------|---------------------------------|
| `battlearena.command.spleef.deathregion` | `/spleef deathregion` |
| `battlearena.command.spleef.layer.add` | `/spleef layer add` |
| `battlearena.command.spleef.layer.remove` | `/spleef layer remove` |
| `battlearena.command.spleef.layer.clear` | `/spleef layer clear` |
| `battlearena.command.spleef.layer.index` | `/spleef layer index` |
| `battlearena.command.spleef.layer.list` | `/spleef layer list` |
| `battlearena.command.spleef.worldguardregion` | `/spleef worldguardregion` |
## API
Spleef-OG exposes a small Bukkit-native API for other plugins. No BattleArena types on the surface — only `org.bukkit.entity.Player`.
Spleef-OG exposes a small Bukkit-native API for other plugins. No BattleArena types on the surface, only `org.bukkit.entity.Player`.
### Check if a player is in a Spleef match
Java:
@@ -77,83 +106,10 @@ Both extend `org.bukkit.event.player.PlayerEvent`.
| `SpleefJoinEvent` | Player joins any Spleef-mode competition | `true` |
| `SpleefLeaveEvent` | Player leaves any Spleef-mode competition | `false` |
Covers every mode (Classic, Splegg, Decay, Bow Spleef). State flips before the event fires, so listeners observe the post-transition value.
### Consumer setup (Gradle Kotlin DSL)
```kotlin
repositories {
maven("https://repo.papermc.io/repository/maven-public/")
}
dependencies {
compileOnly("io.papermc.paper:paper-api:1.19.4-R0.1-SNAPSHOT")
compileOnly(files("libs/Spleef-OG-<version>.jar"))
}
```
`plugin.yml`:
```yaml
depend: [Spleef-OG]
```
### Listener examples
Java:
```java
import org.battleplugins.arena.spleef.api.SpleefAPI;
import org.battleplugins.arena.spleef.api.SpleefJoinEvent;
import org.battleplugins.arena.spleef.api.SpleefLeaveEvent;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
public class SpleefHooks implements Listener {
@EventHandler
public void onJoin(SpleefJoinEvent event) {
Player player = event.getPlayer();
// SpleefAPI.isInSpleef(player) == true
}
@EventHandler
public void onLeave(SpleefLeaveEvent event) {
Player player = event.getPlayer();
// SpleefAPI.isInSpleef(player) == false
}
}
```
Kotlin:
```kotlin
import org.battleplugins.arena.spleef.api.SpleefAPI
import org.battleplugins.arena.spleef.api.SpleefJoinEvent
import org.battleplugins.arena.spleef.api.SpleefLeaveEvent
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
class SpleefHooks : Listener {
@EventHandler
fun onJoin(event: SpleefJoinEvent) {
val player = event.player
// SpleefAPI.isInSpleef(player) == true
}
@EventHandler
fun onLeave(event: SpleefLeaveEvent) {
val player = event.player
// SpleefAPI.isInSpleef(player) == false
}
}
```
Covers Classic Spleef and Bow Spleef. State flips before the event fires, so listeners observe the post-transition value.
## Building
```
```text
./gradlew build
```
Output: `build/libs/Spleef-OG-<version>.jar`
+1 -1
View File
@@ -58,7 +58,7 @@ repositories {
dependencies {
compileOnly("io.papermc.paper:paper-api:1.19.4-R0.1-SNAPSHOT")
compileOnly("org.battleplugins:arena:4.0.0-SNAPSHOT")
compileOnly("com.sk89q.worldguard:worldguard-bukkit:7.0.9")
compileOnly("com.sk89q.worldguard:worldguard-bukkit:7.0.8") // Import WorldGuard API.
}
/* ---------------------- Reproducible jars ---------------------------- */
@@ -1,6 +1,7 @@
package org.battleplugins.arena.spleef;
import org.battleplugins.arena.config.ArenaOption;
import org.bukkit.Location;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
@@ -10,20 +11,14 @@ import java.util.Map;
public class SpleefConfig {
@ArenaOption(name = "projectiles-break-blocks", description = "Whether snowballs should break blocks.")
private boolean projectilesBreakBlocks = false;
@ArenaOption(name = "shovels", description = "The shovels for this spleef game.", required = true)
private Map<String, ItemStack> shovels;
@ArenaOption(name = "world-whitelist", description = "Worlds where Spleef arenas may be created and run. Empty list = all worlds allowed.")
private List<String> worldWhitelist;
public boolean shouldProjectilesBreakBlocks() {
return this.projectilesBreakBlocks;
}
@ArenaOption(name = "region-whitelist", description = "WorldGuard regions where Spleef arenas may be created and matches may modify blocks. Empty list = all regions allowed.")
private List<String> regionWhitelist;
@Nullable
public ItemStack getShovel(String name) {
@@ -74,4 +69,57 @@ public class SpleefConfig {
}
public boolean hasRegionWhitelist() {
return this.regionWhitelist != null && !this.regionWhitelist.isEmpty();
}
public boolean isRegionAllowed(String regionName) {
if (!this.hasRegionWhitelist()) {
return true;
}
if (regionName == null) {
return false;
}
String target = regionName.toLowerCase(Locale.ROOT);
for (String allowed : this.regionWhitelist) {
if (allowed != null && allowed.toLowerCase(Locale.ROOT).equals(target)) {
return true;
}
}
return false;
}
public boolean isRegionAllowedAt(Location location) {
if (!this.hasRegionWhitelist()) {
return true;
}
return WorldGuardSupport.isInsideAnyRegion(location, this.regionWhitelist);
}
public List<String> getRegionWhitelist() {
return this.regionWhitelist == null ? List.of() : List.copyOf(this.regionWhitelist);
}
}
@@ -1,6 +1,5 @@
package org.battleplugins.arena.spleef;
import com.destroystokyo.paper.event.entity.ThrownEggHatchEvent;
import org.battleplugins.arena.ArenaPlayer;
import org.battleplugins.arena.BattleArena;
import org.battleplugins.arena.competition.Competition;
@@ -11,9 +10,9 @@ import org.battleplugins.arena.spleef.arena.SpleefCompetition;
import org.battleplugins.arena.spleef.arena.SpleefGame;
import org.battleplugins.arena.spleef.arena.SpleefMap;
import org.bukkit.entity.AbstractArrow;
import org.bukkit.entity.Egg;
import org.bukkit.entity.Player;
import org.bukkit.entity.Projectile;
import org.bukkit.entity.Snowball;
import org.bukkit.event.block.TNTPrimeEvent;
import org.bukkit.event.entity.ProjectileHitEvent;
import org.bukkit.projectiles.ProjectileSource;
@@ -55,11 +54,16 @@ public final class SpleefEventResolvers {
}
boolean breakBlocks = ArenaSpleef.getInstance().getMainConfig().shouldProjectilesBreakBlocks();
SpleefConfig config = ArenaSpleef.getInstance().getMainConfig();
if (config != null && !config.isRegionAllowedAt(event.getHitBlock().getLocation())) {
if (projectile instanceof Egg egg) {
return null;
if (egg.hasMetadata("splegg") || breakBlocks) {
}
if (projectile instanceof Snowball) {
if (arena.getGame() == SpleefGame.CLASSIC) {
return arenaPlayer.getCompetition();
@@ -71,7 +75,7 @@ public final class SpleefEventResolvers {
if (projectile instanceof AbstractArrow) {
if (arena.getGame() == SpleefGame.BOW_SPLEEF || breakBlocks) {
if (arena.getGame() == SpleefGame.BOW_SPLEEF) {
return arenaPlayer.getCompetition();
@@ -81,42 +85,10 @@ public final class SpleefEventResolvers {
}
if (breakBlocks) {
return arenaPlayer.getCompetition();
}
return null;
};
public static final Function<ThrownEggHatchEvent, LiveCompetition<?>> THROWN_EGG_HATCH = event -> {
if (!event.getEgg().hasMetadata("splegg")) {
return null;
}
ProjectileSource shooter = event.getEgg().getShooter();
if (!(shooter instanceof Player player)) {
return null;
}
ArenaPlayer arenaPlayer = ArenaPlayer.getArenaPlayer(player);
if (arenaPlayer == null || !(arenaPlayer.getArena() instanceof SpleefArena)) {
return null;
}
return arenaPlayer.getCompetition();
};
public static final Function<SpleefArena, Function<TNTPrimeEvent, LiveCompetition<?>>> TNT_PRIME = arena -> event -> {
if (arena.getGame() != SpleefGame.BOW_SPLEEF) {
@@ -154,6 +126,13 @@ public final class SpleefEventResolvers {
}
SpleefConfig config = ArenaSpleef.getInstance().getMainConfig();
if (config != null && !config.isRegionAllowedAt(event.getBlock().getLocation())) {
continue;
}
if (WorldGuardSupport.isInsideRegion(event.getBlock().getLocation(), worldGuardRegion)) {
return spleefCompetition;
@@ -1,15 +1,20 @@
package org.battleplugins.arena.spleef;
import org.battleplugins.arena.BattleArena;
import org.battleplugins.arena.command.ArenaCommand;
import org.battleplugins.arena.command.ArenaCommandExecutor;
import org.battleplugins.arena.command.Argument;
import org.battleplugins.arena.competition.map.CompetitionMap;
import org.battleplugins.arena.competition.map.options.Bounds;
import org.battleplugins.arena.config.ParseException;
import org.battleplugins.arena.messages.Messages;
import org.battleplugins.arena.spleef.arena.SpleefArena;
import org.battleplugins.arena.spleef.arena.SpleefMap;
import org.battleplugins.arena.spleef.editor.SpleefEditorWizards;
import org.bukkit.entity.Player;
import java.io.IOException;
public class SpleefExecutor extends ArenaCommandExecutor {
public SpleefExecutor(SpleefArena arena) {
@@ -37,6 +42,13 @@ public class SpleefExecutor extends ArenaCommandExecutor {
}
if (config != null && !config.isRegionAllowedAt(player.getLocation())) {
SpleefMessages.CREATE_REGION_NOT_WHITELISTED.send(player);
return;
}
if (!WorldGuardSupport.isSpleefAllowedAt(player)) {
SpleefMessages.CREATE_WG_FLAG_REQUIRED.send(player);
@@ -178,4 +190,45 @@ public class SpleefExecutor extends ArenaCommandExecutor {
}
@ArenaCommand(commands = "worldguardregion", description = "Sets the WorldGuard region for a spleef arena.", permissionNode = "worldguardregion")
public void setWorldGuardRegion(Player player, CompetitionMap map, @Argument(name = "region") String region) {
if (!(map instanceof SpleefMap spleefMap)) {
return; // Should not happen but just incase
}
if (!WorldGuardSupport.isEnabled()) {
SpleefMessages.CREATE_WG_MISSING.send(player);
return;
}
if (spleefMap.getWorld() == null || !WorldGuardSupport.regionExists(spleefMap.getWorld(), region)) {
SpleefMessages.WORLDGUARD_REGION_NOT_FOUND.send(player, region);
return;
}
spleefMap.setWorldGuardRegion(region);
try {
spleefMap.save();
} catch (ParseException | IOException e) {
BattleArena.getInstance().error("Failed to save map file for arena {}", this.arena.getName(), e);
Messages.MAP_FAILED_TO_SAVE.send(player, spleefMap.getName());
return;
}
SpleefMessages.WORLDGUARD_REGION_SET.send(player, region);
}
}
@@ -36,11 +36,19 @@ public final class SpleefMessages {
public static final Message CREATE_WORLD_NOT_WHITELISTED = Messages.error("spleef-create-world-not-whitelisted",
"Spleef arenas cannot be created in this world. World <secondary>{}</secondary> is not in the Spleef world whitelist.");
public static final Message CREATE_REGION_NOT_WHITELISTED = Messages.error("spleef-create-region-not-whitelisted",
"Spleef arenas cannot be created here. Stand inside a WorldGuard region listed in the Spleef region whitelist.");
public static final Message CREATE_WG_FLAG_REQUIRED = Messages.error("spleef-create-wg-flag-required",
"You must stand inside a WorldGuard region that has the <secondary>allow-spleef</secondary> flag set to <secondary>allow</secondary> to create a Spleef arena here.");
public static final Message CREATE_WG_MISSING = Messages.error("spleef-create-wg-missing",
"WorldGuard is not loaded; Spleef arena creation is disabled.");
public static final Message WORLD_BLOCKED_AT_RUNTIME = Messages.error("spleef-world-blocked",
"Spleef matches are not permitted in world <secondary>{}</secondary>.");
public static final Message MATCH_REGION_NOT_WHITELISTED = Messages.error("spleef-match-region-not-whitelisted",
"This Spleef map is not inside a whitelisted WorldGuard region.");
public static final Message WORLDGUARD_REGION_NOT_FOUND = Messages.error("spleef-worldguard-region-not-found",
"WorldGuard region <secondary>{}</secondary> was not found in this map's world.");
public static final Message WORLDGUARD_REGION_SET = Messages.success("spleef-worldguard-region-set",
"WorldGuard region set to <secondary>{}</secondary>.");
}
@@ -1,54 +0,0 @@
package org.battleplugins.arena.spleef;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.util.NumberConversions;
public final class SpleefUtil {
public static Block getBlockUnderPlayer(int y, Location location) {
Location loc = location.clone();
loc.setY(y);
double boxWidth = 0.3;
Block block1 = getBlock(loc, +boxWidth, -boxWidth);
if (!block1.getType().isAir()) {
return block1;
}
Block block2 = getBlock(loc, -boxWidth, +boxWidth);
if (!block2.getType().isAir()) {
return block2;
}
Block block3 = getBlock(loc, +boxWidth, +boxWidth);
if (!block3.getType().isAir()) {
return block3;
}
Block block4 = getBlock(loc, -boxWidth, -boxWidth);
if (!block4.getType().isAir()) {
return block4;
}
return null;
}
private static Block getBlock(Location loc, double x, double z) {
return loc.getWorld().getBlockAt(NumberConversions.floor(loc.getX() + x), loc.getBlockY(),
NumberConversions.floor(loc.getZ() + z));
}
}
@@ -10,8 +10,11 @@ import com.sk89q.worldguard.protection.flags.StateFlag;
import com.sk89q.worldguard.protection.managers.RegionManager;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
import java.util.List;
public final class WorldGuardSupport {
private WorldGuardSupport() {
@@ -26,6 +29,12 @@ public final class WorldGuardSupport {
public static boolean isInsideRegion(Location location, String regionId) {
if (location.getWorld() == null) {
return false;
}
RegionManager regionManager = WorldGuard.getInstance().getPlatform().getRegionContainer()
.get(BukkitAdapter.adapt(location.getWorld()));
if (regionManager == null) {
@@ -40,6 +49,43 @@ public final class WorldGuardSupport {
}
public static boolean isInsideAnyRegion(Location location, List<String> regionIds) {
if (location == null || location.getWorld() == null || regionIds == null || regionIds.isEmpty()) {
return false;
}
RegionManager regionManager = WorldGuard.getInstance().getPlatform().getRegionContainer()
.get(BukkitAdapter.adapt(location.getWorld()));
if (regionManager == null) {
return false;
}
BlockVector3 position = BlockVector3.at(location.getBlockX(), location.getBlockY(), location.getBlockZ());
ApplicableRegionSet regions = regionManager.getApplicableRegions(position);
return regions.getRegions().stream().anyMatch(region -> regionIds.stream()
.anyMatch(allowed -> allowed != null && region.getId().equalsIgnoreCase(allowed)));
}
public static boolean regionExists(World world, String regionId) {
RegionManager regionManager = WorldGuard.getInstance().getPlatform().getRegionContainer()
.get(BukkitAdapter.adapt(world));
if (regionManager == null) {
return false;
}
return regionManager.getRegion(regionId) != null;
}
public static boolean isSpleefAllowedAt(Player player) {
StateFlag flag = SpleefFlags.getAllowSpleefFlag();
@@ -1,6 +1,5 @@
package org.battleplugins.arena.spleef.arena;
import com.destroystokyo.paper.event.entity.ThrownEggHatchEvent;
import io.papermc.paper.math.BlockPosition;
import org.battleplugins.arena.Arena;
import org.battleplugins.arena.ArenaPlayer;
@@ -9,43 +8,33 @@ import org.battleplugins.arena.competition.map.MapFactory;
import org.battleplugins.arena.competition.phase.CompetitionPhaseType;
import org.battleplugins.arena.config.ArenaOption;
import org.battleplugins.arena.event.ArenaEventHandler;
import org.battleplugins.arena.event.arena.ArenaPhaseCompleteEvent;
import org.battleplugins.arena.event.arena.ArenaPhaseStartEvent;
import org.battleplugins.arena.event.player.ArenaLeaveEvent;
import org.battleplugins.arena.options.ArenaOptionType;
import org.battleplugins.arena.options.types.BooleanArenaOption;
import org.battleplugins.arena.spleef.ArenaSpleef;
import org.battleplugins.arena.spleef.SpleefEventResolvers;
import org.battleplugins.arena.spleef.SpleefExecutor;
import org.battleplugins.arena.spleef.SpleefUtil;
import org.battleplugins.arena.spleef.SpleefMessages;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.block.Block;
import org.bukkit.entity.Egg;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Snow;
import org.bukkit.entity.Player;
import org.bukkit.entity.TNTPrimed;
import org.bukkit.event.EventPriority;
import org.bukkit.event.block.Action;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.TNTPrimeEvent;
import org.bukkit.event.entity.ProjectileHitEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.persistence.PersistentDataType;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
public class SpleefArena extends Arena {
@ArenaOption(name = "layer-decay-delay", description = "The delay before each layer decays.")
private Duration layerDecayDelay = Duration.ofMinutes(2);
@ArenaOption(name = "layer-decay-time", description = "The time it takes to decay and entire layer.")
private Duration layerDecayTime = Duration.ofMinutes(1);
@ArenaOption(name = "game", description = "The spleef game.")
private SpleefGame game = SpleefGame.CLASSIC;
@@ -54,7 +43,6 @@ public class SpleefArena extends Arena {
super();
this.getEventManager().registerArenaResolver(ProjectileHitEvent.class, SpleefEventResolvers.PROJECTILE_HIT);
this.getEventManager().registerArenaResolver(ThrownEggHatchEvent.class, SpleefEventResolvers.THROWN_EGG_HATCH);
this.getEventManager().registerArenaResolver(TNTPrimeEvent.class, SpleefEventResolvers.TNT_PRIME.apply(this));
}
@@ -91,28 +79,20 @@ public class SpleefArena extends Arena {
ArenaSpleef.getInstance().getSLF4JLogger().warn(
"Refusing to start Spleef competition '{}' — world '{}' is not in the Spleef world whitelist.",
spleefCompetition.getMap().getName(), worldName);
this.leaveWorldBlockedCompetition(spleefCompetition, worldName);
return;
}
spleefCompetition.beginLayerDecay();
if (!spleefCompetition.isMatchRegionAllowed()) {
}
ArenaSpleef.getInstance().getSLF4JLogger().warn(
"Refusing to start Spleef competition '{}' — map is not inside a whitelisted WorldGuard region.",
spleefCompetition.getMap().getName());
this.leaveRegionBlockedCompetition(spleefCompetition);
return;
}
@ArenaEventHandler
public void onPhaseComplete(ArenaPhaseCompleteEvent event) {
if (!CompetitionPhaseType.INGAME.equals(event.getPhase().getType())) {
return;
}
if (event.getCompetition() instanceof SpleefCompetition spleefCompetition) {
spleefCompetition.stopLayerDecay();
}
}
@@ -141,7 +121,16 @@ public class SpleefArena extends Arena {
SpleefMap.Layer layer = spleefMap.getLayer(pos);
if (layer != null) {
if (!(player.getCompetition() instanceof SpleefCompetition competition)
|| !competition.isLocationAllowed(event.getBlock().getLocation()))
{
return;
}
event.setCancelled(false);
this.giveSnowballsFromFloor(event);
}
@@ -163,79 +152,9 @@ public class SpleefArena extends Arena {
if (spleefMap.getDeathRegion() != null && spleefMap.getDeathRegion().isInside(event.getTo())) {
event.getPlayer().damage(10000.0D); // Kill the player
return;
}
// Ensure that the game is the decay spleef game
if (this.game != SpleefGame.DECAY) {
return;
}
// If a player changes blocks and the block below is of a
// spleef layer, then decay the layer
Block block = SpleefUtil.getBlockUnderPlayer(event.getTo().getBlockY() - 1, event.getTo());
if (block == null || block.getType().isAir()) {
return;
}
BlockPosition pos = block.getLocation().toBlock();
SpleefMap.Layer layer = spleefMap.getLayer(pos);
if (layer == null) {
return;
}
competition.decayBlock(pos);
}
@ArenaEventHandler(priority = EventPriority.LOW)
public void onInteract(PlayerInteractEvent event) {
ItemStack item = event.getItem();
if (item == null || item.getItemMeta() == null) {
return;
}
if (this.game != SpleefGame.SPLEGG) {
return;
}
if (event.getAction() != Action.RIGHT_CLICK_AIR && event.getAction() != Action.RIGHT_CLICK_BLOCK) {
return;
}
ItemMeta meta = item.getItemMeta();
if (!meta.getPersistentDataContainer().has(ArenaSpleef.getInstance().getSpleefItemKey(),
PersistentDataType.BOOLEAN))
{
return; // Not a spleef item, do not fire egg
}
event.setCancelled(true);
// Shoot an egg
event.getPlayer().launchProjectile(Egg.class, null, egg -> {
egg.setMetadata("splegg", new FixedMetadataValue(ArenaSpleef.getInstance(), true));
event.getPlayer().playSound(event.getPlayer().getLocation(), Sound.ENTITY_EGG_THROW, 1, 1);
});
}
// For the events below:
@@ -257,21 +176,13 @@ public class SpleefArena extends Arena {
}
block.setType(Material.AIR);
}
@ArenaEventHandler(priority = EventPriority.LOW)
public void onEggHatch(ThrownEggHatchEvent event) {
Egg egg = event.getEgg();
if (!egg.hasMetadata("splegg")) {
if (!competition.isLocationAllowed(block.getLocation())) {
return;
}
event.setHatching(false);
block.setType(Material.AIR);
}
@@ -301,15 +212,68 @@ public class SpleefArena extends Arena {
}
public Duration getLayerDecayDelay() {
private void giveSnowballsFromFloor(BlockBreakEvent event) {
return this.layerDecayDelay;
int amount = this.getSnowballDropCount(event.getBlock());
if (amount <= 0) {
return;
}
event.setDropItems(false);
event.getPlayer().getInventory().addItem(new ItemStack(Material.SNOWBALL, amount));
}
public Duration getLayerDecayTime() {
private int getSnowballDropCount(Block block) {
return this.layerDecayTime;
if (block.getType() == Material.SNOW_BLOCK) {
return 4;
}
if (block.getType() != Material.SNOW) {
return 0;
}
BlockData blockData = block.getBlockData();
if (blockData instanceof Snow snow) {
return snow.getLayers();
}
return 1;
}
private void leaveWorldBlockedCompetition(SpleefCompetition competition, String worldName) {
List<ArenaPlayer> players = new ArrayList<>(competition.getPlayers());
players.addAll(competition.getSpectators());
for (ArenaPlayer player : players) {
SpleefMessages.WORLD_BLOCKED_AT_RUNTIME.send(player.getPlayer(), worldName);
competition.leave(player, ArenaLeaveEvent.Cause.PLUGIN);
}
}
private void leaveRegionBlockedCompetition(SpleefCompetition competition) {
List<ArenaPlayer> players = new ArrayList<>(competition.getPlayers());
players.addAll(competition.getSpectators());
for (ArenaPlayer player : players) {
SpleefMessages.MATCH_REGION_NOT_WHITELISTED.send(player.getPlayer());
competition.leave(player, ArenaLeaveEvent.Cause.PLUGIN);
}
}
@@ -1,117 +1,89 @@
package org.battleplugins.arena.spleef.arena;
import io.papermc.paper.math.Position;
import org.battleplugins.arena.competition.CompetitionType;
import org.battleplugins.arena.competition.JoinResult;
import org.battleplugins.arena.competition.LiveCompetition;
import org.battleplugins.arena.competition.PlayerRole;
import org.battleplugins.arena.competition.map.options.Bounds;
import org.battleplugins.arena.spleef.ArenaSpleef;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.battleplugins.arena.spleef.SpleefConfig;
import org.battleplugins.arena.spleef.SpleefMessages;
import org.battleplugins.arena.spleef.WorldGuardSupport;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.entity.Player;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class SpleefCompetition extends LiveCompetition<SpleefCompetition> {
private final SpleefArena arena;
private final SpleefMap map;
private static final JoinResult REGION_NOT_JOINABLE = new JoinResult(false,
SpleefMessages.MATCH_REGION_NOT_WHITELISTED);
private final List<BukkitTask> decayTasks = new ArrayList<>();
private final List<Position> pendingDecays = new ArrayList<>();
private final SpleefMap map;
public SpleefCompetition(SpleefArena arena, CompetitionType type, SpleefMap map) {
super(arena, type, map);
this.arena = arena;
this.map = map;
}
public void beginLayerDecay() {
@Override
public CompletableFuture<JoinResult> canJoin(Collection<Player> players, PlayerRole role) {
List<SpleefMap.Layer> layers = new ArrayList<>(this.map.getLayers());
layers.sort((layer1, layer2) -> layer2.getBounds().getMaxY() - layer1.getBounds().getMaxY());
if (!this.isMatchRegionAllowed()) {
for (int i = 0; i < layers.size(); i++) {
SpleefMap.Layer layer = layers.get(i);
long delay = (i + 1) * this.arena.getLayerDecayDelay().toSeconds() * 20;
BukkitTask decayTask = Bukkit.getScheduler().runTaskLater(ArenaSpleef.getInstance(), () -> {
Duration layerDecayTime = this.arena.getLayerDecayTime();
List<Block> blocks = new ArrayList<>(layer.getBounds().getVolume());
for (int x = layer.getBounds().getMinX(); x <= layer.getBounds().getMaxX(); x++) {
for (int y = layer.getBounds().getMinY(); y <= layer.getBounds().getMaxY(); y++) {
for (int z = layer.getBounds().getMinZ(); z <= layer.getBounds().getMaxZ(); z++) {
blocks.add(this.map.getWorld().getBlockAt(x, y, z));
}
}
}
if (blocks.isEmpty()) {
return;
}
Collections.shuffle(blocks);
long totalDecayTimeTicks = layerDecayTime.getSeconds() * 20;
long intervalBetweenDecayTicks = Math.max(1L, totalDecayTimeTicks / blocks.size());
this.decayTasks.add(new BukkitRunnable() {
private int index = 0;
@Override
public void run() {
if (index >= blocks.size()) {
this.cancel();
return;
}
Block block = blocks.get(index);
block.setType(Material.AIR);
this.index++;
}
}.runTaskTimer(ArenaSpleef.getInstance(), 0, intervalBetweenDecayTicks));
}, delay);
this.decayTasks.add(decayTask);
return CompletableFuture.completedFuture(REGION_NOT_JOINABLE);
}
return super.canJoin(players, role);
}
public void stopLayerDecay() {
public boolean isMatchRegionAllowed() {
for (BukkitTask decayTask : this.decayTasks) {
SpleefConfig config = ArenaSpleef.getInstance().getMainConfig();
if (config == null || !config.hasRegionWhitelist()) {
decayTask.cancel();
return true;
}
this.decayTasks.clear();
World world = this.map.getWorld();
if (world == null) {
return false;
}
String worldGuardRegion = this.map.getWorldGuardRegion();
if (config.isRegionAllowed(worldGuardRegion) && WorldGuardSupport.regionExists(world, worldGuardRegion)) {
return true;
}
List<SpleefMap.Layer> layers = this.map.getLayers();
if (!layers.isEmpty()) {
return layers.stream().allMatch(layer -> this.isBoundsInAllowedRegion(layer.getBounds()));
}
return this.map.bounds().map(this::isBoundsInAllowedRegion).orElse(false);
}
public boolean isLocationAllowed(Location location) {
SpleefConfig config = ArenaSpleef.getInstance().getMainConfig();
return config == null || config.isRegionAllowedAt(location);
}
@@ -127,6 +99,12 @@ public class SpleefCompetition extends LiveCompetition<SpleefCompetition> {
for (int z = layer.getBounds().getMinZ(); z <= layer.getBounds().getMaxZ(); z++) {
Block block = this.map.getWorld().getBlockAt(x, y, z);
if (!this.isLocationAllowed(block.getLocation())) {
continue;
}
block.setBlockData(layer.getBlockData());
}
@@ -139,24 +117,23 @@ public class SpleefCompetition extends LiveCompetition<SpleefCompetition> {
}
public void decayBlock(Position position) {
private boolean isBoundsInAllowedRegion(Bounds bounds) {
if (this.pendingDecays.contains(position)) {
World world = this.map.getWorld();
if (world == null) {
return;
return false;
}
this.pendingDecays.add(position);
Bukkit.getScheduler().runTaskLater(ArenaSpleef.getInstance(), () -> {
Block block = this.map.getWorld().getBlockAt(position.blockX(), position.blockY(), position.blockZ());
block.setType(Material.AIR);
this.pendingDecays.remove(position);
}, 5);
return this.isLocationAllowed(new Location(world, bounds.getMinX(), bounds.getMinY(), bounds.getMinZ()))
&& this.isLocationAllowed(new Location(world, bounds.getMinX(), bounds.getMinY(), bounds.getMaxZ()))
&& this.isLocationAllowed(new Location(world, bounds.getMinX(), bounds.getMaxY(), bounds.getMinZ()))
&& this.isLocationAllowed(new Location(world, bounds.getMinX(), bounds.getMaxY(), bounds.getMaxZ()))
&& this.isLocationAllowed(new Location(world, bounds.getMaxX(), bounds.getMinY(), bounds.getMinZ()))
&& this.isLocationAllowed(new Location(world, bounds.getMaxX(), bounds.getMinY(), bounds.getMaxZ()))
&& this.isLocationAllowed(new Location(world, bounds.getMaxX(), bounds.getMaxY(), bounds.getMinZ()))
&& this.isLocationAllowed(new Location(world, bounds.getMaxX(), bounds.getMaxY(), bounds.getMaxZ()));
}
@@ -1,5 +1,5 @@
package org.battleplugins.arena.spleef.arena;
public enum SpleefGame {
CLASSIC, SPLEGG, DECAY, BOW_SPLEEF
CLASSIC, BOW_SPLEEF
}
@@ -28,7 +28,7 @@ public class SpleefMap extends LiveCompetitionMap {
@ArenaOption(name = "death-region", description = "The region where players will die if they fall into.")
private Bounds deathRegion;
@ArenaOption(name = "worldguard-region", description = "The WorldGuard region ID used for Bow Spleef TNT.")
@ArenaOption(name = "worldguard-region", description = "The WorldGuard region ID used for match region checks and Bow Spleef TNT.")
private String worldGuardRegion;
private final Map<Position, Layer> positionToLayers = new HashMap<>();
@@ -194,6 +194,12 @@ public class SpleefMap extends LiveCompetitionMap {
}
public void setWorldGuardRegion(String worldGuardRegion) {
this.worldGuardRegion = worldGuardRegion;
}
public static class Layer {
@ArenaOption(name = "bounds", description = "The bounds of this layer.", required = true)
+3 -3
View File
@@ -1,5 +1,7 @@
name: Spleef
type: Match
# Optional. If config.yml region-whitelist is set, use a whitelisted region ID here.
# worldguard-region: spleef_classic
team-options:
named-teams: false
team-size: 1
@@ -14,8 +16,6 @@ victory-conditions:
amount: 1
time-limit:
time-limit: 5m
layer-decay-delay: 2m
layer-decay-time: 1m
game: classic
events:
on-join:
@@ -115,4 +115,4 @@ phases:
- play-sound{sound=block.anvil.place;pitch=0;volume=1}
on-draw:
- send-message{message=<yellow>It's a draw!</yellow>}
- play-sound{sound=block.beacon.deactivate;pitch=0;volume=1}
- play-sound{sound=block.beacon.deactivate;pitch=0;volume=1}
+7 -7
View File
@@ -7,16 +7,16 @@
# -----------------
config-version: 2.0 # The config version, do not change!
# Whether projectiles (i.e. snowballs) should break blocks or not.
projectiles-break-blocks: false
# Worlds where Spleef arenas may be created and where matches may run.
# Leave empty to allow every world. Names are matched case-insensitively.
world-whitelist: []
world-whitelist:
- world
# The shovels to use for Spleef.
# WorldGuard regions where Spleef arenas may be created and matches may modify blocks.
# Leave empty to allow any region in an allowed world.
region-whitelist: []
# The tools to use for Spleef.
shovels:
classic: diamond_shovel{display-name=<yellow>Spleef Shovel}
super-shovel: golden_shovel{display-name=<aqua><bold>Super Spleef Shovel;enchantments=[unbreaking:10,efficiency:5]}
egg-cannon: netherite_shovel{display-name=<gold><bold>Egg Cannon;enchantments=[unbreaking:10,efficiency:5];item-flags=[hide_enchants]}
bow: bow{display-name=<red><bold>Fire Bow;enchantments=[unbreaking:10,infinity:1,flame:1]}
+1 -1
View File
@@ -1,7 +1,7 @@
name: Spleef-OG
main: org.battleplugins.arena.spleef.ArenaSpleef
version: ${version}
description: A Spleef mode for BattleArena.
description: Classic and Bow Spleef modes for BattleArena WorldGuard regions.
author: BattlePlugins
website: https://battleplugins.org
api-version: ${apiVersion}
+1 -3
View File
@@ -15,10 +15,8 @@ victory-conditions:
amount: 1
time-limit:
time-limit: 5m
layer-decay-delay: 2m
layer-decay-time: 1m
game: bow_spleef
# Required for Bow Spleef TNT handling.
# Required for Bow Spleef TNT handling. If config.yml region-whitelist is set, use a whitelisted region ID here.
# worldguard-region: bowspleef
events:
on-join:
+3 -3
View File
@@ -1,5 +1,7 @@
name: Spleef
type: Match
# Optional. If config.yml region-whitelist is set, use a whitelisted region ID here.
# worldguard-region: spleef_classic
team-options:
named-teams: false
team-size: 1
@@ -14,8 +16,6 @@ victory-conditions:
amount: 1
time-limit:
time-limit: 5m
layer-decay-delay: 2m
layer-decay-time: 1m
game: classic
events:
on-join:
@@ -115,4 +115,4 @@ phases:
- play-sound{sound=block.anvil.place;pitch=0;volume=1}
on-draw:
- send-message{message=<yellow>It's a draw!</yellow>}
- play-sound{sound=block.beacon.deactivate;pitch=0;volume=1}
- play-sound{sound=block.beacon.deactivate;pitch=0;volume=1}
-102
View File
@@ -1,102 +0,0 @@
name: DecaySpleef
mode: Spleef
type: Match
team-options:
named-teams: false
team-size: 1
team-amount: 2+
team-selection: none
modules:
- scoreboards
lives:
enabled: false
victory-conditions:
teams-alive:
amount: 1
time-limit:
time-limit: 5m
layer-decay-delay: 2m
layer-decay-time: 1m
game: decay
events:
on-join:
- store{types=all}
- change-gamemode{gamemode=adventure}
- flight{enabled=false}
- teleport{location=waitroom}
on-spectate:
- store{types=all}
- change-gamemode{gamemode=spectator}
- flight{enabled=true}
- teleport{location=spectator}
on-leave:
- clear-effects
- restore{types=all}
- remove-scoreboard
on-death:
- clear-inventory
- respawn
- delay{ticks=1}
- change-gamemode{gamemode=adventure}
- teleport{location=waitroom}
options:
- block-break{enabled=false}
- block-place{enabled=false}
- block-drops{enabled=false}
- block-interact{enabled=false}
- item-drops{enabled=false}
- keep-inventory{enabled=true}
- keep-experience{enabled=true}
- damage-entities{option=never}
- damage-players{option=never}
initial-phase: waiting
phases:
waiting:
allow-join: true
next-phase: countdown
events:
on-start:
- apply-scoreboard{scoreboard=waiting}
on-join:
- apply-scoreboard{scoreboard=waiting}
countdown:
allow-join: false
allow-spectate: true
revert-phase: true
next-phase: ingame
countdown-time: 5s
events:
on-start:
- paste-layers
- apply-scoreboard{scoreboard=countdown}
on-complete:
- teleport{location=team_spawn}
- play-sound{sound=block.note_block.pling;pitch=2;volume=1}
ingame:
allow-join: false
allow-spectate: true
next-phase: victory
options:
- block-interact{enabled=true}
events:
on-start:
- change-gamemode{gamemode=survival}
- apply-scoreboard{scoreboard=ingame-list-alive}
victory:
allow-join: false
allow-spectate: false
next-phase: waiting
duration: 5s
events:
on-complete:
- leave
- remove-scoreboard
on-victory:
- send-message{message=<green>Congrats, you won!</green>}
- play-sound{sound=entity.player.levelup;pitch=1;volume=1}
on-lose:
- send-message{message=<red>Sorry, you lost!</red>}
- play-sound{sound=block.anvil.place;pitch=0;volume=1}
on-draw:
- send-message{message=<yellow>It's a draw!</yellow>}
- play-sound{sound=block.beacon.deactivate;pitch=0;volume=1}
-103
View File
@@ -1,103 +0,0 @@
name: Splegg
mode: Spleef
type: Match
team-options:
named-teams: false
team-size: 1+
team-amount: 2
team-selection: none
modules:
- scoreboards
lives:
enabled: false
victory-conditions:
teams-alive:
amount: 1
time-limit:
time-limit: 5m
layer-decay-delay: 2m
layer-decay-time: 1m
game: splegg
events:
on-join:
- store{types=all}
- change-gamemode{gamemode=adventure}
- flight{enabled=false}
- teleport{location=waitroom}
on-spectate:
- store{types=all}
- change-gamemode{gamemode=spectator}
- flight{enabled=true}
- teleport{location=spectator}
on-leave:
- clear-effects
- restore{types=all}
- remove-scoreboard
on-death:
- clear-inventory
- respawn
- delay{ticks=1}
- change-gamemode{gamemode=adventure}
- teleport{location=waitroom}
options:
- block-break{enabled=false}
- block-place{enabled=false}
- block-drops{enabled=false}
- block-interact{enabled=false}
- item-drops{enabled=false}
- keep-inventory{enabled=true}
- keep-experience{enabled=true}
- damage-entities{option=never}
- damage-players{option=never}
initial-phase: waiting
phases:
waiting:
allow-join: true
next-phase: countdown
events:
on-start:
- apply-scoreboard{scoreboard=waiting}
on-join:
- apply-scoreboard{scoreboard=waiting}
countdown:
allow-join: false
allow-spectate: true
revert-phase: true
next-phase: ingame
countdown-time: 5s
events:
on-start:
- paste-layers
- apply-scoreboard{scoreboard=countdown}
on-complete:
- teleport{location=team_spawn}
- play-sound{sound=block.note_block.pling;pitch=2;volume=1}
ingame:
allow-join: false
allow-spectate: true
next-phase: victory
options:
- block-interact{enabled=true}
events:
on-start:
- change-gamemode{gamemode=survival}
- give-shovel{shovel=egg-cannon}
- apply-scoreboard{scoreboard=ingame-list-alive}
victory:
allow-join: false
allow-spectate: false
next-phase: waiting
duration: 5s
events:
on-complete:
- leave
- remove-scoreboard
on-victory:
- send-message{message=<green>Congrats, you won!</green>}
- play-sound{sound=entity.player.levelup;pitch=1;volume=1}
on-lose:
- send-message{message=<red>Sorry, you lost!</red>}
- play-sound{sound=block.anvil.place;pitch=0;volume=1}
on-draw:
- send-message{message=<yellow>It's a draw!</yellow>}
- play-sound{sound=block.beacon.deactivate;pitch=0;volume=1}