Some refactoring
All checks were successful
/ build (push) Successful in 34s

This commit is contained in:
Minecon724 2025-05-27 15:55:18 +02:00
commit 89301375c8
Signed by: Minecon724
GPG key ID: A02E6E67AB961189
20 changed files with 980 additions and 325 deletions

View file

@ -0,0 +1,22 @@
on: [push]
jobs:
build:
runs-on: docker
container: eclipse-temurin:21-alpine
steps:
- name: Install build dependencies
run: apk add nodejs # GitHub/actions require Node.js can you believe it
- name: Checkout
uses: https://github.com/actions/checkout@v4
- name: Generate test keystore
run: keytool -keystore keystore.jks -storepass 123456 -keyalg rsa -genkey -alias mykey -dname "CN=Testing key"
- name: Build
run: ./mvnw package
- name: Upload artifacts
uses: https://github.com/actions/upload-artifact@v3
with:
path: target/giants-*.jar

13
.idea/giants.iml generated
View file

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

2
.idea/modules.xml generated
View file

@ -2,7 +2,7 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/giants.iml" filepath="$PROJECT_DIR$/.idea/giants.iml" />
<module fileurl="file://$PROJECT_DIR$/giants.iml" filepath="$PROJECT_DIR$/giants.iml" />
</modules>
</component>
</project>

19
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View file

@ -0,0 +1,19 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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
#
# http://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.
wrapperVersion=3.3.2
distributionType=only-script
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip

259
mvnw vendored Executable file
View file

@ -0,0 +1,259 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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
#
# http://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.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.3.2
#
# Optional ENV vars
# -----------------
# JAVA_HOME - location of a JDK home dir, required when download maven via java source
# MVNW_REPOURL - repo url base for downloading maven distribution
# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output
# ----------------------------------------------------------------------------
set -euf
[ "${MVNW_VERBOSE-}" != debug ] || set -x
# OS specific support.
native_path() { printf %s\\n "$1"; }
case "$(uname)" in
CYGWIN* | MINGW*)
[ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")"
native_path() { cygpath --path --windows "$1"; }
;;
esac
# set JAVACMD and JAVACCMD
set_java_home() {
# For Cygwin and MinGW, ensure paths are in Unix format before anything is touched
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"
JAVACCMD="$JAVA_HOME/jre/sh/javac"
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACCMD="$JAVA_HOME/bin/javac"
if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then
echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2
echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2
return 1
fi
fi
else
JAVACMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v java
)" || :
JAVACCMD="$(
'set' +e
'unset' -f command 2>/dev/null
'command' -v javac
)" || :
if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then
echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2
return 1
fi
fi
}
# hash string like Java String::hashCode
hash_string() {
str="${1:-}" h=0
while [ -n "$str" ]; do
char="${str%"${str#?}"}"
h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296))
str="${str#?}"
done
printf %x\\n $h
}
verbose() { :; }
[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; }
die() {
printf %s\\n "$1" >&2
exit 1
}
trim() {
# MWRAPPER-139:
# Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds.
# Needed for removing poorly interpreted newline sequences when running in more
# exotic environments such as mingw bash on Windows.
printf "%s" "${1}" | tr -d '[:space:]'
}
# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties
while IFS="=" read -r key value; do
case "${key-}" in
distributionUrl) distributionUrl=$(trim "${value-}") ;;
distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;;
esac
done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties"
[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties"
case "${distributionUrl##*/}" in
maven-mvnd-*bin.*)
MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/
case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in
*AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;;
:Darwin*x86_64) distributionPlatform=darwin-amd64 ;;
:Darwin*arm64) distributionPlatform=darwin-aarch64 ;;
:Linux*x86_64*) distributionPlatform=linux-amd64 ;;
*)
echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2
distributionPlatform=linux-amd64
;;
esac
distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip"
;;
maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;;
*) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;;
esac
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}"
distributionUrlName="${distributionUrl##*/}"
distributionUrlNameMain="${distributionUrlName%.*}"
distributionUrlNameMain="${distributionUrlNameMain%-bin}"
MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}"
MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")"
exec_maven() {
unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || :
exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD"
}
if [ -d "$MAVEN_HOME" ]; then
verbose "found existing MAVEN_HOME at $MAVEN_HOME"
exec_maven "$@"
fi
case "${distributionUrl-}" in
*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;;
*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;;
esac
# prepare tmp dir
if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then
clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; }
trap clean HUP INT TERM EXIT
else
die "cannot create temp dir"
fi
mkdir -p -- "${MAVEN_HOME%/*}"
# Download and Install Apache Maven
verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
verbose "Downloading from: $distributionUrl"
verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
# select .zip or .tar.gz
if ! command -v unzip >/dev/null; then
distributionUrl="${distributionUrl%.zip}.tar.gz"
distributionUrlName="${distributionUrl##*/}"
fi
# verbose opt
__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR=''
[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v
# normalize http auth
case "${MVNW_PASSWORD:+has-password}" in
'') MVNW_USERNAME='' MVNW_PASSWORD='' ;;
has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;;
esac
if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then
verbose "Found wget ... using wget"
wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl"
elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then
verbose "Found curl ... using curl"
curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl"
elif set_java_home; then
verbose "Falling back to use Java to download"
javaSource="$TMP_DOWNLOAD_DIR/Downloader.java"
targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName"
cat >"$javaSource" <<-END
public class Downloader extends java.net.Authenticator
{
protected java.net.PasswordAuthentication getPasswordAuthentication()
{
return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() );
}
public static void main( String[] args ) throws Exception
{
setDefault( new Downloader() );
java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() );
}
}
END
# For Cygwin/MinGW, switch paths to Windows format before running javac and java
verbose " - Compiling Downloader.java ..."
"$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java"
verbose " - Running Downloader.java ..."
"$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")"
fi
# If specified, validate the SHA-256 sum of the Maven distribution zip file
if [ -n "${distributionSha256Sum-}" ]; then
distributionSha256Result=false
if [ "$MVN_CMD" = mvnd.sh ]; then
echo "Checksum validation is not supported for maven-mvnd." >&2
echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
elif command -v sha256sum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
elif command -v shasum >/dev/null; then
if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then
distributionSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
if [ $distributionSha256Result = false ]; then
echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2
echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2
exit 1
fi
fi
# unzip and move
if command -v unzip >/dev/null; then
unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip"
else
tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar"
fi
printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url"
mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME"
clean || :
exec_maven "$@"

149
mvnw.cmd vendored Normal file
View file

@ -0,0 +1,149 @@
<# : batch portion
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.3.2
@REM
@REM Optional ENV vars
@REM MVNW_REPOURL - repo url base for downloading maven distribution
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------
@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0)
@SET __MVNW_CMD__=
@SET __MVNW_ERROR__=
@SET __MVNW_PSMODULEP_SAVE=%PSModulePath%
@SET PSModulePath=
@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @(
IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B)
)
@SET PSModulePath=%__MVNW_PSMODULEP_SAVE%
@SET __MVNW_PSMODULEP_SAVE=
@SET __MVNW_ARG0_NAME__=
@SET MVNW_USERNAME=
@SET MVNW_PASSWORD=
@IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*)
@echo Cannot start maven from wrapper >&2 && exit /b 1
@GOTO :EOF
: end batch / begin powershell #>
$ErrorActionPreference = "Stop"
if ($env:MVNW_VERBOSE -eq "true") {
$VerbosePreference = "Continue"
}
# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties
$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl
if (!$distributionUrl) {
Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties"
}
switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) {
"maven-mvnd-*" {
$USE_MVND = $true
$distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip"
$MVN_CMD = "mvnd.cmd"
break
}
default {
$USE_MVND = $false
$MVN_CMD = $script -replace '^mvnw','mvn'
break
}
}
# apply MVNW_REPOURL and calculate MAVEN_HOME
# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-<version>,maven-mvnd-<version>-<platform>}/<hash>
if ($env:MVNW_REPOURL) {
$MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" }
$distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')"
}
$distributionUrlName = $distributionUrl -replace '^.*/',''
$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$',''
$MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain"
if ($env:MAVEN_USER_HOME) {
$MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain"
}
$MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join ''
$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME"
if (Test-Path -Path "$MAVEN_HOME" -PathType Container) {
Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME"
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"
exit $?
}
if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) {
Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl"
}
# prepare tmp dir
$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile
$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir"
$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null
trap {
if ($TMP_DOWNLOAD_DIR.Exists) {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
}
New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null
# Download and Install Apache Maven
Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..."
Write-Verbose "Downloading from: $distributionUrl"
Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName"
$webclient = New-Object System.Net.WebClient
if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) {
$webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD)
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null
# If specified, validate the SHA-256 sum of the Maven distribution zip file
$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum
if ($distributionSha256Sum) {
if ($USE_MVND) {
Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties."
}
Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash
if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) {
Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property."
}
}
# unzip and move
Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null
Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null
try {
Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null
} catch {
if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) {
Write-Error "fail to move MAVEN_HOME"
}
} finally {
try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null }
catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" }
}
Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD"

18
pom.xml
View file

@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>eu.m724</groupId>
<artifactId>giants</artifactId>
<version>2.0.12-SNAPSHOT</version>
<version>2.1.0-SNAPSHOT</version>
<properties>
<maven.compiler.release>21</maven.compiler.release>
@ -30,21 +30,6 @@
<artifactId>spigot-api</artifactId>
<version>1.16.5-R0.1-SNAPSHOT</version>
<scope>provided</scope>
<!-- fix vulnerabilities complaints -->
<exclusions>
<exclusion>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
<exclusion>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
@ -53,7 +38,6 @@
<version>0.1.2</version>
</dependency>
<dependency>
<groupId>eu.m724</groupId>
<artifactId>jarupdater</artifactId>

View file

@ -0,0 +1,71 @@
package eu.m724.giants;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class DebugLogger {
private DebugLogger() {}
static Logger logger;
public static void info(String message, Object... format) {
log(Level.INFO, message, format);
}
public static void warning(String message, Object... format) {
log(Level.WARNING, message, format);
}
public static void severe(String message, Object... format) {
log(Level.SEVERE, message, format);
}
public static void fine(String message, Object... format) {
log(Level.FINE, message, format);
}
public static void finer(String message, Object... format) {
log(Level.FINER, message, format);
}
private static void log(Level level, String message, Object... format) {
if (!logger.isLoggable(level)) {
return;
}
message = message.formatted(format);
if (logger.getLevel().intValue() <= Level.FINE.intValue()) {
message = "[" + getCaller() + "] " + message;
}
if (level.intValue() < Level.INFO.intValue()) { // levels below info are never logged even if set for some reason
// colors text gray (cyan is close to gray)
if (level == Level.FINE) {
message = "\033[38;5;250m" + message + "\033[39m";
} else {
message = "\033[38;5;245m" + message + "\033[39m";
}
level = Level.INFO;
}
logger.log(level, message);
}
private static String getCaller() {
String caller = Thread.currentThread().getStackTrace()[4].getClassName();
if (caller.startsWith("eu.m724.giants.")) {
caller = caller.substring(15);
String[] packages = caller.split("\\.");
caller = IntStream.range(0, packages.length - 1)
.mapToObj(i -> packages[i].substring(0, 2))
.collect(Collectors.joining(".")) + "." + packages[packages.length - 1];
}
return caller;
}
}

View file

@ -5,14 +5,27 @@ import org.bukkit.inventory.ItemStack;
import java.util.concurrent.ThreadLocalRandom;
public record Drop(ItemStack itemStack, int min, int max, double chance) {
/**
* Represents an item drop.
*
* @param itemStack the item to drop
* @param min Minimum quantity to drop, inclusive
* @param max Maximum quantity to drop, inclusive
* @param chance The chance that the item will be dropped (doesn't affect quantity)
*/
public record Drop(
ItemStack itemStack,
int min,
int max,
double chance
) {
/**
* Randomizes quantity and returns {@link ItemStack}.<br>
* This should be called every drop.
*
* @return A {@link ItemStack} with randomized quantity
*/
private ItemStack generate() {
private ItemStack generateItemStack() {
int amount = ThreadLocalRandom.current().nextInt(min, max + 1);
ItemStack itemStack = this.itemStack.clone();
@ -21,13 +34,13 @@ public record Drop(ItemStack itemStack, int min, int max, double chance) {
}
/**
* Drops the item at {@code location} taking into account quantity and chance.
* Drops the item at the specified location. Quantity and chance are used.
*
* @param location The location to drop the drop at
*/
public void dropAt(Location location) {
if (chance > ThreadLocalRandom.current().nextDouble()) {
ItemStack itemStack = generate();
if (chance > ThreadLocalRandom.current().nextDouble()) { // TODO faster random here as well?
ItemStack itemStack = generateItemStack();
location.getWorld().dropItemNaturally(location, itemStack);
}
}

View file

@ -50,6 +50,8 @@ public class GiantsCommand implements CommandExecutor {
updateCommand.updateCommand(sender, args);
else
sender.sendMessage(ChatColor.GRAY + "Updater is disabled");
} else {
sender.sendMessage(ChatColor.RED + "No such command: " + action);
}
return true;

View file

@ -1,6 +1,6 @@
package eu.m724.giants;
import eu.m724.giants.ai.GiantProcessor;
import eu.m724.giants.ai.*;
import eu.m724.giants.configuration.Configuration;
import eu.m724.giants.updater.PluginUpdater;
import eu.m724.giants.updater.UpdateCommand;
@ -12,27 +12,43 @@ import org.bukkit.command.CommandExecutor;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
// TODO unmess this
public class GiantsPlugin extends MStatsPlugin implements CommandExecutor {
private static GiantsPlugin INSTANCE;
private final File configFile = new File(getDataFolder(), "config.yml");
private Configuration configuration;
private GiantProcessor giantProcessor;
private GiantSpawnTools giantSpawnTools;
@Override
public void onEnable() {
long start = System.nanoTime();
INSTANCE = this;
if (!configFile.exists()) {
saveResource("config.yml", false);
}
this.configuration = Configuration.load(this, configFile);
this.giantProcessor = new GiantProcessor(this, configuration);
this.configuration = Configuration.load(configFile);
giantProcessor.start();
getLogger().setLevel(configuration.debug() ? Level.FINEST : Level.INFO);
DebugLogger.logger = getLogger();
GiantAndZombieTracker tracker = new GiantAndZombieTracker();
this.giantSpawnTools = new GiantSpawnTools(this, configuration.potionEffects(), tracker);
if (configuration.aiEnabled()) {
new GiantTicker(
new GiantJumper(configuration.jumpMode(), configuration.jumpDelay(), configuration.jumpCondition(), configuration.jumpHeight()),
tracker, configuration.attackReach(), configuration.attackDamage()
).runTaskTimer(this, 0, configuration.attackDelay());
}
getServer().getPluginManager().registerEvents(new GiantEventListener(giantSpawnTools, tracker, configuration.drops(), configuration.spawnChance(), configuration.burningHead()), this);
PluginUpdater updater = null;
UpdateCommand updateCommand = null;
@ -64,6 +80,8 @@ public class GiantsPlugin extends MStatsPlugin implements CommandExecutor {
getCommand("giants").setExecutor(new GiantsCommand(this, updateCommand));
mStats(3);
DebugLogger.fine("Took %.3f milliseconds", (System.nanoTime() - start) / 1000000.0);
}
public static GiantsPlugin getInstance() {
@ -74,6 +92,10 @@ public class GiantsPlugin extends MStatsPlugin implements CommandExecutor {
return configuration;
}
public GiantSpawnTools getGiantSpawnTools() {
return giantSpawnTools;
}
/**
* Checks if a giant can be spawned at a location<br>
* The check is very approximate, but works for most scenarios
@ -82,6 +104,6 @@ public class GiantsPlugin extends MStatsPlugin implements CommandExecutor {
* @return Whether a giant can be spawned
*/
public boolean isSpawnableAt(Location location) {
return giantProcessor.isSpawnableAt(location);
return giantSpawnTools.isSpawnableAt(location);
}
}

View file

@ -0,0 +1,47 @@
package eu.m724.giants.ai;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import org.bukkit.entity.Giant;
import org.bukkit.entity.Zombie;
import org.bukkit.event.Listener;
import java.util.*;
public class GiantAndZombieTracker implements Listener {
private final BiMap<Giant, Zombie> giantZombieMap = HashBiMap.create();
public Set<Zombie> getZombies() {
// Shallow copy; copies references, efficient.
return new HashSet<>(giantZombieMap.values());
}
public Set<Giant> getGiants() {
// Shallow copy; copies references, efficient.
return new HashSet<>(giantZombieMap.keySet());
}
void add(Giant giant, Zombie zombie) {
giantZombieMap.put(giant, zombie);
}
void remove(Giant giant) {
giantZombieMap.remove(giant);
}
void remove(Zombie zombie) {
giantZombieMap.inverse().remove(zombie);
}
Zombie getZombieOf(Giant giant) {
return giantZombieMap.get(giant);
}
Giant getGiantOf(Zombie zombie) {
return giantZombieMap.inverse().get(zombie);
}
Zombie removeAndGetZombieOf(Giant giant) {
return giantZombieMap.remove(giant);
}
}

View file

@ -0,0 +1,151 @@
package eu.m724.giants.ai;
import eu.m724.giants.DebugLogger;
import eu.m724.giants.Drop;
import org.bukkit.entity.*;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.*;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.event.world.ChunkUnloadEvent;
import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;
public class GiantEventListener implements Listener {
private final GiantSpawnTools spawnTools;
private final GiantAndZombieTracker tracker;
private final Drop[] drops;
private final double spawnChance;
private final boolean burningHead;
public GiantEventListener(GiantSpawnTools spawnTools, GiantAndZombieTracker tracker, Drop[] drops, double spawnChance, boolean burningHead) {
this.spawnTools = spawnTools;
this.tracker = tracker;
this.drops = drops;
this.spawnChance = spawnChance;
this.burningHead = burningHead;
}
@EventHandler
public void onChunkLoad(ChunkLoadEvent event) {
DebugLogger.finer("Chunk %d, %d loaded, checking for Giants", event.getChunk().getX(), event.getChunk().getZ());
Entity[] entities = event.getChunk().getEntities();
Arrays.stream(entities)
.filter(spawnTools::isValidLegacyPassengerHusk)
.forEach(Entity::remove); // Legacy husks removed
Arrays.stream(entities)
.filter(spawnTools::isValidPassengerZombie)
.forEach(Entity::remove); // Will be respawned just below, if needed
Arrays.stream(entities)
.filter(entity -> entity.getType() == EntityType.GIANT)
.map(Giant.class::cast)
.forEach(spawnTools::applyGiantsLogic); // Respawn Zombie. Effects can be reapplied
}
@EventHandler
public void onChunkUnload(ChunkUnloadEvent event) {
Entity[] entities = event.getChunk().getEntities();
// Zombies are supposed to be ephemeral
Arrays.stream(entities)
.filter(spawnTools::isValidPassengerZombie)
.map(Zombie.class::cast)
.forEach(Entity::remove);
}
@EventHandler(priority = EventPriority.MONITOR)
public void entitySpawn(EntitySpawnEvent e) {
if (e.isCancelled()) return;
if (e.getEntity() instanceof Giant giant) {
DebugLogger.fine("Giant #%d just spawned at %s", giant.getEntityId(), giant.getLocation().toString());
spawnTools.applyGiantsLogic(giant);
}
if (e.getEntityType() == EntityType.ZOMBIE) {
DebugLogger.finer("Zombie spawned at %s", e.getLocation().toString());
if (spawnChance > ThreadLocalRandom.current().nextDouble()) { // TODO faster random
if (spawnTools.isSpawnableAt(e.getLocation())) {
DebugLogger.fine("Spawning a Giant at %s due to chance", e.getLocation().toString());
e.getLocation().getWorld().spawnEntity(e.getLocation(), EntityType.GIANT);
e.setCancelled(true);
}
}
}
}
@EventHandler
public void entityDeath(EntityDeathEvent e) {
LivingEntity entity = e.getEntity();
if (entity instanceof Giant giant) {
DebugLogger.fine("Giant #%d just died at %s", giant.getEntityId(), giant.getLocation().toString());
Zombie zombie = tracker.removeAndGetZombieOf(giant);
for (Drop drop : drops) {
DebugLogger.finer("Dropping: %s", drop.itemStack().toString());
drop.dropAt(giant.getLocation());
}
if (zombie != null) {
DebugLogger.finer("Removing Zombie #%d", zombie.getEntityId());
zombie.remove();
} else {
DebugLogger.finer("Giant #%d had no passenger Zombie!", giant.getEntityId());
}
}
// TODO can Zombie die?
}
@EventHandler
public void onEntityDamageByEntity(EntityDamageByEntityEvent event) {
if (!(event.getEntity() instanceof Giant giant)) return;
DebugLogger.finer("Giant #%d damaged", giant.getEntityId());
Zombie zombie = tracker.getZombieOf(giant);
if (zombie != null) {
DebugLogger.fine("Giant #%d damaged, damaging Zombie #%d", giant.getEntityId(), zombie.getEntityId());
// Make the Zombie target the attacker.
// TODO this seems to not work.
zombie.damage(0, event.getDamager());
} else {
DebugLogger.finer("Giant #%d has no passenger Zombie!", giant.getEntityId());
}
}
// TODO remove this debug event
@EventHandler
public void onEntityTarget(EntityTargetEvent event) {
DebugLogger.fine("%s targeted %s", event.getEntity().toString(), event.getTarget());
}
@EventHandler
public void onEntityBurn(EntityCombustEvent event) {
if (burningHead) return;
if (spawnTools.isValidPassengerZombie(event.getEntity())) {
event.setCancelled(true);
}
}
@EventHandler
public void onEntityDamage(EntityDamageEvent event) {
if (spawnTools.isValidPassengerZombie(event.getEntity())) {
event.setCancelled(true);
}
}
}

View file

@ -1,7 +1,6 @@
package eu.m724.giants.ai;
import eu.m724.giants.GiantsPlugin;
import eu.m724.giants.configuration.Configuration;
import eu.m724.giants.DebugLogger;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.util.Vector;
@ -10,42 +9,73 @@ import java.util.HashMap;
import java.util.Map;
public class GiantJumper {
private final Configuration configuration = GiantsPlugin.getInstance().getConfiguration();
private final Map<Entity, Long> giantLastJump = new HashMap<>();
private final Map<Entity, Location> giantLocations = new HashMap<>();
private final int jumpMode; // this too
private final int jumpDelayMillis;
private final int jumpCondition; // TODO make enum?
private final double jumpHeight;
public GiantJumper(int jumpMode, int jumpDelayMillis, int jumpCondition, double jumpHeight) {
this.jumpMode = jumpMode;
this.jumpDelayMillis = jumpDelayMillis;
this.jumpCondition = jumpCondition;
this.jumpHeight = jumpHeight;
}
void tick(Entity giant, Entity target) {
if (jumpMode == 0) return;
if (target == null) return;
DebugLogger.finer("Attempting to make Giant #%d jump", giant.getEntityId());
// tracking location is only required for jumping
Location currentLocation = giant.getLocation();
Location previousLocation = giantLocations.put(giant, currentLocation);
if (previousLocation == null) {
previousLocation = currentLocation;
}
void processJump(Entity giant, Location prevLocation, Location location, Location targetLocation) {
long now = System.currentTimeMillis();
if (now - giantLastJump.getOrDefault(giant, 0L) < configuration.jumpDelay()) {
if (now - giantLastJump.getOrDefault(giant, 0L) < jumpDelayMillis) {
return;
}
if (giant.isOnGround()) {
giantLastJump.put(giant, now);
if (configuration.jumpCondition() == 0) {
if (targetLocation.subtract(location).getY() > 0) {
jump(giant);
}
} else if (configuration.jumpCondition() == 1) {
Location delta = prevLocation.subtract(location);
if (targetLocation.subtract(location).getY() > 0 && (delta.getX() == 0 || delta.getZ() == 0)) {
jump(giant);
}
} else if (configuration.jumpCondition() == 2) {
Location delta = prevLocation.subtract(location);
if (delta.getX() == 0 || delta.getZ() == 0) {
jump(giant);
}
} // I could probably simplify that code
switch (jumpCondition) {
case 0: {
if (target.getLocation().subtract(currentLocation).getY() > 0) {
makeJump(giant);
}
}
case 1: {
Location delta = previousLocation.subtract(currentLocation);
if (target.getLocation().subtract(currentLocation).getY() > 0 && (delta.getX() == 0 || delta.getZ() == 0)) {
makeJump(giant);
}
}
case 2: {
Location delta = previousLocation.subtract(currentLocation);
if (delta.getX() == 0 || delta.getZ() == 0) {
makeJump(giant);
}
}
}
}
}
private void jump(Entity giant) {
if (configuration.jumpMode() == 1) {
giant.setVelocity(new Vector(0, configuration.jumpHeight(), 0));
} else if (configuration.jumpMode() == 2) {
giant.teleport(giant.getLocation().add(0, configuration.jumpHeight(), 0));
private void makeJump(Entity giant) {
DebugLogger.finer("Yes, Giant #%d should jump", giant.getEntityId());
switch (jumpMode) {
case 1: {
giant.setVelocity(new Vector(0, jumpHeight, 0));
}
case 2: {
giant.teleport(giant.getLocation().add(0, jumpHeight, 0));
}
}
}
}

View file

@ -1,163 +0,0 @@
package eu.m724.giants.ai;
import eu.m724.giants.Drop;
import eu.m724.giants.configuration.Configuration;
import org.bukkit.Location;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.*;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDeathEvent;
import org.bukkit.event.entity.EntitySpawnEvent;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.logging.Level;
import java.util.logging.Logger;
// TODO move ai stuff to another class
/**
* A processor class that processes giants
*/
public class GiantProcessor implements Listener {
private static final int VERSION = 1;
private final JavaPlugin plugin;
final Configuration configuration;
private final Logger logger;
final Set<Husk> trackedHusks = new HashSet<>();
final Map<Entity, Location> giantLocationMap = new HashMap<>();
private final ThreadLocalRandom random = ThreadLocalRandom.current();
private final NamespacedKey huskKey;
public GiantProcessor(JavaPlugin plugin, Configuration configuration) {
this.plugin = plugin;
this.configuration = configuration;
this.logger = Logger.getLogger(plugin.getLogger().getName() + ".GiantProcessor");
logger.setLevel(Level.ALL);
this.huskKey = new NamespacedKey(plugin, "husk");
}
public void start() {
if (configuration.aiEnabled()) {
new GiantTicker(this).schedule(plugin);
}
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
/**
* The check is very approximate
*
* @param location the location
* @return whether a giant can be spawned here
*/
public boolean isSpawnableAt(Location location) {
for (int y=0; y<=12; y++) {
if (!location.clone().add(0, y, 0).getBlock().isEmpty()) // isPassable also seems good
return false;
}
return true;
}
@EventHandler
public void onChunkLoad(ChunkLoadEvent event) {
Entity[] entities = event.getChunk().getEntities();
logger.fine("Chunk loaded: " + event.getChunk().getX() + " " + event.getChunk().getZ());
Husk[] husks = Arrays.stream(entities)
.filter(entity -> entity instanceof Husk && entity.getPersistentDataContainer().has(huskKey, PersistentDataType.INTEGER))
.map(Husk.class::cast)
.toArray(Husk[]::new);
for (Husk husk : husks) {
logger.fine("Husk found at " + husk.getLocation());
Entity giant = husk.getVehicle();
if (giant instanceof Giant) {
trackedHusks.add(husk);
logger.fine("Tracking a loaded Giant at " + giant.getLocation());
} else {
// kill stray husks, that is those without a giant
husk.setHealth(0);
logger.fine("Stray Husk killed at " + husk.getLocation());
}
}
}
public void applyGiantsLogic(Giant giant) {
if (configuration.aiEnabled()) {
// The husk moves the giant. That's the magic.
LivingEntity passenger = (LivingEntity) giant.getWorld().spawnEntity(giant.getLocation(), EntityType.HUSK);
new PotionEffect(PotionEffectType.INVISIBILITY, Integer.MAX_VALUE, 1).apply(passenger);
passenger.setInvulnerable(true);
passenger.setPersistent(true);
passenger.getPersistentDataContainer().set(huskKey, PersistentDataType.INTEGER, VERSION);
giant.addPassenger(passenger);
trackedHusks.add((Husk) passenger);
}
configuration.potionEffects().forEach(giant::addPotionEffect);
}
@EventHandler
public void entitySpawn(EntitySpawnEvent e) {
if (e.getEntityType() == EntityType.GIANT) {
logger.fine("Handling spawned Giant at " + e.getLocation());
var giant = (Giant) e.getEntity();
if (giant.hasAI()) // NoAI flag
applyGiantsLogic(giant);
}
if (configuration.worldBlacklist().contains(e.getLocation().getWorld().getName()))
return;
if (e.getEntityType() == EntityType.ZOMBIE) {
if (configuration.spawnChance() > random.nextDouble()) {
logger.fine("Trying to spawn a Giant by chance at " + e.getLocation());
if (isSpawnableAt(e.getLocation())) {
logger.fine("Spawned a Giant by chance at " + e.getLocation());
e.getLocation().getWorld().spawnEntity(e.getLocation(), EntityType.GIANT);
e.setCancelled(true);
}
}
}
}
@EventHandler
public void entityDeath(EntityDeathEvent e) {
LivingEntity entity = e.getEntity();
if (entity.getType() == EntityType.GIANT) {
Location location = entity.getLocation();
logger.fine("A Giant died at " + location);
for (Drop drop : configuration.drops()) {
logger.fine("Rolling a drop: " + drop.itemStack().toString());
drop.dropAt(location);
}
for (Entity passenger : entity.getPassengers()) {
if (passenger.getPersistentDataContainer().has(huskKey, PersistentDataType.INTEGER)) {
((LivingEntity) passenger).setHealth(0);
logger.fine("Killed a Husk");
}
}
}
}
}

View file

@ -0,0 +1,90 @@
package eu.m724.giants.ai;
import eu.m724.giants.DebugLogger;
import org.bukkit.Location;
import org.bukkit.NamespacedKey;
import org.bukkit.entity.*;
import org.bukkit.event.Listener;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.plugin.Plugin;
import org.bukkit.potion.PotionEffect;
/**
* A processor class that processes giants
*/
public class GiantSpawnTools implements Listener {
/**
* The mechanics version. If it's significantly changed how the plugin works, it will be incremented.
*/
public static final int VERSION = 1;
private final PotionEffect[] potionEffects;
private final GiantAndZombieTracker tracker;
private final NamespacedKey passengerZombieKey;
private final NamespacedKey legacyPassengerHuskKey;
public GiantSpawnTools(Plugin plugin, PotionEffect[] potionEffects, GiantAndZombieTracker tracker) {
this.potionEffects = potionEffects;
this.tracker = tracker;
this.passengerZombieKey = new NamespacedKey(plugin, "zombie");
this.legacyPassengerHuskKey = new NamespacedKey(plugin, "husk");
}
/**
* The check is very approximate
*
* @param location the location
* @return whether a giant can be spawned here
*/
public boolean isSpawnableAt(Location location) {
for (int y=0; y<=12; y++) {
if (!location.clone().add(0, y, 0).getBlock().isEmpty()) // isPassable also seems good
return false;
}
return true;
}
public void applyGiantsLogic(Giant giant) {
DebugLogger.fine("Applying Giants logic to Giant #%d", giant.getEntityId());
Zombie zombie = spawnZombie(giant.getLocation());
giant.addPassenger(zombie);
// TODO probably not to everyone's liking
giant.getActivePotionEffects()
.forEach(potionEffect -> giant.removePotionEffect(potionEffect.getType()));
for (PotionEffect potionEffect : potionEffects) {
potionEffect.apply(giant);
}
tracker.add(giant, zombie);
}
private Zombie spawnZombie(Location location) {
// The zombie controls the giant. That's the entire magic.
Zombie zombie = (Zombie) location.getWorld().spawnEntity(location, EntityType.ZOMBIE);
zombie.setInvisible(true);
zombie.getPersistentDataContainer().set(passengerZombieKey, PersistentDataType.INTEGER, VERSION);
DebugLogger.finer("Spawned Zombie #%d at %d, %d, %d", zombie.getEntityId(), location.getBlockX(), location.getBlockY(), location.getBlockZ());
return zombie;
}
public boolean isValidPassengerZombie(Entity entity) {
return entity.getType() == EntityType.ZOMBIE
&& entity.getPersistentDataContainer().has(passengerZombieKey, PersistentDataType.INTEGER);
}
@Deprecated
public boolean isValidLegacyPassengerHusk(Entity entity) {
return entity.getType() == EntityType.HUSK
&& entity.getPersistentDataContainer().has(legacyPassengerHuskKey, PersistentDataType.INTEGER);
}
}

View file

@ -1,75 +1,62 @@
package eu.m724.giants.ai;
import eu.m724.giants.GiantsPlugin;
import eu.m724.giants.configuration.Configuration;
import org.bukkit.Location;
import org.bukkit.entity.*;
import org.bukkit.plugin.Plugin;
import eu.m724.giants.DebugLogger;
import org.bukkit.entity.Giant;
import org.bukkit.entity.Player;
import org.bukkit.entity.Zombie;
import org.bukkit.scheduler.BukkitRunnable;
import java.util.Set;
import org.bukkit.util.Vector;
/**
* Ticks giants
*/
public class GiantTicker extends BukkitRunnable {
private final Configuration configuration = GiantsPlugin.getInstance().getConfiguration();
private final GiantJumper jumper = new GiantJumper();
private final GiantJumper giantJumper;
private final GiantAndZombieTracker tracker;
private final GiantProcessor giantProcessor;
private final Vector attackReach;
private final double attackDamage;
public GiantTicker(GiantProcessor giantProcessor) {
this.giantProcessor = giantProcessor;
}
public GiantTicker(GiantJumper giantJumper, GiantAndZombieTracker tracker, Vector attackReach, double attackDamage) {
this.giantJumper = giantJumper;
this.tracker = tracker;
void schedule(Plugin plugin) {
this.runTaskTimer(plugin, 0, configuration.attackDelay());
this.attackReach = attackReach;
this.attackDamage = attackDamage;
}
@Override
public void run() {
for (Husk husk : Set.copyOf(giantProcessor.trackedHusks)) {
if (husk.isValid()) {
Location huskLocation = husk.getLocation();
Entity giant = husk.getVehicle();
DebugLogger.finer("Ticking Giants");
if (giant instanceof Giant) {
if (giant.isValid()) { // TODO reconsider
giant.getWorld().getNearbyEntities(
giant.getBoundingBox().expand(configuration.attackReach()),
e -> (e instanceof Player && !e.isInvulnerable())
).forEach(p -> ((Player) p).damage(configuration.attackDamage(), giant));
giant.setRotation(huskLocation.getYaw(), huskLocation.getPitch());
for (Giant giant : tracker.getGiants()) {
DebugLogger.finer("Ticking Giant #%d at %s", giant.getEntityId(), giant.getLocation().toString());
Zombie zombie = tracker.getZombieOf(giant);
// TODO move whole into that class?
if (configuration.jumpMode() != 0) {
// tracking location is only required for jumping
Location prevLocation = giantProcessor.giantLocationMap.get(giant);
Location location = giant.getLocation();
if (prevLocation == null) {
prevLocation = location;
}
giantProcessor.giantLocationMap.put(giant, location);
if (!giant.isValid()) {
DebugLogger.fine("Removing Giant #%d, it's invalid", giant.getEntityId());
tracker.remove(giant);
LivingEntity target = husk.getTarget();
if (target != null) {
jumper.processJump(giant, prevLocation, location, target.getLocation());
}
}
}
} else {
// no vehicle means the giant doesn't exist anymore and the husk should also not exist
husk.setHealth(0);
giant.remove();
if (zombie != null) zombie.remove();
giantProcessor.trackedHusks.remove(husk);
//logger.fine("Husk killed because Giant died at " + husk.getLocation());
}
} else {
giantProcessor.trackedHusks.remove(husk);
//logger.fine("Husk unloaded at " + husk.getLocation());
continue;
}
// TODO https://git.m724.eu/Minecon724/Giants/issues/5
giant.getWorld().getNearbyEntities(
giant.getBoundingBox().expand(attackReach),
e -> (e instanceof Player && !e.isInvulnerable())
).forEach(p -> ((Player) p).damage(attackDamage, giant));
giant.setRotation(
zombie.getLocation().getYaw(),
zombie.getLocation().getPitch()
);
giantJumper.tick(giant, zombie.getTarget());
}
// TODO remove zombies?
}
}

View file

@ -1,18 +1,17 @@
package eu.m724.giants.configuration;
import eu.m724.giants.DebugLogger;
import eu.m724.giants.Drop;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
import org.bukkit.potion.PotionEffect;
import org.bukkit.util.Vector;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.logging.Logger;
public record Configuration(
boolean debug,
boolean updaterEnabled,
String updaterChannel,
@ -28,19 +27,16 @@ public record Configuration(
double jumpHeight,
double spawnChance,
List<String> worldBlacklist,
List<PotionEffect> potionEffects,
List<Drop> drops
PotionEffect[] potionEffects,
Drop[] drops,
boolean burningHead
) {
// TODO not use logger here
private static Logger LOGGER;
public static Configuration load(Plugin plugin, File file) {
LOGGER = Logger.getLogger(plugin.getLogger().getName() + ".Configuration");
public static Configuration load(File file) {
YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
boolean debug = config.getBoolean("debug");
boolean updaterEnabled = true;
String updaterChannel = config.getString("updater", "release");
@ -48,7 +44,6 @@ public record Configuration(
updaterEnabled = false;
}
boolean aiEnabled = config.getBoolean("ai");
double attackDamage = config.getDouble("attackDamage");
@ -66,20 +61,20 @@ public record Configuration(
double spawnChance = config.getDouble("chance");
List<String> worldBlacklist = config.getStringList("blacklist");
List<PotionEffect> potionEffects = config.getStringList("effects").stream()
PotionEffect[] potionEffects = config.getStringList("effects").stream()
.map(Configuration::makePotionEffect)
.filter(Objects::nonNull)
.toList();
.toArray(PotionEffect[]::new);
List<Drop> drops = config.getMapList("drops").stream()
Drop[] drops = config.getMapList("drops").stream()
.map(Configuration::makeDrop)
.filter(Objects::nonNull)
.toList();
.toArray(Drop[]::new);
boolean burningHead = config.getBoolean("burningHead"); // easter egg, hidden
return new Configuration(
updaterEnabled, updaterChannel, aiEnabled, attackDamage, attackDelay, attackReach, jumpMode, jumpCondition, jumpDelay, jumpHeight, spawnChance, worldBlacklist, potionEffects, drops
debug, updaterEnabled, updaterChannel, aiEnabled, attackDamage, attackDelay, attackReach, jumpMode, jumpCondition, jumpDelay, jumpHeight, spawnChance, potionEffects, drops, burningHead
);
}
@ -95,9 +90,9 @@ public record Configuration(
try {
return ListParsers.makeDrop(dropMap);
} catch (ParseException e) {
LOGGER.warning("Failed to parse drop:");
LOGGER.warning(" At: " + dropMap);
LOGGER.warning(" " + e.getMessage());
DebugLogger.warning("Failed to parse drop:");
DebugLogger.warning(" At: " + dropMap);
DebugLogger.warning(" " + e.getMessage());
}
return null;
@ -107,19 +102,14 @@ public record Configuration(
try {
return ListParsers.makePotionEffect(line);
} catch (ParseException e) {
LOGGER.warning("Failed to parse potion effect:");
LOGGER.warning(" At line: " + line);
LOGGER.warning(" " + e.getMessage());
DebugLogger.warning("Failed to parse potion effect:");
DebugLogger.warning(" At line: " + line);
DebugLogger.warning(" " + e.getMessage());
}
return null;
}
static void assertParse(boolean assertion, String message) throws ParseException {
if (!assertion) throw new ParseException(message);
}
public static class ParseException extends Exception {
public ParseException(String message) {
super(message);

View file

@ -9,8 +9,6 @@ import org.bukkit.potion.PotionEffectType;
import java.util.Map;
import static eu.m724.giants.configuration.Configuration.assertParse;
public class ListParsers {
public static PotionEffect makePotionEffect(String line) throws ParseException {
if (line == null || line.trim().isEmpty()) {
@ -33,7 +31,7 @@ public class ListParsers {
assertParse(amplifier > 0, "Amplifier must be bigger than 0, is: " + amplifier);
assertParse(amplifier < 256, "Amplifier must be at most 255, is: " + amplifier);
return new PotionEffect(effectType, Integer.MAX_VALUE, amplifier);
return new PotionEffect(effectType, Integer.MAX_VALUE, amplifier, true, false);
}
// TODO refactor this
@ -66,4 +64,8 @@ public class ListParsers {
return new Drop(itemStack, min, max, chance);
}
private static void assertParse(boolean assertion, String message) throws ParseException {
if (!assertion) throw new ParseException(message);
}
}

View file

@ -47,12 +47,6 @@ jumpHeight: -1
# 0 is 0% (no zombie becomes a giant), 1 is 100% (every zombie becomes a giant), so the default 0.005 is 0.5%
chance: 0.005
# Worlds that Giants will not spawn in naturally.
# You can still use the command to spawn
blacklist:
- "world_nether"
- "world_the_end"
# Potion effects applied to a giant
# type:amplifier
# types: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/potion/PotionEffectType.html
@ -72,5 +66,4 @@ drops:
- material: WOODEN_SWORD
chance: 0.05 # 5%
quantityMin: 1
quantityMax: 1
quantityMax: 1