Add initial Capture the Flag plugin implementation
Implemented the core structure for a Capture the Flag (CTF) plugin. This includes team management, game phases, player classes, command handling, and configuration support. The project is set up with Gradle for dependency management and provides placeholders for future feature expansion.
This commit is contained in:
commit
6e38d42f2d
43
.gitignore
vendored
Normal file
43
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
.gradle
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
#.idea/modules.xml
|
||||
#.idea/jarRepositories.xml
|
||||
#.idea/compiler.xml
|
||||
#.idea/libraries/
|
||||
.idea/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
out/
|
||||
!**/src/main/**/out/
|
||||
!**/src/test/**/out/
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
bin/
|
||||
!**/src/main/**/bin/
|
||||
!**/src/test/**/bin/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
44
TODO.md
Normal file
44
TODO.md
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
- [ ] Game manager
|
||||
- [ ] Stores/creates/modifies teams
|
||||
- [ ] Allows for rollback of world through CoreProtect
|
||||
- [ ] Creates teams (exclusively) (load from config?)
|
||||
- [ ] Stores all players
|
||||
- [ ] Allows starting, restarting, and ending game
|
||||
- [ ] Limit playing area (x, y, and z) (could be done through WorldGuard)
|
||||
- [ ] Stores location for ppl to watch/wait in or handles putting them in gmsp
|
||||
- [ ] Team
|
||||
- [x] Stores members
|
||||
- [ ] Stores score
|
||||
- [ ] Stores respawn location
|
||||
- [ ] Respawn player on team by calling respawn function
|
||||
- [ ] Functions as starting point for team
|
||||
- [ ] Check if player is in respawn point (for class changes
|
||||
- [ ] Stores team colors (for armor)
|
||||
- [ ] Allows creation of Team player object (exclusively)
|
||||
- [ ] Manages respawns (config timer)
|
||||
- [ ] Tracks flag
|
||||
- [ ] Handles team losing
|
||||
- [ ] Stops respawns after team loses flag
|
||||
- [ ] Buff/Debuff for flag carrier and dead teams and chases?
|
||||
- [ ] Flag location indicator (beacon beams, compass, particles)
|
||||
- [ ] Snowball storage
|
||||
- [ ] Team player
|
||||
- [ ] Must be member of team
|
||||
- [ ] Stores health
|
||||
- [ ] Stores individual score
|
||||
- [ ] Class manager
|
||||
- [ ] Stores list of all classes
|
||||
- [ ] Allows Team member to select class
|
||||
- [ ] Classes
|
||||
- [ ] Fighter: lower health, higher dmg/throwing speed
|
||||
- [ ] Tank: Has shield, invincibility effect, slower (short + long cooldown)?
|
||||
- [ ] Engineer: Better shovel, lower health, lower dmg, (can drop snowballs to team members?) store snow at base, can build?
|
||||
- [ ] HARD Mage: Drops snowballs in area (casting cost)
|
||||
- [ ] HARD Scout: low dmg, high speed, (invisible sometimes?) can see flag carrier through walls or maybe see their path in particles?
|
||||
- [ ] Game events
|
||||
- [ ] Blocks dropping items (could be done through WorldGuard flag)
|
||||
- [ ] Hit by snowball (handles damage) (no friendly fire)
|
||||
- [ ] Only allows breaking snow (for snowballs)
|
||||
- [ ] Blocks building with anything other than snow (could be done through WorldGuard flag)
|
||||
- [ ] Blocks breaking anything other than snow (could be done through WorldGuard flag)
|
||||
- [ ] OPTIONAL: Wind to move players a bit if they are high up to discourage towering
|
||||
22
build.gradle.kts
Normal file
22
build.gradle.kts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
plugins {
|
||||
id("java")
|
||||
}
|
||||
|
||||
group = "com.alttd.ctf"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.alttd:Galaxy-API:1.21-R0.1-SNAPSHOT") {
|
||||
isChanging = true
|
||||
}
|
||||
|
||||
compileOnly("org.projectlombok:lombok:1.18.32")
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.32")
|
||||
|
||||
testImplementation(platform("org.junit:junit-bom:5.10.0"))
|
||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#Fri Dec 20 21:17:54 CET 2024
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
234
gradlew
vendored
Normal file
234
gradlew
vendored
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original 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 POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=${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 "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# 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" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
89
gradlew.bat
vendored
Normal file
89
gradlew.bat
vendored
Normal 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
|
||||
16
settings.gradle.kts
Normal file
16
settings.gradle.kts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
rootProject.name = "CaptureTheFlag"
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
maven("https://repo.destro.xyz/snapshots") // Altitude - Galaxy
|
||||
}
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
}
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
32
src/main/java/com/alttd/ctf/Main.java
Normal file
32
src/main/java/com/alttd/ctf/Main.java
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package com.alttd.ctf;
|
||||
|
||||
import com.alttd.ctf.commands.CommandManager;
|
||||
import com.alttd.ctf.config.Config;
|
||||
import com.alttd.ctf.events.OnSnowballHit;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
@Slf4j
|
||||
public class Main extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
log.info("Plugin enabled!");
|
||||
reloadConfigs();
|
||||
GameManager gameManager = new GameManager();
|
||||
CommandManager commandManager = new CommandManager(this, gameManager);
|
||||
registerEvents(gameManager);
|
||||
}
|
||||
|
||||
public void reloadConfigs() {
|
||||
Config.reload(this);
|
||||
}
|
||||
|
||||
private void registerEvents(GameManager gameManager) {
|
||||
PluginManager pluginManager = getServer().getPluginManager();
|
||||
pluginManager.registerEvents(new OnSnowballHit(gameManager), this);
|
||||
}
|
||||
|
||||
}
|
||||
118
src/main/java/com/alttd/ctf/commands/CommandManager.java
Normal file
118
src/main/java/com/alttd/ctf/commands/CommandManager.java
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
package com.alttd.ctf.commands;
|
||||
|
||||
import com.alttd.ctf.Main;
|
||||
import com.alttd.ctf.commands.subcommands.ChangeTeam;
|
||||
import com.alttd.ctf.commands.subcommands.Start;
|
||||
import com.alttd.ctf.config.Messages;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.bukkit.command.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Getter
|
||||
@Slf4j
|
||||
public class CommandManager implements CommandExecutor, TabExecutor {
|
||||
private final List<SubCommand> subCommands;
|
||||
|
||||
public CommandManager(Main main, GameManager gameManager) {
|
||||
PluginCommand command = main.getCommand("ctf");
|
||||
if (command == null) {
|
||||
subCommands = null;
|
||||
log.error("Unable to find transfer command.");
|
||||
return;
|
||||
}
|
||||
command.setExecutor(this);
|
||||
command.setTabCompleter(this);
|
||||
|
||||
subCommands = Arrays.asList(
|
||||
new ChangeTeam(gameManager),
|
||||
new Start(gameManager)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String cmd, @NotNull String[] args) {
|
||||
if (args.length == 0) {
|
||||
commandSender.sendRichMessage(Messages.HELP.HELP_MESSAGE_WRAPPER.replaceAll("<commands>", subCommands.stream()
|
||||
.filter(subCommand -> commandSender.hasPermission(subCommand.getPermission()))
|
||||
.map(SubCommand::getHelpMessage)
|
||||
.collect(Collectors.joining("\n"))));
|
||||
return true;
|
||||
}
|
||||
|
||||
SubCommand subCommand = getSubCommand(args[0]);
|
||||
if (subCommand == null)
|
||||
return false;
|
||||
|
||||
if (!commandSender.hasPermission(subCommand.getPermission())) {
|
||||
commandSender.sendRichMessage(Messages.GENERIC.NO_PERMISSION, Placeholder.parsed("permission", subCommand.getPermission()));
|
||||
return true;
|
||||
}
|
||||
|
||||
int failedPos = subCommand.onCommand(commandSender, args);
|
||||
if (failedPos > 0) {
|
||||
commandSender.sendRichMessage(String.format("<hover:show_text:'%s'>%s</hover>",
|
||||
getHoverText(command.getName(), args, failedPos),
|
||||
subCommand.getHelpMessage()));
|
||||
} else if (failedPos < 0) {
|
||||
commandSender.sendRichMessage(subCommand.getHelpMessage());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private String getHoverText(String commandName, String[] args, int failedPos) {
|
||||
StringBuilder hoverText = new StringBuilder();
|
||||
|
||||
hoverText.append("<green>/").append(commandName);
|
||||
for (int i = 0; i < failedPos; i++) {
|
||||
hoverText.append(" ").append(args[i]);
|
||||
}
|
||||
hoverText.append("</green>");
|
||||
|
||||
if (failedPos < args.length) {
|
||||
hoverText.append(" <red>").append(args[failedPos]).append("</red>");
|
||||
}
|
||||
|
||||
for (int i = failedPos + 1; i < args.length; i++) {
|
||||
hoverText.append(" <yellow>").append(args[i]).append("</yellow>");
|
||||
}
|
||||
|
||||
return hoverText.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String cmd, @NotNull String[] args) {
|
||||
List<String> res = new ArrayList<>();
|
||||
|
||||
if (args.length <= 1) {
|
||||
res.addAll(subCommands.stream()
|
||||
.filter(subCommand -> commandSender.hasPermission(subCommand.getPermission()))
|
||||
.map(SubCommand::getName)
|
||||
.filter(name -> args.length == 0 || name.startsWith(args[0]))
|
||||
.toList()
|
||||
);
|
||||
} else {
|
||||
SubCommand subCommand = getSubCommand(args[0]);
|
||||
if (subCommand != null && commandSender.hasPermission(subCommand.getPermission()))
|
||||
res.addAll(subCommand.getTabComplete(commandSender, args).stream()
|
||||
.filter(str -> str.toLowerCase().startsWith(args[args.length - 1].toLowerCase()))
|
||||
.toList());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private SubCommand getSubCommand(String cmdName) {
|
||||
return subCommands.stream()
|
||||
.filter(subCommand -> subCommand.getName().equals(cmdName))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
25
src/main/java/com/alttd/ctf/commands/SubCommand.java
Normal file
25
src/main/java/com/alttd/ctf/commands/SubCommand.java
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package com.alttd.ctf.commands;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public abstract class SubCommand {
|
||||
|
||||
public SubCommand() {}
|
||||
|
||||
//A return value other than 0 means the command failed
|
||||
// If the value is more than 0 it should be the position in the array of the argument it failed on
|
||||
// If the value is less than 0 it means something else failed (like the amount of arguments)
|
||||
public abstract int onCommand(CommandSender commandSender, String[] args);
|
||||
|
||||
public abstract String getName();
|
||||
|
||||
public String getPermission() {
|
||||
return "ctf." + getName();
|
||||
}
|
||||
|
||||
public abstract List<String> getTabComplete(CommandSender commandSender, String[] args);
|
||||
|
||||
public abstract String getHelpMessage();
|
||||
}
|
||||
109
src/main/java/com/alttd/ctf/commands/subcommands/ChangeTeam.java
Normal file
109
src/main/java/com/alttd/ctf/commands/subcommands/ChangeTeam.java
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
package com.alttd.ctf.commands.subcommands;
|
||||
|
||||
import com.alttd.ctf.commands.SubCommand;
|
||||
import com.alttd.ctf.config.Messages;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.team.Team;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class ChangeTeam extends SubCommand {
|
||||
|
||||
private final GameManager gameManager;
|
||||
|
||||
public ChangeTeam(GameManager gameManager) {
|
||||
this.gameManager = gameManager;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface ChangeTeamConsumer {
|
||||
int apply(Player player, Team team);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onCommand(CommandSender commandSender, String[] args) {
|
||||
return handle(commandSender, args, (player, team) -> {
|
||||
changeTeam(commandSender, player, team);
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
private int handle(CommandSender commandSender, String[] args, ChangeTeamConsumer consumer) {
|
||||
if (args.length != 3) {
|
||||
return -1;
|
||||
}
|
||||
Player player = Bukkit.getPlayer(args[1]);
|
||||
if (player == null) {
|
||||
commandSender.sendRichMessage("<red>Please provide a valid player</red>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int teamId;
|
||||
try {
|
||||
teamId = Integer.parseInt(args[2]);
|
||||
} catch (NumberFormatException e) {
|
||||
commandSender.sendRichMessage(String.format("<red>Please enter a valid integer, %s is not a valid integer</red>", args[2]));
|
||||
return 2;
|
||||
}
|
||||
Optional<Team> optionalTeam = gameManager.getTeams().stream().filter(team -> team.getId() == teamId).findFirst();
|
||||
if (optionalTeam.isEmpty()) {
|
||||
commandSender.sendRichMessage(String.format("<red>Please provide a valid team id %d is not a valid team id</red>", teamId));
|
||||
return 3;
|
||||
}
|
||||
return consumer.apply(player, optionalTeam.get());
|
||||
}
|
||||
|
||||
private void changeTeam(CommandSender commandSender, Player player, Team team) {
|
||||
Optional<Team> optionalOldTeam = gameManager.getTeam(player.getUniqueId());
|
||||
if (optionalOldTeam.isPresent()) {
|
||||
moveBetweenTeams(commandSender, player, team, optionalOldTeam.get());
|
||||
return;
|
||||
}
|
||||
gameManager.registerPlayer(team, player);
|
||||
commandSender.sendRichMessage("<green><player> has been placed in<team>.</green>",
|
||||
TagResolver.resolver(
|
||||
Placeholder.component("player", player.displayName()),
|
||||
Placeholder.component("team", team.getName())));
|
||||
}
|
||||
|
||||
private void moveBetweenTeams(CommandSender commandSender, Player player, Team newTeam, Team oldTeam) {
|
||||
if (oldTeam.equals(newTeam)) {
|
||||
commandSender.sendRichMessage("<green><player> was already in <team>, nothing has changed.</green>",
|
||||
TagResolver.resolver(
|
||||
Placeholder.component("player", player.displayName()),
|
||||
Placeholder.component("team", newTeam.getName())));
|
||||
return;
|
||||
}
|
||||
gameManager.registerPlayer(newTeam, player);
|
||||
commandSender.sendRichMessage("<green><player> has been moved from <old_team> to <new_team>.</green>",
|
||||
TagResolver.resolver(
|
||||
Placeholder.component("player", player.displayName()),
|
||||
Placeholder.component("old_team", oldTeam.getName())),
|
||||
Placeholder.component("new_team", newTeam.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "changeteam";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTabComplete(CommandSender commandSender, String[] args) {
|
||||
return switch (args.length) {
|
||||
case 2 -> Bukkit.getOnlinePlayers().stream().map(Player::getName).toList();
|
||||
case 3 -> gameManager.getTeams().stream().map(Team::getId).map(Object::toString).toList();
|
||||
default -> List.of();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpMessage() {
|
||||
return Messages.HELP.CHANGE_TEAM;
|
||||
}
|
||||
}
|
||||
64
src/main/java/com/alttd/ctf/commands/subcommands/Start.java
Normal file
64
src/main/java/com/alttd/ctf/commands/subcommands/Start.java
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package com.alttd.ctf.commands.subcommands;
|
||||
|
||||
import com.alttd.ctf.commands.SubCommand;
|
||||
import com.alttd.ctf.config.Messages;
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
|
||||
public class Start extends SubCommand {
|
||||
|
||||
private final GameManager gameManager;
|
||||
|
||||
public Start(GameManager gameManager) {
|
||||
this.gameManager = gameManager;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface StartConsumer {
|
||||
int apply(Duration combatTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onCommand(CommandSender commandSender, String[] args) {
|
||||
return handle(commandSender, args, combatTime -> {
|
||||
gameManager.start(combatTime);
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
private int handle(CommandSender commandSender, String[] args, StartConsumer consumer) {
|
||||
if (args.length != 2) {
|
||||
return -1;
|
||||
}
|
||||
Duration combatTime;
|
||||
try {
|
||||
combatTime = Duration.ofSeconds(Integer.parseInt(args[1]));
|
||||
} catch (NumberFormatException e) {
|
||||
commandSender.sendRichMessage("<red>Please enter a valid integer</red>");
|
||||
return 1;
|
||||
}
|
||||
return consumer.apply(combatTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "start";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getTabComplete(CommandSender commandSender, String[] args) {
|
||||
//noinspection SwitchStatementWithTooFewBranches
|
||||
return switch (args.length) {
|
||||
case 2 -> List.of("30", "45", "60");
|
||||
default -> List.of();
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHelpMessage() {
|
||||
return Messages.HELP.START;
|
||||
}
|
||||
}
|
||||
148
src/main/java/com/alttd/ctf/config/AbstractConfig.java
Normal file
148
src/main/java/com/alttd/ctf/config/AbstractConfig.java
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
package com.alttd.ctf.config;
|
||||
|
||||
import com.alttd.ctf.Main;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.InvalidConfigurationException;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
@SuppressWarnings({"unused", "SameParameterValue"})
|
||||
abstract class AbstractConfig {
|
||||
File file;
|
||||
YamlConfiguration yaml;
|
||||
|
||||
AbstractConfig(Main main, String filename) {
|
||||
init(new File(main.getDataFolder(), filename), filename);
|
||||
}
|
||||
|
||||
AbstractConfig(File file, String filename) {
|
||||
init(new File(file.getPath() + File.separator + filename), filename);
|
||||
}
|
||||
|
||||
private void init(File file, String filename) {
|
||||
this.file = file;
|
||||
this.yaml = new YamlConfiguration();
|
||||
try {
|
||||
yaml.load(file);
|
||||
} catch (IOException ignore) {
|
||||
} catch (InvalidConfigurationException ex) {
|
||||
log.error("Could not load {}, please correct your syntax errors", filename, ex);
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
yaml.options().copyDefaults(true);
|
||||
}
|
||||
|
||||
void readConfig(Class<?> clazz, Object instance) {
|
||||
for (Class<?> declaredClass : clazz.getDeclaredClasses()) {
|
||||
for (Method method : declaredClass.getDeclaredMethods()) {
|
||||
if (!Modifier.isPrivate(method.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
if (method.getParameterTypes().length != 0 || method.getReturnType() != Void.TYPE) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
method.setAccessible(true);
|
||||
method.invoke(instance);
|
||||
} catch (InvocationTargetException ex) {
|
||||
throw new RuntimeException(ex.getCause());
|
||||
} catch (Exception ex) {
|
||||
log.error("Error invoking {}.", method, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
save();
|
||||
}
|
||||
|
||||
private void save() {
|
||||
try {
|
||||
yaml.save(file);
|
||||
} catch (IOException ex) {
|
||||
log.error("Could not save {}.", file.toString(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
void set(String prefix, String path, Object val) {
|
||||
path = prefix + path;
|
||||
yaml.addDefault(path, val);
|
||||
yaml.set(path, val);
|
||||
save();
|
||||
}
|
||||
|
||||
String getString(String prefix, String path, String def) {
|
||||
path = prefix + path;
|
||||
yaml.addDefault(path, def);
|
||||
return yaml.getString(path, yaml.getString(path));
|
||||
}
|
||||
|
||||
boolean getBoolean(String prefix, String path, boolean def) {
|
||||
path = prefix + path;
|
||||
yaml.addDefault(path, def);
|
||||
return yaml.getBoolean(path, yaml.getBoolean(path));
|
||||
}
|
||||
|
||||
int getInt(String prefix, String path, int def) {
|
||||
path = prefix + path;
|
||||
yaml.addDefault(path, def);
|
||||
return yaml.getInt(path, yaml.getInt(path));
|
||||
}
|
||||
|
||||
double getDouble(String prefix, String path, double def) {
|
||||
path = prefix + path;
|
||||
yaml.addDefault(path, def);
|
||||
return yaml.getDouble(path, yaml.getDouble(path));
|
||||
}
|
||||
|
||||
<T> List<String> getList(String prefix, String path, T def) {
|
||||
path = prefix + path;
|
||||
yaml.addDefault(path, def);
|
||||
List<?> list = yaml.getList(path, yaml.getList(path));
|
||||
return list == null ? null : list.stream().map(Object::toString).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
List<String> getStringList(String prefix, String path, List<String> def) {
|
||||
path = prefix + path;
|
||||
yaml.addDefault(path, def);
|
||||
return yaml.getStringList(path);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
<T> Map<String, T> getMap(String prefix, @NonNull String path, final @Nullable Map<String, T> def) {
|
||||
path = prefix + path;
|
||||
final ImmutableMap.Builder<String, T> builder = ImmutableMap.builder();
|
||||
if (def != null && yaml.getConfigurationSection(path) == null) {
|
||||
yaml.addDefault(path, def.isEmpty() ? new HashMap<>() : def);
|
||||
return def;
|
||||
}
|
||||
final ConfigurationSection section = yaml.getConfigurationSection(path);
|
||||
if (section != null) {
|
||||
for (String key : section.getKeys(false)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
final T val = (T) section.get(key);
|
||||
if (val != null) {
|
||||
builder.put(key, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
ConfigurationSection getConfigurationSection(String path) {
|
||||
return yaml.getConfigurationSection(path);
|
||||
}
|
||||
}
|
||||
44
src/main/java/com/alttd/ctf/config/Config.java
Normal file
44
src/main/java/com/alttd/ctf/config/Config.java
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
package com.alttd.ctf.config;
|
||||
|
||||
import com.alttd.ctf.Main;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
@Slf4j
|
||||
public class Config extends AbstractConfig{
|
||||
|
||||
static Config config;
|
||||
private final Main main;
|
||||
|
||||
Config(Main main) {
|
||||
super(main, "config.yml");
|
||||
this.main = main;
|
||||
}
|
||||
|
||||
public static void reload(Main main) {
|
||||
log.info("Reloading config");
|
||||
config = new Config(main);
|
||||
config.readConfig(Config.class, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class SETTINGS {
|
||||
private static final String prefix = "settings.";
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static void load() {
|
||||
if (config.getBoolean(prefix, "debug", false)) {
|
||||
config.main.getLogger().setLevel(Level.FINE);
|
||||
} else if (config.getBoolean(prefix, "warnings", true)) {
|
||||
config.main.getLogger().setLevel(Level.WARNING);
|
||||
} else {
|
||||
config.main.getLogger().setLevel(Level.INFO);
|
||||
}
|
||||
log.debug("Debug logging is enabled");
|
||||
log.warn("Warning logging is enabled");
|
||||
log.info("Info logging is enabled");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
48
src/main/java/com/alttd/ctf/config/GameConfig.java
Normal file
48
src/main/java/com/alttd/ctf/config/GameConfig.java
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package com.alttd.ctf.config;
|
||||
|
||||
import com.alttd.ctf.Main;
|
||||
import com.alttd.ctf.game.GamePhase;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
|
||||
@Slf4j
|
||||
public class GameConfig extends AbstractConfig{
|
||||
|
||||
static GameConfig config;
|
||||
|
||||
GameConfig(Main main) {
|
||||
super(main, "game-config.yml");
|
||||
}
|
||||
|
||||
public static void reload(Main main) {
|
||||
log.info("Reloading config");
|
||||
config = new GameConfig(main);
|
||||
config.readConfig(GameConfig.class, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static class PHASES {
|
||||
private static final String prefix = "phases.";
|
||||
|
||||
private static final HashMap<GamePhase, Duration> GAME_PHASE_DURATION = new HashMap<>();
|
||||
|
||||
public static HashMap<GamePhase, Duration> getGAME_PHASE_DURATION() {
|
||||
return new HashMap<>(GAME_PHASE_DURATION);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static void load() {
|
||||
GAME_PHASE_DURATION.clear();
|
||||
for (GamePhase phase : GamePhase.values()) {
|
||||
if (phase.equals(GamePhase.COMBAT))
|
||||
continue;
|
||||
int phaseDurationInMinutes = config.getInt(prefix, phase.name().toLowerCase(), 5);
|
||||
GAME_PHASE_DURATION.put(phase, Duration.ofMinutes(phaseDurationInMinutes));
|
||||
log.debug("Set {} phase duration to {} minutes", phase.name(), phaseDurationInMinutes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
52
src/main/java/com/alttd/ctf/config/Messages.java
Normal file
52
src/main/java/com/alttd/ctf/config/Messages.java
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package com.alttd.ctf.config;
|
||||
|
||||
import com.alttd.ctf.Main;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class Messages extends AbstractConfig {
|
||||
static Messages config;
|
||||
|
||||
Messages(Main main) {
|
||||
super(main, "config.yml");
|
||||
}
|
||||
|
||||
public static void reload(Main main) {
|
||||
log.info("Reloading messages config");
|
||||
config = new Messages(main);
|
||||
config.readConfig(Messages.class, null);
|
||||
}
|
||||
|
||||
public static class HELP {
|
||||
private static final String prefix = "help.";
|
||||
|
||||
public static String HELP_MESSAGE_WRAPPER = "<gold>Main help:\n<commands></gold>";
|
||||
public static String HELP_MESSAGE = "<green>Show this menu: <gold>/ctf help</gold></green>";
|
||||
public static String CHANGE_TEAM = "<green>Change a players team: <gold>/ctf changeteam <player> <team></gold></green>";
|
||||
public static String START = "<green>Start a new game: <gold>/ctf start <time_in_minutes></gold></green>";
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static void load() {
|
||||
HELP_MESSAGE_WRAPPER = config.getString(prefix, "help-wrapper", HELP_MESSAGE_WRAPPER);
|
||||
HELP_MESSAGE = config.getString(prefix, "help", HELP_MESSAGE);
|
||||
CHANGE_TEAM = config.getString(prefix, "change-team", CHANGE_TEAM);
|
||||
START = config.getString(prefix, "start", START);
|
||||
}
|
||||
}
|
||||
|
||||
public static class GENERIC {
|
||||
private static final String prefix = "generic.";
|
||||
|
||||
public static String NO_PERMISSION = "<red><hover:show_text:'<red><permission></red>'>You don't have permission for this command</hover></red>";
|
||||
public static String PLAYER_ONLY = "<red>This command can only be executed as a player</red>";
|
||||
public static String PLAYER_NOT_FOUND = "<red>Unable to find online player <player></red>";
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static void load() {
|
||||
NO_PERMISSION = config.getString(prefix, "no-permission", NO_PERMISSION);
|
||||
PLAYER_ONLY = config.getString(prefix, "player-only", PLAYER_ONLY);
|
||||
PLAYER_NOT_FOUND = config.getString(prefix, "player-only", PLAYER_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
78
src/main/java/com/alttd/ctf/events/OnSnowballHit.java
Normal file
78
src/main/java/com/alttd/ctf/events/OnSnowballHit.java
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
package com.alttd.ctf.events;
|
||||
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.game.GamePhase;
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import com.alttd.ctf.team.TeamPlayer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Slf4j
|
||||
public class OnSnowballHit implements Listener {
|
||||
|
||||
private final GameManager gameManager;
|
||||
|
||||
public OnSnowballHit(GameManager gameManager) {
|
||||
this.gameManager = gameManager;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface SnowballHitConsumer {
|
||||
void apply(Player hitPlayer, Player shooter, TeamPlayer shooterTeamPlayer);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onSnowballHit(EntityDamageByEntityEvent event) {
|
||||
handle(event, (hitPlayer, shooter, shooterTeamPlayer) -> {
|
||||
GameClass shooterClass = shooterTeamPlayer.getGameClass();
|
||||
shooter.setCooldown(Material.SNOWBALL, shooterClass.getThrowTickSpeed());
|
||||
|
||||
double newHealth = hitPlayer.getHealth() - shooterClass.getDamage();
|
||||
hitPlayer.setHealth(Math.max(newHealth, 0));
|
||||
log.debug("{} health was set to {} because of a snowball thrown by {}",
|
||||
hitPlayer.getName(), Math.max(newHealth, 0), shooter.getName());
|
||||
});
|
||||
}
|
||||
|
||||
private void handle(EntityDamageByEntityEvent event, SnowballHitConsumer consumer) {
|
||||
Optional<GamePhase> optionalGamePhase = gameManager.getGamePhase();
|
||||
if (optionalGamePhase.isEmpty()) {
|
||||
log.debug("No game is running but player was hit by snowball");
|
||||
return;
|
||||
}
|
||||
GamePhase gamePhase = optionalGamePhase.get();
|
||||
|
||||
if (!gamePhase.equals(GamePhase.COMBAT)) {
|
||||
log.debug("Not in combat phase but player was hit by snowball");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(event.getEntity() instanceof Player hitPlayer)) {
|
||||
log.debug("An entity other than a player was hit by a snowball");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(event.getDamager() instanceof org.bukkit.entity.Snowball snowball)) {
|
||||
log.debug("The player was hit by something other than a snowball");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(snowball.getShooter() instanceof Player shooter)) {
|
||||
log.debug("The shooter that hit a player with a snowball was not a player");
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<TeamPlayer> teamPlayer = gameManager.getTeamPlayer(shooter.getUniqueId());
|
||||
if (teamPlayer.isEmpty()) {
|
||||
log.debug("The shooter that hit a player with a snowball was not a team player");
|
||||
return;
|
||||
}
|
||||
consumer.apply(hitPlayer, shooter, teamPlayer.get());
|
||||
}
|
||||
}
|
||||
77
src/main/java/com/alttd/ctf/game/GameManager.java
Normal file
77
src/main/java/com/alttd/ctf/game/GameManager.java
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
package com.alttd.ctf.game;
|
||||
|
||||
import com.alttd.ctf.game.phases.ClassSelectionPhase;
|
||||
import com.alttd.ctf.game_class.implementations.Fighter;
|
||||
import com.alttd.ctf.team.Team;
|
||||
import com.alttd.ctf.team.TeamPlayer;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class GameManager {
|
||||
|
||||
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
|
||||
private final HashMap<GamePhase, GamePhaseExecutor> phases;
|
||||
private RunningGame runningGame;
|
||||
private final HashMap<Integer, Team> teams = new HashMap<>();
|
||||
|
||||
public GameManager() {
|
||||
phases = new HashMap<>();
|
||||
//TODO initialize this somewhere else (maybe load it from a json file?)
|
||||
phases.put(GamePhase.CLASS_SELECTION, new ClassSelectionPhase(this, new Fighter(List.of(Material.LEATHER_CHESTPLATE), List.of(), new ItemStack(Material.STONE), 15, 3, 5)));
|
||||
}
|
||||
|
||||
public Optional<GamePhase> getGamePhase() {
|
||||
return runningGame == null ? Optional.empty() : Optional.of(runningGame.getCurrentPhase());
|
||||
}
|
||||
|
||||
public void registerPlayer(Team team, Player player) {
|
||||
unregisterPlayer(player);
|
||||
teams.get(team.getId()).addPlayer(player.getUniqueId());
|
||||
}
|
||||
|
||||
public void unregisterPlayer(Player player) {
|
||||
teams.values().forEach(team -> team.removePlayer(player.getUniqueId()));
|
||||
}
|
||||
|
||||
public Collection<Team> getTeams() {
|
||||
return teams.values();
|
||||
}
|
||||
|
||||
public Optional<Team> getTeam(@NotNull UUID uuid) {
|
||||
return getTeams().stream().filter(filterTeam -> filterTeam.getPlayer(uuid).isPresent()).findAny();
|
||||
}
|
||||
|
||||
public Optional<TeamPlayer> getTeamPlayer(@NotNull UUID uuid) {
|
||||
return getTeams().stream()
|
||||
.map(team -> team.getPlayer(uuid))
|
||||
.filter(Optional::isPresent)
|
||||
.findFirst()
|
||||
.orElseGet(Optional::empty);
|
||||
}
|
||||
|
||||
public void start(Duration duration) {
|
||||
if (runningGame != null) {
|
||||
runningGame.end();
|
||||
executorService.shutdown();
|
||||
executorService = Executors.newSingleThreadScheduledExecutor();
|
||||
}
|
||||
runningGame = new RunningGame(this, duration);
|
||||
executorService.scheduleAtFixedRate(runningGame, 0, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
protected GamePhaseExecutor getPhaseExecutor(GamePhase gamePhase) {
|
||||
GamePhaseExecutor gamePhaseExecutor = phases.get(gamePhase);
|
||||
if (gamePhaseExecutor == null) {
|
||||
throw new IllegalArgumentException("No phase executor found for phase " + gamePhase);
|
||||
}
|
||||
return gamePhaseExecutor;
|
||||
}
|
||||
}
|
||||
19
src/main/java/com/alttd/ctf/game/GamePhase.java
Normal file
19
src/main/java/com/alttd/ctf/game/GamePhase.java
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package com.alttd.ctf.game;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
|
||||
@Getter
|
||||
public enum GamePhase {
|
||||
CLASS_SELECTION(MiniMessage.miniMessage().deserialize("<green>Class selection phase</green>")),
|
||||
GATHERING(MiniMessage.miniMessage().deserialize("<green>Gathering phase</green>")),
|
||||
COMBAT(MiniMessage.miniMessage().deserialize("<green>Combat phase</green>")),
|
||||
ENDED(MiniMessage.miniMessage().deserialize("<green>Game end phase</green>"));
|
||||
|
||||
private final Component displayName;
|
||||
|
||||
GamePhase(Component displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
}
|
||||
8
src/main/java/com/alttd/ctf/game/GamePhaseExecutor.java
Normal file
8
src/main/java/com/alttd/ctf/game/GamePhaseExecutor.java
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
package com.alttd.ctf.game;
|
||||
|
||||
public interface GamePhaseExecutor {
|
||||
|
||||
void start();
|
||||
|
||||
void end();
|
||||
}
|
||||
71
src/main/java/com/alttd/ctf/game/RunningGame.java
Normal file
71
src/main/java/com/alttd/ctf/game/RunningGame.java
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
package com.alttd.ctf.game;
|
||||
|
||||
import com.alttd.ctf.config.GameConfig;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class RunningGame implements Runnable {
|
||||
|
||||
private final HashMap<GamePhase, Duration> phaseDurations = GameConfig.PHASES.getGAME_PHASE_DURATION();
|
||||
private final GameManager gameManager;
|
||||
@Getter
|
||||
private GamePhase currentPhase = GamePhase.values()[0];
|
||||
private Instant phaseStartTime = null;
|
||||
|
||||
public RunningGame(GameManager gameManager, Duration gameDuration) {
|
||||
this.gameManager = gameManager;
|
||||
phaseDurations.put(GamePhase.COMBAT, gameDuration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (phaseStartTime == null) {
|
||||
phaseStartTime = Instant.now();
|
||||
GamePhase nextPhase = GamePhase.values()[currentPhase.ordinal() + 1];
|
||||
nextPhaseActions(currentPhase, nextPhase);
|
||||
}
|
||||
|
||||
if (Duration.between(phaseStartTime, Instant.now()).compareTo(phaseDurations.get(currentPhase)) >= 0) {
|
||||
GamePhase nextPhase = GamePhase.values()[currentPhase.ordinal() + 1];
|
||||
nextPhaseActions(currentPhase, nextPhase);
|
||||
currentPhase = nextPhase;
|
||||
phaseStartTime = Instant.now();
|
||||
}
|
||||
}
|
||||
|
||||
private void nextPhaseActions(@NotNull GamePhase phase, @Nullable GamePhase nextPhase) {
|
||||
//TODO class/functions for each phase that they run at the start of that phase
|
||||
// These should notify the player of what the phase is and that it started as well
|
||||
gameManager.getPhaseExecutor(phase).start();
|
||||
if (nextPhase != null) {
|
||||
broadcastNextPhaseStartTime(phase, nextPhase);
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcastNextPhaseStartTime(GamePhase currentPhase, GamePhase nextPhase) {//TODO check how this works/what it should do
|
||||
//Remaining time for this phase
|
||||
Duration duration = phaseDurations.get(currentPhase).minus(Duration.between(phaseStartTime, Instant.now()));
|
||||
if (duration.toMinutes() > 5 && duration.toMinutes() % 15 == 0) {
|
||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize(
|
||||
"<green>The <phase> will start in <bold><red>" + duration.toMinutes() + "</red> minutes</bold></green>",
|
||||
Placeholder.component("phase", nextPhase.getDisplayName())
|
||||
));
|
||||
} else if (duration.toMinutes() < 5) {
|
||||
//TODO start minute countdown -> second countdown
|
||||
}
|
||||
}
|
||||
|
||||
public void end() {
|
||||
//TODO say the phase ended early?
|
||||
currentPhase = GamePhase.ENDED;
|
||||
nextPhaseActions(currentPhase, null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package com.alttd.ctf.game.phases;
|
||||
|
||||
import com.alttd.ctf.game.GameManager;
|
||||
import com.alttd.ctf.game.GamePhaseExecutor;
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import com.alttd.ctf.team.Team;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class ClassSelectionPhase implements GamePhaseExecutor {
|
||||
|
||||
private final GameManager gameManager;
|
||||
private final GameClass defaultClass;
|
||||
|
||||
public ClassSelectionPhase(@NotNull GameManager gameManager, @NotNull GameClass defaultClass) {
|
||||
this.gameManager = gameManager;
|
||||
this.defaultClass = defaultClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
teleportPlayersToStartingZone();
|
||||
Bukkit.broadcast(MiniMessage.miniMessage().deserialize("<green>Select your class</green>"));
|
||||
//TODO let players select classes
|
||||
// They should always be able to do this when in their starting zone
|
||||
// They should be locked into their starting zone until the next phase starts
|
||||
// That phase should handle opening the zone
|
||||
}
|
||||
|
||||
private void teleportPlayersToStartingZone() {
|
||||
Bukkit.getOnlinePlayers().forEach(player -> {
|
||||
Optional<Team> team = gameManager.getTeam(player.getUniqueId());
|
||||
if (team.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Location spawnLocation = team.get().getSpawnLocation();
|
||||
player.teleportAsync(spawnLocation);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void end() {
|
||||
gameManager.getTeams().forEach(team -> team.getPlayers().forEach(player -> {
|
||||
if (player.getGameClass() != null) {
|
||||
return;
|
||||
}
|
||||
defaultClass.apply(player);
|
||||
}));
|
||||
}
|
||||
}
|
||||
81
src/main/java/com/alttd/ctf/game_class/GameClass.java
Normal file
81
src/main/java/com/alttd/ctf/game_class/GameClass.java
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
package com.alttd.ctf.game_class;
|
||||
|
||||
import com.alttd.ctf.team.TeamColor;
|
||||
import com.alttd.ctf.team.TeamPlayer;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Color;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
import org.bukkit.attribute.AttributeInstance;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.inventory.meta.LeatherArmorMeta;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public abstract class GameClass {
|
||||
|
||||
private final List<Material> armor;
|
||||
private final List<ItemStack> tools;
|
||||
private final ItemStack displayItem;
|
||||
private final double health;
|
||||
//Delay in ticks between throws (prevents auto clickers)
|
||||
@Getter
|
||||
private final int throwTickSpeed;
|
||||
//Damage done when hitting a player with a snowball
|
||||
@Getter
|
||||
private final int damage;
|
||||
|
||||
protected GameClass(List<Material> armor, List<ItemStack> tools, ItemStack displayItem, double health, int throwTickSpeed, int damage) {
|
||||
this.armor = armor;
|
||||
this.tools = tools;
|
||||
this.displayItem = displayItem;
|
||||
this.health = health;
|
||||
this.throwTickSpeed = throwTickSpeed;
|
||||
this.damage = damage;
|
||||
}
|
||||
|
||||
public void apply(TeamPlayer teamPlayer) {
|
||||
Player player = Bukkit.getPlayer(teamPlayer.getUuid());
|
||||
if (player == null || !player.isOnline()) {
|
||||
log.warn("Tried to give class to offline player {}", player == null ? teamPlayer.getUuid() : player.getName());
|
||||
return;
|
||||
}
|
||||
AttributeInstance maxHealthAttribute = player.getAttribute(Attribute.GENERIC_MAX_HEALTH);
|
||||
if (maxHealthAttribute == null) {
|
||||
log.error("Player does not have max health attribute");
|
||||
return;
|
||||
}
|
||||
//Always reset the player inventory since other classes might have had them get items
|
||||
player.getInventory().clear();
|
||||
|
||||
player.getInventory().setContents(tools.toArray(ItemStack[]::new));
|
||||
TeamColor color = teamPlayer.getTeam().getColor();
|
||||
setArmor(player, color.r(), color.g(), color.b());
|
||||
|
||||
player.updateInventory();
|
||||
maxHealthAttribute.setBaseValue(health);
|
||||
teamPlayer.setGameClass(this);
|
||||
}
|
||||
|
||||
private void setArmor(Player player, int r, int g, int b) {
|
||||
player.getInventory().setArmorContents(armor.stream().map(material -> {
|
||||
ItemStack itemStack = new ItemStack(material);
|
||||
ItemMeta itemMeta = itemStack.getItemMeta();
|
||||
if (itemMeta instanceof LeatherArmorMeta leatherArmorMeta) {
|
||||
leatherArmorMeta.setColor(Color.fromBGR(r, g, b));
|
||||
itemStack.setItemMeta(leatherArmorMeta);
|
||||
}
|
||||
return new ItemStack(material);
|
||||
}).toArray(ItemStack[]::new));
|
||||
}
|
||||
|
||||
ItemStack getDisplayItem() {
|
||||
return displayItem;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.alttd.ctf.game_class.implementations;
|
||||
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public class Fighter extends GameClass {
|
||||
|
||||
public Fighter(List<Material> armor, List<ItemStack> tools, ItemStack displayItem, double health, int tickThrowSpeed, int damage) {
|
||||
super(armor, tools, displayItem, health, tickThrowSpeed, damage);
|
||||
}
|
||||
|
||||
}
|
||||
74
src/main/java/com/alttd/ctf/team/Team.java
Normal file
74
src/main/java/com/alttd/ctf/team/Team.java
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
package com.alttd.ctf.team;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@Slf4j
|
||||
public class Team {
|
||||
|
||||
private final HashMap<UUID, TeamPlayer> players = new HashMap<>();
|
||||
@Getter
|
||||
private Component name;
|
||||
@Getter
|
||||
private final int id;
|
||||
@Getter
|
||||
private Location spawnLocation;
|
||||
@Getter
|
||||
private Location worldBorderCenter; //TODO https://github.com/yannicklamprecht/WorldBorderAPI/blob/main/how-to-use.md
|
||||
//TODO store team color to be used for kits and chat colors (in rgb?)
|
||||
@Getter
|
||||
private TeamColor color;
|
||||
|
||||
public Team(int id) {
|
||||
this.id = id;
|
||||
reloadTeamData();
|
||||
}
|
||||
|
||||
private void reloadTeamData() {
|
||||
this.color = new TeamColor(255, 0, 0, "#FF0000");
|
||||
this.name = MiniMessage.miniMessage().deserialize(String.format("<color:#%s>Test Team</color>", color.hex()));
|
||||
this.spawnLocation = Bukkit.getWorld("world").getSpawnLocation();
|
||||
//TODO load team data from config
|
||||
}
|
||||
|
||||
public void addPlayer(UUID uuid) {
|
||||
players.put(uuid, new TeamPlayer(uuid, this));
|
||||
log.debug("Added player with uuid {} to team with id {}", uuid, id);
|
||||
}
|
||||
|
||||
public Optional<TeamPlayer> getPlayer(@NotNull UUID uuid) {
|
||||
if (!players.containsKey(uuid))
|
||||
return Optional.empty();
|
||||
return Optional.of(players.get(uuid));
|
||||
}
|
||||
|
||||
public Collection<TeamPlayer> getPlayers() {
|
||||
return players.values();
|
||||
}
|
||||
|
||||
public void removePlayer(@NotNull UUID uuid) {
|
||||
TeamPlayer remove = players.remove(uuid);
|
||||
if (remove != null) {
|
||||
log.debug("Removed player with uuid {} from team with id {}", uuid, id);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Team other = (Team) obj;
|
||||
return Objects.equals(id, other.getId());
|
||||
}
|
||||
}
|
||||
4
src/main/java/com/alttd/ctf/team/TeamColor.java
Normal file
4
src/main/java/com/alttd/ctf/team/TeamColor.java
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
package com.alttd.ctf.team;
|
||||
|
||||
public record TeamColor(int r, int g, int b, String hex) {
|
||||
}
|
||||
39
src/main/java/com/alttd/ctf/team/TeamPlayer.java
Normal file
39
src/main/java/com/alttd/ctf/team/TeamPlayer.java
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package com.alttd.ctf.team;
|
||||
|
||||
import com.alttd.ctf.game_class.GameClass;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
@Getter
|
||||
public class TeamPlayer {
|
||||
|
||||
private final UUID uuid;
|
||||
private final Team team;
|
||||
@Setter
|
||||
private GameClass gameClass;
|
||||
|
||||
protected TeamPlayer(UUID uuid, Team team) {
|
||||
this.uuid = uuid;
|
||||
this.team = team;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
TeamPlayer other = (TeamPlayer) obj;
|
||||
return Objects.equals(uuid, other.uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(uuid);
|
||||
}
|
||||
}
|
||||
9
src/main/resources/plugin.yml
Normal file
9
src/main/resources/plugin.yml
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
name: CTF
|
||||
version: '${version}'
|
||||
main: com.alttd.ctf.Main
|
||||
api-version: '1.21'
|
||||
commands:
|
||||
ctf:
|
||||
description: Base command for capture the flag
|
||||
permission: ctf.use
|
||||
usage: /ctf
|
||||
Loading…
Reference in New Issue
Block a user