Compare commits

..

133 Commits

Author SHA1 Message Date
092d33beb3 prototype for grief detection 2025-03-29 22:03:12 +01:00
71d5d8303d added lightning fire control 2025-03-15 23:37:24 +01:00
49eeb646ea using common interface instead of individual methods in displayname appliance 2025-03-15 21:54:18 +01:00
ceca038b27 code reformat 2025-03-15 21:41:07 +01:00
76ceb9ef79 removed public directive where possible to reduce number of global accessible classes 2025-03-15 21:38:55 +01:00
219879974c categorized appliances in groups 2025-03-15 21:20:57 +01:00
bd630ebb7a fixed tabcomplete on mute command 2025-02-01 23:01:48 +01:00
c56f318f1c fixed wrong formatting on playtime 2025-02-01 23:01:28 +01:00
4d98d7aa75 removed restart kick message to trigger limbo when used 2024-12-27 11:01:18 +01:00
619190d0ae changed acInform teleport action to grim spectate 2024-12-27 11:00:55 +01:00
06641c5d84 added chatmute 2024-12-27 10:59:56 +01:00
2a52177043 added support for sentences in acinform reports 2024-12-26 20:50:01 +01:00
fc067a2ae0 moved late integrity check to login flow 2024-12-26 20:49:24 +01:00
116a9c11a2 updated ac inform design 2024-12-26 01:56:40 +01:00
3f29ceb08f fixed not respawning at spawnpoint on player death 2024-12-25 23:34:23 +01:00
a33ee357e8 fixed acinform not working with floating point numbers 2024-12-25 23:06:15 +01:00
e36256d5be async whitelist check 2024-12-25 21:09:42 +01:00
0e3a54a1b9 made repository calls async 2024-12-25 16:12:07 +01:00
2e67b41b44 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	src/main/java/eu/mhsl/craftattack/spawn/appliances/spawnpoint/Spawnpoint.java
2024-12-25 00:37:31 +01:00
e4ac8f7a63 renamed spawnpoint key name 2024-12-25 00:37:12 +01:00
e89e9d2181 renamed spawnpoint key name 2024-12-25 00:35:09 +01:00
8faf0efd60 added spawnpoint 2024-12-25 00:32:54 +01:00
2f1aeb71ee disabled infobars join restore 2024-12-25 00:13:54 +01:00
6475a7b825 updated texts and coordinates 2024-12-24 11:23:19 +01:00
193d8d778f Merge remote-tracking branch 'origin/master' 2024-12-20 21:52:25 +01:00
38da5b1d34 updated coordinates 2024-12-20 21:52:22 +01:00
04e3ddb09f changed custom advancements datapack name 2024-12-20 21:49:04 +01:00
47db27a86e removed bedrock block from WorldMuseum 2024-12-20 19:55:07 +01:00
f13534da3f more robust error handling on Whitelist 2024-12-20 19:54:38 +01:00
e45698c88a fix swapped command feedback 2024-12-20 18:46:20 +01:00
9197840873 removed unwanted HotbarRefill message 2024-12-20 13:34:32 +01:00
63d8335b3a reformatted code 2024-12-17 13:58:41 +01:00
184617e9c3 added EventRepository 2024-12-16 00:30:38 +01:00
696c4bc260 added bedrock displayname prefix 2024-12-09 22:04:30 +01:00
9004609c1b better synchronous call warning 2024-12-08 22:36:11 +01:00
ddedcea8ea added FeedbackRepository 2024-12-08 22:27:54 +01:00
318a30fe54 Merge branch 'refs/heads/master' into develop-apiUtil
# Conflicts:
#	src/main/java/eu/mhsl/craftattack/spawn/appliances/report/Report.java
2024-12-08 13:10:27 +01:00
4808d22f6a Merge remote-tracking branch 'origin/develop-apiUtil' into develop-apiUtil
# Conflicts:
#	src/main/java/eu/mhsl/craftattack/spawn/api/client/repositories/ReportRepository.java
#	src/main/java/eu/mhsl/craftattack/spawn/appliances/report/Report.java
2024-12-05 23:10:05 +01:00
6b2a323a9c implemented report repository 2024-12-05 23:02:15 +01:00
694ca0efba implemented reports request in Report.java 2024-12-05 22:16:15 +01:00
0276763a8d started implementing report repository 2024-12-05 18:58:55 +01:00
8811328571 implemented working WhitelistRepository 2024-12-05 12:46:51 +01:00
b3c43f1763 WIP: repositoryLoader and infrastructure 2024-12-04 23:34:12 +01:00
86677c942f implemented repository design pattern 2024-12-04 22:11:28 +01:00
31581fc643 added api util 2024-12-04 09:23:20 +01:00
a412f5c24c Merge remote-tracking branch 'refs/remotes/origin/master' into develop-feedback 2024-12-03 20:38:06 +01:00
eae979ee65 added feedback 2024-12-03 20:37:14 +01:00
28b9b84e07 changed afk timings map and time limit 2024-12-01 21:40:52 +01:00
a5cdb93f1b added /reports command 2024-12-01 18:03:33 +01:00
1572096020 Merge remote-tracking branch 'refs/remotes/origin/master' into develop-feedback 2024-12-01 17:41:12 +01:00
0ff9512a7f added wait message for ComponentUtil 2024-12-01 17:39:51 +01:00
f0ffb3ad21 Merge remote-tracking branch 'refs/remotes/origin/master' into develop-feedback 2024-11-28 22:16:13 +01:00
3c82939120 removed unwanted logging 2024-11-28 21:24:10 +01:00
c9935637b9 removed inconsistent spacing in acinform 2024-11-28 21:22:28 +01:00
b35c5b9240 updated afk and sleep tag icons 2024-11-25 20:40:43 +01:00
39814dae05 added sleepTag 2024-11-24 22:44:48 +01:00
7e3b043c98 added afkTag 2024-11-24 21:13:25 +01:00
5324749a64 Merge remote-tracking branch 'origin/master' 2024-11-24 20:06:22 +01:00
b9f88bc4b2 extended infobars, added coloring 2024-11-24 20:06:18 +01:00
8470115a74 added endPrevent 2024-11-24 02:33:36 +01:00
0f976d2316 added infobars 2024-11-10 17:28:02 +01:00
c52207298e Merge remote-tracking branch 'origin/master' 2024-11-10 12:23:52 +01:00
fd902f4ec6 updated maintenance kick message 2024-11-10 12:23:44 +01:00
0a7052b6f5 added error message for missing player name AcInform 2024-11-06 10:29:20 +01:00
0ea2d4958b Merge remote-tracking branch 'origin/master' 2024-11-05 22:28:33 +01:00
e8534b42ac prevented players from using ac inform command 2024-11-05 22:28:11 +01:00
3abf5a95e8 updated maintenance command feedback 2024-11-05 21:24:06 +01:00
fc5b76290e Merge remote-tracking branch 'origin/master' 2024-11-05 21:23:48 +01:00
01d1926104 removed webserver shutdown 2024-11-05 21:23:43 +01:00
e7075140e3 added anticheat info command 2024-11-05 20:48:42 +01:00
125b604393 small refactoring and added missing async task 2024-10-20 14:24:12 +02:00
c468696537 started with feedback applience 2024-10-19 19:53:51 +02:00
bc84b06f0d added AdminChat 2024-10-19 18:11:19 +02:00
b1427ac90e added playertime command 2024-10-19 15:46:34 +02:00
918ee5ed00 updated interfering listener 2024-10-09 14:36:08 +02:00
77fbc12873 Merge pull request 'develop-chatReply' (#5) from develop-chatReply into master
Reviewed-on: #5
Reviewed-by: Elias Müller <elias@elias-mueller.com>
2024-10-06 13:52:58 +00:00
14a33cae39 Merge branch 'master' into develop-chatReply 2024-10-06 13:52:18 +00:00
8462e43e89 replaced replyMapping.get(sender) with variable replyList 2024-10-06 15:50:17 +02:00
985b36ddc8 solved pr comments except making chatMessages an object variable 2024-10-06 15:23:37 +02:00
d69089a0eb cleaned up the code 2024-10-06 12:32:20 +02:00
346847d2b2 Merge pull request 'master-customAdvancements' (#4) from master-customAdvancements into master
Reviewed-on: #4
Reviewed-by: Elias Müller <elias@elias-mueller.com>
2024-10-05 16:40:51 +00:00
e3b07aa62f solved pr comments 2024-10-05 18:37:04 +02:00
497c5ad749 too old conversations get deleted, added prefix, changed some logic 2024-10-02 23:37:45 +02:00
471cd8e610 finished logic for reply command 2024-10-02 22:27:16 +02:00
f96356b620 added choose options 2024-09-30 16:23:32 +02:00
d8259b79ae Merge remote-tracking branch 'origin/develop-chatReply' into develop-chatReply 2024-09-29 22:44:10 +02:00
26b9fd5e0f changed basic reply logic 2024-09-29 22:43:55 +02:00
9390d8c67b Merge remote-tracking branch 'refs/remotes/origin/master' into develop-chatReply
# Conflicts:
#	src/main/java/eu/mhsl/craftattack/spawn/Main.java
#	src/main/java/eu/mhsl/craftattack/spawn/util/statistics/NetworkMonitor.java
#	src/main/resources/config.yml
#	src/main/resources/plugin.yml
2024-09-29 12:45:36 +02:00
da33e6e592 added more custom advancements, added pending advancements 2024-09-28 23:48:52 +02:00
1aebae6cd5 added fleischerchest and pixelblock advancements 2024-09-28 15:43:14 +02:00
bf94e152c8 changed snowball knockback 2024-09-28 12:59:42 +02:00
3c1dea3451 Merge remote-tracking branch 'origin/master' 2024-09-27 23:43:12 +02:00
36fc89e915 added snowball knockback 2024-09-27 23:42:56 +02:00
7a15984f19 added snowball knockback 2024-09-27 23:35:01 +02:00
87870c96f7 added snowball knockback 2024-09-27 23:32:28 +02:00
70a1740644 fixed rainbowComponent not folding back on end of color spectrum, ensuring no sudden color switch 2024-09-27 18:16:42 +02:00
24fbd50c62 added appendWithSpace component util 2024-09-27 18:02:24 +02:00
05e88845be network monitor now disables itself when not being able to read system stats properly 2024-09-27 17:09:45 +02:00
e5ff3d36fa added reflection for appliance loading, extended logging and error handling 2024-09-27 11:07:33 +02:00
d66996bc73 added year rank 2024-09-26 20:16:25 +02:00
247dae0155 added OptionLinks which are shown to the client as 'Server Links' 2024-09-25 19:04:12 +02:00
d7bc440620 added tab completion for maintenance command 2024-09-25 17:52:41 +02:00
b2021d5815 added maintenance mode 2024-09-25 14:49:30 +02:00
f4da7e7674 small cleanup in settings 2024-09-24 21:49:06 +02:00
da8e421532 moved setting declaration to each owning appliance 2024-09-24 19:20:59 +02:00
7a97b1595e added option to disable specific appliances 2024-09-24 18:31:48 +02:00
aaad9fe7d8 show more info in technical tab view 2024-09-23 19:18:19 +02:00
f89a935c05 added texture pack selector 2024-09-17 22:11:59 +02:00
f49cca7f33 added glowing berries 2024-09-14 20:36:04 +02:00
e11b3fd7bc renamed eventHandlers to listeners 2024-09-14 20:35:14 +02:00
d6a3fe358d added system monitor to tablist 2024-08-31 16:31:02 +02:00
0959eb4aa5 moved appliance queries to base method 2024-08-31 15:06:46 +02:00
50e4192e32 fixed double doors opening when sneaking 2024-08-31 12:14:48 +02:00
d21f009f7d door knocking no longer triggers when destroying doors 2024-08-31 11:39:40 +02:00
825fed639c fixed portable crafting when placing crafting tables 2024-08-31 11:17:55 +02:00
077c40f29d added knocking doors 2024-08-31 00:43:53 +02:00
2d696dcdbd added double doors 2024-08-30 23:58:01 +02:00
577e99fcfc added basic reply 2024-08-28 19:32:07 +02:00
ebab4cbd34 added chat mentions, added setting categories 2024-08-25 19:26:14 +02:00
4c4975b0a4 fixed hotbarRefill in creative mode 2024-08-25 17:42:57 +02:00
1ef4f86a14 added multi bool setting and switched hotbar replace to it 2024-08-25 14:30:18 +02:00
f187a867af added auto hotbar refill 2024-08-24 19:32:35 +02:00
62703ffd12 added anti sign edit 2024-08-24 16:49:35 +02:00
772687b15d added autoshulker, added support for SelectSettings 2024-08-24 15:06:21 +02:00
a60b6265a8 added portable crafting 2024-08-24 11:31:15 +02:00
3dc25d63fd applied code styling 2024-08-24 11:02:28 +02:00
78ba105291 added linebreaks for settings descriptions 2024-08-24 10:56:08 +02:00
35b40262a4 expanded debug commands, added setting toggle for join and leave messages 2024-08-24 02:46:50 +02:00
70058c552d added settings with technicalTab toggle 2024-08-24 01:37:02 +02:00
c01ae32f1f added networking statistics, refactored aggregates back to appliances 2024-08-23 23:17:02 +02:00
eb2c0505f5 added graceful stop for builtin webserver 2024-07-28 01:26:20 +02:00
7678fe11a3 Merge remote-tracking branch 'origin/master' 2024-07-28 01:26:07 +02:00
b5bab4e4b6 updated tablist 2024-07-28 01:25:54 +02:00
219 changed files with 7380 additions and 1775 deletions

View File

@ -22,13 +22,14 @@ repositories {
} }
dependencies { dependencies {
compileOnly 'io.papermc.paper:paper-api:1.19.4-R0.1-SNAPSHOT' compileOnly 'io.papermc.paper:paper-api:1.21.1-R0.1-SNAPSHOT'
compileOnly 'org.geysermc.floodgate:api:2.2.2-SNAPSHOT' compileOnly 'org.geysermc.floodgate:api:2.2.2-SNAPSHOT'
implementation 'org.apache.httpcomponents:httpclient:4.5.14' implementation 'org.apache.httpcomponents:httpclient:4.5.14'
implementation 'com.sparkjava:spark-core:2.9.4' implementation 'com.sparkjava:spark-core:2.9.4'
implementation 'org.reflections:reflections:0.10.2'
} }
def targetJavaVersion = 17 def targetJavaVersion = 21
java { java {
def javaVersion = JavaVersion.toVersion(targetJavaVersion) def javaVersion = JavaVersion.toVersion(targetJavaVersion)
sourceCompatibility = javaVersion sourceCompatibility = javaVersion
@ -58,3 +59,10 @@ tasks.register('copyJarToServer', Exec) {
commandLine 'scp', 'build/libs/spawn-1.0-all.jar', 'root@10.20.6.1:/home/minecraft/server/plugins' commandLine 'scp', 'build/libs/spawn-1.0-all.jar', 'root@10.20.6.1:/home/minecraft/server/plugins'
} }
tasks.register('copyJarToTestServer', Exec) {
dependsOn shadowJar
mustRunAfter shadowJar
commandLine 'cp', 'build/libs/spawn-1.0-all.jar', '/home/elias/Dokumente/mcTestServer/plugins/spawn-1.0-all.jar'
}

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

19
gradlew vendored
View File

@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@ -83,7 +83,8 @@ done
# This is normally unused # This is normally unused
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #( case $MAX_FD in #(
max*) max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045 # shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) || MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit" warn "Could not query maximum file descriptor limit"
esac esac
@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #( '' | soft) :;; #(
*) *)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045 # shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" || ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD" warn "Could not set maximum file descriptor limit to $MAX_FD"
esac esac
@ -201,11 +202,11 @@ fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # 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"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command; # Collect all arguments for the java command:
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# shell script including quotes and variable substitutions, so put them in # and any embedded shellness will be escaped.
# double quotes to make sure that they get re-expanded; and # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# * put everything else in single quotes, so that it's not re-expanded. # treated as '${Hostname}' itself on the command line.
set -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \

20
gradlew.bat vendored
View File

@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail
@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. echo location of your Java installation. 1>&2
goto fail goto fail

View File

@ -1,100 +1,98 @@
package eu.mhsl.craftattack.spawn; package eu.mhsl.craftattack.spawn;
import eu.mhsl.craftattack.spawn.aggregates.displayName.DisplayName; import eu.mhsl.craftattack.spawn.api.client.RepositoryLoader;
import eu.mhsl.craftattack.spawn.api.HttpServer; import eu.mhsl.craftattack.spawn.api.server.HttpServer;
import eu.mhsl.craftattack.spawn.appliance.Appliance; import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliances.adminMarker.AdminMarker;
import eu.mhsl.craftattack.spawn.appliances.customAdvancements.CustomAdvancements;
import eu.mhsl.craftattack.spawn.appliances.fleischerchest.Fleischerchest;
import eu.mhsl.craftattack.spawn.appliances.kick.Kick;
import eu.mhsl.craftattack.spawn.appliances.chatMessages.ChatMessages;
import eu.mhsl.craftattack.spawn.appliances.outlawed.Outlawed;
import eu.mhsl.craftattack.spawn.appliances.panicBan.PanicBan;
import eu.mhsl.craftattack.spawn.appliances.projectStart.ProjectStart;
import eu.mhsl.craftattack.spawn.appliances.debug.Debug;
import eu.mhsl.craftattack.spawn.appliances.event.Event;
import eu.mhsl.craftattack.spawn.appliances.help.Help;
import eu.mhsl.craftattack.spawn.appliances.playerlimit.PlayerLimit;
import eu.mhsl.craftattack.spawn.appliances.report.Report;
import eu.mhsl.craftattack.spawn.appliances.restart.Restart;
import eu.mhsl.craftattack.spawn.appliances.tablist.Tablist;
import eu.mhsl.craftattack.spawn.appliances.titleClear.TitleClear;
import eu.mhsl.craftattack.spawn.appliances.whitelist.Whitelist;
import eu.mhsl.craftattack.spawn.config.Configuration; import eu.mhsl.craftattack.spawn.config.Configuration;
import eu.mhsl.craftattack.spawn.appliances.worldmuseum.WorldMuseum;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.event.HandlerList; import org.bukkit.event.HandlerList;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.reflections.Reflections;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
public final class Main extends JavaPlugin { public final class Main extends JavaPlugin {
private static Main instance; private static Main instance;
private static Logger logger;
private List<Appliance> appliances; private List<Appliance> appliances;
private HttpServer httpApi; private RepositoryLoader repositoryLoader;
@Override @Override
public void onEnable() { public void onEnable() {
instance = this; instance = this;
saveDefaultConfig(); logger = instance().getLogger();
this.saveDefaultConfig();
try {
this.wrappedEnable();
} catch(Exception e) {
Main.logger().log(Level.SEVERE, "Error while initializing Spawn plugin, shutting down!", e);
Bukkit.shutdown();
}
}
private void wrappedEnable() {
Configuration.readConfig(); Configuration.readConfig();
List<String> disabledAppliances = Configuration.pluginConfig.getStringList("disabledAppliances");
appliances = List.of( Main.logger().info("Loading Repositories...");
new AdminMarker(), this.repositoryLoader = new RepositoryLoader();
new WorldMuseum(), Main.logger().info(String.format("Loaded %d repositories!", this.repositoryLoader.getRepositories().size()));
new TitleClear(),
new ProjectStart(),
new Tablist(),
new ChatMessages(),
new Report(),
new Event(),
new Help(),
new PlayerLimit(),
new Whitelist(),
new Restart(),
new Kick(),
new PanicBan(),
new Outlawed(),
new DisplayName(),
new Debug(),
new Fleischerchest(),
new CustomAdvancements()
);
Bukkit.getLogger().info("Loading appliances..."); Main.logger().info("Loading appliances...");
appliances.forEach(appliance -> { Reflections reflections = new Reflections(this.getClass().getPackageName());
Bukkit.getLogger().info("Enabling " + appliance.getClass().getSimpleName()); Set<Class<? extends Appliance>> applianceClasses = reflections.getSubTypesOf(Appliance.class);
this.appliances = applianceClasses.stream()
.filter(applianceClass -> !disabledAppliances.contains(applianceClass.getSimpleName()))
.map(applianceClass -> {
try {
return (Appliance) applianceClass.getDeclaredConstructor().newInstance();
} catch(Exception e) {
throw new RuntimeException(String.format("Failed to create instance of '%s'", applianceClass.getName()), e);
}
})
.toList();
Main.logger().info(String.format("Loaded %d appliances!", this.appliances.size()));
Main.logger().info("Initializing appliances...");
this.appliances.forEach(appliance -> {
appliance.onEnable(); appliance.onEnable();
appliance.initialize(this); appliance.initialize(this);
}); });
Bukkit.getLogger().info("Loaded " + appliances.size() + " appliances!"); Main.logger().info(String.format("Initialized %d appliances!", this.appliances.size()));
Bukkit.getLogger().info("Starting HTTP API"); Main.logger().info("Starting HTTP API...");
this.httpApi = new HttpServer(); new HttpServer();
getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); this.getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
Main.logger().info("Startup complete!");
} }
@Override @Override
public void onDisable() { public void onDisable() {
Bukkit.getLogger().info("Disabling appliances..."); Main.logger().info("Disabling appliances...");
appliances.forEach(appliance -> { this.appliances.forEach(appliance -> {
Bukkit.getLogger().info("Disabling " + appliance.getClass().getSimpleName()); Main.logger().info("Disabling " + appliance.getClass().getSimpleName());
appliance.onDisable(); appliance.onDisable();
appliance.destruct(this); appliance.destruct(this);
}); });
HandlerList.unregisterAll(this); HandlerList.unregisterAll(this);
Bukkit.getScheduler().cancelTasks(this); Bukkit.getScheduler().cancelTasks(this);
Bukkit.getLogger().info("Disabled " + appliances.size() + " appliances!"); Main.logger().info("Disabled " + this.appliances.size() + " appliances!");
} }
public <T extends Appliance> T getAppliance(Class<T> clazz) { public <T extends Appliance> T getAppliance(Class<T> clazz) {
return this.appliances.stream() return this.appliances.stream()
.filter(clazz::isInstance) .filter(clazz::isInstance)
.map(clazz::cast) .map(clazz::cast)
.findFirst() .findFirst()
.orElseThrow(() -> new RuntimeException(String.format("Appliance %s not loaded or instantiated!", clazz))); .orElseThrow(() -> new RuntimeException(String.format("Appliance %s not loaded or instantiated!", clazz)));
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -103,10 +101,18 @@ public final class Main extends JavaPlugin {
} }
public List<Appliance> getAppliances() { public List<Appliance> getAppliances() {
return appliances; return this.appliances;
}
public RepositoryLoader getRepositoryLoader() {
return this.repositoryLoader;
} }
public static Main instance() { public static Main instance() {
return instance; return instance;
} }
public static Logger logger() {
return logger;
}
} }

View File

@ -1,10 +0,0 @@
package eu.mhsl.craftattack.spawn.aggregate;
import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
public abstract class Aggregate extends Appliance {
public <T extends Appliance> T queryAppliance(Class<T> clazz) {
return Main.instance().getAppliance(clazz);
}
}

View File

@ -1,53 +0,0 @@
package eu.mhsl.craftattack.spawn.aggregates.displayName;
import eu.mhsl.craftattack.spawn.aggregate.Aggregate;
import eu.mhsl.craftattack.spawn.appliances.adminMarker.AdminMarker;
import eu.mhsl.craftattack.spawn.appliances.adminMarker.AdminMarkerListener;
import eu.mhsl.craftattack.spawn.appliances.outlawed.Outlawed;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentBuilder;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.function.Supplier;
public class DisplayName extends Aggregate {
public void update(Player player) {
TextColor playerColor = queryAppliance(AdminMarker.class).getPlayerColor(player);
List<Supplier<Component>> prefixes = List.of(
() -> queryAppliance(Outlawed.class).getNamePrefix(player)
);
ComponentBuilder<TextComponent, TextComponent.Builder> playerName = Component.text();
prefixes.forEach(supplier -> {
Component prefix = supplier.get();
if(prefix == null) return;
playerName.append(prefix).append(Component.text(" "));
});
playerName.append(Component.text(player.getName(), playerColor));
setGlobal(player, playerName.build());
}
private void setGlobal(Player player, Component component) {
try {
player.customName(component);
player.displayName(component);
player.playerListName(component);
} catch (Exception e) {
//TODO this throws often exceptions, but still works, don't know why
//Main.instance().getLogger().log(Level.SEVERE, e, e::getMessage);
}
}
@Override
protected @NotNull List<Listener> eventHandlers() {
return List.of(new AdminMarkerListener());
}
}

View File

@ -1,13 +0,0 @@
package eu.mhsl.craftattack.spawn.aggregates.displayName;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.player.PlayerJoinEvent;
public class DisplayNameUpdateListener extends ApplianceListener<DisplayName> {
@EventHandler(priority = EventPriority.LOWEST)
public void onJoin(PlayerJoinEvent event) {
getAppliance().update(event.getPlayer());
}
}

View File

@ -0,0 +1,91 @@
package eu.mhsl.craftattack.spawn.api.client;
import eu.mhsl.craftattack.spawn.Main;
import org.apache.http.client.utils.URIBuilder;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.function.Consumer;
@RepositoryLoader.IgnoreRepository
public abstract class HttpRepository extends Repository {
private final Consumer<URIBuilder> baseUriBuilder;
public HttpRepository(URI basePath) {
this(basePath, null);
}
public HttpRepository(URI basePath, @Nullable Consumer<URIBuilder> baseUriBuilder) {
super(basePath);
this.baseUriBuilder = baseUriBuilder == null
? uriBuilder -> {
}
: baseUriBuilder;
}
protected <TInput, TOutput> ReqResp<TOutput> post(String command, TInput data, Class<TOutput> outputType) {
return this.post(command, parameters -> {
}, data, outputType);
}
protected <TInput, TOutput> ReqResp<TOutput> post(String command, Consumer<URIBuilder> parameters, TInput data, Class<TOutput> outputType) {
HttpRequest request = this.getRequestBuilder(this.getUri(command, parameters))
.POST(HttpRequest.BodyPublishers.ofString(this.gson.toJson(data)))
.build();
return this.execute(request, outputType);
}
protected <TOutput> ReqResp<TOutput> get(String command, Class<TOutput> outputType) {
return this.get(command, parameters -> {
}, outputType);
}
protected <TOutput> ReqResp<TOutput> get(String command, Consumer<URIBuilder> parameters, Class<TOutput> outputType) {
HttpRequest request = this.getRequestBuilder(this.getUri(command, parameters))
.GET()
.build();
return this.execute(request, outputType);
}
private URI getUri(String command, Consumer<URIBuilder> parameters) {
try {
URIBuilder builder = new URIBuilder(this.basePath + "/" + command);
this.baseUriBuilder.accept(builder);
parameters.accept(builder);
return builder.build();
} catch(URISyntaxException e) {
throw new RuntimeException(e);
}
}
private HttpRequest.Builder getRequestBuilder(URI endpoint) {
return HttpRequest.newBuilder()
.uri(endpoint)
.header("User-Agent", Main.instance().getServer().getBukkitVersion())
.header("Content-Type", "application/json");
}
private <TResponse> ReqResp<TResponse> execute(HttpRequest request, Class<TResponse> clazz) {
ReqResp<String> rawResponse = this.sendHttp(request);
return new ReqResp<>(rawResponse.status(), this.gson.fromJson(rawResponse.data(), clazz));
}
private ReqResp<String> sendHttp(HttpRequest request) {
try(HttpClient client = HttpClient.newHttpClient()) {
this.validateThread(request.uri().getPath());
HttpResponse<String> httpResponse = client.send(request, HttpResponse.BodyHandlers.ofString());
return new ReqResp<>(httpResponse.statusCode(), httpResponse.body());
} catch(IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,27 @@
package eu.mhsl.craftattack.spawn.api.client;
import com.google.gson.Gson;
import eu.mhsl.craftattack.spawn.Main;
import org.bukkit.Bukkit;
import java.net.URI;
public abstract class Repository {
protected URI basePath;
protected Gson gson;
public Repository(URI basePath) {
this.basePath = basePath;
this.gson = new Gson();
}
protected void validateThread(String commandName) {
if(!Bukkit.isPrimaryThread()) return;
Main.logger().warning(String.format(
"Repository '%s' was called synchronously with command '%s'!",
this.getClass().getSimpleName(),
commandName
));
}
}

View File

@ -0,0 +1,48 @@
package eu.mhsl.craftattack.spawn.api.client;
import org.apache.commons.lang3.NotImplementedException;
import org.reflections.Reflections;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Set;
public class RepositoryLoader {
private final List<Repository> repositories;
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreRepository {
}
public RepositoryLoader() {
Reflections reflections = new Reflections(this.getClass().getPackageName());
Set<Class<? extends Repository>> repositories = reflections.getSubTypesOf(Repository.class);
this.repositories = repositories.stream()
.filter(repository -> !repository.isAnnotationPresent(IgnoreRepository.class))
.map(repository -> {
try {
return (Repository) repository.getDeclaredConstructor().newInstance();
} catch(InstantiationException | IllegalAccessException | InvocationTargetException |
NoSuchMethodException e) {
throw new RuntimeException(e);
}
})
.toList();
}
public <T> T getRepository(Class<T> clazz) {
//noinspection unchecked
return this.repositories.stream()
.filter(clazz::isInstance)
.map(repository -> (T) repository)
.findFirst()
.orElseThrow(() -> new NotImplementedException(String.format("Repository '%s' not found!", clazz.getSimpleName())));
}
public List<Repository> getRepositories() {
return this.repositories;
}
}

View File

@ -0,0 +1,4 @@
package eu.mhsl.craftattack.spawn.api.client;
public record ReqResp<TData>(int status, TData data) {
}

View File

@ -0,0 +1,29 @@
package eu.mhsl.craftattack.spawn.api.client.repositories;
import eu.mhsl.craftattack.spawn.api.client.HttpRepository;
import eu.mhsl.craftattack.spawn.api.client.ReqResp;
import eu.mhsl.craftattack.spawn.util.api.EventApiUtil;
import java.util.UUID;
public class EventRepository extends HttpRepository {
public EventRepository() {
super(EventApiUtil.getBaseUri());
}
public record CreatedRoom(UUID uuid) {
}
public record QueueRoom(UUID player, UUID room) {
public record Response(String error) {
}
}
public ReqResp<CreatedRoom> createSession() {
return this.post("room", null, CreatedRoom.class);
}
public ReqResp<QueueRoom.Response> queueRoom(QueueRoom request) {
return this.post("queueRoom", request, QueueRoom.Response.class);
}
}

View File

@ -0,0 +1,27 @@
package eu.mhsl.craftattack.spawn.api.client.repositories;
import com.google.common.reflect.TypeToken;
import eu.mhsl.craftattack.spawn.api.client.HttpRepository;
import eu.mhsl.craftattack.spawn.api.client.ReqResp;
import eu.mhsl.craftattack.spawn.util.api.WebsiteApiUtil;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class FeedbackRepository extends HttpRepository {
public FeedbackRepository() {
super(WebsiteApiUtil.getBaseUri(), WebsiteApiUtil::withAuthorizationSecret);
}
public record Request(String event, List<UUID> users) {
}
public ReqResp<Map<UUID, String>> createFeedbackUrls(Request data) {
final Type responseType = new TypeToken<Map<UUID, String>>() {
}.getType();
ReqResp<Object> rawData = this.post("feedback", data, Object.class);
return new ReqResp<>(rawData.status(), this.gson.fromJson(this.gson.toJson(rawData.data()), responseType));
}
}

View File

@ -0,0 +1,57 @@
package eu.mhsl.craftattack.spawn.api.client.repositories;
import eu.mhsl.craftattack.spawn.api.client.HttpRepository;
import eu.mhsl.craftattack.spawn.api.client.ReqResp;
import eu.mhsl.craftattack.spawn.util.api.WebsiteApiUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.UUID;
public class ReportRepository extends HttpRepository {
public ReportRepository() {
super(WebsiteApiUtil.getBaseUri(), WebsiteApiUtil::withAuthorizationSecret);
}
public record ReportCreationInfo(@NotNull UUID reporter, @Nullable UUID reported, String reason) {
}
public record ReportUrl(@NotNull String url) {
}
public record PlayerReports(
List<Report> from_self,
Object to_self
) {
public record Report(
@Nullable Reporter reported,
@NotNull String subject,
boolean draft,
@NotNull String status,
@NotNull String url
) {
public record Reporter(
@NotNull String username,
@NotNull String uuid
) {
}
}
}
public ReqResp<PlayerReports> queryReports(UUID player) {
return this.get(
"report",
(parameters) -> parameters.addParameter("uuid", player.toString()),
PlayerReports.class
);
}
public ReqResp<ReportUrl> createReport(ReportCreationInfo data) {
return this.post(
"report",
data,
ReportUrl.class
);
}
}

View File

@ -0,0 +1,31 @@
package eu.mhsl.craftattack.spawn.api.client.repositories;
import eu.mhsl.craftattack.spawn.api.client.HttpRepository;
import eu.mhsl.craftattack.spawn.api.client.ReqResp;
import eu.mhsl.craftattack.spawn.util.api.WebsiteApiUtil;
import java.util.UUID;
public class WhitelistRepository extends HttpRepository {
public WhitelistRepository() {
super(WebsiteApiUtil.getBaseUri(), WebsiteApiUtil::withAuthorizationSecret);
}
public record UserData(
UUID uuid,
String username,
String firstname,
String lastname,
Long banned_until,
Long outlawed_until
) {
}
public ReqResp<UserData> getUserData(UUID userId) {
return this.get(
"user",
parameters -> parameters.addParameter("uuid", userId.toString()),
UserData.class
);
}
}

View File

@ -1,4 +1,4 @@
package eu.mhsl.craftattack.spawn.api; package eu.mhsl.craftattack.spawn.api.server;
import com.google.gson.Gson; import com.google.gson.Gson;
import eu.mhsl.craftattack.spawn.Main; import eu.mhsl.craftattack.spawn.Main;
@ -15,6 +15,7 @@ public class HttpServer {
protected final Gson gson = new Gson(); protected final Gson gson = new Gson();
public static Object nothing = null; public static Object nothing = null;
public HttpServer() { public HttpServer() {
Spark.port(8080); Spark.port(8080);
@ -37,6 +38,7 @@ public class HttpServer {
} }
private final String applianceName; private final String applianceName;
private ApiBuilder(Appliance appliance) { private ApiBuilder(Appliance appliance) {
this.applianceName = appliance.getClass().getSimpleName().toLowerCase(); this.applianceName = appliance.getClass().getSimpleName().toLowerCase();
} }
@ -65,7 +67,7 @@ public class HttpServer {
HttpServer.Response response; HttpServer.Response response;
try { try {
response = new Response(Response.Status.SUCCESS, null, exec.get()); response = new Response(Response.Status.SUCCESS, null, exec.get());
} catch (Exception e) { } catch(Exception e) {
response = new Response(Response.Status.FAILURE, e, null); response = new Response(Response.Status.FAILURE, e, null);
} }
return HttpServer.this.gson.toJson(response); return HttpServer.this.gson.toJson(response);

View File

@ -1,6 +1,8 @@
package eu.mhsl.craftattack.spawn.appliance; package eu.mhsl.craftattack.spawn.appliance;
import eu.mhsl.craftattack.spawn.api.HttpServer; import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.api.client.Repository;
import eu.mhsl.craftattack.spawn.api.server.HttpServer;
import eu.mhsl.craftattack.spawn.config.Configuration; import eu.mhsl.craftattack.spawn.config.Configuration;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.PluginCommand; import org.bukkit.command.PluginCommand;
@ -20,12 +22,15 @@ import java.util.Optional;
*/ */
public abstract class Appliance { public abstract class Appliance {
private String localConfigPath; private String localConfigPath;
private List<Listener> listeners;
private List<ApplianceCommand<?>> commands;
public Appliance() { public Appliance() {
} }
/** /**
* Use this constructor to specify a config sub-path for use with the localConfig() method. * Use this constructor to specify a config sub-path for use with the localConfig() method.
*
* @param localConfigPath sub path, if not found, the whole config will be used * @param localConfigPath sub path, if not found, the whole config will be used
*/ */
public Appliance(String localConfigPath) { public Appliance(String localConfigPath) {
@ -34,15 +39,17 @@ public abstract class Appliance {
/** /**
* Provides a list of listeners for the appliance. All listeners will be automatically registered. * Provides a list of listeners for the appliance. All listeners will be automatically registered.
*
* @return List of listeners * @return List of listeners
*/ */
@NotNull @NotNull
protected List<Listener> eventHandlers() { protected List<Listener> listeners() {
return new ArrayList<>(); return new ArrayList<>();
} }
/** /**
* Provides a list of commands for the appliance. All commands will be automatically registered. * Provides a list of commands for the appliance. All commands will be automatically registered.
*
* @return List of commands * @return List of commands
*/ */
@NotNull @NotNull
@ -53,29 +60,47 @@ public abstract class Appliance {
/** /**
* Called on initialization to add all needed API Routes. * Called on initialization to add all needed API Routes.
* The routeBuilder can be used to get the correct Path prefixes * The routeBuilder can be used to get the correct Path prefixes
*
* @param apiBuilder holds data for needed route prefixes. * @param apiBuilder holds data for needed route prefixes.
*/ */
public void httpApi(HttpServer.ApiBuilder apiBuilder) {} public void httpApi(HttpServer.ApiBuilder apiBuilder) {
}
/** /**
* Provides a localized config section. Path can be set in appliance constructor. * Provides a localized config section. Path can be set in appliance constructor.
*
* @return Section of configuration for your appliance * @return Section of configuration for your appliance
*/ */
@NotNull @NotNull
public ConfigurationSection localConfig() { public ConfigurationSection localConfig() {
return Optional.ofNullable(Configuration.cfg.getConfigurationSection(localConfigPath)).orElse(Configuration.cfg); return Optional.ofNullable(Configuration.cfg.getConfigurationSection(this.localConfigPath))
.orElseGet(() -> Configuration.cfg.createSection(this.localConfigPath));
} }
public void onEnable() {} public void onEnable() {
public void onDisable() {} }
public void onDisable() {
}
public void initialize(@NotNull JavaPlugin plugin) { public void initialize(@NotNull JavaPlugin plugin) {
eventHandlers().forEach(listener -> Bukkit.getPluginManager().registerEvents(listener, plugin)); this.listeners = this.listeners();
commands().forEach(command -> setCommandExecutor(plugin, command.commandName, command)); this.commands = this.commands();
this.listeners.forEach(listener -> Bukkit.getPluginManager().registerEvents(listener, plugin));
this.commands.forEach(command -> this.setCommandExecutor(plugin, command.commandName, command));
} }
public void destruct(@NotNull JavaPlugin plugin) { public void destruct(@NotNull JavaPlugin plugin) {
eventHandlers().forEach(HandlerList::unregisterAll); this.listeners.forEach(HandlerList::unregisterAll);
}
protected <T extends Appliance> T queryAppliance(Class<T> clazz) {
return Main.instance().getAppliance(clazz);
}
protected <T extends Repository> T queryRepository(Class<T> clazz) {
return Main.instance().getRepositoryLoader().getRepository(clazz);
} }
private void setCommandExecutor(JavaPlugin plugin, String name, ApplianceCommand<?> executor) { private void setCommandExecutor(JavaPlugin plugin, String name, ApplianceCommand<?> executor) {
@ -84,8 +109,16 @@ public abstract class Appliance {
command.setExecutor(executor); command.setExecutor(executor);
command.setTabCompleter(executor); command.setTabCompleter(executor);
} else { } else {
Bukkit.getLogger().warning("Command " + name + " is not specified in plugin.yml!"); Main.logger().warning("Command " + name + " is not specified in plugin.yml!");
throw new RuntimeException("All commands must be registered in plugin.yml. Missing command: " + name); throw new RuntimeException("All commands must be registered in plugin.yml. Missing command: " + name);
} }
} }
public List<Listener> getListeners() {
return this.listeners;
}
public List<ApplianceCommand<?>> getCommands() {
return this.commands;
}
} }

View File

@ -1,8 +1,8 @@
package eu.mhsl.craftattack.spawn.appliance; package eu.mhsl.craftattack.spawn.appliance;
import eu.mhsl.craftattack.spawn.Main;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -17,9 +17,10 @@ import java.util.Optional;
/** /**
* Utility class which enables command name definition over a constructor. * Utility class which enables command name definition over a constructor.
*/ */
public abstract class ApplianceCommand<T extends Appliance> extends ApplianceSupplier<T> implements TabCompleter, CommandExecutor { public abstract class ApplianceCommand<T extends Appliance> extends CachedApplianceSupplier<T> implements TabCompleter, CommandExecutor {
public String commandName; public String commandName;
protected Component errorMessage = Component.text("Fehler: ").color(NamedTextColor.RED); protected Component errorMessage = Component.text("Fehler: ").color(NamedTextColor.RED);
public ApplianceCommand(String command) { public ApplianceCommand(String command) {
this.commandName = command; this.commandName = command;
} }
@ -32,12 +33,12 @@ public abstract class ApplianceCommand<T extends Appliance> extends ApplianceSup
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
try { try {
execute(sender, command, label, args); this.execute(sender, command, label, args);
} catch (Error e) { } catch(Error e) {
sender.sendMessage(errorMessage.append(Component.text(e.getMessage()))); sender.sendMessage(this.errorMessage.append(Component.text(e.getMessage())));
} catch (Exception e) { } catch(Exception e) {
sender.sendMessage(errorMessage.append(Component.text("Interner Fehler"))); sender.sendMessage(this.errorMessage.append(Component.text("Interner Fehler")));
Bukkit.getLogger().warning("Error executing appliance command " + commandName + ": " + e.getMessage()); Main.logger().warning("Error executing appliance command " + this.commandName + ": " + e.getMessage());
e.printStackTrace(System.err); e.printStackTrace(System.err);
return false; return false;
} }
@ -50,7 +51,7 @@ public abstract class ApplianceCommand<T extends Appliance> extends ApplianceSup
} }
protected List<String> tabCompleteReducer(List<String> response, String[] args) { protected List<String> tabCompleteReducer(List<String> response, String[] args) {
return response.stream().filter(s -> s.startsWith(args[args.length-1])).toList(); return response.stream().filter(s -> s.startsWith(args[args.length - 1])).toList();
} }
protected abstract void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception; protected abstract void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception;
@ -61,6 +62,7 @@ public abstract class ApplianceCommand<T extends Appliance> extends ApplianceSup
public static abstract class PlayerChecked<T extends Appliance> extends ApplianceCommand<T> { public static abstract class PlayerChecked<T extends Appliance> extends ApplianceCommand<T> {
private Player player; private Player player;
private Component notPlayerMessage = Component.text("Dieser Command kann nur von Spielern ausgeführt werden!").color(NamedTextColor.RED); private Component notPlayerMessage = Component.text("Dieser Command kann nur von Spielern ausgeführt werden!").color(NamedTextColor.RED);
public PlayerChecked(String command) { public PlayerChecked(String command) {
super(command); super(command);
} }
@ -78,7 +80,7 @@ public abstract class ApplianceCommand<T extends Appliance> extends ApplianceSup
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(!(sender instanceof Player)) { if(!(sender instanceof Player)) {
sender.sendMessage(notPlayerMessage); sender.sendMessage(this.notPlayerMessage);
return false; return false;
} }
this.player = (Player) sender; this.player = (Player) sender;

View File

@ -5,8 +5,9 @@ import org.bukkit.event.Listener;
/** /**
* Utility class which provides a specific, type save appliance. * Utility class which provides a specific, type save appliance.
* You can access the appliance with the protected 'appliance' field. * You can access the appliance with the protected 'appliance' field.
*
* @param <T> the type of your appliance * @param <T> the type of your appliance
*/ */
public abstract class ApplianceListener<T extends Appliance> extends ApplianceSupplier<T> implements Listener { public abstract class ApplianceListener<T extends Appliance> extends CachedApplianceSupplier<T> implements Listener {
} }

View File

@ -2,11 +2,11 @@ package eu.mhsl.craftattack.spawn.appliance;
import eu.mhsl.craftattack.spawn.Main; import eu.mhsl.craftattack.spawn.Main;
public class ApplianceSupplier<T extends Appliance> implements IApplianceSupplier<T> { public class CachedApplianceSupplier<T extends Appliance> implements IApplianceSupplier<T> {
private final T appliance; private final T appliance;
public ApplianceSupplier() { public CachedApplianceSupplier() {
this.appliance = Main.instance().getAppliance(Main.getApplianceType(getClass())); this.appliance = Main.instance().getAppliance(Main.getApplianceType(this.getClass()));
} }
@Override @Override

View File

@ -1,22 +0,0 @@
package eu.mhsl.craftattack.spawn.appliances.adminMarker;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.Color;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class AdminMarker extends Appliance {
public TextColor getPlayerColor(Player player) {
if (player.hasPermission("chatcolor")) return TextColor.color(Color.AQUA.asRGB()); // TODO read permission from config
return TextColor.color(Color.WHITE.asRGB());
}
@Override
protected @NotNull List<Listener> eventHandlers() {
return List.of(new AdminMarkerListener());
}
}

View File

@ -1,22 +0,0 @@
package eu.mhsl.craftattack.spawn.appliances.adminMarker;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.Color;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
public class AdminMarkerListener extends ApplianceListener<AdminMarker> {
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player p = event.getPlayer();
}
private TextColor getPlayerColor(Player player) {
if (player.hasPermission("chatcolor")) return TextColor.color(Color.AQUA.asRGB()); // TODO read permission from config
return TextColor.color(Color.WHITE.asRGB());
}
}

View File

@ -1,14 +0,0 @@
package eu.mhsl.craftattack.spawn.appliances.chatMessages;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class ChatMessages extends Appliance {
@Override
protected @NotNull List<Listener> eventHandlers() {
return List.of(new ChatMessagesListener());
}
}

View File

@ -1,70 +0,0 @@
package eu.mhsl.craftattack.spawn.appliances.chatMessages;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import io.papermc.paper.event.player.AsyncChatEvent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.Color;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import java.util.Optional;
public class ChatMessagesListener extends ApplianceListener<ChatMessages> {
@EventHandler
public void onPlayerChatEvent(AsyncChatEvent event) {
event.renderer(
(source, sourceDisplayName, message, viewer) ->
Component.text("")
.append(getReportablePlayerName(source))
.append(Component.text(" > ").color(TextColor.color(Color.GRAY.asRGB())))
.append(message).color(TextColor.color(Color.SILVER.asRGB()))
);
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
event.joinMessage(
Component
.text(">>> ").color(NamedTextColor.GREEN)
.append(getReportablePlayerName(event.getPlayer()))
);
}
@EventHandler
public void onPlayerLeave(PlayerQuitEvent event) {
event.quitMessage(
Component
.text("<<< ").color(NamedTextColor.RED)
.append(getReportablePlayerName(event.getPlayer()))
);
}
@EventHandler
public void onDeath(PlayerDeathEvent event) {
event.deathMessage(
Component
.text("")
.append(
Optional
.ofNullable(event.deathMessage())
.orElse(Component.text(event.getPlayer().getName()))
)
.color(TextColor.color(Color.SILVER.asRGB()))
);
}
private Component getReportablePlayerName(Player player) {
return Component
.text("")
.append(player.displayName())
.hoverEvent(HoverEvent.showText(Component.text("Klicke, um diesen Spieler zu reporten").color(NamedTextColor.GOLD)))
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/report " + player.getName() + " "));
}
}

View File

@ -1,22 +0,0 @@
package eu.mhsl.craftattack.spawn.appliances.customAdvancements;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Objects;
public class CustomAdvancements extends Appliance {
public void grantAdvancement(String advancementName, Player player) {
player.getAdvancementProgress(Objects.requireNonNull(Bukkit.getAdvancement(Objects.requireNonNull(NamespacedKey.fromString("custom_advancements:craftattack/" + advancementName))))).awardCriteria("criteria");
}
@Override
protected @NotNull List<Listener> eventHandlers() {
return List.of(new CustomAdvancementsDamageEntityListener());
}
}

View File

@ -1,20 +0,0 @@
package eu.mhsl.craftattack.spawn.appliances.customAdvancements;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
public class CustomAdvancementsDamageEntityListener extends ApplianceListener<CustomAdvancements> {
@EventHandler
public void onEntityDamageEntity(EntityDamageByEntityEvent event) {
Entity damaged = event.getEntity();
if(!(damaged instanceof Player)) return;
Entity damager = event.getDamager();
if(!(damager instanceof Player)) return;
if(damaged.hasPermission("admin")) {
getAppliance().grantAdvancement("search_trouble", (Player) damager);
}
}
}

View File

@ -1,18 +0,0 @@
package eu.mhsl.craftattack.spawn.appliances.debug;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.debug.command.AppliancesCommand;
import eu.mhsl.craftattack.spawn.appliances.debug.command.UserInfoCommand;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class Debug extends Appliance {
@Override
protected @NotNull List<ApplianceCommand<?>> commands() {
return List.of(
new UserInfoCommand(),
new AppliancesCommand()
);
}
}

View File

@ -1,30 +0,0 @@
package eu.mhsl.craftattack.spawn.appliances.debug.command;
import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.debug.Debug;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentBuilder;
import net.kyori.adventure.text.TextComponent;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.stream.Collectors;
public class AppliancesCommand extends ApplianceCommand<Debug> {
public AppliancesCommand() {
super("appliances");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
ComponentBuilder<TextComponent, TextComponent.Builder> componentBuilder = Component.text()
.append(Component.text(Main.instance().getAppliances().size()))
.append(Component.text(" appliances loaded:"))
.appendNewline()
.append(Component.text(Main.instance().getAppliances().stream().map(appliance -> appliance.getClass().getSimpleName()).collect(Collectors.joining(", "))));
sender.sendMessage(componentBuilder.build());
}
}

View File

@ -1,82 +0,0 @@
package eu.mhsl.craftattack.spawn.appliances.debug.command;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.debug.Debug;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Stream;
public class UserInfoCommand extends ApplianceCommand<Debug> {
public UserInfoCommand() {
super("userInfo");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(args.length != 1) {
sender.sendMessage(Component.text("Bitte gib einen Nutzernamen an.", NamedTextColor.RED));
return;
}
OfflinePlayer player = Bukkit.getOfflinePlayer(args[0]);
sender.sendMessage(
Component.text()
.appendNewline()
.append(Component.text("Informationen zu: ", NamedTextColor.GOLD))
.append(
Component
.text(Objects.requireNonNull(player.getName()), NamedTextColor.YELLOW)
.clickEvent(ClickEvent.copyToClipboard(Objects.requireNonNull(player.getName())))
)
.appendNewline()
.append(
Component
.text("UUID: " + player.getUniqueId(), NamedTextColor.GRAY)
.clickEvent(ClickEvent.copyToClipboard(player.getUniqueId().toString()))
)
.appendNewline()
.append(
Component
.text("Erster Besuch: " + formatUnixTimestamp(player.getFirstPlayed()), NamedTextColor.GRAY)
.clickEvent(ClickEvent.copyToClipboard(String.valueOf(player.getFirstPlayed())))
)
.appendNewline()
.append(
Component
.text("Letzter Besuch: " + formatUnixTimestamp(player.getLastSeen()), NamedTextColor.GRAY)
.clickEvent(ClickEvent.copyToClipboard(String.valueOf(player.getLastSeen())))
)
.appendNewline()
);
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(args.length < 2) {
return Stream.concat(
Bukkit.getOnlinePlayers().stream().map(Player::getName),
Arrays.stream(Bukkit.getOfflinePlayers()).map(OfflinePlayer::getName)
).toList();
}
return new ArrayList<>();
}
private String formatUnixTimestamp(long timestamp) {
DateFormat format = new SimpleDateFormat("E dd.MM.yyyy H:m:s");
return format.format(new Date(timestamp));
}
}

View File

@ -1,237 +0,0 @@
package eu.mhsl.craftattack.spawn.appliances.event;
import com.google.gson.Gson;
import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.api.HttpServer;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.event.command.*;
import eu.mhsl.craftattack.spawn.appliances.event.listener.ApplyPendingRewardsListener;
import eu.mhsl.craftattack.spawn.util.IteratorUtil;
import eu.mhsl.craftattack.spawn.util.entity.DisplayVillager;
import eu.mhsl.craftattack.spawn.util.server.PluginMessage;
import eu.mhsl.craftattack.spawn.util.listener.DismissInventoryOpenFromHolder;
import eu.mhsl.craftattack.spawn.util.listener.PlayerInteractAtEntityEventListener;
import eu.mhsl.craftattack.spawn.util.text.ComponentUtil;
import eu.mhsl.craftattack.spawn.util.text.Countdown;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.entity.Villager;
import org.bukkit.event.Listener;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.eclipse.jetty.util.security.CertificateUtils;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.*;
public class Event extends Appliance {
enum AdvertisementStatus {
BEFORE,
ADVERTISED,
DONE
}
Countdown advertiseCountdown = new Countdown(
120,
announcementData -> Component.text()
.append(ComponentUtil.createRainbowText("Event", 30))
.append(Component.text(" Start in ", NamedTextColor.GOLD))
.append(Component.text(announcementData.count(), NamedTextColor.AQUA))
.append(Component.text(" " + announcementData.unit() + "!", NamedTextColor.GOLD))
.build(),
component -> IteratorUtil.onlinePlayers(player -> player.sendMessage(component)),
() -> this.advertiseStatus = AdvertisementStatus.DONE
);
public DisplayVillager.ConfigBound villager;
private boolean isOpen = false;
private AdvertisementStatus advertiseStatus = AdvertisementStatus.BEFORE;
private UUID roomId;
private final HttpClient eventServerClient = HttpClient.newHttpClient();
private final List<Reward> pendingRewards = new ArrayList<>();
record RewardConfiguration(String memorialMaterial, String memorialTitle, String memorialLore, List<UUID> memorials, String material, Map<UUID, Integer> rewards) {}
record Reward(UUID playerUuid, ItemStack itemStack) {}
public Event() {
super("event");
}
@Override
public void onEnable() {
this.villager = new DisplayVillager.ConfigBound(
localConfig(),
villager -> {
villager.customName(Component.text("Events", NamedTextColor.GOLD));
villager.setProfession(Villager.Profession.LIBRARIAN);
villager.setVillagerType(Villager.Type.SNOW);
}
);
this.isOpen = localConfig().getBoolean("enabled", false);
if(this.isOpen) this.roomId = UUID.fromString(localConfig().getString("roomId", ""));
}
public void openEvent() throws URISyntaxException, IOException, InterruptedException {
if(isOpen) throw new ApplianceCommand.Error("Es läuft derzeit bereits ein Event!");
HttpRequest createRoomRequest = HttpRequest.newBuilder()
.uri(new URI(localConfig().getString("api") + "/room"))
.POST(HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> rawResponse = eventServerClient.send(createRoomRequest, HttpResponse.BodyHandlers.ofString());
if(rawResponse.statusCode() != 200) throw new ApplianceCommand.Error("Event-Server meldet Fehler: " + rawResponse.statusCode());
record Response(UUID uuid) {}
Response response = new Gson().fromJson(rawResponse.body(), Response.class);
isOpen = true;
roomId = response.uuid;
}
public void joinEvent(Player p) {
if(!isOpen) {
p.sendMessage(Component.text("Zurzeit ist kein Event geöffnet.", NamedTextColor.RED));
return;
}
if(!p.hasPermission("admin") && advertiseStatus == AdvertisementStatus.BEFORE) {
p.sendMessage(Component.text("Die Event befinden sich noch in der Vorbereitung.", NamedTextColor.RED));
return;
}
if(!p.hasPermission("admin") && advertiseStatus == AdvertisementStatus.DONE) {
p.sendMessage(Component.text("Die Events laufen bereits. Ein nachträgliches Beitreten ist nicht möglich.", NamedTextColor.RED));
return;
}
try {
Main.instance().getLogger().info("Verbinde mit eventserver: " + p.getName());
p.sendMessage(Component.text("Authentifiziere...", NamedTextColor.GREEN));
record Request(UUID player, UUID room) {}
Request request = new Request(p.getUniqueId(), this.roomId);
HttpRequest queueRoomRequest = HttpRequest.newBuilder()
.uri(new URI(localConfig().getString("api") + "/queueRoom"))
.POST(HttpRequest.BodyPublishers.ofString(new Gson().toJson(request)))
.build();
record Response(String error) {}
HttpResponse<String> rawResponse = eventServerClient.send(queueRoomRequest, HttpResponse.BodyHandlers.ofString());
Main.instance().getLogger().info("Response: " + rawResponse.body());
Response response = new Gson().fromJson(rawResponse.body(), Response.class);
if(rawResponse.statusCode() != 200 || response.error != null) {
p.sendMessage(Component.text("Fehler beim Betreten: " + response.error, NamedTextColor.RED));
return;
}
p.sendMessage(Component.text("Betrete...", NamedTextColor.GREEN));
PluginMessage.connect(p, localConfig().getString("connect-server-name"));
} catch (URISyntaxException | IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
public void endEvent() {
if(!isOpen) throw new ApplianceCommand.Error("Es läuft derzeit kein Event!");
isOpen = false;
}
private void rewardPlayers(RewardConfiguration rewardConfiguration) {
rewardConfiguration.rewards.forEach((uuid, amount) -> {
Reward reward = new Reward(uuid, new ItemStack(Objects.requireNonNull(Material.matchMaterial(rewardConfiguration.material)), amount));
if(Bukkit.getPlayer(uuid) == null) {
pendingRewards.add(reward);
return;
}
giveReward(reward);
});
rewardConfiguration.memorials.forEach(uuid -> {
ItemStack memorialItem = new ItemStack(Objects.requireNonNull(Material.matchMaterial(rewardConfiguration.memorialMaterial)));
ItemMeta meta = memorialItem.getItemMeta();
meta.displayName(Component.text(rewardConfiguration.memorialTitle, NamedTextColor.GOLD));
meta.lore(List.of(Component.text(rewardConfiguration.memorialLore, NamedTextColor.AQUA)));
memorialItem.setItemMeta(meta);
Reward memorial = new Reward(uuid, memorialItem);
if (Bukkit.getPlayer(uuid) == null) {
pendingRewards.add(memorial);
return;
}
giveReward(memorial);
});
}
private void giveReward(Reward reward) {
Player player = Bukkit.getPlayer(reward.playerUuid);
if(player == null) throw new RuntimeException("Cannot reward offline playerUuid!");
Map<Integer, ItemStack> remaining = player.getInventory().addItem(reward.itemStack);
Bukkit.getScheduler().runTask(
Main.instance(),
() -> remaining.values().forEach(remainingStack -> player.getWorld().dropItem(player.getLocation(), remainingStack))
);
}
public void applyPendingRewards(Player player) {
if(this.pendingRewards.isEmpty()) return;
List<Reward> givenRewards = this.pendingRewards.stream().filter(reward -> reward.playerUuid.equals(player.getUniqueId())).toList();
this.pendingRewards.removeAll(givenRewards);
givenRewards.forEach(this::giveReward);
}
public void advertise() {
advertiseCountdown.cancelIfRunning();
this.advertiseStatus = AdvertisementStatus.ADVERTISED;
IteratorUtil.onlinePlayers(player -> {
player.sendMessage(
Component.text()
.append(Component.text("-".repeat(10), NamedTextColor.GRAY)).appendNewline()
.append(Component.text("Ein Event wurde gestartet!", NamedTextColor.GOLD)).appendNewline()
.append(Component.text("Nutze "))
.append(Component.text("/event", NamedTextColor.AQUA))
.append(Component.text(", um dem Event beizutreten!")).appendNewline()
.append(Component.text("-".repeat(10), NamedTextColor.GRAY)).appendNewline()
);
});
advertiseCountdown.start();
}
@Override
public void httpApi(HttpServer.ApiBuilder apiBuilder) {
apiBuilder.post("reward", RewardConfiguration.class, (rewardConfiguration, request) -> {
this.rewardPlayers(rewardConfiguration);
return HttpServer.nothing;
});
}
@Override
protected @NotNull List<ApplianceCommand<?>> commands() {
return List.of(
new EventCommand(),
new MoveEventVillagerCommand(),
new EventOpenSessionCommand(),
new EventEndSessionCommand(),
new EventAdvertiseCommand()
);
}
@Override
protected @NotNull List<Listener> eventHandlers() {
return List.of(
new ApplyPendingRewardsListener(),
new PlayerInteractAtEntityEventListener(this.villager.getUniqueId(), playerInteractAtEntityEvent -> joinEvent(playerInteractAtEntityEvent.getPlayer())),
new DismissInventoryOpenFromHolder(this.villager.getUniqueId())
);
}
}

View File

@ -1,13 +0,0 @@
package eu.mhsl.craftattack.spawn.appliances.event.listener;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.appliances.event.Event;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
public class ApplyPendingRewardsListener extends ApplianceListener<Event> {
@EventHandler
public void onJoin(PlayerJoinEvent event) {
getAppliance().applyPendingRewards(event.getPlayer());
}
}

View File

@ -0,0 +1,47 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.antiSignEdit;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.datatypes.SelectSetting;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.block.sign.SignSide;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class AntiSignEdit extends Appliance {
@Override
public void onEnable() {
Settings.instance().declareSetting(SignEditSetting.class);
}
public boolean preventSignEdit(Player p, SignSide sign) {
SelectSetting.Options.Option setting = Settings.instance().getSetting(p, Settings.Key.SignEdit, SelectSetting.Options.Option.class);
if(setting.is(SignEditSetting.editable)) return false;
if(setting.is(SignEditSetting.readOnly)) {
p.sendActionBar(Component.text("Das Bearbeiten von Schildern ist in deinen Einstellungen deaktiviert.", NamedTextColor.RED));
return true;
}
if(setting.is(SignEditSetting.editableWhenEmpty)) {
boolean hasText = sign.lines().stream()
.anyMatch(line -> !PlainTextComponentSerializer.plainText().serialize(line).isBlank());
if(hasText) {
p.sendActionBar(Component.text("Das Bearbeiten von Schildern, welch bereits beschrieben sind, ist bei dir deaktiviert.", NamedTextColor.RED));
return true;
}
}
return false;
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(new OnSignEditListener());
}
}

View File

@ -0,0 +1,15 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.antiSignEdit;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import io.papermc.paper.event.player.PlayerOpenSignEvent;
import org.bukkit.block.sign.SignSide;
import org.bukkit.event.EventHandler;
class OnSignEditListener extends ApplianceListener<AntiSignEdit> {
@EventHandler
public void onEdit(PlayerOpenSignEvent event) {
if(event.getCause().equals(PlayerOpenSignEvent.Cause.PLACE)) return;
SignSide signSide = event.getSign().getSide(event.getSide());
event.setCancelled(this.getAppliance().preventSignEdit(event.getPlayer(), signSide));
}
}

View File

@ -0,0 +1,50 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.antiSignEdit;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.CategorizedSetting;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.SettingCategory;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.datatypes.SelectSetting;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import java.util.List;
import java.util.Locale;
public class SignEditSetting extends SelectSetting implements CategorizedSetting {
private static final String namespace = SignEditSetting.class.getSimpleName().toLowerCase(Locale.ROOT);
public static Options.Option editable = new Options.Option("Bearbeitbar", new NamespacedKey(namespace, "editable"));
public static Options.Option editableWhenEmpty = new Options.Option("Bearbeitbar wenn leer", new NamespacedKey(namespace, "emptyeditable"));
public static Options.Option readOnly = new Options.Option("Nicht bearbeitbar", new NamespacedKey(namespace, "readonly"));
public SignEditSetting() {
super(
Settings.Key.SignEdit,
new Options(List.of(editable, editableWhenEmpty, readOnly))
);
}
@Override
protected String title() {
return "Bearbeiten von Schildern";
}
@Override
protected String description() {
return "Bei einem Klick auf ein Schild, kann dieses Bearbeitet werden";
}
@Override
protected Material icon() {
return Material.OAK_SIGN;
}
@Override
protected Options.Option defaultValue() {
return editableWhenEmpty;
}
@Override
public SettingCategory category() {
return SettingCategory.Gameplay;
}
}

View File

@ -0,0 +1,53 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.autoShulker;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.Settings;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Material;
import org.bukkit.block.ShulkerBox;
import org.bukkit.entity.Item;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.BlockStateMeta;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.List;
public class AutoShulker extends Appliance {
@Override
public void onEnable() {
Settings.instance().declareSetting(AutoShulkerSetting.class);
}
public boolean tryAutoShulker(Player p, Item item) {
ItemStack itemStack = item.getItemStack();
ItemStack offhandStack = p.getInventory().getItemInOffHand();
if(itemStack.getType().equals(Material.SHULKER_BOX)) return false;
if(!offhandStack.getType().equals(Material.SHULKER_BOX)) return false;
BlockStateMeta blockStateMeta = (BlockStateMeta) offhandStack.getItemMeta();
ShulkerBox shulkerBox = (ShulkerBox) blockStateMeta.getBlockState();
HashMap<Integer, ItemStack> leftOver = shulkerBox.getInventory().addItem(itemStack);
if(leftOver.size() > 1)
throw new IllegalStateException("Multiple ItemStacks cannot be processed by AutoShulker!");
if(itemStack.equals(leftOver.get(0))) {
p.sendActionBar(Component.text("Die Shulkerbox ist voll!", NamedTextColor.RED));
return false;
}
if(leftOver.isEmpty()) {
item.remove();
}
blockStateMeta.setBlockState(shulkerBox);
offhandStack.setItemMeta(blockStateMeta);
return true;
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(new ItemPickupListener());
}
}

View File

@ -0,0 +1,50 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.autoShulker;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.CategorizedSetting;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.SettingCategory;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.datatypes.SelectSetting;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import java.util.List;
import java.util.Locale;
public class AutoShulkerSetting extends SelectSetting implements CategorizedSetting {
private static final String namespace = AutoShulkerSetting.class.getSimpleName().toLowerCase(Locale.ROOT);
public static Options.Option disabled = new Options.Option("Deaktiviert", new NamespacedKey(namespace, "disabled"));
public static Options.Option notFromPlayers = new Options.Option("Keine manuell gedroppten Items", new NamespacedKey(namespace, "noplayerdrops"));
public static Options.Option enabled = new Options.Option("Alle", new NamespacedKey(namespace, "enabled"));
public AutoShulkerSetting() {
super(
Settings.Key.AutoShulker,
new Options(List.of(disabled, notFromPlayers, enabled))
);
}
@Override
protected String title() {
return "Shulker in offhand automatisch befüllen";
}
@Override
protected String description() {
return "Wenn eine Shulker in der zweiten Hand gehalten wird, werden alle aufgesammelten Items in dieser abgelegt";
}
@Override
protected Material icon() {
return Material.SHULKER_BOX;
}
@Override
protected Options.Option defaultValue() {
return disabled;
}
@Override
public SettingCategory category() {
return SettingCategory.Gameplay;
}
}

View File

@ -0,0 +1,20 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.autoShulker;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.datatypes.SelectSetting;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.EntityPickupItemEvent;
class ItemPickupListener extends ApplianceListener<AutoShulker> {
@EventHandler
public void onPickup(EntityPickupItemEvent event) {
if(event.getEntity() instanceof Player p) {
SelectSetting.Options.Option setting = Settings.instance().getSetting(p, Settings.Key.AutoShulker, SelectSetting.Options.Option.class);
if(setting.is(AutoShulkerSetting.disabled)) return;
if(setting.is(AutoShulkerSetting.notFromPlayers) && event.getItem().getThrower() != null) return;
event.setCancelled(this.getAppliance().tryAutoShulker(p, event.getItem()));
}
}
}

View File

@ -0,0 +1,11 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.customAdvancements;
public class Advancements {
public static String searchTrouble = "search_trouble";
public static String fleischerchest = "fleischerchest";
public static String craftPixelblock = "craft_pixelblock";
public static String usePixelblock = "use_pixelblock";
public static String start = "start";
public static String winner = "winner";
public static String participateEvent = "participate_event";
}

View File

@ -0,0 +1,12 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.customAdvancements;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
class ApplyPendingAdvancementsListener extends ApplianceListener<CustomAdvancements> {
@EventHandler
public void onJoin(PlayerJoinEvent event) {
this.getAppliance().applyPendingAdvancements(event.getPlayer());
}
}

View File

@ -0,0 +1,66 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.customAdvancements;
import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.advancement.Advancement;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
public class CustomAdvancements extends Appliance {
record PendingAdvancement(UUID receiver, String advancement) {
}
private final List<PendingAdvancement> pendingAdvancements = new ArrayList<>();
public void grantAdvancement(String advancementName, UUID playerUUID) {
Player player = Bukkit.getPlayer(playerUUID);
if(player == null) {
this.addPendingAdvancement(playerUUID, advancementName);
return;
}
try {
NamespacedKey namespacedKey = Objects.requireNonNull(
NamespacedKey.fromString("craftattack_advancements:craftattack/" + advancementName),
String.format("NamespacedKey with '%s' is invalid!", advancementName)
);
Advancement advancement = Objects.requireNonNull(
Bukkit.getAdvancement(namespacedKey),
String.format("The advancement '%s' does not exist!", namespacedKey.asString())
);
player.getAdvancementProgress(advancement).awardCriteria("criteria");
} catch(Exception e) {
Main.logger().info("Advancement " + advancementName + " not found! (is Custom Advancements data pack loaded?)");
throw e;
}
}
public void applyPendingAdvancements(Player player) {
if(this.pendingAdvancements.isEmpty()) return;
List<PendingAdvancement> grantedAdvancements = this.pendingAdvancements.stream()
.filter(pendingAdvancement -> pendingAdvancement.receiver.equals(player.getUniqueId())).toList();
this.pendingAdvancements.removeAll(grantedAdvancements);
grantedAdvancements.forEach(pendingAdvancement -> this.grantAdvancement(pendingAdvancement.advancement(), player.getUniqueId()));
}
private void addPendingAdvancement(UUID receiver, String advancement) {
this.pendingAdvancements.add(new PendingAdvancement(receiver, advancement));
}
@Override
@NotNull
protected List<Listener> listeners() {
return List.of(
new CustomAdvancementsListener(),
new ApplyPendingAdvancementsListener()
);
}
}

View File

@ -0,0 +1,46 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.customAdvancements;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import net.kyori.adventure.text.Component;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
import org.bukkit.event.inventory.CraftItemEvent;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.inventory.ItemStack;
class CustomAdvancementsListener extends ApplianceListener<CustomAdvancements> {
@EventHandler
public void onEntityDamageEntity(EntityDamageByEntityEvent event) {
if(!(event.getEntity() instanceof Player damaged)) return;
if(!(event.getDamager() instanceof Player damager)) return;
if(!damager.getInventory().getItemInMainHand().getType().equals(Material.AIR)) return;
if(!damaged.hasPermission("admin")) return;
this.getAppliance().grantAdvancement(Advancements.searchTrouble, damager.getUniqueId());
}
@EventHandler
public void onCraftItem(CraftItemEvent event) {
ItemStack result = event.getInventory().getResult();
if(result == null) return;
if(!(event.getView().getPlayer() instanceof Player player)) return;
if(result.getType() == Material.RED_SHULKER_BOX) {
this.getAppliance().grantAdvancement(Advancements.fleischerchest, player.getUniqueId());
return;
}
if(result.getItemMeta().itemName().equals(Component.text("98fdf0ae-c3ab-4ef7-ae25-efd518d600de"))) {
this.getAppliance().grantAdvancement(Advancements.craftPixelblock, player.getUniqueId());
}
}
@EventHandler
public void onChangeWorld(PlayerChangedWorldEvent event) {
if(!event.getPlayer().getWorld().getName().startsWith("plugins/PixelBlocks/worlds")) return;
this.getAppliance().grantAdvancement(Advancements.usePixelblock, event.getPlayer().getUniqueId());
}
}

View File

@ -0,0 +1,51 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.doubleDoor;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.Settings;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Door;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class DoubleDoor extends Appliance {
@Override
public void onEnable() {
Settings.instance().declareSetting(DoubleDoorSetting.class);
}
public void openNextDoor(Block block) {
BlockData clickedData = block.getBlockData();
if(!(clickedData instanceof Door clickedDoor)) return;
BlockFace neighborFace = this.getNeighborFace(clickedDoor.getFacing(), clickedDoor.getHinge());
Block neighbourBlock = block.getRelative(neighborFace);
BlockData neighbourData = neighbourBlock.getBlockData();
if(!(neighbourData instanceof Door neighbourDoor)) return;
if(!(neighbourDoor.getFacing() == clickedDoor.getFacing())) return;
if(neighbourDoor.getHinge() == clickedDoor.getHinge()) return;
neighbourDoor.setOpen(!clickedDoor.isOpen());
neighbourBlock.setBlockData(neighbourDoor);
}
private @NotNull BlockFace getNeighborFace(BlockFace face, Door.Hinge hinge) {
return switch(face) {
case EAST -> (hinge == Door.Hinge.RIGHT) ? BlockFace.NORTH : BlockFace.SOUTH;
case WEST -> (hinge == Door.Hinge.RIGHT) ? BlockFace.SOUTH : BlockFace.NORTH;
case SOUTH -> (hinge == Door.Hinge.RIGHT) ? BlockFace.EAST : BlockFace.WEST;
case NORTH -> (hinge == Door.Hinge.RIGHT) ? BlockFace.WEST : BlockFace.EAST;
default ->
throw new IllegalStateException(String.format("BlockFace '%s' of clicked door is not valid!", face));
};
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(new OnDoorInteractListener());
}
}

View File

@ -0,0 +1,38 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.doubleDoor;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.CategorizedSetting;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.SettingCategory;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.datatypes.BoolSetting;
import org.bukkit.Material;
public class DoubleDoorSetting extends BoolSetting implements CategorizedSetting {
public DoubleDoorSetting() {
super(Settings.Key.DoubleDoors);
}
@Override
protected String title() {
return "Automatische Doppeltüren";
}
@Override
protected String description() {
return "Öffnet und schließt die zweite Hälfte einer Doppeltür automatisch";
}
@Override
protected Material icon() {
return Material.OAK_DOOR;
}
@Override
protected Boolean defaultValue() {
return false;
}
@Override
public SettingCategory category() {
return SettingCategory.Gameplay;
}
}

View File

@ -0,0 +1,27 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.doubleDoor;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.Settings;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.EquipmentSlot;
import java.util.Objects;
class OnDoorInteractListener extends ApplianceListener<DoubleDoor> {
@EventHandler
public void onPlayerInteract(PlayerInteractEvent event) {
if(!event.hasBlock()) return;
if(!Objects.equals(event.getHand(), EquipmentSlot.HAND)) return;
if(!event.getAction().equals(Action.RIGHT_CLICK_BLOCK)) return;
if(event.getPlayer().isSneaking()) return;
Block clickedBlock = event.getClickedBlock();
if(clickedBlock == null) return;
if(clickedBlock.getType().equals(Material.IRON_DOOR)) return;
if(!Settings.instance().getSetting(event.getPlayer(), Settings.Key.DoubleDoors, Boolean.class)) return;
this.getAppliance().openNextDoor(clickedBlock);
}
}

View File

@ -1,4 +1,4 @@
package eu.mhsl.craftattack.spawn.appliances.fleischerchest; package eu.mhsl.craftattack.spawn.appliances.gameplay.fleischerchest;
import eu.mhsl.craftattack.spawn.appliance.Appliance; import eu.mhsl.craftattack.spawn.appliance.Appliance;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
@ -18,7 +18,8 @@ public class Fleischerchest extends Appliance {
} }
@Override @Override
protected @NotNull List<Listener> eventHandlers() { @NotNull
protected List<Listener> listeners() {
return List.of(new FleischerchestCraftItemListener()); return List.of(new FleischerchestCraftItemListener());
} }
} }

View File

@ -1,4 +1,4 @@
package eu.mhsl.craftattack.spawn.appliances.fleischerchest; package eu.mhsl.craftattack.spawn.appliances.gameplay.fleischerchest;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener; import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import org.bukkit.Material; import org.bukkit.Material;
@ -6,13 +6,13 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.inventory.PrepareItemCraftEvent; import org.bukkit.event.inventory.PrepareItemCraftEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
public class FleischerchestCraftItemListener extends ApplianceListener<Fleischerchest> { class FleischerchestCraftItemListener extends ApplianceListener<Fleischerchest> {
@EventHandler @EventHandler
public void onPrepareItemCraft(PrepareItemCraftEvent event) { public void onPrepareItemCraft(PrepareItemCraftEvent event) {
ItemStack result = event.getInventory().getResult(); ItemStack result = event.getInventory().getResult();
if(result == null) return; if(result == null) return;
if(result.getType() == Material.RED_SHULKER_BOX) { if(result.getType() != Material.RED_SHULKER_BOX) return;
getAppliance().renameItem(event.getInventory().getResult());
} this.getAppliance().renameItem(event.getInventory().getResult());
} }
} }

View File

@ -0,0 +1,35 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.glowingBerries;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.util.Ticks;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class GlowingBerries extends Appliance {
private static final PotionEffect glowEffect = new PotionEffect(
PotionEffectType.GLOWING,
Ticks.TICKS_PER_SECOND * 15,
1,
false,
true,
true
);
public void letPlayerGlow(Player player) {
player.addPotionEffect(glowEffect);
Sound sound = Sound.sound(org.bukkit.Sound.BLOCK_AMETHYST_BLOCK_CHIME.key(), Sound.Source.PLAYER, 1f, 1f);
player.stopSound(sound);
player.playSound(sound, Sound.Emitter.self());
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(new OnBerryEaten());
}
}

View File

@ -0,0 +1,14 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.glowingBerries;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import org.bukkit.Material;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerItemConsumeEvent;
class OnBerryEaten extends ApplianceListener<GlowingBerries> {
@EventHandler
public void onEat(PlayerItemConsumeEvent event) {
if(event.getItem().getType().equals(Material.GLOW_BERRIES))
this.getAppliance().letPlayerGlow(event.getPlayer());
}
}

View File

@ -0,0 +1,57 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.hotbarRefill;
import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.Settings;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.NoSuchElementException;
public class HotbarRefill extends Appliance {
@Override
public void onEnable() {
Settings.instance().declareSetting(HotbarRefillSetting.class);
}
public void handleHotbarChange(Player player, ItemStack item) {
if(player.getGameMode().equals(GameMode.CREATIVE)) return;
if(item.getAmount() != 1) return;
Inventory inventory = player.getInventory();
int itemSlot = inventory.first(item);
if(itemSlot > 8) return;
try {
int replacementSlot = inventory.all(item.getType()).entrySet().stream()
.filter(entry -> entry.getKey() > 8)
.findFirst()
.orElseThrow()
.getKey();
Bukkit.getScheduler().scheduleSyncDelayedTask(Main.instance(), () -> {
ItemStack firstItem = inventory.getItem(itemSlot);
ItemStack secondItem = inventory.getItem(replacementSlot);
inventory.setItem(itemSlot, secondItem);
inventory.setItem(replacementSlot, firstItem);
player.sendActionBar(Component.text("Die Hotbar wurde aufgefüllt", NamedTextColor.GREEN));
}, 1);
} catch(NoSuchElementException ignored) {
}
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(new HotbarRefillListener());
}
}

View File

@ -0,0 +1,49 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.hotbarRefill;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.Settings;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.player.PlayerItemBreakEvent;
import org.bukkit.event.player.PlayerItemConsumeEvent;
import org.bukkit.inventory.ItemStack;
import java.util.List;
class HotbarRefillListener extends ApplianceListener<HotbarRefill> {
private HotbarRefillSetting.HotbarReplaceConfig getPlayerSetting(Player player) {
return Settings.instance().getSetting(
player,
Settings.Key.HotbarReplacer,
HotbarRefillSetting.HotbarReplaceConfig.class
);
}
@EventHandler
public void blockPlace(BlockPlaceEvent event) {
ItemStack stackInHand = event.getItemInHand();
if(stackInHand.getAmount() != 1) return;
if(stackInHand.getType().getMaxDurability() > 0) return;
if(stackInHand.getType().getMaxStackSize() > 0) return;
if(!this.getPlayerSetting(event.getPlayer()).onBlocks()) return;
this.getAppliance().handleHotbarChange(event.getPlayer(), stackInHand);
}
@EventHandler
public void onPlayerItemBreak(PlayerItemBreakEvent event) {
if(!this.getPlayerSetting(event.getPlayer()).onTools()) return;
this.getAppliance().handleHotbarChange(event.getPlayer(), event.getBrokenItem());
}
@EventHandler
public void onPlayerItemConsume(PlayerItemConsumeEvent event) {
if(List.of(Material.POTION, Material.HONEY_BOTTLE).contains(event.getItem().getType())) return;
if(!this.getPlayerSetting(event.getPlayer()).onConsumable()) return;
this.getAppliance().handleHotbarChange(event.getPlayer(), event.getItem());
}
}

View File

@ -0,0 +1,50 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.hotbarRefill;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.CategorizedSetting;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.SettingCategory;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.datatypes.MultiBoolSetting;
import org.bukkit.Material;
public class HotbarRefillSetting extends MultiBoolSetting<HotbarRefillSetting.HotbarReplaceConfig> implements CategorizedSetting {
@Override
public SettingCategory category() {
return SettingCategory.Gameplay;
}
public record HotbarReplaceConfig(
@DisplayName("Blöcke") boolean onBlocks,
@DisplayName("Werkzeuge") boolean onTools,
@DisplayName("Essen") boolean onConsumable
) {
}
public HotbarRefillSetting() {
super(Settings.Key.HotbarReplacer);
}
@Override
protected String title() {
return "Automatische Hotbar";
}
@Override
protected String description() {
return "Verschiebe Items automatisch von deinem Inventar in die Hotbar, wenn diese verbraucht werden";
}
@Override
protected Material icon() {
return Material.CHEST;
}
@Override
protected HotbarReplaceConfig defaultValue() {
return new HotbarReplaceConfig(false, false, false);
}
@Override
public Class<?> dataType() {
return HotbarReplaceConfig.class;
}
}

View File

@ -0,0 +1,65 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.knockDoor;
import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.datatypes.SelectSetting;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.scheduler.BukkitRunnable;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class KnockDoor extends Appliance {
@Override
public void onEnable() {
Settings.instance().declareSetting(KnockDoorSetting.class);
}
public void knockAtDoor(Player knockingPlayer, Block knockedBlock) {
SelectSetting.Options.Option setting = Settings.instance().getSetting(knockingPlayer, Settings.Key.KnockDoors, SelectSetting.Options.Option.class);
if(setting.is(KnockDoorSetting.disabled)) return;
if(setting.is(KnockDoorSetting.knockSingleTime)) {
this.playSound(knockedBlock);
}
if(setting.is(KnockDoorSetting.knockThreeTimes)) {
String metadataKey = new NamespacedKey(Main.instance(), KnockDoor.class.getName()).getNamespace();
if(knockingPlayer.hasMetadata(metadataKey)) return;
knockingPlayer.setMetadata(metadataKey, new FixedMetadataValue(Main.instance(), 0));
new BukkitRunnable() {
@Override
public void run() {
int timesKnocked = knockingPlayer.getMetadata(metadataKey).getFirst().asInt();
if(timesKnocked >= 3) {
knockingPlayer.removeMetadata(metadataKey, Main.instance());
this.cancel();
return;
}
KnockDoor.this.playSound(knockedBlock);
knockingPlayer.setMetadata(metadataKey, new FixedMetadataValue(Main.instance(), timesKnocked + 1));
}
}.runTaskTimer(Main.instance(), 0, 8);
}
}
private void playSound(Block knockedBlock) {
Location knockLocation = knockedBlock.getLocation();
Sound sound = knockedBlock.getType() == Material.IRON_DOOR
? Sound.ENTITY_ZOMBIE_ATTACK_IRON_DOOR
: Sound.ITEM_SHIELD_BLOCK;
knockLocation.getWorld().playSound(knockLocation, sound, SoundCategory.PLAYERS, 1f, 1f);
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(new KnockDoorListener());
}
}

View File

@ -0,0 +1,18 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.knockDoor;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import org.bukkit.GameMode;
import org.bukkit.block.Block;
import org.bukkit.block.data.type.Door;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.BlockDamageAbortEvent;
class KnockDoorListener extends ApplianceListener<KnockDoor> {
@EventHandler
public void onKnock(BlockDamageAbortEvent event) {
if(event.getPlayer().getGameMode() != GameMode.SURVIVAL) return;
Block block = event.getBlock();
if(!(block.getBlockData() instanceof Door)) return;
this.getAppliance().knockAtDoor(event.getPlayer(), block);
}
}

View File

@ -0,0 +1,50 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.knockDoor;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.CategorizedSetting;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.SettingCategory;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.datatypes.SelectSetting;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import java.util.List;
import java.util.Locale;
public class KnockDoorSetting extends SelectSetting implements CategorizedSetting {
private static final String namespace = KnockDoorSetting.class.getSimpleName().toLowerCase(Locale.ROOT);
public static Options.Option disabled = new Options.Option("Deaktiviert", new NamespacedKey(namespace, "disabled"));
public static Options.Option knockSingleTime = new Options.Option("Einmal an der Tür anklopfen", new NamespacedKey(namespace, "single"));
public static Options.Option knockThreeTimes = new Options.Option("Dreimal an der Tür anklopfen", new NamespacedKey(namespace, "three"));
public KnockDoorSetting() {
super(
Settings.Key.KnockDoors,
new Options(List.of(disabled, knockSingleTime, knockThreeTimes))
);
}
@Override
public SettingCategory category() {
return SettingCategory.Gameplay;
}
@Override
protected String title() {
return "Klopfen an Türen";
}
@Override
protected String description() {
return "Klopft durch das schlagen an eine Tür an.";
}
@Override
protected Material icon() {
return Material.BELL;
}
@Override
protected Options.Option defaultValue() {
return disabled;
}
}

View File

@ -0,0 +1,7 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.outlawed;
class OutlawChangeNotPermitted extends Exception {
public OutlawChangeNotPermitted(String message) {
super(message);
}
}

View File

@ -1,10 +1,10 @@
package eu.mhsl.craftattack.spawn.appliances.outlawed; package eu.mhsl.craftattack.spawn.appliances.gameplay.outlawed;
import eu.mhsl.craftattack.spawn.Main; import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.aggregates.displayName.DisplayName;
import eu.mhsl.craftattack.spawn.appliance.Appliance; import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.whitelist.Whitelist; import eu.mhsl.craftattack.spawn.appliances.metaGameplay.displayName.DisplayName;
import eu.mhsl.craftattack.spawn.appliances.tooling.whitelist.Whitelist;
import eu.mhsl.craftattack.spawn.config.Configuration; import eu.mhsl.craftattack.spawn.config.Configuration;
import eu.mhsl.craftattack.spawn.util.text.DisconnectInfo; import eu.mhsl.craftattack.spawn.util.text.DisconnectInfo;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
@ -17,9 +17,10 @@ import org.jetbrains.annotations.NotNull;
import java.util.*; import java.util.*;
public class Outlawed extends Appliance { public class Outlawed extends Appliance implements DisplayName.Prefixed {
public final int timeoutInMs = 1000*60*60*6; public final int timeoutInMs = 1000 * 60 * 60 * 6;
private final Map<UUID, Long> timeouts = new HashMap<>(); private final Map<UUID, Long> timeouts = new HashMap<>();
public enum Status { public enum Status {
DISABLED, DISABLED,
VOLUNTARILY, VOLUNTARILY,
@ -32,100 +33,102 @@ public class Outlawed extends Appliance {
public Outlawed() { public Outlawed() {
super("outlawed"); super("outlawed");
Bukkit.getScheduler().runTaskTimerAsynchronously( Bukkit.getScheduler().runTaskTimerAsynchronously(
Main.instance(), Main.instance(),
() -> { () -> this.playerStatusMap.forEach((player, status) -> {
playerStatusMap.forEach((player, status) -> { if(!player.isOnline()) return;
if(!player.isOnline()) return; if(status != Status.FORCED) return;
if(status != Status.FORCED) return; try {
try { this.queryAppliance(Whitelist.class).fullIntegrityCheck(player);
Main.instance().getAppliance(Whitelist.class).integrityCheck(player); } catch(DisconnectInfo.Throwable e) {
} catch (DisconnectInfo.Throwable e) { e.getDisconnectScreen().applyKick(player);
Bukkit.getScheduler().runTask(Main.instance(), () -> e.getDisconnectScreen().applyKick(player)); }
} }),
}); 20 * 60,
}, 20 * 60 * 5
20*60,
20*60*5
); );
} }
public void switchLawStatus(Player player) throws OutlawChangeNotPermitted { public void switchLawStatus(Player player) throws OutlawChangeNotPermitted {
if(getLawStatus(player).equals(Status.FORCED)) { if(this.getLawStatus(player).equals(Status.FORCED)) {
throw new OutlawChangeNotPermitted("Dein Vogelfreistatus wurde als Strafe auferlegt und kann daher nicht verändert werden."); throw new OutlawChangeNotPermitted("Dein Vogelfreistatus wurde als Strafe auferlegt und kann daher nicht verändert werden.");
} }
if(isTimeout(player)) { if(this.isTimeout(player)) {
throw new OutlawChangeNotPermitted("Du kannst deinen Vogelfreistatus nicht so schnell wechseln. Bitte warte einige Stunden bevor du umschaltest!"); throw new OutlawChangeNotPermitted("Du kannst deinen Vogelfreistatus nicht so schnell wechseln. Bitte warte einige Stunden bevor du umschaltest!");
} }
setLawStatus(player, isOutlawed(player) ? Status.DISABLED : Status.VOLUNTARILY); this.setLawStatus(player, this.isOutlawed(player) ? Status.DISABLED : Status.VOLUNTARILY);
setTimeout(player); this.setTimeout(player);
} }
public void updateForcedStatus(Player player, boolean forced) { public void updateForcedStatus(Player player, boolean forced) {
setLawStatus(player, forced ? Status.FORCED : getLawStatus(player) == Status.FORCED ? Status.DISABLED : getLawStatus(player)); this.setLawStatus(player, forced ? Status.FORCED : this.getLawStatus(player) == Status.FORCED ? Status.DISABLED : this.getLawStatus(player));
} }
public Status getLawStatus(Player player) { public Status getLawStatus(Player player) {
return playerStatusMap.computeIfAbsent(player, p -> { return this.playerStatusMap.computeIfAbsent(player, p -> {
if(localConfig().getStringList(voluntarilyEntry).contains(p.getUniqueId().toString())) return Status.VOLUNTARILY; if(this.localConfig().getStringList(this.voluntarilyEntry).contains(p.getUniqueId().toString()))
return Status.VOLUNTARILY;
return Status.DISABLED; return Status.DISABLED;
}); });
} }
private void setLawStatus(Player player, Status status) { private void setLawStatus(Player player, Status status) {
playerStatusMap.put(player, status); this.playerStatusMap.put(player, status);
Main.instance().getAppliance(DisplayName.class).update(player); this.queryAppliance(DisplayName.class).update(player);
List<String> newList = localConfig().getStringList(voluntarilyEntry); List<String> newList = this.localConfig().getStringList(this.voluntarilyEntry);
if(status.equals(Status.VOLUNTARILY)) { if(status.equals(Status.VOLUNTARILY)) {
newList.add(player.getUniqueId().toString()); newList.add(player.getUniqueId().toString());
} else { } else {
newList.remove(player.getUniqueId().toString()); newList.remove(player.getUniqueId().toString());
} }
localConfig().set(voluntarilyEntry, newList.stream().distinct().toList()); this.localConfig().set(this.voluntarilyEntry, newList.stream().distinct().toList());
Configuration.saveChanges(); Configuration.saveChanges();
} }
public boolean isOutlawed(Player player) { public boolean isOutlawed(Player player) {
return getLawStatus(player) != Status.DISABLED; return this.getLawStatus(player) != Status.DISABLED;
} }
private boolean isTimeout(Player player) { private boolean isTimeout(Player player) {
return timeouts.get(player.getUniqueId()) < System.currentTimeMillis() - timeoutInMs; return this.timeouts.getOrDefault(player.getUniqueId(), 0L) > System.currentTimeMillis() - this.timeoutInMs;
} }
private void setTimeout(Player player) { private void setTimeout(Player player) {
timeouts.put(player.getUniqueId(), System.currentTimeMillis()); this.timeouts.put(player.getUniqueId(), System.currentTimeMillis());
} }
public Component getStatusDescription(Status status) { public Component getStatusDescription(Status status) {
return switch (status) { return switch(status) {
case DISABLED -> Component.text("Vogelfreistatus inaktiv: ", NamedTextColor.GREEN) case DISABLED -> Component.text("Vogelfreistatus inaktiv: ", NamedTextColor.GREEN)
.append(Component.text("Es gelten die Standard Regeln!", NamedTextColor.GOLD)); .append(Component.text("Es gelten die Standard Regeln!", NamedTextColor.GOLD));
case VOLUNTARILY, FORCED -> Component.text("Vogelfreistatus aktiv: ", NamedTextColor.RED) case VOLUNTARILY, FORCED -> Component.text("Vogelfreistatus aktiv: ", NamedTextColor.RED)
.append(Component.text("Du darfst von jedem angegriffen und getötet werden!", NamedTextColor.GOLD)); .append(Component.text("Du darfst von jedem angegriffen und getötet werden!", NamedTextColor.GOLD));
}; };
} }
@Override
public Component getNamePrefix(Player player) { public Component getNamePrefix(Player player) {
if(isOutlawed(player)) { if(this.isOutlawed(player)) {
return Component.text("[☠]", NamedTextColor.RED) return Component.text("[☠]", NamedTextColor.RED)
.hoverEvent(HoverEvent.showText(Component.text("Vogelfreie Spieler dürfen ohne Grund angegriffen werden!"))); .hoverEvent(HoverEvent.showText(Component.text("Vogelfreie Spieler dürfen ohne Grund angegriffen werden!")));
} }
return null; return null;
} }
@Override @Override
protected @NotNull List<ApplianceCommand<?>> commands() { @NotNull
protected List<ApplianceCommand<?>> commands() {
return List.of(new OutlawedCommand()); return List.of(new OutlawedCommand());
} }
@Override @Override
protected @NotNull List<Listener> eventHandlers() { @NotNull
protected List<Listener> listeners() {
return List.of(new OutlawedReminderListener()); return List.of(new OutlawedReminderListener());
} }
} }

View File

@ -1,4 +1,4 @@
package eu.mhsl.craftattack.spawn.appliances.outlawed; package eu.mhsl.craftattack.spawn.appliances.gameplay.outlawed;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
@ -7,7 +7,7 @@ import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
public class OutlawedCommand extends ApplianceCommand.PlayerChecked<Outlawed> { class OutlawedCommand extends ApplianceCommand.PlayerChecked<Outlawed> {
public OutlawedCommand() { public OutlawedCommand() {
super("vogelfrei"); super("vogelfrei");
} }
@ -15,12 +15,12 @@ public class OutlawedCommand extends ApplianceCommand.PlayerChecked<Outlawed> {
@Override @Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception { protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
try { try {
getAppliance().switchLawStatus(getPlayer()); this.getAppliance().switchLawStatus(this.getPlayer());
sender.sendMessage( sender.sendMessage(
getAppliance() this.getAppliance()
.getStatusDescription(getAppliance().getLawStatus(getPlayer())) .getStatusDescription(this.getAppliance().getLawStatus(this.getPlayer()))
); );
} catch (OutlawChangeNotPermitted e) { } catch(OutlawChangeNotPermitted e) {
sender.sendMessage(Component.text(e.getMessage(), NamedTextColor.RED)); sender.sendMessage(Component.text(e.getMessage(), NamedTextColor.RED));
} }
} }

View File

@ -0,0 +1,14 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.outlawed;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
class OutlawedReminderListener extends ApplianceListener<Outlawed> {
@EventHandler
public void onJoin(PlayerJoinEvent e) {
if(this.getAppliance().isOutlawed(e.getPlayer())) {
e.getPlayer().sendMessage(this.getAppliance().getStatusDescription(this.getAppliance().getLawStatus(e.getPlayer())));
}
}
}

View File

@ -0,0 +1,16 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.portableCrafting;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import org.bukkit.Material;
import org.bukkit.event.EventHandler;
import org.bukkit.event.block.Action;
import org.bukkit.event.player.PlayerInteractEvent;
class OnCraftingTableUseListener extends ApplianceListener<PortableCrafting> {
@EventHandler
public void inInteract(PlayerInteractEvent event) {
if(!event.getAction().equals(Action.RIGHT_CLICK_AIR)) return;
if(!event.getMaterial().equals(Material.CRAFTING_TABLE)) return;
this.getAppliance().openFor(event.getPlayer());
}
}

View File

@ -0,0 +1,26 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.portableCrafting;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.Settings;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class PortableCrafting extends Appliance {
@Override
public void onEnable() {
Settings.instance().declareSetting(PortableCraftingSetting.class);
}
public void openFor(Player player) {
if(!Settings.instance().getSetting(player, Settings.Key.EnablePortableCrafting, Boolean.class)) return;
player.openWorkbench(null, true);
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(new OnCraftingTableUseListener());
}
}

View File

@ -0,0 +1,38 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.portableCrafting;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.CategorizedSetting;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.SettingCategory;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.datatypes.BoolSetting;
import org.bukkit.Material;
public class PortableCraftingSetting extends BoolSetting implements CategorizedSetting {
public PortableCraftingSetting() {
super(Settings.Key.EnablePortableCrafting);
}
@Override
protected String title() {
return "Portables Crafting";
}
@Override
protected String description() {
return "Erlaubt das öffnen einer Werkbank in der Hand, ohne sie plazieren zu müssen";
}
@Override
protected Material icon() {
return Material.CRAFTING_TABLE;
}
@Override
protected Boolean defaultValue() {
return true;
}
@Override
public SettingCategory category() {
return SettingCategory.Gameplay;
}
}

View File

@ -0,0 +1,22 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.snowballKnockback;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class SnowballKnockback extends Appliance {
public void dealSnowballKnockback(LivingEntity entity, Entity snowball) {
entity.damage(0.1);
entity.knockback(0.4, -snowball.getVelocity().getX(), -snowball.getVelocity().getZ());
}
@Override
@NotNull
protected List<Listener> listeners() {
return List.of(new SnowballKnockbackListener());
}
}

View File

@ -0,0 +1,20 @@
package eu.mhsl.craftattack.spawn.appliances.gameplay.snowballKnockback;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.EventHandler;
import org.bukkit.event.entity.ProjectileHitEvent;
class SnowballKnockbackListener extends ApplianceListener<SnowballKnockback> {
@EventHandler
public void onSnowballHit(ProjectileHitEvent event) {
if(event.getHitEntity() == null) return;
if(!event.getEntityType().equals(EntityType.SNOWBALL)) return;
if(!(event.getHitEntity() instanceof LivingEntity hitEntity)) return;
Entity snowball = event.getEntity();
this.getAppliance().dealSnowballKnockback(hitEntity, snowball);
}
}

View File

@ -1,27 +0,0 @@
package eu.mhsl.craftattack.spawn.appliances.help;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.help.command.DiscordCommand;
import eu.mhsl.craftattack.spawn.appliances.help.command.HelpCommand;
import eu.mhsl.craftattack.spawn.appliances.help.command.SpawnCommand;
import eu.mhsl.craftattack.spawn.appliances.help.command.TeamspeakCommand;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class Help extends Appliance {
public Help() {
super("help");
}
@Override
protected @NotNull List<ApplianceCommand<?>> commands() {
return List.of(
new HelpCommand(),
new SpawnCommand(),
new TeamspeakCommand(),
new DiscordCommand()
);
}
}

View File

@ -0,0 +1,20 @@
package eu.mhsl.craftattack.spawn.appliances.internal.debug;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.internal.debug.command.AppliancesCommand;
import eu.mhsl.craftattack.spawn.appliances.internal.debug.command.UserInfoCommand;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class Debug extends Appliance {
@Override
@NotNull
protected List<ApplianceCommand<?>> commands() {
return List.of(
new UserInfoCommand(),
new AppliancesCommand()
);
}
}

View File

@ -0,0 +1,65 @@
package eu.mhsl.craftattack.spawn.appliances.internal.debug.command;
import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.internal.debug.Debug;
import eu.mhsl.craftattack.spawn.util.text.ComponentUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentBuilder;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class AppliancesCommand extends ApplianceCommand<Debug> {
public AppliancesCommand() {
super("appliances");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
ComponentBuilder<TextComponent, TextComponent.Builder> componentBuilder = Component.text()
.append(Component.text(Main.instance().getAppliances().size()))
.append(Component.text(" appliances running:"))
.appendNewline();
Main.instance().getAppliances().forEach(appliance -> {
List<ApplianceCommand<?>> commands = appliance.getCommands();
List<Listener> listener = appliance.getListeners();
componentBuilder
.append(Component.text(appliance.getClass().getSimpleName(), NamedTextColor.GREEN)
.hoverEvent(HoverEvent.showText(Component.text(appliance.getClass().getName()))))
.append(Component.text(": ", NamedTextColor.DARK_GRAY))
.append(Component.text(commands.size() + " Commands", NamedTextColor.GRAY)
.hoverEvent(HoverEvent.showText(commands.stream()
.map(applianceCommand -> Component.text()
.append(Component.text(applianceCommand.commandName, NamedTextColor.DARK_GREEN))
.append(Component.text(": "))
.append(Component.text(applianceCommand.getClass().getName()))
.build())
.reduce(ComponentUtil::appendWithNewline)
.orElse(Component.text("No commands available")))))
.append(Component.text(", ", NamedTextColor.GRAY))
.append(Component.text(listener.size() + " Listener", NamedTextColor.GRAY)
.hoverEvent(HoverEvent.showText(listener.stream()
.map(eventHandler -> Component.text()
.append(Component.text(eventHandler.getClass().getSimpleName(), NamedTextColor.DARK_GREEN))
.append(Component.text(": "))
.append(Component.text(eventHandler.getClass().getName()))
.build())
.reduce(ComponentUtil::appendWithNewline)
.orElse(Component.text("No listeners available")))))
.appendNewline();
});
componentBuilder.append(Component.text(Main.instance().getClass().getName(), NamedTextColor.GRAY));
sender.sendMessage(componentBuilder.build());
}
}

View File

@ -0,0 +1,82 @@
package eu.mhsl.craftattack.spawn.appliances.internal.debug.command;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.internal.debug.Debug;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Stream;
public class UserInfoCommand extends ApplianceCommand<Debug> {
public UserInfoCommand() {
super("userInfo");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(args.length != 1) {
sender.sendMessage(Component.text("Bitte gib einen Nutzernamen an.", NamedTextColor.RED));
return;
}
OfflinePlayer player = Bukkit.getOfflinePlayer(args[0]);
sender.sendMessage(
Component.text()
.appendNewline()
.append(Component.text("Informationen zu: ", NamedTextColor.GOLD))
.append(
Component
.text(Objects.requireNonNull(player.getName()), NamedTextColor.YELLOW)
.clickEvent(ClickEvent.copyToClipboard(Objects.requireNonNull(player.getName())))
)
.appendNewline()
.append(
Component
.text("UUID: " + player.getUniqueId(), NamedTextColor.GRAY)
.clickEvent(ClickEvent.copyToClipboard(player.getUniqueId().toString()))
)
.appendNewline()
.append(
Component
.text("Erster Besuch: " + this.formatUnixTimestamp(player.getFirstPlayed()), NamedTextColor.GRAY)
.clickEvent(ClickEvent.copyToClipboard(String.valueOf(player.getFirstPlayed())))
)
.appendNewline()
.append(
Component
.text("Letzter Besuch: " + this.formatUnixTimestamp(player.getLastSeen()), NamedTextColor.GRAY)
.clickEvent(ClickEvent.copyToClipboard(String.valueOf(player.getLastSeen())))
)
.appendNewline()
);
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(args.length < 2) {
return Stream.concat(
Bukkit.getOnlinePlayers().stream().map(Player::getName),
Arrays.stream(Bukkit.getOfflinePlayers()).map(OfflinePlayer::getName)
).toList();
}
return new ArrayList<>();
}
private String formatUnixTimestamp(long timestamp) {
DateFormat format = new SimpleDateFormat("E dd.MM.yyyy H:m:s");
return format.format(new Date(timestamp));
}
}

View File

@ -1,4 +1,4 @@
package eu.mhsl.craftattack.spawn.appliances.titleClear; package eu.mhsl.craftattack.spawn.appliances.internal.titleClear;
import eu.mhsl.craftattack.spawn.appliance.Appliance; import eu.mhsl.craftattack.spawn.appliance.Appliance;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -11,8 +11,10 @@ public class TitleClear extends Appliance {
public void clearTitle(Player player) { public void clearTitle(Player player) {
player.clearTitle(); player.clearTitle();
} }
@Override @Override
protected @NotNull List<Listener> eventHandlers() { @NotNull
protected List<Listener> listeners() {
return List.of( return List.of(
new TitleClearListener() new TitleClearListener()
); );

View File

@ -1,12 +1,12 @@
package eu.mhsl.craftattack.spawn.appliances.titleClear; package eu.mhsl.craftattack.spawn.appliances.internal.titleClear;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener; import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerJoinEvent;
public class TitleClearListener extends ApplianceListener<TitleClear> { class TitleClearListener extends ApplianceListener<TitleClear> {
@EventHandler @EventHandler
public void onPlayerJoin(PlayerJoinEvent event) { public void onPlayerJoin(PlayerJoinEvent event) {
getAppliance().clearTitle(event.getPlayer()); this.getAppliance().clearTitle(event.getPlayer());
} }
} }

View File

@ -0,0 +1,14 @@
package eu.mhsl.craftattack.spawn.appliances.metaGameplay.adminMarker;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.Color;
import org.bukkit.entity.Player;
public class AdminMarker extends Appliance {
public TextColor getPlayerColor(Player player) {
if(player.hasPermission("chatcolor"))
return TextColor.color(Color.AQUA.asRGB()); // TODO read permission from config
return TextColor.color(Color.WHITE.asRGB());
}
}

View File

@ -0,0 +1,30 @@
package eu.mhsl.craftattack.spawn.appliances.metaGameplay.afkTag;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import io.papermc.paper.event.player.AsyncChatEvent;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerMoveEvent;
class AfkResetListener extends ApplianceListener<AfkTag> {
@EventHandler
public void onMove(PlayerMoveEvent event) {
this.getAppliance().resetTiming(event.getPlayer());
}
@EventHandler
public void onInteract(PlayerInteractEvent event) {
this.getAppliance().resetTiming(event.getPlayer());
}
@EventHandler
public void onChat(AsyncChatEvent event) {
this.getAppliance().resetTiming(event.getPlayer());
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
this.getAppliance().resetTiming(event.getPlayer());
}
}

View File

@ -0,0 +1,74 @@
package eu.mhsl.craftattack.spawn.appliances.metaGameplay.afkTag;
import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.displayName.DisplayName;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.util.Ticks;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
public class AfkTag extends Appliance implements DisplayName.Prefixed {
private final HashMap<UUID, Long> afkTimings = new HashMap<>();
private static final int updateIntervalSeconds = 30;
private static final int afkWhenMillis = 3 * 60 * 1000;
@Override
public void onEnable() {
Bukkit.getScheduler().runTaskTimerAsynchronously(
Main.instance(),
this::checkAfkPlayers,
Ticks.TICKS_PER_SECOND,
Ticks.TICKS_PER_SECOND * updateIntervalSeconds
);
}
public void resetTiming(Player player) {
boolean wasAfk = this.isAfk(player);
this.afkTimings.put(player.getUniqueId(), System.currentTimeMillis());
if(wasAfk) this.updateAfkPrefix(player);
}
private void checkAfkPlayers() {
this.afkTimings.keySet().stream()
.map(Bukkit::getPlayer)
.filter(Objects::nonNull)
.filter(this::isAfk)
.forEach(this::updateAfkPrefix);
}
private boolean isAfk(Player player) {
if(player.isSleeping()) return false;
long lastTimeActive = this.afkTimings.getOrDefault(player.getUniqueId(), 0L);
long timeSinceLastActive = System.currentTimeMillis() - lastTimeActive;
return timeSinceLastActive >= afkWhenMillis;
}
private void updateAfkPrefix(Player player) {
Main.instance().getAppliance(DisplayName.class).update(player);
}
@Override
public @Nullable Component getNamePrefix(Player player) {
if(this.isAfk(player)) return Component.text("[ᵃᶠᵏ]", NamedTextColor.GRAY)
.hoverEvent(HoverEvent.showText(Component.text("Der Spieler ist AFK")));
return null;
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(new AfkResetListener());
}
}

View File

@ -0,0 +1,66 @@
package eu.mhsl.craftattack.spawn.appliances.metaGameplay.chatMention;
import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.Settings;
import net.kyori.adventure.sound.Sound;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public class ChatMention extends Appliance {
private static List<String> playerNames;
public String formatPlayer(String name) {
return "@" + name;
}
public List<String> getPlayerNames() {
return playerNames;
}
public void refreshPlayers() {
Bukkit.getScheduler().runTaskAsynchronously(
Main.instance(),
() -> playerNames = Arrays.stream(Bukkit.getOfflinePlayers())
.map(OfflinePlayer::getName)
.filter(Objects::nonNull)
.toList()
);
}
public void notifyPlayers(List<String> playerNames) {
playerNames.stream()
.distinct()
.map(Bukkit::getPlayer)
.filter(Objects::nonNull)
.filter(player -> Settings.instance()
.getSetting(player, Settings.Key.ChatMentions, ChatMentionSetting.ChatMentionConfig.class)
.notifyOnMention()
)
.forEach(player -> player.playSound(
Sound.sound(
org.bukkit.Sound.ENTITY_EXPERIENCE_ORB_PICKUP,
Sound.Source.PLAYER,
1.0f,
1.0f
)
));
}
@Override
public void onEnable() {
Settings.instance().declareSetting(ChatMentionSetting.class);
this.refreshPlayers();
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(new ChatMentionListener());
}
}

View File

@ -0,0 +1,56 @@
package eu.mhsl.craftattack.spawn.appliances.metaGameplay.chatMention;
import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.chatMessages.ChatMessages;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.util.text.ComponentUtil;
import io.papermc.paper.event.player.AsyncChatDecorateEvent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
import java.util.ArrayList;
import java.util.List;
class ChatMentionListener extends ApplianceListener<ChatMention> {
@SuppressWarnings("UnstableApiUsage")
@EventHandler
public void coloringEvent(AsyncChatDecorateEvent event) {
String message = PlainTextComponentSerializer.plainText().serialize(event.result());
List<String> words = List.of(message.split(" "));
List<String> mentioned = new ArrayList<>();
ChatMentionSetting.ChatMentionConfig config = Settings.instance()
.getSetting(event.player(), Settings.Key.ChatMentions, ChatMentionSetting.ChatMentionConfig.class);
ChatMessages chatMessages = Main.instance().getAppliance(ChatMessages.class);
Component result = words.stream()
.map(word -> {
String wordWithoutAnnotation = word.replace("@", "");
boolean isPlayer = this.getAppliance().getPlayerNames().contains(wordWithoutAnnotation);
if(isPlayer && config.applyMentions()) {
mentioned.add(wordWithoutAnnotation);
Component mention = Component.text(
this.getAppliance().formatPlayer(wordWithoutAnnotation),
NamedTextColor.GOLD
);
return chatMessages.addReportActions(mention, wordWithoutAnnotation);
} else {
return Component.text(word);
}
})
.reduce(ComponentUtil::appendWithSpace)
.orElseThrow();
this.getAppliance().notifyPlayers(mentioned);
event.result(result);
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
this.getAppliance().refreshPlayers();
}
}

View File

@ -0,0 +1,49 @@
package eu.mhsl.craftattack.spawn.appliances.metaGameplay.chatMention;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.CategorizedSetting;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.SettingCategory;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.datatypes.MultiBoolSetting;
import org.bukkit.Material;
public class ChatMentionSetting extends MultiBoolSetting<ChatMentionSetting.ChatMentionConfig> implements CategorizedSetting {
@Override
public SettingCategory category() {
return SettingCategory.Visuals;
}
public record ChatMentionConfig(
@DisplayName("Spielernamen hervorheben") boolean applyMentions,
@DisplayName("Benachrichtigungston") boolean notifyOnMention
) {
}
public ChatMentionSetting() {
super(Settings.Key.ChatMentions);
}
@Override
protected String title() {
return "Erwähnungen im Chat";
}
@Override
protected String description() {
return "Erwähnungen werden automatisch im Chat angewandt und der Empfänger erhält einen Signalton";
}
@Override
protected Material icon() {
return Material.FEATHER;
}
@Override
protected ChatMentionConfig defaultValue() {
return new ChatMentionConfig(true, true);
}
@Override
public Class<?> dataType() {
return ChatMentionConfig.class;
}
}

View File

@ -0,0 +1,36 @@
package eu.mhsl.craftattack.spawn.appliances.metaGameplay.chatMessages;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.Settings;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class ChatMessages extends Appliance {
@Override
public void onEnable() {
Settings.instance().declareSetting(ShowJoinAndLeaveMessagesSetting.class);
}
public Component getReportablePlayerName(Player player) {
return this.addReportActions(player.displayName(), player.getName());
}
public Component addReportActions(Component message, String username) {
return message
.hoverEvent(HoverEvent.showText(Component.text("Klicke, um diesen Spieler zu reporten").color(NamedTextColor.GOLD)))
.clickEvent(ClickEvent.clickEvent(ClickEvent.Action.SUGGEST_COMMAND, String.format("/report %s ", username)));
}
@Override
@NotNull
protected List<Listener> listeners() {
return List.of(new ChatMessagesListener());
}
}

View File

@ -0,0 +1,70 @@
package eu.mhsl.craftattack.spawn.appliances.metaGameplay.chatMessages;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.util.IteratorUtil;
import io.papermc.paper.event.player.AsyncChatEvent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.Color;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import java.util.Optional;
class ChatMessagesListener extends ApplianceListener<ChatMessages> {
@EventHandler
public void onPlayerChatEvent(AsyncChatEvent event) {
event.renderer(
(source, sourceDisplayName, message, viewer) ->
Component.text("")
.append(this.getAppliance().getReportablePlayerName(source))
.append(Component.text(" > ").color(TextColor.color(Color.GRAY.asRGB())))
.append(message).color(TextColor.color(Color.SILVER.asRGB()))
);
}
@EventHandler(priority = EventPriority.HIGH)
public void onPlayerJoin(PlayerJoinEvent event) {
event.joinMessage(null);
IteratorUtil.onlinePlayers(player -> {
if(!Settings.instance().getSetting(player, Settings.Key.ShowJoinAndLeaveMessages, Boolean.class)) return;
player.sendMessage(
Component
.text(">>> ").color(NamedTextColor.GREEN)
.append(this.getAppliance().getReportablePlayerName(event.getPlayer()))
);
});
}
@EventHandler
public void onPlayerLeave(PlayerQuitEvent event) {
event.quitMessage(null);
IteratorUtil.onlinePlayers(player -> {
if(!Settings.instance().getSetting(player, Settings.Key.ShowJoinAndLeaveMessages, Boolean.class)) return;
player.sendMessage(
Component
.text("<<< ").color(NamedTextColor.RED)
.append(this.getAppliance().getReportablePlayerName(event.getPlayer()))
);
});
}
@EventHandler
public void onDeath(PlayerDeathEvent event) {
event.deathMessage(
Component
.text("")
.append(
Optional
.ofNullable(event.deathMessage())
.orElse(Component.text(event.getPlayer().getName()))
)
.color(TextColor.color(Color.SILVER.asRGB()))
);
}
}

View File

@ -0,0 +1,38 @@
package eu.mhsl.craftattack.spawn.appliances.metaGameplay.chatMessages;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.CategorizedSetting;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.SettingCategory;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.Settings;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.settings.datatypes.BoolSetting;
import org.bukkit.Material;
public class ShowJoinAndLeaveMessagesSetting extends BoolSetting implements CategorizedSetting {
public ShowJoinAndLeaveMessagesSetting() {
super(Settings.Key.ShowJoinAndLeaveMessages);
}
@Override
protected String title() {
return "Join & Leave Nachrichten anzeigen";
}
@Override
protected String description() {
return "Zeige allgemeine Beitritts und Verlassensmeldungen im Chat";
}
@Override
protected Material icon() {
return Material.PLAYER_HEAD;
}
@Override
protected Boolean defaultValue() {
return true;
}
@Override
public SettingCategory category() {
return SettingCategory.Visuals;
}
}

View File

@ -0,0 +1,75 @@
package eu.mhsl.craftattack.spawn.appliances.metaGameplay.displayName;
import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliances.gameplay.outlawed.Outlawed;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.adminMarker.AdminMarker;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.afkTag.AfkTag;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.sleepTag.SleepTag;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.yearRank.YearRank;
import eu.mhsl.craftattack.spawn.util.server.Floodgate;
import eu.mhsl.craftattack.spawn.util.text.ComponentUtil;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentBuilder;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.logging.Level;
public class DisplayName extends Appliance {
public interface Prefixed {
@Nullable
Component getNamePrefix(Player player);
}
public void update(Player player) {
TextColor playerColor = this.queryAppliance(AdminMarker.class).getPlayerColor(player);
List<Supplier<Prefixed>> prefixes = List.of(
() -> this.queryAppliance(Outlawed.class),
() -> this.queryAppliance(YearRank.class),
() -> this.queryAppliance(AfkTag.class),
() -> this.queryAppliance(SleepTag.class)
);
ComponentBuilder<TextComponent, TextComponent.Builder> playerName = Component.text();
prefixes.stream()
.map(prefixed -> prefixed.get().getNamePrefix(player))
.filter(Objects::nonNull)
.forEach(prefix -> playerName
.append(prefix)
.append(ComponentUtil.clearedSpace())
);
if(Floodgate.isBedrock(player)) {
playerName
.append(
Component.text("\uD83C\uDFAE", NamedTextColor.GRAY)
.hoverEvent(HoverEvent.showText(Component.text(
String.format("%s spielt die Minecraft: Bedrock Edition", player.getName())
)))
)
.append(ComponentUtil.clearedSpace());
}
playerName.append(Component.text(player.getName(), playerColor));
this.setGlobal(player, playerName.build());
}
private void setGlobal(Player player, Component component) {
try {
player.customName(component);
player.displayName(component);
player.playerListName(component);
} catch(Exception e) {
Main.instance().getLogger().log(Level.SEVERE, e, e::getMessage);
}
}
}

View File

@ -0,0 +1,13 @@
package eu.mhsl.craftattack.spawn.appliances.metaGameplay.displayName;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.player.PlayerJoinEvent;
class DisplayNameUpdateListener extends ApplianceListener<DisplayName> {
@EventHandler(priority = EventPriority.LOW)
public void onJoin(PlayerJoinEvent event) {
this.getAppliance().update(event.getPlayer());
}
}

View File

@ -0,0 +1,12 @@
package eu.mhsl.craftattack.spawn.appliances.metaGameplay.event;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
class ApplyPendingRewardsListener extends ApplianceListener<Event> {
@EventHandler
public void onJoin(PlayerJoinEvent event) {
this.getAppliance().applyPendingRewards(event.getPlayer());
}
}

View File

@ -0,0 +1,230 @@
package eu.mhsl.craftattack.spawn.appliances.metaGameplay.event;
import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.api.client.ReqResp;
import eu.mhsl.craftattack.spawn.api.client.repositories.EventRepository;
import eu.mhsl.craftattack.spawn.api.server.HttpServer;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.gameplay.customAdvancements.Advancements;
import eu.mhsl.craftattack.spawn.appliances.gameplay.customAdvancements.CustomAdvancements;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.event.command.*;
import eu.mhsl.craftattack.spawn.util.IteratorUtil;
import eu.mhsl.craftattack.spawn.util.api.HttpStatus;
import eu.mhsl.craftattack.spawn.util.entity.DisplayVillager;
import eu.mhsl.craftattack.spawn.util.listener.DismissInventoryOpenFromHolder;
import eu.mhsl.craftattack.spawn.util.listener.PlayerInteractAtEntityEventListener;
import eu.mhsl.craftattack.spawn.util.server.PluginMessage;
import eu.mhsl.craftattack.spawn.util.text.ComponentUtil;
import eu.mhsl.craftattack.spawn.util.text.Countdown;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.entity.Villager;
import org.bukkit.event.Listener;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import java.util.*;
public class Event extends Appliance {
enum AdvertisementStatus {
BEFORE,
ADVERTISED,
DONE
}
Countdown advertiseCountdown = new Countdown(
120,
announcementData -> Component.text()
.append(ComponentUtil.createRainbowText("Event", 30))
.append(Component.text(" Start in ", NamedTextColor.GOLD))
.append(Component.text(announcementData.count(), NamedTextColor.AQUA))
.append(Component.text(" " + announcementData.unit() + "!", NamedTextColor.GOLD))
.build(),
component -> IteratorUtil.onlinePlayers(player -> player.sendMessage(component)),
() -> this.advertiseStatus = AdvertisementStatus.DONE
);
public DisplayVillager.ConfigBound villager;
private boolean isOpen = false;
private AdvertisementStatus advertiseStatus = AdvertisementStatus.BEFORE;
private UUID roomId;
private final List<Reward> pendingRewards = new ArrayList<>();
record RewardConfiguration(String memorialMaterial, String memorialTitle, String memorialLore, List<UUID> memorials,
String material, Map<UUID, Integer> rewards) {
}
record Reward(UUID playerUuid, ItemStack itemStack) {
}
public Event() {
super("event");
}
@Override
public void onEnable() {
this.villager = new DisplayVillager.ConfigBound(
this.localConfig(),
villager -> {
villager.customName(Component.text("Events", NamedTextColor.GOLD));
villager.setProfession(Villager.Profession.LIBRARIAN);
villager.setVillagerType(Villager.Type.SNOW);
}
);
this.isOpen = this.localConfig().getBoolean("enabled", false);
if(this.isOpen) this.roomId = UUID.fromString(this.localConfig().getString("roomId", ""));
}
public void openEvent() {
if(this.isOpen) throw new ApplianceCommand.Error("Es läuft derzeit bereits ein Event!");
Bukkit.getScheduler().runTaskAsynchronously(Main.instance(), () -> {
ReqResp<EventRepository.CreatedRoom> sessionResponse = this.queryRepository(EventRepository.class).createSession();
if(sessionResponse.status() != HttpStatus.OK)
throw new ApplianceCommand.Error("Event-Server meldet Fehler: " + sessionResponse.status());
this.isOpen = true;
this.roomId = sessionResponse.data().uuid();
});
}
public void joinEvent(Player p) {
if(!this.isOpen) {
p.sendMessage(Component.text("Zurzeit ist kein Event geöffnet.", NamedTextColor.RED));
return;
}
if(!p.hasPermission("admin") && this.advertiseStatus == AdvertisementStatus.BEFORE) {
p.sendMessage(Component.text("Die Event befinden sich noch in der Vorbereitung.", NamedTextColor.RED));
return;
}
if(!p.hasPermission("admin") && this.advertiseStatus == AdvertisementStatus.DONE) {
p.sendMessage(Component.text("Die Events laufen bereits. Ein nachträgliches Beitreten ist nicht möglich.", NamedTextColor.RED));
return;
}
Main.instance().getLogger().info("Verbinde mit eventserver: " + p.getName());
p.sendMessage(Component.text("Authentifiziere...", NamedTextColor.GREEN));
Bukkit.getScheduler().runTaskAsynchronously(Main.instance(), () -> {
ReqResp<EventRepository.QueueRoom.Response> queueResponse = this.queryRepository(EventRepository.class)
.queueRoom(new EventRepository.QueueRoom(p.getUniqueId(), this.roomId));
if(queueResponse.status() != HttpStatus.OK || queueResponse.data().error() != null) {
p.sendMessage(Component.text("Fehler beim Betreten: " + queueResponse.data().error(), NamedTextColor.RED));
return;
}
p.sendMessage(Component.text("Betrete...", NamedTextColor.GREEN));
PluginMessage.connect(p, this.localConfig().getString("connect-server-name"));
});
}
public void endEvent() {
if(!this.isOpen) throw new ApplianceCommand.Error("Es läuft derzeit kein Event!");
this.isOpen = false;
}
private void rewardPlayers(RewardConfiguration rewardConfiguration) {
rewardConfiguration.rewards.forEach((uuid, amount) -> {
Reward reward = new Reward(uuid, new ItemStack(Objects.requireNonNull(Material.matchMaterial(rewardConfiguration.material)), amount));
if(Bukkit.getPlayer(uuid) == null) {
this.pendingRewards.add(reward);
return;
}
this.giveReward(reward);
});
rewardConfiguration.memorials.forEach(uuid -> {
ItemStack memorialItem = new ItemStack(Objects.requireNonNull(Material.matchMaterial(rewardConfiguration.memorialMaterial)));
ItemMeta meta = memorialItem.getItemMeta();
meta.displayName(Component.text(rewardConfiguration.memorialTitle, NamedTextColor.GOLD));
meta.lore(List.of(Component.text(rewardConfiguration.memorialLore, NamedTextColor.AQUA)));
memorialItem.setItemMeta(meta);
Reward memorial = new Reward(uuid, memorialItem);
Main.instance().getAppliance(CustomAdvancements.class).grantAdvancement(Advancements.participateEvent, uuid);
if(Bukkit.getPlayer(uuid) == null) {
this.pendingRewards.add(memorial);
return;
}
this.giveReward(memorial);
});
rewardConfiguration.rewards.keySet().stream()
.max(Comparator.comparing(rewardConfiguration.rewards::get))
.ifPresent(uuid -> Main.instance().getAppliance(CustomAdvancements.class)
.grantAdvancement(Advancements.winner, uuid));
}
private void giveReward(Reward reward) {
Player player = Bukkit.getPlayer(reward.playerUuid);
if(player == null) throw new RuntimeException("Cannot reward offline playerUuid!");
Map<Integer, ItemStack> remaining = player.getInventory().addItem(reward.itemStack);
Bukkit.getScheduler().runTask(
Main.instance(),
() -> remaining.values().forEach(remainingStack -> player.getWorld().dropItem(player.getLocation(), remainingStack))
);
}
public void applyPendingRewards(Player player) {
if(this.pendingRewards.isEmpty()) return;
List<Reward> givenRewards = this.pendingRewards.stream().filter(reward -> reward.playerUuid.equals(player.getUniqueId())).toList();
this.pendingRewards.removeAll(givenRewards);
givenRewards.forEach(this::giveReward);
}
public void advertise() {
this.advertiseCountdown.cancelIfRunning();
this.advertiseStatus = AdvertisementStatus.ADVERTISED;
IteratorUtil.onlinePlayers(player -> player.sendMessage(
Component.text()
.append(Component.text("-".repeat(10), NamedTextColor.GRAY)).appendNewline()
.append(Component.text("Ein Event wurde gestartet!", NamedTextColor.GOLD)).appendNewline()
.append(Component.text("Nutze "))
.append(Component.text("/event", NamedTextColor.AQUA))
.append(Component.text(", um dem Event beizutreten!")).appendNewline()
.append(Component.text("-".repeat(10), NamedTextColor.GRAY)).appendNewline()
));
this.advertiseCountdown.start();
}
@Override
public void httpApi(HttpServer.ApiBuilder apiBuilder) {
apiBuilder.post("reward", RewardConfiguration.class, (rewardConfiguration, request) -> {
this.rewardPlayers(rewardConfiguration);
return HttpServer.nothing;
});
}
@Override
@NotNull
protected List<ApplianceCommand<?>> commands() {
return List.of(
new EventCommand(),
new MoveEventVillagerCommand(),
new EventOpenSessionCommand(),
new EventEndSessionCommand(),
new EventAdvertiseCommand()
);
}
@Override
@NotNull
protected List<Listener> listeners() {
return List.of(
new ApplyPendingRewardsListener(),
new PlayerInteractAtEntityEventListener(this.villager.getUniqueId(), playerInteractAtEntityEvent -> this.joinEvent(playerInteractAtEntityEvent.getPlayer())),
new DismissInventoryOpenFromHolder(this.villager.getUniqueId())
);
}
}

View File

@ -1,7 +1,7 @@
package eu.mhsl.craftattack.spawn.appliances.event.command; package eu.mhsl.craftattack.spawn.appliances.metaGameplay.event.command;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.event.Event; import eu.mhsl.craftattack.spawn.appliances.metaGameplay.event.Event;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -13,6 +13,6 @@ public class EventAdvertiseCommand extends ApplianceCommand<Event> {
@Override @Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception { protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
getAppliance().advertise(); this.getAppliance().advertise();
} }
} }

View File

@ -1,7 +1,7 @@
package eu.mhsl.craftattack.spawn.appliances.event.command; package eu.mhsl.craftattack.spawn.appliances.metaGameplay.event.command;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.event.Event; import eu.mhsl.craftattack.spawn.appliances.metaGameplay.event.Event;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -13,6 +13,6 @@ public class EventCommand extends ApplianceCommand.PlayerChecked<Event> {
@Override @Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception { protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
getAppliance().joinEvent(getPlayer()); this.getAppliance().joinEvent(this.getPlayer());
} }
} }

View File

@ -1,7 +1,7 @@
package eu.mhsl.craftattack.spawn.appliances.event.command; package eu.mhsl.craftattack.spawn.appliances.metaGameplay.event.command;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.event.Event; import eu.mhsl.craftattack.spawn.appliances.metaGameplay.event.Event;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -13,6 +13,6 @@ public class EventEndSessionCommand extends ApplianceCommand<Event> {
@Override @Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
getAppliance().endEvent(); this.getAppliance().endEvent();
} }
} }

View File

@ -1,7 +1,7 @@
package eu.mhsl.craftattack.spawn.appliances.event.command; package eu.mhsl.craftattack.spawn.appliances.metaGameplay.event.command;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.event.Event; import eu.mhsl.craftattack.spawn.appliances.metaGameplay.event.Event;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command; import org.bukkit.command.Command;
@ -15,7 +15,7 @@ public class EventOpenSessionCommand extends ApplianceCommand<Event> {
@Override @Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception { protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
getAppliance().openEvent(); this.getAppliance().openEvent();
sender.sendMessage(Component.text("Event-Server erfolgreich gestartet!", NamedTextColor.GREEN)); sender.sendMessage(Component.text("Event-Server gestartet!", NamedTextColor.GREEN));
} }
} }

View File

@ -1,7 +1,7 @@
package eu.mhsl.craftattack.spawn.appliances.event.command; package eu.mhsl.craftattack.spawn.appliances.metaGameplay.event.command;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.event.Event; import eu.mhsl.craftattack.spawn.appliances.metaGameplay.event.Event;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -13,6 +13,6 @@ public class MoveEventVillagerCommand extends ApplianceCommand.PlayerChecked<Eve
@Override @Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
getAppliance().villager.updateLocation(getPlayer().getLocation()); this.getAppliance().villager.updateLocation(this.getPlayer().getLocation());
} }
} }

View File

@ -0,0 +1,77 @@
package eu.mhsl.craftattack.spawn.appliances.metaGameplay.feedback;
import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.api.client.ReqResp;
import eu.mhsl.craftattack.spawn.api.client.repositories.FeedbackRepository;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.util.api.HttpStatus;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentBuilder;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class Feedback extends Appliance {
public Feedback() {
super("feedback");
}
public void requestFeedback(String eventName, List<Player> receivers, @Nullable String question) {
ReqResp<Map<UUID, String>> response = this.queryRepository(FeedbackRepository.class).createFeedbackUrls(
new FeedbackRepository.Request(eventName, receivers.stream().map(Entity::getUniqueId).toList())
);
System.out.println(response.toString());
System.out.println(response.status());
if(response.status() != HttpStatus.CREATED) throw new RuntimeException();
Component border = Component.text("-".repeat(40), NamedTextColor.GRAY);
receivers.forEach(player -> {
String feedbackUrl = response.data().get(player.getUniqueId());
if(feedbackUrl == null) {
Main.logger().warning(String.format("FeedbackUrl not found for player '%s' from backend!", player.getUniqueId()));
return;
}
ComponentBuilder<TextComponent, TextComponent.Builder> message = Component.text()
.append(border)
.appendNewline();
if(question != null) {
message
.append(Component.text(question, NamedTextColor.GREEN))
.appendNewline()
.appendNewline();
}
message
.append(Component.text("Klicke hier und gib uns Feedback, damit wir dein Spielerlebnis verbessern können!", NamedTextColor.DARK_GREEN)
.clickEvent(ClickEvent.openUrl(feedbackUrl)))
.hoverEvent(HoverEvent.showText(Component.text("Klicke, um Feedback zu geben.")))
.appendNewline()
.append(border);
player.sendMessage(message.build());
});
}
@Override
protected @NotNull List<ApplianceCommand<?>> commands() {
return List.of(
new FeedbackCommand(),
new RequestFeedbackCommand()
);
}
}

View File

@ -0,0 +1,30 @@
package eu.mhsl.craftattack.spawn.appliances.metaGameplay.feedback;
import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.util.text.ComponentUtil;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.List;
class FeedbackCommand extends ApplianceCommand.PlayerChecked<Feedback> {
public FeedbackCommand() {
super("feedback");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
sender.sendMessage(ComponentUtil.pleaseWait());
Bukkit.getScheduler().runTaskAsynchronously(
Main.instance(),
() -> this.getAppliance().requestFeedback(
"self-issued-ingame",
List.of(this.getPlayer()),
null
)
);
}
}

View File

@ -0,0 +1,27 @@
package eu.mhsl.craftattack.spawn.appliances.metaGameplay.feedback;
import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
class RequestFeedbackCommand extends ApplianceCommand<Feedback> {
public RequestFeedbackCommand() {
super("requestFeedback");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
Bukkit.getScheduler().runTaskAsynchronously(
Main.instance(),
() -> this.getAppliance().requestFeedback(
"admin-issued-ingame",
new ArrayList<>(Bukkit.getOnlinePlayers()), String.join(" ", args)
)
);
}
}

View File

@ -0,0 +1,28 @@
package eu.mhsl.craftattack.spawn.appliances.metaGameplay.help;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.help.command.DiscordCommand;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.help.command.HelpCommand;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.help.command.SpawnCommand;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.help.command.TeamspeakCommand;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class Help extends Appliance {
public Help() {
super("help");
}
@Override
@NotNull
protected List<ApplianceCommand<?>> commands() {
return List.of(
new HelpCommand(),
new SpawnCommand(),
new TeamspeakCommand(),
new DiscordCommand()
);
}
}

View File

@ -1,8 +1,9 @@
package eu.mhsl.craftattack.spawn.appliances.help.command; package eu.mhsl.craftattack.spawn.appliances.metaGameplay.help.command;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.help.Help; import eu.mhsl.craftattack.spawn.appliances.metaGameplay.help.Help;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -13,11 +14,14 @@ public class DiscordCommand extends ApplianceCommand<Help> {
super("discord"); super("discord");
} }
private final static String discordLink = "https://discord.gg/TXxspGVanq";
@Override @Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception { protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
sender.sendMessage( sender.sendMessage(
Component.text("Einen offiziellen Discord Server gibt es nicht, aber Du kannst gerne unserem Teamspeak joinen: ", NamedTextColor.GOLD) Component.text("Offizieller Discord Server: ", NamedTextColor.GOLD)
.append(Component.text("mhsl.eu", NamedTextColor.AQUA)) .append(Component.text(discordLink, NamedTextColor.AQUA))
.clickEvent(ClickEvent.openUrl(discordLink))
); );
} }
} }

View File

@ -1,7 +1,7 @@
package eu.mhsl.craftattack.spawn.appliances.help.command; package eu.mhsl.craftattack.spawn.appliances.metaGameplay.help.command;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.help.Help; import eu.mhsl.craftattack.spawn.appliances.metaGameplay.help.Help;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command; import org.bukkit.command.Command;
@ -16,9 +16,10 @@ public class HelpCommand extends ApplianceCommand<Help> {
@Override @Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
sender.sendMessage( sender.sendMessage(
Component.text("Willkommen auf Craftattack!", NamedTextColor.GOLD) Component.text("Willkommen auf Craftattack!", NamedTextColor.GOLD)
.appendNewline() .appendNewline()
.append(Component.text("Hier ist ein Hilfetext!", NamedTextColor.GRAY)) .append(Component.text("Wenn du hilfe benötigst kannst du dich jederzeit an einen Admin wenden." +
" Weitere Informationen zu Funktionen und Befehlen erhältst du zudem im Turm am Spawn.", NamedTextColor.GRAY))
); );
} }
} }

View File

@ -1,7 +1,7 @@
package eu.mhsl.craftattack.spawn.appliances.help.command; package eu.mhsl.craftattack.spawn.appliances.metaGameplay.help.command;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.help.Help; import eu.mhsl.craftattack.spawn.appliances.metaGameplay.help.Help;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command; import org.bukkit.command.Command;
@ -12,13 +12,15 @@ import java.util.Objects;
public class SpawnCommand extends ApplianceCommand<Help> { public class SpawnCommand extends ApplianceCommand<Help> {
private static final String spawnKey = "spawn"; private static final String spawnKey = "spawn";
public SpawnCommand() { public SpawnCommand() {
super("spawn"); super("spawn");
} }
@Override @Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(!getAppliance().localConfig().isString(spawnKey)) throw new ApplianceCommand.Error("Es wurde kein Spawnbereich hinterlegt!"); if(!this.getAppliance().localConfig().isString(spawnKey))
sender.sendMessage(Component.text(Objects.requireNonNull(getAppliance().localConfig().getString(spawnKey)), NamedTextColor.GOLD)); throw new ApplianceCommand.Error("Es wurde kein Spawnbereich hinterlegt!");
sender.sendMessage(Component.text(Objects.requireNonNull(this.getAppliance().localConfig().getString(spawnKey)), NamedTextColor.GOLD));
} }
} }

View File

@ -1,7 +1,7 @@
package eu.mhsl.craftattack.spawn.appliances.help.command; package eu.mhsl.craftattack.spawn.appliances.metaGameplay.help.command;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.help.Help; import eu.mhsl.craftattack.spawn.appliances.metaGameplay.help.Help;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command; import org.bukkit.command.Command;
@ -10,23 +10,25 @@ import org.jetbrains.annotations.NotNull;
public class TeamspeakCommand extends ApplianceCommand<Help> { public class TeamspeakCommand extends ApplianceCommand<Help> {
private static final String teamspeakKey = "teamspeak"; private static final String teamspeakKey = "teamspeak";
public TeamspeakCommand() { public TeamspeakCommand() {
super("teamspeak"); super("teamspeak");
} }
@Override @Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(!getAppliance().localConfig().isString(teamspeakKey)) throw new ApplianceCommand.Error("Es wurde kein Teamspeak hinterlegt!"); if(!this.getAppliance().localConfig().isString(teamspeakKey))
throw new ApplianceCommand.Error("Es wurde kein Teamspeak hinterlegt!");
sender.sendMessage( sender.sendMessage(
Component.text() Component.text()
.append(Component.text("Joine unserem Teamspeak: ", NamedTextColor.GOLD)) .append(Component.text("Joine unserem Teamspeak: ", NamedTextColor.GOLD))
.append(getTeamspeakIp(getAppliance().localConfig().getString(teamspeakKey))) .append(this.getTeamspeakIp(this.getAppliance().localConfig().getString(teamspeakKey)))
); );
} }
private Component getTeamspeakIp(String ip) { private Component getTeamspeakIp(String ip) {
return Component.text() return Component.text()
.append(Component.text(ip, NamedTextColor.AQUA)) .append(Component.text(ip, NamedTextColor.AQUA))
.build(); .build();
} }
} }

View File

@ -0,0 +1,69 @@
package eu.mhsl.craftattack.spawn.appliances.metaGameplay.infoBars;
import eu.mhsl.craftattack.spawn.Main;
import net.kyori.adventure.bossbar.BossBar;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.util.Ticks;
import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitTask;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
public abstract class Bar {
private BossBar bossBar;
private final BukkitTask updateTask;
public Bar() {
long refreshRateInTicks = this.refresh().get(ChronoUnit.SECONDS) * Ticks.TICKS_PER_SECOND;
this.updateTask = Bukkit.getScheduler().runTaskTimerAsynchronously(
Main.instance(),
this::update,
refreshRateInTicks,
refreshRateInTicks
);
}
public BossBar getBossBar() {
if(this.bossBar == null) this.bossBar = this.createBar();
return this.bossBar;
}
private BossBar createBar() {
return BossBar.bossBar(
this.title(),
this.correctedProgress(),
this.color(),
this.overlay()
);
}
private void update() {
if(this.bossBar == null) return;
this.beforeRefresh();
this.bossBar.name(this.title());
this.bossBar.progress(this.correctedProgress());
this.bossBar.color(this.color());
this.bossBar.overlay(this.overlay());
}
public void stopUpdate() {
this.updateTask.cancel();
}
private float correctedProgress() {
return Math.clamp(this.progress(), 0, 1);
}
protected void beforeRefresh() {
}
protected abstract Duration refresh();
protected abstract String name();
protected abstract Component title();
protected abstract float progress();
protected abstract BossBar.Color color();
protected abstract BossBar.Overlay overlay();
}

View File

@ -0,0 +1,32 @@
package eu.mhsl.craftattack.spawn.appliances.metaGameplay.infoBars;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
class InfoBarCommand extends ApplianceCommand.PlayerChecked<InfoBars> {
public InfoBarCommand() {
super("infobar");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) throws Exception {
if(args.length == 0) throw new Error("<show|hide|hideall> [bar name]");
switch(args[0]) {
case "hideAll" -> this.getAppliance().hideAll(this.getPlayer());
case "show" -> this.getAppliance().show(this.getPlayer(), args[1]);
case "hide" -> this.getAppliance().hide(this.getPlayer(), args[1]);
default -> throw new Error("Erlaubte Optionen sind 'show', 'hide', 'hideAll'!");
}
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(args.length == 1) return List.of("show", "hide", "hideAll");
return this.getAppliance().getInfoBars().stream().map(Bar::name).toList();
}
}

View File

@ -0,0 +1,92 @@
package eu.mhsl.craftattack.spawn.appliances.metaGameplay.infoBars;
import eu.mhsl.craftattack.spawn.Main;
import eu.mhsl.craftattack.spawn.appliance.Appliance;
import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.infoBars.bars.MsptBar;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.infoBars.bars.PlayerCounterBar;
import eu.mhsl.craftattack.spawn.appliances.metaGameplay.infoBars.bars.TpsBar;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class InfoBars extends Appliance {
private final NamespacedKey infoBarKey = new NamespacedKey(Main.instance(), "infobars");
private final List<Bar> infoBars = List.of(
new TpsBar(),
new MsptBar(),
new PlayerCounterBar()
);
public void showAll(Player player) {
this.getStoredBars(player).forEach(bar -> this.show(player, bar));
}
public void hideAll(Player player) {
this.getStoredBars(player).forEach(bar -> this.hide(player, bar));
this.setStoredBars(player, List.of());
}
public void show(Player player, String bar) {
this.validateBarName(bar);
List<String> existingBars = new ArrayList<>(this.getStoredBars(player));
existingBars.add(bar);
player.showBossBar(this.getBarByName(bar).getBossBar());
this.setStoredBars(player, existingBars);
}
public void hide(Player player, String bar) {
this.validateBarName(bar);
List<String> existingBars = new ArrayList<>(this.getStoredBars(player));
existingBars.remove(bar);
player.hideBossBar(this.getBarByName(bar).getBossBar());
this.setStoredBars(player, existingBars);
}
private List<String> getStoredBars(Player player) {
PersistentDataContainer container = player.getPersistentDataContainer();
if(!container.has(this.infoBarKey)) return List.of();
return container.get(this.infoBarKey, PersistentDataType.LIST.strings());
}
private void setStoredBars(Player player, List<String> bars) {
player.getPersistentDataContainer().set(this.infoBarKey, PersistentDataType.LIST.strings(), bars);
}
private Bar getBarByName(String name) {
return this.infoBars.stream()
.filter(bar -> bar.name().equalsIgnoreCase(name))
.findFirst()
.orElse(null);
}
private void validateBarName(String name) {
if(this.getBarByName(name) == null)
throw new ApplianceCommand.Error(String.format("Ungültiger infobar name '%s'", name));
}
public List<Bar> getInfoBars() {
return this.infoBars;
}
@Override
public void onDisable() {
this.infoBars.forEach(Bar::stopUpdate);
}
@Override
protected @NotNull List<Listener> listeners() {
return List.of(new ShowPreviousBarsListener());
}
@Override
protected @NotNull List<ApplianceCommand<?>> commands() {
return List.of(new InfoBarCommand());
}
}

View File

@ -0,0 +1,12 @@
package eu.mhsl.craftattack.spawn.appliances.metaGameplay.infoBars;
import eu.mhsl.craftattack.spawn.appliance.ApplianceListener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.player.PlayerJoinEvent;
class ShowPreviousBarsListener extends ApplianceListener<InfoBars> {
@EventHandler
public void onJoin(PlayerJoinEvent event) {
// this.getAppliance().showAll(event.getPlayer());
}
}

Some files were not shown because too many files have changed in this diff Show More