first commit

This commit is contained in:
Soph :3 2026-02-15 20:48:20 +02:00
commit a2935ad53d
31 changed files with 3127 additions and 0 deletions

170
.gitignore vendored Normal file
View file

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

8
.idea/.gitignore generated vendored Normal file
View file

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

6
.idea/compiler.xml generated Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>

10
.idea/misc.xml generated Normal file
View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

9
.idea/modules.xml generated Normal file
View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/modules/SnowCore.main.iml" filepath="$PROJECT_DIR$/.idea/modules/SnowCore.main.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/SnowCore.test.iml" filepath="$PROJECT_DIR$/.idea/modules/SnowCore.test.iml" />
</modules>
</component>
</project>

13
.idea/modules/SnowCore.main.iml generated Normal file
View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="FacetManager">
<facet type="minecraft" name="Minecraft">
<configuration>
<autoDetectTypes>
<platformType>ADVENTURE</platformType>
</autoDetectTypes>
<projectReimportVersion>1</projectReimportVersion>
</configuration>
</facet>
</component>
</module>

13
.idea/modules/SnowCore.test.iml generated Normal file
View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="FacetManager">
<facet type="minecraft" name="Minecraft">
<configuration>
<autoDetectTypes>
<platformType>ADVENTURE</platformType>
</autoDetectTypes>
<projectReimportVersion>1</projectReimportVersion>
</configuration>
</facet>
</component>
</module>

73
build.gradle Normal file
View file

@ -0,0 +1,73 @@
plugins {
id 'java'
id("xyz.jpenilla.run-paper") version "2.3.1"
id "io.github.goooler.shadow" version "8.1.8"
}
group = 'ovh.sad'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
maven {
name = "papermc-repo"
url = "https://repo.papermc.io/repository/maven-public/"
}
maven { url 'https://jitpack.io' }
maven {
url = 'https://repo.extendedclip.com/releases/'
}
}
tasks.build {
dependsOn shadowJar
}
tasks.shadowJar {
archiveClassifier.set("shaded") // removes "-all"
}
shadowJar {
relocate("dev.triumphteam.gui", "ovh.sad.snowcore.gui")
}
dependencies {
compileOnly("io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT")
implementation "dev.triumphteam:triumph-gui:3.1.13"
compileOnly("com.github.MilkBowl:VaultAPI:1.7") { exclude( group: 'org.bukkit', module: 'bukkit' ) }
compileOnly 'me.clip:placeholderapi:2.12.2'
}
tasks {
runServer {
// Configure the Minecraft version for our task.
// This is the only required configuration besides applying the plugin.
// Your plugin's jar (or shadowJar if present) will be used automatically.
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
}
}

0
gradle.properties Normal file
View file

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

Binary file not shown.

View file

@ -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

249
gradlew vendored Executable file
View file

@ -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" "$@"

92
gradlew.bat vendored Normal file
View file

@ -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

1
settings.gradle Normal file
View file

@ -0,0 +1 @@
rootProject.name = 'SnowCore'

View file

@ -0,0 +1,179 @@
package ovh.sad.snowcore;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.data.Ageable;
import org.bukkit.block.data.Bisected;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.Waterlogged;
import java.util.concurrent.ThreadLocalRandom;
public class Area {
static class MaterialWithChance {
public Material material;
public float chance; // in precentage 1 to 100, 33.33 works aswell
public MaterialWithChance(Material material, float chance) {
this.material = material;
this.chance = chance;
}
}
private final Location positionOne;
private final Location positionTwo;
public final String name;
private final MaterialWithChance[] materials;
public final float airPercentage;
public Area(String name, MaterialWithChance[] materials,
Location positionOne,
Location positionTwo,
float airPercentage
) {
this.name = name;
this.materials = materials;
this.positionOne = positionOne;
this.positionTwo = positionTwo;
this.airPercentage = airPercentage;
}
public void regen(World world) {
int minX = Math.min(positionOne.getBlockX(), positionTwo.getBlockX());
int minY = Math.min(positionOne.getBlockY(), positionTwo.getBlockY());
int minZ = Math.min(positionOne.getBlockZ(), positionTwo.getBlockZ());
int maxX = Math.max(positionOne.getBlockX(), positionTwo.getBlockX());
int maxZ = Math.max(positionOne.getBlockZ(), positionTwo.getBlockZ());
ThreadLocalRandom random = ThreadLocalRandom.current();
for (int x = minX; x <= maxX; x++) {
for (int z = minZ; z <= maxZ; z++) {
Block base = world.getBlockAt(x, minY, z);
if (!isReplaceable(base.getType())) continue;
if (airPercentage > 0 && random.nextInt(100) < airPercentage) {
for (int y = minY; y <= minY + 3; y++) {
Block b = world.getBlockAt(x, y, z);
if (isReplaceable(b.getType())) {
b.setType(Material.AIR, false);
}
}
continue;
}
int totalWeight = 0;
for (MaterialWithChance m : materials) {
totalWeight += m.chance;
}
int roll = random.nextInt(totalWeight);
int current = 0;
MaterialWithChance chosen = null;
for (MaterialWithChance m : materials) {
current += m.chance;
if (roll < current) {
chosen = m;
break;
}
}
if (chosen == null) continue;
for (int y = minY; y <= minY + 3; y++) {
Block b = world.getBlockAt(x, y, z);
if (isReplaceable(b.getType())) {
b.setType(Material.AIR, false);
}
}
if (isDoubleHeight(chosen.material)) {
placeBisected(world, x, minY, z, chosen.material);
} else {
base.setType(chosen.material, false);
BlockData data = base.getBlockData();
if(data instanceof Ageable ageable) {
ageable.setAge(1+random.nextInt(ageable.getMaximumAge()));
base.setBlockData(ageable, false);
}
if (data instanceof Waterlogged waterlogged) {
waterlogged.setWaterlogged(false);
base.setBlockData(waterlogged, false);
}
if (data instanceof org.bukkit.block.data.type.FlowerBed petals) {
int max = petals.getMaximumFlowerAmount();
petals.setFlowerAmount(random.nextInt(1, max + 1));
base.setBlockData(petals, false);
}
}
}
}
}
private boolean isReplaceable(Material m) {
if (m == Material.AIR) return true;
for (MaterialWithChance allowed : materials) {
if (allowed.material == m) return true;
}
return false;
}
private static void placeBisected(World world, int x, int y, int z, Material mat) {
Block bottom = world.getBlockAt(x, y, z);
Block top = world.getBlockAt(x, y + 1, z);
bottom.setType(mat, false);
top.setType(mat, false);
Bisected lower = (Bisected) bottom.getBlockData();
Bisected upper = (Bisected) top.getBlockData();
lower.setHalf(Bisected.Half.BOTTOM);
upper.setHalf(Bisected.Half.TOP);
bottom.setBlockData(lower, false);
top.setBlockData(upper, false);
}
public float getAirPercentage(World world) {
int minX = Math.min(positionOne.getBlockX(), positionTwo.getBlockX());
int maxX = Math.max(positionOne.getBlockX(), positionTwo.getBlockX());
int minY = Math.min(positionOne.getBlockY(), positionTwo.getBlockY());
int minZ = Math.min(positionOne.getBlockZ(), positionTwo.getBlockZ());
int maxZ = Math.max(positionOne.getBlockZ(), positionTwo.getBlockZ());
int total = 0;
int airCount = 0;
for (int x = minX; x <= maxX; x++) {
for (int z = minZ; z <= maxZ; z++) {
total++;
Block block = world.getBlockAt(x, minY, z);
if (block.getType() == Material.AIR) airCount++;
}
}
if (total == 0) return 0f;
return (airCount / (float) total) * 100f;
}
private static boolean isDoubleHeight(Material m) {
return m.createBlockData() instanceof Bisected;
}
}

View file

@ -0,0 +1,115 @@
package ovh.sad.snowcore;
import org.bukkit.Material;
import org.jspecify.annotations.Nullable;
import ovh.sad.snowcore.items.Hoe;
import ovh.sad.snowcore.items.Shovel;
import ovh.sad.snowcore.items.Tool;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Prices {
public static final Map<Material, Double> SELL_PRICES = new HashMap<>();
private static final Map<Material, Integer> toolPrices = new HashMap<>();
static {
/* =========================
Snow (very common / farmable)
========================= */
SELL_PRICES.put(Material.SNOWBALL, 0.5); // safe value
/* =========================
Cactus Area
========================= */
SELL_PRICES.put(Material.CACTUS_FLOWER, 4.0);
SELL_PRICES.put(Material.PEONY, 4.0);
SELL_PRICES.put(Material.PINK_TULIP, 4.0);
SELL_PRICES.put(Material.CACTUS, 7.0);
/* =========================
Spruce Area
========================= */
SELL_PRICES.put(Material.WILDFLOWERS, 1.0);
SELL_PRICES.put(Material.SPRUCE_SAPLING, 10.0);
/* =========================
Pink Area
========================= */
SELL_PRICES.put(Material.PINK_PETALS, 1.0);
SELL_PRICES.put(Material.ALLIUM, 7.0);
SELL_PRICES.put(Material.CHERRY_SAPLING, 20.0);
/* =========================
White Area
========================= */
SELL_PRICES.put(Material.OXEYE_DAISY, 2.0);
SELL_PRICES.put(Material.LILY_OF_THE_VALLEY, 2.0);
SELL_PRICES.put(Material.CLOSED_EYEBLOSSOM, 10.0);
/* =========================
Gray Area
========================= */
SELL_PRICES.put(Material.DEAD_FIRE_CORAL, 4.0);
SELL_PRICES.put(Material.DEAD_TUBE_CORAL, 4.0);
SELL_PRICES.put(Material.DEAD_HORN_CORAL, 4.0);
SELL_PRICES.put(Material.DEAD_BRAIN_CORAL_FAN, 4.0);
SELL_PRICES.put(Material.OPEN_EYEBLOSSOM, 20.0);
/* =========================
Red Area
========================= */
SELL_PRICES.put(Material.ROSE_BUSH, 3.0);
SELL_PRICES.put(Material.POPPY, 3.0);
SELL_PRICES.put(Material.RED_MUSHROOM, 3.0);
SELL_PRICES.put(Material.CRIMSON_FUNGUS, 10.0);
/* Farming areas */
SELL_PRICES.put(Material.BEETROOT_SEEDS, 0.5);
SELL_PRICES.put(Material.POTATO, 1.0);
SELL_PRICES.put(Material.CARROT, 1.0);
SELL_PRICES.put(Material.WHEAT_SEEDS, 0.5);
SELL_PRICES.put(Material.POISONOUS_POTATO, 10.0 );
SELL_PRICES.put(Material.WHEAT, 4.0);
SELL_PRICES.put(Material.BEETROOT, 2.0);
// Load hoe prices
List<? extends Tool<?>> hoes = Hoe.HoeDefinitions.ORDERED;
int[] hoePrices = {0, 1000, 5000, 10000, 40000, 200000}; // adjust if needed
for (int i = 0; i < hoes.size() && i < hoePrices.length; i++) {
toolPrices.put(hoes.get(i).material, hoePrices[i]);
}
// Load shovel prices
List<? extends Tool<?>> shovels = Shovel.ShovelDefinitions.ORDERED;
int[] shovelPrices = {0, 1000, 5000, 10000, 40000, 200000}; // adjust if needed
for (int i = 0; i < shovels.size() && i < shovelPrices.length; i++) {
toolPrices.put(shovels.get(i).material, shovelPrices[i]);
}
}
public static Integer getToolBuyPrice(Tool<?> tool) {
return toolPrices.get(tool.material);
}
public static boolean hasSellPrice(Material mat) {
return SELL_PRICES.containsKey(mat);
}
public static double getSellPrice(Material mat) {
return SELL_PRICES.get(mat);
}
}

View file

@ -0,0 +1,318 @@
package ovh.sad.snowcore;
import io.papermc.paper.command.brigadier.BasicCommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.block.data.type.Snow;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
import ovh.sad.snowcore.commands.RegenCommand;
import ovh.sad.snowcore.commands.SellCommand;
import ovh.sad.snowcore.commands.SnowifyCommand;
import ovh.sad.snowcore.commands.ViewItemsCommand;
import ovh.sad.snowcore.listeners.BreakListener;
import ovh.sad.snowcore.listeners.InteractListener;
import ovh.sad.snowcore.listeners.PlaceListener;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
public final class Snowcore extends JavaPlugin {
public static Economy econ = null;
public static Area[] areas = new Area[]{
new Area(
"Cactus Area",
new Area.MaterialWithChance[]{
new Area.MaterialWithChance(Material.CACTUS_FLOWER, 28.57f),
new Area.MaterialWithChance(Material.PEONY, 28.57f),
new Area.MaterialWithChance(Material.PINK_TULIP, 28.57f),
new Area.MaterialWithChance(Material.CACTUS, 14.29f)
},
new Location(null, 80, 129, 161),
new Location(null, 73, 131, 184),
15
),
new Area(
"Spruce Area",
new Area.MaterialWithChance[]{
new Area.MaterialWithChance(Material.SPRUCE_SAPLING, 10),
new Area.MaterialWithChance(Material.WILDFLOWERS, 80)
},
new Location(null, 64, 129, 161),
new Location(null, 69, 129, 170),
5
),
new Area(
"Pink Area",
new Area.MaterialWithChance[]{
new Area.MaterialWithChance(Material.PINK_PETALS, 80),
new Area.MaterialWithChance(Material.ALLIUM, 15),
new Area.MaterialWithChance(Material.CHERRY_SAPLING, 5)
},
new Location(null, 64, 130, 174),
new Location(null, 69, 129, 184),
0
),
new Area(
"White Area",
new Area.MaterialWithChance[]{
new Area.MaterialWithChance(Material.OXEYE_DAISY, 45),
new Area.MaterialWithChance(Material.LILY_OF_THE_VALLEY, 45),
new Area.MaterialWithChance(Material.CLOSED_EYEBLOSSOM, 10)
},
new Location(null, 69, 129, 189),
new Location(null, 64, 129, 198),
10
),
new Area(
"Gray Area",
new Area.MaterialWithChance[]{
new Area.MaterialWithChance(Material.DEAD_FIRE_CORAL, 23.75f),
new Area.MaterialWithChance(Material.DEAD_TUBE_CORAL, 23.75f),
new Area.MaterialWithChance(Material.DEAD_HORN_CORAL, 23.75f),
new Area.MaterialWithChance(Material.DEAD_BRAIN_CORAL_FAN, 23.75f),
new Area.MaterialWithChance(Material.OPEN_EYEBLOSSOM, 5f)
},
new Location(null, 69, 129, 202),
new Location(null, 64, 129, 211),
20
),
new Area(
"Red Area",
new Area.MaterialWithChance[]{
new Area.MaterialWithChance(Material.ROSE_BUSH, 30),
new Area.MaterialWithChance(Material.POPPY, 30),
new Area.MaterialWithChance(Material.RED_MUSHROOM, 30),
new Area.MaterialWithChance(Material.CRIMSON_FUNGUS, 10)
},
new Location(null, 73, 129, 189),
new Location(null, 80, 131, 211),
30
),
new Area(
"Wheat & Beetroot Area",
new Area.MaterialWithChance[]{
new Area.MaterialWithChance(Material.WHEAT, 50),
new Area.MaterialWithChance(Material.BEETROOTS, 50)
},
new Location(null, 43, 129, 184),
new Location(null, 59, 129, 161),
35
),
new Area(
"Carrot & Potato Area",
new Area.MaterialWithChance[]{
new Area.MaterialWithChance(Material.CARROTS, 50),
new Area.MaterialWithChance(Material.POTATOES, 50)
},
new Location(null, 43, 129, 189),
new Location(null, 59, 129, 211),
35
)
};
public static void snowify(World world, Location pos1, Location pos2) {
int minX = Math.min(pos1.getBlockX(), pos2.getBlockX());
int maxX = Math.max(pos1.getBlockX(), pos2.getBlockX());
int minY = Math.min(pos1.getBlockY(), pos2.getBlockY());
int maxY = Math.max(pos1.getBlockY(), pos2.getBlockY());
int minZ = Math.min(pos1.getBlockZ(), pos2.getBlockZ());
int maxZ = Math.max(pos1.getBlockZ(), pos2.getBlockZ());
ThreadLocalRandom random = ThreadLocalRandom.current();
for (int x = minX; x <= maxX; x++) {
for (int z = minZ; z <= maxZ; z++) {
for (int y = maxY; y >= minY; y--) {
Block block = world.getBlockAt(x, y, z);
// Only care about full snow blocks as the base
if (block.getType() != Material.SNOW_BLOCK) continue;
Block above = world.getBlockAt(x, y + 1, z);
Material aboveType = above.getType();
if (aboveType == Material.AIR) {
if (random.nextInt(100) < 35) {
above.setType(Material.SNOW, false);
Snow snow = (Snow) above.getBlockData();
int layers = random.nextInt(1, 5); // 14
snow.setLayers(Math.min(layers, snow.getMaximumLayers()));
above.setBlockData(snow, false);
}
} else if (aboveType == Material.SNOW) {
if (random.nextInt(100) < 35) {
Snow snow = (Snow) above.getBlockData();
int add = random.nextInt(1, 5); // 14
int max = snow.getMaximumLayers();
snow.setLayers(Math.min(snow.getLayers() + add, max));
above.setBlockData(snow, false);
}
}
}
}
}
}
public static ComponentLogger LOGGER;
public static JavaPlugin PLUGIN;
@Override
public void onEnable() {
LOGGER = this.getComponentLogger();
PLUGIN = this;
BasicCommand regen = new RegenCommand();
BasicCommand snowify = new SnowifyCommand();
BasicCommand sell = new SellCommand();
BasicCommand viewitems = new ViewItemsCommand();
RegisteredServiceProvider<Economy> rsp =
getServer().getServicesManager().getRegistration(Economy.class);
if (rsp == null) {
getLogger().severe("No Vault economy provider found!");
getServer().getPluginManager().disablePlugin(this);
return;
}
if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) {
new SnowcoreExpansion(this).register();
}
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
econ = rsp.getProvider();
}
try {
registerCommand("regen", regen);
registerCommand("sell", sell);
registerCommand("snowify", snowify);
registerCommand("viewitems", viewitems);
getServer().getPluginManager().registerEvents(new BreakListener(), this);
getServer().getPluginManager().registerEvents(new PlaceListener(), this);
getServer().getPluginManager().registerEvents(new InteractListener(), this);
} catch(Exception e) {
LOGGER.error(Component.text("You are most likely restarting the plugin via /plm restart snowcore. Please don't do this, commands will not be registered."));
}
LOGGER.info(Component.text("SNOWCORE - good morning!", Style.style(TextColor.color(0, 255, 0))));
Random random = new Random();
new BukkitRunnable() {
@Override
public void run() {
World world = Bukkit.getWorld("world");
for(Area area : areas) {
if (area.getAirPercentage(world) > 65) {
area.regen(world);
final Component areaReplenish = MiniMessage.miniMessage().deserialize(
"<rainbow><bold>‧₊˚❀༉‧₊˚.</rainbow> <white>Seems like the <area> area has been replenished..</white> <rainbow><bold>‧₊˚❀༉‧₊˚.</rainbow>",
Placeholder.unparsed("area", area.name)
);
Bukkit.getOnlinePlayers().forEach(player -> {
player.sendActionBar(areaReplenish);
});
}
}
}
}.runTaskTimer(this, 0L, 10L); // 0L delay, 200L = 10s
new BukkitRunnable() {
@Override
public void run() {
if (random.nextInt(100) < 15) {
final Component snowing = MiniMessage.miniMessage().deserialize(
"<blue><bold>❄❄❄</blue> <white>It seems to be snowing..</white>"
);
World world = Bukkit.getWorld("world");
Snowcore.snowify(
world,
new Location(world, 39, 127, 157),
new Location(world, 8, 139, 215)
);
Bukkit.broadcast(snowing);
}
}
}.runTaskTimer(this, 0L, 100L); // 0L delay, 200L = 10s
}
public static int fortuneToDrops(int fortune, int drops) {
if (fortune <= 0) return drops;
// Clamp fortune to 1-20
fortune = Math.min(Math.max(fortune, 1), 20);
// Base chance multiplier
double multiplier = 1.0 + Math.pow(fortune, 1.3) / 10.0;
// Apply multiplier to original drops
int newDrops = (int) Math.round(drops * multiplier);
return Math.max(newDrops, drops); // never return less than original
}
public static int getLevelFromExp(int exp) {
// EXP = 5 * L^2 -> L = sqrt(exp / 5)
return (int) Math.floor(Math.sqrt(exp / 5.0));
}
public static void sendActionBar(Player player, Component message) {
Component component = MiniMessage.miniMessage().deserialize(
"<red><bold>❌</bold> "
).append(message);
player.sendActionBar(component);
}
public static void sellInventory(Player player) {
var inv = player.getInventory();
double finalPrice = 0d;
for (int slot = 0; slot < 35; slot++) {
ItemStack item = inv.getItem(slot);
if (item == null || item.getType().isAir()) continue;
boolean prices = Prices.hasSellPrice(item.getType());
if(prices) {
double price = Prices.getSellPrice(item.getType()) * item.getAmount();
finalPrice += price;
inv.setItem(slot, null);
}
}
Snowcore.econ.depositPlayer(player, finalPrice);
player.sendMessage(Component.text("Sold items for §a" + finalPrice + "§r!"));
}
@Override
public void onDisable() {
LOGGER.info(Component.text("SNOWCORE - good night.", Style.style(TextColor.color(255, 0, 0))));
}
}

View file

@ -0,0 +1,79 @@
package ovh.sad.snowcore;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import ovh.sad.snowcore.items.Hoe;
import ovh.sad.snowcore.items.ToolUtils;
import ovh.sad.snowcore.items.Shovel;
public class SnowcoreExpansion extends PlaceholderExpansion {
private final Snowcore plugin; //
public SnowcoreExpansion(Snowcore plugin) {
this.plugin = plugin;
}
@Override
@NotNull
public String getAuthor() {
return String.join(", ", plugin.getPluginMeta().getAuthors());
}
@Override
@NotNull
public String getIdentifier() {
return "snowcore";
}
@Override
@NotNull
public String getVersion() {
return plugin.getPluginMeta().getVersion();
}
@Override
public boolean persist() {
return true;
}
@Override
public String onRequest(OfflinePlayer oplayer, @NotNull String params) {
if (params.equalsIgnoreCase("tool_level")) {
Player player = oplayer.getPlayer();
if (player != null) {
ItemStack item = player.getInventory().getItemInMainHand();
Material type = item.getType();
if (Hoe.HoeDefinitions.ALL_HOES.containsKey(type)
|| Shovel.ShovelDefinitions.ALL_SHOVELS.containsKey(type)) {
int level = ToolUtils.getLevel(item);
// Clamp between 1 and 80
int clampedLevel = Math.max(1, Math.min(80, level));
// Interpolate hue (0° red 270° violet)
float hue = (clampedLevel - 1) / 79.0f * 270f;
java.awt.Color color = java.awt.Color.getHSBColor(hue / 360f, 1f, 1f);
String hex = String.format("#%02x%02x%02x",
color.getRed(),
color.getGreen(),
color.getBlue());
return net.md_5.bungee.api.ChatColor.of(hex) + "" + level;
} else {
return "0";
}
}
}
return null;
}
}

View file

@ -0,0 +1,25 @@
package ovh.sad.snowcore.commands;
import io.papermc.paper.command.brigadier.BasicCommand;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import ovh.sad.snowcore.Area;
import ovh.sad.snowcore.Snowcore;
import java.util.Objects;
@NullMarked
public class RegenCommand implements BasicCommand {
@Override
public void execute(CommandSourceStack source, String[] args) {
for(Area area: Snowcore.areas) {
area.regen(Objects.requireNonNull(source.getExecutor()).getWorld());
}
}
@Override
public @Nullable String permission() {
return "snowcore.regen_areas";
}
}

View file

@ -0,0 +1,123 @@
package ovh.sad.snowcore.commands;
import dev.triumphteam.gui.builder.item.ItemBuilder;
import dev.triumphteam.gui.guis.Gui;
import dev.triumphteam.gui.guis.GuiItem;
import io.papermc.paper.command.brigadier.BasicCommand;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import net.kyori.adventure.text.Component;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import ovh.sad.snowcore.Prices;
import ovh.sad.snowcore.Snowcore;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
@NullMarked
public class SellCommand implements BasicCommand {
@Override
public void execute(CommandSourceStack source, String[] args) {
AtomicBoolean sold = new AtomicBoolean(false);
Gui gui = Gui.gui()
.title(Component.text("Sell Items"))
.rows(6)
.create();
gui.setDefaultClickAction(event -> {
if (event.getSlot() >= 45) event.setCancelled(true); // only block bottom row
});
GuiItem pane = ItemBuilder.from(Material.BLACK_STAINED_GLASS_PANE)
.name(Component.text(" "))
.asGuiItem();
// Fill entire bottom row
for (int col = 1; col <= 9; col++) {
gui.setItem(6, col, pane);
}
GuiItem confirm = ItemBuilder.from(Material.GREEN_WOOL)
.name(Component.text("§aConfirm Sell"))
.asGuiItem(event -> {
event.setCancelled(true);
Player player = (Player) event.getWhoClicked();
sellItemsFromInv(sold, player, event.getInventory());
});
// Cancel (red wool)
GuiItem cancel = ItemBuilder.from(Material.RED_WOOL)
.name(Component.text("§cCancel"))
.asGuiItem(event -> {
Player player = (Player)event.getWhoClicked();
event.setCancelled(true);
player.closeInventory();
});
gui.setItem(6, 4, confirm);
gui.setItem(6, 6, cancel);
gui.setCloseGuiAction(event -> {
Player player = (Player) event.getPlayer();
UUID id = player.getUniqueId();
// If they sold, DON'T return items
if (!sold.get()) {
sellItemsFromInv(sold, player, event.getInventory());
}
});
gui.open((Player) Objects.requireNonNull(source.getExecutor()));
}
private void sellItemsFromInv(AtomicBoolean sold, Player player, Inventory inventory) {
sold.set(true); // mark as sold
var inv = inventory;
double finalPrice = 0d;
for (int slot = 0; slot < 45; slot++) {
ItemStack item = inv.getItem(slot);
if (item == null || item.getType().isAir()) continue;
boolean prices = Prices.hasSellPrice(item.getType());
if(!prices) {
player.getInventory().addItem(item);
inv.setItem(slot, null);
} else {
double price = Prices.getSellPrice(item.getType()) * item.getAmount();
finalPrice += price;
inv.setItem(slot, null);
}
}
Snowcore.econ.depositPlayer(player, finalPrice);
player.sendMessage(Component.text("Sold items for §a" + finalPrice + "§r!"));
player.closeInventory();
}
@Override
public @Nullable String permission() {
return "snowcore.sell";
}
}

View file

@ -0,0 +1,30 @@
package ovh.sad.snowcore.commands;
import io.papermc.paper.command.brigadier.BasicCommand;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import org.bukkit.Location;
import org.bukkit.World;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import ovh.sad.snowcore.Snowcore;
import java.util.Objects;
@NullMarked
public class SnowifyCommand implements BasicCommand {
@Override
public void execute(CommandSourceStack source, String[] args) {
World world = Objects.requireNonNull(source.getExecutor()).getWorld();
Snowcore.snowify(
world,
new Location(world, 39, 127, 157),
new Location(world, 8, 139, 215)
);
}
@Override
public @Nullable String permission() {
return "snowcore.snowify";
}
}

View file

@ -0,0 +1,39 @@
package ovh.sad.snowcore.commands;
import dev.triumphteam.gui.guis.Gui;
import dev.triumphteam.gui.guis.GuiItem;
import io.papermc.paper.command.brigadier.BasicCommand;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.Nullable;
import ovh.sad.snowcore.items.Hoe;
import ovh.sad.snowcore.items.Shovel;
import java.util.Objects;
public class ViewItemsCommand implements BasicCommand {
@Override
public void execute(@NotNull CommandSourceStack source, String @NotNull [] args) {
Gui gui = Gui.gui()
.title(Component.text("All obtainable items"))
.rows(6)
.create();
Hoe.HoeDefinitions.ALL_HOES.forEach((material, hoe) -> {
gui.addItem(new GuiItem(hoe.renderItem(null)));
});
Shovel.ShovelDefinitions.ALL_SHOVELS.forEach((material, shovel) -> {
gui.addItem(new GuiItem(shovel.renderItem(null)));
});
gui.open((Player) Objects.requireNonNull(source.getExecutor()));
}
@Override
public @Nullable String permission() {
return "snowcore.view_items";
}
}

View file

@ -0,0 +1,200 @@
package ovh.sad.snowcore.guis;
import dev.triumphteam.gui.builder.item.ItemBuilder;
import dev.triumphteam.gui.guis.Gui;
import dev.triumphteam.gui.guis.GuiItem;
import dev.triumphteam.gui.guis.PaginatedGui;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.inventory.ItemStack;
import ovh.sad.snowcore.Prices;
import ovh.sad.snowcore.Snowcore;
import ovh.sad.snowcore.items.Hoe;
import ovh.sad.snowcore.items.Shovel;
import ovh.sad.snowcore.items.Tool;
import ovh.sad.snowcore.items.ToolUtils;
import java.util.concurrent.atomic.AtomicBoolean;
public class UpgradeYourToolsGui {
public static void open(PlayerInteractEntityEvent event) {
openHoes(event.getPlayer());
}
// =========================================================
// HOES
// =========================================================
private static void openHoes(Player player) {
PaginatedGui gui = Gui.paginated()
.title(Component.text("Upgrade Your Tools - Hoes"))
.rows(3)
.pageSize(7) // only columns 2-8
.create();
gui.setDefaultClickAction(e -> e.setCancelled(true));
fillLayout(gui);
// Add ordered hoes
for (Hoe hoe : Hoe.HoeDefinitions.ORDERED) {
gui.addItem(createToolGuiItem(hoe, player, gui));
}
// Navigation (bottom right)
gui.setItem(3, 9,
ItemBuilder.from(Material.ARROW)
.name(Component.text("Next → Shovels", NamedTextColor.YELLOW))
.asGuiItem(e -> {
e.setCancelled(true);
openShovels(player);
})
);
gui.open(player);
}
// =========================================================
// SHOVELS
// =========================================================
private static void openShovels(Player player) {
PaginatedGui gui = Gui.paginated()
.title(Component.text("Upgrade Your Tools - Shovels"))
.rows(3)
.pageSize(7)
.create();
gui.setDefaultClickAction(e -> e.setCancelled(true));
fillLayout(gui);
for (Shovel shovel : Shovel.ShovelDefinitions.ORDERED) {
gui.addItem(createToolGuiItem(shovel, player, gui));
}
gui.setItem(3, 9,
ItemBuilder.from(Material.ARROW)
.name(Component.text("← Previous Hoes", NamedTextColor.YELLOW))
.asGuiItem(e -> {
e.setCancelled(true);
openHoes(player);
})
);
gui.open(player);
}
private static <T extends Tool<?>> T getPreviousTool(T tool) {
int index;
if (tool instanceof Shovel) {
index = Shovel.ShovelDefinitions.ORDERED.indexOf(tool);
if (index <= 0) return null;
return (T) Shovel.ShovelDefinitions.ORDERED.get(index - 1);
} else if (tool instanceof Hoe) {
index = Hoe.HoeDefinitions.ORDERED.indexOf(tool);
if (index <= 0) return null;
return (T) Hoe.HoeDefinitions.ORDERED.get(index - 1);
}
return null;
}
private static <T extends Tool<?>> GuiItem createToolGuiItem(T tool, Player player, PaginatedGui gui) {
return new GuiItem(
tool.renderItem(null),
e -> {
e.setCancelled(true);
if (tool.material == Material.WOODEN_SHOVEL || tool.material == Material.WOODEN_HOE) {
// First tool is free
player.sendMessage(
MiniMessage.miniMessage().deserialize(
"<green>✔ You've been given a <name>! Enjoy Snowing.",
Placeholder.component("name", tool.name())
)
);
player.getInventory().addItem(tool.renderItem(null));
gui.close(player);
return;
}
// Get previous tool
Tool<?> previousTool = getPreviousTool(tool);
if (previousTool == null) return; // safety
// Check requirement
boolean hasPrevious = false;
for (ItemStack item : player.getInventory()) {
if (item != null && item.getType() == previousTool.material) {
hasPrevious = true;
break;
}
}
if (!hasPrevious) {
player.sendMessage(MiniMessage.miniMessage().deserialize(
"<red>❌ Missing a requirement:</red> <previousname>",
Placeholder.component("previousname", previousTool.name())
));
return;
}
// Check economy
double balance = Snowcore.econ.getBalance(player);
Integer cost = Prices.getToolBuyPrice(tool);
if (balance >= cost) {
Snowcore.econ.withdrawPlayer(player, cost);
player.getInventory().remove(previousTool.material);
player.getInventory().addItem(tool.renderItem(null));
player.sendMessage(
MiniMessage.miniMessage().deserialize(
"<green>✔ Unlocked <yellow><name></yellow> for <gold>$<cost></gold> and <previousname>",
Placeholder.component("previousname", previousTool.name()),
Placeholder.component("name", tool.name()),
Placeholder.unparsed("cost", cost.toString())
)
);
gui.close(player);
} else {
player.sendMessage(
MiniMessage.miniMessage().deserialize(
"<red>✘ Not enough money! Need <gold>$<cost></gold>, you have <yellow>$<balance></yellow>",
Placeholder.unparsed("balance", String.valueOf(balance)),
Placeholder.unparsed("cost", cost.toString())
)
);
}
}
);
}
private static void fillLayout(PaginatedGui gui) {
GuiItem pane = ItemBuilder.from(Material.GRAY_STAINED_GLASS_PANE)
.name(Component.empty())
.asGuiItem(e -> e.setCancelled(true));
for (int col = 1; col <= 9; col++) {
gui.setItem(1, col, pane);
}
// Row 2 (side panes only)
gui.setItem(2, 1, pane);
gui.setItem(2, 9, pane);
// Row 3 (all panes)
for (int col = 1; col <= 9; col++) {
gui.setItem(3, col, pane);
}
}
}

View file

@ -0,0 +1,140 @@
package ovh.sad.snowcore.items;
import org.bukkit.Material;
import org.jspecify.annotations.Nullable;
import java.util.*;
public class Hoe extends Tool<Hoe> {
private final List<Material> canBreak = new ArrayList<>();
private final List<Hoe> dependsOn = new ArrayList<>();
public Hoe(Material material, String name) {
super(material, name);
}
public Hoe addCanBreaks(Collection<Material> m) {
canBreak.addAll(m);
return this;
}
public Hoe addDepend(Hoe h) {
dependsOn.add(h);
return this;
}
public static class HoeDefinitions {
public static final Hoe WOODEN_HOE = new Hoe(Material.WOODEN_HOE, "<#DCB190><b>Wooden Hoe</b></#DCB190>")
.addEnchantment(ToolUtils.EnchantmentType.WIND, 5)
.addCanBreaks(List.of( Material.SPRUCE_SAPLING, Material.WILDFLOWERS ));
public static final Hoe STONE_HOE = new Hoe(Material.STONE_HOE, "<gray><b>Stone Hoe</b><gray>")
.addEnchantment(ToolUtils.EnchantmentType.WIND, 15)
.addEnchantment(ToolUtils.EnchantmentType.DOUBLE_DROP, 5)
.addCanBreaks(List.of( Material.PINK_PETALS, Material.ALLIUM, Material.CHERRY_SAPLING ))
.addDepend(WOODEN_HOE);
public static final Hoe COPPER_HOE = new Hoe(Material.COPPER_HOE, "<gold><b>Copper Hoe</b><gold>")
.addEnchantment(ToolUtils.EnchantmentType.WIND, 25)
.addEnchantment(ToolUtils.EnchantmentType.DOUBLE_DROP, 15)
.addEnchantment(ToolUtils.EnchantmentType.FORTUNE, 1)
.limit(ToolUtils.EnchantmentType.FORTUNE, 5)
.limit(ToolUtils.EnchantmentType.WIND, 40)
.addCanBreaks(List.of(
Material.OXEYE_DAISY, Material.LILY_OF_THE_VALLEY, Material.CLOSED_EYEBLOSSOM,
Material.CARROTS, Material.POTATOES
))
.addDepend(STONE_HOE);
public static final Hoe IRON_HOE = new Hoe(Material.IRON_HOE, "<white><b>Iron Hoe</b><white>")
.addEnchantment(ToolUtils.EnchantmentType.WIND, 30)
.addEnchantment(ToolUtils.EnchantmentType.DOUBLE_DROP, 20)
.addEnchantment(ToolUtils.EnchantmentType.FORTUNE, 2)
.limit(ToolUtils.EnchantmentType.FORTUNE, 10)
.limit(ToolUtils.EnchantmentType.WIND, 50)
.addAbility(ToolUtils.AbilityType.JACKPOT, 3, 0, 0, true)
.addAbility(ToolUtils.AbilityType.MULTIPLIER, 1.5, 5, 50_000, false)
.addCanBreaks(
List.of(
Material.WHEAT,
Material.BEETROOTS,
Material.DEAD_FIRE_CORAL, Material.DEAD_TUBE_CORAL, Material.DEAD_HORN_CORAL, Material.DEAD_BRAIN_CORAL_FAN, Material.OPEN_EYEBLOSSOM
))
.addDepend(COPPER_HOE);
public static final Hoe DIAMOND_HOE = new Hoe(Material.DIAMOND_HOE, "<aqua><b>Diamond Hoe</b><aqua>")
.addEnchantment(ToolUtils.EnchantmentType.WIND, 30)
.addEnchantment(ToolUtils.EnchantmentType.DOUBLE_DROP, 20)
.addEnchantment(ToolUtils.EnchantmentType.FORTUNE, 3)
.limit(ToolUtils.EnchantmentType.FORTUNE, 20)
.limit(ToolUtils.EnchantmentType.WIND, 60)
.addAbility(ToolUtils.AbilityType.MULTIPLIER, 2.5, 5, 50_000, false)
.addAbility(ToolUtils.AbilityType.JACKPOT, 5, 0, 0, true)
.addAbility(ToolUtils.AbilityType.THREE_BY_THREE, 0, 0, 0, true)
.addAbility(ToolUtils.AbilityType.AUTOSELL, 1, 15, 100_000, false)
.addCanBreaks( List.of( Material.ROSE_BUSH, Material.POPPY, Material.RED_MUSHROOM, Material.CRIMSON_FUNGUS ) )
.addDepend(IRON_HOE);
public static final Hoe NETHERITE_HOE = new Hoe(Material.NETHERITE_HOE, "<dark_purple><b>Netherite Hoe</b><dark_purple>")
.addEnchantment(ToolUtils.EnchantmentType.WIND, 40)
.addEnchantment(ToolUtils.EnchantmentType.DOUBLE_DROP, 30)
.addEnchantment(ToolUtils.EnchantmentType.FORTUNE, 4)
.limit(ToolUtils.EnchantmentType.FORTUNE, 30)
.limit(ToolUtils.EnchantmentType.WIND, 70)
.limit(ToolUtils.EnchantmentType.DOUBLE_DROP, 60)
.addAbility(ToolUtils.AbilityType.MULTIPLIER, 5, 5, 75_000, false)
.addAbility(ToolUtils.AbilityType.AUTOSELL, 1, 25, 150_000, false)
.addAbility(ToolUtils.AbilityType.THREE_BY_THREE, 0, 0, 0, true)
.addAbility(ToolUtils.AbilityType.JACKPOT, 15, 0, 15_000, true)
.addCanBreaks( List.of( Material.CACTUS_FLOWER, Material.PEONY, Material.PINK_TULIP, Material.CACTUS ) )
.addDepend(DIAMOND_HOE);
public static final HashMap<Material, Hoe> ALL_HOES = new HashMap<>(
Map.of(WOODEN_HOE.material, WOODEN_HOE,
STONE_HOE.material, STONE_HOE,
COPPER_HOE.material, COPPER_HOE,
IRON_HOE.material, IRON_HOE,
DIAMOND_HOE.material, DIAMOND_HOE,
NETHERITE_HOE.material, NETHERITE_HOE
)
);
public static final List<Hoe> ORDERED = List.of(
WOODEN_HOE,
STONE_HOE,
COPPER_HOE,
IRON_HOE,
DIAMOND_HOE,
NETHERITE_HOE
);
public static @Nullable Hoe getRequiredHoe(Material material) {
for (Hoe hoe : ORDERED) {
if (getAllBlocks(hoe).contains(material)) {
return hoe;
}
}
return null;
}
public static List<Material> getAllBlocks(Hoe hoe) {
Set<Material> result = new HashSet<>();
Set<Hoe> visited = new HashSet<>();
collectBlocks(hoe, result, visited);
return new ArrayList<>(result);
}
private static void collectBlocks(Hoe hoe, Set<Material> result, Set<Hoe> visited) {
if (!visited.add(hoe)) return; // already processed
result.addAll(hoe.canBreak);
for (Hoe parent : hoe.dependsOn) {
collectBlocks(parent, result, visited);
}
}
}
}

View file

@ -0,0 +1,85 @@
package ovh.sad.snowcore.items;
import org.bukkit.Material;
import java.util.*;
public class Shovel extends Tool<Shovel> {
public Shovel(Material material, String name) {
super(material, name);
}
public static class ShovelDefinitions {
public static final Shovel WOODEN_SHOVEL = new Shovel(Material.WOODEN_SHOVEL, "<#90bbdc><b>Wooden Shovel</b></#90bbdc>")
.addEnchantment(ToolUtils.EnchantmentType.SWEEPER, 10)
.limit(ToolUtils.EnchantmentType.SWEEPER, 15);
public static final Shovel STONE_SHOVEL = new Shovel(Material.STONE_SHOVEL, "<gray><b>Stone Shovel</b><gray>")
.addEnchantment(ToolUtils.EnchantmentType.SWEEPER, 20)
.addEnchantment(ToolUtils.EnchantmentType.LAYER_REMOVER, 10)
.limit(ToolUtils.EnchantmentType.SWEEPER, 25)
.limit(ToolUtils.EnchantmentType.LAYER_REMOVER, 12);
public static final Shovel COPPER_SHOVEL = new Shovel(Material.COPPER_SHOVEL, "<#6699ff><b>Copper Shovel</b><#6699ff>")
.addEnchantment(ToolUtils.EnchantmentType.SWEEPER, 30)
.addEnchantment(ToolUtils.EnchantmentType.LAYER_REMOVER, 15)
.addEnchantment(ToolUtils.EnchantmentType.FORTUNE, 1)
.limit(ToolUtils.EnchantmentType.FORTUNE, 5)
.limit(ToolUtils.EnchantmentType.SWEEPER, 40)
.limit(ToolUtils.EnchantmentType.LAYER_REMOVER, 20);
public static final Shovel IRON_SHOVEL = new Shovel(Material.IRON_SHOVEL, "<white><b>Iron Shovel</b><white>")
.addEnchantment(ToolUtils.EnchantmentType.SWEEPER, 40)
.addEnchantment(ToolUtils.EnchantmentType.LAYER_REMOVER, 25)
.addEnchantment(ToolUtils.EnchantmentType.FORTUNE, 3)
.limit(ToolUtils.EnchantmentType.FORTUNE, 10)
.limit(ToolUtils.EnchantmentType.SWEEPER, 50)
.limit(ToolUtils.EnchantmentType.LAYER_REMOVER, 30)
.addAbility(ToolUtils.AbilityType.NUKE, 0, 0, 0, true)
.addAbility(ToolUtils.AbilityType.MULTIPLIER, 2, 5, 40_000, false)
.addAbility(ToolUtils.AbilityType.AUTOSELL, 1, 20, 15_000, false);
public static final Shovel DIAMOND_SHOVEL = new Shovel(Material.DIAMOND_SHOVEL, "<#ff5555><b>Diamond Shovel</b><#ff5555>")
.addEnchantment(ToolUtils.EnchantmentType.SWEEPER, 50)
.addEnchantment(ToolUtils.EnchantmentType.LAYER_REMOVER, 35)
.addEnchantment(ToolUtils.EnchantmentType.FORTUNE, 4)
.limit(ToolUtils.EnchantmentType.FORTUNE, 10)
.limit(ToolUtils.EnchantmentType.SWEEPER, 60)
.limit(ToolUtils.EnchantmentType.LAYER_REMOVER, 40)
.addAbility(ToolUtils.AbilityType.NUKE, 0, 0, 0, true)
.addAbility(ToolUtils.AbilityType.WIFI, 2, 0, 0, true)
.addAbility(ToolUtils.AbilityType.MULTIPLIER, 3, 5, 50_000, false)
.addAbility(ToolUtils.AbilityType.AUTOSELL, 1, 25, 40_000, false);
public static final Shovel NETHERITE_SHOVEL = new Shovel(Material.NETHERITE_SHOVEL, "<#4dc44d><b>Netherite Shovel</b><#4dc44d>")
.addEnchantment(ToolUtils.EnchantmentType.SWEEPER, 50)
.addEnchantment(ToolUtils.EnchantmentType.LAYER_REMOVER, 40)
.addEnchantment(ToolUtils.EnchantmentType.FORTUNE, 6)
.limit(ToolUtils.EnchantmentType.FORTUNE, 30)
.limit(ToolUtils.EnchantmentType.SWEEPER, 100)
.limit(ToolUtils.EnchantmentType.LAYER_REMOVER, 100)
.addAbility(ToolUtils.AbilityType.MULTIPLIER, 5, 5, 75_000, false)
.addAbility(ToolUtils.AbilityType.AUTOSELL, 1, 30, 60_000, false)
.addAbility(ToolUtils.AbilityType.NUKE, 0, 0, 0, true)
.addAbility(ToolUtils.AbilityType.WIFI, 6, 0, 0 , true);
public static final HashMap<Material, Shovel> ALL_SHOVELS = new HashMap<>(
Map.of(WOODEN_SHOVEL.material, WOODEN_SHOVEL,
STONE_SHOVEL.material, STONE_SHOVEL,
COPPER_SHOVEL.material, COPPER_SHOVEL,
IRON_SHOVEL.material, IRON_SHOVEL,
DIAMOND_SHOVEL.material, DIAMOND_SHOVEL,
NETHERITE_SHOVEL.material, NETHERITE_SHOVEL
)
);
public static final List<Shovel> ORDERED = List.of(
WOODEN_SHOVEL,
STONE_SHOVEL,
COPPER_SHOVEL,
IRON_SHOVEL,
DIAMOND_SHOVEL,
NETHERITE_SHOVEL
);
}
}

View file

@ -0,0 +1,184 @@
package ovh.sad.snowcore.items;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.Material;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.jspecify.annotations.Nullable;
import java.util.*;
public abstract class Tool<T extends Tool<T>> {
public final Material material;
protected final String name;
public final List<ToolUtils.Enchantment> definedEnchantments = new ArrayList<>();
public final Map<ToolUtils.EnchantmentType, Integer> definedEnchantLimits = new HashMap<>();
public final List<ToolUtils.Ability> definedAbilities = new ArrayList<>();
protected Tool(Material material, String name) {
this.material = material;
this.name = name;
}
public ItemStack renderItem(@Nullable ItemStack itemToRender) {
if(itemToRender == null) {
ItemStack item = new ItemStack(material);
ItemMeta meta = item.getItemMeta();
if(meta == null) throw new Error("RenderItem errored, meta could not be found!");
Component name = MiniMessage.miniMessage().deserialize(
"<shadow:black><gray>[</gray><green>✩ 1<gray>, <blue>❁ 1</blue>]</gray></shadow> " + this.name
);
meta.setUnbreakable(true);
meta.addItemFlags(ItemFlag.HIDE_UNBREAKABLE);
meta.displayName(name);
List<Component> lore = new ArrayList<>();
List<ToolUtils.Ability> abilities = definedAbilities.stream().filter(a -> a.byDefault).toList();
lore.add(MiniMessage.miniMessage().deserialize(
"<gray><i>(Shift rightclick to upgrade!)"
));
lore.add(Component.text(""));
definedEnchantments.forEach(z -> {
if(z.type.usesLevel) {
lore.add(MiniMessage.miniMessage().deserialize(
"<yellow> • " + z.type.name + " " + z.amount
));
} else {
lore.add(MiniMessage.miniMessage().deserialize(
"<white> • " + z.type.name + " (" + z.amount + "%)"
));
}
});
abilities.forEach(z -> {
lore.add(MiniMessage.miniMessage().deserialize(
"<aqua> ⭐ <b>" + z.type.name + "</b>, " + z.type.description.replaceAll("%maxValue%", String.valueOf(z.maxValue))
));
});
meta.lore(lore);
PersistentDataContainer pdc = meta.getPersistentDataContainer();
pdc.set(ToolUtils.EXPERIENCE, PersistentDataType.INTEGER, 1);
pdc.set(ToolUtils.TOTAL_EXPERIENCE, PersistentDataType.INTEGER, 1);
pdc.set(ToolUtils.ENCHANTMENTS,
PersistentDataType.LIST.listTypeFrom(ToolUtils.Enchantment.EMPTY_INSTANCE),
definedEnchantments);
pdc.set(ToolUtils.ABILITIES,
PersistentDataType.LIST.listTypeFrom(ToolUtils.Ability.EMPTY_INSTANCE),
definedAbilities.stream().filter(a -> a.byDefault).toList());
meta.setEnchantmentGlintOverride(true);
item.setItemMeta(meta);
return item;
} else {
ItemStack item = itemToRender.clone();
ItemMeta meta = item.getItemMeta();
if(meta == null) throw new Error("RenderItem errored, meta could not be found!");
Integer exp = ToolUtils.getExp(item);
Integer level = ToolUtils.getLevel(item);
Component name = MiniMessage.miniMessage().deserialize(
"<shadow:black><gray>[</gray><green>✩ " + exp + "<gray>, <blue>❁ " + level + "</blue>]</gray></shadow> " + this.name
);
meta.displayName(name);
List<Component> lore = new ArrayList<>();
List<ToolUtils.Ability> abilities = ToolUtils.getAbilities(item);
lore.add(MiniMessage.miniMessage().deserialize(
"<gray><i>(Shift rightclick to upgrade!)"
));
lore.add(Component.text(""));
ToolUtils.getEnchantments(item).forEach(z -> {
if(z.type.usesLevel) {
lore.add(MiniMessage.miniMessage().deserialize(
"<yellow> • " + z.type.name + " " + z.amount
));
} else {
lore.add(MiniMessage.miniMessage().deserialize(
"<white> • " + z.type.name + " (" + z.amount + "%)"
));
}
});
abilities.forEach(z -> {
lore.add(MiniMessage.miniMessage().deserialize(
"<aqua> ⭐ <b>" + z.type.name + "</b>, " + z.type.description.replaceAll("%maxValue%", String.valueOf(z.maxValue))
));
});
meta.lore(lore);
meta.setEnchantmentGlintOverride(true);
item.setItemMeta(meta);
return item;
}
};
public ItemStack increaseEXP(ItemStack item, int amount) {
ItemMeta meta = item.getItemMeta();
if (meta == null) return item;
PersistentDataContainer pdc = meta.getPersistentDataContainer();
int exp = pdc.get(ToolUtils.EXPERIENCE, PersistentDataType.INTEGER) + amount;
int total = pdc.get(ToolUtils.TOTAL_EXPERIENCE, PersistentDataType.INTEGER) + amount;
pdc.set(ToolUtils.EXPERIENCE, PersistentDataType.INTEGER, exp);
pdc.set(ToolUtils.TOTAL_EXPERIENCE, PersistentDataType.INTEGER, total);
item.setItemMeta(meta);
return this.renderItem(item);
}
public Component name() {
return MiniMessage.miniMessage().deserialize(name);
}
/* ------------------------------------------------ */
/* Builder API (shared) */
/* ------------------------------------------------ */
@SuppressWarnings("unchecked")
public T addEnchantment(ToolUtils.EnchantmentType type, int amount) {
definedEnchantments.add(new ToolUtils.Enchantment(type, amount));
return (T) this;
}
@SuppressWarnings("unchecked")
public T limit(ToolUtils.EnchantmentType type, int max) {
definedEnchantLimits.put(type, max);
return (T) this;
}
@SuppressWarnings("unchecked")
public T addAbility(ToolUtils.AbilityType type, double max, int unlock, double cost, boolean byDefault) {
definedAbilities.add(new ToolUtils.Ability(type, max, unlock, cost, byDefault));
return (T) this;
}
}

View file

@ -0,0 +1,267 @@
package ovh.sad.snowcore.items;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataAdapterContext;
import org.bukkit.persistence.PersistentDataContainer;
import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
import org.jspecify.annotations.Nullable;
import ovh.sad.snowcore.Snowcore;
import java.nio.ByteBuffer;
import java.util.List;
public class ToolUtils {
static final NamespacedKey ABILITIES = new NamespacedKey(Snowcore.PLUGIN, "snowcore_abilities");
static final NamespacedKey ENCHANTMENTS = new NamespacedKey(Snowcore.PLUGIN, "snowcore_enchants");
static final NamespacedKey EXPERIENCE = new NamespacedKey(Snowcore.PLUGIN, "snowcore_exp");
static final NamespacedKey TOTAL_EXPERIENCE = new NamespacedKey(Snowcore.PLUGIN, "snowcore_exp_total");
public enum EnchantmentType {
FORTUNE("Fortune", 200, true),
WIND("Wind", 120, false),
DOUBLE_DROP("Double Drop", 350, false),
NONE("418 Teapot", 0, false),
SWEEPER("Sweeper", 100, false),
LAYER_REMOVER("Layer Remover", 150, false);
public final String name;
public final int baseExpCost;
public final boolean usesLevel; // false = chance %
EnchantmentType(String name, int baseExpCost, boolean usesLevel) {
this.baseExpCost = baseExpCost;
this.usesLevel = usesLevel;
this.name = name;
}
}
public static class Enchantment implements PersistentDataType<byte[], Enchantment> {
public static final Enchantment EMPTY_INSTANCE = new Enchantment(EnchantmentType.NONE, 0);
public final EnchantmentType type;
public int amount;
public Enchantment(EnchantmentType type, int amount) {
this.type = type;
this.amount = amount;
}
public int getExpRequired(int level) {
if(type.usesLevel) {
return type.baseExpCost * level * level;
} else {
double l = level / 10.0;
return (int)Math.ceil(type.baseExpCost * Math.pow(l, 1.7));
}
}
public Enchantment copy() {
return new Enchantment(type, amount);
}
@Override
public @NotNull Class<byte[]> getPrimitiveType() {
return byte[].class;
}
@Override
public @NotNull Class<Enchantment> getComplexType() {
return Enchantment.class;
}
@Override
public byte @NotNull [] toPrimitive(Enchantment complex, @NotNull PersistentDataAdapterContext context) {
ByteBuffer bb = ByteBuffer.allocate(
Integer.BYTES + // enum ordinal
Integer.BYTES // amount
);
bb.putInt(complex.type.ordinal());
bb.putInt(complex.amount);
return bb.array();
}
@Override
public @NotNull Enchantment fromPrimitive(byte @NotNull [] primitive, @NotNull PersistentDataAdapterContext context) {
ByteBuffer bb = ByteBuffer.wrap(primitive);
EnchantmentType type = EnchantmentType.values()[bb.getInt()];
int amount = bb.getInt();
return new Enchantment(type, amount);
}
}
public enum AbilityType {
// Hoe only
JACKPOT("Jackpot", "you have a %maxValue%% chance to drop 5x items!"),
THREE_BY_THREE("3x3", "you mine blocks in a 3x3 grid!"),
// Shovel only
NUKE("Nuke", "you have a 10% chance to remove 108 layers of snow in a 6x6 area!"),
WIFI("Free wifi anywhere you go", "you have a %maxValue%% chance of getting a inventory full of snowballs!"),
// Mutual
MULTIPLIER("Multiplier", "your drops are multiplied by %maxValue%x!"),
AUTOSELL("Auto-sell", "you automatically sell all of your items when your inventory is full"),
NONE("HTTP", "418 Teapot");
public final String name;
public final String description;
AbilityType(String name, String description) {
this.name = name;
this.description = description;
}
}
public static class Ability implements PersistentDataType<byte[], Ability> {
public static final Ability EMPTY_INSTANCE = new Ability(AbilityType.NONE, 0, 0, 0, false);
public AbilityType type;
public double maxValue;
public int unlockLevel;
public double baseCost;
public boolean byDefault;
public Ability(AbilityType type, double maxValue, int unlockLevel, double baseCost, boolean byDefault) {
this.type = type;
this.maxValue = maxValue;
this.unlockLevel = unlockLevel;
this.baseCost = baseCost;
this.byDefault = byDefault;
}
@Override
public @NotNull Class<byte[]> getPrimitiveType() {
return byte[].class;
}
@Override
public @NotNull Class<Ability> getComplexType() {
return Ability.class;
}
@Override
public byte @NotNull [] toPrimitive(Ability complex, @NotNull PersistentDataAdapterContext context) {
ByteBuffer bb = ByteBuffer.allocate(
Integer.BYTES + // enum ordinal
Double.BYTES + // maxValue
Integer.BYTES + // unlockLevel
Double.BYTES + // baseCost
Short.BYTES // byDefault
);
bb.putInt(complex.type.ordinal());
bb.putDouble(complex.maxValue);
bb.putInt(complex.unlockLevel);
bb.putDouble(complex.baseCost);
if(complex.byDefault) {
bb.putShort((short)1);
} else {
bb.putShort((short)0);
}
return bb.array();
}
@Override
public @NotNull Ability fromPrimitive(byte @NotNull [] primitive, @NotNull PersistentDataAdapterContext context) {
ByteBuffer bb = ByteBuffer.wrap(primitive);
AbilityType type = AbilityType.values()[bb.getInt()];
double maxValue = bb.getDouble();
int unlockLevel = bb.getInt();
double baseCost = bb.getDouble();
boolean byDefault = bb.getShort() == 1;
return new Ability(type, maxValue, unlockLevel, baseCost, byDefault);
}
}
public static @Nullable Integer getLevel(@NotNull ItemStack item) {
ItemMeta meta = item.getItemMeta();
if (meta == null) return null;
PersistentDataContainer pdc = meta.getPersistentDataContainer();
return Snowcore.getLevelFromExp(pdc.get(ToolUtils.TOTAL_EXPERIENCE, PersistentDataType.INTEGER));
}
public static @Nullable Integer getExp(@NotNull ItemStack item) {
ItemMeta meta = item.getItemMeta();
if (meta == null) return null;
PersistentDataContainer pdc = meta.getPersistentDataContainer();
return pdc.get(ToolUtils.EXPERIENCE, PersistentDataType.INTEGER);
}
public static @Nullable ItemStack setExp(Tool<?> tool, @NotNull ItemStack item, Integer exp) {
ItemMeta meta = item.getItemMeta();
if (meta == null) return null;
PersistentDataContainer pdc = meta.getPersistentDataContainer();
pdc.set(ToolUtils.EXPERIENCE, PersistentDataType.INTEGER, exp);
item.setItemMeta(meta);
return tool.renderItem(item);
}
public static @Nullable List<Enchantment> getEnchantments(@NotNull ItemStack item) {
ItemMeta meta = item.getItemMeta();
if (meta == null) return null;
PersistentDataContainer pdc = meta.getPersistentDataContainer();
return pdc.get(ToolUtils.ENCHANTMENTS, PersistentDataType.LIST.listTypeFrom(ToolUtils.Enchantment.EMPTY_INSTANCE));
}
public static @NotNull ItemStack setEnchantments(Tool<?> tool, @NotNull ItemStack item, @NotNull List<ToolUtils.Enchantment> enchantments) {
ItemMeta meta = item.getItemMeta();
if (meta == null) return item;
PersistentDataContainer pdc = meta.getPersistentDataContainer();
pdc.set(ToolUtils.ENCHANTMENTS,
PersistentDataType.LIST.listTypeFrom(ToolUtils.Enchantment.EMPTY_INSTANCE),
enchantments);
item.setItemMeta(meta);
return tool.renderItem(item);
}
public static @Nullable List<ToolUtils.Ability> getAbilities(@NotNull ItemStack item) {
ItemMeta meta = item.getItemMeta();
if (meta == null) return null;
PersistentDataContainer pdc = meta.getPersistentDataContainer();
return pdc.get(ToolUtils.ABILITIES, PersistentDataType.LIST.listTypeFrom(ToolUtils.Ability.EMPTY_INSTANCE));
}
public static @NotNull ItemStack setAbilities(Tool<?> tool, @NotNull ItemStack item, @NotNull List<ToolUtils.Ability> abilities) {
ItemMeta meta = item.getItemMeta();
if (meta == null) return item;
PersistentDataContainer pdc = meta.getPersistentDataContainer();
pdc.set(ToolUtils.ABILITIES,
PersistentDataType.LIST.listTypeFrom(ToolUtils.Ability.EMPTY_INSTANCE),
abilities);
item.setItemMeta(meta);
return tool.renderItem(item);
}
}

View file

@ -0,0 +1,400 @@
package ovh.sad.snowcore.listeners;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.title.Title;
import org.apache.commons.lang3.tuple.Pair;
import org.bukkit.*;
import org.bukkit.block.Block;
import org.bukkit.block.data.type.Snow;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.inventory.ItemStack;
import java.util.*;
import org.bukkit.inventory.PlayerInventory;
import ovh.sad.snowcore.items.Hoe;
import ovh.sad.snowcore.items.ToolUtils;
import ovh.sad.snowcore.Snowcore;
import ovh.sad.snowcore.items.Shovel;
public class BreakListener implements Listener {
@EventHandler
public void onBlockBreak(BlockBreakEvent event) {
Player player = event.getPlayer();
// Creative mode can break anything
if (player.getGameMode() == GameMode.CREATIVE) return;
PlayerInventory inv = player.getInventory();
ItemStack hand = inv.getItemInMainHand();
Material tool = hand.getType();
Material blockType = event.getBlock().getType();
Hoe hoe = Hoe.HoeDefinitions.ALL_HOES.get(tool);
// If block has a required hoe and player doesn't meet it
if (hoe != null && !Hoe.HoeDefinitions.getAllBlocks(hoe).contains(blockType)) {
event.setCancelled(true);
Snowcore.sendActionBar(player,
Component.text("You need at least a ")
.append(Objects.requireNonNull(Hoe.HoeDefinitions.getRequiredHoe(blockType)).name())
.append(Component.text(" to break this block!"))
);
return;
}
if(hoe != null) {
event.setDropItems(false);
List<ToolUtils.Ability> abilities = ToolUtils.getAbilities(hand);
List<ToolUtils.Enchantment> enchants = ToolUtils.getEnchantments(hand);
int multiplier = 1;
Optional<ToolUtils.Ability> hasMultiplier
= abilities.stream().filter(z -> z.type == ToolUtils.AbilityType.MULTIPLIER).findFirst();
Optional<ToolUtils.Ability> hasJackpot
= abilities.stream().filter(z -> z.type == ToolUtils.AbilityType.JACKPOT).findFirst();
Optional<ToolUtils.Enchantment> windEnchantment
= enchants.stream().filter(z -> z.type == ToolUtils.EnchantmentType.WIND).findFirst();
Optional<ToolUtils.Enchantment> doubleDropEnchantment
= enchants.stream().filter(z -> z.type == ToolUtils.EnchantmentType.DOUBLE_DROP).findFirst();
if(hasMultiplier.isPresent()) {
multiplier = (int)Math.ceil(hasMultiplier.get().maxValue);
}
boolean jackpotHit = false;
double random = Math.random(); // 0.0 - 1.0
if (hasJackpot.isPresent()) {
if (random <= (hasJackpot.get().maxValue / 100)) { // 10% chance
multiplier *= 5;
jackpotHit = true;
}
}
if (jackpotHit) {
player.sendActionBar(MiniMessage.miniMessage().deserialize("<yellow> ࿏࿏࿏࿏ <white> Jackpot! You got 5x drops!"));
}
ArrayList<ItemStack> drops = new ArrayList<>();
List<Material> breakableBlocks = Hoe.HoeDefinitions.getAllBlocks(hoe);
List<Block> blocksToBreak = new ArrayList<>();
blocksToBreak.add(event.getBlock());
if (abilities.stream().anyMatch(z -> z.type == ToolUtils.AbilityType.THREE_BY_THREE)) {
for (int dx = -1; dx <= 1; dx++) {
for (int dz = -1; dz <= 1; dz++) {
if (dx == 0 && dz == 0) continue; // skip center (already added)
Block b = event.getBlock().getWorld().getBlockAt(
event.getBlock().getX() + dx,
event.getBlock().getY(),
event.getBlock().getZ() + dz
);
if (breakableBlocks.contains(b.getType())) {
blocksToBreak.add(b);
}
}
}
}
if(windEnchantment.isPresent() && Math.random() <= (double) windEnchantment.get().amount / 100.0) {
org.bukkit.util.Vector randomDir = new org.bukkit.util.Vector(
(Math.random() * 2.0) - 1.0,
0,
(Math.random() * 2.0) - 1.0
).normalize();
World world = event.getBlock().getWorld();
Block origin = event.getBlock();
for (int i = 1; i <= 3; i++) {
org.bukkit.util.Vector offset = randomDir.clone().multiply(i);
Block b = world.getBlockAt(
origin.getX() + offset.getBlockX(),
origin.getY(),
origin.getZ() + offset.getBlockZ()
);
if (breakableBlocks.contains(b.getType())) {
blocksToBreak.add(b);
}
}
//org.bukkit.util.Vector velocityBoost = randomDir.clone().multiply(0.2);
//velocityBoost.setY(0.1);
//player.setVelocity(player.getVelocity().add(velocityBoost));
}
for (Block b : blocksToBreak) {
Collection<ItemStack> dropsForThisBlock = b.getDrops(hand);
if(dropsForThisBlock.isEmpty()) {
drops.add(new ItemStack(b.getType(), 1));
}
drops.addAll(dropsForThisBlock);
b.setType(Material.AIR);
}
Optional<ToolUtils.Ability> hasAutosell
= abilities.stream().filter(z -> z.type == ToolUtils.AbilityType.AUTOSELL).findFirst();
Optional<ToolUtils.Enchantment> fortuneLevel = enchants.stream().filter(z -> z.type == ToolUtils.EnchantmentType.FORTUNE).findFirst();
int exp = 0;
for (ItemStack drop : drops) {
int amount = drop.getAmount();
if(fortuneLevel.isPresent()) {
amount = Snowcore.fortuneToDrops(fortuneLevel.get().amount, drop.getAmount());
}
if(doubleDropEnchantment.isPresent() && Math.random() <= (double) doubleDropEnchantment.get().amount / 100.0) {
drop.setAmount((int) Math.ceil(Math.pow(amount * multiplier, 2)));
} else {
drop.setAmount(amount * multiplier);
}
exp += amount * multiplier;
HashMap<Integer, ItemStack> leftover = inv.addItem(drop);
if(!leftover.isEmpty()) {
if(hasAutosell.isPresent()) {
Snowcore.sellInventory(player);
} else {
fullInventory(player);
event.setCancelled(true);
return;
}
}
}
ItemStack stack = hoe.increaseEXP(hand, exp);
inv.setItemInMainHand(stack);
player.incrementStatistic(Statistic.MINE_BLOCK, Material.BEDROCK, breakableBlocks.size()); // bedrock because i'd like to group all flowers up into a single statistic. this works well enough!
event.getBlock().setType(Material.AIR);
} else {
if(Tag.ITEMS_SHOVELS.isTagged(tool) && blockType.equals(Material.SNOW)) {
event.setDropItems(false);
Snow snowData = (Snow) event.getBlock().getBlockData();
Shovel shovel = Shovel.ShovelDefinitions.ALL_SHOVELS.get(tool);
List<ToolUtils.Ability> abilities = ToolUtils.getAbilities(hand);
List<ToolUtils.Enchantment> enchants = ToolUtils.getEnchantments(hand);
int amountOfSnow = snowData.getLayers();
Block origin = event.getBlock();
var world = origin.getWorld();
ToolUtils.Ability nukeAbility = abilities.stream()
.filter(a -> a.type == ToolUtils.AbilityType.NUKE)
.findFirst()
.orElse(null);
ToolUtils.Enchantment sweepEnchantment = enchants.stream()
.filter(a -> a.type == ToolUtils.EnchantmentType.SWEEPER)
.findFirst()
.orElse(null);
ToolUtils.Enchantment layerRemoverEnchantment = enchants.stream()
.filter(a -> a.type == ToolUtils.EnchantmentType.LAYER_REMOVER)
.findFirst()
.orElse(null);
ToolUtils.Ability multiplierAbility = abilities.stream()
.filter(a -> a.type == ToolUtils.AbilityType.MULTIPLIER)
.findFirst()
.orElse(null);
boolean hasNuke = nukeAbility != null;
double chance = hasNuke ? nukeAbility.maxValue : 0.0;
boolean triggerNuke = hasNuke && Math.random() <= chance / 100.0;
List<Pair<Block, Integer>> targets = new ArrayList<>();
targets.add(Pair.of(event.getBlock(), amountOfSnow));
if (sweepEnchantment != null &&
Math.random() <= (double) sweepEnchantment.amount / 100.0) {
// player horizontal facing
org.bukkit.util.Vector forward = player.getLocation().getDirection().setY(0).normalize();
// perpendicular sideways vector
org.bukkit.util.Vector side = new org.bukkit.util.Vector(-forward.getZ(), 0, forward.getX());
for (int depth = 1; depth <= 2; depth++) {
for (int width = -1; width <= 1; width++) {
org.bukkit.util.Vector offset = forward.clone().multiply(depth)
.add(side.clone().multiply(width));
Block b = origin.getRelative(
offset.getBlockX(),
0,
offset.getBlockZ()
);
if (b.getType() == Material.SNOW) {
Snow snow = (Snow) b.getBlockData();
int snowToRemove = Math.min(2, snow.getLayers());
amountOfSnow += snowToRemove;
targets.add(Pair.of(b, snowToRemove));
}
}
}
}
if(layerRemoverEnchantment != null && Math.random() <= (double) layerRemoverEnchantment.amount / 100.0) {
org.bukkit.util.Vector forward = player.getLocation().getDirection().setY(0).normalize();
org.bukkit.util.Vector side = new org.bukkit.util.Vector(-forward.getZ(), 0, forward.getX());
for (int depth = 1; depth <= 6; depth++) {
org.bukkit.util.Vector forwardOffset = forward.clone().multiply(depth);
Block center = origin.getRelative(
forwardOffset.getBlockX(),
0,
forwardOffset.getBlockZ()
);
if (center.getType() == Material.SNOW) {
Snow snow = (Snow) center.getBlockData();
int layers = snow.getLayers();
amountOfSnow += layers;
targets.add(Pair.of(center, layers));
}
for (int dir = -1; dir <= 1; dir += 2) {
org.bukkit.util.Vector sideOffset = forwardOffset.clone()
.add(side.clone().multiply(dir));
Block sideBlock = origin.getRelative(
sideOffset.getBlockX(),
0,
sideOffset.getBlockZ()
);
if (sideBlock.getType() == Material.SNOW) {
Snow snow = (Snow) sideBlock.getBlockData();
int snowToRemove = Math.min(
1 + (int)(Math.random() * 2),
snow.getLayers()
);
amountOfSnow += snowToRemove;
targets.add(Pair.of(sideBlock, snowToRemove));
}
}
}
}
if (triggerNuke) {
for (int dx = -6; dx <= 6; dx++) {
for (int dz = -6; dz <= 6; dz++) {
if (dx == 0 && dz == 0) continue;
Block b = world.getBlockAt(
origin.getX() + dx,
origin.getY(),
origin.getZ() + dz
);
if (b.getType() != Material.SNOW) continue;
Snow snow = (Snow) b.getBlockData();
int snowToRemove = Math.min(3, snow.getLayers());
amountOfSnow += snowToRemove;
targets.add(Pair.of(b, snowToRemove)); // store for later destruction
}
}
}
Optional<ToolUtils.Enchantment> fortune = enchants.stream().filter(z-> z.type == ToolUtils.EnchantmentType.FORTUNE).findFirst();
if(fortune.isPresent()) {
amountOfSnow = Snowcore.fortuneToDrops(fortune.get().amount, amountOfSnow);
}
if(multiplierAbility != null) {
amountOfSnow += (int) Math.ceil(multiplierAbility.maxValue);
}
HashMap<Integer, ItemStack> leftover =
inv.addItem(new ItemStack(Material.SNOWBALL, amountOfSnow));
if (!leftover.isEmpty()) {
boolean hasAutosell = abilities.stream()
.anyMatch(a -> a.type == ToolUtils.AbilityType.AUTOSELL);
if (hasAutosell) {
Snowcore.sellInventory(player);
event.setCancelled(true);
return; // nothing destroyed
} else {
fullInventory(player);
event.setCancelled(true);
return; // nothing destroyed
}
}
for (Pair<Block, Integer> p : targets) {
Snow snow = (Snow) p.getLeft().getBlockData();
int layers = snow.getLayers();
if (layers <= p.getRight()) {
p.getLeft().setType(Material.AIR, false);
} else {
snow.setLayers(layers - p.getRight());
p.getLeft().setBlockData(snow);
}
}
ItemStack stack = shovel.increaseEXP(hand, amountOfSnow);
player.getInventory().setItemInMainHand(stack);
player.incrementStatistic(Statistic.MINE_BLOCK, Material.SNOW, amountOfSnow);
event.getBlock().setType(Material.AIR);
event.setCancelled(true);
} else {
if(blockType.equals(Material.SNOW)) {
Snowcore.sendActionBar(player, Component.text("You need a shovel to break snow with!"));
} else {
Snowcore.sendActionBar(player, Component.text("You do not have permission to break this block."));
}
event.setCancelled(true);
}
}
}
private void fullInventory(Player player) {
player.showTitle(Title.title(
Component.text("⚠ ⚠ WARNING ⚠ ⚠")
.style(Style.style(TextColor.color(255, 0, 0), TextDecoration.BOLD)),
Component.text("Your inventory is full! Use /sell")
.style(Style.style(TextColor.color(255, 255, 0)))
));
}
}

View file

@ -0,0 +1,266 @@
package ovh.sad.snowcore.listeners;
import dev.triumphteam.gui.guis.Gui;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.GameMode;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerArmorStandManipulateEvent;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.event.player.PlayerInteractEvent;
import org.bukkit.inventory.EquipmentSlot;
import org.bukkit.inventory.ItemStack;
import ovh.sad.snowcore.Snowcore;
import ovh.sad.snowcore.guis.UpgradeYourToolsGui;
import ovh.sad.snowcore.items.Hoe;
import dev.triumphteam.gui.guis.GuiItem;
import dev.triumphteam.gui.builder.item.ItemBuilder;
import org.bukkit.Material;
import ovh.sad.snowcore.items.ToolUtils;
import ovh.sad.snowcore.items.Shovel;
import ovh.sad.snowcore.items.Tool;
import java.util.*;
import java.util.stream.Collectors;
public class InteractListener implements Listener {
@EventHandler
public void glass(PlayerInteractEntityEvent event) {
UUID entityUuid = event.getRightClicked().getUniqueId();
if(entityUuid.equals(UUID.fromString("96a1d63d-f9ba-490c-bee7-f5edb73a08e8"))) {
event.setCancelled(true);
UpgradeYourToolsGui.open(event);
} else if(entityUuid.equals(UUID.fromString("237d77c8-a25f-4cd8-882e-c9da2d62ea79"))) {
event.setCancelled(true);
event.getPlayer().sendMessage(MiniMessage.miniMessage().deserialize("<yellow> \uD83D\uDEA7\uD83D\uDEA7 Under construction!"));
}
}
@EventHandler
public void armorStand(PlayerArmorStandManipulateEvent pasme) {
if(pasme.getPlayer().getGameMode() != GameMode.CREATIVE) {
pasme.setCancelled(true);
}
}
@EventHandler
public void onInteract(PlayerInteractEvent piEvent) {
Player player = piEvent.getPlayer();
ItemStack item = player.getInventory().getItemInMainHand();
if (piEvent.getClickedBlock() != null) return;
if (!player.isSneaking()) return;
Tool<?> tool = Hoe.HoeDefinitions.ALL_HOES.get(item.getType());
if(tool == null) {
tool = Shovel.ShovelDefinitions.ALL_SHOVELS.get(item.getType());
}
if(tool == null) return;
// Create GUI
Gui gui = Gui.gui()
.title(Component.text("Upgrade your Tool"))
.rows(6)
.create();
GuiItem filler = ItemBuilder.from(Material.BLACK_STAINED_GLASS_PANE)
.name(Component.text(" "))
.asGuiItem();
for (int row = 1; row <= 6; row++) {
for (int col = 1; col <= 9; col++) {
if (row == 1 && col == 5)
continue;
if ((row == 2 || row == 3) && (col >= 2 && col <= 8)) continue;
if ((row == 4 || row == 5) && (col >= 2 && col <= 8)) continue;
gui.setItem(row, col, filler);
}
}
Map<ToolUtils.EnchantmentType, ToolUtils.Enchantment> ownedEnchants =
Objects.requireNonNull(ToolUtils.getEnchantments(item))
.stream()
.collect(Collectors.toMap(e -> e.type, e -> e));
Map<ToolUtils.AbilityType, ToolUtils.Ability> ownedAbilities =
Objects.requireNonNull(ToolUtils.getAbilities(item))
.stream()
.collect(Collectors.toMap(a -> a.type, a -> a));
int exp = ToolUtils.getExp(item);
int hoeLevel = ToolUtils.getLevel(item);
Iterator<ToolUtils.Enchantment> definedEnchants = tool.definedEnchantments.stream().map(ToolUtils.Enchantment::copy).iterator();
for (int row = 2; row <= 3 && definedEnchants.hasNext(); row++) {
for (int col = 2; col <= 8 && definedEnchants.hasNext(); col++) {
ToolUtils.Enchantment def = definedEnchants.next();
ToolUtils.Enchantment owned = ownedEnchants.get(def.type);
boolean has = owned != null;
int level = has ? owned.amount : 0;
int nextLevel = level + 1;
int cost = def.getExpRequired(nextLevel);
Component name;
List<Component> lore = new ArrayList<>();
Integer hasEnchantmentLimit = tool.definedEnchantLimits.get(def.type);
if(hasEnchantmentLimit != null && level >= hasEnchantmentLimit) {
name = Component.text("" + def.type.name + " is at max level!", NamedTextColor.RED);
} else if (has) {
name = Component.text("" + def.type.name + " Lv." + level, NamedTextColor.GREEN);
lore.add(Component.text("Click to upgrade → Lv." + nextLevel, NamedTextColor.YELLOW));
lore.add(Component.text("Cost: " + cost + " EXP", NamedTextColor.GOLD));
} else {
name = Component.text(def.type.name, NamedTextColor.YELLOW);
lore.add(Component.text("Unlock for " + cost + " EXP", NamedTextColor.GOLD));
}
Tool<?> finalTool = tool;
gui.setItem(row, col,
ItemBuilder.from(Material.ENCHANTED_BOOK)
.name(name)
.lore(lore)
.asGuiItem(ev -> {
ev.setCancelled(true);
Player whoClicked = (Player) ev.getWhoClicked();
if(level >= finalTool.definedEnchantLimits.get(def.type)) {
return;
}
if (exp >= cost) {
if(has) {
owned.amount = owned.amount + 1;
ownedEnchants.put(owned.type, owned);
} else {
ToolUtils.Enchantment copy = def.copy();
copy.amount = copy.amount + 1;
ownedEnchants.put(copy.type, copy);
}
ItemStack realItem = ToolUtils.setExp(
finalTool,
ToolUtils.setEnchantments(finalTool, item, ownedEnchants.values().stream().toList()),
exp - cost
);
whoClicked.getInventory().setItemInMainHand(realItem);
whoClicked.sendMessage(
MiniMessage.miniMessage().deserialize(
"<green>✔ Unlocked <yellow>" + def.type.name() +
"</yellow> for <aqua>" + (int) cost + " EXP</aqua>"
)
);
gui.close(whoClicked);
} else {
whoClicked.sendMessage(
MiniMessage.miniMessage().deserialize(
"<red>✘ Not enough EXP! Need <aqua>" + (int) cost +
" EXP </aqua>, you have <aqua>" + (int) exp + " EXP</aqua>"
)
);
}
})
);
}
}
Iterator<ToolUtils.Ability> definedAbilities = tool.definedAbilities.stream().iterator();
for (int row = 4; row <= 5 && definedAbilities.hasNext(); row++) {
for (int col = 2; col <= 8 && definedAbilities.hasNext(); col++) {
ToolUtils.Ability def = definedAbilities.next();
ToolUtils.Ability owned = ownedAbilities.get(def.type);
boolean has = owned != null || def.byDefault;
boolean unlocked = hoeLevel >= def.unlockLevel;
Component name;
List<Component> lore = new ArrayList<>();
if (has) {
name = Component.text("" + def.type.name, NamedTextColor.GREEN);
lore.add(Component.text("Already unlocked", NamedTextColor.GRAY));
} else if (!unlocked) {
name = Component.text("🔒 " + def.type.name, NamedTextColor.RED);
lore.add(Component.text("Unlocks at level " + def.unlockLevel, NamedTextColor.GRAY));
} else {
name = Component.text(def.type.name, NamedTextColor.YELLOW);
lore.add(Component.text("Cost: " + (int) def.baseCost + "$", NamedTextColor.GOLD));
lore.add(Component.text("Click to unlock", NamedTextColor.GRAY));
}
Tool<?> finalTool1 = tool;
gui.setItem(row, col,
ItemBuilder.from(Material.NETHER_STAR)
.name(name)
.lore(lore)
.asGuiItem(ev -> {
ev.setCancelled(true);
Player whoClicked = (Player) ev.getWhoClicked();
if(def.unlockLevel > hoeLevel) {
whoClicked.sendMessage(
MiniMessage.miniMessage().deserialize(
"<red>✘ Your level is not high enough. Required level is " + def.unlockLevel
)
);
return;
}
double cost = def.baseCost;
double balance = Snowcore.econ.getBalance(whoClicked);
if (balance >= cost) {
Snowcore.econ.withdrawPlayer(whoClicked, cost);
ownedAbilities.put(def.type, def);
ItemStack realItem =
ToolUtils.setAbilities(finalTool1, item, ownedAbilities.values().stream().toList());
whoClicked.getInventory().setItemInMainHand(realItem);
whoClicked.sendMessage(
MiniMessage.miniMessage().deserialize(
"<green>✔ Unlocked <yellow>" + def.type.name() +
"</yellow> for <gold>$" + (int) cost + "</gold>"
)
);
gui.close(whoClicked);
} else {
whoClicked.sendMessage(
MiniMessage.miniMessage().deserialize(
"<red>✘ Not enough money! Need <gold>$" + (int) cost +
"</gold>, you have <yellow>$" + (int) balance + "</yellow>"
)
);
}
})
);
}
}
gui.setItem(1, 5, new GuiItem(item));
gui.setDefaultClickAction(ev -> ev.setCancelled(true));
gui.open(player);
}
}

View file

@ -0,0 +1,18 @@
package ovh.sad.snowcore.listeners;
import net.kyori.adventure.text.Component;
import org.bukkit.GameMode;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockPlaceEvent;
import ovh.sad.snowcore.Snowcore;
public class PlaceListener implements Listener {
@EventHandler
public void onPlace(BlockPlaceEvent event) {
if(event.getPlayer().getGameMode() != GameMode.CREATIVE) {
Snowcore.sendActionBar(event.getPlayer(), Component.text("You cannot place blocks here!"));
event.setCancelled(true);
}
}
}

View file

@ -0,0 +1,8 @@
name: snowcore
version: '1.0-SNAPSHOT'
main: ovh.sad.snowcore.Snowcore
api-version: '1.21'
depend:
- Vault
- EconomySystem
- PlaceholderAPI