Initial commit.

This commit is contained in:
2022-05-29 19:09:00 -04:00
commit a5ccc11b40
23 changed files with 4213 additions and 0 deletions

View File

@@ -0,0 +1,616 @@
/*
* Copyright 2022 Logan Fick
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
package dev.logal.snowbrawl;
import dev.logal.snowbrawl.managers.MatchManager;
import dev.logal.snowbrawl.types.Arena;
import net.nemez.cmdmgr.Command;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.List;
/**
* Handles all the different commands implemented by Snowbrawl through the Command Manager.
*/
public final class CommandHandler {
private final Snowbrawl snowbrawl;
/**
* Creates a new command handler which is owned by a given instance of Snowbrawl.
*
* @param snowbrawl The instance of Snowbrawl which owns this command handler. Used for logging and to configure events.
*/
public CommandHandler(final Snowbrawl snowbrawl){
this.snowbrawl = snowbrawl;
}
/**
* Generates a chat message with the plugin name and formatting to indicate successful execution.
*
* @param message The message to format.
*
* @return The message with formatting applied.
*/
public static String successMessage(final String message){
return ChatColor.DARK_GREEN + "[" + ChatColor.GREEN + "Snowbrawl" + ChatColor.DARK_GREEN + "] " + ChatColor.GREEN + message;
}
/**
* Generates a chat message with the plugin name and formatting to indicate failed execution.
*
* @param message The message to format.
*
* @return The message with formatting applied.
*/
public static String failMessage(final String message){
return ChatColor.DARK_RED + "[" + ChatColor.RED + "Snowbrawl" + ChatColor.DARK_RED + "] " + ChatColor.RED + message;
}
/**
* Joins or removes the player from the queue.
*
* @param sender The user who executed the command.
*/
@Command(hook = "join_queue")
public void commandJoinQueue(CommandSender sender){
MatchManager matchManager = this.snowbrawl.getMatchManager();
Player player = (Player) sender;
if (matchManager.isInQueue(player)){
matchManager.removeFromQueue(player);
sender.sendMessage(successMessage("You have been removed from the queue."));
} else {
matchManager.addToQueue(player);
sender.sendMessage(successMessage("You have been added to the queue."));
}
}
/**
* Lists all known arenas.
*
* @param sender The user who executed the command.
*/
@Command(hook = "get_arenas")
public void commandArenaList(CommandSender sender){
List<Arena> arenas = this.snowbrawl.getArenaManager().getArenas();
if (arenas.size() == 0){
sender.sendMessage(successMessage("There are no known arenas."));
} else {
sender.sendMessage(successMessage("The following arenas are known:"));
for (Arena arena : arenas){
sender.sendMessage(successMessage(" - " + arena.getName()));
}
}
}
/**
* Creates a new arena with a given name, inferring the world name from the player's position.
*
* @param sender The user who executed the command.
* @param name The name for the new arena.
*/
@Command(hook = "create_arena")
public void commandArenaCreate(CommandSender sender, String name){
Player player = (Player) sender; // CommandManager blocks console from executing this command, so this should never fail.
Arena arena;
try{
arena = new Arena(name, player.getWorld());
} catch (final IllegalArgumentException exception){
sender.sendMessage(failMessage(exception.getMessage()));
return;
}
try{
this.snowbrawl.getArenaManager().registerArena(arena);
} catch (final IllegalArgumentException exception){
sender.sendMessage(failMessage(exception.getMessage()));
return;
}
sender.sendMessage(successMessage("Successfully created new arena " + arena.getName() + "."));
}
/**
* Creates a new arena with a given name, inferring the world name from the player's position.
*
* @param sender The user who executed the command.
* @param name The name for the new arena.
*/
@Command(hook = "delete_arena")
public void commandArenaDelete(CommandSender sender, String name){
Arena arena = this.snowbrawl.getArenaManager().getArenaByName(name);
if (arena == null){
sender.sendMessage(failMessage("The specified arena does not exist."));
return;
}
this.snowbrawl.getArenaManager().unregisterArena(arena);
sender.sendMessage(successMessage("Successfully deleted arena " + arena.getName() + "."));
}
/**
* Creates a new arena with a given name in a specific world.
*
* @param sender The user who executed the command.
* @param name The name for the new arena.
* @param worldName The name of the world to create the arena in.
*/
@Command(hook = "create_arena_in_specific_world")
public void commandArenaCreate(CommandSender sender, String name, String worldName){
World world = Bukkit.getWorld(worldName);
if (world == null){
sender.sendMessage(failMessage("The specified world name does not exist."));
return;
}
Arena arena;
try{
arena = new Arena(name, world);
} catch (final IllegalArgumentException exception){
sender.sendMessage(failMessage(exception.getMessage()));
return;
}
try{
this.snowbrawl.getArenaManager().registerArena(arena);
} catch (final IllegalArgumentException exception){
sender.sendMessage(failMessage(exception.getMessage()));
return;
}
sender.sendMessage(successMessage("A new arena named " + arena.getName() + " has been created."));
}
/**
* Prints all configuration options and their values for an arena.
*
* @param sender The user who executed the command.
* @param name The name of the arena.
*/
@Command(hook = "get_arena_config")
public void commandArenaConfig(CommandSender sender, String name){
Arena arena = this.snowbrawl.getArenaManager().getArenaByName(name);
if (arena == null){
sender.sendMessage(failMessage("The specified arena does not exist."));
return;
}
sender.sendMessage(successMessage("Configuration variables for arena " + arena.getName()) + ":");
sender.sendMessage(successMessage(" - Refill: " + arena.getRefill() + " snowballs"));
sender.sendMessage(successMessage(" - Player Limit: " + arena.getPlayerLimit() + " players"));
sender.sendMessage(successMessage(" - Grace Time: " + (arena.getGraceTime() / 20) + "s"));
sender.sendMessage(successMessage(" - Match Time: " + (arena.getMatchTime() / 20) + "s"));
sender.sendMessage(successMessage(" - Damage: " + arena.getDamage() + " HP"));
sender.sendMessage(successMessage(" - World: " + arena.getWorld().getName()));
Location pos1 = arena.getPos1(), pos2 = arena.getPos2();
sender.sendMessage(successMessage(" - Pos1: " + pos1.getX() + ", " + pos1.getY() + ", " + pos1.getZ()));
sender.sendMessage(successMessage(" - Pos2: " + pos2.getX() + ", " + pos2.getY() + ", " + pos2.getZ()));
}
/**
* Changes the name of an arena.
*
* @param sender The user who executed the command.
* @param name The name of the arena.
* @param newName The new name for the arena.
*/
@Command(hook = "set_arena_name")
public void commandArenaConfigName(CommandSender sender, String name, String newName){
Arena arena = this.snowbrawl.getArenaManager().getArenaByName(name);
if (arena == null){
sender.sendMessage(failMessage("The specified arena does not exist."));
return;
}
if (this.snowbrawl.getArenaManager().getArenaByName(newName) != null){
sender.sendMessage(failMessage("An arena with that name already exists."));
return;
}
String oldName = arena.getName();
try{
arena.setName(newName);
} catch (final IllegalArgumentException exception){
sender.sendMessage(failMessage(exception.getMessage()));
return;
}
sender.sendMessage(successMessage("The name for arena " + oldName + " has been changed to " + arena.getName()));
this.snowbrawl.saveConfig();
}
/**
* Changes the amount of snowballs given when a player refills their stock in an arena
*
* @param sender The user who executed the command.
* @param name The name of the arena.
* @param amount The new refill amount for the arena.
*/
@Command(hook = "set_arena_refill")
public void commandArenaConfigRefill(CommandSender sender, String name, int amount){
Arena arena = this.snowbrawl.getArenaManager().getArenaByName(name);
if (arena == null){
sender.sendMessage(failMessage("The specified arena does not exist."));
return;
}
int oldRefill = arena.getRefill();
try{
arena.setRefill(amount);
} catch (final IllegalArgumentException exception){
sender.sendMessage(failMessage(exception.getMessage()));
return;
}
sender.sendMessage(successMessage("The refill for arena " + arena.getName() + " has been changed from " + oldRefill + " snowballs to " + arena.getRefill() + " snowballs."));
this.snowbrawl.saveConfig();
}
/**
* Changes the maximum amount of players allowed to play in an arena.
*
* @param sender The user who executed the command.
* @param name The name of the arena.
* @param players The new player limit for the arena.
*/
@Command(hook = "set_arena_player_limit")
public void commandArenaConfigPlayers(CommandSender sender, String name, int players){
Arena arena = this.snowbrawl.getArenaManager().getArenaByName(name);
if (arena == null){
sender.sendMessage(failMessage("The specified arena does not exist."));
return;
}
int oldPlayerLimit = arena.getPlayerLimit();
try{
arena.setPlayerLimit(players);
} catch (final IllegalArgumentException exception){
sender.sendMessage(failMessage(exception.getMessage()));
return;
}
sender.sendMessage(successMessage("The player limit for arena " + arena.getName() + " has been changed from " + oldPlayerLimit + " players to " + arena.getPlayerLimit() + " players."));
this.snowbrawl.saveConfig();
}
/**
* Changes the amount of grace time allowed before each match in an arena.
*
* @param sender The user who executed the command.
* @param name The name of the arena.
* @param seconds The amount of grace time in seconds for the arena.
*/
@Command(hook = "set_arena_grace_time")
public void commandArenaConfigGraceTime(CommandSender sender, String name, int seconds){
Arena arena = this.snowbrawl.getArenaManager().getArenaByName(name);
if (arena == null){
sender.sendMessage(failMessage("The specified arena does not exist."));
return;
}
int oldGraceTime = arena.getGraceTime();
try{
arena.setGraceTime(seconds * 20);
} catch (final IllegalArgumentException exception){
sender.sendMessage(failMessage(exception.getMessage()));
return;
}
sender.sendMessage(successMessage("The grace time for arena " + arena.getName() + " has been changed from " + (oldGraceTime / 20) + " seconds to " + (arena.getGraceTime() / 20) + " seconds."));
this.snowbrawl.saveConfig();
}
/**
* Changes the amount of match time for each match in an arena.
*
* @param sender The user who executed the command.
* @param name The name of the arena.
* @param seconds The amount of match time in seconds for the arena.
*/
@Command(hook = "set_arena_match_time")
public void commandArenaConfigMatchTime(CommandSender sender, String name, int seconds){
Arena arena = this.snowbrawl.getArenaManager().getArenaByName(name);
if (arena == null){
sender.sendMessage(failMessage("The specified arena does not exist."));
return;
}
int oldMatchTime = arena.getMatchTime();
try{
arena.setMatchTime(seconds * 20);
} catch (final IllegalArgumentException exception){
sender.sendMessage(failMessage(exception.getMessage()));
return;
}
sender.sendMessage(successMessage("The match time for arena " + arena.getName() + " has been changed from " + (oldMatchTime / 20) + " seconds to " + (arena.getMatchTime() / 20) + " seconds."));
this.snowbrawl.saveConfig();
}
/**
* Changes the amount of damage done per snowball hit in this arena.
*
* @param sender The user who executed the command.
* @param name The name of the arena.
* @param damage The amount of damage for this arena.
*/
@Command(hook = "set_arena_damage")
public void commandArenasDamage(CommandSender sender, String name, int damage){
Arena arena = this.snowbrawl.getArenaManager().getArenaByName(name);
if (arena == null){
sender.sendMessage(failMessage("The specified arena does not exist."));
return;
}
int oldDamage = arena.getDamage();
try{
arena.setDamage(damage);
} catch (final IllegalArgumentException exception){
sender.sendMessage(failMessage(exception.getMessage()));
return;
}
sender.sendMessage(successMessage("The damage for arena " + arena.getName() + " has been changed from " + oldDamage + " HP to " + arena.getDamage() + " HP."));
this.snowbrawl.saveConfig();
}
/**
* Sets one corner of this arena's bounds to the given coordinates.
*
* @param sender The user who executed the command
* @param name The name of the arena.
* @param x The X coordinate for the corner.
* @param y The Y coordinate for the corner.
* @param z The Z coordinate for the corner.
*/
@Command(hook = "set_arena_pos1")
public void commandArenaConfigBoundsPos1(CommandSender sender, String name, int x, int y, int z){
Arena arena = this.snowbrawl.getArenaManager().getArenaByName(name);
if (arena == null){
sender.sendMessage(failMessage("The specified arena does not exist."));
return;
}
Location oldPos1 = arena.getPos1();
try{
arena.setPos1(new Location(null, x, y, z));
} catch (final IllegalStateException exception){
sender.sendMessage(failMessage(exception.getMessage()));
return;
}
Location newPos1 = arena.getPos1();
sender.sendMessage(successMessage("Pos1 for arena " + arena.getName() + " has been changed from " + oldPos1.getX() + "/" + oldPos1.getY() + "/" + oldPos1.getZ() + " to " + newPos1.getX() + "/" + newPos1.getY() + "/" + newPos1.getZ() + "."));
this.snowbrawl.saveConfig();
}
/**
* Sets one corner of this arena's bounds to the player's current position.
*
* @param sender The user who executed the command
* @param name The name of the arena.
*/
@Command(hook = "set_arena_pos1_from_playerpos")
public void commandArenaConfigBoundsPos1FromPlayerPos(CommandSender sender, String name){
Arena arena = this.snowbrawl.getArenaManager().getArenaByName(name);
if (arena == null){
sender.sendMessage(failMessage("The specified arena does not exist."));
return;
}
Location playerPos = ((Player) sender).getLocation(); // CommandManager blocks console from executing this command, so this should never fail.
this.commandArenaConfigBoundsPos1(sender, name, (int) playerPos.getX(), (int) playerPos.getY(), (int) playerPos.getZ());
}
/**
* Sets the other corner of this arena's bounds to the given coordinates.
*
* @param sender The user who executed the command
* @param name The name of the arena.
* @param x The X coordinate for the corner.
* @param y The Y coordinate for the corner.
* @param z The Z coordinate for the corner.
*/
@Command(hook = "set_arena_pos2")
public void commandArenaConfigBoundsPos2(CommandSender sender, String name, int x, int y, int z){
Arena arena = this.snowbrawl.getArenaManager().getArenaByName(name);
if (arena == null){
sender.sendMessage(failMessage("The specified arena does not exist."));
return;
}
Location oldPos2 = arena.getPos2();
try{
arena.setPos2(new Location(null, x, y, z));
} catch (final IllegalStateException exception){
sender.sendMessage(failMessage(exception.getMessage()));
return;
}
Location newPos2 = arena.getPos2();
sender.sendMessage(successMessage("Pos2 for arena " + arena.getName() + " has been changed from " + oldPos2.getX() + "/" + oldPos2.getY() + "/" + oldPos2.getZ() + " to " + newPos2.getX() + "/" + newPos2.getY() + "/" + newPos2.getZ() + "."));
this.snowbrawl.saveConfig();
}
/**
* Sets the other corner of this arena's bounds to the player's current position.
*
* @param sender The user who executed the command
* @param name The name of the arena.
*/
@Command(hook = "set_arena_pos2_from_playerpos")
public void commandArenaConfigBoundsPos2FromPlayerPos(CommandSender sender, String name){
Arena arena = this.snowbrawl.getArenaManager().getArenaByName(name);
if (arena == null){
sender.sendMessage(failMessage("The specified arena does not exist."));
return;
}
Location playerPos = ((Player) sender).getLocation(); // CommandManager blocks console from executing this command, so this should never fail.
this.commandArenaConfigBoundsPos2(sender, name, (int) playerPos.getX(), (int) playerPos.getY(), (int) playerPos.getZ());
}
/**
* Sets this arena's bounds to the given pair of coordinates.
*
* @param sender The user who executed the command.
* @param name The name of the arena.
* @param x1 The X coordinate for Pos1.
* @param y1 The Y coordinate for Pos1.
* @param z1 The Z coordinate for Pos1.
* @param x2 The X coordinate for Pos2.
* @param y2 The Y coordinate for Pos2.
* @param z2 The Z coordinate for Pos2.
*/
@Command(hook = "set_arena_bounds")
public void commandArenaConfigBounds(CommandSender sender, String name, int x1, int y1, int z1, int x2, int y2, int z2){
Arena arena = this.snowbrawl.getArenaManager().getArenaByName(name);
if (arena == null){
sender.sendMessage(failMessage("The specified arena does not exist."));
return;
}
this.commandArenaConfigBoundsPos1(sender, name, x1, y1, z1);
this.commandArenaConfigBoundsPos2(sender, name, x2, y2, z2);
}
/**
* Lists an arena's spawn points.
*
* @param sender The user who executed the command.
* @param name The name of the arena.
*/
@Command(hook = "get_arena_spawnpoints")
public void commandArenaConfigSpawnpointsList(CommandSender sender, String name){
Arena arena = this.snowbrawl.getArenaManager().getArenaByName(name);
if (arena == null){
sender.sendMessage(failMessage("The specified arena does not exist."));
return;
}
if (arena.getSpawnPoints().size() == 0){
sender.sendMessage(successMessage("Arena " + arena.getName() + " has no spawn points."));
} else {
sender.sendMessage(successMessage("Arena " + arena.getName() + " has the following spawn points:"));
List<Location> spawnPoints = arena.getSpawnPoints();
for (int i = 0; i < spawnPoints.size(); i++){
Location spawnPoint = spawnPoints.get(i);
sender.sendMessage(successMessage(" - #" + i + ": " + spawnPoint.getX() + "/" + spawnPoint.getY() + "/" + spawnPoint.getZ() + "/" + spawnPoint.getYaw() + "/" + spawnPoint.getPitch()));
}
}
}
/**
* Adds a player's current location as a spawn point.
*
* @param sender The user who executed the command.
* @param name The name of the arena.
*/
@Command(hook = "add_arena_spawnpoint_from_playerpos")
public void commandArenaConfigSpawnpointsAddFromPlayerPos(CommandSender sender, String name){
Arena arena = this.snowbrawl.getArenaManager().getArenaByName(name);
if (arena == null){
sender.sendMessage(failMessage("The specified arena does not exist."));
return;
}
Location playerPos = ((Player) sender).getLocation(); // CommandManager blocks console from executing this command, so this should never fail.
try{
arena.addSpawnPoint(playerPos);
} catch (final IllegalArgumentException exception){
sender.sendMessage(failMessage(exception.getMessage()));
return;
}
sender.sendMessage(successMessage("A spawn point at " + playerPos.getX() + "/" + playerPos.getY() + "/" + playerPos.getZ() + "/" + playerPos.getYaw() + "/" + playerPos.getPitch() + " has been added."));
this.snowbrawl.saveConfig();
}
/**
* Adds a new spawn point with a given set of coordinates.
*
* @param sender The user who executed the command.
* @param name The name of the arena.
* @param x The X coordinate for the spawn point.
* @param y The Y coordiante for the spawn point.
* @param z The Z coordinate for the spawn point.
* @param yaw The yaw for the spawn point.
* @param pitch The pitch for the spawn point.
*/
@Command(hook = "add_arena_spawnpoint")
public void commandArenaConfigSpawnpointsAdd(CommandSender sender, String name, double x, double y, double z, double yaw, double pitch){
Arena arena = this.snowbrawl.getArenaManager().getArenaByName(name);
if (arena == null){
sender.sendMessage(failMessage("The specified arena does not exist."));
return;
}
Location coordinates = new Location(arena.getWorld(), x, y, z, (float) yaw, (float) pitch); // TODO: Is an upstream fix to CommandManager required?
try{
arena.addSpawnPoint(coordinates);
} catch (final IllegalArgumentException exception){
sender.sendMessage(failMessage(exception.getMessage()));
return;
}
sender.sendMessage(successMessage("A spawn point at " + coordinates.getX() + "/" + coordinates.getY() + "/" + coordinates.getZ() + "/" + coordinates.getYaw() + "/" + coordinates.getPitch() + " has been added."));
this.snowbrawl.saveConfig();
}
/**
* Deletes a spawn point from an arena.
*
* @param sender The user who executed the command.
* @param name The name of the arena.
* @param id The ID of the spawn point. Obtainable
*/
@Command(hook = "delete_arena_spawnpoint")
public void commandArenaConfigSpawnpointsDelete(CommandSender sender, String name, int id){
Arena arena = this.snowbrawl.getArenaManager().getArenaByName(name);
if (arena == null){
sender.sendMessage(failMessage("The specified arena does not exist."));
return;
}
Location spawnpoint;
try{
spawnpoint = arena.getSpawnPoints().get(id);
} catch (final IndexOutOfBoundsException exception){
sender.sendMessage(failMessage("The specified spawn point ID does not exist."));
return;
}
arena.removeSpawnPoint(spawnpoint);
sender.sendMessage(successMessage("The spawn point at " + spawnpoint.getX() + "/" + spawnpoint.getY() + "/" + spawnpoint.getZ() + "/" + spawnpoint.getYaw() + "/" + spawnpoint.getPitch() + " has been deleted."));
this.snowbrawl.saveConfig();
}
/**
* Teleports to a random spawn point within an arena.
*
* @param sender The user who executed the command.
* @param name The name of the arena.
*/
@Command(hook = "go_to_arena")
public void commandArenaTeleport(CommandSender sender, String name){
Arena arena = this.snowbrawl.getArenaManager().getArenaByName(name);
if (arena == null){
sender.sendMessage(failMessage("The specified arena does not exist."));
return;
}
((Player) sender).teleport(arena.getRandomSpawnPoint()); // CommandManager blocks console from executing this command, so this should never fail.
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright 2022 Logan Fick
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
package dev.logal.snowbrawl;
import dev.logal.snowbrawl.managers.MatchManager;
import dev.logal.snowbrawl.types.Match;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.GameMode;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.event.player.PlayerJoinEvent;
/**
* Handles leftover events which do not really fit within the match or arena manager.
*/
public final class EventsHandler implements Listener {
final MatchManager matchManager;
/**
* Creates a new event handler which is owned by a given instance of Snowbrawl.
*
* @param snowbrawl The instance of Snowbrawl which owns this event handler. Used for querying the match manager for information.
*/
public EventsHandler(final Snowbrawl snowbrawl){
this.matchManager = snowbrawl.getMatchManager();
}
/**
* Sets the default state of players who join the server.
*
* @param event The event.
*/
@EventHandler
public void onPlayerJoin(final PlayerJoinEvent event){
Player player = event.getPlayer();
player.setGameMode(GameMode.SPECTATOR);
player.teleport(player.getWorld().getSpawnLocation());
// Reset player stats to known normal values.
player.setHealth(20);
player.setLevel(0);
player.setTotalExperience(0);
player.getInventory().clear();
player.updateInventory();
// Decorate the player list a little.
player.setPlayerListHeader(ChatColor.WHITE + "" + ChatColor.BOLD + "Snow" + ChatColor.AQUA + "" + ChatColor.BOLD + "Brawl " + ChatColor.BLUE + Bukkit.getServer().getBukkitVersion().split("-")[0]);
}
/**
* Rewrites the format of chat to convey information about players in matches.
*
* @param event The event.
*/
@EventHandler
public void onAsyncPlayerChat(final AsyncPlayerChatEvent event){
final Player player = event.getPlayer();
Match match = this.matchManager.getMatchFromPlayer(player);
if (player.isOp()){
if (match != null){
event.setFormat(ChatColor.BLUE + "[" + match.getArena().getName() + "] " + ChatColor.RED + "%s" + ChatColor.WHITE + ": %s");
} else if (this.matchManager.isInQueue(player)){
event.setFormat(ChatColor.AQUA + "[Queued] " + ChatColor.WHITE + ChatColor.RED + "%s" + ChatColor.WHITE + ": %s");
} else {
event.setFormat(ChatColor.RED + "%s" + ChatColor.WHITE + ": %s");
}
} else {
if (match != null){
event.setFormat(ChatColor.BLUE + "[" + match.getArena().getName() + "] " + ChatColor.WHITE + "%s: %s");
} else if (this.matchManager.isInQueue(player)){
event.setFormat(ChatColor.AQUA + "[Queued] " + ChatColor.WHITE + "%s: %s");
} else {
event.setFormat(ChatColor.WHITE + "%s: %s");
}
}
}
}

View File

@@ -0,0 +1,211 @@
/*
* Copyright 2022 Logan Fick
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
package dev.logal.snowbrawl;
import dev.logal.snowbrawl.managers.ArenaManager;
import dev.logal.snowbrawl.managers.BossBarManager;
import dev.logal.snowbrawl.managers.MatchManager;
import dev.logal.snowbrawl.types.Arena;
import net.nemez.cmdmgr.CommandManager;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
public final class Snowbrawl extends JavaPlugin {
private static final String COMMAND_FILE_PATH = "/commands.cmd";
private ArenaManager arenaManager;
private MatchManager matchManager;
private BossBarManager bossBarManager;
private int matchManagerTickTaskID = -1;
@Override
public void onEnable(){
this.loadConfig();
this.getLogger().info("Starting match manager...");
this.bossBarManager = new BossBarManager(this);
this.matchManager = new MatchManager(this);
this.matchManagerTickTaskID = this.getServer().getScheduler().scheduleSyncRepeatingTask(this, this.matchManager, 1, 1);
this.getServer().getPluginManager().registerEvents(new EventsHandler(this), this);
this.getLogger().info("Registering commands...");
CommandManager.enable(this, CommandManager.VERSION);
CommandManager.registerCommands(getClass().getResourceAsStream(COMMAND_FILE_PATH), new CommandHandler(this), this);
}
@Override
public void onDisable(){
this.getLogger().info("Unregistering commands...");
CommandManager.disable(this);
this.getLogger().info("Stopping match manager...");
this.getServer().getScheduler().cancelTask(this.matchManagerTickTaskID);
this.matchManager = null;
this.getLogger().info("Cleaning up...");
this.getBossBarManager().clearAllBossBars();
this.bossBarManager = null;
this.saveConfig();
}
/**
* Saves all arenas and their attributes to the plugin's configuration file.
*/
@Override
public void saveConfig(){
this.getLogger().info("Saving configuration file...");
// Most likely does nothing since the configuration file should exist by this point.
this.saveDefaultConfig();
// Create a new configuration file to ensure _everything_ is overridden.
YamlConfiguration newConfig = new YamlConfiguration();
// Dump arena configuration variables.
for (int id = 0; id < this.arenaManager.getArenas().size(); id++){
Arena arena = this.arenaManager.getArenas().get(id);
newConfig.set("arenas." + id + ".name", arena.getName());
newConfig.set("arenas." + id + ".refill", arena.getRefill());
newConfig.set("arenas." + id + ".playerLimit", arena.getPlayerLimit());
newConfig.set("arenas." + id + ".graceTime", arena.getGraceTime());
newConfig.set("arenas." + id + ".matchTime", arena.getMatchTime());
newConfig.set("arenas." + id + ".damage", arena.getDamage());
newConfig.set("arenas." + id + ".world", arena.getWorld().getName());
newConfig.set("arenas." + id + ".pos1.x", arena.getPos1().getX());
newConfig.set("arenas." + id + ".pos1.y", arena.getPos1().getY());
newConfig.set("arenas." + id + ".pos1.z", arena.getPos1().getZ());
newConfig.set("arenas." + id + ".pos2.x", arena.getPos2().getX());
newConfig.set("arenas." + id + ".pos2.y", arena.getPos2().getY());
newConfig.set("arenas." + id + ".pos2.z", arena.getPos2().getZ());
for (int spawnPointId = 0; spawnPointId < arena.getSpawnPoints().size(); spawnPointId++){
Location spawnPoint = arena.getSpawnPoints().get(spawnPointId);
newConfig.set("arenas." + id + ".spawnpoints." + spawnPointId + ".x", spawnPoint.getX());
newConfig.set("arenas." + id + ".spawnpoints." + spawnPointId + ".y", spawnPoint.getY());
newConfig.set("arenas." + id + ".spawnpoints." + spawnPointId + ".z", spawnPoint.getZ());
newConfig.set("arenas." + id + ".spawnpoints." + spawnPointId + ".yaw", spawnPoint.getYaw());
newConfig.set("arenas." + id + ".spawnpoints." + spawnPointId + ".pitch", spawnPoint.getPitch());
}
}
// Replace the JavaPlugin's config file with our new one, then save.
try{
this.getConfig().loadFromString(newConfig.saveToString());
} catch (InvalidConfigurationException exception){
// This shouldn't be possible as we are going directly from an export to an import, but maybe a cosmic ray hit the RAM.
this.getLogger().severe("An impossible internal error occurred while saving the configuration file.");
exception.printStackTrace();
}
super.saveConfig();
}
/**
* Loads all arenas from the configuration file and initializes the arena manager with the loaded arenas.
*/
public void loadConfig(){
Logger logger = this.getLogger();
logger.info("Loading configuration file...");
this.saveDefaultConfig();
FileConfiguration config = this.getConfig();
// Load arena configuration variables.
List<Arena> loadedArenas = new ArrayList<>();
for (int id = 0; id < Integer.MAX_VALUE; id++){
// An arena is considered to exist if the "name" attribute is defined. If we run into null, we probably reached the end of the ""list"".
String name = config.getString("arenas." + id + ".name");
if (name == null){
break;
}
// We now know we have a name, the only other required variable is the world. Try to use the world in the config.
String worldName = config.getString("arenas." + id + ".world");
World world = Bukkit.getWorld(worldName);
if (world == null){
// Hmm, the world doesn't exist. Let's default to the first world Bukkit gives us.
// TODO: This is blindly assuming at least one world exists. Is this guaranteed?
world = Bukkit.getWorlds().get(0);
logger.warning("The arena with name \"" + name + "\" and ID " + id + " is supposedly located in a world named \"" + worldName + "\", but that world couldn't be found. Defaulting to world named \"" + world.getName() + "\".");
}
// We can now safely create a world object.
Arena arena = new Arena(name, world);
// Finally, we populate all the configuration variables.
// If any of these config options don't exist in the config file, then default to the defaults defined in Arena.
arena.setRefill(config.getInt("arenas." + id + ".refill", Arena.DEFAULT_REFILL));
arena.setPlayerLimit(config.getInt("arenas." + id + ".playerLimit", Arena.DEFAULT_PLAYER_LIMIT));
arena.setGraceTime(config.getInt("arenas." + id + ".graceTime", Arena.DEFAULT_GRACE_TIME));
arena.setMatchTime(config.getInt("arenas." + id + ".matchTime", Arena.DEFAULT_MATCH_TIME));
arena.setDamage(config.getInt("arenas." + id + ".damage", Arena.DEFAULT_DAMAGE));
// The world can be null because Arena overrides it with the actual world. Only the coordinates matter.
arena.setPos1(new Location(null, config.getDouble("arenas." + id + ".pos1.x", Arena.DEFAULT_POS1_X), config.getDouble("arenas." + id + ".pos1.y", Arena.DEFAULT_POS1_Y), config.getDouble("arenas." + id + ".pos1.z", Arena.DEFAULT_POS1_Z)));
arena.setPos2(new Location(null, config.getDouble("arenas." + id + ".pos2.x", Arena.DEFAULT_POS2_X), config.getDouble("arenas." + id + ".pos2.y", Arena.DEFAULT_POS2_Y), config.getDouble("arenas." + id + ".pos2.z", Arena.DEFAULT_POS2_Z)));
for (int spawnPointId = 0; spawnPointId < Integer.MAX_VALUE; spawnPointId++){
// config.getDouble returns 0 if the option doesn't exist, which could be a valid coordinate.
// To test if this spawn exists, first load it as a String and test for null.
String xTest = config.getString("arenas." + id + ".spawnpoints." + spawnPointId + ".x");
if (xTest == null){
break;
}
arena.addSpawnPoint(new Location(arena.getWorld(), config.getDouble("arenas." + id + ".spawnpoints." + spawnPointId + ".x"), config.getDouble("arenas." + id + ".spawnpoints." + spawnPointId + ".y"), config.getDouble("arenas." + id + ".spawnpoints." + spawnPointId + ".z"), ((float) config.getDouble("arenas." + id + ".spawnpoints." + spawnPointId + ".yaw")), ((float) config.getDouble("arenas." + id + ".spawnpoints." + spawnPointId + ".pitch"))));
}
loadedArenas.add(arena);
}
this.arenaManager = new ArenaManager(this, loadedArenas);
}
/**
* Gets the arena manager used by this instance of Snowbrawl.
*
* @return The ArenaManager.
*/
public ArenaManager getArenaManager(){
return this.arenaManager;
}
/**
* Gets the match manager used by this instance of Snowbrawl.
*
* @return The MatchManager.
*/
public MatchManager getMatchManager(){
return this.matchManager;
}
/**
* Gets the boss bar manager used by this instance of Snowbrawl.
*
* @return The MatchManager.
*/
public BossBarManager getBossBarManager(){
return this.bossBarManager;
}
}

View File

@@ -0,0 +1,910 @@
/*
* Copyright 2022 Logan Fick
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
package dev.logal.snowbrawl.managers;
import dev.logal.snowbrawl.Snowbrawl;
import dev.logal.snowbrawl.types.Arena;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.entity.*;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.*;
import org.bukkit.event.entity.*;
import org.bukkit.event.player.*;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.spigotmc.event.entity.EntityMountEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Random;
import java.util.logging.Logger;
/**
* Keeps track of Arenas and establishes default mechanics for activities inside of arenas.
*/
public class ArenaManager implements Listener {
private final Snowbrawl snowbrawl;
private final List<Arena> arenas = new ArrayList<>(); // Holds registered arenas.
private final HashMap<Player, Arena> playersInArenas = new HashMap<>(); // Keeps track of which arena a given player is currently in.
private final Random rng = new Random();
/**
* Creates a new Arena Manager which is owned by a given instance of Snowbrawl and pre-registers a given array of given Arenas.
*
* @param snowbrawl The instance of Snowbrawl which owns this arena manager. Used for logging, save the configuration file, and registering events.
* @param initialArenas A list of Arenas to pre-register.
*/
public ArenaManager(final Snowbrawl snowbrawl, final List<Arena> initialArenas){
this.snowbrawl = snowbrawl;
for (Arena arena : initialArenas){
snowbrawl.getLogger().info("Arena loaded.");
logArenaInfo(arena);
this.arenas.add(arena);
}
this.snowbrawl.getServer().getPluginManager().registerEvents(this, this.snowbrawl);
}
/**
* Adds an arena to the pool of arenas.
*
* @param arena The arena to register.
*/
public void registerArena(final Arena arena){
if (this.getArenaByName(arena.getName()) != null){
throw new IllegalArgumentException("The specified arena name is already in use.");
}
this.snowbrawl.getLogger().info("Arena registered.");
this.logArenaInfo(arena);
this.arenas.add(arena);
this.snowbrawl.saveConfig();
}
/**
* Removes an arena from the pool of arenas.
*
* @param arena The arena to unregister.
*/
public void unregisterArena(final Arena arena){
if (arenas.remove(arena)){
this.snowbrawl.getLogger().info("Arena unregistered.");
this.logArenaInfo(arena);
this.snowbrawl.saveConfig();
}
}
/**
* Gets a copy of all known arenas.
*
* @return A List of all known Arenas.
*/
public List<Arena> getArenas(){
return new ArrayList<>(this.arenas); // Return a copy rather than the reference to the original list.
}
/**
* Gets an arena by its name.
*
* @param name The name of the arena to search for. Case-insensitive.
*
* @return The Arena with the given name if found, null otherwise.
*/
public Arena getArenaByName(final String name){
if (name != null){
for (Arena arena : this.getArenas()){
if (arena.getName().equalsIgnoreCase(name)){
return arena;
}
}
}
return null;
}
/**
* Gets the first arena whose bounding box contains the given location.
*
* @param location The location to test for.
*
* @return The arena which contains the given location, null if none.
*/
public Arena getArenaByLocation(final Location location){
if (location != null){
for (Arena arena : this.getArenas()){
if (arena.isWithinBoundingBox(location)){
return arena;
}
}
}
return null;
}
/*
* These event handlers establish the default gameplay mechanics of activity which occurs inside the bounding box of arenas.
*
* These defaults are very strict and prevent most activities from occurring in order to preserve the integrity of the arena.
* These defaults also implement most of the core mechanics of Snowbrawl, like making snowballs explode.
*
* Most restrictions here can be bypassed by simply being an operator, so they can build and modify arenas.
* It is intended for the match manager to implement the same restrictions on operators who are in matches.
*/
/**
* Prevents blocks from burning insides of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onBlockBurn(final BlockBurnEvent event){
final Arena arena = this.getArenaByLocation(event.getBlock().getLocation());
if (arena != null){
event.setCancelled(true);
}
}
/**
* Prevents non-operators from building inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onBlockCanBuild(final BlockCanBuildEvent event){
final Arena arena = this.getArenaByLocation(event.getBlock().getLocation());
final Player player = event.getPlayer();
if (arena != null && (player == null || !player.isOp())){
event.setBuildable(false);
}
}
/**
* Prevents non-operators from damaging blocks inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onBlockDamage(final BlockDamageEvent event){
final Arena arena = this.getArenaByLocation(event.getBlock().getLocation());
final Player player = event.getPlayer();
if (arena != null && !player.isOp()){
event.setCancelled(true);
}
}
/**
* Prevents blocks from creating item drops inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onBlockDropItem(final BlockDropItemEvent event){
final Arena arena = this.getArenaByLocation(event.getBlock().getLocation());
if (arena != null){
event.setCancelled(true);
}
}
/**
* Prevents blocks from creating experience orbs inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onBlockExp(final BlockExpEvent event){
final Arena arena = this.getArenaByLocation(event.getBlock().getLocation());
if (arena != null){
event.setExpToDrop(0);
}
}
/**
* Prevents blocks from exploding inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onBlockExplode(final BlockExplodeEvent event){
final Arena arena = this.getArenaByLocation(event.getBlock().getLocation());
if (arena != null){
event.setCancelled(true);
}
}
/**
* Prevents blocks from fading inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onBlockFade(final BlockFadeEvent event){
final Arena arena = this.getArenaByLocation(event.getBlock().getLocation());
if (arena != null){
event.setCancelled(true);
}
}
/**
* Prevents non-operators from fertilizing blocks inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onBlockFertilize(final BlockFertilizeEvent event){
final Arena arena = this.getArenaByLocation(event.getBlock().getLocation());
final Player player = event.getPlayer();
if (arena != null && (player == null || !player.isOp())){
event.setCancelled(true);
}
}
/**
* Prevents crops from naturally growing inside of arenas.
* Operators can manually grow crops and the amount of growth will be frozen.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onBlockGrow(final BlockGrowEvent event){
final Arena arena = this.getArenaByLocation(event.getBlock().getLocation());
if (arena != null){
event.setCancelled(true);
}
}
/**
* Prevents non-operators from igniting blocks inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onBlockIgnite(final BlockIgniteEvent event){
final Arena arena = this.getArenaByLocation(event.getBlock().getLocation());
final Player player = event.getPlayer();
if (arena != null && (player == null || !player.isOp())){
event.setCancelled(true);
}
}
/**
* Prevents non-operators from placing blocks inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onBlockPlace(final BlockPlaceEvent event){
final Arena arena = this.getArenaByLocation(event.getBlock().getLocation());
final Player player = event.getPlayer();
if (arena != null && !player.isOp()){
event.setCancelled(true);
}
}
/**
* Prevents blocks from receiving game events inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onBlockReceiveGame(final BlockReceiveGameEvent event){
final Arena arena = this.getArenaByLocation(event.getBlock().getLocation());
if (arena != null){
event.setCancelled(true);
}
}
/**
* Prevents blocks from shearing entities inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onBlockShearEntity(final BlockShearEntityEvent event){
final Arena arena = this.getArenaByLocation(event.getBlock().getLocation());
if (arena != null){
event.setCancelled(true);
}
}
/**
* Prevents anything except operators from changing cauldron levels.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onCauldronLevelChange(final CauldronLevelChangeEvent event){
final Arena arena = this.getArenaByLocation(event.getBlock().getLocation());
if (arena == null){
return;
}
final Entity entity = event.getEntity();
if (entity instanceof final Player player){
if (!player.isOp()){
event.setCancelled(true);
}
} else {
event.setCancelled(true);
}
}
/**
* Prevents leaves from naturally decaying inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onLeavesDecay(final LeavesDecayEvent event){
final Arena arena = this.getArenaByLocation(event.getBlock().getLocation());
if (arena != null){
event.setCancelled(true);
}
}
/**
* Prevents non-operators from changing signs inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onSignChange(final SignChangeEvent event){
final Arena arena = this.getArenaByLocation(event.getBlock().getLocation());
final Player player = event.getPlayer();
if (arena != null && !player.isOp()){
event.setCancelled(true);
}
}
/**
* Prevents entities from changing blocks inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onEntityChangeBlock(final EntityChangeBlockEvent event){
final Arena arena = this.getArenaByLocation(event.getBlock().getLocation());
if (arena != null){
event.setCancelled(true);
}
}
/**
* Turns snowballs which hit entities inside of arenas into damage dealing explosions.
* All other entity damage by entity is prevented.
*
* @param event The event.
*/
@EventHandler
public void onEntityDamageByEntity(final EntityDamageByEntityEvent event){
final Arena arena = this.getArenaByLocation(event.getEntity().getLocation());
if (arena != null){
final Entity attacker = event.getDamager();
// Was the entity hit directly by a snowball?
if (attacker instanceof Snowball && event.getCause().equals(EntityDamageEvent.DamageCause.PROJECTILE)){
// After hours of testing, it seems Minecraft is extremely inconsistent about dealing damage to an entity from a projectile spawning an explosion.
// To try and make direct hits deal damage consistently, let's set the event damage to whatever is configured in the arena, then spawn a 0 power decorative explosion.
// Even this doesn't seem perfectly consistent, but it's the best solution so far.
event.setDamage(arena.getDamage());
attacker.getWorld().createExplosion(attacker.getLocation(), 0f, false, false, attacker);
}
}
}
/**
* Changes drops from entities inside of arenas into snowballs.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onEntityDeath(final EntityDeathEvent event){
final Arena arena = this.getArenaByLocation(event.getEntity().getLocation());
if (arena != null){
if (event.getDrops().size() > 0){ // Was this entity about to drop anything?
event.getDrops().clear(); // Reset the drops to nothing.
event.getDrops().add(new ItemStack(Material.SNOWBALL, this.rng.nextInt(1, 4))); // Add some snowballs.
}
event.setDroppedExp(0); // Ensure no experience orbs get spawned.
}
}
/**
* Prevents entities from dropping items inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onEntityDropItem(final EntityDropItemEvent event){
final Arena arena = this.getArenaByLocation(event.getEntity().getLocation());
if (arena != null){
event.setCancelled(true);
}
}
/**
* Prevents entities from entering blocks inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onEntityEnterBlock(final EntityEnterBlockEvent event){
final Arena blockArena = this.getArenaByLocation(event.getBlock().getLocation());
if (blockArena != null){
event.setCancelled(true);
return;
}
final Arena entityArena = this.getArenaByLocation(event.getEntity().getLocation());
if (entityArena != null){
event.setCancelled(true);
}
}
/**
* Prevents entities from interacting with blocks inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onEntityInteract(final EntityInteractEvent event){
final Arena blockArena = this.getArenaByLocation(event.getBlock().getLocation());
if (blockArena != null){
event.setCancelled(true);
return;
}
final Arena entityArena = this.getArenaByLocation(event.getEntity().getLocation());
if (entityArena != null){
event.setCancelled(true);
}
}
/**
* Prevents entities from mounting entities inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onEntityMount(final EntityMountEvent event){
final Arena entityArena = this.getArenaByLocation(event.getEntity().getLocation());
if (entityArena != null){
event.setCancelled(true);
return;
}
final Arena mountArena = this.getArenaByLocation(event.getMount().getLocation());
if (mountArena != null){
event.setCancelled(true);
}
}
/**
* Except for operators, prevents entities from picking up items other than snowballs on the ground.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onEntityPickupItem(final EntityPickupItemEvent event){
final Entity entity = event.getEntity();
final Arena arena = this.getArenaByLocation(entity.getLocation());
if (arena != null){ // Is the entity picking up the item in an arena?
if (entity instanceof final Player player){ // Is the entity a player?
if (!player.isOp()){ // Is that player not an operator?
if (!event.getItem().getItemStack().getType().equals(Material.SNOWBALL)){ // Is the item not a snowball?
event.setCancelled(true);
}
}
} else {
event.setCancelled(true);
}
}
}
/**
* Prevents non-operators from placing entities from items.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onEntityPlace(final EntityPlaceEvent event){
final Player player = event.getPlayer();
final Arena entityArena = this.getArenaByLocation(player.getLocation()); // getLocation may be null, but getArenaByLocation will simply pass it through.
final Arena blockArena = this.getArenaByLocation(event.getBlock().getLocation());
if (entityArena != null || blockArena != null){ // Is the player placing the entity from or in an arena?
if (!player.isOp()){ // Is that player also not an operator?
event.setCancelled(true);
}
}
}
/**
* Converts any arrows shot inside of arenas into snowballs.
*
* @param event The event.
*/
@EventHandler
public void onEntityShootBow(final EntityShootBowEvent event){
final Entity entity = event.getEntity();
final Arena arena = this.getArenaByLocation(entity.getLocation());
if (arena != null){ // Is the entity shooting from inside an arena?
final Entity arrow = event.getProjectile(); // Get the arrow which was about to be shot.
final Entity snowball = entity.getWorld().spawnEntity(arrow.getLocation(), EntityType.SNOWBALL); // Spawn a new snowball at the arrow's location.
// TODO: This is not enough info to transfer information about the shooter. Dying entities only know they were killed by Snowball.
snowball.setVelocity(arrow.getVelocity()); // Copy the arrow's velocity to the new snowball.
snowball.setLastDamageCause(arrow.getLastDamageCause()); // Carry over damage cause from the arrow to the snowball.
event.setProjectile(snowball); // Change the projectile to the snowball.
}
}
/**
* Not implemented.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onEntitySpawn(final EntitySpawnEvent event){
// TODO: Add configurable option for preventing entity spawns in arenas.
}
/**
* Prevents entities inside of arenas from casting spells.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onEntitySpellCast(final EntitySpellCastEvent event){
final Arena arena = this.getArenaByLocation(event.getEntity().getLocation());
if (arena != null){
event.setCancelled(true);
}
}
/**
* Prevents entities inside of arenas from being tamed.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onEntityTame(final EntityTameEvent event){
final Arena arena = this.getArenaByLocation(event.getEntity().getLocation());
if (arena != null){
event.setCancelled(true);
}
}
/**
* Creates an explosion when a snowball hits a block.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onProjectileHit(final ProjectileHitEvent event){
Projectile projectile = event.getEntity();
if (projectile instanceof Snowball){ // Is the projectile a snowball?
Block hitBlock = event.getHitBlock();
if (hitBlock != null && this.getArenaByLocation(hitBlock.getLocation()) != null){ // Did the snowball hit a block inside an arena?
projectile.getWorld().createExplosion(projectile.getLocation(), 1f, false, false, projectile); // Spawn an explosion at the impact location.
}
}
}
/**
* Prevents non-operators from entering beds.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onPlayerBedEnter(final PlayerBedEnterEvent event){
final Arena arena = this.getArenaByLocation(event.getBed().getLocation());
final Player player = event.getPlayer();
if (arena != null && !player.isOp()){
event.setCancelled(true);
}
}
/**
* Prevents non-operators from capturing entities inside of buckets.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onPlayerBucketEntity(final PlayerBucketEntityEvent event){
final Arena arena = this.getArenaByLocation(event.getEntity().getLocation());
final Player player = event.getPlayer();
if (arena != null && !player.isOp()){
event.setCancelled(true);
}
}
/**
* Prevents non-operators from dropping items.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onPlayerDropItem(final PlayerDropItemEvent event){
final Player player = event.getPlayer();
final Arena arena = this.getArenaByLocation(player.getLocation());
if (arena != null && !player.isOp()){
event.setCancelled(true);
}
}
/**
* Prevents non-operators from editing books inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onPlayerEditBook(final PlayerEditBookEvent event){
final Player player = event.getPlayer();
final Arena arena = this.getArenaByLocation(player.getLocation());
if (arena != null && !player.isOp()){
event.setCancelled(true);
}
}
/**
* Prevents non-operators from fishing inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onPlayerFish(final PlayerFishEvent event){
final Player player = event.getPlayer();
final Arena arena = this.getArenaByLocation(player.getLocation());
if (arena != null && !player.isOp()){
event.setCancelled(true);
}
}
/**
* Prevents non-operators from harvesting blocks inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onPlayerHarvestBlock(final PlayerHarvestBlockEvent event){
final Arena arena = this.getArenaByLocation(event.getHarvestedBlock().getLocation());
final Player player = event.getPlayer();
if (arena != null && !player.isOp()){
event.setCancelled(true);
}
}
/**
* Prevents non-operators from interacting with entities inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onPlayerInteractEntity(final PlayerInteractEntityEvent event){
final Arena arena = this.getArenaByLocation(event.getRightClicked().getLocation());
final Player player = event.getPlayer();
if (arena != null && !player.isOp()){
event.setCancelled(true);
}
}
/**
* Makes right-clicking snow blocks inside of arenas grant snowballs in the first hotbar slot.
*
* @param event The event.
*/
@EventHandler
public void onPlayerInteract(final PlayerInteractEvent event){
final Player player = event.getPlayer();
final Arena arena = this.getArenaByLocation(player.getLocation());
if (arena != null){
// TODO: getClickedBlock doesn't only mean it was right clicked. It could also have been destroyed. This causes a bug where operators cannot delete snow blocks.
final Block rightClickedBlock = event.getClickedBlock();
if (rightClickedBlock != null && rightClickedBlock.getType().equals(Material.SNOW_BLOCK)){
event.setCancelled(true);
PlayerInventory playerInventory = event.getPlayer().getInventory();
if (!playerInventory.containsAtLeast(new ItemStack(Material.SNOWBALL, arena.getRefill()), arena.getRefill())){ // Does this player already have at least the refill amount of snowballs?
playerInventory.clear();
playerInventory.setItem(4, new ItemStack(Material.SNOWBALL, arena.getRefill())); // Set the middle hotbar slot to a new snowball stack.
playerInventory.setHeldItemSlot(4); // For convenience, set the selected hotbar slot to the same one.
player.updateInventory(); // Synchronize the player's inventory.
player.playSound(player, Sound.ITEM_ARMOR_EQUIP_GENERIC, SoundCategory.MASTER, 1f, 1f); // Provide audible confirmation of the snowballs added to the inventory.
}
}
}
}
/**
* Prevents non-operators from consuming items inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onPlayerItemConsume(final PlayerItemConsumeEvent event){
final Player player = event.getPlayer();
final Arena arena = this.getArenaByLocation(player.getLocation());
if (arena != null && !player.isOp()){
event.setCancelled(true);
}
}
/**
* Prevents non-operators' items taking durability damage inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onPlayerItemDamage(final PlayerItemDamageEvent event){
final Player player = event.getPlayer();
final Arena arena = this.getArenaByLocation(player.getLocation());
if (arena != null && !player.isOp()){
event.setCancelled(true);
}
}
/**
* Monitors player movement and displays a title screen with an arena's name if they cross into its bounding box.
* Also monitor's player movement and applies a buff depending on if they walk over a certain block:
* - Sponge: Jump Boost for 3 seconds.
* - Glass: Speed for 3 seconds.
* - Obsidian: Damage Resistance for 15 seconds.
*
* @param event The event.
*/
@EventHandler
public void onPlayerMove(final PlayerMoveEvent event){
// TODO: After a teleport, the title screen only appears after the player moves. It might be worth handling teleport events to also show title screens.
final Player player = event.getPlayer();
final Arena arena = this.getArenaByLocation(event.getTo());
if (arena != null){ // Did this player move into an arena's bounding box?
if (this.playersInArenas.get(player) != arena){ // Is the arena they entered different than the one they were last in? (Including not in an arena)
// TODO: Add configurable arena subtitle (such as for giving credit to the arena's builders)
player.sendTitle(ChatColor.AQUA + "" + arena.getName(), "", 10, 70, 20); // Display a title screen with the arena's name.
this.playersInArenas.put(player, arena); // Store which arena the player is in for future checks.
}
Location playerPos = player.getLocation();
Material material = player.getWorld().getBlockAt(playerPos.getBlockX(), playerPos.getBlockY() - 1, playerPos.getBlockZ()).getType();
if (material.equals(Material.SPONGE)){
player.addPotionEffect(new PotionEffect(PotionEffectType.JUMP, 60, 0));
} else if (material.equals(Material.GLASS)){
// TODO: Support stained glass.
player.addPotionEffect(new PotionEffect(PotionEffectType.SPEED, 60, 0));
} else if (material.equals(Material.OBSIDIAN)){
// TODO: May need to be buffed. Damage resistance I really doesn't feel like much.
player.addPotionEffect(new PotionEffect(PotionEffectType.DAMAGE_RESISTANCE, 300, 0));
}
} else {
this.playersInArenas.remove(player);
}
}
/**
* Cleans up tracking of player movement for arena name title screens.
*
* @param event The event.
*/
@EventHandler
public void onPlayerQuit(final PlayerQuitEvent event){
this.playersInArenas.remove(event.getPlayer());
}
/**
* Prevents non-operators from discovering recipes inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onRecipeDiscover(final PlayerRecipeDiscoverEvent event){
final Player player = event.getPlayer();
final Arena arena = this.getArenaByLocation(player.getLocation());
if (arena != null && !player.isOp()){
event.setCancelled(true);
}
}
/**
* Prevents non-operators from shearing entities inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onPlayerShearEntity(final PlayerShearEntityEvent event){
final Player player = event.getPlayer();
final Arena arena = this.getArenaByLocation(event.getEntity().getLocation());
if (arena != null && !player.isOp()){
event.setCancelled(true);
}
}
/**
* Prevents non-operators from taking books off a lectern inside of arenas.
*
* @param event The events.
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onPlayerTakeLecternBook(final PlayerTakeLecternBookEvent event){
final Player player = event.getPlayer();
final Arena arena = this.getArenaByLocation(event.getLectern().getLocation());
if (arena != null && !player.isOp()){
event.setCancelled(true);
}
}
/*
* Private helper methods
*/
/**
* Logs all configuration variables about a given arena to console.
*
* @param arena The arena to log about.
*/
private void logArenaInfo(Arena arena){
Logger logger = this.snowbrawl.getLogger();
logger.info(" - Name: " + arena.getName());
logger.info(" - Refill: " + arena.getRefill() + " snowballs");
logger.info(" - Player Limit: " + arena.getPlayerLimit() + " players");
logger.info(" - Grace Time: " + arena.getGraceTime() + "s");
logger.info(" - Match Time: " + arena.getMatchTime() + "s");
logger.info(" - Damage: " + arena.getDamage() + " HP");
logger.info(" - World: " + arena.getWorld().getName());
Location pos1 = arena.getPos1(), pos2 = arena.getPos2();
logger.info(" - Bounding Box: " + pos1.getX() + ", " + pos1.getY() + ", " + pos1.getZ() + " / " + pos2.getX() + ", " + pos2.getY() + ", " + pos2.getZ());
logger.info(" - Spawn Points: " + arena.getSpawnPoints().size());
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2022 Logan Fick
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
package dev.logal.snowbrawl.managers;
import dev.logal.snowbrawl.Snowbrawl;
import org.bukkit.boss.BarColor;
import org.bukkit.boss.BarStyle;
import org.bukkit.boss.BossBar;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import java.util.HashMap;
/**
* Keeps track of boss bars on a per-player basis.
*/
public class BossBarManager implements Listener {
private final Snowbrawl snowbrawl;
private final HashMap<Player, BossBar> bossBars = new HashMap<>();
/**
* Creates a new boss bar manager which is owned by a given instance of Snowbrawl.
*
* @param snowbrawl The instance of Snowbrawl which owns this arena manager. Used to configure boss bars on the Minecraft server itself and register events.
*/
public BossBarManager(final Snowbrawl snowbrawl){
this.snowbrawl = snowbrawl;
this.snowbrawl.getServer().getPluginManager().registerEvents(this, this.snowbrawl);
}
/**
* Creates a boss at the top of a given player's GUI with a given text, color, style, and progress level.
* If the player already has a boss bar, the attributes are overwritten.
*
* @param player The player whose boss bar to set.
* @param text The text which appears above the boss bar.
* @param color The color of the boss bar.
* @param style The visual style of the boss bar.
* @param progress A value between 0.0 and 1.0 representing the boss bar's progress.
*/
public void setBossBar(final Player player, final String text, final BarColor color, final BarStyle style, final double progress){
if (this.bossBars.containsKey(player)){
BossBar bossBar = this.bossBars.get(player);
bossBar.setTitle(text);
bossBar.setColor(color);
bossBar.setStyle(style);
bossBar.setProgress(progress);
} else {
BossBar bossBar = this.snowbrawl.getServer().createBossBar(text, color, style);
bossBar.setProgress(progress);
bossBar.addPlayer(player);
this.bossBars.put(player, bossBar);
}
}
/**
* Removes the boss bar from the top of a given player's GUI, if one exists.
*
* @param player The player whose boss bar to clear.
*/
public void clearBossBar(final Player player){
if (bossBars.containsKey(player)){
this.bossBars.get(player).removeAll();
this.bossBars.remove(player);
}
}
/**
* Clears all boss bars owned by this boss bar manager.
*/
public void clearAllBossBars(){
// TODO: There was probably a ConcurrentModificationException caused here during a server shut down at one point.
for (Player player : this.bossBars.keySet()){
this.clearBossBar(player);
}
}
/**
* Cleans up any boss bars on a player's GUI when they leave.
* This ensures a clean slate the next time they log in.
*
* @param event The event.
*/
@EventHandler
public void onPlayerLeave(final PlayerQuitEvent event){
this.clearBossBar(event.getPlayer());
}
}

View File

@@ -0,0 +1,662 @@
/*
* Copyright 2022 Logan Fick
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
package dev.logal.snowbrawl.managers;
import dev.logal.snowbrawl.Snowbrawl;
import dev.logal.snowbrawl.types.Arena;
import dev.logal.snowbrawl.types.Match;
import org.bukkit.Material;
import org.bukkit.boss.BarColor;
import org.bukkit.boss.BarStyle;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.block.*;
import org.bukkit.event.entity.EntityPickupItemEvent;
import org.bukkit.event.entity.EntityPlaceEvent;
import org.bukkit.event.entity.FoodLevelChangeEvent;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.*;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
/**
* Handles queueing of players for matches, creating matches, and keeping track of those matches until conclusion.
*/
public class MatchManager implements Listener, Runnable {
public static final int MAXIMUM_QUEUE_WAIT_TIME_TICKS = 300; // The maximum amount of ticks the queue will wait to start a match when all criteria for a match is met.
private final Snowbrawl snowbrawl;
private final LinkedList<Player> queue = new LinkedList<>(); // Holds the current queue of players waiting for a match.
private final ArrayList<Match> matches = new ArrayList<>(); // Holds current ongoing matches.
private final Random rng = new Random();
private int queueWaitTimeTicksRemaining = MAXIMUM_QUEUE_WAIT_TIME_TICKS; // Tracks how many ticks are remaining in the queue wait time.
/**
* Creates a new match manager which is owned by a given instance of Snowbrawl.
*
* @param snowbrawl The instance of Snowbrawl which owns this match manager. Used for logging and to configure events.
*/
public MatchManager(final Snowbrawl snowbrawl){
this.snowbrawl = snowbrawl;
this.snowbrawl.getServer().getPluginManager().registerEvents(this, this.snowbrawl);
}
/**
* Adds a player to the queue of players waiting to enter a match.
*
* @param player The player to add to the queue.
*/
public void addToQueue(final Player player){
if (this.getMatchFromPlayer(player) != null){
throw new IllegalStateException("A player cannot be in a match and in the queue at the same time.");
}
this.snowbrawl.getLogger().info(player.getName() + " has been added to the queue.");
this.queue.add(player);
}
/**
* Removes a player from the queue of players waiting to enter a match.
*
* @param player The player to remove from the queue.
*/
public void removeFromQueue(final Player player){
this.snowbrawl.getLogger().info(player.getName() + " has been removed from the queue.");
this.queue.remove(player);
}
/**
* Gets whether a given player is in the queue of players waiting to enter a match.
*
* @param player The player to test for.
*
* @return True if the given player is in the queue, false otherwise.
*/
public boolean isInQueue(final Player player){
return this.queue.contains(player);
}
/**
* Gets a copy of the current queue of players waiting to enter a match.
*
* @return A List containing players in the queue.
*/
public List<Player> getQueue(){
return new LinkedList<>(this.queue); // Return a copy rather than the reference to the original list.
}
/**
* Gets the match this player is currently in.
*
* @param player The player to look up.
*
* @return The match the player is in, null if they are not in a match.
*/
public Match getMatchFromPlayer(final Player player){
for (Match match : this.matches){
if (match.isParticipant(player)){
return match;
}
}
return null;
}
/**
* Gets all arenas which are not currently in use by a match.
*
* @return A list of available arenas.
*/
public List<Arena> getAvailableArenas(){
ArrayList<Arena> availableArenas = new ArrayList<>();
for (Arena arena : this.snowbrawl.getArenaManager().getArenas()){
if (this.getMatchFromArena(arena) == null){
availableArenas.add(arena);
}
}
return availableArenas;
}
/**
* Gets the match a given arena is in use by.
*
* @param arena The arena to look up.
*
* @return The match the arena is in use by, null if the arena is not in use.
*/
public Match getMatchFromArena(final Arena arena){
for (Match match : this.matches){
if (match.getArena().equals(arena)){
return match;
}
}
return null;
}
/**
* Updates the state of the match manager and all matches it tracks. Intended to be called once per tick on the server's main thread.
*/
@Override
public void run(){
this.tick();
}
/**
* Updates the state of the match manager and all matches it tracks. Intended to be called once per tick on the server's main thread.
*/
public void tick(){
this.updateQueue();
// Tick all matches. The newest match will be included as well.
// Clone the list of matches to prevent concurrent modification.
List<Match> matchesToTick = new ArrayList<>(this.matches);
for (Match match : matchesToTick){
if (!match.tick()){
this.matches.remove(match);
}
}
}
private void updateQueue(){
// TODO: The next 2 lines look cringe and make IntelliJ very unhappy. It works and hasn't yet thrown an exception, but maybe it can be done better.
// TODO: This doesn't look like it filters users who are also in matches. It likely isn't a problem since the default boss bar is displayed first, then updated by the match. However, probably still worth fixing.
List<? extends Player> leftOverPlayers = this.snowbrawl.getServer().getOnlinePlayers().stream().collect(Collectors.toList());
// Remove queued players from the list.
leftOverPlayers.removeAll(this.snowbrawl.getMatchManager().getQueue());
// Display a default boss bar instruction to players who are not queued and not in a match.
for (Player player : leftOverPlayers){
if (this.snowbrawl.getMatchManager().getMatchFromPlayer(player) == null){
this.snowbrawl.getBossBarManager().setBossBar(player, "You are not in the queue. Run /sb to queue for a match.", BarColor.YELLOW, BarStyle.SOLID, 1.0);
}
}
// There are either 0 or 1 players in the queue, which is not enough for a match.
if (this.queue.size() < 2){
// Reset the queue timer.
this.queueWaitTimeTicksRemaining = MAXIMUM_QUEUE_WAIT_TIME_TICKS;
// Tell players in the queue via boss bar update.
for (Player player : this.queue){
this.snowbrawl.getBossBarManager().setBossBar(player, "There are not enough players in the queue.", BarColor.RED, BarStyle.SOLID, 1.0);
}
// Stop any further update to the queue.
return;
}
// There are at least 2 people in the queue. Now get a list of available arenas.
final List<Arena> availableArenas = this.getAvailableArenas();
if (availableArenas.size() == 0){
// No arenas available. Reset queue timer and stop handling the queue.
this.queueWaitTimeTicksRemaining = MAXIMUM_QUEUE_WAIT_TIME_TICKS;
// Tell players in the queue about the problem via boss bar update.
for (Player player : this.queue){
this.snowbrawl.getBossBarManager().setBossBar(player, "There are no arenas available.", BarColor.RED, BarStyle.SOLID, 1.0);
}
// Stop any further update to the queue.
return;
}
// There are available arenas. Pick a random one.
final Arena candidateArena = availableArenas.get(rng.nextInt(0, availableArenas.size()));
// Has the queue timer run out?
if (queueWaitTimeTicksRemaining <= 0){
// We have a candidate arena and the queue timer has expired. Time to build a match.
// First, let's pull some players from the head of the queue.
final ArrayList<Player> players = new ArrayList<>();
// TODO: This seems over complicated. There's most likely a better way to do this.
// Iterate x amount of times, where x is the maximum amount of players allowed in the arena.
for (int i = 0; i < candidateArena.getPlayerLimit(); i++){
// Are there still players remaining in the queue?
if (this.queue.size() > 0){
// Yes. Add them to the players list.
players.add(this.queue.poll());
} else {
// No. stop adding players.
break;
}
}
// Finally, create the match.
this.matches.add(new Match(this.snowbrawl, candidateArena, players));
return;
}
queueWaitTimeTicksRemaining -= 1;
final double bossBarProgress = (double) queueWaitTimeTicksRemaining / (double) MAXIMUM_QUEUE_WAIT_TIME_TICKS;
for (Player player : this.queue){
this.snowbrawl.getBossBarManager().setBossBar(player, "The next match will start in " + (queueWaitTimeTicksRemaining / 20 + 1) + " seconds.", BarColor.GREEN, BarStyle.SEGMENTED_10, bossBarProgress);
}
// The above queue system works, but has some problems.
// TODO: Speed up timer based on how full the queue is. Maybe this can be done based on what percent of the server population is queued.
// TODO: Pick bigger maps (larger player limit) when bigger amounts of players are in the queue. The current pure RNG makes some odd choices at times.
}
/*
* These event handlers build upon the default event handlers in the arena manager.
*
* Most of the event handlers remove the operator exemptions for operators who are in matches. The intent is to prevent operators from accidentally tampering with the arena during normal gameplay.
* Some event handlers also forward events down to specific matches if the match itself needs to perform an action in response to an event.
*/
/**
* Prevents match participants from building inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onBlockCanBuild(final BlockCanBuildEvent event){
final Match match = this.getMatchFromPlayer(event.getPlayer());
if (match != null){
event.setBuildable(false);
}
}
/**
* Prevents match participants from damaging blocks inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onBlockDamage(final BlockDamageEvent event){
final Match match = this.getMatchFromPlayer(event.getPlayer());
if (match != null){
event.setCancelled(true);
}
}
/**
* Prevents match participants from fertilizing blocks inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onBlockFertilize(final BlockFertilizeEvent event){
final Match match = this.getMatchFromPlayer(event.getPlayer());
if (match != null){
event.setCancelled(true);
}
}
/**
* Prevents match participants from igniting blocks inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onBlockIgnite(final BlockIgniteEvent event){
final Player player = event.getPlayer();
if (player != null){
final Match match = this.getMatchFromPlayer(event.getPlayer());
if (match != null){
event.setCancelled(true);
}
}
}
/**
* Prevents match participants from placing blocks inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onBlockPlace(final BlockPlaceEvent event){
final Match match = this.getMatchFromPlayer(event.getPlayer());
if (match != null){
event.setCancelled(true);
}
}
/**
* Prevents match participants from changing cauldron levels.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onCauldronLevelChange(final CauldronLevelChangeEvent event){
final Entity entity = event.getEntity();
if (entity instanceof final Player player){
final Match match = this.getMatchFromPlayer(player);
if (match != null){
event.setCancelled(true);
}
}
}
/**
* Prevents match participants from changing signs inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onSignChange(final SignChangeEvent event){
final Match match = this.getMatchFromPlayer(event.getPlayer());
if (match != null){
event.setCancelled(true);
}
}
/**
* Notifies appropriate matches of player deaths.
*
* @param event The event.
*/
@EventHandler
public void onPlayerDeath(final PlayerDeathEvent event){
final Player player = event.getEntity();
Match match = this.getMatchFromPlayer(player);
if (match != null){
match.onPlayerDeath(event);
}
}
/**
* Prevents match participants from picking up items except snowballs.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onEntityPickupItem(final EntityPickupItemEvent event){
final Entity entity = event.getEntity();
if (entity instanceof final Player player){
final Match match = this.getMatchFromPlayer(player);
if (match != null){
if (!event.getItem().getItemStack().getType().equals(Material.SNOWBALL)){ // Is the item not a snowball?
event.setCancelled(true);
}
}
}
}
/**
* Prevents match participants from placing entities from items.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onEntityPlace(final EntityPlaceEvent event){
final Match match = this.getMatchFromPlayer(event.getPlayer());
if (match != null){
event.setCancelled(true);
}
}
/**
* Prevents match participants from gaining hunger.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onFoodLevelChange(final FoodLevelChangeEvent event){
if (event.getEntity() instanceof final Player player){
final Match match = this.getMatchFromPlayer(player);
if (match != null){
event.setCancelled(true);
}
}
}
/**
* Prevents match participants from entering beds.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onPlayerBedEnter(final PlayerBedEnterEvent event){
final Match match = this.getMatchFromPlayer(event.getPlayer());
if (match != null){
event.setCancelled(true);
}
}
/**
* Prevents match participants from capturing entities inside of buckets.
*
* @param event The event.
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onPlayerBucketEntity(final PlayerBucketEntityEvent event){
final Match match = this.getMatchFromPlayer(event.getPlayer());
if (match != null){
event.setCancelled(true);
}
}
/**
* Prevents match participants from dropping items.
*
* @param event The event.
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onPlayerDropItem(final PlayerDropItemEvent event){
final Match match = this.getMatchFromPlayer(event.getPlayer());
if (match != null){
event.setCancelled(true);
}
}
/**
* Prevents match participants from editing books inside of arenas.
*
* @param event The event.
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onPlayerEditBook(final PlayerEditBookEvent event){
final Match match = this.getMatchFromPlayer(event.getPlayer());
if (match != null){
event.setCancelled(true);
}
}
/**
* Prevents match participants from fishing inside of arenas.
*
* @param event The event.
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onPlayerFish(final PlayerFishEvent event){
final Match match = this.getMatchFromPlayer(event.getPlayer());
if (match != null){
event.setCancelled(true);
}
}
/**
* Prevents non-operators from harvesting blocks inside of arenas.
*
* @param event The event.
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onPlayerHarvestBlock(final PlayerHarvestBlockEvent event){
final Match match = this.getMatchFromPlayer(event.getPlayer());
if (match != null){
event.setCancelled(true);
}
}
/**
* Prevents match participants from interacting with entities inside of arenas.
*
* @param event The event.
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onPlayerInteractEntity(final PlayerInteractEntityEvent event){
final Match match = this.getMatchFromPlayer(event.getPlayer());
if (match != null){
event.setCancelled(true);
}
}
/**
* Notifies appropriate matches of player interactions.
*
* @param event The event.
*/
@EventHandler
public void onPlayerInteract(final PlayerInteractEvent event){
final Match match = this.getMatchFromPlayer(event.getPlayer());
if (match != null){
match.onPlayerInteract(event);
}
}
/**
* Prevents match participants from consuming items inside of arenas.
*
* @param event The event.
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onPlayerItemConsume(final PlayerItemConsumeEvent event){
final Match match = this.getMatchFromPlayer(event.getPlayer());
if (match != null){
event.setCancelled(true);
}
}
/**
* Prevents match participants' items taking durability damage inside of arenas.
*
* @param event The event.
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onPlayerItemDamage(final PlayerItemDamageEvent event){
final Match match = this.getMatchFromPlayer(event.getPlayer());
if (match != null){
event.setCancelled(true);
}
}
/**
* Notifies appropriate matches of player movements.
*
* @param event The event.
*/
@EventHandler
public void onPlayerMove(final PlayerMoveEvent event){
final Player player = event.getPlayer();
Match match = this.getMatchFromPlayer(player);
if (match != null){
match.onPlayerMove(event);
}
}
/**
* Removes quitting players from the queue and notifies appropriate matches of the quits.
*
* @param event The event.
*/
@EventHandler
public void onPlayerQuit(final PlayerQuitEvent event){
final Player player = event.getPlayer();
if (this.isInQueue(player)){
this.removeFromQueue(player);
}
Match match = this.getMatchFromPlayer(player);
if (match != null){
match.onPlayerQuit(event);
}
}
/**
* Prevents match participants from discovering recipes inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onRecipeDiscover(final PlayerRecipeDiscoverEvent event){
final Match match = this.getMatchFromPlayer(event.getPlayer());
if (match != null){
event.setCancelled(true);
}
}
/**
* Notifies appropriate matches of player respawns.
*
* @param event The event.
*/
@EventHandler
public void onPlayerRespawn(final PlayerRespawnEvent event){
final Match match = this.getMatchFromPlayer(event.getPlayer());
if (match != null){
match.onPlayerRespawn(event);
}
}
/**
* Prevents match participants from shearing entities inside of arenas.
*
* @param event The event.
*/
@EventHandler(ignoreCancelled = true)
public void onPlayerShearEntity(final PlayerShearEntityEvent event){
final Match match = this.getMatchFromPlayer(event.getPlayer());
if (match != null){
event.setCancelled(true);
}
}
/**
* Prevents match participants from taking books off a lectern inside of arenas.
*
* @param event The events.
*/
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
public void onPlayerTakeLecternBook(final PlayerTakeLecternBookEvent event){
final Match match = this.getMatchFromPlayer(event.getPlayer());
if (match != null){
event.setCancelled(true);
}
}
}

View File

@@ -0,0 +1,356 @@
/*
* Copyright 2022 Logan Fick
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
package dev.logal.snowbrawl.types;
import org.bukkit.Location;
import org.bukkit.World;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* Represents an arena in a minecraft world and holds all of its attributes.
*/
public class Arena {
// Default config variables
public static final int DEFAULT_REFILL = 16;
public static final int DEFAULT_PLAYER_LIMIT = 8;
public static final int DEFAULT_GRACE_TIME = 15 * 20;
public static final int DEFAULT_MATCH_TIME = 300 * 20;
public static final int DEFAULT_DAMAGE = 4;
public static final double DEFAULT_POS1_X = 0, DEFAULT_POS1_Y = 0, DEFAULT_POS1_Z = 0;
public static final double DEFAULT_POS2_X = 0, DEFAULT_POS2_Y = 0, DEFAULT_POS2_Z = 0;
private final ArrayList<Location> spawnPoints = new ArrayList<>(); // A list of locations identifying spawn locations.
// Runtime variables
private final Random rng = new Random();
// Config variables
private String name; // A friendly name for this arena
private int refill; // Amount of snowballs to give players per snow block right click.
private int playerLimit; // Maximum amount of players allowed in this arena in a match.
private int graceTime, matchTime;
private int damage; // Damage dealt to players per snowball hit.
private World world; // Which world the coordinates apply to.
private Location pos1, pos2; // Coordinates identifying the 2 corners of the arena.
/**
* Creates a new arena with a given name in a given world and sets all other attributes to the below defaults:
* <ul>
* <li>refill = Arena.DEFAULT_REFILL</li>
* <li>playerLimit = Arena.DEFAULT_PLAYER_LIMIT</li>
* <li>graceTime = Arena.DEFAULT_GRACE_TIME</li>
* <li>matchTime = Arena.DEFAULT_MATCH_TIME</li>
* <li>damage = Arena.DEFAULT_DAMAGE</li>
* <li>pos1 = new Location(world, 0, 0, 0)</li>
* <li>pos2 = new Location(world, 0, 0, 0)</li>
* <li>spawnPoints = new ArrayList<>();</li>
* </ul>
*/
public Arena(final String name, final World world){
this.setName(name);
this.setRefill(DEFAULT_REFILL);
this.setPlayerLimit(DEFAULT_PLAYER_LIMIT);
this.setGraceTime(DEFAULT_GRACE_TIME);
this.setMatchTime(DEFAULT_MATCH_TIME);
this.setDamage(DEFAULT_DAMAGE);
this.setWorld(world);
this.setPos1(new Location(this.getWorld(), 0, 0, 0));
this.setPos2(new Location(this.getWorld(), 0, 0, 0));
}
/**
* Gets the friendly name for this arena.
*
* @return The friendly name.
*/
public String getName(){
return this.name;
}
/**
* Changes the friendly name for this arena.
* All spaces are converted to underscores to ensure the name can be used in commands.
*
* @param name The new friendly name.
*/
public void setName(final String name){
if (name == null || name.isEmpty()){
throw new IllegalArgumentException("The name must be a non-null and non-empty String.");
}
this.name = name.replaceAll(" ", "_");
}
/**
* Gets the amount of snowballs given to players each time they click a snow block to refill their snowballs.
*
* @return The amount of snowballs given to players.
*/
public int getRefill(){
return this.refill;
}
/**
* Sets the amount of snowballs given to players each time they click a snow block to refill their snowballs.
*
* @param refill The amount of snowballs to give to players.
*/
public void setRefill(final int refill){
if (refill < 1){
throw new IllegalArgumentException("The refill amount must be at least 1.");
}
this.refill = refill;
}
/**
* Gets the maximum amount of players allowed to play on this arena.
*
* @return The maximum amount of players.
*/
public int getPlayerLimit(){
return this.playerLimit;
}
/**
* Sets the maximum amount of players allowed to play on this arena.
*
* @param playerLimit The maximum amount of players.
*/
public void setPlayerLimit(final int playerLimit){
if (playerLimit < 2){
throw new IllegalArgumentException("The player limit must be at least 2.");
}
this.playerLimit = playerLimit;
}
/**
* Gets the amount of time players are allowed to safely explore the map before the match begins in this arena.
*
* @return The grace time, in ticks.
*/
public int getGraceTime(){
return this.graceTime;
}
/**
* Sets the amount of time players are allowed to safely explore the map before the match begins in this arena.
*
* @param graceTime The grace time, in ticks.
*/
public void setGraceTime(final int graceTime){
if (graceTime < (5 * 20)){
throw new IllegalArgumentException("The grace time must be at least 5 seconds.");
}
this.graceTime = graceTime;
}
/**
* Gets the amount of time a match lasts in this arena.
*
* @return The match time, in ticks.
*/
public int getMatchTime(){
return this.matchTime;
}
/**
* Sets the amount of time a match lasts in this arena.
*
* @param matchTime The match time, in ticks.
*/
public void setMatchTime(final int matchTime){
if (matchTime < (5 * 20)){
throw new IllegalArgumentException("The match time must be at least 5 seconds.");
}
this.matchTime = matchTime;
}
/**
* Gets the amount of damage each snowball hit does to a player in this arena.
*
* @return The amount of damage done by each snowball.
*/
public int getDamage(){
return this.damage;
}
/**
* Sets the amount of damage each snowball hit does to a player in this arena.
*
* @param damage The amount of damage done by each snowball.
*/
public void setDamage(final int damage){
if (damage < 1){
throw new IllegalArgumentException("The damage must be at least 1.");
}
this.damage = damage;
}
/**
* Gets the position of one corner of the bounding box of this arena.
*
* @return A Location representing one corner.
*/
public Location getPos1(){
return this.pos1.clone();
}
/**
* Sets the position of one corner of the bounding box of this arena.
*
* @param pos1 A Location representing one corner.
*/
public void setPos1(final Location pos1){
if (this.spawnPoints.size() > 0){
throw new IllegalStateException("The arena bounding box cannot be changed with spawn points set.");
}
// Get a rid of any information about the world and store only the coordinates.
// The world is tracked separately and yaw/pitch doesn't matter.
this.pos1 = new Location(null, pos1.getX(), pos1.getY(), pos1.getZ());
}
/**
* Gets the position of the opposite corner of the bounding box of this arena.
*
* @return A Location representing one corner.
*/
public Location getPos2(){
return this.pos2.clone();
}
/**
* Sets the position of the opposite corner of the bounding box of this arena.
*
* @param pos1 A Location representing one corner.
*/
public void setPos2(final Location pos1){
if (this.spawnPoints.size() > 0){
throw new IllegalStateException("The arena bounding box cannot be changed with spawn points set.");
}
// Get a rid of any information about the world and store only the coordinates.
// The world is tracked separately and yaw/pitch doesn't matter.
this.pos2 = new Location(null, pos1.getX(), pos1.getY(), pos1.getZ());
}
/**
* Gets the world this arena is located in.
*
* @return The world.
*/
public World getWorld(){
return this.world;
}
/**
* Sets the world this arena is located in.
*
* @param world The world.
*/
public void setWorld(final World world){
this.world = world;
}
/**
* Adds a new spawn point to this arena.
*
* @param spawnPoint A Location representing the new spawn point.
*/
public void addSpawnPoint(final Location spawnPoint){
if (!this.isWithinBoundingBox(spawnPoint)){
throw new IllegalArgumentException("The specified spawn point is outside the bounds of this arena.");
}
this.spawnPoints.add(new Location(this.getWorld(), spawnPoint.getX(), spawnPoint.getY(), spawnPoint.getZ(), spawnPoint.getYaw(), spawnPoint.getPitch()));
}
/**
* Removes a spawn point from this arena.
*
* @param spawnPoint The spawn point to remove from this arena.
*/
public void removeSpawnPoint(final Location spawnPoint){
this.spawnPoints.remove(spawnPoint);
}
/**
* Gets a copy of all spawn points for this arena.
*
* @return A list of Locations representing the spawn points for this arena.
*/
public List<Location> getSpawnPoints(){
return new ArrayList<>(this.spawnPoints); // Return a copy rather than the reference to the original list.
}
/**
* Checks if a given location is within the bounding box of this arena.
*
* @param location The location to check.
*
* @return True if the location is within the bounding box, false otherwise.
*/
public boolean isWithinBoundingBox(Location location){
if (location.getWorld() != this.getWorld()){
return false;
}
double highX = Math.max(pos1.getX(), pos2.getX()); // Highest X coordinate.
double lowX = Math.min(pos1.getX(), pos2.getX()); // Lowest X coordinate.
double highY = Math.max(pos1.getY(), pos2.getY()); // Highest Y coordinate.
double lowY = Math.min(pos1.getY(), pos2.getY()); // Lowest Y coordinate.
double highZ = Math.max(pos1.getZ(), pos2.getZ()); // Highest Y coordinate.
double lowZ = Math.min(pos1.getZ(), pos2.getZ()); // Lowest Y coordinate.
if (location.getX() > highX){
return false;
} else if (location.getX() < lowX){
return false;
} else if (location.getY() > highY){
return false;
} else if (location.getY() < lowY){
return false;
} else if (location.getZ() > highZ){
return false;
} else {
return !(location.getZ() < lowZ);
}
}
/**
* Gets a random spawn point for this arena. If no spawn points are configured, then the exact center of the arena is returned instead.
*
* @return A Location representing the randomly selected spawnpoint.
*/
public Location getRandomSpawnPoint(){
Location destination;
if (this.spawnPoints.size() > 0){
// If spawn points are configured, teleport the player to a random spawn point.
destination = this.spawnPoints.get(this.rng.nextInt(0, spawnPoints.size()));
} else {
// If no spawn points are configured, teleport the player to the center of the bounding box.
double x = (pos1.getX() + pos2.getX()) / 2;
double y = (pos1.getY() + pos2.getY()) / 2;
double z = (pos1.getZ() + pos2.getZ()) / 2;
destination = new Location(this.getWorld(), x, y, z);
}
return destination;
}
}

View File

@@ -0,0 +1,360 @@
/*
* Copyright 2022 Logan Fick
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
package dev.logal.snowbrawl.types;
import dev.logal.snowbrawl.Snowbrawl;
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.*;
import org.bukkit.boss.BarColor;
import org.bukkit.boss.BarStyle;
import org.bukkit.entity.Player;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
/**
* Represents the state of a match taking place in an arena.
*/
public class Match {
private final Snowbrawl snowbrawl;
private final Arena arena;
private final List<MatchParticipant> participants;
private int graceTimeTicksRemaining; // The amount of grace time remaining in ticks.
private int matchTimeTicksRemaining; // The amount of match time remaining in ticks.
/**
* Creates a new match which is owned by a given instance of Snowbrawl and uses a given arena with a given list of players.
*
* @param snowbrawl The instance of Snowbrawl which owns this match manager. Used for setting boss bars, logging, and scheduling delayed tasks.
* @param arena The arena this match is taking place in.
* @param players A List of Players who are participating in this match.
*/
public Match(final Snowbrawl snowbrawl, final Arena arena, final List<Player> players){
this.snowbrawl = snowbrawl;
this.arena = arena;
this.participants = new ArrayList<>();
for (final Player player : players){
participants.add(new MatchParticipant(player, this));
}
this.graceTimeTicksRemaining = arena.getGraceTime();
this.matchTimeTicksRemaining = arena.getMatchTime();
}
/**
* Gets the current leaderboard of players based on their score.
*
* @return A sorted List of match participants where the first element is the top scoring player.
*/
public List<MatchParticipant> getLeaderboard(){
List<MatchParticipant> participants = new LinkedList<>(this.getParticipants());
participants.sort(Comparator.comparingInt(MatchParticipant::getScore).reversed());
return participants;
}
/**
* Gets the arena this match is taking place in.
*
* @return The arena.
*/
public Arena getArena(){
return this.arena;
}
/**
* Gets a copy of all match participants.
*
* @return A list of match participants.
*/
public List<MatchParticipant> getParticipants(){
return new ArrayList<>(this.participants); // Return a copy rather than the reference to the original list.
}
/**
* Gets the match participant which represents a given player.
*
* @param player The player to get the match participant for.
* @return The match participant representing the player, or null if the player is not a participant in this match.
*/
public MatchParticipant getParticipant(final Player player){
for (final MatchParticipant participant : this.getParticipants()){
if (participant.getPlayer().equals(player)){
return participant;
}
}
return null;
}
/**
* Checks whether a given player is a participant in this match.
*
* @param player The player to check.
* @return True of the player is a participant in this match, false otherwise.
*/
public boolean isParticipant(final Player player){
for (final MatchParticipant participant : this.getParticipants()){
if (participant.getPlayer().equals(player)){
return true;
}
}
return false;
}
/**
* Removes a given match participant from this match.
*
* @param participant The match participant to remove.
*/
public void removeParticipant(final MatchParticipant participant){
final boolean success = this.participants.remove(participant);
if (success){
this.broadcastMessageToPlayers(ChatColor.RED + participant.getPlayer().getName() + " has been removed from the the match.");
}
}
/**
* Updates the state of this match. Intended to be called once per tick on the server's main thread by the match manager.
*
* @return True if the match is still ongoing, false if the final tick has just executed and no further ticks are required.
*/
public boolean tick(){
if (this.getParticipants().size() < 2){
this.endMatch();
}
// First tick of grace period.
if (graceTimeTicksRemaining >= arena.getGraceTime()){
final double bossBarProgress = (double) graceTimeTicksRemaining / (double) (arena.getGraceTime());
for (MatchParticipant participant : this.getParticipants()){
final Player player = participant.getPlayer();
player.teleport(this.arena.getRandomSpawnPoint()); // Teleport them to a random spawn point.
player.setGameMode(GameMode.ADVENTURE); // Change their gamemode to adventure.
player.setHealth(20.0); // Reset health to full.
player.setFoodLevel(10); // Reset food level to full.
player.setSaturation(0.0f); // Remove any saturation to make health regeneration minimal.
player.getInventory().clear(); // Clear their inventory.
player.updateInventory(); // Synchronize their inventory.
this.snowbrawl.getBossBarManager().setBossBar(player, "Grace period. The match will begin in " + (graceTimeTicksRemaining / 20 + 1) + " seconds.", BarColor.GREEN, BarStyle.SEGMENTED_10, bossBarProgress);
player.sendMessage(ChatColor.GOLD + "Grace period has begun. You may explore the arena before the match begins in " + (graceTimeTicksRemaining / 20) + " seconds.");
player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, SoundCategory.MASTER, 1.0f, 0.75f);
}
graceTimeTicksRemaining -= 1;
return true;
}
// Not first tick of grace period, but grace time still remaining.
if (graceTimeTicksRemaining > 0){
final double bossBarProgress = (double) graceTimeTicksRemaining / (double) (arena.getGraceTime());
for (MatchParticipant participant : this.getParticipants()){
final Player player = participant.getPlayer();
this.snowbrawl.getBossBarManager().setBossBar(player, "Grace period. The match will begin in " + (graceTimeTicksRemaining / 20 + 1) + " seconds.", BarColor.GREEN, BarStyle.SEGMENTED_10, bossBarProgress);
if ((graceTimeTicksRemaining <= 100 && graceTimeTicksRemaining % 20 == 0) || (graceTimeTicksRemaining <= 300 && graceTimeTicksRemaining % 100 == 0) || (graceTimeTicksRemaining % 600 == 0)){
player.sendMessage(ChatColor.YELLOW + "The match will begin in " + (graceTimeTicksRemaining / 20) + " seconds.");
player.playSound(player.getLocation(), Sound.BLOCK_DISPENSER_DISPENSE, SoundCategory.MASTER, 1.0f, 1.0f);
}
}
graceTimeTicksRemaining -= 1;
return true;
}
// No more grace time remaining, first tick of match.
if (matchTimeTicksRemaining >= arena.getMatchTime()){
for (MatchParticipant participant : this.getParticipants()){
final Player player = participant.getPlayer();
player.teleport(this.arena.getRandomSpawnPoint());
player.setGameMode(GameMode.ADVENTURE); // Change their gamemode to adventure.
player.setHealth(20.0); // Reset health to full.
player.setFoodLevel(10); // Reset food level to full.
player.setSaturation(0.0f); // Remove any saturation to make health regeneration minimal.
player.getInventory().clear(); // Clear their inventory.
player.updateInventory(); // Synchronize their inventory.
}
matchTimeTicksRemaining -= 1;
return true;
}
// Match time has run out. Time to end the match.
if (matchTimeTicksRemaining <= 0){
this.endMatch();
return false;
}
// No special cases above are true. Executes when match time is remaining.
final double bossBarProgress = (double) matchTimeTicksRemaining / (double) (arena.getMatchTime());
for (MatchParticipant participant : this.getParticipants()){
final Player player = participant.getPlayer();
// Update boss bar
this.snowbrawl.getBossBarManager().setBossBar(player, (matchTimeTicksRemaining / 20 + 1) + " seconds remaining.", BarColor.GREEN, BarStyle.SEGMENTED_10, bossBarProgress);
// If required, provide an update to the player that the match is ending soon.
if ((matchTimeTicksRemaining <= 100 && matchTimeTicksRemaining % 20 == 0) || (matchTimeTicksRemaining <= 300 && matchTimeTicksRemaining % 100 == 0) || (matchTimeTicksRemaining <= 600 && matchTimeTicksRemaining % 600 == 0)){
player.sendMessage(ChatColor.YELLOW + "The match will end in " + (matchTimeTicksRemaining / 20) + " seconds.");
player.playSound(player.getLocation(), Sound.BLOCK_DISPENSER_DISPENSE, SoundCategory.MASTER, 1.0f, 1.0f);
}
// Update the player's action bar with theirs tats.
final String actionBarText = ChatColor.GREEN + "" + participant.getKills() + ChatColor.WHITE + " / " + ChatColor.RED + participant.getDeaths();
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText(actionBarText));
//player.setGameMode(GameMode.ADVENTURE);
// Keep food levels up to date.
player.setFoodLevel(10);
player.setSaturation(0.0f);
}
matchTimeTicksRemaining--;
return true;
}
/**
* Ends this match by displaying a leaderboard and teleporting all players to the world's spawn.
*/
public void endMatch(){
// Ensure no time is remaining on timers, just in case this match is prematurely ending.
this.graceTimeTicksRemaining = 0;
this.matchTimeTicksRemaining = 0;
// Get final leaderboard.
final List<MatchParticipant> leaderboard = this.getLeaderboard();
// For each player still a member of the match
for (MatchParticipant participant : this.getParticipants()){
final Player player = participant.getPlayer();
player.setGameMode(GameMode.SPECTATOR);
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponent.fromLegacyText("")); // Clear action bar.
player.teleport(player.getWorld().getSpawnLocation()); // TODO: This maybe needs to be a configurable "global spawn" so that arenas can live in dedicated worlds, if needed.
player.getInventory().clear();
player.updateInventory();
// Start printing conclusion message
player.sendMessage(ChatColor.GREEN + "----------== [ MATCH SUMMARY ] ==----------");
player.sendMessage(ChatColor.YELLOW + "Top Players:");
// For the top 3 slots of the leaderboard, if a player is present, display their stats.
for (int place = 0; place <= 2; place++){
if (place >= leaderboard.size()){
break;
}
final MatchParticipant leaderboardPlayer = leaderboard.get(place);
if (place == 0){ // TODO: Good meme, but is this the right place to do this?
leaderboardPlayer.getPlayer().sendTitle(ChatColor.GOLD + "WINNER WINNER CHICKEN DINNER!", ChatColor.YELLOW + "You got that bread.", 10, 70, 20);
} // TODO: Add titles for other places, at least second and third.
player.sendMessage(ChatColor.YELLOW + " - " + (place + 1) + ": " + leaderboardPlayer.getPlayer().getName() + ": " + ChatColor.GOLD + ChatColor.BOLD + leaderboardPlayer.getScore() + ChatColor.YELLOW + " (" + ChatColor.GREEN + leaderboardPlayer.getKills() + ChatColor.YELLOW + "/" + ChatColor.RED + leaderboardPlayer.getDeaths() + ChatColor.YELLOW + ")");
}
// Display this player's own stats.
player.sendMessage();
player.sendMessage(ChatColor.YELLOW + "Your score: " + ChatColor.GOLD + ChatColor.BOLD + participant.getScore() + ChatColor.YELLOW + " (" + ChatColor.GREEN + participant.getKills() + ChatColor.YELLOW + "/" + ChatColor.RED + participant.getDeaths() + ChatColor.YELLOW + ")");
// Conclusion.
player.sendMessage(ChatColor.GREEN + "-----------------------------------------");
}
}
/**
* Broadcasts a chat message to all match participants.
*
* @param message The message to broadcast.
*/
public void broadcastMessageToPlayers(final String message){
for (MatchParticipant participant : this.getParticipants()){
participant.getPlayer().sendMessage(message);
}
}
/**
* Removes quitting match participants from this match.
*
* @param event The event.
*/
public void onPlayerQuit(final PlayerQuitEvent event){
this.removeParticipant(this.getParticipant(event.getPlayer()));
}
/**
* Kills match participants who leave the bounds of the arena.
*
* @param event The event.
*/
public void onPlayerMove(final PlayerMoveEvent event){
final Player player = event.getPlayer();
if (!this.getArena().isWithinBoundingBox(player.getLocation())){
player.setHealth(0.0);
}
}
/**
* Prevents match participants from interacting with blocks inside the arena.
*
* @param event The event.
*/
public void onPlayerInteract(final PlayerInteractEvent event){
// TODO: This is incomplete. Needs to block everything except snow blocks at all time.
if (this.graceTimeTicksRemaining > 0){
event.setCancelled(true);
}
}
/**
* Increments kill and death stats as needed and broadcasts death messages to match participants.
*
* @param event The event.
*/
public void onPlayerDeath(final PlayerDeathEvent event){
// Ensure no XP is dropped.
event.setDroppedExp(0);
final Player victimPlayer = event.getEntity();
final MatchParticipant victim = this.getParticipant(victimPlayer);
victim.awardDeath();
MatchParticipant killer = this.getParticipant(victim.getPlayer().getKiller());
if (killer != null && killer != victim){
killer.awardKill();
}
// Suppress global death message and only send to match participants.
this.broadcastMessageToPlayers(ChatColor.GRAY + "" + ChatColor.ITALIC + event.getDeathMessage());
event.setDeathMessage(null);
// Force the player to respawn in one second.
Bukkit.getScheduler().scheduleSyncDelayedTask(this.snowbrawl, () -> victimPlayer.spigot().respawn(), 20);
// Clear the player's inventory.
victimPlayer.getInventory().clear();
victimPlayer.updateInventory();
}
/**
* Changes the respawn point of dying match participants to a random spawn point in the arena.
*
* @param event The event.
*/
public void onPlayerRespawn(final PlayerRespawnEvent event){
event.setRespawnLocation(this.getArena().getRandomSpawnPoint());
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2022 Logan Fick
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at: https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
package dev.logal.snowbrawl.types;
import org.bukkit.entity.Player;
/**
* Represents a player participating in a match.
*/
public class MatchParticipant {
private final Player player;
private final Match match;
private int kills = 0, deaths = 0;
/**
* Creates a new match participant with a given player participating in a given match.
*
* @param player The player.
* @param match The match they are participating in.
*/
public MatchParticipant(final Player player, final Match match){
this.player = player;
this.match = match;
}
/**
* Increments this match participant's kill count.
*/
public void awardKill(){
this.kills++;
}
/**
* Increments this match participants death count.
*/
public void awardDeath(){
this.deaths++;
}
/**
* Gets the current amount of kills this match participant has.
*
* @return The number of kills.
*/
public int getKills(){
return this.kills;
}
/**
* Gets the current amount of deaths this match participant has.
*
* @return The number of deaths.
*/
public int getDeaths(){
return this.deaths;
}
/**
* Gets the current score for this player. Calculated by subtracing deaths from kills.
*
* @return The score.
*/
public int getScore(){
return this.getKills() - this.getDeaths();
}
/**
* Gets the player represented by this match participant.
*
* @return The player.
*/
public Player getPlayer(){
return this.player;
}
/**
* Gets the match this match participant is participating in.
*
* @return The match.
*/
public Match getMatch(){
return this.match;
}
}