49 Commits

Author SHA1 Message Date
5c4ddf892c Fixed missing import 2023-12-27 01:17:28 +01:00
7ea619c596 Wide variety of changes for release 2023-12-27 01:16:58 +01:00
c63e4badf4 Added infrastructure code for game summaries 2023-12-02 21:24:33 +01:00
894b5464dd Prevent api driven rooms from automatic ownership change 2023-11-28 10:28:49 +01:00
8b47eb1139 Fixed invisible players for joining players by sending custom packets 2023-11-26 18:19:03 +01:00
440d4f1b48 Revert to official minestom 2023-11-25 18:29:20 +01:00
76192b1f97 Changed room join method for api driven rooms 2023-11-19 13:06:34 +01:00
5f6473b0fb Adder gradle task and fixed config bug 2023-11-12 15:56:06 +01:00
5d29c387a3 Added API driven Rooms 2023-11-12 00:59:17 +01:00
2306308071 added missing gradle files 2023-11-11 23:22:52 +01:00
541eb1e733 Added option for persistent resources like configuration files 2023-11-11 22:45:23 +01:00
c6ecbe7548 fixed gitignore 2023-11-11 22:39:23 +01:00
5191d5ece3 added configuration options 2023-11-11 22:38:08 +01:00
6dc4269367 Refactored Generation and class structure 2023-11-11 22:13:58 +01:00
d9bbaf9865 Added AcidRain game 2023-10-03 22:25:47 +02:00
8ef61e0880 Refactored Generation class structure 2023-10-03 18:30:34 +02:00
b3b6644d24 Added TNT-Run 2023-10-03 17:53:01 +02:00
b9b683dcde Polished TrafficLightRace 2023-10-03 16:50:59 +02:00
16cf29a590 Refactored User room to be optional 2023-10-03 10:40:32 +02:00
4b82b81d63 Refactored score system 2023-10-01 20:54:48 +02:00
91069b76c6 Added configuration options to spleef game 2023-10-01 19:54:02 +02:00
61448e77f2 Refactored Command structure, began support for API driven Rooms 2023-10-01 19:32:14 +02:00
8fddabe00f Added command refresh after persmission change 2023-10-01 18:11:32 +02:00
de79433c32 Better prespace system for chatmessages 2023-10-01 01:40:31 +02:00
8304b6a105 Polished and finalized spleef gamemode 2023-10-01 01:30:19 +02:00
95750701f2 Added fakeplayer command for testing 2023-10-01 00:48:30 +02:00
ee9115b774 Added pipe in chat message spacer 2023-09-30 23:53:08 +02:00
cbe16a669c Refactored chat messaging api 2023-09-30 23:31:36 +02:00
3489800f28 Fixed scores duplicates 2023-09-30 22:34:38 +02:00
57c4993a30 updated gitignore 2023-09-30 22:33:02 +02:00
b836874b46 Added op command 2023-09-30 22:31:07 +02:00
61919b71b1 Added spleef gamemode 2023-09-30 21:58:31 +02:00
eecdeff77c Refactored Score system 2023-09-30 21:58:08 +02:00
4fc8503d63 Fix SquareTerrainGenerator not being square 2023-05-21 00:19:50 +02:00
671857b9e4 Added Backrooms gamemode 2023-05-18 21:18:08 +02:00
7c5d610803 Added Bedwars assets 2023-01-07 23:55:40 +01:00
d1432347c1 Added Bedwars
Added ConfigurationHandler for games
Further progress
2023-01-07 23:54:55 +01:00
bf4d8b97ed Added Bedwars game 2022-10-10 14:40:13 +02:00
89aca9bd98 Fixed Deathcube not showing blocks, aswell edited the win condition.
Edited the Options for Deathcube to more realistic values
2022-10-08 23:40:05 +02:00
631463d845 Removed autojoin for Room 2022-10-08 23:38:51 +02:00
189da92542 Fixed GamemodeCommand using capital letters instead of formatted letters 2022-10-08 23:37:07 +02:00
2284912b16 Fixed Options not applying correctly from GameConfigurationInventory 2022-10-08 23:36:30 +02:00
c9bc9dd2da Inspection and Cleanup 2022-10-03 21:13:35 +02:00
6907a767c6 Added translation to all Components 2022-10-03 20:56:14 +02:00
18db3514e3 InventoryItemAlignment 2022-10-02 16:58:52 +02:00
4bdb3f94d5 Working Restrictions 2022-10-01 16:58:27 +02:00
b5468b1fe9 Added restrictions 2022-09-25 23:34:43 +02:00
8409d1cc7d Game api additions, Refactoring, Translation api edits 2022-09-18 22:57:19 +02:00
59a6e1c423 Initial commit 2022-09-17 10:49:36 +02:00
217 changed files with 7775 additions and 2 deletions

6
.gitignore vendored
View File

@ -1,4 +1,3 @@
# ---> Java
# Compiled class file # Compiled class file
*.class *.class
@ -24,3 +23,8 @@
hs_err_pid* hs_err_pid*
replay_pid* replay_pid*
.idea
.gradle
build/*
/resources/

View File

@ -2,7 +2,7 @@ MIT License
Copyright (c) <year> <copyright holders> Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without IRestriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

94
build.gradle Normal file
View File

@ -0,0 +1,94 @@
//for jar packaging see https://github.com/Protonull/BasicMinestomServer/blob/master/build.gradle.kts
plugins {
id 'java'
id "com.github.johnrengelman.shadow" version "7.1.0"
}
group 'eu.mhsl.minenet'
version '1.0-SNAPSHOT'
repositories {
//maven 'https://repo.unnamed.team/repository/unnamed-public/'
mavenCentral()
google()
maven {
url 'https://jitpack.io'
}
maven {
url "https://repo.unnamed.team/repository/unnamed-public/"
}
maven {
url 'https://oss.sonatype.org/content/repositories/snapshots'
}
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.0'
//https://jitpack.io/#Minestom/Minestom
// implementation 'com.github.Minestom:Minestom:c496ee357'
// implementation 'com.github.waxeria:Minestom:e0427a36f3'
// implementation 'dev.hollowcube:minestom-ce:5bcc72b911'
implementation 'dev.hollowcube:minestom-ce:8715f4305d'
implementation 'dev.hollowcube:minestom-ce-extensions:1.2.0'
//Tools
implementation 'de.articdive:jnoise:3.0.2'
implementation 'net.md-5:bungeecord-config:1.19-R0.1-SNAPSHOT'
implementation 'org.apache.commons:commons-text:1.10.0'
implementation 'org.spongepowered:configurate-yaml:4.1.2'
implementation 'com.sparkjava:spark-core:2.9.4'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.google.guava:guava:31.0.1-jre'
//PvP
implementation 'com.github.TogAr2:MinestomPvP:35e5661'
//implementation 'com.github.TogAr2:MinestomPvP:135ec9e2b7'
// Hephaestus engine
implementation("team.unnamed:hephaestus-api:0.2.1-SNAPSHOT")
implementation("team.unnamed:hephaestus-reader-blockbench:0.2.1-SNAPSHOT")
implementation("team.unnamed:hephaestus-runtime-minestom:0.2.1-SNAPSHOT")
}
tasks {
jar {
manifest {
attributes 'Main-Class': 'eu.mhsl.minenet.minigames.Main'
attributes 'Multi-Release': true
}
duplicatesStrategy = 'exclude'
from configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
build {
dependsOn(shadowJar)
}
shadowJar {
mergeServiceFiles()
archiveClassifier.set("")
}
}
tasks.register('copyJarToServer', Exec) {
dependsOn shadowJar
mustRunAfter shadowJar
commandLine 'scp', 'build/libs/Minigames-1.0-SNAPSHOT.jar', 'root@10.20.6.5:/root/minigames'
}

View File

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

234
gradlew vendored Normal file
View File

@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# eu.mhsl.minenet.minigames.command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «eu.mhsl.minenet.minigames.command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java eu.mhsl.minenet.minigames.command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java eu.mhsl.minenet.minigames.command, stacking in reverse order:
# * args from the eu.mhsl.minenet.minigames.command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java eu.mhsl.minenet.minigames.command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor eu.mhsl.minenet.minigames.command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View File

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

2
settings.gradle Normal file
View File

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

View File

@ -0,0 +1,74 @@
package eu.mhsl.minenet.minigames;
import eu.mhsl.minenet.minigames.api.HttpServer;
import eu.mhsl.minenet.minigames.command.Commands;
import eu.mhsl.minenet.minigames.handler.Listeners;
import eu.mhsl.minenet.minigames.lang.Languages;
import eu.mhsl.minenet.minigames.server.tasks.TablistUpdateTask;
import eu.mhsl.minenet.minigames.server.provider.ByPlayerNameUuidProvider;
import io.github.bloepiloepi.pvp.PvpExtension;
import net.minestom.server.MinecraftServer;
import net.minestom.server.extras.bungee.BungeeCordProxy;
import net.minestom.server.extras.lan.OpenToLAN;
import net.minestom.server.extras.velocity.VelocityProxy;
import net.minestom.server.timer.TaskSchedule;
import org.spongepowered.configurate.ConfigurateException;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
import java.util.Objects;
import java.util.logging.Logger;
public class Main {
/**
* Starts minenet minigames services
*/
private final static Logger logger = Logger.getGlobal();
public static ConfigurationNode globalConfig;
public static void main(String[] args) throws ConfigurateException {
//noinspection ResultOfMethodCallIgnored
Resource.values(); // This initializes and preloads the enum and extracts the resources
Languages.getInstance(); //Preload languages into the jvm
globalConfig = YamlConfigurationLoader
.builder()
.path(Resource.CONFIG.getPath())
.build()
.load();
ConfigurationNode serverConfig = globalConfig.node("server");
logger.info("Initialize Minecraft server...");
MinecraftServer server = MinecraftServer.init();
PvpExtension.init();
MinecraftServer.setBrandName("mhsl.eu - minenet - credits to minestom");
MinecraftServer.setCompressionThreshold(serverConfig.node("compression-threshold").getInt(0));
System.setProperty("minestom.chunk-view-distance", String.valueOf(serverConfig.node("view-distance").getInt()));
MinecraftServer.getConnectionManager().setUuidProvider(new ByPlayerNameUuidProvider());
Commands.values();
Listeners.values();
new HttpServer();
MinecraftServer.getSchedulerManager().scheduleTask(new TablistUpdateTask(), TaskSchedule.tick(20), TaskSchedule.tick(20));
logger.info("Starting Minecraft server ... ");
if(serverConfig.node("open-to-lan").getBoolean()) OpenToLAN.open();
if(globalConfig.node("bungeecord", "enabled").getBoolean()) BungeeCordProxy.enable();
if(globalConfig.node("velocity", "enabled").getBoolean()) {
VelocityProxy.enable(Objects.requireNonNull(globalConfig.node("velocity", "secret").getString()));
}
server.start("0.0.0.0", serverConfig.node("port").getInt(25565));
System.gc();
MinecraftServer.getSchedulerManager().scheduleNextTick(() -> logger.info("Minecraft server is now running!"));
}
}

View File

@ -0,0 +1,47 @@
package eu.mhsl.minenet.minigames;
import eu.mhsl.minenet.minigames.util.ResourceUtils;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.logging.Logger;
/**
* Predefined resources which are extracted on Runtime
*/
public enum Resource {
CONFIG("config.yml", true),
HUB_MAP("maps/hub"),
LOBBY_MAP("maps/lobby"),
RESULT_DISPLAY("maps/resultdisplay"),
GAME_MAP("maps/game"),
LOCALES("lang");
private final Path path;
private final String name;
Resource(String name, boolean keepOutdated) {
this.name = name;
this.path = Path.of("resources/" + name);
try {
Logger.getLogger("ressource").info("extracting resource " + name + " ... ");
ResourceUtils.extractResource(name, keepOutdated);
} catch (URISyntaxException | IOException e) {
throw new RuntimeException(e);
}
}
Resource(String name) {
this(name, false);
}
public Path getPath() {
return path;
}
public String getName() {
return name;
}
}

View File

@ -0,0 +1,35 @@
package eu.mhsl.minenet.minigames.api;
import com.google.gson.Gson;
import spark.Request;
import spark.Response;
import spark.Route;
import java.lang.reflect.ParameterizedType;
public abstract class Controller<Q, R> implements Route {
private final Class<Q> requestType;
@SuppressWarnings("unchecked")
public Controller() {
this.requestType = ((Class<Q>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]);
}
@Override
public Object handle(Request request, Response response) throws Exception {
response.header("Access-Control-Allow-Origin", "*");
response.header("Access-Control-Allow-Methods", "*");
Q req;
if(request.body().isEmpty()) {
req = null;
} else {
req = new Gson().fromJson(request.body(), this.requestType);
}
return new Gson().toJson(handle(req, response));
}
public abstract R handle(Q request, Response response);
}

View File

@ -0,0 +1,35 @@
package eu.mhsl.minenet.minigames.api;
import eu.mhsl.minenet.minigames.Main;
import eu.mhsl.minenet.minigames.api.routes.closeRoom.CloseRoom;
import eu.mhsl.minenet.minigames.api.routes.createRoom.CreateRoom;
import eu.mhsl.minenet.minigames.api.routes.queueRoom.QueueRoom;
import org.spongepowered.configurate.ConfigurationNode;
import static spark.Spark.*;
public class HttpServer {
private static final ConfigurationNode apiConfig = Main.globalConfig.node("api");
public HttpServer() {
if(!apiConfig.node("enabled").getBoolean()) return;
port(apiConfig.node("port").getInt());
initExceptionHandler(e -> e.printStackTrace(System.err));
exception(Exception.class, (exception, request, response) -> {
exception.printStackTrace(System.err);
response.status(500);
});
before((request, response) -> {
String userKey = request.headers("Authorization");
String serverKey = apiConfig.node("secret").getString();
if(userKey != null && !userKey.equals(serverKey)) {
halt(401, "Authorization header missing or wrong");
}
});
post("/room", new CreateRoom());
delete("/room", new CloseRoom());
post("/queueRoom", new QueueRoom());
}
}

View File

@ -0,0 +1,17 @@
package eu.mhsl.minenet.minigames.api;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class QueuedPlayerRooms {
private static final Map<UUID, UUID> playersToRoom = new HashMap<>();
public static void queuePlayer(UUID playerUUID, UUID roomUUID) {
playersToRoom.put(playerUUID, roomUUID);
}
public static UUID pullQueue(UUID playerUUID) {
return playersToRoom.remove(playerUUID);
}
}

View File

@ -0,0 +1,17 @@
package eu.mhsl.minenet.minigames.api.routes.closeRoom;
import eu.mhsl.minenet.minigames.api.Controller;
import eu.mhsl.minenet.minigames.instance.room.Room;
import spark.Response;
import java.util.UUID;
record Req(UUID room) {}
record Resp() {}
public class CloseRoom extends Controller<Req, Resp> {
@Override
public Resp handle(Req request, Response response) {
Room.deleteRoom(Room.getRoom(request.room()).orElseThrow());
return new Resp();
}
}

View File

@ -0,0 +1,17 @@
package eu.mhsl.minenet.minigames.api.routes.createRoom;
import eu.mhsl.minenet.minigames.api.Controller;
import eu.mhsl.minenet.minigames.instance.room.Room;
import spark.Response;
import java.util.UUID;
record Req() {}
record Resp(UUID uuid) {}
public class CreateRoom extends Controller<Req, Resp> {
@Override
public Resp handle(Req request, Response response) {
Room createdRoom = Room.createOwnerlessRoom();
return new Resp(createdRoom.uuid);
}
}

View File

@ -0,0 +1,24 @@
package eu.mhsl.minenet.minigames.api.routes.queueRoom;
import eu.mhsl.minenet.minigames.api.Controller;
import eu.mhsl.minenet.minigames.api.QueuedPlayerRooms;
import eu.mhsl.minenet.minigames.instance.room.Room;
import net.minestom.server.MinecraftServer;
import spark.Response;
import java.util.UUID;
record Req(UUID player, UUID room) {}
record Resp(String error) {}
public class QueueRoom extends Controller<Req, Resp> {
@Override
public Resp handle(Req request, Response response) {
if(Room.getRoom(request.room()).isEmpty()) return new Resp("Raum nicht gefunden");
if(MinecraftServer.getConnectionManager().getOnlinePlayers().size() >= Integer.parseInt(System.getProperty("minenet.playerlimit", "999"))) {
return new Resp("Der Server hat eine Spielerzahllimitierung und ist bereits voll!");
}
QueuedPlayerRooms.queuePlayer(request.player(), request.room());
return new Resp(null);
}
}

View File

@ -0,0 +1,47 @@
package eu.mhsl.minenet.minigames.command;
import eu.mhsl.minenet.minigames.command.anonymous.SkinCommand;
import eu.mhsl.minenet.minigames.command.privileged.*;
import eu.mhsl.minenet.minigames.command.anonymous.HubCommand;
import eu.mhsl.minenet.minigames.command.anonymous.LeaveCommand;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.builder.Command;
public enum Commands {
HUB(new HubCommand()),
LEAVE(new LeaveCommand()),
DEBUG(new DebugCommand()),
FLY(new FlyCommand()),
GAMEMODE(new GamemodeCommand()),
GC(new GcCommand()),
LANGTEST(new LangTestCommand()),
ROOM(new RoomCommand()),
UPDATE(new RefreshCommandsCommand()),
OP(new OpCommand()),
FAKEPLAYER(new FakeplayerCommand()),
KICK(new KickCommand()),
SKIN(new SkinCommand()),
SETOWNER(new SetRoomOwnerCommand()),
SETREWARD(new SetRewardCommand()),
PUBLISHREWARD(new PublishRewardCommand()),
ROOMPROXYMOVE(new InstanceProxyMoveCommand()),
GAMESTART(new GameStartCommand()),
GAMESTOP(new GameStopCommand()),
GAMETIMEOUT(new GameTimeoutCommand()),
PLAYERLIMIT(new PlayerLimitCommand()),
SETMEMORIAL(new SetMemorialCommand());
Commands(Command handler) {
MinecraftServer.getCommandManager().register(handler);
}
static {
MinecraftServer.getCommandManager().setUnknownCommandCallback((sender, command) -> {
if(command.isBlank()) return;
new ChatMessage(Icon.ERROR).appendStatic("Unknown command").quote(command).send(sender);
});
}
}

View File

@ -0,0 +1,36 @@
package eu.mhsl.minenet.minigames.command;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.condition.CommandCondition;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
public class PrivilegedCommand extends Command {
private final List<CommandCondition> conditions = new ArrayList<>();
public PrivilegedCommand(@NotNull String name, @Nullable String... aliases) {
super(name, aliases);
construct();
}
public PrivilegedCommand(@NotNull String name) {
super(name);
construct();
}
private void construct() {
addCondition(isPrivileged());
setCondition((sender, commandString) -> conditions.parallelStream().allMatch(condition -> condition.canUse(sender, commandString)));
}
protected CommandCondition isPrivileged() {
return (sender, commandString) -> sender.hasPermission("admin");
}
protected void addCondition(CommandCondition condition) {
conditions.add(condition);
}
}

View File

@ -0,0 +1,20 @@
package eu.mhsl.minenet.minigames.command.anonymous;
import eu.mhsl.minenet.minigames.util.MoveInstance;
import eu.mhsl.minenet.minigames.instance.hub.Hub;
import eu.mhsl.minenet.minigames.instance.room.Room;
import net.minestom.server.command.builder.Command;
import net.minestom.server.entity.Player;
public class HubCommand extends Command {
public HubCommand() {
super("hub");
setCondition((sender, commandString) -> ((Player) sender).getInstance() instanceof Room);
setDefaultExecutor((sender, context) -> {
Room.unsetRoom((Player) sender);
MoveInstance.move((Player) sender, Hub.INSTANCE);
});
}
}

View File

@ -0,0 +1,16 @@
package eu.mhsl.minenet.minigames.command.anonymous;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.instance.room.Room;
import net.minestom.server.command.builder.Command;
import net.minestom.server.entity.Player;
public class LeaveCommand extends Command {
public LeaveCommand() {
super("leave");
setCondition((sender, commandString) -> ((Player) sender).getInstance() instanceof Game);
setDefaultExecutor((sender, context) -> Room.setOwnRoom((Player) sender));
}
}

View File

@ -0,0 +1,18 @@
package eu.mhsl.minenet.minigames.command.anonymous;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.PlayerSkin;
public class SkinCommand extends Command {
public SkinCommand() {
super("skin");
addSyntax((sender, context) -> {
if(sender instanceof Player p) {
p.setSkin(PlayerSkin.fromUsername(context.getRaw("target")));
}
}, ArgumentType.Entity("target").onlyPlayers(true).onlyPlayers(true));
}
}

View File

@ -0,0 +1,45 @@
package eu.mhsl.minenet.minigames.command.privileged;
import eu.mhsl.minenet.minigames.command.PrivilegedCommand;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.type.ActionBarMessage;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import eu.mhsl.minenet.minigames.message.type.TitleMessage;
import eu.mhsl.minenet.minigames.util.PacketUtil;
import net.minestom.server.entity.Player;
import java.util.ArrayList;
import java.util.List;
public class DebugCommand extends PrivilegedCommand {
public DebugCommand() {
super("debug");
setDefaultExecutor((sender, args) -> {
new ChatMessage(Icon.CHAT).appendTranslated("sample").send(sender);
new ActionBarMessage().appendTranslated("sample").send(sender);
new TitleMessage().subtitle(subtitleMessage -> subtitleMessage.appendTranslated("sample")).appendTranslated("sample").send(sender);
List<String> testplayers = new ArrayList<>() {
{
add("MineTec");
add("Goldi187");
add("Test");
}
};
new ChatMessage(Icon.STAR, true)
.appendTranslated("score#result")
.newLine()
.indent()
.numberedList(testplayers)
.undent()
.newLine()
.appendTranslated("score#thanks")
.send(sender);
PacketUtil.resendPlayerList(((Player) sender));
new ChatMessage(Icon.SCIENCE).appendStatic(((Player) sender).getUuid().toString()).send(sender);
});
}
}

View File

@ -0,0 +1,33 @@
package eu.mhsl.minenet.minigames.command.privileged;
import eu.mhsl.minenet.minigames.command.PrivilegedCommand;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.fakeplayer.FakePlayer;
import net.minestom.server.entity.fakeplayer.FakePlayerOption;
import java.util.UUID;
public class FakeplayerCommand extends PrivilegedCommand {
public FakeplayerCommand() {
super("fakeplayer");
addSyntax((sender, context) -> {
if(sender instanceof Player p) {
if(p.getInstance() instanceof Room room) {
FakePlayer.initPlayer(
UUID.randomUUID(),
context.getRaw("name"),
new FakePlayerOption().setInTabList(true).setRegistered(true),
fakePlayer -> Room.setRoom(fakePlayer, room)
);
} else {
new ChatMessage(Icon.ERROR).appendStatic("Du musst dich in einer Raumlobby befinden!").send(sender);
}
}
}, ArgumentType.String("name"));
}
}

View File

@ -0,0 +1,18 @@
package eu.mhsl.minenet.minigames.command.privileged;
import eu.mhsl.minenet.minigames.command.PrivilegedCommand;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Player;
public class FlyCommand extends PrivilegedCommand {
public FlyCommand() {
super("fly");
setDefaultExecutor((sender, context) -> {
Player p = (Player) sender;
p.setVelocity(new Vec(0, 5, 0));
p.setFlying(!p.isFlying());
p.setAllowFlying(!p.isAllowFlying());
});
}
}

View File

@ -0,0 +1,17 @@
package eu.mhsl.minenet.minigames.command.privileged;
import eu.mhsl.minenet.minigames.command.PrivilegedCommand;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import net.minestom.server.entity.Player;
public class GameStartCommand extends PrivilegedCommand {
public GameStartCommand() {
super("gameStart");
setDefaultExecutor((sender, context) -> {
Player player = (Player) sender;
if(player.getInstance() instanceof StatelessGame game) {
game.startAccessor();
}
});
}
}

View File

@ -0,0 +1,17 @@
package eu.mhsl.minenet.minigames.command.privileged;
import eu.mhsl.minenet.minigames.command.PrivilegedCommand;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import net.minestom.server.entity.Player;
public class GameStopCommand extends PrivilegedCommand {
public GameStopCommand() {
super("gameStop");
setDefaultExecutor((sender, context) -> {
Player player = (Player) sender;
if(player.getInstance() instanceof StatelessGame game) {
game.stop();
}
});
}
}

View File

@ -0,0 +1,22 @@
package eu.mhsl.minenet.minigames.command.privileged;
import eu.mhsl.minenet.minigames.command.PrivilegedCommand;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.command.builder.arguments.number.ArgumentInteger;
import net.minestom.server.entity.Player;
public class GameTimeoutCommand extends PrivilegedCommand {
public GameTimeoutCommand() {
super("gameTimeout");
ArgumentInteger timeout = ArgumentType.Integer("timeout");
addSyntax((sender, context) -> {
Player player = (Player) sender;
if(player.getInstance() instanceof StatelessGame game) {
game.setTimeLimit(context.get(timeout));
}
}, timeout);
}
}

View File

@ -0,0 +1,19 @@
package eu.mhsl.minenet.minigames.command.privileged;
import eu.mhsl.minenet.minigames.command.PrivilegedCommand;
import net.minestom.server.command.builder.arguments.ArgumentEnum;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
public class GamemodeCommand extends PrivilegedCommand {
public GamemodeCommand() {
super("gamemode", "gm");
addSyntax((sender, context) -> ((Player) sender).setGameMode(
context.get("target")),
ArgumentType.Enum("target", GameMode.class).setFormat(ArgumentEnum.Format.LOWER_CASED)
);
}
}

View File

@ -0,0 +1,33 @@
package eu.mhsl.minenet.minigames.command.privileged;
import eu.mhsl.minenet.minigames.command.PrivilegedCommand;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.util.Monitoring;
public class GcCommand extends PrivilegedCommand {
private static long lastRun = System.currentTimeMillis();
public GcCommand() {
super("gc");
setDefaultExecutor((sender, context) -> {
long nextRun = (lastRun - (System.currentTimeMillis() - 30*1000)) / 1000;
if(nextRun > 0) {
new ChatMessage(Icon.ERROR).appendStatic("Please wait ").appendStatic(String.valueOf(nextRun)).appendStatic(" seconds before running GC again!").send(sender);
return;
}
lastRun = System.currentTimeMillis();
long before = Monitoring.getRamUsage();
System.gc();
long after = Monitoring.getRamUsage();
new ChatMessage(Icon.SUCCESS).appendStatic("Garbage collector ran successfully!").newLine()
.appendStatic("before: ").appendStatic(String.valueOf(before)).appendStatic("MB").newLine()
.appendStatic("now: ").appendStatic(String.valueOf(after)).appendStatic("MB").newLine()
.appendStatic("difference: ").appendStatic(String.valueOf(before-after)).appendStatic("MB")
.send(sender);
});
}
}

View File

@ -0,0 +1,24 @@
package eu.mhsl.minenet.minigames.command.privileged;
import eu.mhsl.minenet.minigames.command.PrivilegedCommand;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.util.PluginMessageUtil;
import net.minestom.server.command.builder.arguments.ArgumentWord;
import net.minestom.server.entity.Player;
import net.minestom.server.instance.Instance;
public class InstanceProxyMoveCommand extends PrivilegedCommand {
public InstanceProxyMoveCommand() {
super("instanceProxyMove");
ArgumentWord serverArgument = new ArgumentWord("server");
addSyntax((sender, context) -> {
Instance room = ((Player) sender).getInstance();
room.getPlayers().forEach(player -> {
Room.unsetRoom(player);
PluginMessageUtil.connect(player, context.get(serverArgument));
});
}, serverArgument);
}
}

View File

@ -0,0 +1,32 @@
package eu.mhsl.minenet.minigames.command.privileged;
import eu.mhsl.minenet.minigames.command.PrivilegedCommand;
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.entity.Player;
import java.util.Objects;
public class KickCommand extends PrivilegedCommand {
public KickCommand() {
super("kick");
addSyntax(
(sender, context) ->
kick(context.getRaw("player"), ""),
ArgumentType.Entity("player").onlyPlayers(true)
);
addSyntax(
(sender, context) ->
kick(context.getRaw("player"), context.getRaw("reason")),
ArgumentType.Entity("player").onlyPlayers(true),
ArgumentType.String("reason")
);
}
private void kick(String playername, String reason) {
Player playerToKick = MinecraftServer.getConnectionManager().findPlayer(playername);
Objects.requireNonNull(playerToKick).kick(reason);
}
}

View File

@ -0,0 +1,26 @@
package eu.mhsl.minenet.minigames.command.privileged;
import eu.mhsl.minenet.minigames.command.PrivilegedCommand;
import eu.mhsl.minenet.minigames.lang.Languages;
import eu.mhsl.minenet.minigames.lang.Lang;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.TranslatableMessage;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.entity.Player;
public class LangTestCommand extends PrivilegedCommand {
public LangTestCommand() {
super("langtest");
setDefaultExecutor((sender, context) -> sendMessage(Languages.getInstance().getLanguage((Player) sender), "sample").send(sender));
var targetString = ArgumentType.String("mapId");
addSyntax((sender, context) -> sendMessage(Languages.getInstance().getLanguage((Player) sender), context.get("mapId")).send(sender), targetString);
}
private TranslatableMessage sendMessage(Lang lang, String mapId) {
return new ChatMessage(Icon.SCIENCE).appendStatic(lang.getLangId()).newLine().appendTranslated(lang.getEntry(mapId));
}
}

View File

@ -0,0 +1,23 @@
package eu.mhsl.minenet.minigames.command.privileged;
import eu.mhsl.minenet.minigames.command.PrivilegedCommand;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.entity.Player;
import net.minestom.server.permission.Permission;
public class OpCommand extends PrivilegedCommand {
public OpCommand() {
super("op");
addSyntax((sender, context) -> {
Player target = MinecraftServer.getConnectionManager().getPlayer(context.getRaw("target"));
if(target != null) {
target.addPermission(new Permission("admin"));
target.refreshCommands();
} else new ChatMessage(Icon.ERROR).appendStatic("Spieler nicht gefunden").send(sender);
}, ArgumentType.Entity("target").onlyPlayers(true));
}
}

View File

@ -0,0 +1,17 @@
package eu.mhsl.minenet.minigames.command.privileged;
import eu.mhsl.minenet.minigames.command.PrivilegedCommand;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.command.builder.arguments.number.ArgumentInteger;
public class PlayerLimitCommand extends PrivilegedCommand {
public PlayerLimitCommand() {
super("playerLimit");
ArgumentInteger count = ArgumentType.Integer("count");
addSyntax((sender, context) -> {
System.setProperty("minenet.playerlimit", String.valueOf(context.get(count)));
}, count);
}
}

View File

@ -0,0 +1,39 @@
package eu.mhsl.minenet.minigames.command.privileged;
import com.google.gson.Gson;
import eu.mhsl.minenet.minigames.command.PrivilegedCommand;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.score.tournament.TournamentDisplay;
import net.minestom.server.entity.Player;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class PublishRewardCommand extends PrivilegedCommand {
private final HttpClient rewardPublishClient = HttpClient.newHttpClient();
public PublishRewardCommand() {
super("publishReward");
setDefaultExecutor((sender, context) -> {
try {
Room room = Room.getRoom((Player) sender).orElseThrow();
TournamentDisplay world = new TournamentDisplay(room.getTournament());
room.moveMembersToInstance(world);
String rewardRequestJson = new Gson().toJson(room.getTournament().getRewards());
HttpRequest giveRewardsRequest = HttpRequest.newBuilder()
.uri(new URI("http://10.20.6.1:8080/api/event/reward"))
.POST(HttpRequest.BodyPublishers.ofString(rewardRequestJson))
.build();
room.getTournament().getRewards();
HttpResponse<Void> rawResponse = rewardPublishClient.send(giveRewardsRequest, HttpResponse.BodyHandlers.discarding());
sender.sendMessage(String.format("Rewards published: HTTP %s", rawResponse.statusCode()));
} catch (Exception e) {
sender.sendMessage(e.getMessage());
}
});
}
}

View File

@ -0,0 +1,18 @@
package eu.mhsl.minenet.minigames.command.privileged;
import eu.mhsl.minenet.minigames.command.PrivilegedCommand;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import eu.mhsl.minenet.minigames.message.Icon;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
public class RefreshCommandsCommand extends PrivilegedCommand {
public RefreshCommandsCommand() {
super("refreshCommands");
setDefaultExecutor((sender, context) -> {
MinecraftServer.getConnectionManager().getOnlinePlayers().forEach(Player::refreshCommands);
new ChatMessage(Icon.SUCCESS).appendStatic("Updated command syntax!").send(sender);
});
}
}

View File

@ -0,0 +1,31 @@
package eu.mhsl.minenet.minigames.command.privileged;
import eu.mhsl.minenet.minigames.command.PrivilegedCommand;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.TranslatableMessage;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import eu.mhsl.minenet.minigames.instance.room.Room;
import net.minestom.server.entity.Player;
import java.util.stream.Collectors;
public class RoomCommand extends PrivilegedCommand {
public RoomCommand() {
super("room");
setDefaultExecutor((sender, context) -> {
TranslatableMessage out = new ChatMessage(Icon.SCIENCE).appendStatic("Rooms:").newLine();
Room.getAllRooms().forEach((roomInstance) -> out
.newLine()
.appendStatic("Owner: ").appendStatic(roomInstance.getOwner() != null ? roomInstance.getOwner().getUsername() : "none").newLine()
.appendStatic("Players: ").appendStatic(String.valueOf(roomInstance.getAllMembers().size())).newLine()
.list(roomInstance.getAllMembers().stream().map(Player::getUsername).collect(Collectors.toList())).newLine());
out.send(sender);
});
}
}

View File

@ -0,0 +1,57 @@
package eu.mhsl.minenet.minigames.command.privileged;
import eu.mhsl.minenet.minigames.command.PrivilegedCommand;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.score.tournament.MemorialConfiguration;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.command.builder.arguments.ArgumentString;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.command.builder.suggestion.SuggestionEntry;
import net.minestom.server.entity.Player;
import net.minestom.server.item.Material;
public class SetMemorialCommand extends PrivilegedCommand {
public SetMemorialCommand() {
super("setMemorial");
ArgumentString materialArgument = ArgumentType.String("material");
ArgumentString titleArgument = ArgumentType.String("title");
ArgumentString loreArgument = ArgumentType.String("lore");
materialArgument.setSuggestionCallback((sender, context, suggestion) -> {
Material
.values()
.stream()
.map(material -> new SuggestionEntry(material.name(), Component.text(material.name())))
.forEach(suggestion::addEntry);
});
addSyntax((sender, context) -> {
Room
.getRoom((Player) sender)
.orElseThrow()
.getTournament()
.setMemorialConfiguration(
new MemorialConfiguration(
Material.fromNamespaceId(context.get(materialArgument)),
context.get(titleArgument),
context.get(loreArgument)
)
);
sender.sendMessage(
Component.text()
.append(Component.text("Memorial gesetzt:", NamedTextColor.GOLD))
.appendNewline()
.append(Component.text(context.get(materialArgument), NamedTextColor.AQUA))
.appendNewline()
.append(Component.text(context.get(titleArgument), NamedTextColor.RED))
.appendNewline()
.append(Component.text(context.get(loreArgument), NamedTextColor.DARK_RED))
.build()
);
}, materialArgument, titleArgument, loreArgument);
}
}

View File

@ -0,0 +1,56 @@
package eu.mhsl.minenet.minigames.command.privileged;
import eu.mhsl.minenet.minigames.command.PrivilegedCommand;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.score.tournament.RewardConfiguration;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.command.builder.arguments.ArgumentStringArray;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.command.builder.arguments.ArgumentWord;
import net.minestom.server.command.builder.suggestion.SuggestionEntry;
import net.minestom.server.entity.Player;
import net.minestom.server.item.Material;
import java.util.Arrays;
import java.util.stream.Collectors;
public class SetRewardCommand extends PrivilegedCommand {
public SetRewardCommand() {
super("setRewards");
ArgumentWord materialArgument = ArgumentType.Word("material");
ArgumentStringArray amountsArgument = ArgumentType.StringArray("amount");
materialArgument.setSuggestionCallback((sender, context, suggestion) -> {
Material
.values()
.stream()
.map(material -> new SuggestionEntry(material.name(), Component.text(material.name())))
.forEach(suggestion::addEntry);
});
addSyntax((sender, context) -> {
Room
.getRoom((Player) sender)
.orElseThrow()
.getTournament()
.setRewardConfiguration(
new RewardConfiguration(
Material.fromNamespaceId(context.get(materialArgument)),
Arrays.stream(context.get(amountsArgument)).map(Integer::valueOf).collect(Collectors.toList())
)
);
sender.sendMessage(
Component.text()
.append(Component.text("Belohnung gesetzt:", NamedTextColor.GOLD))
.appendNewline()
.append(Component.text(context.get(materialArgument), NamedTextColor.AQUA))
.appendNewline()
.append(Component.text(String.join(", ", context.get(amountsArgument)), NamedTextColor.RED))
.build()
);
}, materialArgument, amountsArgument);
}
}

View File

@ -0,0 +1,44 @@
package eu.mhsl.minenet.minigames.command.privileged;
import eu.mhsl.minenet.minigames.command.PrivilegedCommand;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.command.builder.condition.CommandCondition;
import net.minestom.server.entity.Player;
import java.util.Objects;
public class SetRoomOwnerCommand extends PrivilegedCommand {
public SetRoomOwnerCommand() {
super("setRoomOwner");
addCondition((sender, commandString) -> ((Player) sender).getInstance() instanceof Room);
setDefaultExecutor((sender, context) -> {
if(sender instanceof Player p) {
Room.getRoom(p).orElseThrow().setOwner(p);
new ChatMessage(Icon.SUCCESS).appendStatic("You are now the owner of this room!").send(sender);
}
});
addSyntax((sender, context) -> {
System.out.println("Test");
if(sender instanceof Player p) {
Player newOwner = MinecraftServer.getConnectionManager().getPlayer(context.getRaw("player"));
Room.getRoom(p).orElseThrow().setOwner(Objects.requireNonNull(newOwner));
new ChatMessage(Icon.SUCCESS).appendStatic("The new owner has been set!").send(sender);
}
}, ArgumentType.Entity("player").onlyPlayers(true));
}
@Override
protected CommandCondition isPrivileged() {
return (sender, commandString) -> {
Room playerRoom = Room.getRoom(((Player) sender)).orElse(null);
return super.isPrivileged().canUse(sender, commandString) || (playerRoom != null && playerRoom.getOwner() == sender);
};
}
}

View File

@ -0,0 +1,16 @@
package eu.mhsl.minenet.minigames.handler;
import eu.mhsl.minenet.minigames.handler.global.*;
import net.minestom.server.MinecraftServer;
import net.minestom.server.event.EventListener;
public enum Listeners {
SPAWN(new AddEntityToInstanceEventListener()),
CHAT(new PlayerChatHandler()),
LOGIN(new PlayerLoginHandler()),
LEAVE(new PlayerLeaveHandler());
Listeners(EventListener<?> event) {
MinecraftServer.getGlobalEventHandler().addListener(event);
}
}

View File

@ -0,0 +1,36 @@
package eu.mhsl.minenet.minigames.handler.global;
import eu.mhsl.minenet.minigames.instance.Spawnable;
import eu.mhsl.minenet.minigames.util.PacketUtil;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
import net.minestom.server.event.EventListener;
import net.minestom.server.event.instance.AddEntityToInstanceEvent;
import net.minestom.server.potion.Potion;
import net.minestom.server.potion.PotionEffect;
import net.minestom.server.timer.ExecutionType;
import org.jetbrains.annotations.NotNull;
public class AddEntityToInstanceEventListener implements EventListener<AddEntityToInstanceEvent> {
@Override
public @NotNull Class eventType() {
return AddEntityToInstanceEvent.class;
}
@Override
public @NotNull Result run(@NotNull AddEntityToInstanceEvent event) {
if(event.getEntity() instanceof Player p) {
MinecraftServer.getSchedulerManager().scheduleNextTick(p::refreshCommands, ExecutionType.ASYNC);
if(event.getInstance() instanceof Spawnable instance) {
p.setRespawnPoint(instance.getSpawn());
}
PacketUtil.resendPlayerList(p);
p.addEffect(new Potion(PotionEffect.BLINDNESS, (byte) 1, 20)); //TODO Uncomment, currently buggy causes disconnect see https://github.com/Minestom/Minestom/discussions/1302
}
return Result.SUCCESS;
}
}

View File

@ -0,0 +1,26 @@
package eu.mhsl.minenet.minigames.handler.global;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import net.minestom.server.event.EventListener;
import net.minestom.server.event.player.PlayerChatEvent;
import org.jetbrains.annotations.NotNull;
public class PlayerChatHandler implements EventListener<PlayerChatEvent> {
@Override
public @NotNull Class<PlayerChatEvent> eventType() {
return PlayerChatEvent.class;
}
@Override
public @NotNull Result run(@NotNull PlayerChatEvent event) {
event.setChatFormat(
(messages) -> new ChatMessage()
.appendStatic(event.getPlayer().getUsername())
.pipe()
.appendStatic(messages.getMessage())
.build(event.getPlayer())
);
return Result.SUCCESS;
}
}

View File

@ -0,0 +1,20 @@
package eu.mhsl.minenet.minigames.handler.global;
import net.minestom.server.entity.Player;
import net.minestom.server.event.EventListener;
import net.minestom.server.event.player.PlayerDisconnectEvent;
import org.jetbrains.annotations.NotNull;
public class PlayerLeaveHandler implements EventListener<PlayerDisconnectEvent> {
@Override
public @NotNull Class<PlayerDisconnectEvent> eventType() {
return PlayerDisconnectEvent.class;
}
@Override
public @NotNull Result run(@NotNull PlayerDisconnectEvent event) {
Player p = event.getPlayer();
// new ChatMessage(Icon.SCIENCE).appendStatic("unübersetzter Leavetext: ").appendStatic(p.getDisplayName()).send(MinecraftServer.getConnectionManager().getOnlinePlayers());
return Result.SUCCESS;
}
}

View File

@ -0,0 +1,65 @@
package eu.mhsl.minenet.minigames.handler.global;
import eu.mhsl.minenet.minigames.Main;
import eu.mhsl.minenet.minigames.api.QueuedPlayerRooms;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.instance.transfer.Transfer;
import eu.mhsl.minenet.minigames.skin.SkinCache;
import eu.mhsl.minenet.minigames.util.MoveInstance;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
import net.minestom.server.event.EventListener;
import net.minestom.server.event.player.PlayerLoginEvent;
import eu.mhsl.minenet.minigames.instance.hub.Hub;
import net.minestom.server.permission.Permission;
import net.minestom.server.timer.TaskSchedule;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.configurate.serialize.SerializationException;
import java.util.Objects;
import java.util.UUID;
import java.util.logging.Logger;
public class PlayerLoginHandler implements EventListener<PlayerLoginEvent> {
@Override
public @NotNull Class<PlayerLoginEvent> eventType() {
return PlayerLoginEvent.class;
}
@Override
public @NotNull Result run(@NotNull PlayerLoginEvent event) {
Player p = event.getPlayer();
Transfer transferInstance = new Transfer();
p.setRespawnPoint(transferInstance.getSpawn());
event.setSpawningInstance(transferInstance);
UUID pushQueue = QueuedPlayerRooms.pullQueue(event.getPlayer().getUuid());
MinecraftServer.getSchedulerManager().scheduleTask(
() -> {
if(pushQueue != null) {
Room.setRoom(p, Room.getRoom(pushQueue).orElseThrow());
} else {
MoveInstance.move(p, Hub.INSTANCE);
}
},
TaskSchedule.seconds(5),
TaskSchedule.stop()
);
SkinCache.applySkin(p);
try {
if(Objects.requireNonNull(Main.globalConfig.node("admins").getList(String.class)).stream().anyMatch(s -> s.equalsIgnoreCase(p.getUsername()))) {
p.addPermission(new Permission("admin"));
}
} catch (SerializationException | NullPointerException ignored) {}
Logger.getLogger("user").info(p.getUsername() + " joined");
// new ChatMessage(Icon.SCIENCE).appendStatic("unübersetzter Jointext: ").appendStatic(p.getUsername()).send(MinecraftServer.getConnectionManager().getOnlinePlayers());
return Result.SUCCESS;
}
}

View File

@ -0,0 +1,41 @@
package eu.mhsl.minenet.minigames.instance;
import net.minestom.server.MinecraftServer;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.world.DimensionType;
/**
* Prebuilt dimensions
*/
public enum Dimension {
OVERWORLD(
DimensionType
.builder(NamespaceID.from("minenet:fullbright_overworld"))
.ambientLight(2.0f)
.build()
),
NETHER(
DimensionType
.builder(NamespaceID.from("minenet:fullbright_nether"))
.ambientLight(2.0f)
.effects("minecraft:the_nether")
.build()
),
THE_END(
DimensionType
.builder(NamespaceID.from("minenet:fullbright_end"))
.ambientLight(2.0f)
.effects("minecraft:the_end")
.build()
);
public final DimensionType DIMENSION;
Dimension(DimensionType dimType) {
this.DIMENSION = dimType;
MinecraftServer.getDimensionTypeManager().addDimension(this.DIMENSION);
}
}

View File

@ -0,0 +1,72 @@
package eu.mhsl.minenet.minigames.instance;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
import net.minestom.server.event.instance.AddEntityToInstanceEvent;
import net.minestom.server.event.instance.RemoveEntityFromInstanceEvent;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.InstanceContainer;
import net.minestom.server.timer.TaskSchedule;
import net.minestom.server.world.DimensionType;
import java.util.UUID;
public class MineNetInstance extends InstanceContainer {
public MineNetInstance(DimensionType type) {
super(UUID.randomUUID(), type);
MinecraftServer.getInstanceManager().registerInstance(this);
eventNode()
.addListener(AddEntityToInstanceEvent.class, this::onEntityAdd)
.addListener(RemoveEntityFromInstanceEvent.class, this::onEntityRemove);
}
private void onEntityRemove(RemoveEntityFromInstanceEvent removeEntityFromInstanceEvent) {
if(removeEntityFromInstanceEvent.getEntity() instanceof Player p) {
this.onPlayerLeave(p);
}
}
private void onEntityAdd(AddEntityToInstanceEvent addEntityToInstanceEvent) {
if(addEntityToInstanceEvent.getEntity() instanceof Player p) {
addEntityToInstanceEvent.setCancelled(this.onPlayerJoin(p));
}
}
/**
* Called when Player joins this instance
* @param p player who is joining
* @return setCanceled
*/
protected boolean onPlayerJoin(Player p) {
return false;
}
/**
* Called when Player leaves this instance
* @param p player who is leaving
*/
protected void onPlayerLeave(Player p) {
}
/**
*
* @param target
*/
public void destroy(Instance target) {
getPlayers().forEach(player -> {
if(target != null)
player.setInstance(target);
else
player.kick(TranslatedComponent.byId("sample").getAssembled(player));
});
MinecraftServer.getSchedulerManager().scheduleTask(
() -> MinecraftServer.getInstanceManager().unregisterInstance(this),
TaskSchedule.seconds(10),
TaskSchedule.stop()
);
}
}

View File

@ -0,0 +1,7 @@
package eu.mhsl.minenet.minigames.instance;
import net.minestom.server.coordinate.Pos;
public interface Spawnable {
Pos getSpawn();
}

View File

@ -0,0 +1,159 @@
package eu.mhsl.minenet.minigames.instance.game;
import eu.mhsl.minenet.minigames.instance.MineNetInstance;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import eu.mhsl.minenet.minigames.score.Score;
import eu.mhsl.minenet.minigames.util.CommonEventHandles;
import eu.mhsl.minenet.minigames.instance.Spawnable;
import eu.mhsl.minenet.minigames.instance.room.Room;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Player;
import net.minestom.server.event.item.ItemDropEvent;
import net.minestom.server.event.player.PlayerBlockBreakEvent;
import net.minestom.server.event.player.PlayerBlockPlaceEvent;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.timer.ExecutionType;
import net.minestom.server.timer.TaskSchedule;
import net.minestom.server.world.DimensionType;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Logger;
public abstract class Game extends MineNetInstance implements Spawnable {
protected Room parentRoom;
protected boolean isRunning = false;
protected boolean isBeforeBeginning = true;
protected final Random rnd = new Random(); //TODO better way than ths?
protected final Logger logger;
public Game(DimensionType dimensionType) {
super(dimensionType);
MinecraftServer.getInstanceManager().registerInstance(this);
logger = Logger.getLogger("Game:" + getUniqueId());
eventNode()
.addListener(PlayerMoveEvent.class, this::onPlayerMove)
.addListener(PlayerBlockBreakEvent.class, this::onBlockBreak)
.addListener(PlayerBlockPlaceEvent.class, this::onBlockPlace)
.addListener(ItemDropEvent.class, this::onItemDrop);
}
public Game setParent(Room parentRoom) {
this.parentRoom = parentRoom;
return this;
}
public static void initialize(GameFactory factory, List<Option<?>> options, Player owner) {
try {
Game game = factory.manufacture(Room.getRoom(owner).orElseThrow(), options);
game.load();
Room.getRoom(owner).orElseThrow().moveMembersToInstance(game);
} catch (Exception e) {
new ChatMessage(Icon.ERROR).appendStatic("Instance crashed: " + e.getMessage()).send(owner);
MinecraftServer.getSchedulerManager().scheduleNextTick(() -> Room.getRoom(owner).orElseThrow().moveMembersToRoomLobby());
e.printStackTrace(System.err);
}
}
/**
* Load and start countdown
*/
public void load() {
scheduler().submitTask(() -> {
CompletableFuture<Void> callback = new CompletableFuture<>();
this.onLoad(callback);
// callback.whenComplete((unused, throwable) -> this.start());
return TaskSchedule.stop();
}, ExecutionType.ASYNC);
}
protected void start() {
isRunning = true;
isBeforeBeginning = false;
this.onStart();
}
public void stop() {
isRunning = false;
this.onStop();
this.unload();
}
public void unload() {
this.onUnload();
getPlayers().forEach(Room::setOwnRoom);
scheduler().scheduleTask(() -> {
logger.info("stopping game instance " + this.uniqueId);
getPlayers().forEach(player -> player.kick("timeout"));
MinecraftServer.getInstanceManager().unregisterInstance(this);
}, TaskSchedule.seconds(10), TaskSchedule.stop());
}
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
callback.complete(null);
}
protected void onStart() {}
protected void onStop() {}
protected void onUnload() {}
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
}
protected void onBlockBreak(@NotNull PlayerBlockBreakEvent playerBlockBreakEvent) {
playerBlockBreakEvent.setCancelled(true);
}
protected void onBlockPlace(@NotNull PlayerBlockPlaceEvent playerBlockPlaceEvent) {
playerBlockPlaceEvent.setCancelled(true);
}
@Override
protected void onPlayerLeave(Player p) {
this.checkAbandoned();
}
protected void onItemDrop(@NotNull ItemDropEvent itemDropEvent) {
CommonEventHandles.cancel(itemDropEvent);
}
protected void checkAbandoned() {
scheduleNextTick((instance) -> {
if(instance.getPlayers().isEmpty()) this.unload();
});
}
protected void publishScore(Score score) {
this.parentRoom.getTournament().addScore(score);
}
public boolean isRunning() {
return isRunning;
}
public Pos getSpawn() {
return new Pos(0,50,0);
}
}

View File

@ -0,0 +1,44 @@
package eu.mhsl.minenet.minigames.instance.game;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.bowSpleef.BowSpleefFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.elytraRace.ElytraRaceFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms.BackroomsFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.bedwars.BedwarsFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.acidRain.AcidRainFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.deathcube.DeathcubeFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.minerun.MinerunFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.spleef.SpleefFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.stickfight.StickFightFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.tntrun.TntRunFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.TowerdefenseFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.trafficlightrace.TrafficLightRaceFactory;
public enum GameList {
DEATHCUBE(new DeathcubeFactory(), GameType.JUMPNRUN),
STICKFIGHT(new StickFightFactory(), GameType.PVP),
MINERUN(new MinerunFactory(), GameType.JUMPNRUN),
TRAFFICLIGHTRACE(new TrafficLightRaceFactory(), GameType.OTHER),
TOWERDEFENSE(new TowerdefenseFactory(), GameType.PROTOTYPE),
BEDWARS(new BedwarsFactory(), GameType.PROTOTYPE),
BACKROOMS(new BackroomsFactory(), GameType.PROTOTYPE),
TNTRUN(new TntRunFactory(), GameType.OTHER),
ACIDRAIN(new AcidRainFactory(), GameType.PVE),
ELYTRARACE(new ElytraRaceFactory(), GameType.PVP),
SPLEEF(new SpleefFactory(), GameType.PVP),
BOWSPLEEF(new BowSpleefFactory(), GameType.PVP);
private final GameFactory factory;
private final GameType type;
GameList(GameFactory factory, GameType type) {
this.factory = factory;
this.type = type;
}
public GameFactory getFactory() {
return this.factory;
}
public GameType getType() {
return type;
}
}

View File

@ -0,0 +1,37 @@
package eu.mhsl.minenet.minigames.instance.game;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.minestom.server.item.Material;
public enum GameType {
OTHER(Material.GRASS_BLOCK, TranslatedComponent.byId("GameType#other"), TranslatedComponent.byId("GameType#other_description")),
PVP(Material.DIAMOND_SWORD, TranslatedComponent.byId("GameType#pvp"), TranslatedComponent.byId("GameType#pvp_description")),
PVE(Material.DIAMOND_PICKAXE, TranslatedComponent.byId("GameType#pve"), TranslatedComponent.byId("GameType#pve_description")),
JUMPNRUN(Material.DIAMOND_BOOTS, TranslatedComponent.byId("GameType#jumpnrun"), TranslatedComponent.byId("GameType#jumpnrun_description")),
PROTOTYPE(Material.COMMAND_BLOCK, TranslatedComponent.byId("GameType#prototype"), TranslatedComponent.byId("GameType#prototype_description"));
final Material icon;
final TranslatedComponent title;
final TranslatedComponent description;
GameType(Material icon, TranslatedComponent title, TranslatedComponent description) {
this.title = title;
this.description = description;
this.icon = icon;
}
public Material getIcon() {
return icon;
}
public TranslatedComponent getTitle() {
return title;
}
public TranslatedComponent getDescription() {
return description;
}
}

View File

@ -0,0 +1,105 @@
package eu.mhsl.minenet.minigames.instance.game.stateless;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.message.Countdown;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import eu.mhsl.minenet.minigames.message.type.TitleMessage;
import eu.mhsl.minenet.minigames.score.Score;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.timer.ExecutionType;
import net.minestom.server.timer.TaskSchedule;
import net.minestom.server.world.DimensionType;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
public class StatelessGame extends Game {
private final String name;
private final Score score;
private int timeLimit = 0;
private int timePlayed = 0;
public StatelessGame(DimensionType dimensionType, String gameName, Score score) {
super(dimensionType);
this.score = score;
this.name = gameName;
}
public Score getScore() {
return this.score;
}
public String getName() {
return name;
}
public void setTimeLimit(int limit) {
this.timeLimit = limit;
if(timeLimit > 0) {
scheduler().submitTask(() -> {
if(!isRunning || timeLimit == 0) return TaskSchedule.stop();
if(timeLimit <= timePlayed) {
stop();
return TaskSchedule.stop();
}
int timeLeft = timeLimit - timePlayed;
switch (timeLeft) {
case 60, 30, 10, 5, 4, 3, 2, 1 ->
new ChatMessage(Icon.SCIENCE).appendStatic("Noch " + timeLeft + " Sekunden!").send(getPlayers());
}
timePlayed++;
return TaskSchedule.seconds(1);
}, ExecutionType.SYNC);
}
}
@Override
public void load() {
super.load();
}
/**
* Displays countdown and starts the game
* When overriding make sure to call this::start after countdown!
*/
protected CompletableFuture<Void> countdownStart() {
return new Countdown(TitleMessage.class)
.countdown(Audience.audience(getPlayers()), 5, countdownModifier -> countdownModifier.message = new TitleMessage(Duration.ofMillis(300), Duration.ofMillis(700))
.subtitle(subtitleMessage -> subtitleMessage.appendStatic(Component.text("in ", NamedTextColor.DARK_GREEN))
.appendStatic(Component.text(countdownModifier.timeLeft, NamedTextColor.GREEN))
.appendStatic(Component.text(" seconds", NamedTextColor.DARK_GREEN))));
}
public void startAccessor() {
this.start();
}
@Override
protected void start() {
score.setInstance(this);
score.attachListeners();
countdownStart().thenRun(super::start);
}
@Override
public void stop() {
isRunning = false;
this.onStop();
getScore().insertRemainingPlayers(getPlayers());
this.publishScore(getScore());
countdownUnload();
}
private void countdownUnload() {
new TitleMessage(Duration.ofSeconds(1)).appendTranslated("score#done").send(getPlayers());
scheduler().scheduleTask(this::unload, TaskSchedule.seconds(5), TaskSchedule.stop());
}
}

View File

@ -0,0 +1,16 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.config;
import java.util.ArrayList;
public class ConfigManager {
private final ArrayList<Option<?>> items = new ArrayList<>();
public ConfigManager addOption(Option option) {
items.add(option);
return this;
}
public ArrayList<Option<?>> getAll() {
return items;
}
}

View File

@ -0,0 +1,168 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.config;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.Restriction;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.RestrictionData;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.RestrictionHandler;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import eu.mhsl.minenet.minigames.shared.inventory.InteractableInventory;
import eu.mhsl.minenet.minigames.util.InventoryItemAlignment;
import eu.mhsl.minenet.minigames.instance.room.Room;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
import net.minestom.server.event.EventListener;
import net.minestom.server.event.instance.AddEntityToInstanceEvent;
import net.minestom.server.event.instance.RemoveEntityFromInstanceEvent;
import net.minestom.server.inventory.InventoryType;
import net.minestom.server.inventory.click.ClickType;
import net.minestom.server.inventory.condition.InventoryConditionResult;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.sound.SoundEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
public class GameConfigurationInventory extends InteractableInventory {
private final Map<Integer, Option<?>> map = new HashMap<>();
final private Room room;
final private GameFactory factory;
final private Player p;
public GameConfigurationInventory(Room room, Player p, GameFactory factory) {
super(InventoryType.CHEST_5_ROW, factory.name().getAssembled(p));
this.room = room;
this.p = p;
this.factory = factory;
room.eventNode()
.addListener(AddEntityToInstanceEvent.class, addEntityToInstanceEvent -> MinecraftServer.getSchedulerManager().scheduleNextTick(this::updatePlayButton))
.addListener(RemoveEntityFromInstanceEvent.class, removeEntityFromInstanceEvent -> MinecraftServer.getSchedulerManager().scheduleNextTick(this::updatePlayButton));
ConfigManager config = factory.configuration();
setClickableItem(
ItemStack.builder(Material.RED_WOOL)
.displayName(
TranslatedComponent.byId("common#back")
.setColor(NamedTextColor.RED)
.getAssembled(p)
)
.build(),
0,
itemClick -> itemClick.getPlayer().closeInventory(),
true
);
setDummyItem(Material.BLACK_STAINED_GLASS_PANE,1);
setDummyItem(
ItemStack.builder(Material.NAME_TAG)
.displayName(
factory.name().setColor(NamedTextColor.GOLD).getAssembled(p)
)
.build(),
4
);
setDummyItem(Material.BLACK_STAINED_GLASS_PANE,7);
updatePlayButton();
for(int i = 9; i <= 17; i++) {
setDummyItem(Material.BLACK_STAINED_GLASS_PANE, i);
}
if(config == null) {
setDummyItem(
ItemStack.builder(Material.BARRIER)
.displayName(
TranslatedComponent.byId("room#noOption").setColor(NamedTextColor.RED).getAssembled(p)
)
.lore(
TranslatedComponent.assemble("room#noOptionDescription", p)
)
.build(),
31
);
} else {
int offset = 18;
InventoryItemAlignment itemAlignment = new InventoryItemAlignment(config.getAll().size(), 3);
for(Option<?> item : config.getAll()) {
InventoryItemAlignment.ItemOffset current = itemAlignment.next();
map.put(offset + current.get(), item);
setDummyItem(
item.getCurrent(p),
offset + current.get()
);
}
}
}
@Override
protected void onClick(Player player, int slot, ClickType clickType, InventoryConditionResult inventoryConditionResult) {
inventoryConditionResult.setCancel(true);
if(!map.containsKey(slot)) return;
Option<?> item = map.get(slot);
setDummyItem(
item.getNext(p),
slot
);
update();
}
private void updatePlayButton() {
RestrictionHandler restrictionHandler = factory.globalRestrictions();
RestrictionData restrictionData = new RestrictionData(room);
if(restrictionHandler.canPlay(restrictionData)) {
setClickableItem(
ItemStack.builder(restrictionHandler.getWarnings(restrictionData).size() > 0 ? Material.YELLOW_WOOL : Material.GREEN_WOOL)
.displayName(TranslatedComponent.byId("restriction#success").setColor(NamedTextColor.GREEN).getAssembled(p))
.lore(restrictionHandler.getWarnings(restrictionData).stream().map(translatedComponent -> translatedComponent.getAssembled(p)).collect(Collectors.toList()))
.build(),
8,
itemClick -> Game.initialize(factory, map.values().stream().toList(), itemClick.getPlayer()),
true
);
} else {
setClickableItem(
ItemStack.builder(Material.RED_WOOL)
.displayName(TranslatedComponent.byId("restriction#fail").setColor(NamedTextColor.RED).getAssembled(p))
.lore(
restrictionHandler.getRestrictions()
.stream()
.filter(iRestriction -> iRestriction.calculate(restrictionData).type().equals(Restriction.Type.FAIL))
.map(iRestriction -> iRestriction.calculate(restrictionData).description().getAssembled(p))
.collect(Collectors.toList()))
.build(),
8,
itemClick -> {
if(restrictionHandler.canPlay(restrictionData)) {
updatePlayButton();
return;
}
itemClick.getPlayer().playSound(Sound.sound(SoundEvent.ENTITY_SILVERFISH_DEATH, Sound.Source.AMBIENT, 1f, 1f));
},
false
);
}
update();
}
}

View File

@ -0,0 +1,45 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.config;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.RestrictionHandler;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.minestom.server.item.Material;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public interface GameFactory {
TranslatedComponent name();
default ConfigManager configuration() {
return null;
}
default RestrictionHandler globalRestrictions() {
return new RestrictionHandler();
}
default Material symbol() {
return Material.GRASS_BLOCK;
}
default TranslatedComponent description() {
return TranslatedComponent.byId("GameFactory#missingDescription");
}
Game manufacture(Room parent, Map<String, Option<?>> configuration) throws Exception;
default Game manufacture(Room parent, List<Option<?>> configuration) throws Exception {
if(configuration == null) return manufacture(parent);
Map<String, Option<?>> cnf = new HashMap<>();
configuration.forEach(option -> cnf.put(option.getId(), option));
return manufacture(parent, cnf);
}
default Game manufacture(Room parent) throws Exception {
if(this.configuration() == null) return manufacture(parent, List.of());
return manufacture(parent, this.configuration().getAll());
}
}

View File

@ -0,0 +1,66 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.config;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.RestrictionHandler;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.entity.Player;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import java.util.List;
public abstract class Option<T> {
private RestrictionHandler restrictionHandler;
private final Material item;
private final TranslatedComponent name;
private final String id;
protected T currentValue;
private final List<T> options;
private int pointer = 0;
public Option(String id, Material item, TranslatedComponent name, List<T> options) {
this.id = id;
this.item = item;
this.name = name;
this.options = options;
currentValue = options.get(0);
}
public void setRestrictionHandler(RestrictionHandler restrictionHandler) {
this.restrictionHandler = restrictionHandler;
}
public RestrictionHandler getRestrictionHandler() {
return restrictionHandler;
}
public ItemStack getNext(Player p) {
if(++pointer >= options.size()) pointer = 0;
currentValue = options.get(pointer);
return getCurrent(p);
}
public ItemStack getCurrent(Player p) {
int amount = Integer.parseInt(options.get(pointer).toString());
return ItemStack.builder(item)
.displayName(name.getAssembled(p))
.lore(TranslatedComponent.byId("optionCommon#value").setColor(NamedTextColor.GOLD).getAssembled(p)
.append(Component.text(": ")).append(Component.text(amount)))
.build();
}
public int getAsInt() {
return Integer.parseInt(getAsString());
}
public String getAsString() {
return currentValue.toString();
}
public String getId() {
return id;
}
}

View File

@ -0,0 +1,13 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.config.common;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.minestom.server.item.Material;
import java.util.List;
public class BoolOption extends Option<Boolean> {
public BoolOption(String id, Material item, TranslatedComponent name) {
super(id, item, name, List.of(true, false));
}
}

View File

@ -0,0 +1,14 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.config.common;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.minestom.server.item.Material;
import java.util.List;
public class NumericOption extends Option<Integer> {
public NumericOption(String id, Material item, TranslatedComponent name, Integer... options) {
super(id, item, name, List.of(options));
}
}

View File

@ -0,0 +1,6 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction;
public interface IRestriction {
Restriction calculate(RestrictionData data);
}

View File

@ -0,0 +1,24 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
public record Restriction(TranslatedComponent name, TranslatedComponent description, TranslatedComponent warnMessage,
eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.Restriction.Type type) {
public Restriction(TranslatedComponent name, TranslatedComponent description, TranslatedComponent warnMessage, Type type) {
this.name = name;
this.description = description;
this.warnMessage = warnMessage;
this.type = type;
if (this.warnMessage == null && this.type.equals(Type.WARN)) {
throw new IllegalStateException("warnMessage cannot be null when RestrictionType is WARN");
}
}
public enum Type {
OK,
WARN,
FAIL,
}
}

View File

@ -0,0 +1,6 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction;
import eu.mhsl.minenet.minigames.instance.room.Room;
public record RestrictionData(Room room) {
}

View File

@ -0,0 +1,44 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import java.util.ArrayList;
import java.util.List;
public class RestrictionHandler {
private List<IRestriction> restrictions = new ArrayList<>();
public RestrictionHandler(IRestriction... restrictions) {
this.restrictions = List.of(restrictions);
}
public RestrictionHandler() {
}
public RestrictionHandler addRestriction(IRestriction IRestriction) {
this.restrictions.add(IRestriction);
return this;
}
public List<IRestriction> getRestrictions() {
return restrictions;
}
public boolean canPlay(RestrictionData data) {
if(restrictions.size() < 1) return true;
return this.restrictions.stream()
.noneMatch(iRestriction -> iRestriction.calculate(data).type().equals(Restriction.Type.FAIL));
}
public List<TranslatedComponent> getWarnings(RestrictionData data) {
List<TranslatedComponent> warnings = new ArrayList<>();
for (IRestriction r : this.restrictions) {
Restriction calculated = r.calculate(data);
if(!calculated.type().equals(Restriction.Type.WARN)) continue;
warnings.add(calculated.warnMessage());
}
return warnings;
}
}

View File

@ -0,0 +1,24 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.common;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.IRestriction;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.Restriction;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.RestrictionData;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
public class MaximalPlayeramountGameRestriction implements IRestriction {
private final int max;
public MaximalPlayeramountGameRestriction(int max) {
this.max = max;
}
@Override
public Restriction calculate(RestrictionData data) {
return new Restriction(
TranslatedComponent.byId("restriction#maxPlayersInRoom"),
TranslatedComponent.byId("restriction#maxPlayersInRoomDescription"),
null,
data.room().getAllMembers().size() > this.max ? Restriction.Type.FAIL : Restriction.Type.OK
);
}
}

View File

@ -0,0 +1,24 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.common;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.IRestriction;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.Restriction;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.RestrictionData;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
public class MinimalPlayeramountGameRestriction implements IRestriction {
private final int min;
public MinimalPlayeramountGameRestriction(int min) {
this.min = min;
}
@Override
public Restriction calculate(RestrictionData data) {
return new Restriction(
TranslatedComponent.byId("restriction#minPlayersInRoom"),
TranslatedComponent.byId("restriction#minPlayersInRoomDescription"),
null,
data.room().getAllMembers().size() < this.min ? Restriction.Type.FAIL : Restriction.Type.OK
);
}
}

View File

@ -0,0 +1,124 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.acidRain;
import de.articdive.jnoise.JNoise;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.LastWinsScore;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.util.NumberUtil;
import eu.mhsl.minenet.minigames.util.WeatherUtils;
import eu.mhsl.minenet.minigames.world.BlockPallet;
import eu.mhsl.minenet.minigames.world.generator.terrain.CircularPlateTerrainGenerator;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.event.player.PlayerTickEvent;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.block.Block;
import net.minestom.server.particle.Particle;
import net.minestom.server.particle.ParticleCreator;
import net.minestom.server.timer.ExecutionType;
import net.minestom.server.timer.TaskSchedule;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.CompletableFuture;
public class AcidRain extends StatelessGame {
final private int radius = 20;
private int generationOffset = 0;
final private int roofHeight = 55;
private int difficulty = 0;
final JNoise noise = JNoise.newBuilder()
.fastSimplex()
.setSeed(rnd.nextLong())
.setFrequency(0.09)
.build();
public AcidRain() {
super(Dimension.OVERWORLD.DIMENSION, "acidRain", new LastWinsScore());
setGenerator(
new CircularPlateTerrainGenerator(radius)
.setPlateHeight(50)
.setPlatePallet(BlockPallet.STONE)
);
eventNode().addListener(PlayerTickEvent.class, this::onPlayerTick);
}
@Override
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
this.generatePlatform(0);
callback.complete(null);
}
@Override
protected void onStart() {
new WeatherUtils().startRain(this.getPlayers());
MinecraftServer.getSchedulerManager().submitTask(() -> {
getPlayers().forEach(player -> {
player.sendPacket(ParticleCreator.createParticlePacket(Particle.SNEEZE, 0, 60, 0, radius, radius, radius, 500));
player.sendPacket(ParticleCreator.createParticlePacket(Particle.ITEM_SLIME, 0, 60, 0, radius, radius, radius, 500));
});
if(!isRunning) return TaskSchedule.stop();
return TaskSchedule.millis(100);
}, ExecutionType.ASYNC);
MinecraftServer.getSchedulerManager().submitTask(() -> {
generationOffset++;
generatePlatform(generationOffset);
if(!isRunning) return TaskSchedule.stop();
return TaskSchedule.millis((long) NumberUtil.map(50 - difficulty, 0, 50, 100, 1000));
}, ExecutionType.ASYNC);
MinecraftServer.getSchedulerManager().submitTask(() -> {
difficulty++;
if(difficulty >= 50) return TaskSchedule.stop();
return TaskSchedule.seconds(3);
});
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
if(playerMoveEvent.getNewPosition().y() < 50) playerDeath(playerMoveEvent.getPlayer());
}
private void onPlayerTick(PlayerTickEvent playerTickEvent) {
if(isBeforeBeginning) return;
if(getBlock(playerTickEvent.getPlayer().getPosition().withY(roofHeight)).isAir()) playerDeath(playerTickEvent.getPlayer());
}
private void generatePlatform(long offset) {
int radius = this.radius + 20;
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
for(int x = -radius; x <= radius; x++) {
for(int z = -radius; z <= radius; z++) {
batch.setBlock(x, roofHeight, z, Block.AIR);
if(new Pos(x, 0, z).distance(new Pos(0, 0, 0)) > radius) continue;
if(noise.getNoise(x + offset, z + offset) > 0.4) continue;
batch.setBlock(x, roofHeight, z, Block.OAK_PLANKS);
}
}
BatchUtil.loadAndApplyBatch(batch, this, () -> {});
}
private void playerDeath(Player p) {
p.setGameMode(GameMode.SPECTATOR);
getScore().insertResult(p);
}
@Override
public Pos getSpawn() {
return new Pos(0, 51, 0);
}
}

View File

@ -0,0 +1,27 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.acidRain;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.minestom.server.item.Material;
import java.util.Map;
public class AcidRainFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("game_AcidRain#name");
}
@Override
public Material symbol() {
return Material.SLIME_BALL;
}
@Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) throws Exception {
return new AcidRain().setParent(parent);
}
}

View File

@ -0,0 +1,20 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.NoScore;
import net.minestom.server.event.player.PlayerBlockBreakEvent;
import org.jetbrains.annotations.NotNull;
public class Backrooms extends StatelessGame {
public Backrooms() {
super(Dimension.NETHER.DIMENSION, "Backrooms", new NoScore());
BackroomsGenerator generator = new BackroomsGenerator();
setGenerator(unit -> generator.generateRoom(unit, 50));
}
@Override
protected void onBlockBreak(@NotNull PlayerBlockBreakEvent playerBlockBreakEvent) {
playerBlockBreakEvent.setCancelled(false);
}
}

View File

@ -0,0 +1,21 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import java.util.Map;
public class BackroomsFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("gameBackrooms");
}
@Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) throws Exception {
return new Backrooms().setParent(parent);
}
}

View File

@ -0,0 +1,86 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.backrooms;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.generator.GenerationUnit;
import net.minestom.server.instance.generator.UnitModifier;
import java.util.concurrent.ThreadLocalRandom;
public class BackroomsGenerator {
public void generateRoom(GenerationUnit unit, int y) {
var modifier = unit.modifier();
var start = unit.absoluteStart();
var yPos = start.blockY() + y;
for (int x = 0; x < unit.size().blockX(); x++) {
for (int z = 0; z < unit.size().blockZ(); z++) {
var xPos = start.blockX() + x;
var zPos = start.blockZ() + z;
modifier.setBlock(xPos, yPos - 1, zPos, Block.BEDROCK);
modifier.setBlock(xPos, yPos + 5, zPos, Block.BEDROCK);
modifier.setBlock(xPos, yPos, zPos, Block.LIGHT_GRAY_WOOL);
modifier.setBlock(xPos, yPos + 4, zPos, Block.SMOOTH_STONE_SLAB);
// idk man... dont ask about this
if (
(x - 2) % 4 == 0 && (
((z - 2) % 8 == 0) ||
((z - 2) % 8 == 1) ||
((z - 2) % 8 == 3) ||
((z - 2) % 8 == 4)
)
) {
modifier.setBlock(xPos, yPos + 4, zPos, Block.SEA_LANTERN);
}
}
}
generateWalls(modifier, start.blockX() + 0, yPos + 1, start.blockZ() + 0);
generateWalls(modifier, start.blockX() + 8, yPos + 1, start.blockZ() + 0);
generateWalls(modifier, start.blockX() + 0, yPos + 1, start.blockZ() + 8);
generateWalls(modifier, start.blockX() + 8, yPos + 1, start.blockZ() + 8);
}
private void generateWalls(UnitModifier modifier, int xPos, int yPos, int zPos) {
generatePillar(modifier, xPos, yPos, zPos, Block.CHISELED_SANDSTONE);
var random = ThreadLocalRandom.current();
var wall1 = random.nextInt(4) != 0;
var wall2 = random.nextInt(4) != 0;
var door1 = random.nextInt(2) != 0;
var door2 = random.nextInt(2) != 0;
var door1pos = random.nextInt(2, 6);
var door2pos = random.nextInt(2, 6);
if (wall1) {
for (int x = xPos; x < xPos + 8; x++) {
generatePillar(modifier, x, yPos, zPos, Block.SMOOTH_SANDSTONE);
}
}
if (wall2) {
for (int z = zPos; z < zPos + 8; z++) {
generatePillar(modifier, xPos, yPos, z, Block.SMOOTH_SANDSTONE);
}
}
if (door1 && wall1) {
generatePillar(modifier, xPos + door1pos, yPos, zPos, Block.AIR);
generatePillar(modifier, xPos + door1pos + 1, yPos, zPos, Block.AIR);
}
if (door2 && wall2) {
generatePillar(modifier, xPos, yPos, zPos + door2pos, Block.AIR);
generatePillar(modifier, xPos, yPos, zPos + door2pos + 1, Block.AIR);
}
}
private void generatePillar(UnitModifier modifier, int xPos, int yPos, int zPos, Block material) {
for (int y = yPos; y < yPos + 3; y++) {
modifier.setBlock(xPos, y, zPos, material);
}
}
}

View File

@ -0,0 +1,123 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.bedwars;
import eu.mhsl.minenet.minigames.Resource;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.bedwars.data.BedwarsTeam;
import eu.mhsl.minenet.minigames.message.type.ActionBarMessage;
import eu.mhsl.minenet.minigames.score.LastWinsScore;
import eu.mhsl.minenet.minigames.util.MaterialUtil;
import eu.mhsl.minenet.minigames.util.Position;
import net.kyori.adventure.text.Component;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;
import net.md_5.bungee.config.YamlConfiguration;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.ItemEntity;
import net.minestom.server.entity.Player;
import net.minestom.server.event.item.ItemDropEvent;
import net.minestom.server.event.item.PickupItemEvent;
import net.minestom.server.event.player.PlayerBlockBreakEvent;
import net.minestom.server.event.player.PlayerBlockPlaceEvent;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.instance.AnvilLoader;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.timer.ExecutionType;
import net.minestom.server.timer.TaskSchedule;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.*;
public class Bedwars extends StatelessGame {
List<Point> placedBlocks = new ArrayList<>();
List<BedwarsTeam> teams = new ArrayList<>();
public Bedwars() throws IOException {
super(Dimension.OVERWORLD.DIMENSION, "Bedwars", new LastWinsScore());
setChunkLoader(new AnvilLoader(Resource.GAME_MAP.getPath().resolve("bedwars/test")));
Configuration config = ConfigurationProvider.getProvider(YamlConfiguration.class).load(Resource.GAME_MAP.getPath().resolve("bedwars/test/config.yml").toFile());
List<String> teamNames = config.getStringList("setup.teams");
teamNames.forEach(teamName -> {
String path = "teams." + teamName;
teams.add(new BedwarsTeam(
config.getString(path + ".name"),
Position.getPosFromCommaSeparated(config.getString(path + ".pos.spawn")),
Position.getPosFromCommaSeparated(config.getString(path + ".pos.spawner")),
config.getStringList(path + ".pos.bed").stream().map(Position::getPosFromCommaSeparated).toList().toArray(new Pos[0]),
MaterialUtil.fromString(config.getString(path + ".material.block"), Material.BLACK_WOOL)
));
});
eventNode().addListener(PickupItemEvent.class, pickupItemEvent -> {
if(pickupItemEvent.getEntity() instanceof Player p) {
p.getInventory().addItemStack(pickupItemEvent.getItemStack());
}
});
}
@Override
protected void onItemDrop(@NotNull ItemDropEvent itemDropEvent) {
itemDropEvent.setCancelled(false);
}
@Override
protected void onStart() {
scheduler().submitTask(() -> {
if(!isRunning) return TaskSchedule.stop();
teams.forEach(bedwarsTeam -> {
ItemEntity item = new ItemEntity(ItemStack.of(bedwarsTeam.getBlock()));
item.setNoGravity(true);
item.setInstance(this, bedwarsTeam.getSpawner());
});
return TaskSchedule.stop();
}, ExecutionType.SYNC);
}
@Override
protected void onBlockPlace(@NotNull PlayerBlockPlaceEvent playerBlockPlaceEvent) {
placedBlocks.add(playerBlockPlaceEvent.getBlockPosition());
}
@Override
protected void onBlockBreak(@NotNull PlayerBlockBreakEvent playerBlockBreakEvent) {
if(!placedBlocks.contains(playerBlockBreakEvent.getBlockPosition())) playerBlockBreakEvent.setCancelled(true);
teams.forEach(bedwarsTeam -> {
if(Arrays.stream(bedwarsTeam.getBed()).anyMatch(pos -> pos.sameBlock(playerBlockBreakEvent.getBlockPosition())))
breakBed(bedwarsTeam);
});
if(playerBlockBreakEvent.isCancelled())
new ActionBarMessage().appendStatic(Component.text("BLAH")).send(playerBlockBreakEvent.getPlayer());
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
if(playerMoveEvent.getNewPosition().y() < 0) death(playerMoveEvent.getPlayer());
}
private void breakBed(BedwarsTeam team) {
team.removeBed();
for (Pos blockPos : team.getBed()) {
setBlock(blockPos, Material.AIR.block());
}
}
private void death(Player p) {
}
@Override
public Pos getSpawn() {
return new Pos(0, 13, 0);
}
}

View File

@ -0,0 +1,40 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.bedwars;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.RestrictionHandler;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.common.MinimalPlayeramountGameRestriction;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.minestom.server.item.Material;
import java.util.Map;
public class BedwarsFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("game_Bedwars#name");
}
@Override
public RestrictionHandler globalRestrictions() {
return new RestrictionHandler()
.addRestriction(new MinimalPlayeramountGameRestriction(1));
}
@Override
public Material symbol() {
return Material.RED_BED;
}
@Override
public TranslatedComponent description() {
return TranslatedComponent.byId("game_Bedwars#description");
}
@Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) throws Exception {
return new Bedwars().setParent(parent);
}
}

View File

@ -0,0 +1,66 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.bedwars.data;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Player;
import net.minestom.server.item.Material;
import java.util.ArrayList;
import java.util.List;
public class BedwarsTeam {
private List<Player> members = new ArrayList<>();
private final String name;
private final Pos spawn;
private final Pos spawner;
private final Pos[] bed;
private final Material block;
private boolean hasBed = true;
public BedwarsTeam(String name, Pos spawn, Pos spawner, Pos[] bed, Material block) {
this.name = name;
this.spawn = spawn;
this.spawner = spawner;
this.bed = bed;
this.block = block;
}
public void removeBed() {
this.hasBed = false;
}
public boolean isHasBed() {
return hasBed;
}
public List<Player> getMembers() {
return members;
}
public void setMembers(List<Player> members) {
this.members = members;
}
public String getName() {
return name;
}
public Pos getSpawn() {
return spawn;
}
public Pos getSpawner() {
return spawner;
}
public Pos[] getBed() {
return bed;
}
public Material getBlock() {
return block;
}
}

View File

@ -0,0 +1,158 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.bowSpleef;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import eu.mhsl.minenet.minigames.score.LastWinsScore;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.util.GeneratorUtils;
import io.github.bloepiloepi.pvp.events.ProjectileHitEvent;
import io.github.bloepiloepi.pvp.projectile.CustomEntityProjectile;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.*;
import net.minestom.server.entity.metadata.arrow.ArrowMeta;
import net.minestom.server.entity.metadata.other.PrimedTntMeta;
import net.minestom.server.event.EventListener;
import net.minestom.server.event.item.ItemUpdateStateEvent;
import net.minestom.server.event.player.PlayerItemAnimationEvent;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.Enchantment;
import net.minestom.server.item.ItemHideFlag;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.tag.Tag;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.time.TimeUnit;
import org.jetbrains.annotations.NotNull;
import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
public class BowSpleef extends StatelessGame {
private static final Tag<Long> CHARGE_BOW_SINCE = Tag.Long("CHARGE_BOW_SINCE");
private static final Tag<Boolean> ARROW_FIRST_HIT = Tag.Boolean("ARROW_ALREADY_LIT");
private final int radius = 30;
private final int totalElevation = 50;
public BowSpleef() {
super(Dimension.OVERWORLD.DIMENSION, "bowSpleef", new LastWinsScore());
eventNode().addListener(
EventListener
.builder(PlayerItemAnimationEvent.class)
.handler(playerItemAnimationEvent -> playerItemAnimationEvent.getPlayer().setTag(CHARGE_BOW_SINCE, System.currentTimeMillis()))
.filter(playerItemAnimationEvent -> playerItemAnimationEvent.getItemAnimationType() == PlayerItemAnimationEvent.ItemAnimationType.BOW)
.build()
);
eventNode().addListener(
EventListener
.builder(ItemUpdateStateEvent.class)
.handler(event -> {
final Player player = event.getPlayer();
final double chargedFor = (System.currentTimeMillis() - player.getTag(CHARGE_BOW_SINCE)) / 1000D;
final double power = MathUtils.clamp((chargedFor * chargedFor + 2 * chargedFor) / 2D, 0, 1);
if (power > 0.2) {
final CustomEntityProjectile projectile = new CustomEntityProjectile(player, EntityType.ARROW, true);
final ArrowMeta meta = (ArrowMeta) projectile.getEntityMeta();
meta.setCritical(power >= 0.9);
projectile.scheduleRemove(Duration.of(100, TimeUnit.SERVER_TICK));
projectile.setOnFire(true);
final Pos position = player.getPosition().add(0, player.getEyeHeight(), 0);
projectile.setInstance(Objects.requireNonNull(player.getInstance()), position);
final Vec direction = projectile.getPosition().direction();
projectile.shoot(position.add(direction).sub(0, 0.2, 0), power * 3, 1.0);
projectile.setTag(ARROW_FIRST_HIT, true);
}
})
.filter(itemUpdateStateEvent -> itemUpdateStateEvent.getItemStack().material() == Material.BOW)
.build()
);
eventNode().addListener(
EventListener
.builder(ProjectileHitEvent.ProjectileBlockHitEvent.class)
.handler(projectileBlockHitEvent -> {
CustomEntityProjectile projectile = projectileBlockHitEvent.getEntity();
if(!projectile.getTag(ARROW_FIRST_HIT)) {
projectile.remove();
return;
}
float radius = 0.5F;
Point arrowPos = projectile.getPosition();
GeneratorUtils.foreachXZ(arrowPos.add(radius), arrowPos.sub(radius), point -> {
Point location = point.add(projectile.getVelocity().mul(0.04)).withY(totalElevation);
if(!getBlock(location).isAir()) {
setBlock(location, Block.AIR);
Entity fallingTnt = new Entity(EntityType.TNT);
PrimedTntMeta fallingTntMeta = (PrimedTntMeta) fallingTnt.getEntityMeta();
fallingTntMeta.setFuseTime(20);
fallingTnt.setInstance(this, location);
fallingTnt.setVelocity(new Vec(0, 3, 0));
}
});
projectile.setTag(ARROW_FIRST_HIT, false);
})
.build()
);
eventNode().addListener(
EventListener
.builder(ProjectileHitEvent.ProjectileEntityHitEvent.class)
.handler(projectileEntityHitEvent -> projectileEntityHitEvent.setCancelled(true))
.build()
);
}
@Override
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
for(int x = -radius; x <= radius; x++) {
for(int z = -radius; z <= radius; z++) {
if(new Pos(x, 0, z).distance(new Pos(0, 0, 0)) > radius) continue;
batch.setBlock(x, totalElevation, z, Block.TNT);
}
}
BatchUtil.loadAndApplyBatch(batch, this, () -> callback.complete(null));
}
@Override
protected void onStart() {
getPlayers().forEach(player -> {
player.getInventory().setItemStack(
0,
ItemStack
.builder(Material.BOW)
.displayName(TranslatedComponent.byId("bow").getAssembled(player))
.meta(builder -> builder.enchantment(Enchantment.FLAME, (short) 1).build())
.meta(builder -> builder.hideFlag(ItemHideFlag.HIDE_ENCHANTS))
.build()
);
});
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
if(playerMoveEvent.getNewPosition().y() < totalElevation) {
getScore().insertResult(playerMoveEvent.getPlayer());
playerMoveEvent.getPlayer().setGameMode(GameMode.SPECTATOR);
}
}
@Override
public Pos getSpawn() {
return new Pos(0, totalElevation + 1, 0);
}
}

View File

@ -0,0 +1,27 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.bowSpleef;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.minestom.server.item.Material;
import java.util.Map;
public class BowSpleefFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("");
}
@Override
public Material symbol() {
return Material.BOW;
}
@Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) throws Exception {
return new BowSpleef().setParent(parent);
}
}

View File

@ -0,0 +1,71 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.deathcube;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.FirstWinsScore;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.world.BlockPallet;
import eu.mhsl.minenet.minigames.world.generator.terrain.CircularPlateTerrainGenerator;
import io.github.bloepiloepi.pvp.config.AttackConfig;
import io.github.bloepiloepi.pvp.config.DamageConfig;
import io.github.bloepiloepi.pvp.config.PvPConfig;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.CompletableFuture;
class Deathcube extends StatelessGame {
final int radius;
final int height;
final int percentage;
public Deathcube(int radius, int height, int percentage, int pvpEnabled) {
super(Dimension.THE_END.DIMENSION, "Deathcube", new FirstWinsScore());
this.radius = radius;
this.height = height + 49;
this.percentage = percentage;
this.setGenerator(new CircularPlateTerrainGenerator(radius+10).setPlateHeight(50));
if(pvpEnabled == 1) eventNode().addChild(
PvPConfig.emptyBuilder()
.damage(DamageConfig.legacyBuilder().fallDamage(false))
.attack(AttackConfig.legacyBuilder().attackCooldown(true))
.build().createNode()
);
System.out.println(radius);
}
@Override
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
for(int x = -radius; x <= radius; x++) {
for (int z = -radius; z <= radius; z++) {
if(new Pos(x, 0, z).distance(new Pos(0, 0, 0)) > radius) continue;
for (int y = 49; y < height; y++) {
if(super.rnd.nextInt(1, 100) <= percentage) {
batch.setBlock(x, y, z, BlockPallet.WOOD.rnd());
}
}
}
}
BatchUtil.loadAndApplyBatch(batch, this, () -> callback.complete(null));
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
super.onPlayerMove(playerMoveEvent);
if(isBeforeBeginning) if(playerMoveEvent.getNewPosition().y() > 51.5) playerMoveEvent.setCancelled(true);
if(playerMoveEvent.getNewPosition().y() > height) getScore().insertResult(playerMoveEvent.getPlayer());
}
@Override
public Pos getSpawn() {
return new Pos(0, 50, 30);
}
}

View File

@ -0,0 +1,44 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.deathcube;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.ConfigManager;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.common.NumericOption;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.minestom.server.item.Material;
import java.util.Map;
public class DeathcubeFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("game_Deathcube#name");
}
@Override
public TranslatedComponent description() {
return TranslatedComponent.byId("game_Deathcube#description");
}
@Override
public ConfigManager configuration() {
return new ConfigManager()
.addOption(new NumericOption("radius", Material.HEART_OF_THE_SEA, TranslatedComponent.byId("optionCommon#radius"), 10, 20, 30))
.addOption(new NumericOption("height", Material.SCAFFOLDING, TranslatedComponent.byId("optionCommon#height"), 10, 30, 50))
.addOption(new NumericOption("percentage", Material.COBWEB, TranslatedComponent.byId("game_Deathcube#optionPercentageBlocks"), 5, 7, 9, 11, 13))
.addOption(new NumericOption("pvpEnabled", Material.STICK, TranslatedComponent.byId("game_Deathcube#optionPvpEnabled"), 1, 0));
}
@Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) {
return new Deathcube(configuration.get("radius").getAsInt(), configuration.get("height").getAsInt(), configuration.get("percentage").getAsInt(), configuration.get("pvpEnabled").getAsInt()).setParent(parent);
}
@Override
public Material symbol() {
return Material.OAK_FENCE;
}
}

View File

@ -0,0 +1,254 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.elytraRace;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.message.Countdown;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import eu.mhsl.minenet.minigames.message.type.TitleMessage;
import eu.mhsl.minenet.minigames.score.FirstWinsScore;
import eu.mhsl.minenet.minigames.util.*;
import eu.mhsl.minenet.minigames.world.BlockPallet;
import eu.mhsl.minenet.minigames.world.generator.featureEnriched.ValeGenerator;
import eu.mhsl.minenet.minigames.world.generator.terrain.PlaneTerrainGenerator;
import net.kyori.adventure.audience.Audience;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.*;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.network.packet.server.play.ParticlePacket;
import net.minestom.server.particle.Particle;
import net.minestom.server.particle.ParticleCreator;
import net.minestom.server.sound.SoundEvent;
import org.jetbrains.annotations.NotNull;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
public class ElytraRace extends StatelessGame {
private final ValeGenerator vale = new ValeGenerator();
private final int gameHeight = 0;
private final int ringSpacing = 100;
private final int ringCount;
private final Material boostMaterial = Material.FIREWORK_ROCKET;
private final Material resetMaterial = Material.RED_DYE;
private final int boostMultiplier = 50;
private final Material ringMaterial = Material.GOLD_BLOCK;
private int generatedUntil = 0;
private record CheckPointData(int currentCheckpoint, int nextCheckpoint) {
public CheckPointData next(int spacing) {
return new CheckPointData(nextCheckpoint, nextCheckpoint + spacing);
}
}
private final Map<Player, CheckPointData> playerCheckpoints = new HashMap<>();
public ElytraRace(int ringCount) {
super(Dimension.OVERWORLD.DIMENSION, "ElytraRace", new FirstWinsScore());
this.ringCount = ringCount;
setGenerator(vale);
vale.setCalculateSeaLevel(point -> -55);
vale.setXShiftMultiplier(integer -> NumberUtil.map(integer, 50, 500, 0, 1));
vale.addMixIn(new PlaneTerrainGenerator(gameHeight, Block.BARRIER));
eventNode().addListener(PlayerUseItemEvent.class, playerUseItemEvent -> {
Player player = playerUseItemEvent.getPlayer();
Material usedMaterial = playerUseItemEvent.getItemStack().material();
if(usedMaterial.equals(boostMaterial)) {
if(!player.isFlyingWithElytra()) return;
boost(player);
InventoryUtil.removeItemFromPlayer(player, boostMaterial, 1);
} else if(usedMaterial.equals(resetMaterial)) {
toCheckpoint(player);
InventoryUtil.removeItemFromPlayer(player, resetMaterial, 1);
}
});
eventNode().addListener(PlayerStopFlyingWithElytraEvent.class, playerStopFlyingWithElytraEvent -> {
Player player = playerStopFlyingWithElytraEvent.getPlayer();
if(Position.blocksBelowPlayer(this, player).contains(ringMaterial.block())) {
player.setFlyingWithElytra(true);
boost(player);
} else {
toCheckpoint(player);
// getScore().insertResult(playerStopFlyingWithElytraEvent.getPlayer());
// playerStopFlyingWithElytraEvent.getPlayer().setGameMode(GameMode.SPECTATOR);
}
});
eventNode().addListener(PlayerStartFlyingWithElytraEvent.class, playerStartFlyingWithElytraEvent -> {
if(!isRunning) {
playerStartFlyingWithElytraEvent.getPlayer().setFlyingWithElytra(false);
return;
}
boost(playerStartFlyingWithElytraEvent.getPlayer());
});
}
@Override
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
Point spawnpoint = new Pos(vale.getXShiftAtZ(0), -46, 0);
GeneratorUtils.iterateArea(spawnpoint.sub(2, 0, 2), spawnpoint.add(2, 0, 2), point -> {
setBlock(point, BlockPallet.STREET.rnd());
});
generateRing(ringSpacing);
generateRing(ringSpacing * 2);
callback.complete(null);
}
@Override
protected void onStart() {
getPlayers().forEach(player -> {
player.getInventory().setChestplate(ItemStack.of(Material.ELYTRA));
for(int i = 0; i < 3; i++) {
player.getInventory().setItemStack(i, ItemStack.builder(boostMaterial).displayName(TranslatedComponent.byId("boost").getAssembled(player)).build());
}
addResetItemToPlayer(player);
});
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
Player player = playerMoveEvent.getPlayer();
Point newPos = playerMoveEvent.getNewPosition();
playerCheckpoints.putIfAbsent(player, new CheckPointData(ringSpacing, ringSpacing * 2));
if(newPos.z() > generatedUntil - ringSpacing) {
generateRing(generatedUntil + ringSpacing);
}
if(newPos.z() > playerCheckpoints.get(player).nextCheckpoint) {
playerCheckpoints.put(player, playerCheckpoints.get(player).next(ringSpacing));
boost(player);
}
if(newPos.y() > gameHeight - 5) {
Point particlePoint = newPos.withY(gameHeight);
ParticlePacket particle = ParticleCreator.createParticlePacket(
Particle.WAX_ON,
particlePoint.blockX(),
particlePoint.blockY(),
particlePoint.withZ(z -> z+10).blockZ(),
20,
0,
30,
Math.toIntExact((long) NumberUtil.map(newPos.y(), gameHeight - 5, gameHeight, 50, 500))
);
player.sendPacket(particle);
}
if(getBlock(player.getPosition()).equals(Block.WATER)) {
toCheckpoint(player);
}
if(newPos.z() > ringCount * ringSpacing) {
getScore().insertResult(player);
player.setGameMode(GameMode.SPECTATOR);
}
}
@Override
public Pos getSpawn() {
return new Pos(vale.getXShiftAtZ(0), -45, 0);
}
private void addResetItemToPlayer(Player p) {
p.getInventory().setItemStack(8, ItemStack.builder(resetMaterial).displayName(TranslatedComponent.byId("reset").getAssembled(p)).build());
}
private Point getRingPositionAtZ(int z) {
return new Pos(vale.getXShiftAtZ(z), -45, z);
}
private CompletableFuture<Void> generateRing(int zPos) {
if(zPos > ringCount * ringSpacing) return null;
boolean isLast = (zPos == ringCount * ringSpacing);
generatedUntil = zPos;
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
Point ringPos = getRingPositionAtZ(zPos);
GeneratorUtils.iterateArea(
ringPos.sub(100, 0, 0).withY(getDimensionType().getMinY()),
ringPos.add(100, 0, 0).withY(gameHeight),
point -> batch.setBlock(point, Block.BARRIER)
);
GeneratorUtils.iterateArea(
ringPos.sub(3, 3, 0),
ringPos.add(3, 3, 0),
point -> batch.setBlock(point, isLast ? Block.DIAMOND_BLOCK : ringMaterial.block())
);
GeneratorUtils.iterateArea(
ringPos.sub(2, 2, 0),
ringPos.add(2, 2, 0),
point -> batch.setBlock(point, Block.AIR)
);
BatchUtil.loadAndApplyBatch(batch, this, () -> {});
return null;
}
private void boost(Player player) {
player.playSound(Sound.sound(SoundEvent.ENTITY_FIREWORK_ROCKET_LAUNCH, Sound.Source.VOICE, 1f, 1f));
Vec playerVelocity = player.getPosition().direction();
player.setVelocity(
player.getVelocity().add(playerVelocity.mul(boostMultiplier))
.withY(playerVelocity.withY(v -> v * boostMultiplier).y())
);
}
private void toCheckpoint(Player p) {
Point checkpointPos = getRingPositionAtZ(playerCheckpoints.get(p).currentCheckpoint);
p.setVelocity(Vec.ZERO);
p.setFlyingWithElytra(false);
p.teleport(Pos.fromPoint(checkpointPos).add(0.5, 0, 0.5));
p.setFlying(true);
p.setFlyingSpeed(0);
new Countdown(TitleMessage.class)
.countdown(
Audience.audience(p),
3,
countdownModifier ->
countdownModifier.message = new TitleMessage(
Duration.ofMillis(300),
Duration.ofMillis(700)
)
.subtitle(
subtitleMessage ->
subtitleMessage
.appendStatic(Component.text("Launch in ", NamedTextColor.DARK_GREEN))
.appendStatic(Component.text(countdownModifier.timeLeft, NamedTextColor.GREEN))
.appendStatic(Component.text(" seconds", NamedTextColor.DARK_GREEN))
)
).thenRun(() -> {
p.setFlying(false);
p.setFlyingSpeed(1);
p.setFlyingWithElytra(true);
boost(p);
addResetItemToPlayer(p);
});
}
}

View File

@ -0,0 +1,35 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.elytraRace;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.ConfigManager;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.common.NumericOption;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.minestom.server.item.Material;
import java.util.Map;
public class ElytraRaceFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("");
}
@Override
public Material symbol() {
return Material.ELYTRA;
}
@Override
public ConfigManager configuration() {
return new ConfigManager()
.addOption(new NumericOption("ringCount", Material.DIAMOND_BLOCK, TranslatedComponent.byId("ringCount"), 5, 10, 20, 30, 40, 50));
}
@Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) throws Exception {
return new ElytraRace(configuration.get("ringCount").getAsInt()).setParent(parent);
}
}

View File

@ -0,0 +1,104 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.minerun;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.FirstWinsScore;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.util.CommonProperties;
import eu.mhsl.minenet.minigames.util.Intersect;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.world.BlockPallet;
import eu.mhsl.minenet.minigames.world.generator.terrain.SquarePlateTerrainGenerator;
import net.kyori.adventure.sound.Sound;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.block.Block;
import net.minestom.server.sound.SoundEvent;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.concurrent.CompletableFuture;
class Minerun extends StatelessGame {
private final int minePercentage;
private final int width;
private final int length;
private final int preRun = 5;
private final int afterMines = 2;
private final int afterFinishLine = 10;
public Minerun(int width, int length, int minePercentage) {
super(Dimension.THE_END.DIMENSION, "Minerun", new FirstWinsScore());
setGenerator(new SquarePlateTerrainGenerator(width, length + preRun + afterFinishLine).setPlateHeight(50).setGenerateBorders(true));
this.width = width;
this.length = length;
this.minePercentage = minePercentage;
}
@Override
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
int spawnToFinishLine = preRun + length + afterMines;
Random random = new Random();
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
for(int x = 0; x <= width; x++) {
for(int z = preRun; z <= length + preRun; z++) {
if (random.nextInt(0, 100) < minePercentage) {
batch.setBlock(x, 50, z, BlockPallet.PRESSURE_PLATES.rnd());
}
}
}
for(int x = 0; x <= width; x++) {
batch.setBlock(x, 49, spawnToFinishLine, Block.GOLD_BLOCK);
batch.setBlock(x, 49, preRun, Block.GOLD_BLOCK);
batch.setBlock(x, 50, preRun, Block.OAK_FENCE.withProperties(CommonProperties.fenceEastWest));
}
BatchUtil.loadAndApplyBatch(batch, this, () -> callback.complete(null));
}
@Override
protected void onStart() {
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
for(int x = 0; x <= width; x++) {
batch.setBlock(x, 50, preRun, Block.AIR);
}
BatchUtil.loadAndApplyBatch(batch, this, () -> playSound(Sound.sound(SoundEvent.BLOCK_WOOD_BREAK, Sound.Source.BLOCK, 1f, 1f)));
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
Player p = playerMoveEvent.getPlayer();
Pos middle = playerMoveEvent.getNewPosition();
if(isBeforeBeginning && middle.z() > preRun+0.5) { //player cannot go forward before the game start
playerMoveEvent.setCancelled(true);
}
if(Intersect.withPressurePlate(this, BlockPallet.PRESSURE_PLATES, middle)) { //Player died
p.playSound(Sound.sound(SoundEvent.ENTITY_GENERIC_EXPLODE, Sound.Source.PLAYER, 1f, 1f));
p.setPose(Entity.Pose.DYING);
p.teleport(new Pos(p.getPosition().x(), getSpawn().y(), getSpawn().z()));
}
if(middle.z() > preRun + length + afterMines) { // Player finished
getScore().insertResult(p);
p.setGameMode(GameMode.SPECTATOR);
}
}
@Override
public Pos getSpawn() {
return new Pos((double) width /2, 50, 3);
}
}

View File

@ -0,0 +1,42 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.minerun;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.ConfigManager;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.common.NumericOption;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.minestom.server.item.Material;
import java.util.Map;
public class MinerunFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("game_Minerun#name");
}
@Override
public ConfigManager configuration() {
return new ConfigManager()
.addOption(new NumericOption("width", Material.OAK_FENCE, TranslatedComponent.byId("optionCommon#width"), 10, 30, 50, 100))
.addOption(new NumericOption("length", Material.ZOMBIE_HEAD, TranslatedComponent.byId("optionCommon#length"), 50, 100, 150, 200))
.addOption(new NumericOption("percentage", Material.LIGHT_WEIGHTED_PRESSURE_PLATE, TranslatedComponent.byId("game_Minerun#optionPercentageMiens"), 30, 40, 50, 60, 70));
}
@Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) {
return new Minerun(configuration.get("width").getAsInt(), configuration.get("length").getAsInt(), configuration.get("percentage").getAsInt()).setParent(parent);
}
@Override
public Material symbol() {
return Material.LIGHT_WEIGHTED_PRESSURE_PLATE;
}
@Override
public TranslatedComponent description() {
return TranslatedComponent.byId("game_Minerun#description");
}
}

View File

@ -0,0 +1,94 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.spleef;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import eu.mhsl.minenet.minigames.score.LastWinsScore;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.world.BlockPallet;
import eu.mhsl.minenet.minigames.world.generator.terrain.CircularPlateTerrainGenerator;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.GameMode;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.event.player.PlayerStartDiggingEvent;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.*;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.CompletableFuture;
public class Spleef extends StatelessGame {
int radius;
int stackCount;
final int heightPerLevel = 20;
final int totalElevation = 50;
public Spleef(int radius, int stackCount) {
super(Dimension.OVERWORLD.DIMENSION, "Spleef", new LastWinsScore());
getScore().setIgnoreLastPlayers(1);
this.radius = radius;
this.stackCount = stackCount;
setGenerator(new CircularPlateTerrainGenerator(50));
eventNode().addListener(PlayerStartDiggingEvent.class, this::destroyBlock);
}
@Override
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
AbsoluteBlockBatch circle = new AbsoluteBlockBatch();
for (int level = 0; level < stackCount; level++) {
for(int x = -radius; x <= radius; x++) {
for(int z = -radius; z <= radius; z++) {
if(new Pos(x, 0, z).distance(new Pos(0, 0, 0)) > radius) continue;
circle.setBlock(x, totalElevation + (level * heightPerLevel), z, BlockPallet.WINTER.rnd());
}
}
}
BatchUtil.loadAndApplyBatch(circle, this, () -> callback.complete(null));
}
@Override
protected void onStart() {
getPlayers().forEach(player -> {
player.setGameMode(GameMode.SURVIVAL);
player.getInventory().addItemStack(
ItemStack
.builder(Material.DIAMOND_SHOVEL)
.displayName(TranslatedComponent.byId("game_Spleef#shovelName").getAssembled(player))
.meta(
builder -> builder
.enchantment(Enchantment.EFFICIENCY, (short) 99)
.hideFlag(ItemHideFlag.HIDE_ENCHANTS)
.build()
)
.build()
);
player.setHeldItemSlot((byte) 0);
});
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
if(playerMoveEvent.getNewPosition().y() < totalElevation) {
playerMoveEvent.getPlayer().setGameMode(GameMode.SPECTATOR);
playerMoveEvent.getPlayer().getInventory().clear();
getScore().insertResult(playerMoveEvent.getPlayer());
}
}
private void destroyBlock(PlayerStartDiggingEvent event) {
setBlock(event.getBlockPosition(), Block.AIR);
}
@Override
public Pos getSpawn() {
return new Pos(0, totalElevation + heightPerLevel * (stackCount-1) + 1, 0);
}
}

View File

@ -0,0 +1,49 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.spleef;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.ConfigManager;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.common.NumericOption;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.RestrictionHandler;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.common.MinimalPlayeramountGameRestriction;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.minestom.server.item.Material;
import java.util.Map;
public class SpleefFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("game_Spleef#name");
}
@Override
public RestrictionHandler globalRestrictions() {
return new RestrictionHandler()
.addRestriction(new MinimalPlayeramountGameRestriction(2));
}
@Override
public Material symbol() {
return Material.DIAMOND_SHOVEL;
}
@Override
public ConfigManager configuration() {
return new ConfigManager()
.addOption(new NumericOption("radius", Material.HEART_OF_THE_SEA, TranslatedComponent.byId("game_Spleef#radius"), 10, 20, 30))
.addOption(new NumericOption("stackCount", Material.SCAFFOLDING, TranslatedComponent.byId("game_Spleef#stackCount"), 1, 2, 3, 4, 5));
}
@Override
public TranslatedComponent description() {
return TranslatedComponent.byId("game_Spleef#description");
}
@Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) throws Exception {
return new Spleef(configuration.get("radius").getAsInt(), configuration.get("stackCount").getAsInt()).setParent(parent);
}
}

View File

@ -0,0 +1,50 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.stickfight;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.ConfigManager;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.common.NumericOption;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.RestrictionHandler;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.common.MaximalPlayeramountGameRestriction;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.restriction.common.MinimalPlayeramountGameRestriction;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.minestom.server.item.Material;
import java.util.Map;
public class StickFightFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("game_Stickfight#name");
}
@Override
public TranslatedComponent description() {
return TranslatedComponent.byId("game_Stickfight#description");
}
@Override
public ConfigManager configuration() {
return new ConfigManager()
.addOption(new NumericOption("length", Material.SANDSTONE, TranslatedComponent.byId("optionCommon#length"), 5, 7, 9, 11));
}
@Override
public RestrictionHandler globalRestrictions() {
return new RestrictionHandler()
.addRestriction(new MinimalPlayeramountGameRestriction(2))
.addRestriction(new MaximalPlayeramountGameRestriction(4));
}
@Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) {
return new Stickfight().setParent(parent);
}
@Override
public Material symbol() {
return Material.STICK;
}
}

View File

@ -0,0 +1,62 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.stickfight;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.LastWinsScore;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.world.generator.terrain.CircularPlateTerrainGenerator;
import io.github.bloepiloepi.pvp.config.*;
import io.github.bloepiloepi.pvp.events.FinalAttackEvent;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.block.Block;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.CompletableFuture;
public class Stickfight extends StatelessGame {
public Stickfight() {
super(Dimension.OVERWORLD.DIMENSION, "Stickfight", new LastWinsScore());
eventNode().addChild(
PvPConfig.emptyBuilder()
.damage(DamageConfig.legacyBuilder().fallDamage(false))
.attack(AttackConfig.legacyBuilder().attackCooldown(true))
.build().createNode()
);
eventNode().addListener(FinalAttackEvent.class, finalAttackEvent -> {
finalAttackEvent.setBaseDamage(0);
((Player) finalAttackEvent.getTarget()).setHealth(20);
});
setGenerator(new CircularPlateTerrainGenerator(20));
}
@Override
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
for (int z = -10; z <= 10; z++) {
batch.setBlock(0, 50, z, Block.SANDSTONE);
}
batch.setBlock(0, 50, 0, Block.GOLD_BLOCK);
BatchUtil.loadAndApplyBatch(batch, this, () -> callback.complete(null));
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
if(isBeforeBeginning) playerMoveEvent.setCancelled(true);
if(playerMoveEvent.getNewPosition().y() < 40) {
playerMoveEvent.getPlayer().teleport(new Pos(0, 51, 0));
}
}
@Override
public Pos getSpawn() {
return new Pos(0.5, 51, 0.5);
}
}

View File

@ -0,0 +1,85 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.tntrun;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.LastWinsScore;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.world.generator.terrain.CircularPlateTerrainGenerator;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.metadata.other.PrimedTntMeta;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.block.Block;
import net.minestom.server.timer.ExecutionType;
import net.minestom.server.timer.TaskSchedule;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.CompletableFuture;
public class TntRun extends StatelessGame {
final int totalElevation = 50;
final int heightPerLevel = 20;
final int radius;
final int stackCount;
public TntRun(int radius, int stackCount) {
super(Dimension.OVERWORLD.DIMENSION, "tntRun", new LastWinsScore());
this.radius = radius;
this.stackCount = stackCount;
setGenerator(new CircularPlateTerrainGenerator(radius));
}
@Override
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
for (int level = 0; level < stackCount; level++) {
for(int x = -radius; x <= radius; x++) {
for(int z = -radius; z <= radius; z++) {
if(new Pos(x, 0, z).distance(new Pos(0, 0, 0)) > radius) continue;
batch.setBlock(x, totalElevation + (level * heightPerLevel), z, Block.TNT);
}
}
}
BatchUtil.loadAndApplyBatch(batch, this, () -> callback.complete(null));
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
if(playerMoveEvent.getNewPosition().y() < totalElevation) {
playerMoveEvent.getPlayer().setGameMode(GameMode.SPECTATOR);
getScore().insertResult(playerMoveEvent.getPlayer());
}
if(isRunning && !getScore().hasResult(playerMoveEvent.getPlayer())) {
MinecraftServer.getSchedulerManager().scheduleTask(() -> {
float radius = 0.5F;
for (float x = -radius; x <= radius; x++) {
for (float z = -radius; z <= radius; z++) {
Pos firstLocation = playerMoveEvent.getNewPosition().add(x, -1, z);
Pos secondLocation = firstLocation.withY(y -> y-1);
if(!getBlock(firstLocation).isAir() || !getBlock(secondLocation).isAir()) {
setBlock(firstLocation, Block.AIR);
setBlock(secondLocation, Block.AIR);
Entity fallingTnt = new Entity(EntityType.TNT);
PrimedTntMeta fallingTntMeta = (PrimedTntMeta) fallingTnt.getEntityMeta();
fallingTntMeta.setFuseTime(20);
fallingTnt.setInstance(this, secondLocation);
}
}
}
}, TaskSchedule.millis(500), TaskSchedule.stop(), ExecutionType.ASYNC);
}
}
@Override
public Pos getSpawn() {
return new Pos(0, totalElevation + heightPerLevel * (stackCount-1) + 1, 0);
}
}

View File

@ -0,0 +1,36 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.tntrun;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.ConfigManager;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.common.NumericOption;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.minestom.server.item.Material;
import java.util.Map;
public class TntRunFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("game_tntRun#name");
}
@Override
public Material symbol() {
return Material.TNT;
}
@Override
public ConfigManager configuration() {
return new ConfigManager()
.addOption(new NumericOption("radius", Material.STICK, TranslatedComponent.byId("game_tntRun#radius"), 20, 30, 50, 60))
.addOption(new NumericOption("levels", Material.SCAFFOLDING, TranslatedComponent.byId("game_tntRun#levels"), 1, 2, 3, 4, 5));
}
@Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) throws Exception {
return new TntRun(configuration.get("radius").getAsInt(), configuration.get("levels").getAsInt()).setParent(parent);
}
}

View File

@ -0,0 +1,25 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.generator.MazeGenerator;
import eu.mhsl.minenet.minigames.score.NoScore;
import net.minestom.server.entity.Player;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
public class Towerdefense extends StatelessGame {
public Towerdefense() {
super(Dimension.NETHER.DIMENSION, "Towerdefense", new NoScore());
setGenerator(new MazeGenerator());
}
@Override
protected boolean onPlayerJoin(Player p) {
p.getInventory().setItemStack(1, ItemStack.of(Material.ARMOR_STAND));
return false;
}
}

View File

@ -0,0 +1,31 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.minestom.server.item.Material;
import java.util.Map;
public class TowerdefenseFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("game_Towerdefense#name");
}
@Override
public Material symbol() {
return Material.ARMOR_STAND;
}
@Override
public TranslatedComponent description() {
return TranslatedComponent.byId("game_Towerdefense#description");
}
@Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) {
return new Towerdefense().setParent(parent);
}
}

View File

@ -0,0 +1,13 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.towerdefense.generator;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.generator.GenerationUnit;
import net.minestom.server.instance.generator.Generator;
import org.jetbrains.annotations.NotNull;
public class MazeGenerator implements Generator {
@Override
public void generate(@NotNull GenerationUnit unit) {
unit.modifier().fillHeight(0, 1, Block.BLACK_WOOL);
}
}

View File

@ -0,0 +1,29 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.trafficlightrace;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.timer.TaskSchedule;
import java.util.Random;
enum LightPhase {
RED(Material.RED_WOOL, 1000, 5000),
YELLOW(Material.YELLOW_WOOL, 500, 1500),
GREEN(Material.GREEN_WOOL, 2000, 5000);
public final ItemStack item;
private final int minDuration;
private final int maxDuration;
private static final Random rnd = new Random();
LightPhase(Material material, int minDuration, int maxDuration) {
this.item = ItemStack.of(material);
this.minDuration = minDuration;
this.maxDuration = maxDuration;
}
public TaskSchedule taskScheduleRandomDuration() {
return TaskSchedule.millis(rnd.nextLong(minDuration, maxDuration));
}
}

View File

@ -0,0 +1,176 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.trafficlightrace;
import de.articdive.jnoise.JNoise;
import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.FirstWinsScore;
import eu.mhsl.minenet.minigames.util.BatchUtil;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.util.CommonProperties;
import eu.mhsl.minenet.minigames.world.BlockPallet;
import eu.mhsl.minenet.minigames.world.generator.terrain.SquarePlateTerrainGenerator;
import net.kyori.adventure.sound.Sound;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.GameMode;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.block.Block;
import net.minestom.server.sound.SoundEvent;
import net.minestom.server.timer.ExecutionType;
import net.minestom.server.timer.TaskSchedule;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
class TrafficLightRace extends StatelessGame {
private LightPhase phase = LightPhase.RED;
private int phaseCounter = 0;
private final int width;
private final int length;
private final int preRun = 10;
private final int afterRun = 10;
private final List<Pos> trafficLights = new ArrayList<>();
public TrafficLightRace(int width, int length) {
super(Dimension.THE_END.DIMENSION, "Ampelrennen", new FirstWinsScore());
this.width = width;
this.length = length;
setGenerator(
new SquarePlateTerrainGenerator(width, length + preRun + afterRun)
.setPlateHeight(50)
.setGenerateBorders(true)
);
}
@Override
protected void onLoad(@NotNull CompletableFuture<Void> callback) {
final JNoise slowBatches = JNoise.newBuilder()
.fastSimplex()
.setSeed(rnd.nextLong())
.setFrequency(0.1)
.build();
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
for (int x = 0; x <= width; x++) {
for (int z = 0; z <= preRun; z++) {
batch.setBlock(x, 50, z, BlockPallet.GROUND.rnd());
}
for (int z = preRun; z <= length + preRun; z++) {
batch.setBlock(x, 50, z, slowBatches.getNoise(x, z) > 0.6 ? Block.SOUL_SAND : BlockPallet.STREET.rnd());
}
for (int z = preRun + length; z <= preRun + length + afterRun; z++) {
batch.setBlock(x, 50, z, BlockPallet.GROUND.rnd());
}
batch.setBlock(x, 51, preRun-1, Block.OAK_FENCE.withProperties(CommonProperties.fenceEastWest));
batch.setBlock(x, 50, preRun + length, Block.GOLD_BLOCK);
}
record TrafficLightsInput(int x, int z) {}
Consumer<TrafficLightsInput> generateTrafficLight = (input) -> {
batch.setBlock(input.x, 51, input.z, Block.ANVIL);
for(int y = 52; y <= 59; y++) {
batch.setBlock(input.x, y, input.z, Block.ANDESITE_WALL);
}
for (int x = input.x-3; x <= input.x+3; x++) {
batch.setBlock(x, 60, input.z, Block.SMOOTH_STONE_SLAB);
}
batch.setBlock(input.x-2, 59, input.z, Block.STONE_BRICK_WALL);
trafficLights.add(new Pos(input.x-2, 58, input.z));
trafficLights.add(new Pos(input.x-2, 57, input.z));
batch.setBlock(input.x+2, 59, input.z, Block.STONE_BRICK_WALL);
trafficLights.add(new Pos(input.x+2, 58, input.z));
trafficLights.add(new Pos(input.x+2, 57, input.z));
for (Pos trafficLight : trafficLights) {
batch.setBlock(trafficLight, Block.WHITE_WOOL);
}
};
for (int count = 0; count <= this.rnd.nextInt(1, 2); count++) {
generateTrafficLight.accept(new TrafficLightsInput(0, this.rnd.nextInt(preRun + 10, length + preRun - 10)));
generateTrafficLight.accept(new TrafficLightsInput(width, this.rnd.nextInt(preRun + 10, length + preRun - 10)));
}
if(width > 30) {
for (int count = 0; count <= this.rnd.nextInt(1, 2); count++) {
generateTrafficLight.accept(new TrafficLightsInput(width/2, this.rnd.nextInt(preRun, length + preRun)));
}
}
generateTrafficLight.accept(new TrafficLightsInput(0, length + preRun));
generateTrafficLight.accept(new TrafficLightsInput(width, length + preRun));
BatchUtil.loadAndApplyBatch(batch, this, () -> callback.complete(null));
}
@Override
protected void onStart() {
AbsoluteBlockBatch batch = new AbsoluteBlockBatch();
for(int x = 0; x <= width; x++) {
batch.setBlock(x, 51, preRun-1, Block.AIR);
}
BatchUtil.loadAndApplyBatch(batch, this, () -> playSound(Sound.sound(SoundEvent.BLOCK_WOOD_BREAK, Sound.Source.BLOCK, 1f, 1f)));
scheduler().submitTask(() -> {
if(!super.isRunning) return TaskSchedule.stop();
phaseCounter++;
if(phaseCounter >= 3) phaseCounter = 0;
if(phaseCounter == 0) phase = LightPhase.RED;
if(phaseCounter == 1) phase = LightPhase.GREEN;
if(phaseCounter == 2) phase = LightPhase.YELLOW;
getPlayers().forEach(player -> {
for(int i = 0; i < 9; i++) {
player.getInventory().setItemStack(i, phase.item);
}
});
AbsoluteBlockBatch changeLightsBatch = new AbsoluteBlockBatch();
for (Pos trafficLight : trafficLights) {
changeLightsBatch.setBlock(trafficLight, phase.item.material().block());
}
BatchUtil.loadAndApplyBatch(changeLightsBatch, this, () -> {});
return phase.taskScheduleRandomDuration();
}, ExecutionType.SYNC);
}
@Override
protected void onPlayerMove(@NotNull PlayerMoveEvent playerMoveEvent) {
if(isBeforeBeginning) return;
if(phase.equals(LightPhase.RED) && playerMoveEvent.getNewPosition().z()-0.01 > playerMoveEvent.getPlayer().getPosition().z()) {
playerMoveEvent.getPlayer().setVelocity(new Vec(0, 8, -15));
playerMoveEvent.getPlayer().playSound(Sound.sound(SoundEvent.ENTITY_BLAZE_SHOOT, Sound.Source.PLAYER, 1f, 1f));
}
if(playerMoveEvent.getNewPosition().z() > preRun+length) {
getScore().insertResult(playerMoveEvent.getPlayer());
playerMoveEvent.getPlayer().getInventory().clear();
playerMoveEvent.getPlayer().setGameMode(GameMode.SPECTATOR);
}
}
@Override
public Pos getSpawn() {
return new Pos((double) width/2, 51, 3);
}
}

View File

@ -0,0 +1,36 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.trafficlightrace;
import eu.mhsl.minenet.minigames.instance.game.Game;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.ConfigManager;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.common.NumericOption;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.minestom.server.item.Material;
import java.util.Map;
public class TrafficLightRaceFactory implements GameFactory {
@Override
public TranslatedComponent name() {
return TranslatedComponent.byId("game_TrafficlightRace#name");
}
@Override
public ConfigManager configuration() {
return new ConfigManager()
.addOption(new NumericOption("width", Material.OAK_FENCE, TranslatedComponent.byId("game_TrafficlightRace#width"), 20, 30, 40))
.addOption(new NumericOption("length", Material.BLACK_CONCRETE_POWDER, TranslatedComponent.byId("game_TrafficlightRace#length"), 50, 80, 100, 120, 150));
}
@Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) {
return new TrafficLightRace(configuration.get("width").getAsInt(), configuration.get("length").getAsInt()).setParent(parent);
}
@Override
public Material symbol() {
return Material.YELLOW_WOOL;
}
}

View File

@ -0,0 +1,46 @@
package eu.mhsl.minenet.minigames.instance.hub;
import eu.mhsl.minenet.minigames.Resource;
import eu.mhsl.minenet.minigames.instance.MineNetInstance;
import eu.mhsl.minenet.minigames.instance.hub.entity.RoomSelector;
import eu.mhsl.minenet.minigames.util.CommonEventHandles;
import eu.mhsl.minenet.minigames.instance.Spawnable;
import eu.mhsl.minenet.minigames.instance.Dimension;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.event.player.*;
import net.minestom.server.instance.AnvilLoader;
import java.nio.file.Path;
public class Hub extends MineNetInstance implements Spawnable {
public static final Hub INSTANCE = new Hub();
static {
MinecraftServer.getInstanceManager().registerInstance(INSTANCE);
INSTANCE.setChunkLoader(new AnvilLoader(Resource.HUB_MAP.getPath()));
INSTANCE.eventNode()
.addListener(PlayerBlockBreakEvent.class, CommonEventHandles::cancel)
.addListener(PlayerBlockPlaceEvent.class, CommonEventHandles::cancel)
.addListener(PlayerBlockInteractEvent.class, CommonEventHandles::cancel);
new RoomSelector().setInstance(INSTANCE, new Pos(0.5, 11, 4.5));
}
private Hub() {
super(Dimension.THE_END.DIMENSION);
setChunkLoader(new AnvilLoader(Path.of("maps/hub")));
setTime(18000);
setTimeRate(0);
}
@Override
public Pos getSpawn() {
return new Pos(0.5, 11, 0.5);
}
}

View File

@ -0,0 +1,35 @@
package eu.mhsl.minenet.minigames.instance.hub.entity;
import eu.mhsl.minenet.minigames.instance.hub.inventory.HubInventory;
import eu.mhsl.minenet.minigames.shared.entity.InteractableEntity;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.metadata.villager.AbstractVillagerMeta;
import net.minestom.server.event.entity.EntityAttackEvent;
import net.minestom.server.event.instance.AddEntityToInstanceEvent;
import net.minestom.server.event.player.PlayerEntityInteractEvent;
import org.jetbrains.annotations.NotNull;
public class RoomSelector extends InteractableEntity {
final AbstractVillagerMeta abstractVillagerMeta;
public RoomSelector() {
super(EntityType.VILLAGER);
abstractVillagerMeta = (AbstractVillagerMeta) this.getEntityMeta();
}
@Override
public void onSpawn(@NotNull AddEntityToInstanceEvent addEntityToInstanceEvent) {
}
@Override
public void onAttack(@NotNull EntityAttackEvent entityAttackEvent) {
super.onAttack(entityAttackEvent);
abstractVillagerMeta.setHeadShakeTimer(20);
}
@Override
public void onInteract(@NotNull PlayerEntityInteractEvent playerEntityInteractEvent) {
playerEntityInteractEvent.getPlayer().openInventory(new HubInventory(playerEntityInteractEvent.getPlayer()));
}
}

View File

@ -0,0 +1,38 @@
package eu.mhsl.minenet.minigames.instance.hub.inventory;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import eu.mhsl.minenet.minigames.shared.inventory.InteractableInventory;
import net.minestom.server.entity.Player;
import net.minestom.server.inventory.InventoryType;
import net.minestom.server.item.ItemHideFlag;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
public class HubInventory extends InteractableInventory {
public HubInventory(Player p) {
super(InventoryType.CHEST_3_ROW, TranslatedComponent.assemble("hub#invTitle", p));
setClickableItem(
ItemStack
.builder(Material.WRITABLE_BOOK)
.displayName(TranslatedComponent.assemble("hub#create", p))
.lore(TranslatedComponent.assemble("hub#create_description", p))
.meta(metaBuilder -> metaBuilder.hideFlag(ItemHideFlag.HIDE_ATTRIBUTES))
.build(),
12,
itemClick -> Room.createRoom(itemClick.getPlayer()),
true
);
setClickableItem(
ItemStack
.builder(Material.KNOWLEDGE_BOOK)
.displayName(TranslatedComponent.assemble("hub#join", p))
.lore(TranslatedComponent.assemble("hub#join_description", p))
.build(),
14,
itemClick -> itemClick.getPlayer().openInventory(new JoinInventory(itemClick.getPlayer()))
);
}
}

View File

@ -0,0 +1,68 @@
package eu.mhsl.minenet.minigames.instance.hub.inventory;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import eu.mhsl.minenet.minigames.shared.inventory.InteractableInventory;
import eu.mhsl.minenet.minigames.instance.hub.Hub;
import net.kyori.adventure.text.Component;
import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerPacketEvent;
import net.minestom.server.inventory.InventoryType;
import net.minestom.server.inventory.click.ClickType;
import net.minestom.server.inventory.condition.InventoryConditionResult;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.network.packet.client.play.ClientNameItemPacket;
import java.util.Locale;
import java.util.Optional;
public class JoinInventory extends InteractableInventory {
private String typedText = "";
private final String prefix = "Suche: ";
public JoinInventory(Player p) {
super(InventoryType.ANVIL, TranslatedComponent.assemble("hub#join_title", p));
setClickableItem(
ItemStack.builder(Material.GREEN_STAINED_GLASS_PANE)
.displayName(Component.text(prefix))
.build(),
0,
itemClick -> {}
);
Hub.INSTANCE.eventNode().addListener(PlayerPacketEvent.class, event -> {
if (event.getPacket() instanceof ClientNameItemPacket packet) {
typedText = packet.itemName();
}
});
}
@Override
protected void onClick(Player player, int slot, ClickType clickType, InventoryConditionResult inventoryConditionResult) {
if(slot != 2) return;
inventoryConditionResult.setCancel(true);
player.closeInventory();
typedText = formatInput(typedText);
Optional<Room> target = Room.getRoom(MinecraftServer.getConnectionManager().findPlayer(typedText));
if(target.isPresent())
Room.setRoom(player, target.get());
else
new ChatMessage(Icon.ERROR).appendTranslated("hub#join_notFound").appendStatic(" " + typedText).send(player);
}
private String formatInput(String raw) {
if(raw.startsWith(prefix)) {
raw = raw.split(prefix)[1];
}
return raw.toLowerCase(Locale.ROOT).trim();
}
}

View File

@ -0,0 +1,163 @@
package eu.mhsl.minenet.minigames.instance.room;
import eu.mhsl.minenet.minigames.Resource;
import eu.mhsl.minenet.minigames.instance.MineNetInstance;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import eu.mhsl.minenet.minigames.score.tournament.Tournament;
import eu.mhsl.minenet.minigames.util.CommonEventHandles;
import eu.mhsl.minenet.minigames.util.MoveInstance;
import eu.mhsl.minenet.minigames.instance.Spawnable;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.hub.Hub;
import eu.mhsl.minenet.minigames.instance.room.entity.GameSelector;
import net.minestom.server.MinecraftServer;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerBlockBreakEvent;
import net.minestom.server.event.player.PlayerDisconnectEvent;
import net.minestom.server.instance.AnvilLoader;
import java.util.*;
import java.util.logging.Logger;
import java.util.stream.Collectors;
public class Room extends MineNetInstance implements Spawnable {
private static final Set<Room> rooms = new HashSet<>();
private static final Map<Player, Room> players = new WeakHashMap<>();
private static final Logger logger = Logger.getLogger("room");
public static Room createRoom(Player owner) {
logger.info("Creating room with owner " + owner.getUsername());
setRoom(owner, new Room(owner));
return getRoom(owner).orElseThrow();
}
public static Room createOwnerlessRoom() {
Room room = new Room();
rooms.add(room);
return room;
}
public static void deleteRoom(Room room) {
logger.info("Deleting room " + room.uniqueId);
rooms.remove(room);
players.values().removeAll(Collections.singleton(room));
room.getAllMembers().forEach(player -> MoveInstance.move(player, Hub.INSTANCE));
MoveInstance.forceCloseInstance(room);
}
public static Optional<Room> getRoom(Player p) {
return Optional.ofNullable(players.get(p));
}
public static Optional<Room> getRoom(UUID uuid) {
return rooms.stream().filter(room -> room.uuid.equals(uuid)).findFirst();
}
public static void setOwnRoom(Player p) {
setRoom(p, getRoom(p).orElseThrow());
}
public static void setRoom(Player p, Room room) {
p.clearEffects();
p.clearTitle();
p.getInventory().clear();
p.setGameMode(GameMode.ADVENTURE);
rooms.add(room);
players.put(p, room);
MoveInstance.move(p, room);
}
public static void unsetRoom(Player p) {
logger.info("Unset room for " + p.getUsername());
players.remove(p);
}
public static Set<Room> getAllRooms() {
return rooms;
}
private Player owner;
public final UUID uuid = UUID.randomUUID();
public final boolean apiDriven;
private GameSelector gameSelector;
private final Tournament tournament = new Tournament();
private Room(Player owner) {
super(Dimension.THE_END.DIMENSION);
this.apiDriven = false;
construct();
setOwner(owner);
}
protected Room() {
super(Dimension.THE_END.DIMENSION);
this.apiDriven = true;
construct();
}
private void construct() {
MinecraftServer.getInstanceManager().registerInstance(this);
setChunkLoader(new AnvilLoader(Resource.LOBBY_MAP.getPath()));
this.gameSelector = new GameSelector();
this.gameSelector.setInstance(this, new Pos(0.5, 50, 19.5));
eventNode().addListener(PlayerBlockBreakEvent.class, CommonEventHandles::cancel);
eventNode().addListener(PlayerDisconnectEvent.class, playerDisconnectEvent -> unsetRoom(playerDisconnectEvent.getPlayer()));
}
public Player getOwner() {
return owner;
}
public void setOwner(Player newOwner) {
this.owner = newOwner;
this.owner.eventNode().addListener(PlayerDisconnectEvent.class, playerDisconnectEvent -> {
if(this.apiDriven) return; // prevent giving ownership on apiDriven rooms
Player p = playerDisconnectEvent.getPlayer();
if(p != this.owner) return;
getAllMembers().stream()
.filter(player -> player != p) // exclude the current leaving owner
.findFirst()
.ifPresentOrElse(
this::setOwner,
() -> Room.getRoom(p).ifPresent(Room::deleteRoom) // delete room if no players are left
);
Room.unsetRoom(p);
new ChatMessage(Icon.ERROR).appendStatic("The room leader left!").send(getAllMembers());
new ChatMessage(Icon.SCIENCE).appendStatic(this.owner.getUsername()).appendStatic(" is the new Leader!").send(getAllMembers().stream().filter(player -> player != this.owner).collect(Collectors.toSet()));
new ChatMessage(Icon.SUCCESS).appendStatic("You are now the leader.").send(this.owner);
});
}
public void moveMembersToInstance(Spawnable game) {
this.getAllMembers().forEach(player -> MoveInstance.move(player, game));
}
public void moveMembersToRoomLobby() {
this.getAllMembers().forEach(player -> MoveInstance.move(player, this));
}
public Set<Player> getAllMembers() {
return players.keySet().stream()
.filter(player -> getRoom(player).orElse(null) == this)
.collect(Collectors.toSet());
}
public Tournament getTournament() {
return this.tournament;
}
@Override
public Pos getSpawn() {
return new Pos(0.5, 50, 0.5);
}
}

View File

@ -0,0 +1,46 @@
package eu.mhsl.minenet.minigames.instance.room.entity;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.instance.room.inventory.MinigameSelectInventory;
import eu.mhsl.minenet.minigames.message.Icon;
import eu.mhsl.minenet.minigames.message.type.ChatMessage;
import eu.mhsl.minenet.minigames.shared.entity.InteractableEntity;
import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.metadata.villager.AbstractVillagerMeta;
import net.minestom.server.event.entity.EntityAttackEvent;
import net.minestom.server.event.instance.AddEntityToInstanceEvent;
import net.minestom.server.event.player.PlayerEntityInteractEvent;
import org.jetbrains.annotations.NotNull;
public class GameSelector extends InteractableEntity {
final AbstractVillagerMeta abstractVillagerMeta;
public GameSelector() {
super(EntityType.VILLAGER);
abstractVillagerMeta = (AbstractVillagerMeta) this.getEntityMeta();
}
@Override
public void onSpawn(@NotNull AddEntityToInstanceEvent addEntityToInstanceEvent) {
}
@Override
public void onAttack(@NotNull EntityAttackEvent entityAttackEvent) {
super.onAttack(entityAttackEvent);
}
@Override
public void onInteract(@NotNull PlayerEntityInteractEvent playerEntityInteractEvent) {
Room room = (Room) instance;
if(playerEntityInteractEvent.getPlayer() != room.getOwner()) {
abstractVillagerMeta.setHeadShakeTimer(20);
new ChatMessage(Icon.ERROR).appendStatic("Only the room leader can start games!").send(playerEntityInteractEvent.getPlayer());
return;
}
playerEntityInteractEvent.getPlayer().openInventory(new MinigameSelectInventory(room, playerEntityInteractEvent.getPlayer()));
}
}

View File

@ -0,0 +1,70 @@
package eu.mhsl.minenet.minigames.instance.room.inventory;
import eu.mhsl.minenet.minigames.instance.game.GameList;
import eu.mhsl.minenet.minigames.instance.game.GameType;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameConfigurationInventory;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory;
import eu.mhsl.minenet.minigames.instance.room.Room;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import eu.mhsl.minenet.minigames.shared.inventory.InteractableInventory;
import eu.mhsl.minenet.minigames.util.InventoryItemAlignment;
import net.minestom.server.entity.Player;
import net.minestom.server.inventory.InventoryType;
import net.minestom.server.item.ItemHideFlag;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import java.util.Arrays;
import java.util.List;
public class MinigameSelectInventory extends InteractableInventory {
final private Room room;
final private Player p;
public MinigameSelectInventory(Room room, Player p) {
super(InventoryType.CHEST_6_ROW, TranslatedComponent.assemble("room#invTitle", p));
this.room = room;
this.p = p;
InventoryItemAlignment itemAlignment = new InventoryItemAlignment(GameType.values().length, 1);
for (GameType type : GameType.values()) {
setClickableItem(
ItemStack.builder(type.getIcon())
.displayName(type.getTitle().getAssembled(p))
.lore(type.getDescription().addWrap().getWrappedAssembled(p))
.build(),
itemAlignment.next().get(),
itemClick -> drawGames(type)
);
}
for(int i = 9; i <= 17; i++) {
setDummyItem(Material.CYAN_STAINED_GLASS_PANE, i);
}
}
private void drawGames(GameType type) {
for(int i = 18; i <= 53; i++) {
setDummyItem(Material.AIR, i);
}
int offset = 18;
List<GameList> games = Arrays.stream(GameList.values()).filter(gameList -> gameList.getType().equals(type)).toList();
InventoryItemAlignment itemAlignment = new InventoryItemAlignment(games.size(), 4);
for (GameList game : games) {
GameFactory gameFactory = game.getFactory();
setClickableItem(
ItemStack.builder(gameFactory.symbol())
.displayName(gameFactory.name().getAssembled(p))
.lore(gameFactory.description().addWrap().getWrappedAssembled(p))
.meta(metaBuilder -> metaBuilder.hideFlag(ItemHideFlag.HIDE_ATTRIBUTES))
.build(),
offset + itemAlignment.next().get(),
itemClick -> itemClick.getPlayer().openInventory(new GameConfigurationInventory(room, itemClick.getPlayer(), gameFactory))
);
}
}
}

View File

@ -0,0 +1,22 @@
package eu.mhsl.minenet.minigames.instance.transfer;
import eu.mhsl.minenet.minigames.instance.Dimension;
import eu.mhsl.minenet.minigames.instance.MineNetInstance;
import eu.mhsl.minenet.minigames.instance.Spawnable;
import eu.mhsl.minenet.minigames.util.CommonEventHandles;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.instance.block.Block;
public class Transfer extends MineNetInstance implements Spawnable {
public Transfer() {
super(Dimension.THE_END.DIMENSION);
eventNode().addListener(PlayerMoveEvent.class, CommonEventHandles::cancel);
setBlock(0, 0, 0, Block.BARRIER);
}
@Override
public Pos getSpawn() {
return new Pos(0.5, 1, 0.5);
}
}

View File

@ -0,0 +1,12 @@
package eu.mhsl.minenet.minigames.lang;
public class DummyLang extends Lang {
public DummyLang() {
super("dummy");
}
@Override
public String getEntry(String key) {
return "missingI18n:" + key;
}
}

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