Compare commits

...

14 commits

Author SHA1 Message Date
89301375c8
Some refactoring
All checks were successful
/ build (push) Successful in 34s
2025-05-27 15:55:18 +02:00
7375163685
Fix a very embarrassing mistake 2025-05-26 16:08:55 +02:00
f92328070c
Use mStats 2025-05-26 16:05:35 +02:00
79e85d0c3e
Update to 1.16.5, Java 21
Only one Giants user (of ca. 30) doesn't meet the requirements.
The vast majority of servers do: https://bstats.org/global/bukkit
2025-05-26 15:59:29 +02:00
5fb88b0f89
Update dependencies 2025-05-05 11:26:09 +02:00
b3916fb312
Refactoring 2 2025-03-28 15:31:45 +01:00
c1a1978e44
Refactoring 2025-03-25 16:25:24 +01:00
Minecon724
b7bf79ee31
Remove jump commands 2025-01-05 12:45:36 +01:00
Minecon724
bccdc7d232
Remove spawn command 2025-01-05 11:41:49 +01:00
Minecon724
b26b8a1ded
Always send info about version 2025-01-05 11:32:35 +01:00
Minecon724
15c99d007a
[maven-release-plugin] prepare for next development iteration 2025-01-05 11:28:35 +01:00
Minecon724
153c7af274
[maven-release-plugin] prepare release giants-2.0.11 2025-01-05 11:28:33 +01:00
Minecon724
687afb9d5d
Update 2025-01-05 11:28:18 +01:00
Minecon724
f2006797e0
[maven-release-plugin] prepare for next development iteration 2024-11-10 10:04:15 +01:00
24 changed files with 1358 additions and 664 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

2
.idea/misc.xml generated
View file

@ -8,7 +8,7 @@
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="temurin-11" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="temurin-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/classes" />
</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

View file

@ -17,4 +17,13 @@ openssl x509 -inform pem -in cert.cer -pubkey -noout > public_key.pem
When using `mvn`, override with `-Djarsigner.`
```
mvn clean package -Djarsigner.keystore=/home/user/mykeystore.jks -Djarsigner.alias=mykey
```
```
### Color scheme
The following color scheme is used for chat messages:
- Errors: RED
- "Soft errors" (like no permission): GRAY
- Status messages: GRAY
- Notice / call for action: YELLOW (optionally also BOLD)
- Information: GOLD
- Highlight: AQUA (TODO do that)

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"

41
pom.xml
View file

@ -2,10 +2,10 @@
<modelVersion>4.0.0</modelVersion>
<groupId>eu.m724</groupId>
<artifactId>giants</artifactId>
<version>2.0.10</version>
<version>2.1.0-SNAPSHOT</version>
<properties>
<maven.compiler.release>11</maven.compiler.release>
<maven.compiler.release>21</maven.compiler.release>
<jarsigner.keystore>${project.basedir}/keystore.jks</jarsigner.keystore>
<jarsigner.alias>mykey</jarsigner.alias>
<jarsigner.storepass>123456</jarsigner.storepass>
@ -28,36 +28,20 @@
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.14.4-R0.1-SNAPSHOT</version>
<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>
<groupId>org.bstats</groupId>
<artifactId>bstats-bukkit</artifactId>
<version>3.0.2</version>
<scope>compile</scope>
<groupId>eu.m724</groupId>
<artifactId>mstats-spigot</artifactId>
<version>0.1.2</version>
</dependency>
<dependency>
<groupId>eu.m724</groupId>
<artifactId>jarupdater</artifactId>
<version>0.1.10</version>
<version>0.2.0</version>
</dependency>
</dependencies>
@ -84,17 +68,8 @@
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<minimizeJar>true</minimizeJar>
<!-- <shadedArtifactAttached>true</shadedArtifactAttached>
<shadedClassifierName>full</shadedClassifierName> -->
<relocations>
<relocation>
<pattern>org.bstats</pattern>
<shadedPattern>eu.m724.giants</shadedPattern>
</relocation>
</relocations>
<artifactSet>
<includes>
<include>org.bstats:*</include>
<include>eu.m724:jarupdater</include>
</includes>
</artifactSet>
@ -147,7 +122,7 @@
<scm>
<developerConnection>scm:git:git@git.m724.eu:Minecon724/giants.git</developerConnection>
<tag>giants-2.0.10</tag>
<tag>HEAD</tag>
</scm>
<distributionManagement>

View file

@ -1,139 +0,0 @@
package eu.m724.giants;
import org.bukkit.Material;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.Plugin;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Vector;
import java.io.File;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
public class Configuration {
private final File file;
private final Logger logger;
String updater;
boolean ai;
double attackDamage;
int attackDelay;
Vector attackReach;
int jumpMode;
int jumpCondition;
int jumpDelay;
double jumpHeight = -1;
double chance;
List<String> worldBlacklist;
Set<PotionEffect> effects = new HashSet<>();
Set<Drop> drops = new HashSet<>();
public Configuration(Plugin plugin, File file) {
this.file = file;
this.logger = Logger.getLogger(plugin.getLogger().getName() + ".Configuration");
}
public void load() {
YamlConfiguration config = YamlConfiguration.loadConfiguration(file);
updater = config.getString("updater", "true");
if (updater.equalsIgnoreCase("true")) {
updater = "release";
} else if (updater.equalsIgnoreCase("false")) {
updater = null;
}
ai = config.getBoolean("ai");
chance = config.getDouble("chance");
attackDamage = config.getDouble("attackDamage");
attackDelay = config.getInt("attackDelay");
jumpMode = config.getInt("jumpMode");
jumpCondition = config.getInt("jumpCondition");
jumpDelay = config.getInt("jumpDelay");
if (jumpMode != 0) {
jumpHeight = config.getDouble("jumpHeight", -1);
if (jumpHeight == -1) {
jumpHeight = defaultJumpHeight();
}
logger.info("Jumping is experimental.");
logger.info("Jump mode: " + jumpMode);
logger.info("Jump condition: " + jumpCondition);
logger.info("Jump height: " + jumpHeight);
}
double _attackReach = config.getDouble("attackReach");
double _attackVerticalReach = config.getDouble("attackVerticalReach");
attackReach = new Vector(_attackReach, _attackVerticalReach, _attackReach);
worldBlacklist = config.getStringList("blacklist");
for (String line : config.getStringList("effects")) {
String[] parts = line.split(":");
try {
PotionEffectType effectType = PotionEffectType.getByName(parts[0]);
if (effectType == null) {
throw new IllegalArgumentException("Invalid PotionEffectType");
}
int amplifier = Integer.parseInt(parts[1]);
effects.add(new PotionEffect(effectType, Integer.MAX_VALUE, amplifier));
} catch (IllegalArgumentException e) {
logger.warning("Parsing a potion effect failed:");
logger.warning(line);
logger.warning(e.getMessage());
}
}
for (Map<?, ?> dropMap : config.getMapList("drops")) {
try {
ItemStack itemStack;
if (dropMap.containsKey("itemStack")) {
itemStack = (ItemStack) dropMap.get("itemStack");
} else {
Material material = Material.getMaterial((String) dropMap.get("material"));
if (material == null) {
throw new IllegalArgumentException("Invalid Material");
}
itemStack = new ItemStack(material, 1);
}
int min = (int) dropMap.get("quantityMin");
int max = (int) dropMap.get("quantityMax");
double chance;
try {
chance = (double) dropMap.get("chance");
} catch (ClassCastException e) { // pointlessest error ever
chance = ((Integer) dropMap.get("chance")).doubleValue();
}
drops.add(new Drop(itemStack, min, max, chance));
} catch (IllegalArgumentException e) {
logger.warning("Parsing a drop failed:");
logger.warning(e.getMessage());
}
}
}
public double defaultJumpHeight() {
switch (jumpMode) {
case 1:
case 2:
return 0.42;
case 3:
case 4:
return 1.2;
}
return -1;
}
}

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

@ -1,25 +1,47 @@
package eu.m724.giants;
import org.bukkit.Location;
import org.bukkit.inventory.ItemStack;
import java.util.concurrent.ThreadLocalRandom;
public class Drop {
public final ItemStack itemStack;
public final int min, max;
public final double chance;
public Drop(ItemStack itemStack, int min, int max, double chance) {
this.itemStack = itemStack;
this.min = min;
this.max = max;
this.chance = chance;
}
public ItemStack generateItemStack() {
/**
* 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 generateItemStack() {
int amount = ThreadLocalRandom.current().nextInt(min, max + 1);
ItemStack itemStack = this.itemStack.clone();
itemStack.setAmount(amount);
return itemStack;
}
/**
* 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()) { // TODO faster random here as well?
ItemStack itemStack = generateItemStack();
location.getWorld().dropItemNaturally(location, itemStack);
}
}
}

View file

@ -1,254 +0,0 @@
package eu.m724.giants;
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.inventory.ItemStack;
import org.bukkit.persistence.PersistentDataType;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
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;
private final Configuration configuration;
private final Logger logger;
// private final Set<Giant> trackedGiants = new HashSet<>();
private final Set<Husk> trackedHusks = new HashSet<>();
private final Map<Entity, Location> giantLocationMap = new HashMap<>();
private final Map<Entity, Long> giantLastJump = 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");
this.huskKey = new NamespacedKey(plugin, "husk");
}
void start() {
if (configuration.ai) {
new BukkitRunnable() {
@Override
public void run() {
for (Husk husk : Set.copyOf(trackedHusks)) {
if (husk.isValid()) {
Location huskLocation = husk.getLocation();
Entity giant = husk.getVehicle();
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());
// jumping
if (configuration.jumpMode != 0) {
// tracking location is only required for jumping
Location prevLocation = giantLocationMap.get(giant);
Location location = giant.getLocation();
if (prevLocation == null) {
prevLocation = location;
}
giantLocationMap.put(giant, location);
LivingEntity target = husk.getTarget();
if (target != null) {
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);
trackedHusks.remove(husk);
logger.fine("Husk killed because Giant died at " + husk.getLocation());
}
} else {
trackedHusks.remove(husk);
logger.fine("Husk unloaded at " + husk.getLocation());
}
}
}
}.runTaskTimer(plugin, configuration.attackDelay, 0L);
}
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
private void processJump(Entity giant, Location prevLocation, Location location, Location targetLocation) {
long now = System.currentTimeMillis();
if (now - giantLastJump.getOrDefault(giant, 0L) < configuration.jumpDelay) {
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
}
}
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));
}
} // TODO those should be moved
public LivingEntity spawnGiant(Location pos) {
LivingEntity entity = (LivingEntity) pos.getWorld().spawnEntity(pos, EntityType.GIANT);
if (configuration.ai) {
// the husk basically moves the giant
LivingEntity passenger = (LivingEntity) pos.getWorld().spawnEntity(pos, 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);
entity.addPassenger(passenger);
trackedHusks.add((Husk) passenger);
}
configuration.effects.forEach(entity::addPotionEffect);
logger.fine("Spawned a Giant at " + pos);
return entity;
}
/**
* 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 LivingEntity spawnGiantIfPossible(Location location) {
if (isSpawnableAt(location)) {
return spawnGiant(location);
} else {
return null;
}
}
@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);
//trackedGiants.add((Giant) giant);
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());
}
}
}
@EventHandler
public void entitySpawn(EntitySpawnEvent e) {
if (configuration.worldBlacklist.contains(e.getLocation().getWorld().getName()))
return;
if (e.getEntityType() == EntityType.ZOMBIE) {
if (configuration.chance > random.nextDouble()) {
logger.fine("Spawned a Giant by chance at " + e.getLocation());
if (spawnGiantIfPossible(e.getLocation()) != null) {
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");
if (drop.chance > random.nextDouble()) {
ItemStack is = drop.generateItemStack();
entity.getWorld().dropItemNaturally(location, is);
logger.fine("Dropped " + is);
}
}
for (Entity passenger : entity.getPassengers()) {
if (passenger.getPersistentDataContainer().has(huskKey, PersistentDataType.INTEGER)) {
((LivingEntity) passenger).setHealth(0);
logger.fine("Killed a Husk");
}
}
}
}
}

View file

@ -1,6 +1,8 @@
package eu.m724.giants;
import eu.m724.giants.updater.UpdateCommand;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
@ -19,129 +21,91 @@ import java.util.Map;
public class GiantsCommand implements CommandExecutor {
private final GiantsPlugin plugin;
private final Configuration configuration;
private final UpdateCommand updateCommand;
public GiantsCommand(GiantsPlugin plugin, Configuration configuration, UpdateCommand updateCommand) {
public GiantsCommand(GiantsPlugin plugin, UpdateCommand updateCommand) {
this.plugin = plugin;
this.configuration = configuration;
this.updateCommand = updateCommand;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (args.length == 0) {
sender.sendMessage("Giants " + plugin.getDescription().getVersion());
sender.sendMessage(ChatColor.GRAY + "Giants " + plugin.getDescription().getVersion());
return true;
}
String action = args[0];
if (!sender.hasPermission("giants.command." + action)) {
sender.sendMessage("You don't have permission to use this command, or it doesn't exist.");
sender.sendMessage(ChatColor.GRAY + "You don't have permission to use this command, or it doesn't exist.");
return true;
}
Player player = sender instanceof Player ? (Player) sender : null;
if (action.equals("spawn")) {
if (player != null) {
if (plugin.spawnGiantIfPossible(player.getLocation()) == null) {
sender.sendMessage("No space here for a Giant");
} else {
sender.sendMessage("Spawned a Giant");
}
} else {
sender.sendMessage("Only players can use this command.");
}
} else if (action.equals("serialize")) {
if (player != null) {
ItemStack itemStack = player.getInventory().getItemInMainHand();
List<Map<String, Object>> list = new ArrayList<>();
Map<String, Object> map = new LinkedHashMap<>();
map.put("chance", 1);
map.put("quantityMin", itemStack.getAmount());
map.put("quantityMax", itemStack.getAmount());
map.put("itemStack", itemStack);
list.add(map);
YamlConfiguration yamlConfiguration = new YamlConfiguration();
try {
Method method = yamlConfiguration.getClass().getMethod("setInlineComments", String.class, List.class);
yamlConfiguration.set("v", list);
method.invoke(yamlConfiguration, "v", List.of("Copy the below content to your config.yml"));
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
yamlConfiguration.set("v", null); // two latter exceptions happen after setting
yamlConfiguration.set("copy_everything_below_to_config_yml", list);
}
long now = System.currentTimeMillis();
String name = "item-" + now + ".yml";
File file = new File(plugin.getDataFolder(), name);
try {
yamlConfiguration.save(file);
sender.sendMessage("Saved to plugins/Giants/" + name + ". To add it as a drop, see instructions in the file.");
} catch (IOException e) {
sender.sendMessage("Error saving file. See console for details.");
throw new RuntimeException("Error saving file to " + file.getName(), e);
}
} else {
sender.sendMessage("Only players can use this command.");
}
} else if (action.equals("jm")) { // TODO remove
if (args.length > 1) {
int mode = Integer.parseInt(args[1]);
configuration.jumpMode = mode;
sender.sendMessage("Jump mode set to " + mode);
sender.sendMessage("This command doesn't check if it's valid, an invalid value turns off jumping.");
if (configuration.jumpHeight == -1) {
configuration.jumpHeight = configuration.defaultJumpHeight();
sender.sendMessage("Jump height set to " + configuration.jumpHeight + ". Modify it with /giants jumpheight");
}
} else {
sender.sendMessage("Jump mode: " + configuration.jumpMode);
}
} else if (action.equals("jh")) {
if (args.length > 1) {
double height = Double.parseDouble(args[1]);
configuration.jumpHeight = height;
sender.sendMessage("Jump height set to " + height);
} else {
sender.sendMessage("Jump height: " + configuration.jumpHeight);
}
} else if (action.equals("jc")) {
if (args.length > 1) {
int condition = Integer.parseInt(args[1]);
configuration.jumpCondition = condition;
sender.sendMessage("Jump condition set to " + condition);
} else {
sender.sendMessage("Jump condition: " + configuration.jumpCondition);
}
} else if (action.equals("jd")) {
if (args.length > 1) {
int delay = Integer.parseInt(args[1]);
configuration.jumpDelay = delay;
sender.sendMessage("Jump delay set to " + delay);
} else {
sender.sendMessage("Jump delay: " + configuration.jumpDelay);
}
if (action.equals("serialize")) {
serializeCommand(sender);
} else if (action.equals("update")) {
if (updateCommand != null)
updateCommand.onCommand(sender, command, label, args);
updateCommand.updateCommand(sender, args);
else
sender.sendMessage("Updater is disabled");
sender.sendMessage(ChatColor.GRAY + "Updater is disabled");
} else {
sender.sendMessage(ChatColor.RED + "No such command: " + action);
}
return true;
}
private void serializeCommand(CommandSender sender) {
if (!(sender instanceof Player player)) {
sender.sendMessage("Only players can use this command.");
return;
}
ItemStack itemStack = player.getInventory().getItemInMainHand();
if (itemStack.getType() == Material.AIR) {
sender.sendMessage(ChatColor.RED + "You must hold an item in your main hand.");
return;
}
try {
String name = serializeDrop(player.getInventory().getItemInMainHand());
sender.sendMessage("Saved to plugins/Giants/" + name + ". Please follow the instructions in that file.");
} catch (IOException e) {
sender.sendMessage("Error saving file. See console for details.");
throw new RuntimeException("Error saving drop configuration file", e);
}
}
private String serializeDrop(ItemStack itemStack) throws IOException {
List<Map<String, Object>> list = new ArrayList<>();
Map<String, Object> map = new LinkedHashMap<>();
map.put("chance", 1);
map.put("quantityMin", itemStack.getAmount());
map.put("quantityMax", itemStack.getAmount());
map.put("itemStack", itemStack);
list.add(map);
YamlConfiguration yamlConfiguration = new YamlConfiguration();
try {
Method method = yamlConfiguration.getClass().getMethod("setInlineComments", String.class, List.class);
yamlConfiguration.set("v", list);
method.invoke(yamlConfiguration, "v", List.of("Copy the below content to your config.yml"));
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
yamlConfiguration.set("v", null); // two latter exceptions happen after setting
yamlConfiguration.set("copy_everything_below_to_config_yml", list);
}
long now = System.currentTimeMillis();
String name = "item-" + now + ".yml";
File file = new File(plugin.getDataFolder(), name);
yamlConfiguration.save(file);
return name;
}
}

View file

@ -1,47 +1,63 @@
package eu.m724.giants;
import eu.m724.giants.ai.*;
import eu.m724.giants.configuration.Configuration;
import eu.m724.giants.updater.PluginUpdater;
import eu.m724.giants.updater.UpdateCommand;
import eu.m724.jarupdater.verify.VerificationException;
import org.bstats.bukkit.Metrics;
import eu.m724.mstats.MStatsPlugin;
import org.bukkit.Location;
import org.bukkit.command.CommandExecutor;
import org.bukkit.entity.Giant;
import org.bukkit.entity.LivingEntity;
import org.bukkit.plugin.java.JavaPlugin;
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;
public class GiantsPlugin extends JavaPlugin implements CommandExecutor {
private final File configFile = new File(getDataFolder(), "config.yml");
private final Configuration configuration = new Configuration(this, configFile);
private final GiantProcessor giantProcessor = new GiantProcessor(this, configuration);
private Configuration configuration;
private GiantSpawnTools giantSpawnTools;
@Override
public void onEnable() {
long start = System.nanoTime();
INSTANCE = this;
if (!configFile.exists()) {
saveResource("config.yml", false);
}
configuration.load();
this.configuration = Configuration.load(configFile);
giantProcessor.start();
getLogger().setLevel(configuration.debug() ? Level.FINEST : Level.INFO);
DebugLogger.logger = getLogger();
// bStats
new Metrics(this, 14131);
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);
// updater
PluginUpdater updater = null;
if (configuration.updater != null) {
UpdateCommand updateCommand = null;
if (configuration.updaterEnabled()) {
try (InputStream keyInputStream = getResource("verifies_downloaded_jars.pem")) {
updater = PluginUpdater.build(this, getFile(), configuration.updater, keyInputStream);
updater = PluginUpdater.build(this, getFile(), configuration.updaterChannel(), keyInputStream);
} catch (IOException e) {
e.printStackTrace();
getLogger().severe("Failed to load updater");
e.printStackTrace();
}
if (updater != null) {
@ -52,41 +68,32 @@ public class GiantsPlugin extends JavaPlugin implements CommandExecutor {
} catch (VerificationException e) {
getLogger().warning(e.getMessage());
getLogger().warning("Plugin JAR is of invalid signature. If this persists, re-download the JAR from SpigotMC.");
getLogger().warning("Did you update from 2.0.7? If yes, you must re-download 2.0.9+ from SpigotMC, then delete plugins/.paper-remapped");
getLogger().warning("If on Paper, try removing plugins/.paper-remapped");
}
updater.initNotifier();
updateCommand = new UpdateCommand(updater);
}
}
UpdateCommand updateCommand = new UpdateCommand(updater);
getCommand("giants").setExecutor(new GiantsCommand(this, updateCommand));
getCommand("giants").setExecutor(new GiantsCommand(this, configuration, updateCommand));
/* bStats is optional. not anymore
try {
Class<?> clazz = Class.forName("eu.m724.giants.bukkit.Metrics");
Constructor<?> constructor = clazz.getDeclaredConstructor(JavaPlugin.class, int.class);
constructor.newInstance(this, 14131);
getLogger().info("Enabled bStats");
} catch (Exception e) {
getLogger().info("Not using bStats (" + e.getClass().getName() + ")");
}*/
mStats(3);
DebugLogger.fine("Took %.3f milliseconds", (System.nanoTime() - start) / 1000000.0);
}
public static GiantsPlugin getInstance() {
return INSTANCE;
}
public Configuration getConfiguration() {
return configuration;
}
// TODO api, untested
/**
* Spawns a {@link Giant} at the specified {@link Location}
*
* @param location The location
* @return The spawned {@link Giant} (as a {@link LivingEntity}, but you can cast)
*/
public LivingEntity spawnGiant(Location location) {
return giantProcessor.spawnGiant(location);
public GiantSpawnTools getGiantSpawnTools() {
return giantSpawnTools;
}
/**
@ -97,17 +104,6 @@ public class GiantsPlugin extends JavaPlugin implements CommandExecutor {
* @return Whether a giant can be spawned
*/
public boolean isSpawnableAt(Location location) {
return giantProcessor.isSpawnableAt(location);
}
/**
* Checks if a giant can be spawned at a location and spawns it<br>
* * The check is very approximate, but works for most scenarios
*
* @param location The location
* @return The spawned {@link Giant} or null if no room for it
*/
public LivingEntity spawnGiantIfPossible(Location location) {
return giantProcessor.spawnGiantIfPossible(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

@ -0,0 +1,81 @@
package eu.m724.giants.ai;
import eu.m724.giants.DebugLogger;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.util.Vector;
import java.util.HashMap;
import java.util.Map;
public class GiantJumper {
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;
}
long now = System.currentTimeMillis();
if (now - giantLastJump.getOrDefault(giant, 0L) < jumpDelayMillis) {
return;
}
if (giant.isOnGround()) {
giantLastJump.put(giant, now);
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 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

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

@ -0,0 +1,62 @@
package eu.m724.giants.ai;
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 org.bukkit.util.Vector;
/**
* Ticks giants
*/
public class GiantTicker extends BukkitRunnable {
private final GiantJumper giantJumper;
private final GiantAndZombieTracker tracker;
private final Vector attackReach;
private final double attackDamage;
public GiantTicker(GiantJumper giantJumper, GiantAndZombieTracker tracker, Vector attackReach, double attackDamage) {
this.giantJumper = giantJumper;
this.tracker = tracker;
this.attackReach = attackReach;
this.attackDamage = attackDamage;
}
@Override
public void run() {
DebugLogger.finer("Ticking Giants");
for (Giant giant : tracker.getGiants()) {
DebugLogger.finer("Ticking Giant #%d at %s", giant.getEntityId(), giant.getLocation().toString());
Zombie zombie = tracker.getZombieOf(giant);
if (!giant.isValid()) {
DebugLogger.fine("Removing Giant #%d, it's invalid", giant.getEntityId());
tracker.remove(giant);
giant.remove();
if (zombie != null) zombie.remove();
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

@ -0,0 +1,118 @@
package eu.m724.giants.configuration;
import eu.m724.giants.DebugLogger;
import eu.m724.giants.Drop;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.potion.PotionEffect;
import org.bukkit.util.Vector;
import java.io.File;
import java.util.Map;
import java.util.Objects;
public record Configuration(
boolean debug,
boolean updaterEnabled,
String updaterChannel,
boolean aiEnabled,
double attackDamage,
int attackDelay,
Vector attackReach,
int jumpMode,
int jumpCondition,
int jumpDelay,
double jumpHeight,
double spawnChance,
PotionEffect[] potionEffects,
Drop[] drops,
boolean burningHead
) {
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");
if (updaterChannel.equalsIgnoreCase("false")) {
updaterEnabled = false;
}
boolean aiEnabled = config.getBoolean("ai");
double attackDamage = config.getDouble("attackDamage");
int attackDelay = config.getInt("attackDelay");
Vector attackReach = new Vector(
config.getDouble("attackReach"),
config.getDouble("attackVerticalReach"),
config.getDouble("attackReach")
);
int jumpMode = config.getInt("jumpMode");
int jumpCondition = config.getInt("jumpCondition");
int jumpDelay = config.getInt("jumpDelay");
double jumpHeight = config.getDouble("jumpHeight", defaultJumpHeight(jumpMode));
double spawnChance = config.getDouble("chance");
PotionEffect[] potionEffects = config.getStringList("effects").stream()
.map(Configuration::makePotionEffect)
.filter(Objects::nonNull)
.toArray(PotionEffect[]::new);
Drop[] drops = config.getMapList("drops").stream()
.map(Configuration::makeDrop)
.filter(Objects::nonNull)
.toArray(Drop[]::new);
boolean burningHead = config.getBoolean("burningHead"); // easter egg, hidden
return new Configuration(
debug, updaterEnabled, updaterChannel, aiEnabled, attackDamage, attackDelay, attackReach, jumpMode, jumpCondition, jumpDelay, jumpHeight, spawnChance, potionEffects, drops, burningHead
);
}
private static double defaultJumpHeight(int jumpMode) {
return switch (jumpMode) {
case 1, 2 -> 0.42;
case 3, 4 -> 1.2;
default -> -1;
};
}
private static Drop makeDrop(Map<?, ?> dropMap) {
try {
return ListParsers.makeDrop(dropMap);
} catch (ParseException e) {
DebugLogger.warning("Failed to parse drop:");
DebugLogger.warning(" At: " + dropMap);
DebugLogger.warning(" " + e.getMessage());
}
return null;
}
private static PotionEffect makePotionEffect(String line) {
try {
return ListParsers.makePotionEffect(line);
} catch (ParseException e) {
DebugLogger.warning("Failed to parse potion effect:");
DebugLogger.warning(" At line: " + line);
DebugLogger.warning(" " + e.getMessage());
}
return null;
}
public static class ParseException extends Exception {
public ParseException(String message) {
super(message);
}
}
}

View file

@ -0,0 +1,71 @@
package eu.m724.giants.configuration;
import eu.m724.giants.Drop;
import eu.m724.giants.configuration.Configuration.ParseException;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import java.util.Map;
public class ListParsers {
public static PotionEffect makePotionEffect(String line) throws ParseException {
if (line == null || line.trim().isEmpty()) {
throw new ParseException("Cannot parse empty or null line into a PotionEffect.");
}
String[] parts = line.split(":", 2);
assertParse(parts.length == 2, "Invalid PotionEffect format (expected 'TYPE:AMPLIFIER')");
PotionEffectType effectType = PotionEffectType.getByName(parts[0].trim());
assertParse(effectType != null, "Unknown PotionEffectType: " + parts[0].trim());
int amplifier;
try {
amplifier = Integer.parseInt(parts[1].trim());
} catch (IllegalArgumentException e) {
throw new ParseException("Invalid amplifier format (expected integer): " + parts[1].trim());
}
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, true, false);
}
// TODO refactor this
public static Drop makeDrop(Map<?, ?> dropMap) throws ParseException {
ItemStack itemStack;
if (dropMap.containsKey("itemStack")) {
itemStack = (ItemStack) dropMap.get("itemStack");
} else {
Material material = Material.getMaterial((String) dropMap.get("material"));
assertParse(material != null, "Invalid Material: " + dropMap.get("material"));
itemStack = new ItemStack(material, 1);
}
int min = (int) dropMap.get("quantityMin");
int max = (int) dropMap.get("quantityMax");
assertParse(min > 0, "Minimum quantity must be more than 0, is: " + min);
assertParse(max > 0, "Maximum quantity must be more than 0, is: " + max);
assertParse(min < 100, "Minimum quantity must be less than 100, is: " + min);
assertParse(max < 100, "Maximum quantity must be less than 100, is: " + max);
double chance = ((Number) dropMap.get("chance")).doubleValue();
if (chance > 1 && chance <= 100) {
chance /= 100; // user might have misunderstood
}
assertParse(chance > 0, "Chance must be more than 0, is: " + chance);
assertParse(chance <= 1, "Chance must be at most 1, is: " + chance);
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

@ -4,7 +4,6 @@ import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@ -37,79 +36,71 @@ public class UpdateCommand {
}
}
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (updater == null) {
sender.sendMessage("Updater is disabled");
return true;
}
sender.sendMessage("Please wait...");
sender.sendMessage("Channel: " + updater.getEnvironment().getChannel());
public void updateCommand(CommandSender sender, String[] args) {
sender.sendMessage(ChatColor.GRAY + "Please wait...");
sender.sendMessage(ChatColor.GRAY + "Channel: " + updater.getChannel());
if (updater.updatePending) {
sender.sendMessage("Server restart required");
sender.sendMessage(ChatColor.YELLOW + "" + ChatColor.BOLD + "(!) Server restart required");
}
if (args.length == 1) { // remember this function is proxied
String action = args.length > 1 ? args[1] : null; // remember this function is proxied
if (action == null) {
updater.getLatestVersion().thenAccept(metadata -> {
updater.getCurrentVersion().thenAccept(metadata2 -> {
sender.sendMessage(ChatColor.GOLD + "You're on Giants " + metadata2.getLabel() + " released " + formatDate(metadata2.getTimestamp()));
sendChangelogMessage(sender, metadata2.getChangelogUrl());
}).exceptionally(e -> {
sender.sendMessage(ChatColor.RED + "Error retrieving information about current version, see console for details. " + e.getMessage());
e.printStackTrace();
return null;
});
if (metadata != null) {
sender.sendMessage("An update is available!");
sender.sendMessage("Giants " + metadata.getLabel() + " released " + formatDate(metadata.getTimestamp()));
sender.sendMessage(ChatColor.YELLOW + "" + ChatColor.BOLD + "An update is available!");
sender.sendMessage(ChatColor.GOLD + "Giants " + metadata.getLabel() + " released " + formatDate(metadata.getTimestamp()));
sendChangelogMessage(sender, metadata.getChangelogUrl());
sender.sendMessage("To download: /giants update download");
sender.sendMessage(ChatColor.GOLD + "To download: /giants update download");
} else {
sender.sendMessage("No new updates");
updater.getCurrentVersion().thenAccept(metadata2 -> {
sender.sendMessage("You're on Giants " + metadata2.getLabel() + " released " + formatDate(metadata2.getTimestamp()));
sendChangelogMessage(sender, metadata2.getChangelogUrl());
}).exceptionally(e -> {
sender.sendMessage("Error retrieving information about current version, see console for details. " + e.getMessage());
e.printStackTrace();
return null;
});
sender.sendMessage(ChatColor.GRAY + "No new updates");
}
}).exceptionally(e -> {
sender.sendMessage("Error checking for update. See console for details.");
sender.sendMessage(ChatColor.RED + "Error checking for update. See console for details.");
e.printStackTrace();
return null;
});
} else {
String action = args[1]; // remember this function is proxied
if (!sender.hasPermission("giants.update." + action)) {
sender.sendMessage("You don't have permission to use this command, or it doesn't exist.");
return true;
sender.sendMessage(ChatColor.GRAY + "You don't have permission to use this command, or it doesn't exist.");
return;
}
if (action.equals("download")) {
sender.sendMessage("Started download");
sender.sendMessage(ChatColor.GRAY + "Started download");
updater.downloadLatestVersion().thenAccept(file -> {
sender.sendMessage("Download finished, install with /giants update install");
sender.sendMessage(ChatColor.GREEN + "Download finished, install with /giants update install"); // TODO make this clickable
}).exceptionally(e -> {
sender.sendMessage("Download failed. See console for details.");
sender.sendMessage(ChatColor.RED + "Download failed. See console for details.");
e.printStackTrace();
return null;
});
} else if (action.equals("install")) {
try {
updater.installLatestVersion().thenAccept(v -> {
sender.sendMessage("Installation completed, restart server to apply.");
sender.sendMessage(ChatColor.GREEN + "Installation completed, restart server to apply.");
updater.updatePending = true;
}).exceptionally(e -> {
sender.sendMessage("Install failed, see console for details. " + e.getMessage());
sender.sendMessage(ChatColor.RED + "Install failed, see console for details. " + e.getMessage());
e.printStackTrace();
return null;
});
} catch (NoSuchFileException e) {
sender.sendMessage("First, download the update: /giants update download");
sender.sendMessage(ChatColor.YELLOW + "Download the update first: /giants update download");
}
} else {
return false;
}
}
return true;
}
private String formatDate(long timestamp) {

View file

@ -9,7 +9,6 @@ import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitRunnable;
import java.io.IOException;
import java.util.concurrent.CompletionException;
import java.util.function.Consumer;
@ -36,11 +35,7 @@ public class UpdateNotifier extends BukkitRunnable implements Listener { // TODO
try {
latestVersion = updater.getLatestVersion().join();
} catch (CompletionException e) {
Throwable ex = e.getCause();
if (ex instanceof IOException)
plugin.getLogger().info("error trying to contact update server: " + ex.getMessage());
else e.printStackTrace();
plugin.getLogger().info("Error trying to contact update server: " + e.getCause().getMessage());
}
if (latestVersion == null) return;
@ -53,7 +48,6 @@ public class UpdateNotifier extends BukkitRunnable implements Listener { // TODO
}
updateConsumer.accept(latestVersion);
}
@EventHandler

View file

@ -1,5 +1,5 @@
# Notes:
# To have no values in a list, remove every value below it and change to [] like `effects: []`
# To clear a list, remove every value below it and change to [] like `effects: []`
# Enable updater
# It still requires an admin to confirm update
@ -10,6 +10,20 @@ updater: true
# If disabled, the giant will not move or attack
ai: true
# In hearts, 0.5 is half a heart
attackDamage: 1.0
# Attack delay / speed, in ticks
# 20 is 1 second
attackDelay: 20
# Self-explanatory, 0 will attack only colliding (touching) entities
# There's no wall check yet, so it will hit through walls
attackReach: 2
# Vertical reach
attackVerticalReach: 1
# Makes giants jump. Very experimental.
# I prefer velocity mode
# 0 - disabled
@ -27,32 +41,12 @@ jumpDelay: 200
# -1: auto
jumpHeight: -1
# In hearts, 0.5 is half a heart
attackDamage: 1.0
# Attack delay / speed, in ticks
# 20 is 1 second
attackDelay: 20
# Self-explanatory, 0 will attack only colliding (touching) entities
# There's no wall check yet, so it will hit through walls
attackReach: 2
# Vertical reach
attackVerticalReach: 1
###
# Chance of a zombie becoming a giant. This is per each zombie spawn, natural or not.
# 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

View file

@ -1,18 +1,21 @@
name: Giants
version: ${project.version}
api-version: 1.14
main: eu.m724.giants.GiantsPlugin
author: Minecon724
website: https://www.spigotmc.org/resources/giants.99600/
api-version: 1.16
load: STARTUP
main: eu.m724.giants.GiantsPlugin
libraries:
- eu.m724:mstats-spigot:0.1.2
commands:
giants:
description: Utility command for Giants
permissions:
giants.command.spawn:
description: Permits /giants spawn
default: op
giants.command.serialize:
description: Permits /giants serialize
default: op