Initial commit.

This commit is contained in:
Logan Fick 2022-05-29 19:09:00 -04:00
commit a5ccc11b40
Signed by: LogalDeveloper
GPG Key ID: 43E58A0C922AB7D1
23 changed files with 4213 additions and 0 deletions

150
.gitignore vendored Normal file
View File

@ -0,0 +1,150 @@
### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
.idea/artifacts
.idea/compiler.xml
.idea/jarRepositories.xml
.idea/modules.xml
.idea/*.iml
.idea/modules
*.iml
*.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
# Sonarlint plugin
# https://plugins.jetbrains.com/plugin/7973-sonarlint
.idea/**/sonarlint/
# SonarQube Plugin
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
.idea/**/sonarIssues.xml
# Markdown Navigator plugin
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
.idea/**/markdown-navigator.xml
.idea/**/markdown-navigator-enh.xml
.idea/**/markdown-navigator/
# Cache file creation bug
# See https://youtrack.jetbrains.com/issue/JBR-2257
.idea/$CACHE_FILE$
# CodeStream plugin
# https://plugins.jetbrains.com/plugin/12206-codestream
.idea/codestream.xml
# Azure Toolkit for IntelliJ plugin
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
.idea/**/azureSettings.xml
### Java ###
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
### Gradle ###
.gradle
**/build/
!src/**/build/
# Ignore Gradle GUI config
gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# Avoid ignore Gradle wrappper properties
!gradle-wrapper.properties
# Cache of project
.gradletasknamecache
# Eclipse Gradle plugin generated files
# Eclipse Core
.project
# JDT-specific (Eclipse Java Development Tools)
.classpath

View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

15
.idea/misc.xml Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<list size="4">
<item index="0" class="java.lang.String" itemvalue="Command" />
<item index="1" class="java.lang.String" itemvalue="com.nemez.cmdmgr.Command" />
<item index="2" class="java.lang.String" itemvalue="net.nemez.cmdmgr.Command" />
<item index="3" class="java.lang.String" itemvalue="org.bukkit.event.EventHandler" />
</list>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="openjdk-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

176
LICENSE.txt Normal file
View File

@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

42
build.gradle Normal file
View File

@ -0,0 +1,42 @@
plugins {
id 'java'
}
group 'dev.logal.snowbrawl'
version '2'
repositories {
mavenCentral()
// Spigot
maven { url = 'https://oss.sonatype.org/content/repositories/snapshots' }
maven { url = 'https://oss.sonatype.org/content/repositories/central' }
maven {
url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/'
content {
includeGroup 'org.bukkit'
includeGroup 'org.spigotmc'
}
}
// CommandManager
maven { url 'https://git.nemez.net/api/v4/projects/52/packages/maven' }
}
dependencies {
compileOnly 'org.spigotmc:spigot-api:1.18.2-R0.1-SNAPSHOT'
implementation 'net.nemez:cmdmgr:2.0.1'
}
jar {
manifest {
attributes "Class-Path": "../lib/cmdmgr-2.0.1.jar"
}
}
task deployToDevelopmentServer(dependsOn: build, type: Copy) {
from "$buildDir/libs/Snowbrawl-2.jar"
into "development-server/plugins/"
}

2
development-server/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
gradlew vendored Normal file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# 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.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MSYS* | MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

2
settings.gradle Normal file
View File

@ -0,0 +1,2 @@
rootProject.name = 'Snowbrawl'

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;
}
}

View File

@ -0,0 +1,145 @@
command snowbrawl {
alias sb;
[empty] {
run join_queue;
type player;
perm redstoner.snowbrawl;
}
arena list {
perm redstoner.snowbrawl.arena.list;
run get_arenas;
type all;
help Lists all known arenas.;
}
arena create [string:name] {
perm redstoner.snowbrawl.arena.create;
run create_arena name;
type player;
help Creates a new arena.;
}
arena create [string:name] [string:world]{
perm redstoner.snowbrawl.arena.create;
run create_arena_in_specific_world name world;
type all;
help Creates a new arena in a specific world.;
}
arena [string:name] delete {
perm redstoner.snowbrawl.arena.delete;
run delete_arena name;
type all;
help Deletes an arena.;
}
arena [string:name] config {
run get_arena_config name;
type all;
help Prints all configuration variable values for an arena.;
name [string:new_name] {
perm redstoner.snowbrawl.arena.config.name;
run set_arena_name name new_name;
type all;
help Changes the name of an arena.;
}
refill [int:amount] {
perm redstoner.snowbrawl.arena.config.refill;
run set_arena_refill name amount;
type all;
help Changes the amount of snowballs given when a player refills their stock in this arena.;
}
players [int:amount] {
perm redstoner.snowbrawl.arena.config.players;
run set_arena_player_limit name amount;
type all;
help Changes the maximum amount of players allow to play in this arena.;
}
gracetime [int:seconds] {
perm redstoner.snowbrawl.arena.config.gracetime;
run set_arena_grace_time name seconds;
type all;
help Changes the amount of grace time given before each match in this arena.;
}
matchtime [int:seconds] {
perm redstoner.snowbrawl.arena.config.matchtime;
run set_arena_match_time name seconds;
type all;
help Changes the amount of time each match takes in this arena.;
}
damage [int:damage] {
perm redstoner.snowbrawl.arena.config.damage;
run set_arena_damage name damage;
type all;
help Changes the amount of damage done per snowball hit in this arena.;
}
bounds pos1 {
[block_x:x] [block_y:y] [block_z:z] {
run set_arena_pos1 name x y z;
type all;
help Sets one corner of this arena's bounds to the given coordinates.;
}
run set_arena_pos1_from_playerpos name;
type player;
help Sets one corner of this arena's bounds to your current position.;
}
bounds pos2 {
[block_x:x] [block_y:y] [block_z:z] {
run set_arena_pos2 name x y z;
type all;
help Sets the other corner of this arena's bounds to the given coordinates.;
}
run set_arena_pos2_from_playerpos name;
type player;
help Sets the other corner of this arena's bounds to your current position.;
}
bounds [block_x:x1] [block_y:y1] [block_z:z1] [block_x:x2] [block_y:y2] [block_z:z2] {
run set_arena_bounds name x1 y1 z1 x2 y2 z2;
type all;
help Sets this arena's bounds to the given pair of coordinates.;
}
spawnpoints list {
run get_arena_spawnpoints name;
type all;
help Gets all spawn points in this arena.;
}
spawnpoints add {
run add_arena_spawnpoint_from_playerpos name;
type player;
help Adds a new spawn point on your current position.;
}
spawnpoints add [entity_x:x] [entity_y:y] [entity_z:z] [entity_yaw:yaw] [entity_pitch:pitch] {
run add_arena_spawnpoint name x y z yaw pitch;
type player;
help Adds the given coordinates as a spawn point.;
}
spawnpoints delete [int:id] {
run delete_arena_spawnpoint name id;
type player;
help Deletes the spawn point of the given IDs.;
}
}
arena [string:name] teleport {
run go_to_arena name;
type player;
help Teleports to a spawn point in this arena.;
}
}

View File

View File

@ -0,0 +1,7 @@
name: Snowbrawl
version: 2.0-SNAPSHOT
description: "Redstoner's original minigame reborn for modern Minecraft."
author: LogalDeveloper
api-version: 1.18
main: dev.logal.snowbrawl.Snowbrawl