commit 3344047d8958173306465ad4d974e983399dcffd Author: Patrick <147879351+WinniePatGG@users.noreply.github.com> Date: Fri May 1 19:29:02 2026 +0200 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..39aad1a --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.idea/ +*.iml +out/ +srv/ +build/ +*.log +.gradle/ +.DS_Store diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..958ab94 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 WinniePatGG + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f9837b1 --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# SMPPlugin + +1.21.4 Plugin for my SMP Server. This plugin is hardcoded for my Server. If you want to use it, go ahead. You'll need to change some stuff in the code + +## 💡 Features + +- Black Market +- Block Elevators (Currently broken) +- Bloodmoon (Stronger, Faster mobs) +- Polls +- Player reports (With db and gui) +- Starter (To start the Event) +- Suggestions (With db and gui) +- waypoints (Commands) +- trash (Delete Items) +- Coding Tag (/coding on) Requires a Resourcepack (Maybe I'll provide it) + +## 🚀 Getting Started + +### Prerequisites + +- Java 21 +- Gradle +- Paper 1.21.4 Server +- (Not needed but recommended) Resourcepack + +### Build & Run + +```bash +# Clone the repository +git clone https://github.com/WinniePatGG/SMPPlugin.git + +# Navigate into the project folder +cd SMPPlugin + +# Build the plugin (example using Gradle) +./gradlew build +``` +Made with ❤️ by WinniePatGG. \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..59068e5 --- /dev/null +++ b/build.gradle @@ -0,0 +1,70 @@ +plugins { + id 'java' + id("xyz.jpenilla.run-paper") version "2.3.1" +} + +group = 'de.winniepat' +version = '1.0' + +repositories { + mavenCentral() + maven { url = 'https://repo.papermc.io/repository/maven-public/' } + maven { url = 'https://jitpack.io' } + maven { url 'https://repo.lucko.me' } +} + +dependencies { + compileOnly('io.papermc.paper:paper-api:1.21.4-R0.1-SNAPSHOT') + implementation 'org.xerial:sqlite-jdbc:3.42.0.0' + + compileOnly('com.github.MilkBowl:VaultAPI:1.7') { + exclude group: 'org.bukkit', module: 'bukkit' + } + compileOnly 'net.luckperms:api:5.4' +} + +tasks { + runServer { + minecraftVersion("1.21") + } +} + +def targetJavaVersion = 21 +java { + def javaVersion = JavaVersion.toVersion(targetJavaVersion) + sourceCompatibility = javaVersion + targetCompatibility = javaVersion + if (JavaVersion.current() < javaVersion) { + toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) + } +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' + + if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { + options.release.set(targetJavaVersion) + } +} + +processResources { + def props = [version: version] + inputs.properties props + filteringCharset 'UTF-8' + filesMatching('plugin.yml') { + expand props + } + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + from(sourceSets.main.resources.srcDirs) { + include '**/*.yml' + } +} + +tasks.register('copyPlugin', Copy) { + dependsOn build + from("$buildDir/libs") + include('*.jar') + into("$rootDir/srv/plugins") +} + +build.finalizedBy(copyPlugin) \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..e69de29 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e644113 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a441313 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..b740cf1 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/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 + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + 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 +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# 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"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..25da30d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@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=. +@rem This is normally unused +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% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 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! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..16aa99d --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'SMPPlugin' diff --git a/src/main/java/de/winniepat/SMPPlugin/SMPPlugin.java b/src/main/java/de/winniepat/SMPPlugin/SMPPlugin.java new file mode 100644 index 0000000..13fada5 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/SMPPlugin.java @@ -0,0 +1,238 @@ +package de.winniepat.SMPPlugin; + +import de.winniepat.SMPPlugin.blackmarket.*; +import de.winniepat.SMPPlugin.blockelevator.*; +import de.winniepat.SMPPlugin.bloodmoon.*; +import de.winniepat.SMPPlugin.commands.*; +import de.winniepat.SMPPlugin.listeners.*; +import de.winniepat.SMPPlugin.polls.*; +import de.winniepat.SMPPlugin.report.*; +import de.winniepat.SMPPlugin.report.commands.*; +import de.winniepat.SMPPlugin.starter.MoveEvent; +import de.winniepat.SMPPlugin.starter.StartCommand; +import de.winniepat.SMPPlugin.suggestions.*; +import de.winniepat.SMPPlugin.suggestions.commands.*; +import de.winniepat.SMPPlugin.waypoints.*; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.command.*; +import org.bukkit.configuration.file.*; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitRunnable; + +import java.io.File; +import java.sql.SQLException; +import java.util.*; + +public final class SMPPlugin extends JavaPlugin { + private static SMPPlugin instance; + private WaypointsDatabase waypointsDatabase; + private ReportDatabase reportDatabase; + private SuggestionManager suggestionManager; + CodingCommand codingCommand = new CodingCommand(this, this); + private FileConfiguration messages; + private BloodmoonManager bloodmoonManager; + public static Set frozenPlayers = new HashSet<>(); + + @Override + public void onEnable() { + saveDefaultMessages(); + loadMessages(); + instance = this; + + File dbFile = new File(getDataFolder(), "suggestions.db"); + try { + this.suggestionManager = new SuggestionManager(dbFile); + } catch (SQLException e) { + throw new RuntimeException(e); + } + + registerWaypoints(); + getLogger().info("Waypoints registered"); + registerReportDatabase(); + getLogger().info("Reports registered"); + registerBloodMoon(); + getLogger().info("BloodMoon registered"); + registerCommands(); + getLogger().info("Commands registered"); + registerListeners(); + getLogger().info("Listener registered"); + registerPollsystem(); + getLogger().info("PollSystem registered"); + registerSuggestionDatabase(); + getLogger().info("Suggestion Database registered"); + } + + @Override + public void onDisable() { + getLogger().warning("Ach Leck Eier!"); + bloodmoonManager.endBloodmoon(); + } + + private void registerCommands() { + Objects.requireNonNull(getCommand("trash")).setExecutor(new TrashCommand(this, this)); + Objects.requireNonNull(getCommand("report")).setExecutor(new ReportCommand(reportDatabase, this)); + Objects.requireNonNull(getCommand("reports")).setExecutor(new ReportsCommand(this, reportDatabase, this)); + Objects.requireNonNull(getCommand("suggestion")).setExecutor(new SuggestionCommand(suggestionManager, this, this)); + Objects.requireNonNull(getCommand("suggestions")).setExecutor(new SuggestionListCommand(suggestionManager)); + Objects.requireNonNull(getCommand("suggestionstatus")).setExecutor(new SuggestionStatusCommand(suggestionManager, this)); + Objects.requireNonNull(getCommand("coding")).setExecutor(new CodingCommand(this, this)); + Objects.requireNonNull(getCommand("suggestionsgui")).setExecutor(this::handleGuiCommand); + Objects.requireNonNull(getCommand("bloodmoon")).setExecutor(new BloodmoonCommand(bloodmoonManager)); + Objects.requireNonNull(getCommand("blackmarket")).setExecutor(new BlackMarketCommand(this)); + Objects.requireNonNull(getCommand("getwaypoint")).setExecutor(new WaypointCommands(waypointsDatabase, this)); + Objects.requireNonNull(getCommand("addwaypoint")).setExecutor(new WaypointCommands(waypointsDatabase, this)); + Objects.requireNonNull(getCommand("smp")).setExecutor(new StartCommand()); + Objects.requireNonNull(getCommand("spawn")).setExecutor(new SpawnCommand()); + Objects.requireNonNull(getCommand("shop")).setExecutor(new ShopCommand()); + } + + private boolean handleGuiCommand(CommandSender sender, Command command, String label, String[] args) { + if (sender instanceof Player player) { + new SuggestionGui(suggestionManager, this).open(player, null); + } else { + sender.sendMessage("§cOnly players can open the GUI."); + } + return true; + } + + private void registerListeners() { + getServer().getPluginManager().registerEvents(new CodingCleanupListener(codingCommand), this); + getServer().getPluginManager().registerEvents(new BlackMarketEffectListener(this), this); + getServer().getPluginManager().registerEvents(new WaypointListener(waypointsDatabase, this), this); + getServer().getPluginManager().registerEvents(new PlayerJumpListener(), this); + getServer().getPluginManager().registerEvents(new PlayerSneakListener(), this); + getServer().getPluginManager().registerEvents(new MoveEvent(), this); + getServer().getPluginManager().registerEvents(new QuitLightningListener(), this); + Bukkit.getPluginManager().registerEvents(new SuggestionGui(suggestionManager, this), this); + Bukkit.getPluginManager().registerEvents(new FirstPlayerJoinTracker(this), this); + } + + private void registerPollsystem() { + File pollDataFile = new File(getDataFolder(), "polls.yml"); + PollManager pollManager = new PollManager(pollDataFile); + if (!pollDataFile.getParentFile().exists()) { + pollDataFile.getParentFile().mkdirs(); + } + Objects.requireNonNull(getCommand("pollstart")).setExecutor(new PollCommand(pollManager, this)); + Objects.requireNonNull(getCommand("pollvote")).setExecutor((sender, cmd, label, args) -> { + if (sender instanceof Player player) { + new PollVoteGUI(pollManager, this).openVoteGUI(player); + } + return true; + }); + Objects.requireNonNull(getCommand("pollresults")).setExecutor((sender, cmd, label, args) -> { + if (sender instanceof Player player && player.hasPermission("poll.view")) { + new PollResultGUI(pollManager).openResultsGUI(player); + } + return true; + }); + getServer().getPluginManager().registerEvents(new PollVoteGUI(pollManager, this), this); + } + + private void registerSuggestionDatabase() { + if (!getDataFolder().exists()) { + getDataFolder().mkdirs(); + } + File dbFile = new File(getDataFolder(), "suggestions.db"); + try { + suggestionManager = new SuggestionManager(dbFile); + } catch (SQLException e) { + getLogger().severe("Couldn't initialize the Suggestion Database."); + e.printStackTrace(); + return; + } + getLogger().info("Suggestion Database connected successfully"); + } + + private void saveDefaultMessages() { + if (!getDataFolder().exists()) { + getDataFolder().mkdirs(); + } + + File file = new File(getDataFolder(), "messages.yml"); + if (!file.exists()) { + saveResource("messages.yml", false); + } + } + + private void loadMessages() { + File file = new File(getDataFolder(), "messages.yml"); + messages = YamlConfiguration.loadConfiguration(file); + + Bukkit.getLogger().info("[DEBUG] Loaded keys: " + messages.getKeys(true)); + } + + public String getMessage(String key, Map placeholders) { + if (!messages.contains(key)) { + getLogger().warning("Missing message key: " + key); + getLogger().warning("Available keys: " + messages.getKeys(true)); + return ChatColor.RED + "Message not found: " + key; + } + + String msg = messages.getString(key); + + for (Map.Entry entry : placeholders.entrySet()) { + msg = msg.replace("%" + entry.getKey() + "%", entry.getValue()); + } + + return ChatColor.translateAlternateColorCodes('&', msg); + } + + private void checkForElevator(Player player) { + Block blockBelow = player.getLocation().subtract(0, 0.1, 0).getBlock(); + if (blockBelow.getType() != Material.LIGHT_WEIGHTED_PRESSURE_PLATE) return; + + // Handle sneaking (go down) + if (player.isSneaking() && !CooldownManager.isOnCooldown(player.getUniqueId())) { + for (int y = blockBelow.getY() - 1; y >= 0; y--) { + Block below = player.getWorld().getBlockAt(blockBelow.getX(), y, blockBelow.getZ()); + if (below.getType() == Material.LIGHT_WEIGHTED_PRESSURE_PLATE) { + player.teleport(new Location(player.getWorld(), below.getX() + 0.5, y + 1, below.getZ() + 0.5)); + player.playSound(player.getLocation(), Sound.BLOCK_PISTON_CONTRACT, 1f, 1f); + player.getWorld().spawnParticle(Particle.CLOUD, player.getLocation(), 20, 0.3, 0.3, 0.3); + CooldownManager.setCooldown(player.getUniqueId()); + return; + } + } + player.sendMessage(ChatColor.YELLOW + "No elevator block below!"); + CooldownManager.setCooldown(player.getUniqueId()); + } + + if (player.getVelocity().getY() > 0.2 && !CooldownManager.isOnCooldown(player.getUniqueId())) { + for (int y = blockBelow.getY() + 2; y <= player.getWorld().getMaxHeight(); y++) { + Block above = player.getWorld().getBlockAt(blockBelow.getX(), y, blockBelow.getZ()); + if (above.getType() == Material.LIGHT_WEIGHTED_PRESSURE_PLATE) { + player.teleport(new Location(player.getWorld(), above.getX() + 0.5, y + 1, above.getZ() + 0.5)); + player.playSound(player.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1f, 1f); + player.getWorld().spawnParticle(Particle.PORTAL, player.getLocation(), 30, 0.5, 1, 0.5); + CooldownManager.setCooldown(player.getUniqueId()); + return; + } + } + player.sendMessage(ChatColor.YELLOW + "No elevator block above!"); + CooldownManager.setCooldown(player.getUniqueId()); + } + } + + private void registerBloodMoon() { + this.bloodmoonManager = new BloodmoonManager(this); + getServer().getPluginManager().registerEvents(new BloodmoonListener(bloodmoonManager, this), this); + new BloodmoonTask(this, bloodmoonManager).start(); + + } + + private void registerWaypoints() { + reportDatabase = new ReportDatabase(this); + } + + private void registerReportDatabase() { + reportDatabase = new ReportDatabase(this); + } + + public static SMPPlugin getInstance() { + return instance; + } + +} diff --git a/src/main/java/de/winniepat/SMPPlugin/blackmarket/BlackMarketCommand.java b/src/main/java/de/winniepat/SMPPlugin/blackmarket/BlackMarketCommand.java new file mode 100644 index 0000000..0282dde --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/blackmarket/BlackMarketCommand.java @@ -0,0 +1,29 @@ +package de.winniepat.SMPPlugin.blackmarket; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; + +public class BlackMarketCommand implements CommandExecutor { + + private final BlackMarketManager manager; + + public BlackMarketCommand(JavaPlugin plugin) { + this.manager = new BlackMarketManager(plugin); + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player player)) { + sender.sendMessage("This command can only be used by players."); + return true; + } + + manager.spawnMarket(); + player.sendMessage(ChatColor.DARK_PURPLE + "You have summoned the Black Market."); + return true; + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/blackmarket/BlackMarketEffectListener.java b/src/main/java/de/winniepat/SMPPlugin/blackmarket/BlackMarketEffectListener.java new file mode 100644 index 0000000..138edd9 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/blackmarket/BlackMarketEffectListener.java @@ -0,0 +1,132 @@ +package de.winniepat.SMPPlugin.blackmarket; + +import org.bukkit.*; +import org.bukkit.entity.*; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.*; +import org.bukkit.event.player.PlayerExpChangeEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.Random; + +public class BlackMarketEffectListener implements Listener { + + private final JavaPlugin plugin; + private final Random random = new Random(); + private final NamespacedKey catchKey; + + public BlackMarketEffectListener(JavaPlugin plugin) { + this.plugin = plugin; + this.catchKey = new NamespacedKey(plugin, "blackmarket_catch"); + + new BukkitRunnable() { + @Override + public void run() { + for (Player p : Bukkit.getOnlinePlayers()) { + checkCatch(p, EquipmentSlot.HEAD, "hunger_drain", () -> { + if (p.getFoodLevel() > 1) p.setFoodLevel(p.getFoodLevel() - 1); + }); + checkCatch(p, EquipmentSlot.HAND, "burn_in_day", () -> { + if (isDaylight(p)) p.setFireTicks(40); + }); + checkCatch(p, EquipmentSlot.FEET, "random_blindness", () -> { + if (random.nextInt(100) < 5) + p.addPotionEffect(new PotionEffect(PotionEffectType.BLINDNESS, 40, 0)); + }); + } + } + }.runTaskTimer(plugin, 0L, 40L); + } + + @EventHandler + public void onEntityDamage(EntityDamageByEntityEvent event) { + if (!(event.getDamager() instanceof Player p)) return; + ItemStack item = p.getInventory().getItemInMainHand(); + String catchId = getCatch(item); + + if (catchId == null) return; + + switch (catchId) { + case "self_damage" -> { + p.damage(2); + p.sendMessage("§cThe weapon wounds you!"); + } + case "unstable" -> { + if (random.nextInt(100) < 10) + p.getWorld().createExplosion(p.getLocation(), 2f, false, false, p); + } + case "lightning_touch" -> { + Entity target = event.getEntity(); + target.getWorld().strikeLightningEffect(target.getLocation()); + } + case "hurt_nearby_allies" -> { + for (Player nearby : p.getWorld().getPlayers()) { + if (!nearby.equals(p) && nearby.getLocation().distance(p.getLocation()) < 5) + nearby.damage(2); + } + } + case "levitate_on_crit" -> { + if (p.getAttackCooldown() > 0.9f) + p.addPotionEffect(new PotionEffect(PotionEffectType.LEVITATION, 40, 1)); + } + case "arrow_bounceback" -> { + if (p.getInventory().getItemInMainHand().getType().toString().endsWith("BOW") && random.nextInt(100) < 20) { + p.damage(2); + p.sendMessage(ChatColor.DARK_RED + "Your cursed arrow rebounds!"); + } + } + } + } + + @EventHandler + public void onHitTaken(EntityDamageEvent event) { + if (!(event.getEntity() instanceof Player p)) return; + checkCatch(p, EquipmentSlot.CHEST, "slowness_when_hit", () -> { + p.addPotionEffect(new PotionEffect(PotionEffectType.SLOW_FALLING, 60, 2)); + }); + checkCatch(p, EquipmentSlot.FEET, "more_knockback", () -> { + p.setVelocity(p.getVelocity().multiply(2)); + }); + } + + @EventHandler + public void onXpGain(PlayerExpChangeEvent event) { + Player p = event.getPlayer(); + checkCatch(p, EquipmentSlot.HEAD, "xp_boost", () -> { + event.setAmount(event.getAmount() * 2); + }); + } + + private void checkCatch(Player p, EquipmentSlot slot, String catchId, Runnable action) { + ItemStack item = p.getInventory().getItem(slot); + if (item != null && item.hasItemMeta()) { + ItemMeta meta = item.getItemMeta(); + if (meta.getPersistentDataContainer().has(catchKey, PersistentDataType.STRING)) { + String tag = meta.getPersistentDataContainer().get(catchKey, PersistentDataType.STRING); + if (tag != null && tag.equals(catchId)) { + action.run(); + } + } + } + } + + private String getCatch(ItemStack item) { + if (item == null || !item.hasItemMeta()) return null; + ItemMeta meta = item.getItemMeta(); + if (!meta.getPersistentDataContainer().has(catchKey, PersistentDataType.STRING)) return null; + return meta.getPersistentDataContainer().get(catchKey, PersistentDataType.STRING); + } + + private boolean isDaylight(Player player) { + long time = player.getWorld().getTime(); + return time < 12300 || time > 23850; + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/blackmarket/BlackMarketItems.java b/src/main/java/de/winniepat/SMPPlugin/blackmarket/BlackMarketItems.java new file mode 100644 index 0000000..ac24462 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/blackmarket/BlackMarketItems.java @@ -0,0 +1,163 @@ +package de.winniepat.SMPPlugin.blackmarket; + +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.NamespacedKey; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class BlackMarketItems { + + public static List getRandomOffers(JavaPlugin plugin, int count) { + List all = new ArrayList<>(); + all.add(bloodBlade(plugin)); + all.add(xpHood(plugin)); + all.add(vampireAxe(plugin)); + all.add(swiftBoots(plugin)); + all.add(witherBow(plugin)); + all.add(unstableBlade(plugin)); + all.add(lightningAxe(plugin)); + all.add(bounceBow(plugin)); + all.add(levitateChest(plugin)); + Collections.shuffle(all); + return all.subList(0, Math.min(count, all.size())); + } + + private static BlackMarketOffer bloodBlade(JavaPlugin plugin) { + ItemStack item = new ItemStack(Material.NETHERITE_SWORD); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(ChatColor.DARK_RED + "Blood Blade"); + meta.addEnchant(Enchantment.SHARPNESS, 5, true); + meta.addEnchant(Enchantment.FIRE_ASPECT, 2, true); + meta.setLore(List.of("§cDeals devastating damage.", "§7But harms the wielder on hit.")); + meta.getPersistentDataContainer().set(new NamespacedKey(plugin, "blackmarket_catch"), + PersistentDataType.STRING, "self_damage"); + item.setItemMeta(meta); + return new BlackMarketOffer(item, List.of( + new ItemStack(Material.NETHERITE_INGOT, 3), + new ItemStack(Material.ROTTEN_FLESH, 64) + ), "self_damage"); + } + + private static BlackMarketOffer xpHood(JavaPlugin plugin) { + ItemStack item = new ItemStack(Material.NETHERITE_HELMET); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(ChatColor.GOLD + "Hood of Hunger"); + meta.addEnchant(Enchantment.THORNS, 3, true); + meta.setLore(List.of("§eBoosts XP gain.", "§7But constantly drains hunger.")); + meta.getPersistentDataContainer().set(new NamespacedKey(plugin, "blackmarket_catch"), + PersistentDataType.STRING, "hunger_drain"); + item.setItemMeta(meta); + return new BlackMarketOffer(item, List.of( + new ItemStack(Material.EXPERIENCE_BOTTLE, 32) + ), "hunger_drain"); + } + + private static BlackMarketOffer vampireAxe(JavaPlugin plugin) { + ItemStack item = new ItemStack(Material.NETHERITE_AXE); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(ChatColor.DARK_PURPLE + "Vampire Axe"); + meta.addEnchant(Enchantment.SHARPNESS, 5, true); + meta.setLore(List.of("§cHeals on hit.", "§7Burns in sunlight.")); + meta.getPersistentDataContainer().set(new NamespacedKey(plugin, "blackmarket_catch"), + PersistentDataType.STRING, "burn_in_day"); + item.setItemMeta(meta); + return new BlackMarketOffer(item, List.of( + new ItemStack(Material.EMERALD, 20) + ), "burn_in_day"); + } + + private static BlackMarketOffer swiftBoots(JavaPlugin plugin) { + ItemStack item = new ItemStack(Material.NETHERITE_BOOTS); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(ChatColor.AQUA + "Boots of Swiftness"); + meta.addEnchant(Enchantment.FEATHER_FALLING, 4, true); + meta.setLore(List.of("§bIncreases speed.", "§7Causes knockback vulnerability.")); + meta.getPersistentDataContainer().set(new NamespacedKey(plugin, "blackmarket_catch"), + PersistentDataType.STRING, "more_knockback"); + item.setItemMeta(meta); + return new BlackMarketOffer(item, List.of( + new ItemStack(Material.EMERALD, 15) + ), "more_knockback"); + } + + private static BlackMarketOffer witherBow(JavaPlugin plugin) { + ItemStack item = new ItemStack(Material.BOW); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(ChatColor.DARK_GRAY + "Withering Bow"); + meta.addEnchant(Enchantment.POWER, 5, true); + meta.setLore(List.of("§7Applies wither.", "§cHurts allies nearby.")); + meta.getPersistentDataContainer().set(new NamespacedKey(plugin, "blackmarket_catch"), + PersistentDataType.STRING, "hurt_nearby_allies"); + item.setItemMeta(meta); + return new BlackMarketOffer(item, List.of( + new ItemStack(Material.EMERALD, 25) + ), "hurt_nearby_allies"); + } + + private static BlackMarketOffer unstableBlade(JavaPlugin plugin) { + ItemStack item = new ItemStack(Material.DIAMOND_SWORD); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(ChatColor.DARK_PURPLE + "Blade of Instability"); + meta.addEnchant(Enchantment.SHARPNESS, 6, true); + meta.setLore(List.of("§5Massive power.", "§cMight explode on hit.")); + meta.getPersistentDataContainer().set(new NamespacedKey(plugin, "blackmarket_catch"), + PersistentDataType.STRING, "unstable"); + item.setItemMeta(meta); + return new BlackMarketOffer(item, List.of( + new ItemStack(Material.NETHERITE_INGOT, 2), + new ItemStack(Material.TNT, 16) + ), "unstable"); + } + + private static BlackMarketOffer lightningAxe(JavaPlugin plugin) { + ItemStack item = new ItemStack(Material.NETHERITE_AXE); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(ChatColor.GOLD + "Lightning Touch Axe"); + meta.addEnchant(Enchantment.SHARPNESS, 5, true); + meta.setLore(List.of("§eElectrocutes enemies.", "§7Risk of chain lightning.")); + meta.getPersistentDataContainer().set(new NamespacedKey(plugin, "blackmarket_catch"), + PersistentDataType.STRING, "lightning_touch"); + item.setItemMeta(meta); + return new BlackMarketOffer(item, List.of( + new ItemStack(Material.GOLD_BLOCK, 4) + ), "lightning_touch"); + } + + private static BlackMarketOffer bounceBow(JavaPlugin plugin) { + ItemStack item = new ItemStack(Material.BOW); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(ChatColor.GRAY + "Rebounding Bow"); + meta.addEnchant(Enchantment.POWER, 4, true); + meta.addEnchant(Enchantment.PUNCH, 1, true); + meta.setLore(List.of("§7Sometimes bounces arrows back... on YOU.")); + meta.getPersistentDataContainer().set(new NamespacedKey(plugin, "blackmarket_catch"), + PersistentDataType.STRING, "arrow_bounceback"); + item.setItemMeta(meta); + return new BlackMarketOffer(item, List.of( + new ItemStack(Material.SPECTRAL_ARROW, 64) + ), "arrow_bounceback"); + } + + private static BlackMarketOffer levitateChest(JavaPlugin plugin) { + ItemStack item = new ItemStack(Material.NETHERITE_CHESTPLATE); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(ChatColor.DARK_AQUA + "Wings of the Void"); + meta.addEnchant(Enchantment.PROTECTION, 4, true); + meta.setLore(List.of("§3Grants levitation on crit.", "§8Leaves you floating.")); + meta.getPersistentDataContainer().set(new NamespacedKey(plugin, "blackmarket_catch"), + PersistentDataType.STRING, "levitate_on_crit"); + item.setItemMeta(meta); + return new BlackMarketOffer(item, List.of( + new ItemStack(Material.ENDER_PEARL, 16), + new ItemStack(Material.ELYTRA, 1) + ), "levitate_on_crit"); + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/blackmarket/BlackMarketManager.java b/src/main/java/de/winniepat/SMPPlugin/blackmarket/BlackMarketManager.java new file mode 100644 index 0000000..bbb7c93 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/blackmarket/BlackMarketManager.java @@ -0,0 +1,93 @@ +package de.winniepat.SMPPlugin.blackmarket; + +import org.bukkit.*; +import org.bukkit.entity.Player; +import org.bukkit.entity.Villager; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.Random; + +public class BlackMarketManager { + + private final JavaPlugin plugin; + + public BlackMarketManager(JavaPlugin plugin) { + this.plugin = plugin; + } + + public void spawnMarket() { + Location spawnLoc = getRandomSpawnLocation(); + Villager trader = spawnBlackMarketVillager(spawnLoc); + + Bukkit.broadcastMessage(ChatColor.GRAY + "☠ A mysterious trader has appeared nearby..."); + + String coords = String.format("X: %d, Y: %d, Z: %d", + spawnLoc.getBlockX(), spawnLoc.getBlockY(), spawnLoc.getBlockZ()); + + for (Player player : Bukkit.getOnlinePlayers()) { + if (player.getLocation().distance(spawnLoc) <= 200) { + player.sendMessage(ChatColor.GRAY + "§7The trader lurks around §f" + coords); + } + } + + new BukkitRunnable() { + @Override + public void run() { + if (trader == null || trader.isDead() || !trader.isValid()) { + cancel(); + return; + } + + trader.getWorld().spawnParticle( + Particle.ENCHANT, + trader.getLocation().add(0, 2, 0), + 20, + 0.5, 0.5, 0.5, + 0.1 + ); + } + }.runTaskTimer(plugin, 0L, 20L); + + new BukkitRunnable() { + @Override + public void run() { + if (trader != null && trader.isValid()) { + trader.remove(); + Bukkit.broadcastMessage(ChatColor.GRAY + "The Black Market fades into the shadows..."); + } + } + }.runTaskLater(plugin, 20 * 60 * 5); + } + + private Villager spawnBlackMarketVillager(Location loc) { + Villager v = loc.getWorld().spawn(loc, Villager.class); + v.setAI(false); + v.setInvulnerable(true); + v.setCustomName(ChatColor.DARK_PURPLE + "Black Market"); + v.setCustomNameVisible(true); + v.setProfession(Villager.Profession.NITWIT); + v.setVillagerLevel(5); + v.setVillagerExperience(1); + v.setCanPickupItems(false); + + BlackMarketTradeHandler.applyTrades(v, plugin); + + return v; + } + + private Location getRandomSpawnLocation() { + World world = Bukkit.getWorlds().get(0); + Random random = new Random(); + + int baseX = -635; + int baseZ = -837; + int radius = 100; + + int x = baseX + random.nextInt(radius * 2 + 1) - radius; + int z = baseZ + random.nextInt(radius * 2 + 1) - radius; + int y = world.getHighestBlockYAt(x, z) + 1; + + return new Location(world, x + 0.5, y, z + 0.5); + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/blackmarket/BlackMarketOffer.java b/src/main/java/de/winniepat/SMPPlugin/blackmarket/BlackMarketOffer.java new file mode 100644 index 0000000..3469fc0 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/blackmarket/BlackMarketOffer.java @@ -0,0 +1,17 @@ +package de.winniepat.SMPPlugin.blackmarket; + +import org.bukkit.inventory.ItemStack; + +import java.util.List; + +public class BlackMarketOffer { + public final ItemStack item; + public final List price; + public final String catchId; + + public BlackMarketOffer(ItemStack item, List price, String catchId) { + this.item = item; + this.price = price; + this.catchId = catchId; + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/blackmarket/BlackMarketTradeHandler.java b/src/main/java/de/winniepat/SMPPlugin/blackmarket/BlackMarketTradeHandler.java new file mode 100644 index 0000000..cb3630f --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/blackmarket/BlackMarketTradeHandler.java @@ -0,0 +1,30 @@ +package de.winniepat.SMPPlugin.blackmarket; + +import org.bukkit.entity.Villager; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.ArrayList; +import java.util.List; + +public class BlackMarketTradeHandler { + + public static void applyTrades(Villager v, JavaPlugin plugin) { + List recipes = new ArrayList<>(); + + for (BlackMarketOffer offer : BlackMarketItems.getRandomOffers(plugin, 3)) { + MerchantRecipe recipe = new MerchantRecipe(offer.item, 9999); + recipe.setUses(0); + recipe.setExperienceReward(false); + for (ItemStack ingredient : offer.price) { + recipe.addIngredient(ingredient); + } + recipes.add(recipe); + } + + v.setRecipes(recipes); + } + + +} diff --git a/src/main/java/de/winniepat/SMPPlugin/blockelevator/CooldownManager.java b/src/main/java/de/winniepat/SMPPlugin/blockelevator/CooldownManager.java new file mode 100644 index 0000000..afc5456 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/blockelevator/CooldownManager.java @@ -0,0 +1,19 @@ +package de.winniepat.SMPPlugin.blockelevator; + +import java.util.HashMap; +import java.util.UUID; + +public class CooldownManager { + private static final HashMap cooldowns = new HashMap<>(); + private static final long COOLDOWN_TIME_MS = 2; + + public static boolean isOnCooldown(UUID player) { + if (!cooldowns.containsKey(player)) return false; + long lastUsed = cooldowns.get(player); + return (System.currentTimeMillis() - lastUsed) < COOLDOWN_TIME_MS; + } + + public static void setCooldown(UUID player) { + cooldowns.put(player, System.currentTimeMillis()); + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/blockelevator/PlayerJumpListener.java b/src/main/java/de/winniepat/SMPPlugin/blockelevator/PlayerJumpListener.java new file mode 100644 index 0000000..6b92f9f --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/blockelevator/PlayerJumpListener.java @@ -0,0 +1,46 @@ +package de.winniepat.SMPPlugin.blockelevator; + +import de.winniepat.SMPPlugin.SMPPlugin; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.metadata.FixedMetadataValue; + +public class PlayerJumpListener implements Listener { + + @EventHandler + public void onPlayerMove(PlayerMoveEvent event) { + Player player = event.getPlayer(); + + // Check if player moves upwards + if (event.getFrom().getY() < event.getTo().getY()) { + // Always check the block below the player's feet + Block blockBelow = player.getLocation().subtract(0, 0.1, 0).getBlock(); + + if (blockBelow.getType() == Material.CRYING_OBSIDIAN) { + if (!CooldownManager.isOnCooldown(player.getUniqueId())) { + for (int y = blockBelow.getY() + 2; y <= player.getWorld().getMaxHeight(); y++) { + Block above = player.getWorld().getBlockAt(blockBelow.getX(), y, blockBelow.getZ()); + if (above.getType() == Material.CRYING_OBSIDIAN) { + player.teleport(new Location(player.getWorld(), above.getX() + 0.5, y + 1, above.getZ() + 0.5)); + player.playSound(player.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1f, 1f); + player.getWorld().spawnParticle(Particle.PORTAL, player.getLocation(), 30, 0.5, 1, 0.5); + CooldownManager.setCooldown(player.getUniqueId()); + return; + } + } + player.sendMessage(ChatColor.YELLOW + "No elevator block above!"); + } else if (!player.hasMetadata("jumpCooldownNotified")) { + player.sendMessage(ChatColor.RED + "Elevator is cooling down!"); + player.setMetadata("jumpCooldownNotified", new FixedMetadataValue(SMPPlugin.getInstance(), true)); + Bukkit.getScheduler().runTaskLater(SMPPlugin.getInstance(), () -> { + player.removeMetadata("jumpCooldownNotified", SMPPlugin.getInstance()); + }, 20L); + } + } + } + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/blockelevator/PlayerSneakListener.java b/src/main/java/de/winniepat/SMPPlugin/blockelevator/PlayerSneakListener.java new file mode 100644 index 0000000..91671fd --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/blockelevator/PlayerSneakListener.java @@ -0,0 +1,44 @@ +package de.winniepat.SMPPlugin.blockelevator; +import de.winniepat.SMPPlugin.SMPPlugin; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerToggleSneakEvent; +import org.bukkit.metadata.FixedMetadataValue; + +public class PlayerSneakListener implements Listener { + + @EventHandler + public void onPlayerSneak(PlayerToggleSneakEvent event) { + if (!event.isSneaking()) return; + + Player player = event.getPlayer(); + + // Always check the block **below** the player's feet + Block blockBelow = player.getLocation().subtract(0, 0.1, 0).getBlock(); + + if (blockBelow.getType() == Material.CRYING_OBSIDIAN) { + if (!CooldownManager.isOnCooldown(player.getUniqueId())) { + for (int y = blockBelow.getY() - 1; y >= 0; y--) { + Block below = player.getWorld().getBlockAt(blockBelow.getX(), y, blockBelow.getZ()); + if (below.getType() == Material.CRYING_OBSIDIAN) { + player.teleport(new Location(player.getWorld(), below.getX() + 0.5, y + 1, below.getZ() + 0.5)); + player.playSound(player.getLocation(), Sound.BLOCK_PISTON_CONTRACT, 1f, 1f); + player.getWorld().spawnParticle(Particle.CLOUD, player.getLocation(), 20, 0.3, 0.3, 0.3); + CooldownManager.setCooldown(player.getUniqueId()); + return; + } + } + player.sendMessage(ChatColor.YELLOW + "No elevator block below!"); + } else if (!player.hasMetadata("sneakCooldownNotified")) { + player.sendMessage(ChatColor.RED + "Elevator is cooling down!"); + player.setMetadata("sneakCooldownNotified", new FixedMetadataValue(SMPPlugin.getInstance(), true)); + Bukkit.getScheduler().runTaskLater(SMPPlugin.getInstance(), () -> { + player.removeMetadata("sneakCooldownNotified", SMPPlugin.getInstance()); + }, 20L); + } + } + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/bloodmoon/BloodmoonCommand.java b/src/main/java/de/winniepat/SMPPlugin/bloodmoon/BloodmoonCommand.java new file mode 100644 index 0000000..67b5fbc --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/bloodmoon/BloodmoonCommand.java @@ -0,0 +1,34 @@ +package de.winniepat.SMPPlugin.bloodmoon; + +import org.bukkit.ChatColor; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; + +public class BloodmoonCommand implements CommandExecutor { + + private final BloodmoonManager manager; + + public BloodmoonCommand(BloodmoonManager manager) { + this.manager = manager; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!sender.hasPermission("bloodmoon.toggle")) { + sender.sendMessage(ChatColor.RED + "You do not have permission to do that."); + return true; + } + + if (manager.isActive()) { + manager.endBloodmoon(); + sender.sendMessage(ChatColor.GRAY + "☀ Bloodmoon ended."); + } else { + manager.startBloodmoon(); + sender.sendMessage(ChatColor.RED + "☠ Bloodmoon started."); + + } + + return true; + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/bloodmoon/BloodmoonListener.java b/src/main/java/de/winniepat/SMPPlugin/bloodmoon/BloodmoonListener.java new file mode 100644 index 0000000..16307e9 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/bloodmoon/BloodmoonListener.java @@ -0,0 +1,160 @@ +package de.winniepat.SMPPlugin.bloodmoon; + +import org.bukkit.*; +import org.bukkit.attribute.Attribute; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.*; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.player.PlayerBedEnterEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.Random; + +public class BloodmoonListener implements Listener { + + private final BloodmoonManager manager; + private final JavaPlugin plugin; + private final Random random = new Random(); + + public BloodmoonListener(BloodmoonManager manager, JavaPlugin plugin) { + this.manager = manager; + this.plugin = plugin; + } + + @EventHandler + public void onBedEnter(PlayerBedEnterEvent event) { + if (manager.isActive()) { + event.setCancelled(true); + event.getPlayer().sendMessage(ChatColor.RED + "You cannot sleep during a Bloodmoon!"); + } + } + + @EventHandler + public void onMobSpawn(CreatureSpawnEvent event) { + if (!manager.isActive()) return; + + LivingEntity entity = event.getEntity(); + + if (entity instanceof Zombie z && z.isBaby()) return; + + if (random.nextInt(100) < 20) { + spawnBoss(entity); + return; + } + + if (entity instanceof Creeper creeper) { + creeper.setExplosionRadius(6); + if (random.nextInt(100) < 30) { + creeper.setPowered(true); + } + } + + if (entity.getAttribute(Attribute.MAX_HEALTH) != null) { + entity.getAttribute(Attribute.MAX_HEALTH).setBaseValue( + entity.getAttribute(Attribute.MAX_HEALTH).getBaseValue() * 1.5 + ); + entity.setHealth(entity.getAttribute(Attribute.MAX_HEALTH).getBaseValue()); + } + + if (entity.getAttribute(Attribute.ATTACK_DAMAGE) != null) { + entity.getAttribute(Attribute.ATTACK_DAMAGE).setBaseValue( + entity.getAttribute(Attribute.ATTACK_DAMAGE).getBaseValue() + 2 + ); + } + + if (entity.getAttribute(Attribute.MOVEMENT_SPEED) != null) { + entity.getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue( + entity.getAttribute(Attribute.MOVEMENT_SPEED).getBaseValue() * 1.2 + ); + } + } + + private void spawnBoss(LivingEntity entity) { + if (!(entity instanceof Zombie || entity instanceof Skeleton || entity instanceof Husk || entity instanceof Drowned)) + return; + + entity.getPersistentDataContainer().set( + new NamespacedKey(plugin, "bloodmoon_boss"), + PersistentDataType.BYTE, + (byte) 1 + ); + + entity.setCustomName(ChatColor.DARK_RED + "Blood Knight"); + entity.setCustomNameVisible(true); + + if (entity instanceof Zombie || entity instanceof Husk || entity instanceof Drowned) { + entity.getEquipment().setHelmet(createItem(Material.DIAMOND_HELMET, "Blood Helmet")); + entity.getEquipment().setChestplate(createItem(Material.DIAMOND_CHESTPLATE, "Blood Chestplate")); + entity.getEquipment().setLeggings(createItem(Material.DIAMOND_LEGGINGS, "Blood Leggings")); + entity.getEquipment().setBoots(createItem(Material.DIAMOND_BOOTS, "Blood Boots")); + entity.getEquipment().setItemInMainHand(createItem(Material.DIAMOND_SWORD, "Blood Blade")); + } else if (entity instanceof Skeleton) { + entity.getEquipment().setHelmet(createItem(Material.DIAMOND_HELMET, "Blood Helmet")); + entity.getEquipment().setChestplate(createItem(Material.NETHERITE_CHESTPLATE, "Blood Chestplate")); + entity.getEquipment().setItemInMainHand(createItem(Material.BOW, "Blood Bow")); + } + + entity.getEquipment().setHelmetDropChance(0.001f); + entity.getEquipment().setChestplateDropChance(0.001f); + entity.getEquipment().setLeggingsDropChance(0.001f); + entity.getEquipment().setBootsDropChance(0.001f); + entity.getEquipment().setItemInMainHandDropChance(0.001f); + + if (entity.getAttribute(Attribute.MAX_HEALTH) != null) { + entity.getAttribute(Attribute.MAX_HEALTH).setBaseValue(80); + entity.setHealth(80); + } + if (entity.getAttribute(Attribute.MAX_HEALTH) != null) { + entity.getAttribute(Attribute.MAX_HEALTH).setBaseValue(12); + } + if (entity.getAttribute(Attribute.MOVEMENT_SPEED) != null) { + entity.getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(0.35); + } + + startParticleEffect(entity); + } + + private ItemStack createItem(Material material, String name) { + ItemStack item = new ItemStack(material); + ItemMeta meta = item.getItemMeta(); + if (meta != null) { + meta.setDisplayName(ChatColor.RED + name); + meta.addEnchant(Enchantment.PROTECTION, 2, true); + meta.addEnchant(Enchantment.UNBREAKING, 3, true); + meta.addEnchant(Enchantment.SHARPNESS, 2, true); + item.setItemMeta(meta); + } + return item; + } + + private void startParticleEffect(LivingEntity entity) { + new BukkitRunnable() { + int ticks = 0; + + @Override + public void run() { + if (entity == null || entity.isDead() || !entity.isValid()) { + cancel(); + return; + } + + entity.getWorld().spawnParticle( + Particle.DUST, + entity.getLocation().add(0, 1, 0), + 8, + 0.3, 0.5, 0.3, + 0, + new Particle.DustOptions(Color.RED, 1.5f) + ); + + if ((ticks += 5) > 20 * 300) cancel(); + } + }.runTaskTimer(plugin, 0L, 5L); + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/bloodmoon/BloodmoonManager.java b/src/main/java/de/winniepat/SMPPlugin/bloodmoon/BloodmoonManager.java new file mode 100644 index 0000000..38cb831 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/bloodmoon/BloodmoonManager.java @@ -0,0 +1,113 @@ +package de.winniepat.SMPPlugin.bloodmoon; + +import de.winniepat.SMPPlugin.blackmarket.BlackMarketManager; +import org.bukkit.*; +import org.bukkit.boss.BarColor; +import org.bukkit.boss.BarStyle; +import org.bukkit.boss.BossBar; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.Random; + +public class BloodmoonManager { + private final JavaPlugin plugin; + private boolean active = false; + private BossBar bloodmoonBar; + private int durationTicks = 20 * 60 * 8; + private int ticksElapsed = 0; + private BukkitRunnable task; + + public BloodmoonManager(JavaPlugin plugin) { + this.plugin = plugin; + } + + public void startBloodmoon() { + if (active) return; + active = true; + Bukkit.broadcastMessage(ChatColor.DARK_RED + "☠ The Bloodmoon has risen... Be wary!"); + for (Player player : Bukkit.getOnlinePlayers()) { + player.sendTitle(ChatColor.RED + "Bloodmoon", ChatColor.DARK_RED + "Mobs are stronger tonight!", 10, 100, 20); + player.addPotionEffect(new PotionEffect(PotionEffectType.NIGHT_VISION, 20 * 60 * 8, 1, false, false, false)); + player.playSound(player.getLocation(), Sound.ENTITY_WITHER_SPAWN, 1.0f, 1.0f); + + player.spawnParticle(Particle.DUST, player.getLocation().add(0, 10, 0), 100,5, 5, 5, 0, new Particle.DustOptions(Color.RED, 2)); + } + + Bukkit.getScheduler().runTaskTimer(plugin, () -> { + for (Player player : Bukkit.getOnlinePlayers()) { + if (active) { + player.spawnParticle(Particle.DUST, player.getLocation().add( + Math.random() * 10 - 5, Math.random() * 5, Math.random() * 10 - 5), + 5, 0, 0, 0, 0, new Particle.DustOptions(Color.RED, 1)); + } + } + }, 0L, 1L); + + bloodmoonBar = Bukkit.createBossBar("§4🌕 Bloodmoon Night", BarColor.RED, BarStyle.SEGMENTED_10); + bloodmoonBar.setProgress(1.0); + + for (Player player : Bukkit.getOnlinePlayers()) { + bloodmoonBar.addPlayer(player); + } + + task = new BukkitRunnable() { + @Override + public void run() { + ticksElapsed += 20; + double progress = Math.max(0, 1.0 - (double) ticksElapsed / durationTicks); + bloodmoonBar.setProgress(progress); + + if (ticksElapsed >= durationTicks) { + endBloodmoon(); + } + } + }; + task.runTaskTimer(plugin, 20L, 20L); + + } + + public void endBloodmoon() { + if (!active) return; + active = false; + + Bukkit.broadcastMessage(ChatColor.GRAY + "☀ The Bloodmoon has ended. It's safe again."); + for (Player player : Bukkit.getOnlinePlayers()) { + player.sendTitle(ChatColor.YELLOW + "☀", ChatColor.GREEN + "Bloodmoon ended!", 10, 100, 20); + } + NamespacedKey key = new NamespacedKey(plugin, "bloodmoon_boss"); + + for (World world : Bukkit.getWorlds()) { + for (LivingEntity entity : world.getLivingEntities()) { + if (entity.getPersistentDataContainer().has(key, PersistentDataType.BYTE)) { + entity.getWorld().playSound(entity.getLocation(), Sound.ENTITY_WITHER_DEATH, 1f, 0.5f); + + entity.remove(); + } + } + } + if (new Random().nextInt(100) < 5) { + //new BlackMarketManager(plugin).spawnMarket(); + } + if (task != null) { + task.cancel(); + task = null; + } + + if (bloodmoonBar != null) { + bloodmoonBar.removeAll(); + bloodmoonBar = null; + } + + ticksElapsed = 0; + } + + public boolean isActive() { + return active; + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/bloodmoon/BloodmoonTask.java b/src/main/java/de/winniepat/SMPPlugin/bloodmoon/BloodmoonTask.java new file mode 100644 index 0000000..31dae78 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/bloodmoon/BloodmoonTask.java @@ -0,0 +1,47 @@ +package de.winniepat.SMPPlugin.bloodmoon; + +import org.bukkit.Bukkit; +import org.bukkit.World; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.Random; + +public class BloodmoonTask { + + private final JavaPlugin plugin; + private final BloodmoonManager manager; + + private boolean checkedThisNight = false; + + public BloodmoonTask(JavaPlugin plugin, BloodmoonManager manager) { + this.plugin = plugin; + this.manager = manager; + } + + public void start() { + new BukkitRunnable() { + @Override + public void run() { + World world = Bukkit.getWorlds().get(0); + long time = world.getTime(); + if (time >= 13000 && time <= 13100) { + if (!checkedThisNight) { + checkedThisNight = true; + + if (new Random().nextInt(100) < 5) { + manager.startBloodmoon(); + } + } + } + + if (time >= 0 && time < 1000) { + if (manager.isActive()) { + manager.endBloodmoon(); + } + checkedThisNight = false; + } + } + }.runTaskTimer(plugin, 0L, 100L); + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/commands/CodingCommand.java b/src/main/java/de/winniepat/SMPPlugin/commands/CodingCommand.java new file mode 100644 index 0000000..7627cc1 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/commands/CodingCommand.java @@ -0,0 +1,131 @@ +package de.winniepat.SMPPlugin.commands; + +import de.winniepat.SMPPlugin.SMPPlugin; +import org.bukkit.*; +import org.bukkit.command.*; +import org.bukkit.entity.*; +import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.*; + +public class CodingCommand implements CommandExecutor { + + private Plugin plugin; + private SMPPlugin smpPlugin; + private final Map codingStands = new HashMap<>(); + private final Map animationTasks = new HashMap<>(); + + private static final String[] COLORS = { + "#FF0000", "#FF4000", "#FF8000", "#FFBF00", "#FFFF00", + "#BFFF00", "#80FF00", "#40FF00", "#00FF00", "#00FF40", + "#00FF80", "#00FFBF", "#00FFFF", "#00BFFF", "#0080FF", + "#0040FF", "#0000FF", "#4000FF", "#8000FF", "#BF00FF", + "#FF00FF" + }; + + private final String[] frames = new String[21]; + + public CodingCommand(Plugin plugin, SMPPlugin smpPlugin) { + this.plugin = plugin; + this.smpPlugin = smpPlugin; + + char[] glyphs = { + '\uE014', '\uE015', '\uE016', '\uE017', '\uE018', + '\uE019', '\uE020', '\uE021', '\uE022', '\uE023', + '\uE024', '\uE025', '\uE026', '\uE035', '\uE028', + '\uE029', '\uE030', '\uE031', '\uE032', '\uE033', + '\uE034' + }; + for (int i = 0; i < 21; i++) { + frames[i] = glyphs[i] + " " + rgb("Coding...", COLORS[i]); + } + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player player)) return false; + + if (args.length != 1 || (!args[0].equalsIgnoreCase("on") && !args[0].equalsIgnoreCase("off"))) { + sender.sendMessage(smpPlugin.getMessage("command.coding.usage", Map.of("player", sender.getName()))); + return true; + } + + UUID uuid = player.getUniqueId(); + + if (args[0].equalsIgnoreCase("on")) { + if (codingStands.containsKey(uuid)) { + sender.sendMessage(smpPlugin.getMessage("command.coding.already", Map.of("player", sender.getName()))); + return true; + } + + Location loc = player.getLocation().add(0, 2.2, 0); + ArmorStand stand = player.getWorld().spawn(loc, ArmorStand.class); + stand.setVisible(false); + stand.setCustomNameVisible(true); + stand.setGravity(false); + stand.setMarker(true); + + codingStands.put(uuid, stand); + + BukkitRunnable task = new BukkitRunnable() { + int tick = 0; + + @Override + public void run() { + if (!stand.isValid() || !player.isOnline()) { + this.cancel(); + if (stand.isValid()) stand.remove(); + codingStands.remove(uuid); + animationTasks.remove(uuid); + return; + } + + stand.teleport(player.getLocation().add(0, 2.2, 0)); + stand.setCustomName(frames[tick % frames.length]); + tick++; + } + }; + task.runTaskTimer(plugin, 0L, 4L); + + animationTasks.put(uuid, task); + sender.sendMessage(smpPlugin.getMessage("command.coding.marked", Map.of("player", sender.getName()))); + } + + if (args[0].equalsIgnoreCase("off")) { + if (animationTasks.containsKey(uuid)) { + animationTasks.get(uuid).cancel(); + animationTasks.remove(uuid); + } + + if (codingStands.containsKey(uuid)) { + ArmorStand stand = codingStands.remove(uuid); + if (stand != null && !stand.isDead()) { + stand.remove(); + } + player.sendMessage("§aYou are no longer marked as coding."); + sender.sendMessage(smpPlugin.getMessage("command.coding.unmarked", Map.of("player", sender.getName()))); + } else { + sender.sendMessage(smpPlugin.getMessage("command.coding.error", Map.of("player", sender.getName()))); + } + } + + return true; + } + + private static String rgb(String text, String hexColor) { + StringBuilder out = new StringBuilder("§x"); + for (char c : hexColor.substring(1).toCharArray()) { + out.append('§').append(c); + } + return out + text; + } + + public Map getCodingStands() { + return codingStands; + } + + public Map getAnimationTasks() { + return animationTasks; + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/commands/ShopCommand.java b/src/main/java/de/winniepat/SMPPlugin/commands/ShopCommand.java new file mode 100644 index 0000000..29875e4 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/commands/ShopCommand.java @@ -0,0 +1,16 @@ +package de.winniepat.SMPPlugin.commands; + +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +public class ShopCommand implements CommandExecutor { + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String @NotNull [] args) { + Bukkit.dispatchCommand(Bukkit.getConsoleSender(),"minecraft:tp " + sender.getName() + " -696 64 -898 0.0 -7.0"); + return false; + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/commands/SpawnCommand.java b/src/main/java/de/winniepat/SMPPlugin/commands/SpawnCommand.java new file mode 100644 index 0000000..2fb6110 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/commands/SpawnCommand.java @@ -0,0 +1,15 @@ +package de.winniepat.SMPPlugin.commands; + +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; + +public class SpawnCommand implements CommandExecutor { + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + Bukkit.dispatchCommand(Bukkit.getConsoleSender(),"minecraft:tp " + sender.getName() + " -635.5 68 -837.5 -90 0"); + return false; + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/commands/TrashCommand.java b/src/main/java/de/winniepat/SMPPlugin/commands/TrashCommand.java new file mode 100644 index 0000000..f1ac325 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/commands/TrashCommand.java @@ -0,0 +1,45 @@ +package de.winniepat.SMPPlugin.commands; + +import de.winniepat.SMPPlugin.SMPPlugin; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.inventory.Inventory; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.Map; + +public class TrashCommand implements CommandExecutor, Listener { + + private final JavaPlugin plugin; + private final SMPPlugin smpPlugin; + + public TrashCommand(JavaPlugin plugin, SMPPlugin smpPlugin) { + this.plugin = plugin; + this.smpPlugin = smpPlugin; + Bukkit.getPluginManager().registerEvents(this, plugin); + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player p)) { + sender.sendMessage(smpPlugin.getMessage("command.error.no_player", Map.of("player", sender.getName()))); + return true; + } + + Inventory trash = Bukkit.createInventory(p, 27, "§cTrash"); + p.openInventory(trash); + return true; + } + + @org.bukkit.event.EventHandler + public void onInventoryClose(InventoryCloseEvent event) { + if (event.getView().getTitle().equals("§cTrash")) { + event.getInventory().clear(); + } + } +} \ No newline at end of file diff --git a/src/main/java/de/winniepat/SMPPlugin/listeners/CodingCleanupListener.java b/src/main/java/de/winniepat/SMPPlugin/listeners/CodingCleanupListener.java new file mode 100644 index 0000000..eec581a --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/listeners/CodingCleanupListener.java @@ -0,0 +1,35 @@ +package de.winniepat.SMPPlugin.listeners; + +import de.winniepat.SMPPlugin.commands.CodingCommand; +import org.bukkit.entity.ArmorStand; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.UUID; + +public class CodingCleanupListener implements Listener { + + private final CodingCommand codingCommand; + + public CodingCleanupListener(CodingCommand codingCommand) { + this.codingCommand = codingCommand; + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + UUID uuid = event.getPlayer().getUniqueId(); + + if (codingCommand.getAnimationTasks().containsKey(uuid)) { + codingCommand.getAnimationTasks().get(uuid).cancel(); + codingCommand.getAnimationTasks().remove(uuid); + } + + if (codingCommand.getCodingStands().containsKey(uuid)) { + ArmorStand stand = codingCommand.getCodingStands().remove(uuid); + if (stand != null && !stand.isDead()) { + stand.remove(); + } + } + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/listeners/FirstPlayerJoinTracker.java b/src/main/java/de/winniepat/SMPPlugin/listeners/FirstPlayerJoinTracker.java new file mode 100644 index 0000000..b87f018 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/listeners/FirstPlayerJoinTracker.java @@ -0,0 +1,104 @@ +package de.winniepat.SMPPlugin.listeners; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import org.bukkit.Material; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BookMeta; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.util.*; + +public class FirstPlayerJoinTracker implements Listener { + + String longText = "Einst lebten alle friedlich beisammen in der Stadt Lunaris. " + + "Doch eines Abends, als der Blutmond über die Stadt zog, wurde Lunaris von unzähligen Monstern attackiert und fast vollständig zerstört. " + + "Die tapferen Bewohner versuchten mit aller Kraft der Attacke standzuhalten – doch es war vergebens. Die Monster waren einfach zu stark, und es waren zu viele. " + + "Die Stadtbewohner konnten nichts ausrichten. Alles, was blieb, war das Rathaus am Marktplatz und ein paar Überreste der Häuser… " + + "Doch die Hoffnung besteht, dass die Stadt Lunaris bald wieder in vollem Glanz erstrahlen kann und genauso belebt wird, wie sie es einst war."; + + private final JavaPlugin plugin; + private final File file; + private final Gson gson = new Gson(); + private final Set joinedPlayers = new HashSet<>(); + + public FirstPlayerJoinTracker(JavaPlugin plugin) { + this.plugin = plugin; + this.file = new File(plugin.getDataFolder(), "players.json"); + load(); + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + UUID uuid = event.getPlayer().getUniqueId(); + if (joinedPlayers.add(uuid)) { + + ItemStack book = new ItemStack(Material.WRITTEN_BOOK); + BookMeta bookMeta = (BookMeta) book.getItemMeta(); + + bookMeta.setAuthor("Unknown Stranger"); + + try { + bookMeta.getClass().getMethod("setTitle", String.class).invoke(bookMeta, "Willkommen in Lunaris"); + } catch (Exception ignored) { + } + + List pages = splitIntoPages(longText, 250); + bookMeta.setPages(pages); + + book.setItemMeta(bookMeta); + + event.getPlayer().getInventory().addItem(book); + + save(); + } + } + + public void load() { + if (!file.exists()) { + plugin.getDataFolder().mkdirs(); + save(); + return; + } + + try (FileReader reader = new FileReader(file)) { + Set loaded = gson.fromJson(reader, new TypeToken>() {}.getType()); + if (loaded != null) { + for (String id : loaded) { + joinedPlayers.add(UUID.fromString(id)); + } + } + } catch (Exception e) { + plugin.getLogger().warning("Failed to load players.json: " + e.getMessage()); + } + } + + public void save() { + try (FileWriter writer = new FileWriter(file)) { + Set ids = new HashSet<>(); + for (UUID uuid : joinedPlayers) { + ids.add(uuid.toString()); + } + gson.toJson(ids, writer); + } catch (Exception e) { + plugin.getLogger().warning("Failed to save players.json: " + e.getMessage()); + } + } + + private List splitIntoPages(String text, int maxLength) { + List pages = new ArrayList<>(); + int index = 0; + while (index < text.length()) { + int endIndex = Math.min(index + maxLength, text.length()); + pages.add(text.substring(index, endIndex)); + index = endIndex; + } + return pages; + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/listeners/QuitLightningListener.java b/src/main/java/de/winniepat/SMPPlugin/listeners/QuitLightningListener.java new file mode 100644 index 0000000..49dec57 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/listeners/QuitLightningListener.java @@ -0,0 +1,16 @@ +package de.winniepat.SMPPlugin.listeners; + +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; + +public class QuitLightningListener implements Listener { + @EventHandler + public void playerQuitListener(PlayerQuitEvent event) { + Player player = event.getPlayer(); + if (player.isOp()) { + player.getWorld().strikeLightningEffect(player.getLocation()); + } + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/polls/Poll.java b/src/main/java/de/winniepat/SMPPlugin/polls/Poll.java new file mode 100644 index 0000000..108f47d --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/polls/Poll.java @@ -0,0 +1,76 @@ +package de.winniepat.SMPPlugin.polls; + +import java.util.*; + +public class Poll { + private final String question; + private final List options; + private final Map votes = new HashMap<>(); + private final String creator; + private final long endTimeMillis; + + public Poll(String question, List options, String creator, long durationSeconds) { + this.question = question; + this.options = options; + this.creator = creator; + this.endTimeMillis = System.currentTimeMillis() + (durationSeconds * 1000); + } + + public Poll(String question, List options, String creator, long endTimeMillis, Map savedVotes) { + this.question = question; + this.options = options; + this.creator = creator; + this.endTimeMillis = endTimeMillis; + for (Map.Entry entry : savedVotes.entrySet()) { + for (int i = 0; i < entry.getValue(); i++) { + votes.put(UUID.randomUUID(), entry.getKey()); + } + } + } + + public String getQuestion() { + return question; + } + + public List getOptions() { + return options; + } + + public String getCreator() { + return creator; + } + + public long getEndTimeMillis() { + return endTimeMillis; + } + + public boolean vote(UUID player, String option) { + if (!options.contains(option) || isExpired()) return false; + votes.put(player, option); + return true; + } + + public Map getResults() { + Map results = new HashMap<>(); + for (String option : options) { + results.put(option, 0); + } + for (String vote : votes.values()) { + results.put(vote, results.get(vote) + 1); + } + return results; + } + + public boolean hasVoted(UUID player) { + return votes.containsKey(player); + } + + public boolean isExpired() { + return System.currentTimeMillis() > endTimeMillis; + } + + public long getRemainingTimeSeconds() { + long remaining = endTimeMillis - System.currentTimeMillis(); + return Math.max(remaining / 1000, 0); + } +} \ No newline at end of file diff --git a/src/main/java/de/winniepat/SMPPlugin/polls/PollCommand.java b/src/main/java/de/winniepat/SMPPlugin/polls/PollCommand.java new file mode 100644 index 0000000..1e07551 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/polls/PollCommand.java @@ -0,0 +1,69 @@ +package de.winniepat.SMPPlugin.polls; + +import de.winniepat.SMPPlugin.SMPPlugin; +import org.bukkit.Bukkit; +import org.bukkit.command.*; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +public class PollCommand implements CommandExecutor { + + private final PollManager manager; + private final SMPPlugin smpPlugin; + + public PollCommand(PollManager manager, SMPPlugin smpPlugin) { + this.manager = manager; + this.smpPlugin = smpPlugin; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String label, String[] args) { + if (args.length < 3) { + sender.sendMessage(smpPlugin.getMessage("command.poll.usage", Map.of("player", sender.getName()))); + return true; + } + + if (!(sender instanceof Player player)) { + sender.sendMessage(smpPlugin.getMessage("error.no_player", Map.of("player", sender.getName()))); + return true; + } + + if (!player.hasPermission("poll.start")) { + sender.sendMessage(smpPlugin.getMessage("command.error.no_permission", Map.of("player", sender.getName()))); + return true; + } + + if (manager.hasActivePoll()) { + sender.sendMessage(smpPlugin.getMessage("command.poll.already_active", Map.of("player", sender.getName()))); + return true; + } + + if (args[0].startsWith("\"") && args[0].endsWith("\"")) { + String question = args[0].substring(1, args[0].length() - 1); + long duration; + try { + duration = Long.parseLong(args[1]); + } catch (NumberFormatException e) { + sender.sendMessage(smpPlugin.getMessage("command.poll.invalid_time", Map.of("player", sender.getName()))); + return true; + } + List options = Arrays.asList(Arrays.copyOfRange(args, 2, args.length)); + manager.startPoll(question, options, player.getName(), duration); + Bukkit.broadcastMessage("§6Poll §e" + question + " §7(started " + duration + "s)"); + + for (Player online : Bukkit.getOnlinePlayers()) { + online.sendTitle( + "§6New Poll!", + "§7Use §e/vote §7to vote | " + duration + " Seconds left", + 10, 60, 10 + ); + } + return true; + } else { + sender.sendMessage(smpPlugin.getMessage("command.poll.usage", Map.of("player", sender.getName()))); + return true; + } + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/polls/PollManager.java b/src/main/java/de/winniepat/SMPPlugin/polls/PollManager.java new file mode 100644 index 0000000..f3234e8 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/polls/PollManager.java @@ -0,0 +1,85 @@ +package de.winniepat.SMPPlugin.polls; + +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +public class PollManager { + private Poll currentPoll = null; + private final File dataFile; + private final YamlConfiguration data; + private final List expiredPolls = new ArrayList<>(); + + public PollManager(File dataFile) { + this.dataFile = dataFile; + this.data = YamlConfiguration.loadConfiguration(dataFile); + load(); + } + + public void startPoll(String question, List options, String creator, long durationSeconds) { + if (currentPoll != null && currentPoll.isExpired()) { + expiredPolls.add(currentPoll); + } + this.currentPoll = new Poll(question, options, creator, durationSeconds); + save(); + } + + public Poll getCurrentPoll() { + return currentPoll; + } + + public void clearPoll() { + if (currentPoll != null) { + expiredPolls.add(currentPoll); + } + this.currentPoll = null; + data.set("poll", null); + save(); + } + + public boolean hasActivePoll() { + return currentPoll != null && !currentPoll.isExpired(); + } + + public List getExpiredPolls() { + return expiredPolls; + } + + public void save() { + if (currentPoll == null) return; + data.set("poll.question", currentPoll.getQuestion()); + data.set("poll.options", currentPoll.getOptions()); + data.set("poll.creator", currentPoll.getCreator()); + data.set("poll.endTime", currentPoll.getEndTimeMillis()); + data.set("poll.results", currentPoll.getResults()); + try { + data.save(dataFile); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void load() { + if (!data.contains("poll")) return; + String question = data.getString("poll.question"); + List options = data.getStringList("poll.options"); + String creator = data.getString("poll.creator"); + long endTime = data.getLong("poll.endTime"); + Map results = new HashMap<>(); + if (data.contains("poll.results")) { + for (String key : data.getConfigurationSection("poll.results").getKeys(false)) { + results.put(key, data.getInt("poll.results." + key)); + } + } + long duration = (endTime - System.currentTimeMillis()) / 1000; + if (duration > 0) { + this.currentPoll = new Poll(question, options, creator, duration); + } else { + this.currentPoll = new Poll(question, options, creator, endTime, results); + this.expiredPolls.add(currentPoll); + this.currentPoll = null; + } + } +} \ No newline at end of file diff --git a/src/main/java/de/winniepat/SMPPlugin/polls/PollResultGUI.java b/src/main/java/de/winniepat/SMPPlugin/polls/PollResultGUI.java new file mode 100644 index 0000000..3858ebc --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/polls/PollResultGUI.java @@ -0,0 +1,52 @@ +package de.winniepat.SMPPlugin.polls; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.*; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.List; + +public class PollResultGUI { + + private final PollManager manager; + + public PollResultGUI(PollManager manager) { + this.manager = manager; + } + + public void openResultsGUI(Player player) { + int size = 9 * 3; + Inventory inv = Bukkit.createInventory(null, size, "§6Poll-Results"); + + int slot = 0; + + Poll current = manager.getCurrentPoll(); + if (current != null) { + ItemStack item = new ItemStack(Material.WRITABLE_BOOK); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName("§aActive: " + current.getQuestion()); + meta.setLore(current.getResults().entrySet().stream() + .map(e -> "§7" + e.getKey() + ": §e" + e.getValue()) + .toList()); + item.setItemMeta(meta); + inv.setItem(slot++, item); + } + + List pastPolls = manager.getExpiredPolls(); + for (Poll past : pastPolls) { + ItemStack item = new ItemStack(Material.BOOK); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName("§7Ended: " + past.getQuestion()); + meta.setLore(past.getResults().entrySet().stream() + .map(e -> "§7" + e.getKey() + ": §e" + e.getValue()) + .toList()); + item.setItemMeta(meta); + inv.setItem(slot++, item); + if (slot >= size) break; + } + + player.openInventory(inv); + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/polls/PollVoteGUI.java b/src/main/java/de/winniepat/SMPPlugin/polls/PollVoteGUI.java new file mode 100644 index 0000000..b594dae --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/polls/PollVoteGUI.java @@ -0,0 +1,69 @@ +package de.winniepat.SMPPlugin.polls; + +import com.google.gson.annotations.Since; +import de.winniepat.SMPPlugin.SMPPlugin; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.Listener; +import org.bukkit.event.EventHandler; +import org.bukkit.inventory.*; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.List; +import java.util.Map; + +public class PollVoteGUI implements Listener { + + private final PollManager manager; + private final SMPPlugin smpPlugin; + + public PollVoteGUI(PollManager manager, SMPPlugin smpPlugin) { + this.smpPlugin = smpPlugin; + this.manager = manager; + } + + public void openVoteGUI(Player player) { + if (!manager.hasActivePoll()) { + player.sendMessage("§cNo active Poll."); + return; + } + + Poll poll = manager.getCurrentPoll(); + Inventory inv = Bukkit.createInventory(null, 9, "§6Vote: " + poll.getQuestion()); + + int i = 0; + for (String option : poll.getOptions()) { + ItemStack item = new ItemStack(Material.PAPER); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName("§b" + option); + meta.setLore(List.of("§7Remaining Time: §e" + poll.getRemainingTimeSeconds() + " Seconds")); + item.setItemMeta(meta); + inv.setItem(i++, item); + } + + player.openInventory(inv); + } + + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + if (!(event.getWhoClicked() instanceof Player player)) return; + if (event.getView().getTitle().startsWith("§6Vote:")) { + event.setCancelled(true); + + ItemStack clicked = event.getCurrentItem(); + if (clicked == null || !clicked.hasItemMeta()) return; + + String vote = clicked.getItemMeta().getDisplayName().replace("§b", ""); + Poll poll = manager.getCurrentPoll(); + if (poll != null && !poll.hasVoted(player.getUniqueId())) { + poll.vote(player.getUniqueId(), vote); + player.sendMessage("§aYour vote for §e" + vote + " §awas registered."); + player.closeInventory(); + } else { + player.sendMessage(smpPlugin.getMessage("command.error.no_permission", Map.of("player", player.getName()))); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/de/winniepat/SMPPlugin/report/ReportActionGUI.java b/src/main/java/de/winniepat/SMPPlugin/report/ReportActionGUI.java new file mode 100644 index 0000000..aebd7df --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/report/ReportActionGUI.java @@ -0,0 +1,92 @@ +package de.winniepat.SMPPlugin.report; + +import de.winniepat.SMPPlugin.SMPPlugin; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.HandlerList; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.*; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.List; +import java.util.Map; + +public class ReportActionGUI { + + private final SMPPlugin smpPlugin; + + public ReportActionGUI(SMPPlugin smpPlugin) { + this.smpPlugin = smpPlugin; + } + + public static void open(JavaPlugin plugin, ReportDatabase db, Player p, String reportId, SMPPlugin smpPlugin) { + Map report = db.getReportById(reportId); + if (report == null) { + p.sendMessage(smpPlugin.getMessage("command.reportactiongui.not_found", Map.of("player", p.getName()))); + return; + } + + Inventory gui = Bukkit.createInventory(p, 27, "§eActions for the report #" + reportId); + + ItemStack info = new ItemStack(Material.BOOK); + ItemMeta im = info.getItemMeta(); + im.setDisplayName("§bReport Details"); + im.setLore(List.of( + "§7Victim: §f" + report.get("reported"), + "§7By: §a" + report.get("reporter"), + "§7Reason: §f" + report.get("reason"), + "§8ID: " + reportId + )); + info.setItemMeta(im); + gui.setItem(11, info); + + ItemStack delete = new ItemStack(Material.BARRIER); + ItemMeta dm = delete.getItemMeta(); + dm.setDisplayName("§cReport löschen"); + delete.setItemMeta(dm); + gui.setItem(13, delete); + + ItemStack ban = new ItemStack(Material.IRON_SWORD); + ItemMeta bm = ban.getItemMeta(); + bm.setDisplayName("§4Ban Victim"); + bm.setLore(List.of("§7Reason: §cSecurity Ban")); + ban.setItemMeta(bm); + gui.setItem(15, ban); + + p.openInventory(gui); + + Bukkit.getPluginManager().registerEvents(new org.bukkit.event.Listener() { + @EventHandler + public void onClick(InventoryClickEvent e) { + if (!e.getView().getTitle().equals("§eActions for report #" + reportId)) return; + if (!(e.getWhoClicked() instanceof Player clicker)) return; + + ItemStack clicked = e.getCurrentItem(); + if (clicked == null || clicked.getType() == Material.AIR) return; + + if (clicked.getType() == Material.BARRIER) { + db.deleteReportById(reportId); + clicker.closeInventory(); + clicker.playSound(clicker.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1, 0.5f); + clicker.sendMessage(smpPlugin.getMessage("reportactiongui.successful_delete", Map.of("player", clicker.getName()))); + HandlerList.unregisterAll(this); + } + + if (clicked.getType() == Material.IRON_SWORD) { + String target = report.get("reported"); + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), + "ban " + target + " Security Ban"); + clicker.closeInventory(); + clicker.sendMessage("§cPlayer was permanently banned."); + clicker.sendMessage(smpPlugin.getMessage("reportactiongui.successful_ban", Map.of("player", clicker.getName()))); + HandlerList.unregisterAll(this); + } + e.setCancelled(true); + } + }, plugin); + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/report/ReportDatabase.java b/src/main/java/de/winniepat/SMPPlugin/report/ReportDatabase.java new file mode 100644 index 0000000..933d470 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/report/ReportDatabase.java @@ -0,0 +1,115 @@ +package de.winniepat.SMPPlugin.report; + +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.sql.*; +import java.util.*; + +public class ReportDatabase { + + private final JavaPlugin plugin; + private Connection connection; + + public ReportDatabase(JavaPlugin plugin) { + this.plugin = plugin; + connect(); + createTable(); + } + + private void connect() { + try { + File folder = plugin.getDataFolder(); + if (!folder.exists()) folder.mkdirs(); + + String url = "jdbc:sqlite:" + folder.getAbsolutePath() + "/reports.db"; + connection = DriverManager.getConnection(url); + plugin.getLogger().info("Report database successfully connected."); + } catch (SQLException e) { + plugin.getLogger().warning("Error while connecting to the database: " + e.getMessage()); + } + } + + private void createTable() { + if (connection == null) return; + try (Statement stmt = connection.createStatement()) { + stmt.executeUpdate(""" + CREATE TABLE IF NOT EXISTS reports ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + reporter TEXT, + reported TEXT, + reason TEXT, + timestamp TEXT + ); + """); + } catch (SQLException e) { + plugin.getLogger().warning("Error while crating the table: " + e.getMessage()); + } + } + + public void insertReport(String reporter, String reported, String reason) { + if (connection == null) return; + try (PreparedStatement ps = connection.prepareStatement( + "INSERT INTO reports (reporter, reported, reason, timestamp) VALUES (?, ?, ?, datetime('now'))")) { + ps.setString(1, reporter); + ps.setString(2, reported); + ps.setString(3, reason); + ps.executeUpdate(); + } catch (SQLException e) { + plugin.getLogger().warning("Error while saving the report: " + e.getMessage()); + } + } + + public List> getAllReports() { + List> reports = new ArrayList<>(); + if (connection == null) return reports; + + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT * FROM reports ORDER BY id DESC")) { + while (rs.next()) { + Map entry = new HashMap<>(); + entry.put("id", rs.getString("id")); + entry.put("reporter", rs.getString("reporter")); + entry.put("reported", rs.getString("reported")); + entry.put("reason", rs.getString("reason")); + entry.put("timestamp", rs.getString("timestamp")); + reports.add(entry); + } + } catch (SQLException e) { + plugin.getLogger().warning("Error while loading the reports: " + e.getMessage()); + } + return reports; + } + + public Map getReportById(String id) { + if (connection == null) return null; + + try (PreparedStatement ps = connection.prepareStatement("SELECT * FROM reports WHERE id = ?")) { + ps.setString(1, id); + ResultSet rs = ps.executeQuery(); + if (rs.next()) { + Map r = new HashMap<>(); + r.put("id", rs.getString("id")); + r.put("reporter", rs.getString("reporter")); + r.put("reported", rs.getString("reported")); + r.put("reason", rs.getString("reason")); + r.put("timestamp", rs.getString("timestamp")); + return r; + } + } catch (SQLException e) { + plugin.getLogger().warning("Fehler beim Laden Report ID " + id + ": " + e.getMessage()); + } + return null; + } + + public void deleteReportById(String id) { + if (connection == null) return; + + try (PreparedStatement ps = connection.prepareStatement("DELETE FROM reports WHERE id = ?")) { + ps.setString(1, id); + ps.executeUpdate(); + } catch (SQLException e) { + plugin.getLogger().warning("Fehler beim Löschen Report ID " + id + ": " + e.getMessage()); + } + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/report/commands/ReportCommand.java b/src/main/java/de/winniepat/SMPPlugin/report/commands/ReportCommand.java new file mode 100644 index 0000000..351ae01 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/report/commands/ReportCommand.java @@ -0,0 +1,51 @@ +package de.winniepat.SMPPlugin.report.commands; + +import de.winniepat.SMPPlugin.SMPPlugin; +import de.winniepat.SMPPlugin.report.ReportDatabase; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; + +public class ReportCommand implements CommandExecutor { + + private final ReportDatabase db; + private final SMPPlugin plugin; + + public ReportCommand(ReportDatabase db, SMPPlugin plugin) { + this.db = db; + this.plugin = plugin; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String label, String @NotNull [] args) { + if (!(sender instanceof Player p)) { + return true; + } + + if (args.length < 2) { + p.sendMessage(plugin.getMessage("command.report.usage", Map.of("player", sender.getName()))); + return true; + } + + Player target = Bukkit.getPlayerExact(args[0]); + if (target == null || target == p) { + sender.sendMessage(plugin.getMessage("command.report.invalid_player", Map.of("player", sender.getName()))); + return true; + } + + String reason = String.join(" ", args).substring(args[0].length()).trim(); + if (reason.length() < 4) { + sender.sendMessage(plugin.getMessage("command.report.reason", Map.of("player", sender.getName()))); + return true; + } + + db.insertReport(p.getName(), target.getName(), reason); + sender.sendMessage(plugin.getMessage("command.report.success", Map.of("player", sender.getName()))); + return true; + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/report/commands/ReportsCommand.java b/src/main/java/de/winniepat/SMPPlugin/report/commands/ReportsCommand.java new file mode 100644 index 0000000..1e1f3f4 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/report/commands/ReportsCommand.java @@ -0,0 +1,131 @@ +package de.winniepat.SMPPlugin.report.commands; + +import de.winniepat.SMPPlugin.SMPPlugin; +import de.winniepat.SMPPlugin.report.ReportActionGUI; +import de.winniepat.SMPPlugin.report.ReportDatabase; +import org.bukkit.*; +import org.bukkit.command.*; +import org.bukkit.entity.Player; +import org.bukkit.event.*; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.inventory.*; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.*; + +public class ReportsCommand implements CommandExecutor, Listener { + + private final ReportDatabase db; + private final SMPPlugin smpPlugin; + private final JavaPlugin plugin; + private final Map pageMap = new HashMap<>(); + private final int REPORTS_PER_PAGE = 28; + + public ReportsCommand(JavaPlugin plugin, ReportDatabase db, SMPPlugin smpPlugin) { + this.plugin = plugin; + this.smpPlugin = smpPlugin; + this.db = db; + Bukkit.getPluginManager().registerEvents(this, plugin); + } + + @Override + public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { + if (!(sender instanceof Player p)) return true; + if (!p.hasPermission("smputils.reports")) { + sender.sendMessage(smpPlugin.getMessage("command.error.no_permission", Map.of("player", sender.getName()))); + return true; + } + + pageMap.put(p.getUniqueId(), 0); + openReportPage(p, 0); + return true; + } + + private void openReportPage(Player p, int page) { + List> reports = db.getAllReports(); + int totalPages = (int) Math.ceil((double) reports.size() / REPORTS_PER_PAGE); + if (page < 0 || page >= totalPages) page = 0; + + Inventory inv = Bukkit.createInventory(p, 54, "§cAll Reports - Page " + (page + 1)); + + int start = page * REPORTS_PER_PAGE; + int end = Math.min(start + REPORTS_PER_PAGE, reports.size()); + + for (int i = 10, index = start; index < end; index++, i++) { + if ((i + 1) % 9 == 0) i += 2; + Map report = reports.get(index); + ItemStack paper = new ItemStack(Material.PAPER); + ItemMeta meta = paper.getItemMeta(); + meta.setDisplayName("§eVictim: §f" + report.get("reported")); + meta.setLore(List.of( + "§7By: §a" + report.get("reporter"), + "§7Reason: §f" + report.get("reason"), + "§8ID: " + report.get("id") + )); + paper.setItemMeta(meta); + inv.setItem(i, paper); + } + + if (page > 0) { + ItemStack prev = new ItemStack(Material.ARROW); + ItemMeta meta = prev.getItemMeta(); + meta.setDisplayName("§a⬅ Back"); + prev.setItemMeta(meta); + inv.setItem(45, prev); + } + + if (page < totalPages - 1) { + ItemStack next = new ItemStack(Material.ARROW); + ItemMeta meta = next.getItemMeta(); + meta.setDisplayName("§a➡ Next Page"); + next.setItemMeta(meta); + inv.setItem(53, next); + } + + p.openInventory(inv); + pageMap.put(p.getUniqueId(), page); + } + + @EventHandler + public void onClick(InventoryClickEvent e) { + if (!(e.getWhoClicked() instanceof Player p)) return; + String title = e.getView().getTitle(); + if (!title.startsWith("§cAll Reports - Page")) return; + + e.setCancelled(true); + ItemStack item = e.getCurrentItem(); + if (item == null || !item.hasItemMeta() || item.getType() == Material.AIR) return; + + UUID uuid = p.getUniqueId(); + int currentPage = pageMap.getOrDefault(uuid, 0); + + String name = item.getItemMeta().getDisplayName(); + if (name.equals("§a➡ Next Page")) { + openReportPage(p, currentPage + 1); + return; + } + if (name.equals("§a⬅ Back")) { + openReportPage(p, currentPage - 1); + return; + } + + String idLine = item.getItemMeta().getLore().stream() + .filter(l -> l.startsWith("§8ID: ")) + .findFirst().orElse(null); + if (idLine == null) return; + + String reportId = idLine.replace("§8ID: ", ""); + Bukkit.getScheduler().runTask(plugin, () -> ReportActionGUI.open(plugin, db, p, reportId, smpPlugin)); + } + + @EventHandler + public void onInventoryDrag(InventoryDragEvent e) { + if (!(e.getWhoClicked() instanceof Player p)) return; + if (e.getView().getTitle().startsWith("§cAll Reports - Page")) { + e.setCancelled(true); + } + } + +} diff --git a/src/main/java/de/winniepat/SMPPlugin/secrets/SecretButtonHandler.java b/src/main/java/de/winniepat/SMPPlugin/secrets/SecretButtonHandler.java new file mode 100644 index 0000000..b319c05 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/secrets/SecretButtonHandler.java @@ -0,0 +1,74 @@ +package de.winniepat.SMPPlugin.secrets; + +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.CommandBlock; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemRarity; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.Collections; +import java.util.Set; +import java.util.UUID; + +public class SecretButtonHandler implements Listener { + private static final String WORLD = "world"; + private static final int A_X = -652, A_Y = 63, A_Z = -837; + private static final int B_X = 0, B_Y = 0, B_Z = 0; + + + @EventHandler + public void onButtonPress(PlayerInteractEvent event) { + if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return; + if (event.getClickedBlock() == null) return; + + Block block = event.getClickedBlock(); + Player player = event.getPlayer(); + UUID uuid = player.getUniqueId(); + + if (block.getType() != Material.STONE_BUTTON) return; + + String world = block.getWorld().getName(); + int x = block.getX(), y = block.getY(), z = block.getZ(); + + if (!world.equals(WORLD)) return; + + if (x == A_X && y == A_Y && z == A_Z) { + if (SecretsSQLite.hasClaimedSecret(uuid, "secretA")) { + player.sendMessage("§cYou've already claimed this Secret!"); + return; + } + + ItemStack secret1 = new ItemStack(Material.SPORE_BLOSSOM); + ItemMeta secret1meta = secret1.getItemMeta(); + secret1meta.setItemName("Prinzessinnen-Enzian"); + secret1meta.addEnchant(Enchantment.SMITE, 5, false); + secret1meta.addEnchant(Enchantment.LOOTING, 3, false); + secret1meta.addEnchant(Enchantment.UNBREAKING, 3, false); + secret1meta.addEnchant(Enchantment.MENDING, 1, false); + secret1meta.setRarity(ItemRarity.EPIC); + secret1.setItemMeta(secret1meta); + + player.getInventory().addItem(new ItemStack(secret1)); + SecretsSQLite.markSecretClaimed(uuid, "secretA"); + player.sendMessage("§aYou claimed Secret myfcknmelody's secret!"); + return; + } + + if (x == B_X && y == B_Y && z == B_Z) { + if (SecretsSQLite.hasClaimedSecret(uuid, "secretB")) { + player.sendMessage("§cYou've already claimed Secret B!"); + return; + } + player.getInventory().addItem(new ItemStack(Material.DIAMOND)); + SecretsSQLite.markSecretClaimed(uuid, "secretB"); + player.sendMessage("§bYou claimed Secret B!"); + } + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/secrets/SecretsSQLite.java b/src/main/java/de/winniepat/SMPPlugin/secrets/SecretsSQLite.java new file mode 100644 index 0000000..e711727 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/secrets/SecretsSQLite.java @@ -0,0 +1,63 @@ +package de.winniepat.SMPPlugin.secrets; + +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.sql.*; +import java.util.UUID; + +public class SecretsSQLite { + private static Connection connection; + + public static void initDatabase(JavaPlugin plugin) { + try { + File dbFile = new File(plugin.getDataFolder(), "data.db"); + dbFile.getParentFile().mkdirs(); + connection = DriverManager.getConnection("jdbc:sqlite:" + dbFile.getAbsolutePath()); + + try (Statement stmt = connection.createStatement()) { + stmt.execute(""" + CREATE TABLE IF NOT EXISTS secrets ( + uuid TEXT PRIMARY KEY, + secretA INTEGER DEFAULT 0, + secretB INTEGER DEFAULT 0 + ); + """); + } + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public static boolean hasClaimedSecret(UUID uuid, String secret) { + try (PreparedStatement stmt = connection.prepareStatement("SELECT " + secret + " FROM secrets WHERE uuid = ?")) { + stmt.setString(1, uuid.toString()); + ResultSet rs = stmt.executeQuery(); + return rs.next() && rs.getInt(secret) == 1; + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + } + + public static void markSecretClaimed(UUID uuid, String secret) { + try { + // First ensure row exists + try (PreparedStatement insert = connection.prepareStatement("INSERT OR IGNORE INTO secrets(uuid) VALUES (?)")) { + insert.setString(1, uuid.toString()); + insert.executeUpdate(); + } + + try (PreparedStatement update = connection.prepareStatement("UPDATE secrets SET " + secret + " = 1 WHERE uuid = ?")) { + update.setString(1, uuid.toString()); + update.executeUpdate(); + } + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public static boolean hasClaimedBoth(UUID uuid) { + return hasClaimedSecret(uuid, "secretA") && hasClaimedSecret(uuid, "secretB"); + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/starter/MoveEvent.java b/src/main/java/de/winniepat/SMPPlugin/starter/MoveEvent.java new file mode 100644 index 0000000..7ce4fce --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/starter/MoveEvent.java @@ -0,0 +1,17 @@ +package de.winniepat.SMPPlugin.starter; + +import de.winniepat.SMPPlugin.SMPPlugin; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerMoveEvent; + +public class MoveEvent implements Listener { + @EventHandler + public void onPlayerMove(PlayerMoveEvent event) { + if (SMPPlugin.frozenPlayers.contains(event.getPlayer().getUniqueId())) { + if (!event.getFrom().toVector().equals(event.getTo().toVector())) { + event.setTo(event.getFrom()); + } + } + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/starter/StartCommand.java b/src/main/java/de/winniepat/SMPPlugin/starter/StartCommand.java new file mode 100644 index 0000000..89f02d8 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/starter/StartCommand.java @@ -0,0 +1,52 @@ +package de.winniepat.SMPPlugin.starter; + +import de.winniepat.SMPPlugin.SMPPlugin; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +public class StartCommand implements CommandExecutor { + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (args.length == 1 && args[0].equalsIgnoreCase("start")) { + Location spawn = new Location(Bukkit.getWorlds().get(0), 0, Bukkit.getWorlds().get(0).getHighestBlockYAt(0, 0) + 1, 0); + + for (Player player : Bukkit.getOnlinePlayers()) { + if (!player.isOp()) { + player.teleport(spawn); + SMPPlugin.frozenPlayers.add(player.getUniqueId()); + } + } + + new BukkitRunnable() { + int countdown = 3; + + @Override + public void run() { + if (countdown > 0) { + Bukkit.broadcastMessage(ChatColor.YELLOW + "Starts in " + countdown + "..."); + countdown--; + } else { + for (Player player : Bukkit.getOnlinePlayers()) { + SMPPlugin.frozenPlayers.remove(player.getUniqueId()); + player.sendTitle(ChatColor.GREEN + "Have Fun", ChatColor.YELLOW + "-SMP-", 10, 40, 10); + } + cancel(); + } + } + }.runTaskTimer(SMPPlugin.getInstance(), 0, 20); + + return true; + } + + sender.sendMessage(ChatColor.RED + "Usage: /smp start"); + return true; + } + +} diff --git a/src/main/java/de/winniepat/SMPPlugin/suggestions/Suggestion.java b/src/main/java/de/winniepat/SMPPlugin/suggestions/Suggestion.java new file mode 100644 index 0000000..a10fa85 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/suggestions/Suggestion.java @@ -0,0 +1,33 @@ +package de.winniepat.SMPPlugin.suggestions; + +import java.time.Instant; +import java.util.UUID; + +public class Suggestion { + private final int id; + private final UUID playerUuid; + private final String playerName; + private final String title; + private final String content; + private SuggestionStatus status; + private final Instant timestamp; + + public Suggestion(int id, UUID playerUuid, String playerName, String title, String content) { + this.id = id; + this.playerUuid = playerUuid; + this.playerName = playerName; + this.title = title; + this.content = content; + this.status = SuggestionStatus.OPEN; + this.timestamp = Instant.now(); + } + + public int getId() { return id; } + public UUID getPlayerUuid() { return playerUuid; } + public String getPlayerName() { return playerName; } + public String getTitle() { return title; } + public String getContent() { return content; } + public SuggestionStatus getStatus() { return status; } + public void setStatus(SuggestionStatus status) { this.status = status; } + public Instant getTimestamp() { return timestamp; } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/suggestions/SuggestionGui.java b/src/main/java/de/winniepat/SMPPlugin/suggestions/SuggestionGui.java new file mode 100644 index 0000000..02e08ee --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/suggestions/SuggestionGui.java @@ -0,0 +1,157 @@ +package de.winniepat.SMPPlugin.suggestions; + +import de.winniepat.SMPPlugin.SMPPlugin; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.Listener; +import org.bukkit.event.EventHandler; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.*; + +public class SuggestionGui implements Listener { + + private final SuggestionManager manager; + private final Map pageMap = new HashMap<>(); + private final SMPPlugin smpPlugin; + + public SuggestionGui(SuggestionManager manager, SMPPlugin smpPlugin) { + this.smpPlugin = smpPlugin; + this.manager = manager; + } + + public void open(Player player, SuggestionStatus filter) { + open(player, filter, pageMap.getOrDefault(player.getUniqueId(), 0)); + } + + public void open(Player player, SuggestionStatus filter, int page) { + List suggestions = new ArrayList<>(manager.getAllSuggestions()); + if (filter != null) suggestions.removeIf(s -> s.getStatus() != filter); + + int itemsPerPage = 45; + int totalPages = (int) Math.ceil(suggestions.size() / (double) itemsPerPage); + page = Math.max(0, Math.min(page, totalPages - 1)); + pageMap.put(player.getUniqueId(), page); + + Inventory inv = Bukkit.createInventory(null, 54, "§8Suggestions §7Page " + (page + 1)); + + int start = page * itemsPerPage; + int end = Math.min(start + itemsPerPage, suggestions.size()); + List pageItems = suggestions.subList(start, end); + + int i = 0; + for (Suggestion s : pageItems) { + ItemStack paper = new ItemStack(Material.PAPER); + ItemMeta meta = paper.getItemMeta(); + meta.setDisplayName("§e[#" + s.getId() + "] §f" + s.getTitle()); + List lore = new ArrayList<>(); + lore.add("§7Von: §a" + s.getPlayerName()); + lore.add("§7Status: §b" + s.getStatus().name()); + lore.add("§8" + s.getContent()); + lore.add("§7» §dShift-Leftclick: review"); + lore.add("§7» §aLeftclick: accept"); + lore.add("§7» §cRightclick: deny"); + lore.add("§7» §eShift-Rightclick: delete"); + meta.setLore(lore); + paper.setItemMeta(meta); + inv.setItem(i++, paper); + } + + inv.setItem(45, createControlItem(Material.ARROW, "§7« Bacl", "page:prev")); + inv.setItem(46, createFilterItem(Material.GREEN_WOOL, "§aAccepted", SuggestionStatus.ACCEPTED)); + inv.setItem(47, createFilterItem(Material.YELLOW_WOOL, "§eReview", SuggestionStatus.REVIEW)); + inv.setItem(48, createFilterItem(Material.RED_WOOL, "§cDenied", SuggestionStatus.REJECTED)); + inv.setItem(49, createFilterItem(Material.GRAY_WOOL, "§7Show All", SuggestionStatus.NONE)); + inv.setItem(53, createControlItem(Material.ARROW, "§7Next Page »", "page:next")); + + player.openInventory(inv); + } + + private ItemStack createFilterItem(Material mat, String name, SuggestionStatus status) { + ItemStack item = new ItemStack(mat); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(name); + if (status != null) meta.setLore(Collections.singletonList("filter:" + status.name())); + item.setItemMeta(meta); + return item; + } + + private ItemStack createControlItem(Material mat, String name, String tag) { + ItemStack item = new ItemStack(mat); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(name); + meta.setLore(Collections.singletonList(tag)); + item.setItemMeta(meta); + return item; + } + + @EventHandler + public void onClick(InventoryClickEvent e) { + if (!e.getView().getTitle().startsWith("§8Suggestions")) return; + e.setCancelled(true); + ItemStack clicked = e.getCurrentItem(); + if (clicked == null || !clicked.hasItemMeta()) return; + + Player p = (Player) e.getWhoClicked(); + ItemMeta meta = clicked.getItemMeta(); + String name = meta.getDisplayName(); + List lore = meta.getLore(); + + if (lore == null || lore.isEmpty()) return; + String tag = lore.get(0); + + if (tag.startsWith("page:")) { + int currentPage = pageMap.getOrDefault(p.getUniqueId(), 0); + int newPage = tag.equals("page:next") ? currentPage + 1 : currentPage - 1; + open(p, null, newPage); + return; + } + + if (tag.startsWith("filter:")) { + String statusName = tag.substring(7); + SuggestionStatus status = SuggestionStatus.valueOf(statusName); + open(p, status, 0); + return; + } + + if (!name.startsWith("§e[#")) return; + int id; + try { + id = Integer.parseInt(name.split("#")[1].split("]")[0]); + } catch (Exception ex) { + p.sendMessage(smpPlugin.getMessage("suggestiongui.processing_error", Map.of("player", p.getName()))); + return; + } + + Suggestion suggestion = manager.getSuggestionById(id); + if (suggestion == null) { + p.sendMessage("§cSuggestion not found."); + return; + } + + switch (e.getClick()) { + case LEFT -> { + manager.updateStatus(id, SuggestionStatus.ACCEPTED); + p.sendMessage("§aSuggestion §e#" + id + " §aaccepted."); + } + case RIGHT -> { + manager.updateStatus(id, SuggestionStatus.REJECTED); + p.sendMessage("§cSuggestion §e#" + id + " §cdenied."); + } + case SHIFT_RIGHT -> { + manager.deleteSuggestion(id); + p.sendMessage("§eSuggestion §7#" + id + " deleted."); + } + case SHIFT_LEFT -> { + manager.updateStatus(id, SuggestionStatus.REVIEW); + p.sendMessage("§aSuggestion §e#" + id + " §awill be reviewedd."); + } + } + + open(p, null, pageMap.getOrDefault(p.getUniqueId(), 0)); + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/suggestions/SuggestionManager.java b/src/main/java/de/winniepat/SMPPlugin/suggestions/SuggestionManager.java new file mode 100644 index 0000000..3a7ab12 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/suggestions/SuggestionManager.java @@ -0,0 +1,117 @@ +package de.winniepat.SMPPlugin.suggestions; + +import java.io.File; +import java.sql.*; +import java.time.Instant; +import java.util.*; + +public class SuggestionManager { + private final Connection connection; + + public SuggestionManager(File file) throws SQLException { + this.connection = DriverManager.getConnection("jdbc:sqlite:" + file.getAbsolutePath()); + createTable(); + } + + private void createTable() throws SQLException { + String sql = """ + CREATE TABLE IF NOT EXISTS suggestions ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + uuid TEXT NOT NULL, + name TEXT NOT NULL, + title TEXT NOT NULL, + content TEXT NOT NULL, + status TEXT NOT NULL, + timestamp TEXT NOT NULL + ) + """; + try (Statement stmt = connection.createStatement()) { + stmt.execute(sql); + } + } + + public Suggestion addSuggestion(UUID uuid, String name, String title, String content) { + String sql = "INSERT INTO suggestions(uuid, name, title, content, status, timestamp) VALUES(?,?,?,?,?,?)"; + try (PreparedStatement stmt = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + stmt.setString(1, uuid.toString()); + stmt.setString(2, name); + stmt.setString(3, title); + stmt.setString(4, content); + stmt.setString(5, SuggestionStatus.OPEN.name()); + stmt.setString(6, Instant.now().toString()); + stmt.executeUpdate(); + + ResultSet keys = stmt.getGeneratedKeys(); + if (keys.next()) { + int id = keys.getInt(1); + return new Suggestion(id, uuid, name, title, content); + } + } catch (SQLException e) { + e.printStackTrace(); + } + return null; + } + + public Collection getAllSuggestions() { + List list = new ArrayList<>(); + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT * FROM suggestions")) { + while (rs.next()) { + int id = rs.getInt("id"); + UUID uuid = UUID.fromString(rs.getString("uuid")); + String name = rs.getString("name"); + String title = rs.getString("title"); + String content = rs.getString("content"); + SuggestionStatus status = SuggestionStatus.valueOf(rs.getString("status")); + Suggestion suggestion = new Suggestion(id, uuid, name, title, content); + suggestion.setStatus(status); + list.add(suggestion); + } + } catch (SQLException e) { + e.printStackTrace(); + } + return list; + } + + public Suggestion getSuggestionById(int id) { + String sql = "SELECT * FROM suggestions WHERE id = ?"; + try (PreparedStatement stmt = connection.prepareStatement(sql)) { + stmt.setInt(1, id); + ResultSet rs = stmt.executeQuery(); + if (rs.next()) { + UUID uuid = UUID.fromString(rs.getString("uuid")); + String name = rs.getString("name"); + String title = rs.getString("title"); + String content = rs.getString("content"); + SuggestionStatus status = SuggestionStatus.valueOf(rs.getString("status")); + Suggestion suggestion = new Suggestion(id, uuid, name, title, content); + suggestion.setStatus(status); + return suggestion; + } + } catch (SQLException e) { + e.printStackTrace(); + } + return null; + } + + public void updateStatus(int id, SuggestionStatus status) { + String sql = "UPDATE suggestions SET status = ? WHERE id = ?"; + try (PreparedStatement stmt = connection.prepareStatement(sql)) { + stmt.setString(1, status.name()); + stmt.setInt(2, id); + stmt.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + public void deleteSuggestion(int id) { + String sql = "DELETE FROM suggestions WHERE id = ?"; + try (PreparedStatement stmt = connection.prepareStatement(sql)) { + stmt.setInt(1, id); + stmt.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/de/winniepat/SMPPlugin/suggestions/SuggestionStatus.java b/src/main/java/de/winniepat/SMPPlugin/suggestions/SuggestionStatus.java new file mode 100644 index 0000000..643c890 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/suggestions/SuggestionStatus.java @@ -0,0 +1,9 @@ +package de.winniepat.SMPPlugin.suggestions; + +public enum SuggestionStatus { + NONE, + OPEN, + ACCEPTED, + REJECTED, + REVIEW +} \ No newline at end of file diff --git a/src/main/java/de/winniepat/SMPPlugin/suggestions/commands/SuggestionCommand.java b/src/main/java/de/winniepat/SMPPlugin/suggestions/commands/SuggestionCommand.java new file mode 100644 index 0000000..9f6cc2f --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/suggestions/commands/SuggestionCommand.java @@ -0,0 +1,106 @@ +package de.winniepat.SMPPlugin.suggestions.commands; + +import de.winniepat.SMPPlugin.SMPPlugin; +import de.winniepat.SMPPlugin.suggestions.Suggestion; +import de.winniepat.SMPPlugin.suggestions.SuggestionManager; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SuggestionCommand implements CommandExecutor { + + private final SuggestionManager manager; + private final SMPPlugin smpPlugin; + private final Map cooldowns = new HashMap<>(); + private final long COOLDOWN_MILLIS = 60 * 60 * 1000; + + private final File cooldownFile; + private final YamlConfiguration cooldownData; + + public SuggestionCommand(SuggestionManager manager, Plugin plugin, SMPPlugin smpPlugin) { + this.manager = manager; + this.smpPlugin = smpPlugin; + + this.cooldownFile = new File(plugin.getDataFolder(), "suggestion_cooldowns.yml"); + if (!cooldownFile.exists()) { + try { + cooldownFile.getParentFile().mkdirs(); + cooldownFile.createNewFile(); + System.out.println("[Suggestion] Created cooldowns.yml at: " + cooldownFile.getAbsolutePath()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + this.cooldownData = YamlConfiguration.loadConfiguration(cooldownFile); + + for (String key : cooldownData.getKeys(false)) { + try { + UUID uuid = UUID.fromString(key); + long timestamp = cooldownData.getLong(key); + cooldowns.put(uuid, timestamp); + } catch (IllegalArgumentException ignored) {} + } + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player player)) { + sender.sendMessage(smpPlugin.getMessage("command.error.no_player", Map.of("player", sender.getName()))); + return true; + } + + if (args.length == 0) { + player.sendMessage(smpPlugin.getMessage("command.suggestion.usage", Map.of("player", player.getName()))); + return true; + } + + UUID uuid = player.getUniqueId(); + long now = System.currentTimeMillis(); + + if (cooldowns.containsKey(uuid)) { + long lastUsed = cooldowns.get(uuid); + long timeLeft = COOLDOWN_MILLIS - (now - lastUsed); + if (timeLeft > 0) { + player.sendMessage("§cPlease wait §e" + (timeLeft / 1000) + " seconds§c, before you send another Suggestion."); + return true; + } + } + + String input = String.join(" ", args); + Matcher matcher = Pattern.compile("^\"([^\"]{1,50})\"\\s+(.{1,300})$").matcher(input); + + if (!matcher.matches()) { + player.sendMessage(smpPlugin.getMessage("command.suggestion.usage", Map.of("player", player.getName()))); + return true; + } + + String title = matcher.group(1).trim(); + String content = matcher.group(2).trim(); + + Suggestion suggestion = manager.addSuggestion(uuid, player.getName(), title, content); + if (suggestion != null) { + player.sendMessage("§aYour Suggestion was submitted with the ID §e#" + suggestion.getId()); + cooldowns.put(uuid, now); + cooldownData.set(uuid.toString(), now); + try { + cooldownData.save(cooldownFile); + } catch (IOException e) { + e.printStackTrace(); + } + } else { + player.sendMessage(smpPlugin.getMessage("command.suggestion.fail", Map.of("player", player.getName()))); + } + + return true; + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/suggestions/commands/SuggestionListCommand.java b/src/main/java/de/winniepat/SMPPlugin/suggestions/commands/SuggestionListCommand.java new file mode 100644 index 0000000..514c0a8 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/suggestions/commands/SuggestionListCommand.java @@ -0,0 +1,26 @@ +package de.winniepat.SMPPlugin.suggestions.commands; + +import de.winniepat.SMPPlugin.suggestions.Suggestion; +import de.winniepat.SMPPlugin.suggestions.SuggestionManager; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +public class SuggestionListCommand implements CommandExecutor { + + private final SuggestionManager manager; + + public SuggestionListCommand(SuggestionManager manager) { + this.manager = manager; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String @NotNull [] args) { + for (Suggestion suggestion : manager.getAllSuggestions()) { + sender.sendMessage("§7[#" + suggestion.getId() + "] §e" + suggestion.getTitle() + + " §8von §6" + suggestion.getPlayerName() + " §8(§f" + suggestion.getStatus() + "§8)"); + } + return true; + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/suggestions/commands/SuggestionStatusCommand.java b/src/main/java/de/winniepat/SMPPlugin/suggestions/commands/SuggestionStatusCommand.java new file mode 100644 index 0000000..f47e54b --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/suggestions/commands/SuggestionStatusCommand.java @@ -0,0 +1,64 @@ +package de.winniepat.SMPPlugin.suggestions.commands; + +import de.winniepat.SMPPlugin.SMPPlugin; +import de.winniepat.SMPPlugin.suggestions.Suggestion; +import de.winniepat.SMPPlugin.suggestions.SuggestionManager; +import de.winniepat.SMPPlugin.suggestions.SuggestionStatus; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; + +public class SuggestionStatusCommand implements CommandExecutor { + + private final SuggestionManager manager; + private final SMPPlugin smpPlugin; + + public SuggestionStatusCommand(SuggestionManager manager, SMPPlugin smpPlugin) { + this.smpPlugin = smpPlugin; + this.manager = manager; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) { + if (args.length < 2) { + sender.sendMessage(smpPlugin.getMessage("command.suggestionstatus.usage", Map.of("player", sender.getName()))); + return true; + } + + SuggestionStatus status; + switch (args[0].toLowerCase()) { + case "accept" -> status = SuggestionStatus.ACCEPTED; + case "reject" -> status = SuggestionStatus.REJECTED; + case "review" -> status = SuggestionStatus.REVIEW; + default -> { + sender.sendMessage("§cInvalid Status: " + args[0]); + return true; + } + } + + try { + int id = Integer.parseInt(args[1]); + Suggestion suggestion = manager.getSuggestionById(id); + if (suggestion == null) { + sender.sendMessage("§cNo suggestion found with ID " + id); + return true; + } + manager.updateStatus(id, status); + sender.sendMessage("§aSuggestion #" + id + " was set to " + status); + + Player player = Bukkit.getPlayer(suggestion.getPlayerUuid()); + if (player != null && player.isOnline()) { + player.sendMessage("§aYour suggestion (#" + id + ") was set to §e" + status); + } + + } catch (NumberFormatException e) { + sender.sendMessage("§cInvalid ID: " + args[1]); + } + return true; + } +} \ No newline at end of file diff --git a/src/main/java/de/winniepat/SMPPlugin/waypoints/GiveWaypointStoneListener.java b/src/main/java/de/winniepat/SMPPlugin/waypoints/GiveWaypointStoneListener.java new file mode 100644 index 0000000..d1ddaf2 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/waypoints/GiveWaypointStoneListener.java @@ -0,0 +1,44 @@ +package de.winniepat.SMPPlugin.waypoints; + +import de.winniepat.SMPPlugin.SMPPlugin; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataType; + +import java.util.Map; + +public class GiveWaypointStoneListener implements Listener { + + private final SMPPlugin smpPlugin; + + public GiveWaypointStoneListener(SMPPlugin smpPlugin) { + this.smpPlugin = smpPlugin; + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + + if (!player.hasPlayedBefore()) { + ItemStack item = createWaypointStone(); + player.getInventory().addItem(item); + player.sendMessage(smpPlugin.getMessage("waypoint.first_join", Map.of("player", player.getName()))); + } + } + + private ItemStack createWaypointStone() { + ItemStack item = new ItemStack(Material.LODESTONE); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(ChatColor.AQUA + "Waypoint Stone"); + meta.getPersistentDataContainer().set(new NamespacedKey(SMPPlugin.getInstance(), "waypoint"), PersistentDataType.STRING, "true"); + item.setItemMeta(meta); + return item; + } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/waypoints/Waypoint.java b/src/main/java/de/winniepat/SMPPlugin/waypoints/Waypoint.java new file mode 100644 index 0000000..5a4be46 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/waypoints/Waypoint.java @@ -0,0 +1,16 @@ +package de.winniepat.SMPPlugin.waypoints; + +import org.bukkit.Location; + +public class Waypoint { + private final String name; + private final Location location; + + public Waypoint(String name, Location location) { + this.name = name; + this.location = location; + } + + public String getName() { return name; } + public Location getLocation() { return location; } +} diff --git a/src/main/java/de/winniepat/SMPPlugin/waypoints/WaypointCommands.java b/src/main/java/de/winniepat/SMPPlugin/waypoints/WaypointCommands.java new file mode 100644 index 0000000..82b7ed0 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/waypoints/WaypointCommands.java @@ -0,0 +1,56 @@ +package de.winniepat.SMPPlugin.waypoints; + +import de.winniepat.SMPPlugin.SMPPlugin; +import org.bukkit.ChatColor; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.command.*; +import org.bukkit.entity.Player; +import org.bukkit.inventory.*; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataType; + +import java.util.Map; + +public class WaypointCommands implements CommandExecutor { + private final WaypointsDatabase sqliteManager; + private final SMPPlugin smpPlugin; + + public WaypointCommands(WaypointsDatabase sqliteManager, SMPPlugin smpPlugin) { + this.sqliteManager = sqliteManager; + this.smpPlugin = smpPlugin; + } + + @Override + public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { + if (!(sender instanceof Player player)) return false; + + if (label.equalsIgnoreCase("getwaypoint")) { + player.getInventory().addItem(createWaypointStone()); + player.sendMessage(smpPlugin.getMessage("getwaypoint.success", Map.of("player", player.getName()))); + return true; + } + + if (label.equalsIgnoreCase("addwaypoint") && args.length >= 1) { + String name = String.join(" ", args); + boolean success = sqliteManager.saveWaypoint(player.getUniqueId(), new Waypoint(name, player.getLocation())); + if (success) { + player.sendMessage(ChatColor.GREEN + "Waypoint '" + name + "' saved."); + } else { + player.sendMessage(smpPlugin.getMessage("addwaypoint.already_exists", Map.of("player", player.getName()))); + } + return true; + } + + return false; + } + + private ItemStack createWaypointStone() { + ItemStack item = new ItemStack(Material.LODESTONE); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(ChatColor.AQUA + "Your Waypoints"); + meta.getPersistentDataContainer().set(new NamespacedKey(SMPPlugin.getInstance(), "waypoint"), PersistentDataType.STRING, "true"); + item.setItemMeta(meta); + return item; + } +} \ No newline at end of file diff --git a/src/main/java/de/winniepat/SMPPlugin/waypoints/WaypointListener.java b/src/main/java/de/winniepat/SMPPlugin/waypoints/WaypointListener.java new file mode 100644 index 0000000..aaeb535 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/waypoints/WaypointListener.java @@ -0,0 +1,152 @@ +package de.winniepat.SMPPlugin.waypoints; + +import de.winniepat.SMPPlugin.SMPPlugin; +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.player.*; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.inventory.*; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.*; + +public class WaypointListener implements Listener { + private final WaypointsDatabase sqliteManager; + private final Map pendingWaypoints = new HashMap<>(); + private final Map pendingLocations = new HashMap<>(); + private final SMPPlugin smpPlugin; + + public WaypointListener(WaypointsDatabase sqliteManager, SMPPlugin smpPlugin) { + this.sqliteManager = sqliteManager; + this.smpPlugin = smpPlugin; + } + + @EventHandler + public void onPlayerInteract(PlayerInteractEvent e) { + Player player = e.getPlayer(); + if (e.getItem() == null || !e.getItem().hasItemMeta()) return; + + ItemMeta meta = e.getItem().getItemMeta(); + if (!"true".equals(meta.getPersistentDataContainer() + .get(new NamespacedKey(SMPPlugin.getInstance(), "waypoint"), PersistentDataType.STRING))) return; + + if (e.getAction() == Action.RIGHT_CLICK_BLOCK) { + e.setCancelled(true); + Block block = e.getClickedBlock(); + if (block == null) return; + + Location loc = block.getLocation().add(0.5, 1, 0.5); + pendingWaypoints.put(player.getUniqueId(), loc); + pendingLocations.put(player.getUniqueId(), player.getLocation().clone()); + player.sendMessage(ChatColor.YELLOW + "Please enter a name for your waypoint in chat. Don't move!"); + } else if (e.getAction() == Action.RIGHT_CLICK_AIR) { + e.setCancelled(true); + openWaypointMenu(player); + } + } + + @EventHandler + public void onPlayerChat(AsyncPlayerChatEvent e) { + Player player = e.getPlayer(); + if (pendingWaypoints.containsKey(player.getUniqueId())) { + e.setCancelled(true); + String name = e.getMessage().trim(); + Location loc = pendingWaypoints.remove(player.getUniqueId()); + pendingLocations.remove(player.getUniqueId()); + + Bukkit.getScheduler().runTask(SMPPlugin.getInstance(), () -> { + boolean success = sqliteManager.saveWaypoint(player.getUniqueId(), new Waypoint(name, loc)); + if (success) { + player.sendMessage(ChatColor.GREEN + "Waypoint '" + name + "' set!"); + player.playSound(player.getLocation(), Sound.BLOCK_BEACON_ACTIVATE, 1f, 1f); + } else { + player.sendMessage(smpPlugin.getMessage("waypointlistener.already_exists", Map.of("player", player.getName()))); + player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 1f); + } + }); + } + } + + @EventHandler + public void onPlayerMove(PlayerMoveEvent e) { + Player player = e.getPlayer(); + if (pendingLocations.containsKey(player.getUniqueId())) { + Location from = pendingLocations.get(player.getUniqueId()); + if (e.getTo() != null && e.getTo().distance(from) > 0.1) { + pendingWaypoints.remove(player.getUniqueId()); + pendingLocations.remove(player.getUniqueId()); + player.sendMessage(smpPlugin.getMessage("waypointlistener.movement", Map.of("player", player.getName()))); + player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 0.5f); + } + } + } + + public void openWaypointMenu(Player player) { + List waypoints = sqliteManager.getWaypoints(player.getUniqueId()); + Inventory gui = Bukkit.createInventory(null, 9 * 3, "Your Waypoints"); + for (Waypoint wp : waypoints) { + ItemStack item = new ItemStack(Material.LODESTONE); + ItemMeta meta = item.getItemMeta(); + meta.setDisplayName(ChatColor.YELLOW + wp.getName()); + item.setItemMeta(meta); + gui.addItem(item); + } + player.openInventory(gui); + } + + @EventHandler + public void onInventoryClick(InventoryClickEvent e) { + if (e.getView().getTitle().equals("Your Waypoints")) { + e.setCancelled(true); + ItemStack clicked = e.getCurrentItem(); + if (clicked != null && clicked.hasItemMeta()) { + String name = ChatColor.stripColor(clicked.getItemMeta().getDisplayName()); + Player player = (Player) e.getWhoClicked(); + List waypoints = sqliteManager.getWaypoints(player.getUniqueId()); + for (Waypoint wp : waypoints) { + if (wp.getName().equals(name)) { + player.closeInventory(); + startTeleportCountdown(player, wp); + break; + } + } + } + } + } + + private void startTeleportCountdown(Player player, Waypoint waypoint) { + Location initialLoc = player.getLocation().clone(); + player.sendMessage(ChatColor.YELLOW + "Teleporting to " + waypoint.getName() + " in 5 seconds. Don't move!"); + + new BukkitRunnable() { + int countdown = 5; + + @Override + public void run() { + if (!player.isOnline() || player.getLocation().distance(initialLoc) > 0.1) { + player.sendMessage(ChatColor.RED + "Teleport cancelled due to movement."); + player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1f, 0.5f); + cancel(); + return; + } + + if (countdown > 0) { + player.sendActionBar(ChatColor.YELLOW + "Teleporting in " + countdown + "..."); + player.playSound(player.getLocation(), Sound.UI_BUTTON_CLICK, 1f, 1f); + countdown--; + } else { + player.teleport(waypoint.getLocation()); + player.sendMessage(ChatColor.GREEN + "Teleported to " + waypoint.getName() + "!"); + player.playSound(player.getLocation(), Sound.ENTITY_ENDERMAN_TELEPORT, 1f, 1f); + cancel(); + } + } + }.runTaskTimer(SMPPlugin.getInstance(), 0L, 20L); + } +} \ No newline at end of file diff --git a/src/main/java/de/winniepat/SMPPlugin/waypoints/WaypointsDatabase.java b/src/main/java/de/winniepat/SMPPlugin/waypoints/WaypointsDatabase.java new file mode 100644 index 0000000..d899ce2 --- /dev/null +++ b/src/main/java/de/winniepat/SMPPlugin/waypoints/WaypointsDatabase.java @@ -0,0 +1,82 @@ +package de.winniepat.SMPPlugin.waypoints; + +import org.bukkit.Bukkit; +import org.bukkit.Location; + +import java.sql.*; +import java.util.*; + +public class WaypointsDatabase { + private final String url = "jdbc:sqlite:plugins/SMPPlugin/waypoints.db"; + + public WaypointsDatabase() { + try (Connection conn = DriverManager.getConnection(url); + Statement stmt = conn.createStatement()) { + stmt.executeUpdate(""" + CREATE TABLE IF NOT EXISTS waypoints ( + player_uuid TEXT, + name TEXT, + world TEXT, + x DOUBLE, + y DOUBLE, + z DOUBLE + ) + """); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public boolean saveWaypoint(UUID playerUuid, Waypoint waypoint) { + try (Connection conn = DriverManager.getConnection(url); + PreparedStatement check = conn.prepareStatement(""" + SELECT 1 FROM waypoints WHERE player_uuid = ? AND name = ? + """)) { + check.setString(1, playerUuid.toString()); + check.setString(2, waypoint.getName()); + ResultSet rs = check.executeQuery(); + if (rs.next()) { + return false; + } + } catch (SQLException e) { + e.printStackTrace(); + } + + try (Connection conn = DriverManager.getConnection(url); + PreparedStatement ps = conn.prepareStatement(""" + INSERT INTO waypoints (player_uuid, name, world, x, y, z) VALUES (?, ?, ?, ?, ?, ?) + """)) { + ps.setString(1, playerUuid.toString()); + ps.setString(2, waypoint.getName()); + ps.setString(3, waypoint.getLocation().getWorld().getName()); + ps.setDouble(4, waypoint.getLocation().getX()); + ps.setDouble(5, waypoint.getLocation().getY()); + ps.setDouble(6, waypoint.getLocation().getZ()); + ps.executeUpdate(); + return true; + } catch (SQLException e) { + e.printStackTrace(); + } + return false; + } + + public List getWaypoints(UUID playerUuid) { + List list = new ArrayList<>(); + try (Connection conn = DriverManager.getConnection(url); + PreparedStatement ps = conn.prepareStatement(""" + SELECT name, world, x, y, z FROM waypoints WHERE player_uuid = ? + """)) { + ps.setString(1, playerUuid.toString()); + ResultSet rs = ps.executeQuery(); + while (rs.next()) { + String name = rs.getString("name"); + String world = rs.getString("world"); + double x = rs.getDouble("x"), y = rs.getDouble("y"), z = rs.getDouble("z"); + list.add(new Waypoint(name, new Location(Bukkit.getWorld(world), x, y, z))); + } + } catch (SQLException e) { + e.printStackTrace(); + } + return list; + } +} \ No newline at end of file diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml new file mode 100644 index 0000000..2e2a6b2 --- /dev/null +++ b/src/main/resources/messages.yml @@ -0,0 +1,56 @@ +command.error.no_player: "§cOnly players are able to execute this command." +command.error.no_permission: "§cYou don't have permissions to do that." + +# Coding Command +command.coding.usage: "§cUse /coding on or /coding off" +command.coding.already: "§eYou're already marked as coding." +command.coding.marked: "§aYou are now marked as coding." +command.coding.unmarked: "§aYou are no longer marked as coding." +command.coding.error: "§cError while marking you as coding." + +# Poll Command +command.poll.usage: "§c/pollstart \"question\" Duration Option1 Option2 ..." +command.poll.already_active: "§cThere is already an active poll." +command.poll.invalid_time: "§cInvalid Time. Choose a number for seconds." + +# Vote GUI +pollvotegui.already_voted: "§cYou already voted." + +# Report +command.report.invalid_player: "§cInvalid Player." +command.report.reason: "§cPlease tell us exactly the reason." +command.report.success: "§aReport Saved! Staff will look at it." +command.report.usage: "§7Use: §e/report " + +# ReportActionGUI +reportactiongui.not_found: "§cReport not found." +reportactiongui.successful_delete: "§aReport deleted." +reportactiongui.successful_ban: "§cPlayer was permanently banned." + +# Suggestion Command +command.suggestion.usage: "§7Use: §e/suggestion \"Title with quotes\" My super Suggestion" +command.suggestion.fail: "§cAn Error occurred. Your Suggestion couldn't be submitted." + +# SuggestionStatus Command +command.suggestionstatus.usage: "§7Benutze: §e/suggestionstatus " + +# Suggestion GUI +suggestiongui.processing_error: "§cError while processing the suggestion-id." + +# Waypoint System +waypoint.first_join: "§6You've received your first Waystone! Use it to set and teleport to waypoints." + +# GiveWaypointStoneListener +getwaypoint.success: "&aWaypoint Stone added to your inventory." + +# addwaypoint Command +addwaypoint.already_exists: "§cA waypoint with that name already exists." + +# WaypointListener +waypointlistener.already_exists: "§cA waypoint with that name already exists." +waypointlistener.movement: "§cWaypoint creation cancelled due to movement." + +# PlayerSneakListener +blockelevators.on_cooldown: "§cElevator is cooling down" +blockelevators.no_elevator_below: "§eNo elevator block below!" +blockelevators.no_elevator_above: "§eNo elevator block above!" \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..4f7eece --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,70 @@ +name: SMPPlugin +version: '1.0' +main: de.winniepat.SMPPlugin.SMPPlugin +api-version: '1.21' +load: STARTUP +authors: [ WinniePat ] +website: https://winniepat.de +commands: + trash: + description: Öffne den Mülleimer zum Löschen von Items + report: + description: Spieler melden + reports: + description: Alle Reports anzeigen + permission: smputils.reports + suggestion: + description: Sende einen Vorschlag + usage: / | + suggestions: + description: Zeigt alle Vorschläge im Chat + permission: smpplugin.suggestions.use + aliases: + - vorschlag + suggestionstatus: + description: Setze den Status eines Vorschlags + usage: / + permission: smpplugin.suggestion.status + suggestionsgui: + description: Öffne die Vorschlags-GUI + permission: smpplugin.suggestion.gui + coding: + description: Toggle coding status + usage: /coding + permission: smpplugin.command.coding.use + winnie: + description: Toggle winnie status + usage: /winnie + permission: smpplugin.command.winnie.use + pollstart: + description: Start a poll + aliases: + - poll + pollvote: + description: Vote on a poll + aliases: + - vote + pollresults: + description: Shows the results + bloodmoon: + description: Toggle the Bloodmoon event + usage: /bloodmoon + permission: smpplugin.bloodmoon.toggle + blackmarket: + description: Manually spawns the Black Market trader + usage: /blackmarket + permission: smpplugin.blackmarket.spawn + getwaypoint: + description: Get the waypoint stone + permission: smpplugin.waypoints.getwaypoint + addwaypoint: + description: Add a new waypoint + smp: + description: start + usage: /smp start + spawn: + description: spawn + usage: /spawn + shop: + description: shop + usage: /shop \ No newline at end of file