commit fcb6e756f780e32f03dda0c4997b18c7862206e2 Author: Big Pappa Date: Fri Aug 7 02:59:37 2026 -0500 1.0.0 Production Release diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5bbb4a2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Gradle +.gradle/ +build/ + +# IntelliJ IDEA +.idea/ +*.iml +out/ + +# OS Files +.DS_Store +Thumbs.db + +# Logs +*.log +hs_err_pid* + +# Local Paper test server (if inside project) +run/ +server/ +plugins/ +world/ +world_nether/ +world_the_end/ \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..d4f8845 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,47 @@ +plugins { + java + + id("io.papermc.paperweight.userdev") version "2.0.0-beta.21" +} + +group = "com.rsscripting" +version = "1.0.0" +val pluginVersion = version.toString() + +repositories { + mavenCentral() +} + +dependencies { + + // Paper API + NMS + paperweight.paperDevBundle("1.21.11-R0.1-SNAPSHOT") +} + +java { + toolchain.languageVersion.set(JavaLanguageVersion.of(21)) +} + +tasks { + compileJava { + options.encoding = "UTF-8" + } + + processResources { + filteringCharset = "UTF-8" + + val props = mapOf( + "version" to pluginVersion + ) + + inputs.properties(props) + + filesMatching("plugin.yml") { + expand(props) + } + } + + assemble { + dependsOn(reobfJar) + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..0f38311 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,5 @@ +group=com.rsscripting +version=0.1.0 +org.gradle.configuration-cache=true +org.gradle.parallel=true +org.gradle.caching=true diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..d997cfc 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..a351597 --- /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-9.6.1-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..0262dcb --- /dev/null +++ b/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# 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/b631911858264c0b6e4d6603d677ff5218766cee/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 -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || 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 + + + +# 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" ) + + 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, 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" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# 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..c4bdd3a --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,93 @@ +@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 +@rem SPDX-License-Identifier: Apache-2.0 +@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 + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +: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.kts b/settings.gradle.kts new file mode 100644 index 0000000..ec2c3c3 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,8 @@ +pluginManagement { + repositories { + gradlePluginPortal() + maven("https://repo.papermc.io/repository/maven-public/") + } +} + +rootProject.name = "RS-MobCage" \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/MobCage.java b/src/main/java/com/rsscripting/mobcage/MobCage.java new file mode 100644 index 0000000..e957a84 --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/MobCage.java @@ -0,0 +1,42 @@ +package com.rsscripting.mobcage; + +import com.rsscripting.mobcage.item.CageRecipe; +import com.rsscripting.mobcage.listener.MobCageListener; +import com.rsscripting.mobcage.listener.MobReleaseListener; +import com.rsscripting.mobcage.util.Keys; +import org.bukkit.plugin.java.JavaPlugin; + +public final class MobCage extends JavaPlugin { + + private static MobCage instance; + + @Override + public void onEnable() { + instance = this; + + saveDefaultConfig(); + + Keys.initialize(this); + CageRecipe.register(this); + + getServer().getPluginManager().registerEvents( + new MobCageListener(), + this + ); + + getServer().getPluginManager().registerEvents( + new MobReleaseListener(), + this + ); + + } + + @Override + public void onDisable() { + // Plugin shutdown logic + } + + public static MobCage getInstance() { + return instance; + } +} \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/config/ConfigManager.java b/src/main/java/com/rsscripting/mobcage/config/ConfigManager.java new file mode 100644 index 0000000..0a7caf3 --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/config/ConfigManager.java @@ -0,0 +1,18 @@ +package com.rsscripting.mobcage.config; + +import com.rsscripting.mobcage.MobCage; + +public final class ConfigManager { + + private ConfigManager() { + + } + + public static boolean dropEquipOnCapture() { + + return MobCage.getInstance() + .getConfig() + .getBoolean("drop-equip-on-capture", true); + } + +} \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/description/CaptureResult.java b/src/main/java/com/rsscripting/mobcage/description/CaptureResult.java new file mode 100644 index 0000000..7f5c1d1 --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/description/CaptureResult.java @@ -0,0 +1,29 @@ +package com.rsscripting.mobcage.description; + +public class CaptureResult { + + private final boolean allowed; + private final String message; + + private CaptureResult(boolean allowed, String message) { + this.allowed = allowed; + this.message = message; + } + + public boolean isAllowed() { + return allowed; + } + + public String getMessage() { + return message; + } + + public static CaptureResult allowed() { + return new CaptureResult(true, null); + } + + public static CaptureResult denied(String message) { + return new CaptureResult(false, message); + } + +} \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/description/MobAttribute.java b/src/main/java/com/rsscripting/mobcage/description/MobAttribute.java new file mode 100644 index 0000000..ef2c9d6 --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/description/MobAttribute.java @@ -0,0 +1,21 @@ +package com.rsscripting.mobcage.description; + +public class MobAttribute { + + private final String label; + private final String value; + + public MobAttribute(String label, String value) { + this.label = label; + this.value = value; + } + + public String getLabel() { + return label; + } + + public String getValue() { + return value; + } + +} \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/description/MobDescriptionFormatter.java b/src/main/java/com/rsscripting/mobcage/description/MobDescriptionFormatter.java new file mode 100644 index 0000000..3efc3ab --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/description/MobDescriptionFormatter.java @@ -0,0 +1,84 @@ +package com.rsscripting.mobcage.description; + +import com.rsscripting.mobcage.description.descriptor.MobDescriptorRegistry; +import net.kyori.adventure.text.Component; + +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.entity.*; + +import java.util.ArrayList; +import java.util.List; + +public final class MobDescriptionFormatter { + + private MobDescriptionFormatter() { + + } + + public static String getContainedMobName(LivingEntity entity) { + + return MobDescriptorRegistry + .get(entity) + .getDisplayName(entity); + + } + + public static String formatEnumName(String value) { + + String[] words = value.toLowerCase().split("_"); + + StringBuilder builder = new StringBuilder(); + + for (String word : words) { + + if (!builder.isEmpty()) { + builder.append(" "); + } + + builder.append(Character.toUpperCase(word.charAt(0))); + builder.append(word.substring(1)); + } + + return builder.toString(); + } + + public static List getAdditionalLore(LivingEntity entity) { + + List lore = new ArrayList<>(); + + lore.add(Component.empty()); + + lore.add(Component.text( + "Contains:", + NamedTextColor.GREEN + )); + + lore.add(Component.text( + getContainedMobName(entity), + NamedTextColor.YELLOW + )); + + List attributes = MobDescriptorRegistry + .get(entity) + .getAttributes(entity); + + for (MobAttribute attribute : attributes) { + + lore.add(Component.empty()); + + lore.add(Component.text( + attribute.getLabel() + ":", + NamedTextColor.GRAY + )); + + lore.add(Component.text( + attribute.getValue(), + NamedTextColor.YELLOW + )); + + } + + return lore; + } + +} diff --git a/src/main/java/com/rsscripting/mobcage/description/descriptor/BeeDescriptor.java b/src/main/java/com/rsscripting/mobcage/description/descriptor/BeeDescriptor.java new file mode 100644 index 0000000..1c70376 --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/description/descriptor/BeeDescriptor.java @@ -0,0 +1,74 @@ +package com.rsscripting.mobcage.description.descriptor; + +import com.rsscripting.mobcage.description.CaptureResult; +import com.rsscripting.mobcage.description.MobAttribute; +import com.rsscripting.mobcage.util.MobAgeUtil; +import com.rsscripting.mobcage.util.MobHealthUtil; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.entity.Bee; +import org.bukkit.entity.EntityType; +import org.bukkit.Location; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +import java.util.List; + +public class BeeDescriptor implements MobDescriptor { + + @Override + public boolean supports(LivingEntity entity) { + + return entity instanceof Bee; + } + + @Override + public CaptureResult beforeCapture(LivingEntity entity, Player player) { + + CaptureResult result = MobHealthUtil.validateFullHealth(entity); + + if (!result.isAllowed()) { + return result; + } + + MobAgeUtil.normalize(entity); + + return CaptureResult.allowed(); + } + + @Override + public String getDisplayName(LivingEntity entity) { + return MobAgeUtil.getAgePrefix(entity) + "Bee"; + } + + @Override + public List getAttributes(LivingEntity entity) { + return List.of(); + } + + @Override + public void release(Location location, List lore) { + + PlainTextComponentSerializer plain = PlainTextComponentSerializer.plainText(); + + boolean baby = false; + + for (int i = 0; i < lore.size(); i++) { + + String line = plain.serialize(lore.get(i)); + + if (line.equals("Contains:") && i + 1 < lore.size()) { + baby = plain.serialize(lore.get(i + 1)).equals("Baby Bee"); + } + } + + Bee bee = (Bee) location.getWorld().spawnEntity(location, EntityType.BEE); + + if (baby) { + bee.setBaby(); + } else { + bee.setAdult(); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/description/descriptor/CaveSpiderDescriptor.java b/src/main/java/com/rsscripting/mobcage/description/descriptor/CaveSpiderDescriptor.java new file mode 100644 index 0000000..dac9421 --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/description/descriptor/CaveSpiderDescriptor.java @@ -0,0 +1,51 @@ +package com.rsscripting.mobcage.description.descriptor; + +import com.rsscripting.mobcage.description.CaptureResult; +import com.rsscripting.mobcage.description.MobAttribute; +import com.rsscripting.mobcage.util.MobAgeUtil; +import com.rsscripting.mobcage.util.MobHealthUtil; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.Location; +import org.bukkit.entity.*; + +import java.util.List; + +public class CaveSpiderDescriptor implements MobDescriptor { + + @Override + public boolean supports(LivingEntity entity) { + return entity instanceof CaveSpider; + } + + @Override + public CaptureResult beforeCapture(LivingEntity entity, Player player) { + + CaptureResult result = MobHealthUtil.validateFullHealth(entity); + + if (!result.isAllowed()) { + return result; + } + + return CaptureResult.allowed(); + } + + @Override + public String getDisplayName(LivingEntity entity) { + return "Cave Spider"; + } + + @Override + public List getAttributes(LivingEntity entity) { + return List.of(); + } + + @Override + public void release(Location location, List lore) { + + location.getWorld().spawnEntity( + location, + EntityType.CAVE_SPIDER + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/description/descriptor/ChickenDescriptor.java b/src/main/java/com/rsscripting/mobcage/description/descriptor/ChickenDescriptor.java new file mode 100644 index 0000000..40817a1 --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/description/descriptor/ChickenDescriptor.java @@ -0,0 +1,98 @@ +package com.rsscripting.mobcage.description.descriptor; + +import com.rsscripting.mobcage.description.CaptureResult; +import com.rsscripting.mobcage.description.MobAttribute; +import com.rsscripting.mobcage.description.MobDescriptionFormatter; +import com.rsscripting.mobcage.util.MobAgeUtil; +import com.rsscripting.mobcage.util.MobHealthUtil; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.Location; +import org.bukkit.entity.*; + +import java.util.List; + +public class ChickenDescriptor implements MobDescriptor { + + @Override + public boolean supports(LivingEntity entity) { + return entity instanceof Chicken; + } + + @Override + public CaptureResult beforeCapture(LivingEntity entity, Player player) { + + CaptureResult result = MobHealthUtil.validateFullHealth(entity); + + if (!result.isAllowed()) { + return result; + } + + MobAgeUtil.normalize(entity); + + return CaptureResult.allowed(); + } + + @Override + public String getDisplayName(LivingEntity entity) { + return MobAgeUtil.getAgePrefix(entity) + "Chicken"; + } + + @Override + public List getAttributes(LivingEntity entity) { + + Chicken chicken = (Chicken) entity; + + return List.of( + new MobAttribute( + "Variant", + MobDescriptionFormatter.formatEnumName( + chicken.getVariant().getKey().getKey() + ) + ) + ); + } + + @Override + public void release(Location location, List lore) { + + PlainTextComponentSerializer plain = PlainTextComponentSerializer.plainText(); + + boolean baby = false; + Chicken.Variant variant = Chicken.Variant.TEMPERATE; + + for (int i = 0; i < lore.size(); i++) { + + String line = plain.serialize(lore.get(i)); + + if (line.equals("Contains:") && i + 1 < lore.size()) { + baby = plain.serialize(lore.get(i + 1)).equals("Baby Chicken"); + } + + if (line.equals("Variant:") && i + 1 < lore.size()) { + variant = getVariant(plain.serialize(lore.get(i + 1))); + } + } + + Chicken chicken = (Chicken) location.getWorld().spawnEntity(location, EntityType.CHICKEN); + + if (baby) { + chicken.setBaby(); + } else { + chicken.setAdult(); + } + + chicken.setVariant(variant); + } + + private Chicken.Variant getVariant(String value) { + + return switch (value) { + case "Temperate" -> Chicken.Variant.TEMPERATE; + case "Warm" -> Chicken.Variant.WARM; + case "Cold" -> Chicken.Variant.COLD; + default -> Chicken.Variant.TEMPERATE; + }; + } + +} \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/description/descriptor/CowDescriptor.java b/src/main/java/com/rsscripting/mobcage/description/descriptor/CowDescriptor.java new file mode 100644 index 0000000..7286094 --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/description/descriptor/CowDescriptor.java @@ -0,0 +1,99 @@ +package com.rsscripting.mobcage.description.descriptor; + +import com.rsscripting.mobcage.description.CaptureResult; +import com.rsscripting.mobcage.description.MobAttribute; +import com.rsscripting.mobcage.description.MobDescriptionFormatter; +import com.rsscripting.mobcage.util.MobAgeUtil; +import com.rsscripting.mobcage.util.MobHealthUtil; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.Location; +import org.bukkit.entity.*; + +import java.util.List; + +public class CowDescriptor implements MobDescriptor { + + @Override + public boolean supports(LivingEntity entity) { + return entity instanceof Cow; + } + + @Override + public CaptureResult beforeCapture(LivingEntity entity, Player player) { + + CaptureResult result = MobHealthUtil.validateFullHealth(entity); + + if (!result.isAllowed()) { + return result; + } + + MobAgeUtil.normalize(entity); + + return CaptureResult.allowed(); + } + + @Override + public String getDisplayName(LivingEntity entity) { + + return MobAgeUtil.getAgePrefix(entity) + "Cow"; + } + + @Override + public List getAttributes(LivingEntity entity) { + + Cow cow = (Cow) entity; + + return List.of( + new MobAttribute( + "Variant", + MobDescriptionFormatter.formatEnumName( + cow.getVariant().getKey().getKey() + ) + ) + ); + } + + @Override + public void release(Location location, List lore) { + + PlainTextComponentSerializer plain = PlainTextComponentSerializer.plainText(); + + boolean baby = false; + Cow.Variant variant = Cow.Variant.TEMPERATE; + + for (int i = 0; i < lore.size(); i++) { + + String line = plain.serialize(lore.get(i)); + + if (line.equals("Contains:") && i + 1 < lore.size()) { + baby = plain.serialize(lore.get(i + 1)).equals("Baby Cow"); + } + + if (line.equals("Variant:") && i + 1 < lore.size()) { + variant = getVariant(plain.serialize(lore.get(i + 1))); + } + } + +Cow cow = (Cow) location.getWorld().spawnEntity(location, EntityType.COW); + + if (baby) { + cow.setBaby(); + } else { + cow.setAdult(); + } + + cow.setVariant(variant); + } + + private Cow.Variant getVariant(String value) { + + return switch (value) { + case "Temperate" -> Cow.Variant.TEMPERATE; + case "Warm" -> Cow.Variant.WARM; + case "Cold" -> Cow.Variant.COLD; + default -> Cow.Variant.TEMPERATE; + }; + } + +} \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/description/descriptor/HorseDescriptor.java b/src/main/java/com/rsscripting/mobcage/description/descriptor/HorseDescriptor.java new file mode 100644 index 0000000..4506fcf --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/description/descriptor/HorseDescriptor.java @@ -0,0 +1,142 @@ +package com.rsscripting.mobcage.description.descriptor; + +import com.rsscripting.mobcage.MobCage; +import com.rsscripting.mobcage.description.CaptureResult; +import com.rsscripting.mobcage.description.MobAttribute; +import com.rsscripting.mobcage.description.MobDescriptionFormatter; +import com.rsscripting.mobcage.util.MobAgeUtil; +import com.rsscripting.mobcage.util.MobEquipmentUtil; +import com.rsscripting.mobcage.util.MobHealthUtil; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.Location; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Horse; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; + +import java.util.List; + +public class HorseDescriptor implements MobDescriptor { + + @Override + public boolean supports(LivingEntity entity) { + return entity instanceof Horse; + } + + @Override + public CaptureResult beforeCapture(LivingEntity entity, Player player) { + + CaptureResult result = MobHealthUtil.validateFullHealth(entity); + + if (!result.isAllowed()) { + return result; + } + + MobAgeUtil.normalize(entity); + + MobEquipmentUtil.handleEquipmentOnCapture( + MobCage.getInstance(), + entity, + player + ); + + return CaptureResult.allowed(); + } + + @Override + public String getDisplayName(LivingEntity entity) { + return MobAgeUtil.getAgePrefix(entity) + "Horse"; + } + + @Override + public List getAttributes(LivingEntity entity) { + + Horse horse = (Horse) entity; + + return List.of( + new MobAttribute( + "Color", + MobDescriptionFormatter.formatEnumName( + horse.getColor().name() + ) + ), + new MobAttribute( + "Markings", + MobDescriptionFormatter.formatEnumName( + horse.getStyle().name() + ) + ) + ); + } + + @Override + public void release(Location location, List lore) { + + PlainTextComponentSerializer plain = PlainTextComponentSerializer.plainText(); + + boolean baby = false; + Horse.Color color = Horse.Color.BROWN; + Horse.Style style = Horse.Style.NONE; + + for (int i = 0; i < lore.size(); i++) { + + String line = plain.serialize(lore.get(i)); + + if (line.equals("Contains:") && i + 1 < lore.size()) { + baby = plain.serialize(lore.get(i + 1)).equals("Baby Horse"); + } + + if (line.equals("Color:") && i + 1 < lore.size()) { + color = getColor(plain.serialize(lore.get(i + 1))); + } + + if (line.equals("Markings:") && i + 1 < lore.size()) { + style = getStyle(plain.serialize(lore.get(i + 1))); + } + } + + Horse horse = (Horse) location.getWorld().spawnEntity(location, EntityType.HORSE); + + if (baby) { + horse.setBaby(); + } else { + horse.setAdult(); + } + + horse.setColor(color); + horse.setStyle(style); + + horse.setPersistent(true); + } + + private Horse.Color getColor(String value) { + + return switch (value) { + case "White" -> Horse.Color.WHITE; + case "Creamy" -> Horse.Color.CREAMY; + case "Chestnut" -> Horse.Color.CHESTNUT; + case "Brown" -> Horse.Color.BROWN; + case "Black" -> Horse.Color.BLACK; + case "Gray" -> Horse.Color.GRAY; + case "Dark Brown" -> Horse.Color.DARK_BROWN; + default -> Horse.Color.BROWN; + }; + + } + + private Horse.Style getStyle(String value) { + + return switch (value) { + case "None" -> Horse.Style.NONE; + case "White" -> Horse.Style.WHITE; + case "White Field" -> Horse.Style.WHITEFIELD; + case "White Dots" -> Horse.Style.WHITE_DOTS; + case "Black Dots" -> Horse.Style.BLACK_DOTS; + default -> Horse.Style.NONE; + }; + + } + +} \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/description/descriptor/MobDescriptor.java b/src/main/java/com/rsscripting/mobcage/description/descriptor/MobDescriptor.java new file mode 100644 index 0000000..f758638 --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/description/descriptor/MobDescriptor.java @@ -0,0 +1,31 @@ +package com.rsscripting.mobcage.description.descriptor; + +import com.rsscripting.mobcage.description.CaptureResult; +import com.rsscripting.mobcage.description.MobAttribute; +import net.kyori.adventure.text.Component; +import org.bukkit.Location; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; + +import java.util.Collections; +import java.util.List; + +public interface MobDescriptor { + + boolean supports(LivingEntity entity); + + default String getDisplayName(LivingEntity entity) { + return entity.getType().name(); + } + + default List getAttributes(LivingEntity entity) { + return Collections.emptyList(); + } + + default CaptureResult beforeCapture(LivingEntity entity, Player player) { + return CaptureResult.allowed(); + } + + void release(Location location, List lore); + +} \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/description/descriptor/MobDescriptorRegistry.java b/src/main/java/com/rsscripting/mobcage/description/descriptor/MobDescriptorRegistry.java new file mode 100644 index 0000000..76c3b40 --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/description/descriptor/MobDescriptorRegistry.java @@ -0,0 +1,36 @@ +package com.rsscripting.mobcage.description.descriptor; + +import org.bukkit.entity.LivingEntity; + +import java.util.List; + +public final class MobDescriptorRegistry { + + private static final List DESCRIPTORS = List.of( + new BeeDescriptor(), + new CaveSpiderDescriptor(), + new ChickenDescriptor(), + new CowDescriptor(), + new HorseDescriptor(), + new PigDescriptor(), + new SheepDescriptor(), + new SpiderDescriptor(), + new VillagerDescriptor(), + new ZombieDescriptor() + ); + + private MobDescriptorRegistry() { + } + + public static MobDescriptor get(LivingEntity entity) { + + for (MobDescriptor descriptor : DESCRIPTORS) { + + if (descriptor.supports(entity)) + return descriptor; + + } + + return null; + } +} \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/description/descriptor/PigDescriptor.java b/src/main/java/com/rsscripting/mobcage/description/descriptor/PigDescriptor.java new file mode 100644 index 0000000..382c7d5 --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/description/descriptor/PigDescriptor.java @@ -0,0 +1,102 @@ +package com.rsscripting.mobcage.description.descriptor; + +import com.rsscripting.mobcage.description.CaptureResult; +import com.rsscripting.mobcage.description.MobAttribute; +import com.rsscripting.mobcage.description.MobDescriptionFormatter; +import com.rsscripting.mobcage.util.MobAgeUtil; +import com.rsscripting.mobcage.util.MobHealthUtil; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.entity.EntityType; +import org.bukkit.Location; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Pig; +import org.bukkit.entity.Player; + +import java.util.List; + +public class PigDescriptor implements MobDescriptor { + + @Override + public boolean supports(LivingEntity entity) { + + return entity instanceof Pig; + } + + @Override + public CaptureResult beforeCapture(LivingEntity entity, Player player) { + + CaptureResult result = MobHealthUtil.validateFullHealth(entity); + + if (!result.isAllowed()) { + return result; + } + + MobAgeUtil.normalize(entity); + + return CaptureResult.allowed(); + } + + @Override + public String getDisplayName(LivingEntity entity) { + return MobAgeUtil.getAgePrefix(entity) + "Pig"; + } + + @Override + public List getAttributes(LivingEntity entity) { + + Pig pig = (Pig) entity; + + return List.of( + new MobAttribute( + "Variant", + MobDescriptionFormatter.formatEnumName( + pig.getVariant().getKey().getKey() + ) + ) + ); + } + + @Override + public void release(Location location, List lore) { + + PlainTextComponentSerializer plain = PlainTextComponentSerializer.plainText(); + + boolean baby = false; + Pig.Variant variant = Pig.Variant.TEMPERATE; + + for (int i = 0; i < lore.size(); i++) { + + String line = plain.serialize(lore.get(i)); + + if (line.equals("Contains:") && i + 1 < lore.size()) { + baby = plain.serialize(lore.get(i + 1)).equals("Baby Pig"); + } + + if (line.equals("Variant:") && i + 1 < lore.size()) { + variant = getVariant(plain.serialize(lore.get(i + 1))); + } + } + + Pig pig = (Pig) location.getWorld().spawnEntity(location, EntityType.PIG); + + if (baby) { + pig.setBaby(); + } else { + pig.setAdult(); + } + + pig.setVariant(variant); + } + + private Pig.Variant getVariant(String value) { + + return switch (value) { + case "Temperate" -> Pig.Variant.TEMPERATE; + case "Warm" -> Pig.Variant.WARM; + case "Cold" -> Pig.Variant.COLD; + default -> Pig.Variant.TEMPERATE; + }; + } + +} \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/description/descriptor/SheepDescriptor.java b/src/main/java/com/rsscripting/mobcage/description/descriptor/SheepDescriptor.java new file mode 100644 index 0000000..3b4e630 --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/description/descriptor/SheepDescriptor.java @@ -0,0 +1,173 @@ +package com.rsscripting.mobcage.description.descriptor; + +import com.rsscripting.mobcage.description.CaptureResult; +import com.rsscripting.mobcage.description.MobAttribute; +import com.rsscripting.mobcage.description.MobDescriptionFormatter; +import com.rsscripting.mobcage.util.MobAgeUtil; +import com.rsscripting.mobcage.util.MobHealthUtil; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.DyeColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Sheep; +import org.bukkit.inventory.ItemStack; + +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +public class SheepDescriptor implements MobDescriptor { + + @Override + public boolean supports(LivingEntity entity) { + return entity instanceof Sheep; + } + + @Override + public CaptureResult beforeCapture(LivingEntity entity, Player player) { + + CaptureResult result = MobDescriptor.super.beforeCapture(entity, player); + + if (!result.isAllowed()) { + return result; + } + + result = MobHealthUtil.validateFullHealth(entity); + + if (!result.isAllowed()) { + return result; + } + + MobAgeUtil.normalize(entity); + + Sheep sheep = (Sheep) entity; + + if (sheep.isAdult() && !sheep.isSheared()) { + + sheep.setSheared(true); + + int amount = ThreadLocalRandom.current().nextInt(1, 4); + + sheep.getWorld().dropItemNaturally( + sheep.getLocation(), + new ItemStack( + getWool(sheep.getColor()), + amount + ) + ); + + player.sendRichMessage( + "The sheep struggled during capture and lost all of its wool." + ); + } + + return CaptureResult.allowed(); + } + + @Override + public String getDisplayName(LivingEntity entity) { + + Sheep sheep = (Sheep) entity; + + return MobAgeUtil.getAgePrefix(entity) + "Sheep"; + } + + @Override + public List getAttributes(LivingEntity entity) { + + Sheep sheep = (Sheep) entity; + + return List.of( + new MobAttribute( + "Color", + MobDescriptionFormatter.formatEnumName( + sheep.getColor().name() + ) + " Wool" + ) + ); + } + + private Material getWool(DyeColor color) { + + return switch (color) { + case WHITE -> Material.WHITE_WOOL; + case ORANGE -> Material.ORANGE_WOOL; + case MAGENTA -> Material.MAGENTA_WOOL; + case LIGHT_BLUE -> Material.LIGHT_BLUE_WOOL; + case YELLOW -> Material.YELLOW_WOOL; + case LIME -> Material.LIME_WOOL; + case PINK -> Material.PINK_WOOL; + case GRAY -> Material.GRAY_WOOL; + case LIGHT_GRAY -> Material.LIGHT_GRAY_WOOL; + case CYAN -> Material.CYAN_WOOL; + case PURPLE -> Material.PURPLE_WOOL; + case BLUE -> Material.BLUE_WOOL; + case BROWN -> Material.BROWN_WOOL; + case GREEN -> Material.GREEN_WOOL; + case RED -> Material.RED_WOOL; + case BLACK -> Material.BLACK_WOOL; + }; + } + + private DyeColor getDyeColor(String color) { + + return switch (color) { + case "White Wool" -> DyeColor.WHITE; + case "Orange Wool" -> DyeColor.ORANGE; + case "Magenta Wool" -> DyeColor.MAGENTA; + case "Light Blue Wool" -> DyeColor.LIGHT_BLUE; + case "Yellow Wool" -> DyeColor.YELLOW; + case "Lime Wool" -> DyeColor.LIME; + case "Pink Wool" -> DyeColor.PINK; + case "Gray Wool" -> DyeColor.GRAY; + case "Light Gray Wool" -> DyeColor.LIGHT_GRAY; + case "Cyan Wool" -> DyeColor.CYAN; + case "Purple Wool" -> DyeColor.PURPLE; + case "Blue Wool" -> DyeColor.BLUE; + case "Brown Wool" -> DyeColor.BROWN; + case "Green Wool" -> DyeColor.GREEN; + case "Red Wool" -> DyeColor.RED; + case "Black Wool" -> DyeColor.BLACK; + default -> DyeColor.WHITE; + }; + } + + @Override + public void release(Location location, List lore) { + PlainTextComponentSerializer plain = PlainTextComponentSerializer.plainText(); + + boolean baby = false; + DyeColor color = DyeColor.WHITE; + + for (int i = 0; i < lore.size(); i++) { + + String line = plain.serialize(lore.get(i)); + + if (line.equals("Contains:") && i + 1 < lore.size()) { + baby = plain.serialize(lore.get(i + 1)).equals("Baby Sheep"); + } + + if (line.equals("Color:") && i + 1 < lore.size()) { + color = getDyeColor(plain.serialize(lore.get(i + 1))); + } + } + + Sheep sheep = (Sheep) location.getWorld().spawnEntity(location, EntityType.SHEEP); + + if (baby) { + sheep.setBaby(); + } else { + sheep.setAdult(); + sheep.setSheared(true); + } + + sheep.setColor(color); + + } + +} \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/description/descriptor/SpiderDescriptor.java b/src/main/java/com/rsscripting/mobcage/description/descriptor/SpiderDescriptor.java new file mode 100644 index 0000000..589cd63 --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/description/descriptor/SpiderDescriptor.java @@ -0,0 +1,49 @@ +package com.rsscripting.mobcage.description.descriptor; + +import com.rsscripting.mobcage.description.CaptureResult; +import com.rsscripting.mobcage.description.MobAttribute; +import com.rsscripting.mobcage.util.MobHealthUtil; +import net.kyori.adventure.text.Component; +import org.bukkit.Location; +import org.bukkit.entity.*; + +import java.util.List; + +public class SpiderDescriptor implements MobDescriptor { + + @Override + public boolean supports(LivingEntity entity) { + return entity instanceof Spider; + } + + @Override + public CaptureResult beforeCapture(LivingEntity entity, Player player) { + + CaptureResult result = MobHealthUtil.validateFullHealth(entity); + + if (!result.isAllowed()) { + return result; + } + + return CaptureResult.allowed(); + } + + @Override + public String getDisplayName(LivingEntity entity) { + return "Spider"; + } + + @Override + public List getAttributes(LivingEntity entity) { + return List.of(); + } + + @Override + public void release(Location location, List lore) { + + location.getWorld().spawnEntity( + location, + EntityType.SPIDER + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/description/descriptor/VillagerDescriptor.java b/src/main/java/com/rsscripting/mobcage/description/descriptor/VillagerDescriptor.java new file mode 100644 index 0000000..44ef213 --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/description/descriptor/VillagerDescriptor.java @@ -0,0 +1,74 @@ +package com.rsscripting.mobcage.description.descriptor; + +import com.rsscripting.mobcage.description.CaptureResult; +import com.rsscripting.mobcage.description.MobAttribute; +import com.rsscripting.mobcage.util.MobAgeUtil; +import com.rsscripting.mobcage.util.MobHealthUtil; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.Location; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Villager; + +import java.util.List; + +public class VillagerDescriptor implements MobDescriptor { + + @Override + public boolean supports(LivingEntity entity) { + + return entity instanceof Villager; + } + + @Override + public CaptureResult beforeCapture(LivingEntity entity, Player player) { + + CaptureResult result = MobHealthUtil.validateFullHealth(entity); + + if (!result.isAllowed()) { + return result; + } + + MobAgeUtil.normalize(entity); + + return CaptureResult.allowed(); + } + + @Override + public String getDisplayName(LivingEntity entity) { + return MobAgeUtil.getAgePrefix(entity) + "Villager"; + } + + @Override + public List getAttributes(LivingEntity entity) { + return List.of(); + } + + @Override + public void release(Location location, List lore) { + + PlainTextComponentSerializer plain = PlainTextComponentSerializer.plainText(); + + boolean baby = false; + + for (int i = 0; i < lore.size(); i++) { + + String line = plain.serialize(lore.get(i)); + + if (line.equals("Contains:") && i + 1 < lore.size()) { + baby = plain.serialize(lore.get(i + 1)).equals("Baby Villager"); + } + } + + Villager villager = (Villager) location.getWorld().spawnEntity(location, EntityType.VILLAGER); + + if (baby) { + villager.setBaby(); + } else { + villager.setAdult(); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/description/descriptor/ZombieDescriptor.java b/src/main/java/com/rsscripting/mobcage/description/descriptor/ZombieDescriptor.java new file mode 100644 index 0000000..43d772f --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/description/descriptor/ZombieDescriptor.java @@ -0,0 +1,108 @@ +package com.rsscripting.mobcage.description.descriptor; + +import com.rsscripting.mobcage.MobCage; +import com.rsscripting.mobcage.description.CaptureResult; +import com.rsscripting.mobcage.description.MobAttribute; +import com.rsscripting.mobcage.description.MobDescriptionFormatter; +import com.rsscripting.mobcage.util.MobAgeUtil; +import com.rsscripting.mobcage.util.MobEquipmentUtil; +import com.rsscripting.mobcage.util.MobHealthUtil; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.entity.*; +import org.bukkit.Location; + +import java.util.List; + +public class ZombieDescriptor implements MobDescriptor { + + @Override + public boolean supports(LivingEntity entity) { + + return entity instanceof Zombie; + } + + @Override + public CaptureResult beforeCapture(LivingEntity entity, Player player) { + + CaptureResult result = MobHealthUtil.validateFullHealth(entity); + + if (!result.isAllowed()) { + return result; + } + + MobAgeUtil.normalize(entity); + + MobEquipmentUtil.handleEquipmentOnCapture( + MobCage.getInstance(), + entity, + player + ); + + return CaptureResult.allowed(); + } + + @Override + public String getDisplayName(LivingEntity entity) { + + Zombie zombie = (Zombie) entity; + + if (zombie instanceof ZombieVillager) { + return MobAgeUtil.getAgePrefix(entity) + "Zombie Villager"; + } + + return MobAgeUtil.getAgePrefix(entity) + "Zombie"; + } + + @Override + public List getAttributes(LivingEntity entity) { + return List.of(); + } + + @Override + public void release(Location location, List lore) { + + PlainTextComponentSerializer plain = PlainTextComponentSerializer.plainText(); + + boolean baby = false; + boolean villager = false; + + for (int i = 0; i < lore.size(); i++) { + + String line = plain.serialize(lore.get(i)); + + if (line.equals("Contains:") && i + 1 < lore.size()) { + + String contains = plain.serialize(lore.get(i + 1)); + + baby = contains.startsWith("Baby"); + villager = contains.contains("Villager"); + } + } + + Zombie zombie; + + if (villager) { + zombie = (Zombie) location.getWorld().spawnEntity(location, EntityType.ZOMBIE_VILLAGER); + } else { + zombie = (Zombie) location.getWorld().spawnEntity(location, EntityType.ZOMBIE); + } + + MobCage.getInstance().getServer().getScheduler().runTask( + MobCage.getInstance(), + () -> { + if (zombie.getEquipment() != null) { + zombie.getEquipment().clear(); + } + } + ); + + if (baby) { + zombie.setBaby(); + } else { + zombie.setAdult(); + } + + } + +} \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/item/CageItem.java b/src/main/java/com/rsscripting/mobcage/item/CageItem.java new file mode 100644 index 0000000..e17162f --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/item/CageItem.java @@ -0,0 +1,159 @@ +package com.rsscripting.mobcage.item; + +import com.rsscripting.mobcage.description.MobDescriptionFormatter; +import com.rsscripting.mobcage.util.Keys; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Material; + +import java.util.ArrayList; +import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataType; + +import java.util.List; + +public final class CageItem { + + private static final byte STATE_EMPTY = 0; + private static final byte STATE_FILLED = 1; + + private CageItem() { + // Utility class + } + + /** + * Creates an empty Mob Cage. + */ + public static ItemStack createEmpty() { + + ItemStack item = new ItemStack(Material.SPAWNER); + + ItemMeta meta = item.getItemMeta(); + + meta.displayName(Component.text("Mob Cage", NamedTextColor.GOLD)); + + meta.addItemFlags(ItemFlag.HIDE_ADDITIONAL_TOOLTIP); + + meta.lore(List.of( + Component.text( + "Captures and transports mobs.", + NamedTextColor.GREEN + ), + Component.text( + "Right-click a mob with cage to capture it.", + NamedTextColor.GRAY + ), + Component.text( + "" + ), + Component.text( + "NOTE: Cages cannot be placed like blocks.", + NamedTextColor.RED + ) + )); + + meta.getPersistentDataContainer().set( + Keys.MOB_CAGE, + PersistentDataType.BYTE, + (byte) 1 + ); + + meta.getPersistentDataContainer().set( + Keys.CAGE_STATE, + PersistentDataType.BYTE, + STATE_EMPTY + ); + + item.setItemMeta(meta); + + return item; + } + + /** + * Returns true if this ItemStack is a Mob Cage. + */ + public static boolean isMobCage(ItemStack item) { + + if (item == null) + return false; + + if (!item.hasItemMeta()) + return false; + + return item.getItemMeta() + .getPersistentDataContainer() + .has(Keys.MOB_CAGE, PersistentDataType.BYTE); + } + + public static boolean isEmpty(ItemStack item) { + + if (!isMobCage(item)) + return false; + + Byte state = item.getItemMeta() + .getPersistentDataContainer() + .get(Keys.CAGE_STATE, PersistentDataType.BYTE); + + return state != null && state == STATE_EMPTY; + } + + public static boolean isFilled(ItemStack item) { + + if (!isMobCage(item)) + return false; + + Byte state = item.getItemMeta() + .getPersistentDataContainer() + .get(Keys.CAGE_STATE, PersistentDataType.BYTE); + + return state != null && state == STATE_FILLED; + } + + public static void fill(ItemStack item, LivingEntity entity) { + + ItemMeta meta = item.getItemMeta(); + + meta.getPersistentDataContainer().set( + Keys.CAGE_STATE, + PersistentDataType.BYTE, + STATE_FILLED + ); + + List lore = new ArrayList<>(); + + lore.addAll( + MobDescriptionFormatter.getAdditionalLore(entity) + ); + + lore.add(Component.empty()); + + lore.add(Component.text( + "Right-click the ground to release.", + NamedTextColor.GRAY + )); + + meta.lore(lore); + + meta.getPersistentDataContainer().set( + Keys.DATA_VERSION, + PersistentDataType.INTEGER, + 1 + ); + + item.setItemMeta(meta); + + } + + public static ItemStack createFilled(LivingEntity entity) { + + ItemStack cage = createEmpty(); + + fill(cage, entity); + + return cage; + } + +} \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/item/CageRecipe.java b/src/main/java/com/rsscripting/mobcage/item/CageRecipe.java new file mode 100644 index 0000000..a6eed80 --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/item/CageRecipe.java @@ -0,0 +1,30 @@ +package com.rsscripting.mobcage.item; + +import com.rsscripting.mobcage.MobCage; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ShapedRecipe; + +public final class CageRecipe { + + private CageRecipe() { + } + + public static void register(MobCage plugin) { + + ShapedRecipe recipe = new ShapedRecipe( + new NamespacedKey(plugin, "mob_cage"), + CageItem.createEmpty()); + + recipe.shape( + "BBB", + "BIB", + "BBB" + ); + + recipe.setIngredient('B', Material.IRON_BARS); + recipe.setIngredient('I', Material.IRON_INGOT); + + plugin.getServer().addRecipe(recipe); + } +} \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/listener/MobCageListener.java b/src/main/java/com/rsscripting/mobcage/listener/MobCageListener.java new file mode 100644 index 0000000..e72d177 --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/listener/MobCageListener.java @@ -0,0 +1,117 @@ +package com.rsscripting.mobcage.listener; + +import com.rsscripting.mobcage.item.CageItem; +import com.rsscripting.mobcage.description.CaptureResult; +import com.rsscripting.mobcage.description.descriptor.MobDescriptor; +import com.rsscripting.mobcage.description.descriptor.MobDescriptorRegistry; +import com.rsscripting.mobcage.description.MobDescriptionFormatter; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.player.PlayerInteractEntityEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +public class MobCageListener implements Listener { + + + @EventHandler(ignoreCancelled = true) + public void onBlockPlace(BlockPlaceEvent event) { + + if (!CageItem.isMobCage(event.getItemInHand())) + return; + + event.setCancelled(true); + } + + @EventHandler(ignoreCancelled = true) + public void onPlayerInteractEntity(PlayerInteractEntityEvent event) { + + if (event.getHand() != EquipmentSlot.HAND) + return; + + Player player = event.getPlayer(); + + ItemStack item = player.getInventory().getItemInMainHand(); + + if (!CageItem.isMobCage(item)) + return; + + if (!CageItem.isEmpty(item)) + return; + + if (!(event.getRightClicked() instanceof LivingEntity livingEntity)) + return; + + if (livingEntity instanceof Player) { + player.sendMessage("You cannot capture players."); + return; + } + + if (livingEntity.customName() != null) { + player.sendMessage("This mob cannot be captured."); + return; + } + + MobDescriptor descriptor = MobDescriptorRegistry.get(livingEntity); + + if (descriptor == null) { + + player.sendRichMessage( + "The magic within this Mob Cage cannot contain a " + + MobDescriptionFormatter.formatEnumName(livingEntity.getType().name()) + + "." + ); + + event.setCancelled(true); + return; + } + + CaptureResult result = descriptor.beforeCapture(livingEntity, player); + + if (!result.isAllowed()) { + + if (result.getMessage() != null) { + player.sendRichMessage(result.getMessage()); + } + + event.setCancelled(true); + return; + } + + ItemStack held = player.getInventory().getItemInMainHand(); + ItemStack filled = CageItem.createFilled(livingEntity); + + if (held.getAmount() == 1) { + + // Replace the last empty cage + player.getInventory().setItem( + player.getInventory().getHeldItemSlot(), + filled + ); + + } else { + + // Consume one empty cage + held.setAmount(held.getAmount() - 1); + + // Give one filled cage + var leftover = player.getInventory().addItem(filled); + + // Inventory full? Drop it on the ground. + leftover.values().forEach(stack -> + player.getWorld().dropItemNaturally(player.getLocation(), stack) + ); + } + + livingEntity.remove(); + + player.updateInventory(); + + event.setCancelled(true); + + } + +} \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/listener/MobReleaseListener.java b/src/main/java/com/rsscripting/mobcage/listener/MobReleaseListener.java new file mode 100644 index 0000000..24d52b2 --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/listener/MobReleaseListener.java @@ -0,0 +1,132 @@ +package com.rsscripting.mobcage.listener; + +import com.rsscripting.mobcage.description.descriptor.*; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.bukkit.Location; +import org.bukkit.inventory.meta.ItemMeta; +import com.rsscripting.mobcage.item.CageItem; +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.ItemStack; + +public class MobReleaseListener implements Listener { + + @EventHandler(ignoreCancelled = true) + public void onPlayerInteract(PlayerInteractEvent event) { + + ItemStack item = event.getItem(); + + if (item == null) + return; + + if (event.getAction() != Action.RIGHT_CLICK_BLOCK) + return; + + if (!CageItem.isMobCage(item)) + return; + + if (!CageItem.isFilled(item)) + return; + + event.setCancelled(true); + + ItemMeta meta = item.getItemMeta(); + + if (meta.lore() == null) + return; + + var lore = meta.lore(); + + PlainTextComponentSerializer plain = PlainTextComponentSerializer.plainText(); + + String contains = null; + String variant = null; + + for (int i = 0; i < lore.size(); i++) { + + String line = plain.serialize(lore.get(i)); + + if (line.equals("Contains:") && i + 1 < lore.size()) { + contains = plain.serialize(lore.get(i + 1)); + } + + if (line.equals("Variant:") && i + 1 < lore.size()) { + variant = plain.serialize(lore.get(i + 1)); + } + } + + Location location = event.getClickedBlock().getLocation().add(0.5, 1, 0.5); + + switch (contains) { + + case "Bee", "Baby Bee" -> + new BeeDescriptor().release(location, lore); + + case "Cave Spider" -> + new CaveSpiderDescriptor().release(location, lore); + + case "Chicken", "Baby Chicken" -> + new ChickenDescriptor().release(location, lore); + + case "Cow", "Baby Cow" -> + new CowDescriptor().release(location, lore); + + case "Horse", "Baby Horse" -> + new HorseDescriptor().release(location, lore); + + case "Pig", "Baby Pig" -> + new PigDescriptor().release(location, lore); + + case "Sheep", "Baby Sheep" -> + new SheepDescriptor().release(location, lore); + + case "Spider" -> + new SpiderDescriptor().release(location, lore); + + case "Villager", "Baby Villager" -> + new VillagerDescriptor().release(location, lore); + + case "Zombie", "Baby Zombie" -> + new ZombieDescriptor().release(location, lore); + + default -> { + event.getPlayer().sendMessage("Unknown mob type."); + return; + } + } + + ItemStack held = event.getItem(); + + if (held.getAmount() == 1) { + + // Replace the last filled cage + event.getPlayer().getInventory().setItem( + event.getPlayer().getInventory().getHeldItemSlot(), + CageItem.createEmpty() + ); + + } else { + + // Consume one filled cage + held.setAmount(held.getAmount() - 1); + + // Give one empty cage + var leftover = event.getPlayer().getInventory().addItem(CageItem.createEmpty()); + + // Inventory full? Drop it. + leftover.values().forEach(stack -> + event.getPlayer().getWorld().dropItemNaturally( + event.getPlayer().getLocation(), + stack + ) + ); + } + + event.getPlayer().updateInventory(); + + event.getPlayer().sendMessage("You have freed the cage's occupant!"); + + } +} \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/util/InteractionUtil.java b/src/main/java/com/rsscripting/mobcage/util/InteractionUtil.java new file mode 100644 index 0000000..43cc34c --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/util/InteractionUtil.java @@ -0,0 +1,36 @@ +package com.rsscripting.mobcage.util; + +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.data.Openable; +import org.bukkit.block.data.Powerable; +import org.bukkit.block.data.type.Switch; +import org.bukkit.inventory.InventoryHolder; + +public final class InteractionUtil { + + private InteractionUtil() { + } + + public static boolean isInteractable(Block block) { + + if (block == null) + return false; + + BlockState state = block.getState(); + + if (state instanceof InventoryHolder) + return true; + + if (block.getBlockData() instanceof Openable) + return true; + + if (block.getBlockData() instanceof Switch) + return true; + + if (block.getBlockData() instanceof Powerable) + return true; + + return false; + } +} \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/util/Keys.java b/src/main/java/com/rsscripting/mobcage/util/Keys.java new file mode 100644 index 0000000..d11c6a2 --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/util/Keys.java @@ -0,0 +1,21 @@ +package com.rsscripting.mobcage.util; + +import com.rsscripting.mobcage.MobCage; +import org.bukkit.NamespacedKey; + +public final class Keys { + + public static NamespacedKey MOB_CAGE; + public static NamespacedKey DATA_VERSION; + public static NamespacedKey CAGE_STATE; + + private Keys() { + } + + public static void initialize(MobCage plugin) { + MOB_CAGE = new NamespacedKey(plugin, "mob_cage"); + DATA_VERSION = new NamespacedKey(plugin, "data_version"); + CAGE_STATE = new NamespacedKey(plugin, "cage_state"); + + } +} \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/util/MobAgeUtil.java b/src/main/java/com/rsscripting/mobcage/util/MobAgeUtil.java new file mode 100644 index 0000000..a03360a --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/util/MobAgeUtil.java @@ -0,0 +1,44 @@ +package com.rsscripting.mobcage.util; + +import org.bukkit.entity.Ageable; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Zombie; + +public final class MobAgeUtil { + + private MobAgeUtil() { + } + + public static String getAgePrefix(LivingEntity entity) { + + if (entity instanceof Ageable ageable && !ageable.isAdult()) { + return "Baby "; + } + + if (entity instanceof Zombie zombie && !zombie.isAdult()) { + return "Baby "; + } + + return ""; + } + + public static void normalize(LivingEntity entity) { + + // Normalize age + if (entity instanceof Ageable ageable) { + if (ageable.isAdult()) { + ageable.setAdult(); + } else { + ageable.setBaby(); + } + } + + // Normalize runtime state + entity.setTicksLived(1); + entity.setFireTicks(0); + entity.setFreezeTicks(0); + entity.setFallDistance(0); + entity.setRemainingAir(entity.getMaximumAir()); + } + +} \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/util/MobEquipmentUtil.java b/src/main/java/com/rsscripting/mobcage/util/MobEquipmentUtil.java new file mode 100644 index 0000000..240db24 --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/util/MobEquipmentUtil.java @@ -0,0 +1,77 @@ +package com.rsscripting.mobcage.util; + +import com.rsscripting.mobcage.MobCage; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.ItemStack; + +public class MobEquipmentUtil { + + public static void handleEquipmentOnCapture( + MobCage plugin, + LivingEntity entity, + Player player + ) { + + EntityEquipment equipment = entity.getEquipment(); + + if (equipment == null) { + return; + } + + boolean hasEquipment = + (equipment.getHelmet() != null && !equipment.getHelmet().getType().isAir()) || + (equipment.getChestplate() != null && !equipment.getChestplate().getType().isAir()) || + (equipment.getLeggings() != null && !equipment.getLeggings().getType().isAir()) || + (equipment.getBoots() != null && !equipment.getBoots().getType().isAir()) || + (equipment.getItemInMainHand() != null && !equipment.getItemInMainHand().getType().isAir()) || + (equipment.getItemInOffHand() != null && !equipment.getItemInOffHand().getType().isAir()); + + if (!hasEquipment) { + return; + } + + boolean dropEquipment = + plugin.getConfig().getBoolean("drop-equip-on-capture"); + + String message = + plugin.getConfig().getString("equipment-drop-message", ""); + + if (dropEquipment) { + + dropItem(entity, equipment.getHelmet()); + dropItem(entity, equipment.getChestplate()); + dropItem(entity, equipment.getLeggings()); + dropItem(entity, equipment.getBoots()); + dropItem(entity, equipment.getItemInMainHand()); + dropItem(entity, equipment.getItemInOffHand()); + + } + + equipment.setHelmet(null); + equipment.setChestplate(null); + equipment.setLeggings(null); + equipment.setBoots(null); + equipment.setItemInMainHand(null); + equipment.setItemInOffHand(null); + + if (!message.isBlank()) { + player.sendRichMessage(message); + } + + } + + private static void dropItem(LivingEntity entity, ItemStack item) { + + if (item == null || item.getType().isAir()) { + return; + } + + entity.getWorld().dropItemNaturally( + entity.getLocation(), + item.clone() + ); + } + +} \ No newline at end of file diff --git a/src/main/java/com/rsscripting/mobcage/util/MobHealthUtil.java b/src/main/java/com/rsscripting/mobcage/util/MobHealthUtil.java new file mode 100644 index 0000000..b0d0a56 --- /dev/null +++ b/src/main/java/com/rsscripting/mobcage/util/MobHealthUtil.java @@ -0,0 +1,31 @@ +package com.rsscripting.mobcage.util; + +import com.rsscripting.mobcage.description.CaptureResult; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.entity.LivingEntity; + +public final class MobHealthUtil { + + private MobHealthUtil() { + } + + public static CaptureResult validateFullHealth(LivingEntity entity) { + + AttributeInstance attribute = + entity.getAttribute(Attribute.MAX_HEALTH); + + if (attribute == null) { + return CaptureResult.allowed(); + } + + if (entity.getHealth() < attribute.getValue()) { + + return CaptureResult.denied( + "This mob must be fully healed before it can be safely contained." + ); + } + + return CaptureResult.allowed(); + } +} \ No newline at end of file diff --git a/src/main/resources/Versions.yml b/src/main/resources/Versions.yml new file mode 100644 index 0000000..e1f38ba --- /dev/null +++ b/src/main/resources/Versions.yml @@ -0,0 +1,20 @@ +## 0.1.0 + Beginning of Project (Empty Project) + +## 0.1.1 + Create MobCage with recipe finished. + Cage can be crafted + Iron Bars | Iron Bars | Iron Bars + Iron Bars | Iron Ingot | Iron Bars + Iron Bars | Iron Bars | Iron Bars + Cage cannot be placed. + +## 0.1.2 + Mob capture complete. + Named mobs cannot be captured. + Players cannot be captured. + Capture Mob Logic Finished + Release Mob Logic Finished + +## 0.1.3 + Added Horse \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..1b64bc7 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,22 @@ +############################################################ +# MobCage Configuration +############################################################ + +############################################################ +# Mob Equipment +############################################################ + +## MobCage captures creatures, not their equipment. +## +## true = Equipped items are dropped on the ground when the +## mob is captured. +## +## false = Equipped items are destroyed when the mob is +## captured. +drop-equip-on-capture: true + +## +## Message shown when equipment is dropped. +## Leave blank to disable. +## +equipment-drop-message: "The mob struggled during capture and dropped its equipment." \ No newline at end of file diff --git a/src/main/resources/list.txt b/src/main/resources/list.txt new file mode 100644 index 0000000..cd8b0ee --- /dev/null +++ b/src/main/resources/list.txt @@ -0,0 +1,30 @@ +Creeper +Enderman +Blaze +Shulker +Villager ✅ +Bee ✅ +Cave Spider +Spider +Zombie +Skeleton +Witch +Piglin +Hoglin + +Cow ✅ +Chicken✅ +Pig✅ +Sheep✅ +Cod +Salmon +Pufferfish +Squid +Glow Squid +Turtle +Goat +Camel +Horse +Llama +Donkey/Mule +Wolf Cat \ 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..e95f0ae --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,6 @@ +name: MobCage +version: '${version}' + +main: com.rsscripting.mobcage.MobCage +api-version: '1.21.11' +load: POSTWORLD