8 Commits

Author SHA1 Message Date
skippyall f3c934c619 Gehen Sie zu Kotlin. Gehen Sie nicht über Los und ziehen Sie nicht 200 RM ein.
Migrate some classes to Kotlin, let's see if I will regret
2026-05-02 10:48:13 +02:00
skippyall 71016f9e70 Rename .java to .kt 2026-05-02 10:48:13 +02:00
skippyall 08f9763b83 Go Back! 2026-04-30 23:56:21 +02:00
skippyall e117139a63 Port to 26.1 2026-04-29 17:20:13 +02:00
skippyall f5202a4264 Mojang Mappings 2026-04-29 08:51:37 +02:00
skippyall cc9fedd63b Converted 2026-04-29 00:51:31 +02:00
skippyall 324fea04a9 Convert even more 2026-04-05 01:17:53 +02:00
skippyall 7acd083e79 Cast, Convert & more 2026-03-16 23:01:58 +01:00
195 changed files with 5609 additions and 2624 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2024 Copyright (c) 2024 skippyall
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+10
View File
@@ -0,0 +1,10 @@
Module ohne Instruction nicht bei Instruction Auswahl vorschlagen ✓
Mob Spawning durch Instruction an/aus
Referenz kopieren als Item, das beim linken verschwindet ✓
zurück-Button (Jain)
Instructions stoppen / einfrieren bei Entfernung des Moduls ✓
Verbindung mit Minion anzeigen ✓
Schlagen auf Entities fixen ✓
Signaländerung bei Trigger nach Tod des Minions mit Minion synchronisieren ✓
Menüführung für Argumente verbessern (Nö)
direkt Wert zeigen ✓
+35
View File
@@ -0,0 +1,35 @@
- Module:
- Bewegungsmodul ✓
- Angriffsmodul ✓
- Abbaumodul ✓
- Platziermodul / Verwendungsmodul ✓
- Werfmodul
- Inventarmanagementmodul (Noch nicht wirklich)
- Inventaropenmodul (Nä)
- Detektoren:
- Blockdetektor
- Entitydetektor
- Dimensiondetektor
- Positiondetektor
- Inventardetektor
- Programmieren: Nö
- Variablen Nö
- (Listen) Nö
- Warten Nö
- Wiederholen Nö
- Wiederhole bis/während Nö
- Wiederhole x mal/(für jedes Element) Nö
- Bedingung Nö
- VariableTypen:
- Integer ✓
- String ✓
- Position (Nö)
- Block (weisned)
- BlockType
- (NBT) Nö
- (World) Nö
- (Chunk) Nö
- Inventory (weisned)
-138
View File
@@ -1,138 +0,0 @@
plugins {
id 'net.fabricmc.fabric-loom-remap' version '1.14-SNAPSHOT'
id 'maven-publish'
}
version = project.mod_version
group = project.maven_group
base {
archivesName = project.archives_base_name
}
loom {
accessWidenerPath = file("src/main/resources/minions.accesswidener")
splitEnvironmentSourceSets()
mods {
minions {
sourceSet sourceSets.main
sourceSet sourceSets.client
}
}
runs.forEach {
it.vmArg("-XX:+AllowEnhancedClassRedefinition")
}
}
repositories {
// Add repositories to retrieve artifacts from in here.
// You should only use this when depending on other mods because
// Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
// See https://docs.gradle.org/current/userguide/declaring_repositories.html
// for more information about repositories.
maven { url 'https://maven.nucleoid.xyz' }
exclusiveContent {
forRepository {
maven {
name = "Modrinth"
url = "https://api.modrinth.com/maven"
}
}
filter {
includeGroup "maven.modrinth"
}
}
}
dependencies {
// To change the versions see the gradle.properties file
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"
modImplementation "net.fabricmc:fabric-loader:${project.loader_version}"
// Fabric API. This is technically optional, but you probably want it anyway.
modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}"
modImplementation "eu.pb4:polymer-core:${project.polymer_version}"
modImplementation "eu.pb4:polymer-virtual-entity:${project.polymer_version}"
modImplementation "eu.pb4:polymer-resource-pack:${project.polymer_version}"
modLocalRuntime "eu.pb4:polymer-autohost:${project.polymer_version}"
modImplementation include("eu.pb4:sgui:${project.sgui_version}")
modImplementation include("xyz.nucleoid:server-translations-api:${project.server_translations_version}")
implementation include("com.electronwill.night-config:toml:${project.night_config_version}")
modCompileOnly "maven.modrinth:universal-graves:${project.universal_graves_version}"
}
processResources {
inputs.property "version", project.version
inputs.property "minecraft_version", project.minecraft_version
inputs.property "loader_version", project.loader_version
filteringCharset "UTF-8"
filesMatching("fabric.mod.json") {
expand "version": project.version,
"minecraft_version": project.minecraft_version,
"loader_version": project.loader_version
}
}
def targetJavaVersion = 21
tasks.withType(JavaCompile).configureEach {
// ensure that the encoding is set to UTF-8, no matter what the system default is
// this fixes some edge cases with special characters not displaying correctly
// see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html
// If Javadoc is generated, this must be specified in that task too.
it.options.encoding = "UTF-8"
if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) {
it.options.release.set(targetJavaVersion)
}
}
java {
def javaVersion = JavaVersion.toVersion(targetJavaVersion)
if (JavaVersion.current() < javaVersion) {
toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion)
}
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
// if it is present.
// If you remove this line, sources will not be generated.
withSourcesJar()
}
jar {
from("LICENSE") {
rename { "${it}_${project.archives_base_name}"}
}
}
// configure the maven publication
publishing {
publications {
maven (MavenPublication) {
from components.java
}
}
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
repositories {
// Add repositories to publish to here.
// Notice: This block does NOT have the same function as the block in the top level.
// The repositories here will be used for publishing your artifact, not for
// retrieving dependencies.
if(project.hasProperty("foxgalaxy_user_name") && project.hasProperty("foxgalaxy_password")) {
maven {
url = "https://maven.foxgalaxy.de/private"
credentials {
username = project.property("foxgalaxy_user_name")
password = project.property("foxgalaxy_password")
}
}
}
}
}
+128
View File
@@ -0,0 +1,128 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
id("net.fabricmc.fabric-loom")
`maven-publish`
id("org.jetbrains.kotlin.jvm") version "2.3.21"
}
version = providers.gradleProperty("mod_version").get()
group = providers.gradleProperty("maven_group").get()
repositories {
// Add repositories to retrieve artifacts from in here.
// You should only use this when depending on other mods because
// Loom adds the essential maven repositories to download Minecraft and libraries from automatically.
// See https://docs.gradle.org/current/userguide/declaring_repositories.html
// for more information about repositories.
maven("https://maven.nucleoid.xyz")
exclusiveContent {
forRepository {
maven ("https://api.modrinth.com/maven")
}
filter {
includeGroup("maven.modrinth")
}
}
}
loom {
splitEnvironmentSourceSets()
mods {
register("minions") {
sourceSet(sourceSets.main.get())
sourceSet(sourceSets.getByName("client"))
}
}
for (settings in runs) {
settings.vmArg("-XX:+AllowEnhancedClassRedefinition")
}
accessWidenerPath = file("src/main/resources/minions.classtweaker")
}
dependencies {
// To change the versions see the gradle.properties file
minecraft("com.mojang:minecraft:${providers.gradleProperty("minecraft_version").get()}")
implementation("net.fabricmc:fabric-loader:${providers.gradleProperty("loader_version").get()}")
// Fabric API. This is technically optional, but you probably want it anyway.
implementation("net.fabricmc.fabric-api:fabric-api:${providers.gradleProperty("fabric_api_version").get()}")
implementation("net.fabricmc:fabric-language-kotlin:${providers.gradleProperty("fabric_kotlin_version").get()}")
val polymer_version = providers.gradleProperty("polymer_version").get()
implementation("eu.pb4:polymer-core:${polymer_version}")
implementation("eu.pb4:polymer-virtual-entity:${polymer_version}")
implementation("eu.pb4:polymer-resource-pack:${polymer_version}")
localRuntime("eu.pb4:polymer-autohost:${polymer_version}")
implementation("eu.pb4:sgui:${providers.gradleProperty("sgui_version").get()}")
implementation("xyz.nucleoid:server-translations-api:${providers.gradleProperty("server_translations_version").get()}")
implementation("com.electronwill.night-config:toml:${providers.gradleProperty("night_config_version").get()}")
compileOnly("maven.modrinth:universal-graves:${providers.gradleProperty("universal_graves_version").get()}")
}
tasks.processResources {
val version = version
inputs.property("version", version)
filesMatching("fabric.mod.json") {
expand("version" to version)
}
}
tasks.withType<JavaCompile>().configureEach {
options.release = 25
}
kotlin {
compilerOptions {
jvmTarget = JvmTarget.JVM_25
}
}
java {
// Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task
// if it is present.
// If you remove this line, sources will not be generated.
withSourcesJar()
sourceCompatibility = JavaVersion.VERSION_25
targetCompatibility = JavaVersion.VERSION_25
}
tasks.jar {
val projectName = project.name
inputs.property("projectName", projectName)
from("LICENSE") {
rename { "${it}_$projectName" }
}
}
// configure the maven publication
publishing {
publications {
register<MavenPublication>("mavenJava") {
from(components["java"])
}
}
// See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing.
repositories {
// Add repositories to publish to here.
// Notice: This block does NOT have the same function as the block in the top level.
// The repositories here will be used for publishing your artifact, not for
// retrieving dependencies.
maven("https://maven.foxgalaxy.de/private") {
name = "foxgalaxy"
credentials(PasswordCredentials::class.java)
}
}
}
+10 -9
View File
@@ -3,23 +3,24 @@ org.gradle.jvmargs=-Xmx1G
# Fabric Properties # Fabric Properties
# check these on https://modmuss50.me/fabric.html # check these on https://modmuss50.me/fabric.html
minecraft_version=1.21.7 loom_version=1.16-SNAPSHOT
loader_version=0.16.14 minecraft_version=26.1.2
yarn_mappings=1.21.7+build.2 loader_version=0.19.2
# Mod Properties # Mod Properties
mod_version = 1.0.0-SNAPSHOT-1 mod_version = 1.0.0-TEST-1
maven_group = io.github.skippyall maven_group = io.github.skippyall
archives_base_name = Minions archives_base_name = Minions
# Dependencies # Dependencies
# check this on https://modmuss50.me/fabric.html # check this on https://modmuss50.me/fabric.html
fabric_version=0.128.1+1.21.7 fabric_api_version=0.147.0+26.1.2
fabric_kotlin_version=1.13.11+kotlin.2.3.21
polymer_version=0.13.3+1.21.6 polymer_version=0.16.3+26.1.2
sgui_version=1.10.0+1.21.6 sgui_version=2.0.0+26.1
server_translations_version=2.5.1+1.21.5 server_translations_version=3.0.3+26.1
night_config_version=3.8.3 night_config_version=3.8.3
universal_graves_version=3.8.0+1.21.6 universal_graves_version=3.11.0+26.1.2
Binary file not shown.
+7 -1
View File
@@ -1 +1,7 @@
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+248
View File
@@ -0,0 +1,248 @@
#!/bin/sh
#
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
Vendored
+93
View File
@@ -0,0 +1,93 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
bash gradlew publish -PfoxgalaxyPassword=$(read -s -p "Enter Password: ")
-9
View File
@@ -1,9 +0,0 @@
pluginManagement {
repositories {
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
gradlePluginPortal()
}
}
+17
View File
@@ -0,0 +1,17 @@
pluginManagement {
repositories {
maven {
name = "Fabric"
url = uri("https://maven.fabricmc.net/")
}
mavenCentral()
gradlePluginPortal()
}
plugins {
id("net.fabricmc.fabric-loom") version providers.gradleProperty("loom_version")
}
}
// Should match your modid
rootProject.name = "minions"
@@ -1,17 +1,12 @@
package io.github.skippyall.minions.client; package io.github.skippyall.minions.client;
import eu.pb4.polymer.networking.api.client.PolymerClientNetworking; import eu.pb4.polymer.networking.api.client.PolymerClientNetworking;
import io.github.skippyall.minions.registration.MinionBlocks; import io.github.skippyall.minions.polymer.VersionSync;
import io.github.skippyall.minions.util.PolymerUtil;
import net.fabricmc.api.ClientModInitializer; import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.rendering.v1.BlockRenderLayerMap;
import net.minecraft.client.render.BlockRenderLayer;
public class MinionsClient implements ClientModInitializer { public class MinionsClient implements ClientModInitializer {
@Override @Override
public void onInitializeClient() { public void onInitializeClient() {
BlockRenderLayerMap.putBlock(MinionBlocks.MINION_TRIGGER_BLOCK, BlockRenderLayer.TRANSLUCENT); PolymerClientNetworking.registerCommonHandler(VersionSync.VersionSyncPayload.class, (client, handler, payload) -> {});
PolymerClientNetworking.registerCommonHandler(PolymerUtil.VersionSyncPayload.class, (client, handler, payload) -> {});
} }
} }
@@ -21,10 +21,10 @@ public class MinionMixinConfigPlugin implements IMixinConfigPlugin {
@Override @Override
public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { public boolean shouldApplyMixin(String targetClassName, String mixinClassName) {
if(mixinClassName.startsWith("io.github.skippyall.mixins.compat.universal_graves.")) { if(mixinClassName.startsWith("io.github.skippyall.minions.mixins.compat.universal_graves.")) {
return MinionsConfig.get().compat.enableGravesCompat && FabricLoader.getInstance().isModLoaded("universal-graves"); return MinionsConfig.get().compat.enableGravesCompat && FabricLoader.getInstance().isModLoaded("universal-graves");
} }
if(mixinClassName.startsWith("io.github.skippyall.mixins.antimobcap.")) { if(mixinClassName.startsWith("io.github.skippyall.minions.mixins.antimobcap.")) {
return MinionsConfig.get().minion.enableMobCapHacks; return MinionsConfig.get().minion.enableMobCapHacks;
} }
return true; return true;
@@ -2,13 +2,16 @@ package io.github.skippyall.minions;
import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils; import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils;
import io.github.skippyall.minions.command.MinionsCommand; import io.github.skippyall.minions.command.MinionsCommand;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; import io.github.skippyall.minions.docs.DocsManager;
import io.github.skippyall.minions.minion.MinionPersistentState; import io.github.skippyall.minions.minion.MinionPersistentState;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.polymer.VersionSync;
import io.github.skippyall.minions.registration.MinionRegistration; import io.github.skippyall.minions.registration.MinionRegistration;
import io.github.skippyall.minions.util.PolymerUtil;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
import net.minecraft.server.packs.PackType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -23,24 +26,20 @@ public class Minions implements ModInitializer {
MinionRegistration.register(); MinionRegistration.register();
PolymerUtil.register(); VersionSync.register();
ServerLifecycleEvents.SERVER_STARTED.register(server -> { ServerLifecycleEvents.SERVER_STARTED.register(server -> {
MinionPersistentState.get(server).getMinionData().forEach((uuid, data) -> { MinionPersistentState.get(server).getMinionData().forEach((uuid, data) -> {
if(data.isSpawned()) { if(data.isSpawned()) {
MinionFakePlayer.spawnMinion(data, server.getOverworld(), null, null, true); MinionFakePlayer.spawnMinion(data, server.overworld(), null, null, true);
} }
}); });
}); });
CommandRegistrationCallback.EVENT.register(MinionsCommand::register); CommandRegistrationCallback.EVENT.register(MinionsCommand::register);
/*ServerBlockEntityEvents.BLOCK_ENTITY_LOAD.register((blockEntity, world) -> {
if(blockEntity instanceof MinionTriggerBlockEntity) {
world.updateComparators(blockEntity.getPos(), MinionBlocks.MINION_TRIGGER_BLOCK);
}
});*/
PolymerResourcePackUtils.addModAssets(Minions.MOD_ID); PolymerResourcePackUtils.addModAssets(Minions.MOD_ID);
ResourceManagerHelper.get(PackType.SERVER_DATA).registerReloadListener(new DocsManager());
} }
} }
@@ -8,7 +8,6 @@ import com.electronwill.nightconfig.core.serde.ObjectDeserializer;
import com.electronwill.nightconfig.core.serde.ObjectSerializer; import com.electronwill.nightconfig.core.serde.ObjectSerializer;
import com.electronwill.nightconfig.core.serde.SerdeException; import com.electronwill.nightconfig.core.serde.SerdeException;
import com.electronwill.nightconfig.core.serde.annotations.SerdeComment; import com.electronwill.nightconfig.core.serde.annotations.SerdeComment;
import com.electronwill.nightconfig.core.serde.annotations.SerdeSkipDeserializingIf;
import com.electronwill.nightconfig.toml.TomlFormat; import com.electronwill.nightconfig.toml.TomlFormat;
import com.electronwill.nightconfig.toml.TomlParser; import com.electronwill.nightconfig.toml.TomlParser;
import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.FabricLoader;
@@ -17,31 +16,24 @@ import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import static com.electronwill.nightconfig.core.serde.annotations.SerdeSkipDeserializingIf.SkipDeIf.IS_MISSING;
public class MinionsConfig { public class MinionsConfig {
private static MinionsConfig INSTANCE; private static MinionsConfig INSTANCE;
@SerdeSkipDeserializingIf(IS_MISSING)
public Minion minion = new Minion(); public Minion minion = new Minion();
public static class Minion { public static class Minion {
@SerdeComment("The prefix for all minion names") @SerdeComment("The prefix for all minion names")
@SerdeSkipDeserializingIf(IS_MISSING)
public String minionPrefix = "+"; public String minionPrefix = "+";
@SerdeComment("Makes minions not raise the mob cap if they can't spawn mobs.") @SerdeComment("Makes minions not raise the mob cap if they can't spawn mobs.")
@SerdeComment("Might cause incompatibilities.") @SerdeComment("Might cause incompatibilities.")
@SerdeSkipDeserializingIf(IS_MISSING)
public boolean enableMobCapHacks = true; public boolean enableMobCapHacks = true;
} }
@SerdeSkipDeserializingIf(IS_MISSING)
public Compat compat = new Compat(); public Compat compat = new Compat();
public static class Compat { public static class Compat {
@SerdeComment("Enables compat with Universal Graves, which allows everyone to pick up graves from minions") @SerdeComment("Enables compat with Universal Graves, which allows everyone to pick up graves from minions")
@SerdeSkipDeserializingIf(IS_MISSING)
public boolean enableGravesCompat = true; public boolean enableGravesCompat = true;
} }
@@ -66,12 +58,15 @@ public class MinionsConfig {
public static void loadConfig() { public static void loadConfig() {
try { try {
CommentedConfig defaultConfig = ObjectSerializer.standard().serializeFields(new MinionsConfig(), TomlFormat::newConfig);
CommentedConfig config = new TomlParser().parse(getPath(), (file, configFormat) -> { CommentedConfig config = new TomlParser().parse(getPath(), (file, configFormat) -> {
CommentedConfig defaultConfig = ObjectSerializer.standard().serializeFields(new MinionsConfig(), TomlFormat::newConfig);
configFormat.createWriter().write(defaultConfig, file, WritingMode.REPLACE); configFormat.createWriter().write(defaultConfig, file, WritingMode.REPLACE);
return true; return true;
}); });
//Always use default values when entries are missing
config.addAll(defaultConfig);
INSTANCE = ObjectDeserializer.standard().deserializeFields(config, MinionsConfig::new); INSTANCE = ObjectDeserializer.standard().deserializeFields(config, MinionsConfig::new);
} catch (SerdeException | ParsingException | WritingException e) { } catch (SerdeException | ParsingException | WritingException e) {
Minions.LOGGER.error("Error while reading config", e); Minions.LOGGER.error("Error while reading config", e);
@@ -0,0 +1,24 @@
package io.github.skippyall.minions.block.input;
import io.github.skippyall.minions.clipboard.ClipboardItem;
import net.minecraft.core.BlockPos;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
public class AnalogInputBlock extends Block {
public AnalogInputBlock(Properties settings) {
super(settings);
}
@Override
protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) {
if(!world.isClientSide()) {
player.getInventory().placeItemBackInInventory(ClipboardItem.createBlockPosReference(world, pos), true);
}
return InteractionResult.SUCCESS;
}
}
@@ -0,0 +1,65 @@
package io.github.skippyall.minions.block.instruction_bound;
import io.github.skippyall.minions.block.miniontrigger.MinionTriggerBlockEntity;
import io.github.skippyall.minions.clipboard.InstructionClipboard;
import io.github.skippyall.minions.minion.MinionPersistentState;
import io.github.skippyall.minions.registration.MinionBlocks;
import io.github.skippyall.minions.registration.MinionComponentTypes;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundSoundPacket;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
public abstract class InstructionBoundBlock extends Block implements EntityBlock {
public InstructionBoundBlock(Properties settings) {
super(settings);
}
protected abstract BlockEntityType<? extends InstructionBoundBlockEntity<?>> getBlockEntityType();
@Override
protected InteractionResult useItemOn(ItemStack stack, BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) {
if(stack.get(MinionComponentTypes.REFERENCE) instanceof InstructionClipboard instruction && player instanceof ServerPlayer serverPlayer) {
world.getBlockEntity(pos, getBlockEntityType()).ifPresent(be -> {
be.setInstruction(instruction.selectedMinion(), instruction.selectedInstruction());
serverPlayer.connection.send(new ClientboundSoundPacket(SoundEvents.NOTE_BLOCK_CHIME, SoundSource.BLOCKS, pos.getX(), pos.getY(), pos.getZ(), 1, 1, 0));
stack.shrink(1);
});
return InteractionResult.SUCCESS;
}
return super.useItemOn(stack, state, world, pos, player, hand, hit);
}
@Override
protected InteractionResult useWithoutItem(BlockState state, Level world, BlockPos pos, Player player, BlockHitResult hit) {
if(world.isClientSide()) {
return InteractionResult.CONSUME;
}
world.getBlockEntity(pos, getBlockEntityType()).ifPresent(be -> {
String name = MinionPersistentState.get(world.getServer()).getMinionData(be.getMinionUuid()).name();
player.sendSystemMessage(Component.translatable("minions.reference.instruction.tooltip", be.getInstructionName(), name));
});
return InteractionResult.SUCCESS;
}
@Override
protected void affectNeighborsAfterRemoval(BlockState state, ServerLevel world, BlockPos pos, boolean moved) {
super.affectNeighborsAfterRemoval(state, world, pos, moved);
world.getBlockEntity(pos, MinionBlocks.MINION_TRIGGER_BE_TYPE).ifPresent(MinionTriggerBlockEntity::removeListener);
}
}
@@ -0,0 +1,78 @@
package io.github.skippyall.minions.block.instruction_bound;
import io.github.skippyall.minions.listener.BlockEntityMinionListener;
import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.program.instruction.ConfiguredInstruction;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import java.util.Optional;
import java.util.UUID;
public abstract class InstructionBoundBlockEntity<L extends BlockEntityMinionListener<?>> extends BlockEntity {
protected UUID minionUuid;
protected String instructionName = "";
public InstructionBoundBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
super(type, pos, state);
}
protected abstract L createListener();
protected abstract Class<L> getListenerClass();
public void removeListener() {
if(level instanceof ServerLevel serverWorld) {
L listener = getListener();
if(listener != null) {
listener.remove(serverWorld.getServer());
}
}
}
public void addListener() {
if(level instanceof ServerLevel serverWorld) {
L listener = createListener();
listener.add(serverWorld.getServer());
}
}
public void setInstruction(UUID minionUuid, String instructionName) {
removeListener();
this.minionUuid = minionUuid;
this.instructionName = instructionName;
addListener();
setChanged();
}
public Optional<MinionFakePlayer> getMinion() {
if(minionUuid != null && level != null && level.getPlayerByUUID(minionUuid) instanceof MinionFakePlayer minion) {
return Optional.of(minion);
}
return Optional.empty();
}
public UUID getMinionUuid() {
return minionUuid;
}
public String getInstructionName() {
return instructionName;
}
public Optional<ConfiguredInstruction<MinionRuntime>> getInstruction(MinionFakePlayer minion) {
return Optional.ofNullable(minion.getInstructionManager().getInstruction(instructionName));
}
public Optional<ConfiguredInstruction<MinionRuntime>> getInstruction() {
return getMinion().flatMap(this::getInstruction);
}
public L getListener() {
return BlockEntityMinionListener.getListener(level, worldPosition, minionUuid, getListenerClass());
}
}
@@ -3,165 +3,123 @@ package io.github.skippyall.minions.block.miniontrigger;
import com.mojang.serialization.MapCodec; import com.mojang.serialization.MapCodec;
import eu.pb4.polymer.core.api.block.PolymerBlock; import eu.pb4.polymer.core.api.block.PolymerBlock;
import eu.pb4.polymer.core.api.utils.PolymerClientDecoded; import eu.pb4.polymer.core.api.utils.PolymerClientDecoded;
import eu.pb4.polymer.core.api.utils.PolymerKeepModel;
import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils; import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils;
import eu.pb4.polymer.virtualentity.api.BlockWithElementHolder; import eu.pb4.polymer.virtualentity.api.BlockWithElementHolder;
import eu.pb4.polymer.virtualentity.api.ElementHolder; import eu.pb4.polymer.virtualentity.api.ElementHolder;
import eu.pb4.polymer.virtualentity.api.elements.ItemDisplayElement; import eu.pb4.polymer.virtualentity.api.elements.ItemDisplayElement;
import io.github.skippyall.minions.registration.MinionBlocks;
import io.github.skippyall.minions.Minions; import io.github.skippyall.minions.Minions;
import io.github.skippyall.minions.minion.MinionPersistentState; import io.github.skippyall.minions.block.instruction_bound.InstructionBoundBlock;
import io.github.skippyall.minions.clipboard.InstructionClipboard; import io.github.skippyall.minions.polymer.VersionSync;
import io.github.skippyall.minions.registration.MinionComponentTypes; import io.github.skippyall.minions.registration.MinionBlocks;
import io.github.skippyall.minions.util.PolymerUtil; import net.fabricmc.fabric.api.networking.v1.context.PacketContext;
import net.minecraft.block.AbstractRedstoneGateBlock; import net.minecraft.core.BlockPos;
import net.minecraft.block.Block; import net.minecraft.core.Direction;
import net.minecraft.block.BlockState; import net.minecraft.core.component.DataComponents;
import net.minecraft.block.BlockWithEntity; import net.minecraft.resources.Identifier;
import net.minecraft.block.ShapeContext; import net.minecraft.server.level.ServerLevel;
import net.minecraft.block.SideShapeType; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.block.entity.BlockEntity; import net.minecraft.server.network.ServerGamePacketListenerImpl;
import net.minecraft.component.DataComponentTypes; import net.minecraft.world.item.ItemDisplayContext;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.world.item.ItemStack;
import net.minecraft.item.ItemStack; import net.minecraft.world.item.Items;
import net.minecraft.item.Items; import net.minecraft.world.level.BlockGetter;
import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.world.level.Level;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.world.level.LevelReader;
import net.minecraft.server.world.ServerWorld; import net.minecraft.world.level.block.Block;
import net.minecraft.sound.SoundCategory; import net.minecraft.world.level.block.DiodeBlock;
import net.minecraft.sound.SoundEvents; import net.minecraft.world.level.block.SupportType;
import net.minecraft.state.StateManager; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.state.property.BooleanProperty; import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.text.Text; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.util.ActionResult; import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.util.Hand; import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.util.Identifier; import net.minecraft.world.level.redstone.Orientation;
import net.minecraft.util.hit.BlockHitResult; import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.util.math.BlockPos; import net.minecraft.world.phys.shapes.VoxelShape;
import net.minecraft.util.math.Direction;
import net.minecraft.util.shape.VoxelShape;
import net.minecraft.world.BlockView;
import net.minecraft.world.World;
import net.minecraft.world.WorldView;
import net.minecraft.world.block.WireOrientation;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import xyz.nucleoid.packettweaker.PacketContext;
public class MinionTriggerBlock extends BlockWithEntity implements PolymerBlock, PolymerKeepModel, PolymerClientDecoded, BlockWithElementHolder { public class MinionTriggerBlock extends InstructionBoundBlock implements PolymerBlock, PolymerClientDecoded, BlockWithElementHolder {
public static final MapCodec<MinionTriggerBlock> CODEC = createCodec(MinionTriggerBlock::new); public static final MapCodec<MinionTriggerBlock> CODEC = simpleCodec(MinionTriggerBlock::new);
public static final BooleanProperty POWERED = BooleanProperty.of("powered"); public static final BooleanProperty POWERED = BooleanProperty.create("powered");
public static final VoxelShape SHAPE = Block.createColumnShape(16.0, 0.0, 2.0); public static final VoxelShape SHAPE = Block.column(16.0, 0.0, 2.0);
public MinionTriggerBlock(Settings settings) { public MinionTriggerBlock(Properties settings) {
super(settings); super(settings);
setDefaultState(getDefaultState().with(POWERED, false)); registerDefaultState(defaultBlockState().setValue(POWERED, false));
} }
@Override @Override
protected VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) { protected VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) {
return SHAPE; return SHAPE;
} }
@Override @Override
protected boolean canPlaceAt(BlockState state, WorldView world, BlockPos pos) { protected boolean canSurvive(BlockState state, LevelReader world, BlockPos pos) {
BlockPos blockPos = pos.down(); BlockPos blockPos = pos.below();
return this.canPlaceAbove(world, blockPos, world.getBlockState(blockPos)); return this.canPlaceAbove(world, blockPos, world.getBlockState(blockPos));
} }
protected boolean canPlaceAbove(WorldView world, BlockPos pos, BlockState state) { protected boolean canPlaceAbove(LevelReader world, BlockPos pos, BlockState state) {
return state.isSideSolid(world, pos, Direction.UP, SideShapeType.RIGID); return state.isFaceSturdy(world, pos, Direction.UP, SupportType.RIGID);
} }
@Override @Override
protected void onStateReplaced(BlockState state, ServerWorld world, BlockPos pos, boolean moved) { protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
super.onStateReplaced(state, world, pos, moved);
world.getBlockEntity(pos, MinionBlocks.MINION_TRIGGER_BE_TYPE).ifPresent(MinionTriggerBlockEntity::removeListener);
}
@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
builder.add(POWERED); builder.add(POWERED);
} }
@Override @Override
protected ActionResult onUseWithItem(ItemStack stack, BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit) { protected void neighborChanged(BlockState state, Level world, BlockPos pos, Block sourceBlock, @Nullable Orientation wireOrientation, boolean notify) {
if(stack.get(MinionComponentTypes.REFERENCE) instanceof InstructionClipboard instruction) { if(!canSurvive(state, world, pos)) {
world.getBlockEntity(pos, MinionBlocks.MINION_TRIGGER_BE_TYPE).ifPresent(be -> { dropResources(state, world, pos);
be.setInstruction(instruction.selectedMinion(), instruction.selectedInstruction());
player.playSoundToPlayer(SoundEvents.BLOCK_NOTE_BLOCK_CHIME.value(), SoundCategory.BLOCKS, 1, 1);
stack.decrement(1);
});
return ActionResult.SUCCESS;
}
return super.onUseWithItem(stack, state, world, pos, player, hand, hit);
}
@Override
protected ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, BlockHitResult hit) {
if(world.isClient()) {
return ActionResult.CONSUME;
}
world.getBlockEntity(pos, MinionBlocks.MINION_TRIGGER_BE_TYPE).ifPresent(be -> {
String name = MinionPersistentState.get(world.getServer()).getMinionData(be.getMinionUuid()).name();
player.sendMessage(Text.translatable("minions.reference.instruction.tooltip", name, be.getInstructionName()), true);
});
return ActionResult.SUCCESS;
}
@Override
protected void neighborUpdate(BlockState state, World world, BlockPos pos, Block sourceBlock, @Nullable WireOrientation wireOrientation, boolean notify) {
if(!canPlaceAt(state, world, pos)) {
dropStacks(state, world, pos);
world.removeBlock(pos, false); world.removeBlock(pos, false);
return; return;
} }
boolean newPower = world.isReceivingRedstonePower(pos); boolean newPower = world.hasNeighborSignal(pos);
if(state.get(POWERED) != newPower) { if(state.getValue(POWERED) != newPower) {
world.setBlockState(pos, state.with(POWERED, newPower)); world.setBlockAndUpdate(pos, state.setValue(POWERED, newPower));
world.getBlockEntity(pos, MinionBlocks.MINION_TRIGGER_BE_TYPE).ifPresent(MinionTriggerBlockEntity::updatePower); world.getBlockEntity(pos, MinionBlocks.MINION_TRIGGER_BE_TYPE).ifPresent(MinionTriggerBlockEntity::updatePower);
} }
} }
@Override @Override
protected boolean hasComparatorOutput(BlockState state) { protected boolean hasAnalogOutputSignal(BlockState state) {
return true; return true;
} }
@Override @Override
protected int getComparatorOutput(BlockState state, World world, BlockPos pos) { protected int getAnalogOutputSignal(BlockState state, Level world, BlockPos pos, Direction direction) {
return world.getBlockEntity(pos, MinionBlocks.MINION_TRIGGER_BE_TYPE).map(MinionTriggerBlockEntity::getComparatorOutput).orElse(0); return world.getBlockEntity(pos, MinionBlocks.MINION_TRIGGER_BE_TYPE).map(MinionTriggerBlockEntity::getComparatorOutput).orElse(0);
} }
@Override @Override
protected MapCodec<? extends BlockWithEntity> getCodec() { public @Nullable BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return CODEC;
}
@Override
public @Nullable BlockEntity createBlockEntity(BlockPos pos, BlockState state) {
return new MinionTriggerBlockEntity(pos, state); return new MinionTriggerBlockEntity(pos, state);
} }
@Override @Override
public BlockState getPolymerBlockState(BlockState state, PacketContext context) { protected BlockEntityType<MinionTriggerBlockEntity> getBlockEntityType() {
return PolymerUtil.isOnClient(context) ? state : net.minecraft.block.Blocks.COMPARATOR.getDefaultState().with(AbstractRedstoneGateBlock.POWERED, state.get(POWERED)); return MinionBlocks.MINION_TRIGGER_BE_TYPE;
} }
@Override @Override
public boolean handleMiningOnServer(ItemStack tool, BlockState state, BlockPos pos, ServerPlayerEntity player) { public BlockState getPolymerBlockState(BlockState state, PacketContext context) {
return VersionSync.isOnClient(context) ? state : net.minecraft.world.level.block.Blocks.COMPARATOR.defaultBlockState().setValue(DiodeBlock.POWERED, state.getValue(POWERED));
}
@Override
public boolean handleMiningOnServer(ItemStack tool, BlockState state, BlockPos pos, ServerPlayer player) {
return false; return false;
} }
@Override @Override
public @Nullable ElementHolder createElementHolder(ServerWorld world, BlockPos pos, BlockState initialBlockState) { public @Nullable ElementHolder createElementHolder(ServerLevel world, BlockPos pos, BlockState initialBlockState) {
ElementHolder holder = new ElementHolder() { ElementHolder holder = new ElementHolder() {
@Override @Override
public boolean startWatching(ServerPlayNetworkHandler player) { public boolean startWatching(ServerGamePacketListenerImpl player) {
if(PolymerResourcePackUtils.hasMainPack(player)) { if(PolymerResourcePackUtils.hasMainPack(player) && !VersionSync.isOnClient(player)) {
return super.startWatching(player); return super.startWatching(player);
} else { } else {
return false; return false;
@@ -169,9 +127,11 @@ public class MinionTriggerBlock extends BlockWithEntity implements PolymerBlock,
} }
}; };
ItemStack stack = new ItemStack(Items.BARRIER); ItemStack stack = new ItemStack(Items.BARRIER);
stack.set(DataComponentTypes.ITEM_MODEL, Identifier.of(Minions.MOD_ID, "minion_trigger_no_plate_" + (initialBlockState.get(MinionTriggerBlock.POWERED) ? "active" : "inactive"))); stack.set(DataComponents.ITEM_MODEL, Identifier.fromNamespaceAndPath(Minions.MOD_ID, "minion_trigger_no_plate_" + (initialBlockState.getValue(MinionTriggerBlock.POWERED) ? "active" : "inactive")));
holder.addElement(new ItemDisplayElement(stack)); ItemDisplayElement element = new ItemDisplayElement(stack);
element.setItemDisplayContext(ItemDisplayContext.NONE);
holder.addElement(element);
return holder; return holder;
} }
} }
@@ -1,46 +1,30 @@
package io.github.skippyall.minions.block.miniontrigger; package io.github.skippyall.minions.block.miniontrigger;
import io.github.skippyall.minions.listener.BlockEntityMinionListener; import io.github.skippyall.minions.block.instruction_bound.InstructionBoundBlockEntity;
import io.github.skippyall.minions.registration.MinionBlocks; import io.github.skippyall.minions.registration.MinionBlocks;
import io.github.skippyall.minions.minion.MinionRuntime; import net.minecraft.core.BlockPos;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; import net.minecraft.core.UUIDUtil;
import io.github.skippyall.minions.program.instruction.ConfiguredInstruction; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.block.BlockState; import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.block.entity.BlockEntity; import net.minecraft.world.level.storage.ValueOutput;
import net.minecraft.storage.ReadView;
import net.minecraft.storage.WriteView;
import net.minecraft.util.Uuids;
import net.minecraft.util.math.BlockPos;
import java.util.Optional;
import java.util.UUID;
public class MinionTriggerBlockEntity extends BlockEntity {
private UUID minionUuid;
private String instructionName = "";
public class MinionTriggerBlockEntity extends InstructionBoundBlockEntity<MinionTriggerMinionListener> {
public MinionTriggerBlockEntity(BlockPos pos, BlockState state) { public MinionTriggerBlockEntity(BlockPos pos, BlockState state) {
super(MinionBlocks.MINION_TRIGGER_BE_TYPE, pos, state); super(MinionBlocks.MINION_TRIGGER_BE_TYPE, pos, state);
} }
public void removeListener() { @Override
MinionTriggerMinionListener.removeListener(world, pos, minionUuid, instructionName); protected MinionTriggerMinionListener createListener() {
return new MinionTriggerMinionListener(level.dimension(), worldPosition, minionUuid, instructionName);
} }
public void addListener() { @Override
MinionTriggerMinionListener.addListener(world, pos, minionUuid, instructionName); protected Class<MinionTriggerMinionListener> getListenerClass() {
} return MinionTriggerMinionListener.class;
public void setInstruction(UUID minionUuid, String instructionName) {
removeListener();
this.minionUuid = minionUuid;
this.instructionName = instructionName;
addListener();
markDirty();
} }
public void updatePower() { public void updatePower() {
boolean powered = getCachedState().get(MinionTriggerBlock.POWERED); boolean powered = getBlockState().getValue(MinionTriggerBlock.POWERED);
MinionTriggerMinionListener listener = getListener(); MinionTriggerMinionListener listener = getListener();
if(listener != null) { if(listener != null) {
@@ -66,43 +50,16 @@ public class MinionTriggerBlockEntity extends BlockEntity {
return 0; return 0;
} }
public Optional<MinionFakePlayer> getMinion() { @Override
if(minionUuid != null && world != null && world.getPlayerByUuid(minionUuid) instanceof MinionFakePlayer minion) { protected void loadAdditional(ValueInput view) {
return Optional.of(minion); minionUuid = view.read("minionUuid", UUIDUtil.AUTHLIB_CODEC).orElse(null);
} instructionName = view.getStringOr("instructionName", "");
return Optional.empty();
}
public UUID getMinionUuid() {
return minionUuid;
}
public String getInstructionName() {
return instructionName;
}
public Optional<ConfiguredInstruction<MinionRuntime>> getInstruction(MinionFakePlayer minion) {
return Optional.ofNullable(minion.getInstructionManager().getInstruction(instructionName));
}
public Optional<ConfiguredInstruction<MinionRuntime>> getInstruction() {
return getMinion().flatMap(this::getInstruction);
}
public MinionTriggerMinionListener getListener() {
return BlockEntityMinionListener.getListener(world, pos, minionUuid, MinionTriggerMinionListener.class);
} }
@Override @Override
protected void readData(ReadView view) { protected void saveAdditional(ValueOutput view) {
minionUuid = view.read("minionUuid", Uuids.CODEC).orElse(null);
instructionName = view.getString("instructionName", "");
}
@Override
protected void writeData(WriteView view) {
if(minionUuid != null) { if(minionUuid != null) {
view.put("minionUuid", Uuids.CODEC, minionUuid); view.store("minionUuid", UUIDUtil.AUTHLIB_CODEC, minionUuid);
} }
view.putString("instructionName", instructionName); view.putString("instructionName", instructionName);
} }
@@ -2,22 +2,23 @@ package io.github.skippyall.minions.block.miniontrigger;
import eu.pb4.polymer.core.api.item.PolymerBlockItem; import eu.pb4.polymer.core.api.item.PolymerBlockItem;
import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils; import eu.pb4.polymer.resourcepack.api.PolymerResourcePackUtils;
import net.minecraft.block.Block; import net.fabricmc.fabric.api.networking.v1.context.PacketContext;
import net.minecraft.item.Item; import net.minecraft.core.HolderLookup;
import net.minecraft.item.ItemStack; import net.minecraft.resources.Identifier;
import net.minecraft.util.Identifier; import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.Block;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import xyz.nucleoid.packettweaker.PacketContext;
public class MinionTriggerBlockItem extends PolymerBlockItem { public class MinionTriggerBlockItem extends PolymerBlockItem {
public MinionTriggerBlockItem(Block block, Settings settings, Item polymerItem) { public MinionTriggerBlockItem(Block block, Properties settings, Item polymerItem) {
super(block, settings, polymerItem, true); super(block, settings, polymerItem, true);
} }
@Override @Override
public @Nullable Identifier getPolymerItemModel(ItemStack stack, PacketContext context) { public @Nullable Identifier getPolymerItemModel(ItemStack stack, PacketContext context, HolderLookup.Provider lookup) {
if(PolymerResourcePackUtils.hasMainPack(context)) { if(PolymerResourcePackUtils.hasMainPack(context)) {
return super.getPolymerItemModel(stack, context); return super.getPolymerItemModel(stack, context, lookup);
} else { } else {
return null; return null;
} }
@@ -5,16 +5,16 @@ import com.mojang.serialization.codecs.RecordCodecBuilder;
import io.github.skippyall.minions.Minions; import io.github.skippyall.minions.Minions;
import io.github.skippyall.minions.listener.BlockEntityMinionInstructionListener; import io.github.skippyall.minions.listener.BlockEntityMinionInstructionListener;
import io.github.skippyall.minions.minion.MinionRuntime; import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.registration.MinionBlocks;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.program.instruction.ConfiguredInstruction; import io.github.skippyall.minions.program.instruction.ConfiguredInstruction;
import io.github.skippyall.minions.program.instruction.ConfiguredInstructionListener; import io.github.skippyall.minions.program.instruction.ConfiguredInstructionListener;
import net.minecraft.registry.RegistryKey; import io.github.skippyall.minions.registration.MinionBlocks;
import net.minecraft.core.BlockPos;
import net.minecraft.core.UUIDUtil;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.util.Identifier; import net.minecraft.world.level.Level;
import net.minecraft.util.Uuids;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@@ -24,9 +24,9 @@ import java.util.UUID;
public class MinionTriggerMinionListener extends BlockEntityMinionInstructionListener<MinionTriggerBlockEntity> { public class MinionTriggerMinionListener extends BlockEntityMinionInstructionListener<MinionTriggerBlockEntity> {
public static final Codec<MinionTriggerMinionListener> CODEC = RecordCodecBuilder.create(instance -> public static final Codec<MinionTriggerMinionListener> CODEC = RecordCodecBuilder.create(instance ->
instance.group( instance.group(
World.CODEC.fieldOf("world").forGetter(listener -> listener.worldKey), Level.RESOURCE_KEY_CODEC.fieldOf("world").forGetter(listener -> listener.worldKey),
BlockPos.CODEC.fieldOf("pos").forGetter(listener -> listener.pos), BlockPos.CODEC.fieldOf("pos").forGetter(listener -> listener.pos),
Uuids.CODEC.fieldOf("minionUuid").forGetter(listener -> listener.minionUuid), UUIDUtil.AUTHLIB_CODEC.fieldOf("minionUuid").forGetter(listener -> listener.minionUuid),
Codec.STRING.fieldOf("instructionName").forGetter(listener -> listener.instructionName) Codec.STRING.fieldOf("instructionName").forGetter(listener -> listener.instructionName)
).apply(instance, MinionTriggerMinionListener::new)); ).apply(instance, MinionTriggerMinionListener::new));
@@ -36,23 +36,11 @@ public class MinionTriggerMinionListener extends BlockEntityMinionInstructionLis
boolean runningCache; boolean runningCache;
boolean incomingPowerCache; boolean incomingPowerCache;
private MinionTriggerMinionListener(RegistryKey<World> worldKey, BlockPos pos, UUID minionUuid, String instructionName) { MinionTriggerMinionListener(ResourceKey<Level> worldKey, BlockPos pos, UUID minionUuid, String instructionName) {
super(worldKey, pos, minionUuid, MinionBlocks.MINION_TRIGGER_BE_TYPE); super(worldKey, pos, minionUuid, MinionBlocks.MINION_TRIGGER_BE_TYPE);
this.instructionName = Objects.requireNonNull(instructionName); this.instructionName = Objects.requireNonNull(instructionName);
} }
public static void addListener(World world, BlockPos pos, UUID minion, String instructionName) {
MinionTriggerMinionListener listener = new MinionTriggerMinionListener(world.getRegistryKey(), pos, minion, instructionName);
listener.add(world.getServer());
}
public static void removeListener(World world, BlockPos pos, UUID minion, String instructionName) {
MinionTriggerMinionListener old = getListener(world, pos, minion, MinionTriggerMinionListener.class);
if(old != null) {
old.remove(world.getServer());
}
}
@Override @Override
protected Map<String, ConfiguredInstructionListener> getInstructionListeners() { protected Map<String, ConfiguredInstructionListener> getInstructionListeners() {
return Map.of(instructionName, listener); return Map.of(instructionName, listener);
@@ -88,7 +76,7 @@ public class MinionTriggerMinionListener extends BlockEntityMinionInstructionLis
} }
@Override @Override
protected void add(MinecraftServer server) { public void add(MinecraftServer server) {
super.add(server); super.add(server);
runningCache = minion.getInstructionManager().getInstruction(instructionName).isRunning(); runningCache = minion.getInstructionManager().getInstruction(instructionName).isRunning();
updateComparatorsIfLoaded(server); updateComparatorsIfLoaded(server);
@@ -96,13 +84,13 @@ public class MinionTriggerMinionListener extends BlockEntityMinionInstructionLis
@Override @Override
public Optional<Identifier> getCodecId() { public Optional<Identifier> getCodecId() {
return Optional.of(Identifier.of(Minions.MOD_ID, "minion_trigger")); return Optional.of(Identifier.fromNamespaceAndPath(Minions.MOD_ID, "minion_trigger"));
} }
public void updateComparatorsIfLoaded(MinecraftServer server) { public void updateComparatorsIfLoaded(MinecraftServer server) {
World world = server.getWorld(worldKey); Level world = server.getLevel(worldKey);
if(world.isPosLoaded(pos)) { if(world.isLoaded(pos)) {
world.updateComparators(pos, MinionBlocks.MINION_TRIGGER_BLOCK); world.updateNeighbourForOutputSignal(pos, MinionBlocks.MINION_TRIGGER_BLOCK);
} }
} }
@@ -0,0 +1,33 @@
package io.github.skippyall.minions.clipboard;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.core.BlockPos;
import net.minecraft.core.component.DataComponentGetter;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
import java.util.function.Consumer;
public record BlockPosClipboard(ResourceKey<Level> world, BlockPos pos) implements Clipboard {
public static final MapCodec<BlockPosClipboard> CODEC = RecordCodecBuilder.mapCodec(instance ->
instance.group(
Level.RESOURCE_KEY_CODEC.fieldOf("world").forGetter(BlockPosClipboard::world),
BlockPos.CODEC.fieldOf("pos").forGetter(BlockPosClipboard::pos)
).apply(instance, BlockPosClipboard::new)
);
@Override
public MapCodec<BlockPosClipboard> getCodec() {
return CODEC;
}
@Override
public void addToTooltip(Item.TooltipContext context, Consumer<Component> textConsumer, TooltipFlag type, DataComponentGetter components) {
textConsumer.accept(Component.translatable("minions.reference.block.tooltip", pos.toString()));
}
}
@@ -3,12 +3,12 @@ package io.github.skippyall.minions.clipboard;
import com.mojang.serialization.Codec; import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec; import com.mojang.serialization.MapCodec;
import io.github.skippyall.minions.registration.MinionRegistries; import io.github.skippyall.minions.registration.MinionRegistries;
import net.minecraft.item.tooltip.TooltipAppender; import net.minecraft.world.item.component.TooltipProvider;
import java.util.function.Function; import java.util.function.Function;
public interface Clipboard extends TooltipAppender { public interface Clipboard extends TooltipProvider {
Codec<Clipboard> CODEC = MinionRegistries.CLIPBOARD_TYPES.getCodec().dispatch(Clipboard::getCodec, Function.identity()); Codec<Clipboard> CODEC = MinionRegistries.CLIPBOARD_TYPES.byNameCodec().dispatch(Clipboard::getCodec, Function.identity());
MapCodec<? extends Clipboard> getCodec(); MapCodec<? extends Clipboard> getCodec();
} }
@@ -1,43 +1,52 @@
package io.github.skippyall.minions.clipboard; package io.github.skippyall.minions.clipboard;
import eu.pb4.polymer.core.api.item.PolymerItem; import eu.pb4.polymer.core.api.item.PolymerItem;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.registration.MinionComponentTypes; import io.github.skippyall.minions.registration.MinionComponentTypes;
import io.github.skippyall.minions.registration.MinionItems; import io.github.skippyall.minions.registration.MinionItems;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; import net.fabricmc.fabric.api.networking.v1.context.PacketContext;
import net.minecraft.component.DataComponentTypes; import net.minecraft.core.BlockPos;
import net.minecraft.item.Item; import net.minecraft.core.HolderLookup;
import net.minecraft.item.ItemStack; import net.minecraft.core.component.DataComponents;
import net.minecraft.item.Items; import net.minecraft.resources.Identifier;
import net.minecraft.item.tooltip.TooltipType; import net.minecraft.world.item.Item;
import net.minecraft.util.Identifier; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import xyz.nucleoid.packettweaker.PacketContext;
public class ClipboardItem extends Item implements PolymerItem { public class ClipboardItem extends Item implements PolymerItem {
public ClipboardItem(Settings settings) { public ClipboardItem(Properties settings) {
super(settings); super(settings);
} }
@Override @Override
public Item getPolymerItem(ItemStack itemStack, PacketContext context) { public Item getPolymerItem(ItemStack itemStack, PacketContext context) {
return Items.PAPER; return /*VersionSync.isOnClient(context) ? this : */Items.PAPER;
} }
@Override @Override
public @Nullable Identifier getPolymerItemModel(ItemStack stack, PacketContext context) { public @Nullable Identifier getPolymerItemModel(ItemStack stack, PacketContext context, HolderLookup.Provider lookup) {
return null; return null;
} }
@Override @Override
public ItemStack getPolymerItemStack(ItemStack itemStack, TooltipType tooltipType, PacketContext context) { public ItemStack getPolymerItemStack(ItemStack itemStack, TooltipFlag tooltipType, PacketContext context, HolderLookup.Provider lookup) {
ItemStack stack = PolymerItem.super.getPolymerItemStack(itemStack, tooltipType, context); ItemStack stack = PolymerItem.super.getPolymerItemStack(itemStack, tooltipType, context, lookup);
stack.set(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, true); stack.set(DataComponents.ENCHANTMENT_GLINT_OVERRIDE, true);
return stack; return stack;
} }
public static ItemStack createInstructionReference(MinionFakePlayer minion, String instructionName) { public static ItemStack createInstructionReference(MinionFakePlayer minion, String instructionName) {
ItemStack stack = new ItemStack(MinionItems.REFERENCE_ITEM); ItemStack stack = new ItemStack(MinionItems.REFERENCE_ITEM);
stack.set(MinionComponentTypes.REFERENCE, new InstructionClipboard(minion.getUuid(), instructionName, minion.getGameProfile().getName())); stack.set(MinionComponentTypes.REFERENCE, new InstructionClipboard(minion.getUUID(), instructionName, minion.getGameProfile().name()));
return stack;
}
public static ItemStack createBlockPosReference(Level world, BlockPos pos) {
ItemStack stack = new ItemStack(MinionItems.REFERENCE_ITEM);
stack.set(MinionComponentTypes.REFERENCE, new BlockPosClipboard(world.dimension(), pos));
return stack; return stack;
} }
} }
@@ -3,11 +3,11 @@ package io.github.skippyall.minions.clipboard;
import com.mojang.serialization.Codec; import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec; import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder; import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.component.ComponentsAccess; import net.minecraft.core.UUIDUtil;
import net.minecraft.item.Item; import net.minecraft.core.component.DataComponentGetter;
import net.minecraft.item.tooltip.TooltipType; import net.minecraft.network.chat.Component;
import net.minecraft.text.Text; import net.minecraft.world.item.Item;
import net.minecraft.util.Uuids; import net.minecraft.world.item.TooltipFlag;
import java.util.UUID; import java.util.UUID;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -15,7 +15,7 @@ import java.util.function.Consumer;
public record InstructionClipboard(UUID selectedMinion, String selectedInstruction, String visualMinionName) implements Clipboard { public record InstructionClipboard(UUID selectedMinion, String selectedInstruction, String visualMinionName) implements Clipboard {
public static final MapCodec<InstructionClipboard> CODEC = RecordCodecBuilder.mapCodec(instance -> public static final MapCodec<InstructionClipboard> CODEC = RecordCodecBuilder.mapCodec(instance ->
instance.group( instance.group(
Uuids.CODEC.fieldOf("selectedMinion").forGetter(InstructionClipboard::selectedMinion), UUIDUtil.AUTHLIB_CODEC.fieldOf("selectedMinion").forGetter(InstructionClipboard::selectedMinion),
Codec.STRING.fieldOf("selectedInstruction").forGetter(InstructionClipboard::selectedInstruction), Codec.STRING.fieldOf("selectedInstruction").forGetter(InstructionClipboard::selectedInstruction),
Codec.STRING.fieldOf("visualMinionName").forGetter(InstructionClipboard::visualMinionName) Codec.STRING.fieldOf("visualMinionName").forGetter(InstructionClipboard::visualMinionName)
).apply(instance, InstructionClipboard::new)); ).apply(instance, InstructionClipboard::new));
@@ -26,7 +26,7 @@ public record InstructionClipboard(UUID selectedMinion, String selectedInstructi
} }
@Override @Override
public void appendTooltip(Item.TooltipContext context, Consumer<Text> textConsumer, TooltipType type, ComponentsAccess components) { public void addToTooltip(Item.TooltipContext context, Consumer<Component> textConsumer, TooltipFlag type, DataComponentGetter components) {
textConsumer.accept(Text.translatable("minions.reference.instruction.tooltip", selectedInstruction, visualMinionName)); textConsumer.accept(Component.translatable("minions.reference.instruction.tooltip", selectedInstruction, visualMinionName));
} }
} }
@@ -0,0 +1,36 @@
package io.github.skippyall.minions.command;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import io.github.skippyall.minions.docs.DocsManager;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.arguments.IdentifierArgument;
import net.minecraft.resources.Identifier;
import java.util.concurrent.CompletableFuture;
import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal;
public class DocsSubcommand {
public static final LiteralArgumentBuilder<CommandSourceStack> DOCS = literal("docs")
.then(
argument("docName", IdentifierArgument.id())
.suggests(DocsSubcommand::getSuggestions)
.executes(DocsSubcommand::execute)
);
public static CompletableFuture<Suggestions> getSuggestions(CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) {
DocsManager.getDocsEntryIds().forEach(id -> builder.suggest(id.toString()));
return CompletableFuture.completedFuture(builder.build());
}
public static int execute(CommandContext<CommandSourceStack> context) throws CommandSyntaxException {
Identifier id = IdentifierArgument.getId(context, "docName");
DocsManager.showDocsEntry(context.getSource().getPlayerOrException(), id);
return 1;
}
}
@@ -4,21 +4,21 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContext;
import io.github.skippyall.minions.minion.MinionData; import io.github.skippyall.minions.minion.MinionData;
import io.github.skippyall.minions.minion.MinionPersistentState; import io.github.skippyall.minions.minion.MinionPersistentState;
import net.minecraft.server.command.ServerCommandSource; import net.minecraft.commands.CommandSourceStack;
import net.minecraft.text.Text; import net.minecraft.network.chat.Component;
import java.util.Collection; import java.util.Collection;
import static net.minecraft.server.command.CommandManager.literal; import static net.minecraft.commands.Commands.literal;
public class ListSubcommand { public class ListSubcommand {
public static final LiteralArgumentBuilder<ServerCommandSource> LIST = literal("list") public static final LiteralArgumentBuilder<CommandSourceStack> LIST = literal("list")
.executes(ListSubcommand::list); .executes(ListSubcommand::list);
public static int list(CommandContext<ServerCommandSource> context) { public static int list(CommandContext<CommandSourceStack> context) {
Collection<MinionData> minions = MinionPersistentState.get(context.getSource().getServer()).getMinionData().values(); Collection<MinionData> minions = MinionPersistentState.get(context.getSource().getServer()).getMinionData().values();
for (MinionData minion : minions) { for (MinionData minion : minions) {
context.getSource().sendFeedback(() -> Text.literal(minion.name() + "(" + minion.uuid() + "):" + minion.isSpawned()), false); context.getSource().sendSuccess(() -> Component.literal(minion.name() + "(" + minion.uuid() + "):" + minion.isSpawned()), false);
} }
return 0; return 0;
} }
@@ -9,16 +9,15 @@ import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import io.github.skippyall.minions.minion.MinionData; import io.github.skippyall.minions.minion.MinionData;
import io.github.skippyall.minions.minion.MinionPersistentState; import io.github.skippyall.minions.minion.MinionPersistentState;
import io.github.skippyall.minions.minion.MinionProfileUtils; import io.github.skippyall.minions.minion.MinionProfileUtils;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.Text;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public class MinionArgument { public class MinionArgument {
public static final SimpleCommandExceptionType MINION_NOT_PRESENT = new SimpleCommandExceptionType(Text.translatable("minions.command.minion.not_present")); public static final SimpleCommandExceptionType MINION_NOT_PRESENT = new SimpleCommandExceptionType(Component.translatable("minions.command.minion.not_present"));
public static final MinionSuggestionProvider SUGGESTION_PROVIDER = new MinionSuggestionProvider(); public static final MinionSuggestionProvider SUGGESTION_PROVIDER = new MinionSuggestionProvider();
@@ -38,9 +37,9 @@ public class MinionArgument {
return data.get(); return data.get();
} }
public static class MinionSuggestionProvider implements SuggestionProvider<ServerCommandSource> { public static class MinionSuggestionProvider implements SuggestionProvider<CommandSourceStack> {
@Override @Override
public CompletableFuture<Suggestions> getSuggestions(CommandContext<ServerCommandSource> context, SuggestionsBuilder builder) throws CommandSyntaxException { public CompletableFuture<Suggestions> getSuggestions(CommandContext<CommandSourceStack> context, SuggestionsBuilder builder) throws CommandSyntaxException {
for (MinionData data : MinionPersistentState.get(context.getSource().getServer()).getMinionDataList()) { for (MinionData data : MinionPersistentState.get(context.getSource().getServer()).getMinionDataList()) {
builder.suggest(data.name()); builder.suggest(data.name());
} }
@@ -3,17 +3,19 @@ package io.github.skippyall.minions.command;
import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import io.github.skippyall.minions.MinionsConfig; import io.github.skippyall.minions.MinionsConfig;
import net.minecraft.command.CommandRegistryAccess; import net.minecraft.commands.CommandBuildContext;
import net.minecraft.server.command.CommandManager; import net.minecraft.commands.CommandSourceStack;
import net.minecraft.server.command.ServerCommandSource; import net.minecraft.commands.Commands;
import static net.minecraft.server.command.CommandManager.literal; import static net.minecraft.commands.Commands.literal;
public class MinionsCommand { public class MinionsCommand {
public static void register(CommandDispatcher<ServerCommandSource> dispatcher, CommandRegistryAccess access, CommandManager.RegistrationEnvironment environment) { public static void register(CommandDispatcher<CommandSourceStack> dispatcher, CommandBuildContext access, Commands.CommandSelection environment) {
LiteralArgumentBuilder<ServerCommandSource> builder = literal("minions") LiteralArgumentBuilder<CommandSourceStack> builder = literal("minions")
.then(SpawnSubcommand.SPAWN) .then(SpawnSubcommand.SPAWN)
.then(ListSubcommand.LIST); .then(ListSubcommand.LIST)
.then(DocsSubcommand.DOCS)
.then(TestSubcommand.TEST);
if(MinionsConfig.get().minion.enableMobCapHacks) { if(MinionsConfig.get().minion.enableMobCapHacks) {
builder.then(MobCapDebugSubcommand.MOB_CAP_DEBUG); builder.then(MobCapDebugSubcommand.MOB_CAP_DEBUG);
@@ -4,21 +4,21 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContext;
import io.github.skippyall.minions.mixinhelper.antimobcap.ChunkLevelManager$DistanceFromNearestPlayerTrackerAccessor; import io.github.skippyall.minions.mixinhelper.antimobcap.ChunkLevelManager$DistanceFromNearestPlayerTrackerAccessor;
import io.github.skippyall.minions.mixinhelper.antimobcap.ChunkLevelManagerAccessor; import io.github.skippyall.minions.mixinhelper.antimobcap.ChunkLevelManagerAccessor;
import io.github.skippyall.minions.mixins.antimobcap.ServerChunkManagerAccessor; import io.github.skippyall.minions.mixins.antimobcap.ServerChunkCacheAccessor;
import net.minecraft.server.command.ServerCommandSource; import net.minecraft.commands.CommandSourceStack;
import net.minecraft.server.world.ChunkLevelManager; import net.minecraft.network.chat.Component;
import net.minecraft.text.Text; import net.minecraft.server.level.DistanceManager;
import static net.minecraft.server.command.CommandManager.literal; import static net.minecraft.commands.Commands.literal;
public class MobCapDebugSubcommand { public class MobCapDebugSubcommand {
public static final LiteralArgumentBuilder<ServerCommandSource> MOB_CAP_DEBUG = literal("mobcapdebug") public static final LiteralArgumentBuilder<CommandSourceStack> MOB_CAP_DEBUG = literal("mobcapdebug")
.executes(MobCapDebugSubcommand::mobcapdebugCommand); .executes(MobCapDebugSubcommand::mobcapdebugCommand);
public static int mobcapdebugCommand(CommandContext<ServerCommandSource> context) { public static int mobcapdebugCommand(CommandContext<CommandSourceStack> context) {
ChunkLevelManager levelManager = ((ServerChunkManagerAccessor)context.getSource().getWorld().getChunkManager()).getLevelManager(); DistanceManager levelManager = ((ServerChunkCacheAccessor)context.getSource().getLevel().getChunkSource()).getDistanceManager();
int tickedChunkCount = ((ChunkLevelManager$DistanceFromNearestPlayerTrackerAccessor)((ChunkLevelManagerAccessor)levelManager).minions$getMinionless()).minions$getTickedChunkCount(); int tickedChunkCount = ((ChunkLevelManager$DistanceFromNearestPlayerTrackerAccessor)((ChunkLevelManagerAccessor)levelManager).minions$getMinionless()).minions$getTickedChunkCount();
context.getSource().sendFeedback(() -> Text.of(String.valueOf(tickedChunkCount)), false); context.getSource().sendSuccess(() -> Component.nullToEmpty(String.valueOf(tickedChunkCount)), false);
return 0; return 0;
} }
} }
@@ -6,24 +6,25 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.CommandSyntaxException;
import io.github.skippyall.minions.minion.MinionData; import io.github.skippyall.minions.minion.MinionData;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import net.minecraft.command.argument.PosArgument; import net.minecraft.commands.CommandSourceStack;
import net.minecraft.command.argument.Vec3ArgumentType; import net.minecraft.commands.arguments.coordinates.Coordinates;
import net.minecraft.server.command.ServerCommandSource; import net.minecraft.commands.arguments.coordinates.Vec3Argument;
import net.minecraft.server.permissions.Permissions;
import static net.minecraft.server.command.CommandManager.argument; import static net.minecraft.commands.Commands.argument;
import static net.minecraft.server.command.CommandManager.literal; import static net.minecraft.commands.Commands.literal;
public class SpawnSubcommand { public class SpawnSubcommand {
public static final LiteralArgumentBuilder<ServerCommandSource> SPAWN = literal("spawn") public static final LiteralArgumentBuilder<CommandSourceStack> SPAWN = literal("spawn")
.requires(source -> source.hasPermissionLevel(2)) .requires(source -> source.permissions().hasPermission(Permissions.COMMANDS_GAMEMASTER))
.then(argument("minion", StringArgumentType.word()) .then(argument("minion", StringArgumentType.word())
.suggests(MinionArgument.SUGGESTION_PROVIDER) .suggests(MinionArgument.SUGGESTION_PROVIDER)
.then(argument("pos", Vec3ArgumentType.vec3()) .then(argument("pos", Vec3Argument.vec3())
.executes(context -> .executes(context ->
spawnCommand( spawnCommand(
context.getSource(), context.getSource(),
StringArgumentType.getString(context, "minion"), StringArgumentType.getString(context, "minion"),
Vec3ArgumentType.getPosArgument(context, "pos"), Vec3Argument.getCoordinates(context, "pos"),
false false
) )
) )
@@ -32,7 +33,7 @@ public class SpawnSubcommand {
spawnCommand( spawnCommand(
context.getSource(), context.getSource(),
StringArgumentType.getString(context, "minion"), StringArgumentType.getString(context, "minion"),
Vec3ArgumentType.getPosArgument(context, "pos"), Vec3Argument.getCoordinates(context, "pos"),
BoolArgumentType.getBool(context, "force") BoolArgumentType.getBool(context, "force")
) )
) )
@@ -47,9 +48,9 @@ public class SpawnSubcommand {
)) ))
); );
public static int spawnCommand(ServerCommandSource source, String minion, PosArgument pos, boolean force) throws CommandSyntaxException { public static int spawnCommand(CommandSourceStack source, String minion, Coordinates pos, boolean force) throws CommandSyntaxException {
MinionData data = MinionArgument.parse(source.getServer(), minion); MinionData data = MinionArgument.parse(source.getServer(), minion);
MinionFakePlayer.spawnMinion(data, source.getWorld(), pos != null ? pos.getPos(source) : null, pos != null ? pos.getRotation(source) : null, force); MinionFakePlayer.spawnMinion(data, source.getLevel(), pos != null ? pos.getPosition(source) : null, pos != null ? pos.getRotation(source) : null, force);
return 0; return 0;
} }
} }
@@ -0,0 +1,78 @@
package io.github.skippyall.minions.command;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import io.github.skippyall.minions.Minions;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.arguments.coordinates.BlockPosArgument;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.chat.Component;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import java.util.Collection;
import java.util.HashSet;
import static net.minecraft.commands.Commands.argument;
import static net.minecraft.commands.Commands.literal;
public class TestSubcommand {
public static LiteralArgumentBuilder<CommandSourceStack> TEST = literal("test")
.then(argument("pos", BlockPosArgument.blockPos())
.executes(TestSubcommand::execute)
);
private static int execute(CommandContext<CommandSourceStack> context) {
try {
BlockPos pos = BlockPosArgument.getBlockPos(context, "pos");
Collection<BlockPos> result = findInputs(context.getSource().getLevel(), pos);
for (BlockPos resultPos : result) {
context.getSource().sendSuccess(() -> Component.literal(resultPos.toShortString()), false);
}
} catch (Throwable e) {
Minions.LOGGER.error("Error", e);
}
return 0;
}
private static Collection<BlockPos> findInputs(Level world, BlockPos pos) {
//positions that are already processed
Collection<BlockPos> visitedPositions = new HashSet<>();
//positions we are currently looking at
Collection<BlockPos> currentPositions = new HashSet<>();
//new positions are added here
Collection<BlockPos> newPositions = new HashSet<>();
//resulting machines
Collection<BlockPos> machines = new HashSet<>();
currentPositions.add(pos);
while(!currentPositions.isEmpty()) {
for(BlockPos currentPosition : currentPositions) {
for(Direction dir : Direction.values()) {
//check each neighbor of the current positions
BlockPos newPos = currentPosition.relative(dir);
//Do not check blocks that were already checked
if(!visitedPositions.contains(newPos)) {
if (world.getBlockState(newPos).getBlock() == Blocks.REDSTONE_BLOCK) {
//Add pipes to positions for the next iteration
newPositions.add(newPos);
} else if (world.getBlockState(newPos).getBlock() == Blocks.IRON_BLOCK) {
//Add machines to output set
machines.add(newPos);
}
//Add checked blocks here so that they are not checked again
visitedPositions.add(newPos);
}
}
}
//Check the new positions in the next iteration
currentPositions = newPositions;
newPositions = new HashSet<>();
}
return machines;
}
}
@@ -0,0 +1,29 @@
package io.github.skippyall.minions.docs;
import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import io.github.skippyall.minions.registration.MinionRegistries;
import net.minecraft.core.RegistryAccess;
import net.minecraft.server.dialog.body.DialogBody;
import java.util.List;
import java.util.function.Function;
public interface DocsEntry {
Codec<DocsEntry> CODEC = MinionRegistries.DOCS_ENTRY_TYPES.byNameCodec().dispatch(DocsEntry::getCodec, Function.identity());
Metadata getMetadata();
List<DialogBody> getDialog(RegistryAccess manager);
MapCodec<? extends DocsEntry> getCodec();
record Metadata(String titleKey) {
public static final MapCodec<Metadata> CODEC = RecordCodecBuilder.mapCodec(instance ->
instance.group(
Codec.STRING.fieldOf("title").forGetter(Metadata::titleKey)
).apply(instance, Metadata::new)
);
}
}
@@ -0,0 +1,143 @@
package io.github.skippyall.minions.docs;
import com.google.gson.JsonParseException;
import com.mojang.serialization.JsonOps;
import io.github.skippyall.minions.Minions;
import net.fabricmc.fabric.api.resource.SimpleResourceReloadListener;
import net.minecraft.core.Holder;
import net.minecraft.network.chat.ClickEvent;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import net.minecraft.server.dialog.ActionButton;
import net.minecraft.server.dialog.CommonButtonData;
import net.minecraft.server.dialog.CommonDialogData;
import net.minecraft.server.dialog.DialogAction;
import net.minecraft.server.dialog.MultiActionDialog;
import net.minecraft.server.dialog.action.StaticAction;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.packs.resources.Resource;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.util.StrictJsonParser;
import net.minecraft.util.Tuple;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
public class DocsManager implements SimpleResourceReloadListener<Tuple<Map<Identifier, DocsEntry>, DocsTree>> {
private static Map<Identifier, DocsEntry> docs;
private static DocsTree tree;
public static DocsTree getTree() {
return tree;
}
public static void showDocsEntry(ServerPlayer player, Identifier id) {
DocsEntry entry = getDocsEntry(id);
if(entry == null) {
return;
}
List<ActionButton> buttons = new ArrayList<>();
if(tree != null) {
DocsTree.DocElement element = tree.getElement(id);
if (element.previous() != null) {
Identifier previousId = element.previous().getId();
buttons.add(getDialogButton(Component.literal("<- ").append(Component.translatable(getDocsEntry(previousId).getMetadata().titleKey())), previousId.toString()));
}
if (element.next() != null) {
Identifier nextId = element.next().getId();
buttons.add(getDialogButton(Component.translatable(getDocsEntry(nextId).getMetadata().titleKey()).append(Component.literal(" ->")), nextId.toString()));
}
}
buttons.add(new ActionButton(
new CommonButtonData(Component.translatable("gui.ok"), 100),
Optional.empty()
));
player.openDialog(Holder.direct(new MultiActionDialog(
new CommonDialogData(
Component.translatable(entry.getMetadata().titleKey()),
Optional.empty(),
true,
false,
DialogAction.CLOSE,
entry.getDialog(player.registryAccess()),
List.of()
),
buttons,
Optional.empty(),
2
)));
}
private static ActionButton getDialogButton(Component text, String dialogToOpen) {
return new ActionButton(
new CommonButtonData(
text, 100
),
Optional.of(new StaticAction(new ClickEvent.RunCommand("/minions docs " + dialogToOpen)))
);
}
public static DocsEntry getDocsEntry(Identifier id) {
return docs.get(id);
}
public static Collection<Identifier> getDocsEntryIds() {
return docs.keySet();
}
@Override
public CompletableFuture<Tuple<Map<Identifier, DocsEntry>, DocsTree>> load(ResourceManager resourceManager, Executor executor) {
return CompletableFuture.supplyAsync(() -> {
Map<Identifier, Resource> resources = resourceManager.listResources("docs", id -> id.getNamespace().equals(Minions.MOD_ID) && id.getPath().endsWith(".json"));
final DocsTree.BranchElement[] root = {null};
Map<Identifier, DocsEntry> docsEntries = new HashMap<>();
resources.forEach((id, resource) -> {
try(Reader reader = resource.openAsReader()) {
if(id.getPath().equals("docs/tree.json")) {
DocsTree.BranchElement.CODEC.decode(JsonOps.INSTANCE, StrictJsonParser.parse(reader))
.ifSuccess(entry -> root[0] = entry.getFirst())
.ifError(error -> Minions.LOGGER.warn("Could not parse docs tree {}: {}", id, error.message()));
} else {
Identifier docId = Identifier.fromNamespaceAndPath(id.getNamespace(), id.getPath().substring("docs/".length(), id.getPath().length() - ".json".length()));
DocsEntry.CODEC.decode(JsonOps.INSTANCE, StrictJsonParser.parse(reader))
.ifSuccess(entry -> docsEntries.put(docId, entry.getFirst()))
.ifError(error -> Minions.LOGGER.warn("Could not parse docs entry {}: {}", id, error.message()));
}
} catch (IOException | JsonParseException e) {
Minions.LOGGER.warn("Could not read file {}", id, e);
}
});
if(root[0] != null) {
DocsTree tree = new DocsTree(root[0]);
return new Tuple<>(docsEntries, tree);
} else {
return new Tuple<>(docsEntries, null);
}
}, executor);
}
@Override
public CompletableFuture<Void> apply(Tuple<Map<Identifier, DocsEntry>, DocsTree> o, ResourceManager resourceManager, Executor executor) {
return CompletableFuture.supplyAsync(() -> {
docs = o.getA();
tree = o.getB();
return null;
});
}
@Override
public Identifier getFabricId() {
return Identifier.fromNamespaceAndPath(Minions.MOD_ID, "docs");
}
}
@@ -0,0 +1,146 @@
package io.github.skippyall.minions.docs;
import com.mojang.datafixers.util.Either;
import com.mojang.serialization.Codec;
import net.minecraft.resources.Identifier;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
public class DocsTree {
private final BranchElement root;
private final Map<Identifier, DocElement> entries = new HashMap<>();
public DocsTree(BranchElement root) {
this.root = root;
initEntries();
}
private void initEntries() {
DocElement element = root.first();
do {
entries.put(element.id, element);
element = element.next();
} while (element != null);
}
public BranchElement getRoot() {
return root;
}
public DocElement getElement(Identifier id) {
return entries.get(id);
}
public static abstract sealed class Element {
private BranchElement parent;
public BranchElement getParent() {
return parent;
}
void setParent(BranchElement parent) {
this.parent = parent;
}
public DocElement next() {
return parent.next(this);
}
public DocElement previous() {
return parent.previous(this);
}
}
public static final class DocElement extends Element {
public static final Codec<DocElement> CODEC = Identifier.CODEC.xmap(DocElement::new, DocElement::getId);
private final Identifier id;
public DocElement(Identifier element) {
this.id = element;
}
public Identifier getId() {
return id;
}
}
public static final class BranchElement extends Element {
public static final Codec<BranchElement> CODEC = Codec.<BranchElement>recursive("DocsBranch", codec ->
Codec.either(codec, DocElement.CODEC)
.xmap(
Either::unwrap,
element -> switch (element) {
case BranchElement branch -> Either.left(branch);
case DocElement doc -> Either.right(doc);
}
).listOf()
.xmap(BranchElement::new, branch -> branch.e)
).xmap(BranchElement::setParents, Function.identity());
private final List<Element> e;
public BranchElement(List<Element> elements) {
e = elements;
}
public DocElement first() {
return switch (e.getFirst()) {
case BranchElement branch -> branch.first();
case DocElement doc -> doc;
};
}
public DocElement last() {
return switch (e.getLast()) {
case BranchElement branch -> branch.last();
case DocElement doc -> doc;
};
}
public DocElement next(Element current) {
int nextIndex = e.indexOf(current) + 1;
if(nextIndex < e.size()) {
return switch (e.get(nextIndex)) {
case BranchElement branch -> branch.first();
case DocElement doc -> doc;
};
} else {
if(getParent() != null) {
return getParent().next(this);
} else {
return null;
}
}
}
public DocElement previous(Element current) {
int previousIndex = e.indexOf(current) - 1;
if(previousIndex >= 0) {
return switch (e.get(previousIndex)) {
case BranchElement branch -> branch.first();
case DocElement doc -> doc;
};
} else {
if(getParent() != null) {
return getParent().previous(this);
} else {
return null;
}
}
}
private BranchElement setParents() {
for(Element element : e) {
element.setParent(this);
if(element instanceof BranchElement branch) {
branch.setParents();
}
}
return this;
}
}
}
@@ -1,14 +1,81 @@
package io.github.skippyall.minions.docs; package io.github.skippyall.minions.docs;
import com.mojang.serialization.Codec; import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import com.mojang.serialization.codecs.RecordCodecBuilder; import com.mojang.serialization.codecs.RecordCodecBuilder;
import net.minecraft.text.Text; import io.github.skippyall.minions.gui.GuiDisplay;
import net.minecraft.text.TextCodecs; import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentSerialization;
import net.minecraft.resources.Identifier;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.dialog.body.DialogBody;
import net.minecraft.server.dialog.body.ItemBody;
import net.minecraft.server.dialog.body.PlainMessage;
import net.minecraft.world.item.Item;
public record ReferenceEntry(Text shortDescription, Text longDescription) { import java.util.ArrayList;
public static final Codec<ReferenceEntry> CODEC = RecordCodecBuilder.create(instance -> import java.util.List;
import java.util.Optional;
public record ReferenceEntry(Metadata metadata, ResourceKey<?> object, Component shortDescription, Component longDescription) implements DocsEntry {
private static final Codec<ResourceKey<?>> REGISTRY_KEY_CODEC = RecordCodecBuilder.create(instance ->
instance.group( instance.group(
TextCodecs.CODEC.fieldOf("shortDescription").forGetter(ReferenceEntry::shortDescription), Identifier.CODEC.fieldOf("registry").forGetter(ResourceKey::registry),
TextCodecs.CODEC.fieldOf("longDescription").forGetter(ReferenceEntry::longDescription) Identifier.CODEC.fieldOf("value").forGetter(ResourceKey::identifier)
).apply(instance, (registry, value) -> ResourceKey.create(ResourceKey.createRegistryKey(registry), value))
);
public static final MapCodec<ReferenceEntry> CODEC = RecordCodecBuilder.mapCodec(instance ->
instance.group(
Metadata.CODEC.fieldOf("metadata").forGetter(ReferenceEntry::getMetadata),
REGISTRY_KEY_CODEC.fieldOf("object").forGetter(ReferenceEntry::object),
ComponentSerialization.CODEC.fieldOf("shortDescription").forGetter(ReferenceEntry::shortDescription),
ComponentSerialization.CODEC.fieldOf("longDescription").forGetter(ReferenceEntry::longDescription)
).apply(instance, ReferenceEntry::new)); ).apply(instance, ReferenceEntry::new));
@Override
public Metadata getMetadata() {
return metadata;
}
@Override
public List<DialogBody> getDialog(RegistryAccess manager) {
List<DialogBody> bodyElements = new ArrayList<>();
GuiDisplay display = getObjectDisplay(manager);
if(display != null) {
bodyElements.add(new ItemBody(display.createItemStackTemplate(), Optional.empty(), false, false, 16, 16));
}
bodyElements.add(new PlainMessage(Component.translatable(object.identifier().toLanguageKey(object.registry().getPath())), 200));
bodyElements.add(new PlainMessage(longDescription, 200));
return bodyElements;
}
public GuiDisplay getObjectDisplay(RegistryAccess manager) {
GuiDisplay display = GuiDisplay.DEFAULT_DISPLAY;
if(object.isFor(Registries.ITEM) || object.isFor(Registries.BLOCK)) {
Item item;
if(object.isFor(Registries.ITEM)) {
item = BuiltInRegistries.ITEM.getValue(object.identifier());
} else {
item = BuiltInRegistries.BLOCK.getValue(object.identifier()).asItem();
}
if(item != null) {
display = new GuiDisplay.ItemBased(item);
}
} else {
Identifier displayId = object.identifier().withPrefix(object.registry().getPath() + "/");
display = GuiDisplay.getGuiDisplay(displayId, manager);
}
return display;
}
@Override
public MapCodec<? extends DocsEntry> getCodec() {
return CODEC;
}
} }
@@ -1,117 +0,0 @@
package io.github.skippyall.minions.gui;
import eu.pb4.sgui.api.elements.GuiElementBuilder;
import io.github.skippyall.minions.gui.input.ChoiceInput;
import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.program.instruction.ConfiguredInstruction;
import io.github.skippyall.minions.program.supplier.Parameter;
import io.github.skippyall.minions.clipboard.ClipboardItem;
import net.minecraft.item.Items;
import net.minecraft.screen.ScreenHandlerType;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvents;
import net.minecraft.text.Text;
public class ConfigureInstructionGui extends InstructionBoundSimpleGui {
private String name;
private ConfigureInstructionGui(ScreenHandlerType<?> type, ServerPlayerEntity player, MinionFakePlayer minion, ConfiguredInstruction<MinionRuntime> configuredInstruction, String name) {
super(type, player, minion, configuredInstruction);
this.name = name;
init();
}
public static void configureInstructionMenu(String name, ConfiguredInstruction<MinionRuntime> instruction, MinionFakePlayer minion, ServerPlayerEntity player) {
if(!InstructionGui.checkInstructionExists(name, instruction, minion, player)) {
return;
}
ConfigureInstructionGui gui = new ConfigureInstructionGui(ScreenHandlerType.GENERIC_9X3, player, minion, instruction, name);
gui.open();
}
private void init() {
setTitle(Text.literal(name));
setSlot(7, new GuiElementBuilder(Items.ANVIL)
.setName(Text.translatable("minions.gui.instruction.configure.rename"))
.setCallback(() -> InstructionGui.inputInstructionName(minion, player, name).thenAccept(newName -> {
minion.getInstructionManager().setInstructionName(name, newName);
configureInstructionMenu(newName, instruction, minion, player);
}))
);
setSlot(8, new GuiElementBuilder(Items.LAVA_BUCKET)
.setName(Text.translatable("minions.gui.instruction.configure.delete"))
.setCallback(() -> ChoiceInput.confirm(player, Text.translatable("minions.gui.instruction.configure.delete.confirm", name))
.thenAccept(v -> {
minion.getInstructionManager().removeInstruction(name);
InstructionGui.instructionList(minion, player);
}))
);
updateSuppliers();
setSlot(13, InstructionGui.createInstructionElement(instruction.getInstruction(), player.getRegistryManager()));
setSlot(25, new GuiElementBuilder(Items.FEATHER)
.setName(Text.translatable("minions.gui.instruction.configure.copy"))
.addLoreLine(Text.translatable("minions.gui.instruction.configure.copy.description"))
.setCallback(() -> {
player.getInventory().offer(ClipboardItem.createInstructionReference(minion, name), true);
player.playSoundToPlayer(SoundEvents.BLOCK_NOTE_BLOCK_CHIME.value(), SoundCategory.BLOCKS, 1, 1);
})
);
updateRunSlot();
}
@Override
public void onInstructionRename(MinionFakePlayer minion, ConfiguredInstruction<?> instruction, String oldName, String newName) {
this.setTitle(Text.literal(newName));
name = newName;
}
@Override
public void onRun(ConfiguredInstruction<?> instruction) {
updateRunSlot();
}
@Override
public void onStop(ConfiguredInstruction<?> instruction) {
updateRunSlot();
}
@Override
public void onSupplierChange(ConfiguredInstruction<?> instruction, Parameter<?> parameter) {
updateSuppliers();
}
private void updateRunSlot() {
if(!instruction.isRunning()) {
setSlot(26, new GuiElementBuilder(Items.ARROW)
.setName(Text.translatable("minions.gui.instruction.run"))
.setCallback(() -> instruction.run(minion.getInstructionManager()))
);
} else {
setSlot(26, new GuiElementBuilder(Items.BARRIER)
.setName(Text.translatable("minions.gui.instruction.stop"))
.setCallback(() -> instruction.stop(minion.getInstructionManager()))
);
}
}
private void updateSuppliers() {
int slot = 12;
for(Parameter<?> parameter : instruction.getInstruction().getParameters().reversed()) {
setSlot(slot, InstructionGui.createParameterElement(parameter, instruction.getArguments().getArgument(parameter), player.getRegistryManager())
.setCallback(() -> InstructionGui.configureArgumentMenu(name, instruction, parameter, minion, player))
);
slot--;
}
}
}
@@ -1,56 +1,73 @@
package io.github.skippyall.minions.gui; package io.github.skippyall.minions.gui;
import com.mojang.authlib.properties.PropertyMap;
import com.mojang.serialization.Codec; import com.mojang.serialization.Codec;
import io.github.skippyall.minions.registration.MinionRegistries; import io.github.skippyall.minions.registration.MinionRegistries;
import io.github.skippyall.minions.util.TranslationUtil; import io.github.skippyall.minions.util.TranslationUtil;
import it.unimi.dsi.fastutil.objects.ReferenceSortedSets; import net.minecraft.ChatFormatting;
import net.minecraft.component.DataComponentTypes; import net.minecraft.core.Registry;
import net.minecraft.component.type.ProfileComponent; import net.minecraft.core.RegistryAccess;
import net.minecraft.component.type.TooltipDisplayComponent; import net.minecraft.core.UUIDUtil;
import net.minecraft.item.Item; import net.minecraft.core.component.DataComponentPatch;
import net.minecraft.item.ItemStack; import net.minecraft.core.component.DataComponentType;
import net.minecraft.item.Items; import net.minecraft.core.component.DataComponents;
import net.minecraft.registry.DynamicRegistryManager; import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.registry.Registries; import net.minecraft.network.chat.Component;
import net.minecraft.registry.Registry; import net.minecraft.resources.Identifier;
import net.minecraft.text.Text; import net.minecraft.world.item.Item;
import net.minecraft.util.Identifier; import net.minecraft.world.item.ItemStack;
import net.minecraft.util.Rarity; import net.minecraft.world.item.ItemStackTemplate;
import net.minecraft.util.Uuids; import net.minecraft.world.item.Items;
import net.minecraft.world.item.Rarity;
import net.minecraft.world.item.component.ResolvableProfile;
import net.minecraft.world.item.component.TooltipDisplay;
import java.util.Optional; import java.util.LinkedHashSet;
import java.util.UUID; import java.util.UUID;
public interface GuiDisplay { public interface GuiDisplay {
Codec<GuiDisplay> CODEC = MinionRegistries.GUI_DISPLAY_TYPE.getCodec().dispatch(GuiDisplay::getCodec, codec -> codec.fieldOf("data")); Codec<GuiDisplay> CODEC = MinionRegistries.GUI_DISPLAY_TYPE.byNameCodec().dispatch(GuiDisplay::getCodec, codec -> codec.fieldOf("data"));
GuiDisplay DEFAULT_DISPLAY = new ItemBased(Items.BARRIER); GuiDisplay DEFAULT_DISPLAY = new ItemBased(Items.BARRIER);
TooltipDisplay TOOLTIP_DISPLAY = createTooltipDisplay();
static GuiDisplay getGuiDisplay(Identifier id, DynamicRegistryManager manager) { private static TooltipDisplay createTooltipDisplay() {
return manager.getOptional(MinionRegistries.GUI_DISPLAY).map(registry -> registry.get(id)).orElse(DEFAULT_DISPLAY); LinkedHashSet<DataComponentType<?>> set = new LinkedHashSet<>();
for(DataComponentType<?> type : BuiltInRegistries.DATA_COMPONENT_TYPE) {
if(type != DataComponents.LORE) {
set.add(type);
}
}
return new TooltipDisplay(false, set);
} }
static <T> GuiDisplay getGuiDisplayFor(Registry<T> registry, T element, DynamicRegistryManager manager) { static GuiDisplay getGuiDisplay(Identifier id, RegistryAccess manager) {
Identifier elementId = registry.getId(element); return manager.lookup(MinionRegistries.GUI_DISPLAY).map(registry -> registry.getValue(id)).orElse(DEFAULT_DISPLAY);
}
static <T> GuiDisplay getGuiDisplayFor(Registry<T> registry, T element, RegistryAccess manager) {
Identifier elementId = registry.getKey(element);
if(elementId == null) { if(elementId == null) {
return DEFAULT_DISPLAY; return DEFAULT_DISPLAY;
} }
Identifier displayId = elementId.withPrefixedPath(registry.getKey().getValue().getPath() + "/"); Identifier displayId = elementId.withPrefix(registry.key().identifier().getPath() + "/");
return getGuiDisplay(displayId, manager); return getGuiDisplay(displayId, manager);
} }
static <T> ItemStack getDisplayStack(Registry<T> registry, T element, DynamicRegistryManager manager) { static <T> ItemStack getDisplayStack(Registry<T> registry, T element, RegistryAccess manager) {
return getGuiDisplayFor(registry, element, manager).createItemStack(); return getGuiDisplayFor(registry, element, manager).createItemStack();
} }
static <T> ItemStack getDisplayStackWithName(Registry<T> registry, T element, DynamicRegistryManager manager) { static <T> ItemStack getDisplayStackWithName(Registry<T> registry, T element, RegistryAccess manager) {
ItemStack stack = getDisplayStack(registry, element, manager); ItemStack stack = getDisplayStack(registry, element, manager);
stack.set(DataComponentTypes.ITEM_NAME, Text.translatable(TranslationUtil.getTranslationKey(element, registry))); stack.set(DataComponents.CUSTOM_NAME, Component.translatable(TranslationUtil.getTranslationKey(element, registry)).withStyle(style -> style.withItalic(false).withColor(ChatFormatting.WHITE)));
return stack; return stack;
} }
ItemStack createItemStack(); ItemStackTemplate createItemStackTemplate();
default ItemStack createItemStack() {
return createItemStackTemplate().create();
}
Codec<? extends GuiDisplay> getCodec(); Codec<? extends GuiDisplay> getCodec();
@@ -64,10 +81,11 @@ public interface GuiDisplay {
} }
@Override @Override
public ItemStack createItemStack() { public ItemStackTemplate createItemStackTemplate() {
ItemStack stack = new ItemStack(Items.BARRIER); return new ItemStackTemplate(Items.BARRIER, DataComponentPatch.builder()
stack.set(DataComponentTypes.ITEM_MODEL, model); .set(DataComponents.ITEM_MODEL, model)
return stack; .build()
);
} }
@Override @Override
@@ -77,7 +95,7 @@ public interface GuiDisplay {
} }
class ItemBased implements GuiDisplay { class ItemBased implements GuiDisplay {
public static final Codec<ItemBased> CODEC = Registries.ITEM.getCodec().xmap(ItemBased::new, display -> display.item); public static final Codec<ItemBased> CODEC = BuiltInRegistries.ITEM.byNameCodec().xmap(ItemBased::new, display -> display.item);
private final Item item; private final Item item;
@@ -86,11 +104,11 @@ public interface GuiDisplay {
} }
@Override @Override
public ItemStack createItemStack() { public ItemStackTemplate createItemStackTemplate() {
ItemStack stack = new ItemStack(item); return new ItemStackTemplate(item, DataComponentPatch.builder()
stack.set(DataComponentTypes.TOOLTIP_DISPLAY, new TooltipDisplayComponent(true, ReferenceSortedSets.emptySet())); .set(DataComponents.TOOLTIP_DISPLAY, TOOLTIP_DISPLAY)
stack.set(DataComponentTypes.RARITY, Rarity.COMMON); .set(DataComponents.RARITY, Rarity.COMMON)
return stack; .build());
} }
@Override @Override
@@ -100,7 +118,7 @@ public interface GuiDisplay {
} }
class HeadBased implements GuiDisplay { class HeadBased implements GuiDisplay {
public static final Codec<HeadBased> CODEC = Uuids.CODEC.xmap(HeadBased::new, display -> display.uuid); public static final Codec<HeadBased> CODEC = UUIDUtil.AUTHLIB_CODEC.xmap(HeadBased::new, display -> display.uuid);
private final UUID uuid; private final UUID uuid;
@@ -109,10 +127,11 @@ public interface GuiDisplay {
} }
@Override @Override
public ItemStack createItemStack() { public ItemStackTemplate createItemStackTemplate() {
ItemStack stack = new ItemStack(Items.PLAYER_HEAD); return new ItemStackTemplate(Items.PLAYER_HEAD, DataComponentPatch.builder()
stack.set(DataComponentTypes.PROFILE, new ProfileComponent(Optional.empty(), Optional.of(uuid), new PropertyMap())); .set(DataComponents.PROFILE, ResolvableProfile.createUnresolved(uuid))
return stack; .build()
);
} }
@Override @Override
@@ -122,17 +141,17 @@ public interface GuiDisplay {
} }
class StackBased implements GuiDisplay { class StackBased implements GuiDisplay {
public static final Codec<StackBased> CODEC = ItemStack.CODEC.xmap(StackBased::new, StackBased::createItemStack); public static final Codec<StackBased> CODEC = ItemStackTemplate.CODEC.xmap(StackBased::new, StackBased::createItemStackTemplate);
private final ItemStack stack; private final ItemStackTemplate template;
public StackBased(ItemStack stack) { public StackBased(ItemStackTemplate template) {
this.stack = stack; this.template = template;
} }
@Override @Override
public ItemStack createItemStack() { public ItemStackTemplate createItemStackTemplate() {
return stack; return template;
} }
@Override @Override
@@ -1,29 +0,0 @@
package io.github.skippyall.minions.gui;
import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.program.instruction.ConfiguredInstruction;
import io.github.skippyall.minions.program.instruction.ConfiguredInstructionListener;
import net.minecraft.screen.ScreenHandlerType;
import net.minecraft.server.network.ServerPlayerEntity;
public class InstructionBoundSimpleGui extends MinionBoundSimpleGui implements ConfiguredInstructionListener {
protected final ConfiguredInstruction<MinionRuntime> instruction;
public InstructionBoundSimpleGui(ScreenHandlerType<?> type, ServerPlayerEntity player, MinionFakePlayer minion, ConfiguredInstruction<MinionRuntime> instruction) {
super(type, player, minion);
this.instruction = instruction;
instruction.addListener(this);
}
@Override
public void onInstructionRemove(ConfiguredInstruction<?> instruction) {
close();
}
@Override
public void onClose() {
super.onClose();
instruction.removeListener(this);
}
}
@@ -1,232 +0,0 @@
package io.github.skippyall.minions.gui;
import eu.pb4.sgui.api.elements.GuiElementBuilder;
import eu.pb4.sgui.api.gui.SimpleGui;
import io.github.skippyall.minions.registration.MinionComponentTypes;
import io.github.skippyall.minions.registration.MinionRegistries;
import io.github.skippyall.minions.gui.input.Result;
import io.github.skippyall.minions.gui.input.TextInput;
import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.module.MinionModule;
import io.github.skippyall.minions.program.supplier.ValueSupplier;
import io.github.skippyall.minions.program.supplier.ValueSupplierType;
import io.github.skippyall.minions.program.instruction.ConfiguredInstruction;
import io.github.skippyall.minions.program.instruction.InstructionType;
import io.github.skippyall.minions.program.supplier.Parameter;
import io.github.skippyall.minions.util.TranslationUtil;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.registry.DynamicRegistryManager;
import net.minecraft.screen.ScreenHandlerType;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import org.jetbrains.annotations.Nullable;
import java.util.NoSuchElementException;
import java.util.concurrent.CompletableFuture;
public class InstructionGui {
public static void openInstructionMainMenu(MinionFakePlayer minion, ServerPlayerEntity player) {
SimpleGui gui = new MinionBoundSimpleGui(ScreenHandlerType.GENERIC_3X3, player, minion);
gui.setTitle(Text.translatable("minions.gui.instruction.title"));
gui.setSlot(3, new GuiElementBuilder()
.setItem(Items.BOOK)
.setName(Text.translatable("minions.gui.instruction.list"))
.setCallback(() -> instructionList(minion, player))
);
gui.setSlot(5, new GuiElementBuilder()
.setItem(Items.WRITABLE_BOOK)
.setName(Text.translatable("minions.gui.instruction.create"))
.setCallback(() -> createNewInstruction(minion, player))
);
gui.open();
}
public static void instructionList(MinionFakePlayer minion, ServerPlayerEntity player) {
SimpleGui gui = new MinionBoundSimpleGui(ScreenHandlerType.GENERIC_9X3, player, minion) {
@Override
public void onInstructionsUpdate(MinionFakePlayer minion) {
resetInstructionList(this, minion, player);
}
};
gui.setTitle(Text.translatable("minions.gui.instruction.title"));
resetInstructionList(gui, minion, player);
gui.open();
}
private static void resetInstructionList(SimpleGui gui, MinionFakePlayer minion, ServerPlayerEntity player) {
int i = 0;
for (String instructionName : minion.getInstructionManager().getInstructionNames()) {
ConfiguredInstruction<MinionRuntime> instruction = minion.getInstructionManager().getInstruction(instructionName);
gui.setSlot(i, new GuiElementBuilder(GuiDisplay.getGuiDisplayFor(MinionRegistries.INSTRUCTION_TYPES, instruction.getInstruction(), player.getRegistryManager()).createItemStack())
.setName(Text.literal(instructionName))
.setCallback(() -> ConfigureInstructionGui.configureInstructionMenu(instructionName, instruction, minion, player))
);
i++;
}
}
public static void createNewInstruction(MinionFakePlayer minion, ServerPlayerEntity player) {
selectInstructionModuleMenu(minion, player).thenAccept(instructionType ->
inputInstructionName(minion, player, "Instruction").thenAccept(name -> {
if (!minion.isRemoved() && !minion.isDisconnected()) {
ConfiguredInstruction<MinionRuntime> configuredInstruction = minion.getInstructionManager().createInstruction(name, instructionType);
ConfigureInstructionGui.configureInstructionMenu(name, configuredInstruction, minion, player);
}
})
);
}
public static CompletableFuture<String> inputInstructionName(MinionFakePlayer minion, ServerPlayerEntity player, String defaultValue) {
return TextInput.inputSync(player, Text.translatable("minions.gui.instruction.enter_name"), defaultValue, name -> {
if (minion.getInstructionManager().hasInstruction(name)) {
return new Result.Error<>(Text.translatable("minions.gui.instruction.name_already_used"));
}
return new Result.Success<>(name);
});
}
public static boolean checkInstructionExists(String name, ConfiguredInstruction<?> instruction, MinionFakePlayer minion, ServerPlayerEntity player) {
boolean stillExists = !minion.isRemoved() && !minion.isDisconnected() && minion.getInstructionManager().getInstruction(name) == instruction;
if (!stillExists) {
player.closeHandledScreen();
player.sendMessage(Text.translatable("minions.gui.instruction.removed"));
}
return stillExists;
}
public static <T, A extends ValueSupplier<T, MinionRuntime>> void configureArgumentMenu(String instructionName, ConfiguredInstruction<MinionRuntime> instruction, Parameter<T> parameter, MinionFakePlayer minion, ServerPlayerEntity player) {
if (!checkInstructionExists(instructionName, instruction, minion, player)) {
return;
}
@Nullable A argument = instruction.getArguments().getArgument(parameter);
if(argument == null) {
configureTypeAndValue(instructionName, instruction, parameter, minion, player);
return;
}
SimpleGui gui = new InstructionBoundSimpleGui(ScreenHandlerType.GENERIC_3X3, player, minion, instruction);
ItemStack displayStack = GuiDisplay.getDisplayStack(MinionRegistries.VALUE_SUPPLIER_TYPES, argument.getType(), player.getRegistryManager());
gui.setSlot(3, new GuiElementBuilder(displayStack)
.setName(Text.translatable("minions.gui.instruction.argument.configure.type", Text.translatable(TranslationUtil.getTranslationKey(argument.getType(), MinionRegistries.VALUE_SUPPLIER_TYPES, "minions.gui.instruction.argument.configure.type.unset"))))
.setCallback(() -> configureTypeAndValue(instructionName, instruction, parameter, minion, player))
);
gui.setSlot(5, new GuiElementBuilder(Items.STRUCTURE_VOID)
.setName(Text.literal("Configure"))
.setCallback(() -> argument.getType().openConfiguration(player, argument.getValueType(), argument)
.thenAccept(newArgument -> instruction.getArguments().setArgument(parameter, newArgument))
)
);
gui.open();
}
public static CompletableFuture<ValueSupplierType<MinionRuntime>> selectArgumentType(ServerPlayerEntity player, MinionFakePlayer minion, ConfiguredInstruction<MinionRuntime> instruction) {
CompletableFuture<ValueSupplierType<MinionRuntime>> future = new CompletableFuture<>();
SimpleGui gui = new InstructionBoundSimpleGui(ScreenHandlerType.GENERIC_9X3, player, minion, instruction);
for (ValueSupplierType<MinionRuntime> type : MinionRegistries.VALUE_SUPPLIER_TYPES) {
gui.addSlot(new GuiElementBuilder(GuiDisplay.getDisplayStackWithName(MinionRegistries.VALUE_SUPPLIER_TYPES, type, player.getRegistryManager()))
.setCallback(() -> future.complete(type))
);
}
gui.open();
return future;
}
public static <T> void configureTypeAndValue(String name, ConfiguredInstruction<MinionRuntime> instruction, Parameter<T> parameter, MinionFakePlayer minion, ServerPlayerEntity player) {
selectArgumentType(player, minion, instruction)
.thenApply(type -> type.openConfiguration(player, parameter.type(), null)
.thenAccept(newArgument -> {
instruction.getArguments().setArgument(parameter, newArgument);
configureArgumentMenu(name, instruction, parameter, minion, player);
})
);
}
public static CompletableFuture<InstructionType<MinionRuntime>> selectInstructionModuleMenu(MinionFakePlayer minion, ServerPlayerEntity player) {
if (minion.getModuleInventory().getModules().isEmpty()) {
player.sendMessage(Text.translatable("minions.gui.instruction.no_modules"));
return CompletableFuture.failedFuture(new NoSuchElementException("No modules"));
}
CompletableFuture<InstructionType<MinionRuntime>> future = new CompletableFuture<>();
SimpleGui gui = new MinionBoundSimpleGui(ScreenHandlerType.GENERIC_9X3, player, minion) {
@Override
public void onClose() {
if (!future.isDone()) {
future.cancel(false);
}
super.onClose();
}
};
gui.setTitle(Text.translatable("minions.gui.instruction.select_instruction"));
for (int i = 0; i < minion.getModuleInventory().size(); i++) {
ItemStack moduleItem = minion.getModuleInventory().getStack(i);
MinionModule module = moduleItem.get(MinionComponentTypes.MODULE);
if (module != null && !module.instructions().isEmpty()) {
gui.addSlot(new GuiElementBuilder(moduleItem)
.setCallback(() -> selectInstructionMenu(module, minion, player)
.thenApply(future::complete)
)
);
}
}
gui.open();
return future;
}
public static CompletableFuture<InstructionType<MinionRuntime>> selectInstructionMenu(MinionModule module, MinionFakePlayer minion, ServerPlayerEntity player) {
CompletableFuture<InstructionType<MinionRuntime>> future = new CompletableFuture<>();
SimpleGui gui = new MinionBoundSimpleGui(ScreenHandlerType.GENERIC_9X3, player, minion) {
@Override
public void onClose() {
if (!future.isDone()) {
future.cancel(false);
}
super.onClose();
}
};
gui.setTitle(Text.translatable("minions.gui.instruction.select_instruction"));
for (InstructionType<MinionRuntime> instructionType : module.instructions()) {
gui.addSlot(createInstructionElement(instructionType, player.getRegistryManager())
.setCallback(() -> future.complete(instructionType))
);
}
gui.open();
return future;
}
public static GuiElementBuilder createInstructionElement(InstructionType<MinionRuntime> instructionType, DynamicRegistryManager manager) {
GuiElementBuilder instructionBuilder;
if (instructionType != null) {
instructionBuilder = new GuiElementBuilder(GuiDisplay.getDisplayStackWithName(MinionRegistries.INSTRUCTION_TYPES, instructionType, manager));
} else {
instructionBuilder = new GuiElementBuilder(Items.RED_WOOL)
.setName(Text.translatable("minions.gui.instruction.no_instruction_set"));
}
return instructionBuilder;
}
public static GuiElementBuilder createParameterElement(Parameter<?> parameter, @Nullable ValueSupplier<?,?> valueSupplier, DynamicRegistryManager manager) {
GuiElementBuilder builder = new GuiElementBuilder(GuiDisplay.getDisplayStack(MinionRegistries.VALUE_TYPES, parameter.type(), manager))
.setName(Text.translatable("minions.gui.instruction.parameter", parameter.name(), Text.translatable(TranslationUtil.getTranslationKey(parameter.type(), MinionRegistries.VALUE_TYPES))));
if(valueSupplier != null) {
builder.addLoreLine(Text.translatable("minions.gui.instruction.argument", valueSupplier.getDisplayText()));
}
return builder;
}
}
@@ -1,37 +0,0 @@
package io.github.skippyall.minions.gui;
import eu.pb4.sgui.api.gui.SimpleGui;
import io.github.skippyall.minions.minion.MinionListener;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import net.minecraft.screen.ScreenHandlerType;
import net.minecraft.server.network.ServerPlayerEntity;
public class MinionBoundSimpleGui extends SimpleGui implements MinionListener {
protected final MinionFakePlayer minion;
public MinionBoundSimpleGui(ScreenHandlerType<?> type, ServerPlayerEntity player, MinionFakePlayer minion) {
super(type, player, false);
this.minion = minion;
minion.addMinionListener(this);
}
public MinionFakePlayer getMinion() {
return minion;
}
@Override
public void onMinionRemove(MinionFakePlayer minion) {
close();
}
@Override
public void onClose() {
minion.removeMinionListener(this);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof MinionBoundSimpleGui that)) return false;
return minion == that.minion && player == that.player;
}
}
@@ -1,86 +0,0 @@
package io.github.skippyall.minions.gui;
import eu.pb4.sgui.api.elements.GuiElementBuilder;
import eu.pb4.sgui.api.gui.SimpleGui;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.module.ModuleInventory;
import net.minecraft.entity.EquipmentSlot;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.screen.ScreenHandlerType;
import net.minecraft.screen.slot.ArmorSlot;
import net.minecraft.screen.slot.Slot;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
public class MinionGui {
public static void openInventory(ServerPlayerEntity player, MinionFakePlayer minion) {
openServerSideInventory(player, minion);
}
public static void openServerSideInventory(ServerPlayerEntity player, MinionFakePlayer minion) {
SimpleGui gui = new SimpleGui(ScreenHandlerType.GENERIC_3X3, player, false);
gui.setTitle(minion.getName());
gui.setSlot(1, new GuiElementBuilder()
.setItem(Items.COMMAND_BLOCK)
.setName(Text.translatable("minions.gui.main.instructions"))
.setCallback((i, clickType, slotActionType) -> {
InstructionGui.openInstructionMainMenu(minion, player);
})
);
gui.setSlot(3, new GuiElementBuilder()
.setItem(Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE)
.setName(Text.translatable("minions.gui.main.modules"))
.setCallback(() -> {
ModuleInventory.openModuleInventory(player, minion);
})
);
gui.setSlot(5, new GuiElementBuilder()
.setItem(Items.CHEST)
.setName(Text.translatable("minions.gui.main.inventory"))
.setCallback(() -> {
openMinionInventory(player, minion);
})
);
gui.setSlot(7, new GuiElementBuilder()
.setItem(Items.BARRIER)
.setName(Text.translatable("minions.gui.main.pickup"))
.setCallback(() -> {
minion.kill(minion.getWorld());
})
);
gui.open();
}
public static void openMinionInventory(ServerPlayerEntity player, MinionFakePlayer minion) {
SimpleGui gui = new SimpleGui(ScreenHandlerType.GENERIC_9X6, player, false);
gui.setTitle(Text.translatable("minions.gui.inventory.title"));
for(int i = 0; i < 18; i++) {
gui.setSlot(i, new ItemStack(Items.BARRIER));
}
gui.setSlot(2, new ItemStack(Items.LEATHER_HELMET));
gui.setSlot(3, new ItemStack(Items.LEATHER_CHESTPLATE));
gui.setSlot(4, new ItemStack(Items.LEATHER_LEGGINGS));
gui.setSlot(5, new ItemStack(Items.LEATHER_BOOTS));
gui.setSlot(6, new ItemStack(Items.SHIELD));
gui.setSlotRedirect(2 + 9, new ArmorSlot(minion.getInventory(), minion, EquipmentSlot.HEAD, EquipmentSlot.HEAD.getOffsetEntitySlotId(PlayerInventory.MAIN_SIZE), 0, 0, null));
gui.setSlotRedirect(3 + 9, new ArmorSlot(minion.getInventory(), minion, EquipmentSlot.CHEST, EquipmentSlot.CHEST.getOffsetEntitySlotId(PlayerInventory.MAIN_SIZE), 0, 0, null));
gui.setSlotRedirect(4 + 9, new ArmorSlot(minion.getInventory(), minion, EquipmentSlot.LEGS, EquipmentSlot.LEGS.getOffsetEntitySlotId(PlayerInventory.MAIN_SIZE), 0, 0, null));
gui.setSlotRedirect(5 + 9, new ArmorSlot(minion.getInventory(), minion, EquipmentSlot.FEET, EquipmentSlot.FEET.getOffsetEntitySlotId(PlayerInventory.MAIN_SIZE), 0, 0, null));
gui.setSlotRedirect(6 + 9, new Slot(minion.getInventory(), PlayerInventory.OFF_HAND_SLOT, 0, 0));
for (int i = PlayerInventory.HOTBAR_SIZE; i < PlayerInventory.MAIN_SIZE; i++) {
gui.setSlotRedirect(i + 9, new Slot(minion.getInventory(), i, 0, 0));
}
for (int i = 0; i < PlayerInventory.HOTBAR_SIZE; i++) {
gui.setSlotRedirect(i + 45, new Slot(minion.getInventory(), i, 0, 0));
}
gui.open();
}
}
@@ -1,96 +0,0 @@
package io.github.skippyall.minions.gui;
import eu.pb4.sgui.api.elements.GuiElementBuilder;
import eu.pb4.sgui.api.gui.SimpleGui;
import io.github.skippyall.minions.registration.MinionRegistries;
import io.github.skippyall.minions.gui.input.TextInput;
import io.github.skippyall.minions.minion.MinionData;
import io.github.skippyall.minions.minion.MinionItem;
import io.github.skippyall.minions.minion.MinionProfileUtils;
import io.github.skippyall.minions.minion.skin.SkinProvider;
import io.github.skippyall.minions.registration.SkinProviders;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.ProfileComponent;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.screen.ScreenHandlerType;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import java.util.Optional;
public class MinionLookGui extends SimpleGui {
private ItemStack minionItem;
private SkinProvider currentSkinProvider;
public MinionLookGui(ServerPlayerEntity player, ItemStack minionItem) {
super(ScreenHandlerType.GENERIC_9X3, player, false);
this.minionItem = minionItem;
this.currentSkinProvider = SkinProviders.NAME;
}
public void update() {
updateName();
updateSkin();
updateSkinProvider();
}
private void updateName() {
setSlot(10, new GuiElementBuilder()
.setItem(Items.OAK_SIGN)
.setName(Text.literal(getData().name()))
.setCallback(() -> {
openRenameGui(player, minionItem);
})
);
}
private void updateSkin() {
GuiElementBuilder builder = new GuiElementBuilder()
.setItem(Items.PLAYER_HEAD)
.setCallback(() -> currentSkinProvider.openSkinMenu(player).thenAccept(skin -> {
MinionItem.setData(player.getServer(), getData().withSkin(skin), minionItem);
}));
if(MinionItem.getData(player.getServer(), minionItem) != null && MinionItem.getData(player.getServer(), minionItem).skin().isPresent()) {
builder.setComponent(DataComponentTypes.PROFILE, new ProfileComponent(Optional.empty(), Optional.empty(), getData().skin().get()));
}
setSlot(16, builder);
}
private void cycleSkinProvider() {
int currentId = MinionRegistries.SKIN_PROVIDERS.getRawId(currentSkinProvider);
currentId++;
if(MinionRegistries.SKIN_PROVIDERS.size() == currentId) {
currentId = 0;
}
currentSkinProvider = MinionRegistries.SKIN_PROVIDERS.get(currentId);
updateSkinProvider();
}
private void updateSkinProvider() {
setSlot(25, new GuiElementBuilder()
.setItem(Items.GREEN_STAINED_GLASS_PANE)
.setComponent(DataComponentTypes.CUSTOM_NAME, currentSkinProvider.getDisplayName())
.setCallback(this::cycleSkinProvider)
);
}
private MinionData getData() {
return MinionItem.getDataOrDefault(player.getServer(), minionItem);
}
public static void open(ServerPlayerEntity player, ItemStack minionItem) {
MinionLookGui gui = new MinionLookGui(player, minionItem);
gui.update();
gui.open();
}
public void openRenameGui(ServerPlayerEntity player, ItemStack minionItem) {
TextInput.inputSync(player, Text.translatable("minions.gui.look.rename.title"), "Minion", name -> MinionProfileUtils.checkMinionNameWithoutPrefix(player.getServer(), name))
.thenAccept(name -> {
MinionItem.setData(player.getServer(), getData().withName(MinionProfileUtils.getPrefix() + name), minionItem);
open();
});
}
}
@@ -0,0 +1,136 @@
package io.github.skippyall.minions.gui
import com.mojang.authlib.GameProfile
import eu.pb4.sgui.api.elements.GuiElementBuilder
import eu.pb4.sgui.api.gui.SimpleGui
import io.github.skippyall.minions.gui.input.TextInput
import io.github.skippyall.minions.minion.MinionData
import io.github.skippyall.minions.minion.MinionItem
import io.github.skippyall.minions.minion.MinionProfileUtils
import io.github.skippyall.minions.minion.skin.SkinProvider
import io.github.skippyall.minions.registration.MinionRegistries
import io.github.skippyall.minions.registration.SkinProviders
import kotlinx.coroutines.launch
import net.fabricmc.fabric.api.entity.FakePlayer
import net.minecraft.core.component.DataComponents
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.inventory.MenuType
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.item.component.ResolvableProfile
import java.util.Optional
import java.util.function.Consumer
import java.util.function.Function
class MinionLookGui(
viewer: ServerPlayer,
private val minionItem: ItemStack
) : MinionsGui(viewer) {
private lateinit var gui: SimpleGui
private var currentSkinProvider: SkinProvider = SkinProviders.NAME
private val data: MinionData
get() = MinionItem.getDataOrDefault(viewer.level().server, minionItem)
init {
open()
}
override fun open() {
gui = object : SimpleGui(MenuType.GENERIC_9x3, viewer, false) {
override fun onPlayerClose(success: Boolean) {
onBackingClosed()
}
}
update()
gui.open()
}
override fun closeBacking() {
gui.close()
}
fun update() {
updateName()
updateSkin()
updateSkinProvider()
}
private fun updateName() {
gui.setSlot(
10, GuiElementBuilder()
.setItem(Items.OAK_SIGN)
.setName(Component.literal(this.data.name))
.setCallback(this::openRenameGui)
)
}
private fun updateSkin() {
val builder = GuiElementBuilder()
.setItem(Items.PLAYER_HEAD)
.setCallback(this::openSkinGui)
if (MinionItem.getData(viewer.level().server, minionItem)?.skin?.isPresent ?: false) {
builder.setComponent(
DataComponents.PROFILE,
ResolvableProfile.createResolved(GameProfile(FakePlayer.DEFAULT_UUID, "", this.data.skin.get()))
)
}
gui.setSlot(16, builder)
}
fun openSkinGui() {
currentSkinProvider.openSkinMenu(this)
.thenCompose(Function { profile: ResolvableProfile? ->
profile!!.resolveProfile(
viewer.level().server.services().profileResolver()
)
})
.thenAccept(Consumer { skin: GameProfile? ->
MinionItem.setData(
viewer.level().server, this.data.withSkin(
Optional.of(skin!!.properties())
), minionItem
)
})
}
private fun cycleSkinProvider() {
var currentId = MinionRegistries.SKIN_PROVIDERS.getId(currentSkinProvider)
currentId++
if (MinionRegistries.SKIN_PROVIDERS.size() == currentId) {
currentId = 0
}
currentSkinProvider = MinionRegistries.SKIN_PROVIDERS.byId(currentId)!!
updateSkinProvider()
}
private fun updateSkinProvider() {
gui.setSlot(
25, GuiElementBuilder()
.setItem(Items.GREEN_STAINED_GLASS_PANE)
.setComponent(DataComponents.CUSTOM_NAME, currentSkinProvider.getDisplayName())
.setCallback(Runnable { this.cycleSkinProvider() })
)
}
fun openRenameGui() {
scope.launch {
val newName = TextInput.input(
this@MinionLookGui,
Component.translatable("minions.gui.look.rename.title"),
"Minion",
) { name ->
MinionProfileUtils.checkMinionNameWithoutPrefix(viewer.level().server, name)
}
if(newName != null) {
this@MinionLookGui.data.withName(newName)
}
}
}
}
@@ -0,0 +1,84 @@
package io.github.skippyall.minions.gui
import eu.pb4.sgui.api.elements.GuiElementBuilder
import kotlinx.coroutines.CompletableJob
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import net.minecraft.network.chat.Component
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.item.Items
abstract class MinionsGui {
protected val parent: MinionsGui?
@JvmField
val viewer: ServerPlayer
protected var child: MinionsGui? = null
private var open = true
private val job: CompletableJob
val scope: CoroutineScope
constructor(parent: MinionsGui) {
this.viewer = parent.viewer
this.parent = parent
parent.child = this
job = Job(parent.job)
scope = CoroutineScope(Dispatchers.Unconfined.plus(CoroutineName("MinionsGui")).plus(job))
}
constructor(viewer: ServerPlayer) {
this.viewer = viewer
this.parent = null
job = Job(null)
scope = CoroutineScope(Dispatchers.Unconfined.plus(CoroutineName("MinionsGui")).plus(job))
}
protected abstract fun open()
protected fun reopen() {
open()
}
fun onBackingClosed() {
if (child != null && child!!.open) {
return
}
close(true)
}
@JvmOverloads
fun close(alreadyClosed: Boolean = false) {
if (open) {
scope.cancel(null)
open = false
if (child != null) {
child!!.close(alreadyClosed)
} else if (!alreadyClosed) {
closeBacking()
}
}
}
fun goBack() {
if (parent != null) {
open = false
parent.child = null
parent.reopen()
close(true)
} else {
close(false)
}
}
fun backButton(): GuiElementBuilder? {
return GuiElementBuilder(Items.MANGROVE_DOOR)
.setName(Component.translatable("gui.back"))
.setCallback(Runnable { this.goBack() })
}
protected abstract fun closeBacking()
}
@@ -0,0 +1,87 @@
package io.github.skippyall.minions.gui;
import eu.pb4.sgui.api.elements.GuiElementBuilder;
import eu.pb4.sgui.api.gui.SimpleGui;
import net.minecraft.core.IdMap;
import net.minecraft.network.chat.Component;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.Items;
import java.util.List;
import java.util.function.BiFunction;
public class PaginatedList extends MinionsGui {
private int page = 0;
private SimpleGui gui;
private final Component title;
private final int size;
private final BiFunction<Integer, PaginatedList, GuiElementBuilder> display;
public PaginatedList(MinionsGui parent, Component title, int size, BiFunction<Integer, PaginatedList, GuiElementBuilder> display) {
super(parent);
this.title = title;
this.size = size;
this.display = display;
open();
}
@Override
protected void open() {
gui = new SimpleGui(MenuType.GENERIC_9x6, viewer, false) {
@Override
public void onPlayerClose(boolean success) {
onBackingClosed();
}
};
gui.setTitle(title);
gui.setSlot(8, backButton());
addItems();
gui.open();
}
@Override
protected void closeBacking() {
gui.close();
}
public static <T> void createList(MinionsGui parent, Component title, List<T> list, BiFunction<T, PaginatedList, GuiElementBuilder> display) {
new PaginatedList(parent, title, list.size(), (i, gui) -> display.apply(list.get(i), gui));
}
public static <T> void createList(MinionsGui parent, Component title, IdMap<T> list, BiFunction<T, PaginatedList, GuiElementBuilder> display) {
new PaginatedList(parent, title, list.size(), (i, gui) -> display.apply(list.byId(i), gui));
}
private void addItems() {
int slot = 9;
for(int i = 36 * page; i < Math.min(36 * (page + 1), size); i++) {
gui.setSlot(slot, display.apply(i, this));
slot++;
}
if(page > 0) {
gui.setSlot(48, new GuiElementBuilder(Items.SPECTRAL_ARROW)
.setItemName(Component.translatable("book.page_button.previous"))
.setCallback(() -> {
page--;
addItems();
})
);
} else {
gui.clearSlot(48);
}
if(27 * (page + 1) < size) {
gui.setSlot(50, new GuiElementBuilder(Items.ARROW)
.setItemName(Component.translatable("book.page_button.next"))
.setCallback(() -> {
page++;
addItems();
})
);
} else {
gui.clearSlot(50);
}
}
}
@@ -4,10 +4,12 @@ import eu.pb4.sgui.api.elements.GuiElementBuilder;
import eu.pb4.sgui.api.gui.SimpleGui; import eu.pb4.sgui.api.gui.SimpleGui;
import io.github.skippyall.minions.gui.Displayable; import io.github.skippyall.minions.gui.Displayable;
import io.github.skippyall.minions.gui.GuiDisplay; import io.github.skippyall.minions.gui.GuiDisplay;
import net.minecraft.item.Items; import io.github.skippyall.minions.gui.MinionsGui;
import net.minecraft.screen.ScreenHandlerType; import io.github.skippyall.minions.gui.minion.SimpleMinionsGui;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.network.chat.Component;
import net.minecraft.text.Text; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.Items;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -15,13 +17,13 @@ import java.util.function.BiFunction;
import java.util.function.Function; import java.util.function.Function;
public class ChoiceInput { public class ChoiceInput {
public static <T> BiFunction<ServerPlayerEntity, T, CompletableFuture<T>> createDialogOpener(ScreenHandlerType<?> screen, Text title, Function<T, GuiDisplay> displayFunction, T[] values, @Nullable T fallback) { public static <T> BiFunction<ServerPlayer, T, CompletableFuture<T>> createDialogOpener(MenuType<?> screen, Component title, Function<T, GuiDisplay> displayFunction, T[] values, @Nullable T fallback) {
return (player, object) -> { return (player, object) -> {
CompletableFuture<T> future = new CompletableFuture<>(); CompletableFuture<T> future = new CompletableFuture<>();
SimpleGui gui = new SimpleGui(screen, player, false) { SimpleGui gui = new SimpleGui(screen, player, false) {
@Override @Override
public void onClose() { public void onPlayerClose(boolean success) {
if(fallback == null) { if(fallback == null) {
future.cancel(false); future.cancel(false);
} else { } else {
@@ -42,16 +44,16 @@ public class ChoiceInput {
}; };
} }
public static <T extends Displayable> BiFunction<ServerPlayerEntity, T, CompletableFuture<T>> createDialogOpener(T[] values) { public static <T extends Displayable> BiFunction<ServerPlayer, T, CompletableFuture<T>> createDialogOpener(T[] values) {
return createDialogOpener(ScreenHandlerType.GENERIC_9X3, Text.empty(), t -> t != null ? t.getDisplay() : null, values, null); return createDialogOpener(MenuType.GENERIC_9x3, Component.empty(), t -> t != null ? t.getDisplay() : null, values, null);
} }
public static CompletableFuture<Void> confirm(ServerPlayerEntity player, Text title) { public static CompletableFuture<Void> confirm(ServerPlayer player, Component title) {
CompletableFuture<Void> future = new CompletableFuture<>(); CompletableFuture<Void> future = new CompletableFuture<>();
SimpleGui gui = new SimpleGui(ScreenHandlerType.GENERIC_3X3, player, false) { SimpleGui gui = new SimpleGui(MenuType.GENERIC_3x3, player, false) {
@Override @Override
public void onClose() { public void onPlayerClose(boolean success) {
future.cancel(false); future.cancel(false);
} }
}; };
@@ -59,12 +61,12 @@ public class ChoiceInput {
gui.setTitle(title); gui.setTitle(title);
gui.setSlot(3, new GuiElementBuilder(Items.REDSTONE_BLOCK) gui.setSlot(3, new GuiElementBuilder(Items.REDSTONE_BLOCK)
.setName(Text.translatable("minions.gui.abort")) .setName(Component.translatable("minions.gui.abort"))
.setCallback(() -> future.cancel(false)) .setCallback(() -> future.cancel(false))
); );
gui.setSlot(5, new GuiElementBuilder(Items.EMERALD_BLOCK) gui.setSlot(5, new GuiElementBuilder(Items.EMERALD_BLOCK)
.setName(Text.translatable("minions.gui.confirm")) .setName(Component.translatable("minions.gui.confirm"))
.setCallback(() -> future.complete(null)) .setCallback(() -> future.complete(null))
); );
@@ -72,8 +74,44 @@ public class ChoiceInput {
return future; return future;
} }
public static BiFunction<ServerPlayerEntity, Boolean, CompletableFuture<Boolean>> inputBoolean(Text title) { public static CompletableFuture<Boolean> confirm(MinionsGui parent, Component title) {
return createDialogOpener(ScreenHandlerType.GENERIC_3X3, title, value -> { CompletableFuture<Boolean> future = new CompletableFuture<>();
new SimpleMinionsGui(parent, (onClose, me) -> {
SimpleGui gui = new SimpleGui(MenuType.GENERIC_3x3, parent.viewer, false) {
@Override
public void onPlayerClose(boolean success) {
future.complete(false);
onClose.run();
}
};
gui.setTitle(title);
gui.setSlot(3, new GuiElementBuilder(Items.REDSTONE_BLOCK)
.setName(Component.translatable("minions.gui.abort"))
.setCallback(() -> {
future.complete(false);
me.goBack();
})
);
gui.setSlot(5, new GuiElementBuilder(Items.EMERALD_BLOCK)
.setName(Component.translatable("minions.gui.confirm"))
.setCallback(() -> {
future.complete(true);
me.goBack();
})
);
gui.open();
return gui;
});
return future;
}
public static BiFunction<ServerPlayer, Boolean, CompletableFuture<Boolean>> inputBoolean(Component title) {
return createDialogOpener(MenuType.GENERIC_3x3, title, value -> {
if(value) { if(value) {
return new GuiDisplay.ItemBased(Items.EMERALD_BLOCK); return new GuiDisplay.ItemBased(Items.EMERALD_BLOCK);
} else { } else {
@@ -1,10 +1,12 @@
package io.github.skippyall.minions.gui.input; package io.github.skippyall.minions.gui.input;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier;
public interface Result<T, E> { public interface Result<T, E> {
static <T> Result<T, String> wrap(UnsafeOperation<T> toWrap) { static <T> Result<T, String> wrap(UnsafeOperation<T> toWrap) {
@@ -23,44 +25,73 @@ public interface Result<T, E> {
} }
} }
static <T, E> Result<T, E> ofNullable(@Nullable T value, E error) {
if(value != null) {
return new Success<>(value);
} else {
return new Error<>(error);
}
}
static <T, E> Result<T, E> ofNullable(@Nullable T value, Supplier<E> error) {
if(value != null) {
return new Success<>(value);
} else {
return new Error<>(error.get());
}
}
boolean isSuccess(); boolean isSuccess();
@NotNull T getOrDefault(@NotNull T defaultValue); T getOrDefault(T defaultValue);
@NotNull T getOrThrow(); T getOrThrow();
@NotNull E getErrorOrThrow(); E getErrorOrThrow();
@NotNull Optional<T> getOptional(); @NotNull Optional<T> getOptional();
@NotNull Optional<E> getOptionalError();
void ifSuccess(@NotNull Consumer<T> handler); void ifSuccess(@NotNull Consumer<T> handler);
void ifError(@NotNull Consumer<Error<T, E>> handler); void ifError(@NotNull Consumer<Error<T, E>> handler);
record Success<T, E>(@NotNull T result) implements Result<T, E> { <U> Result<U,E> map(Function<T, U> mapper);
<U> Result<U,E> flatMap(Function<T, Result<U, E>> mapper);
<U> Result<T,U> mapError(Function<E, U> mapper);
record Success<T, E>(T result) implements Result<T, E> {
@Override @Override
public boolean isSuccess() { public boolean isSuccess() {
return true; return true;
} }
@Override @Override
public @NotNull T getOrDefault(@NotNull T defaultValue) { public T getOrDefault(T defaultValue) {
return result; return result;
} }
@Override @Override
public @NotNull T getOrThrow() { public @NotNull Optional<E> getOptionalError() {
return Optional.empty();
}
@Override
public T getOrThrow() {
return result; return result;
} }
@Override @Override
public @NotNull E getErrorOrThrow() { public E getErrorOrThrow() {
throw new RuntimeException("Result was not an Error"); throw new RuntimeException("Result was not an Error");
} }
@Override @Override
public @NotNull Optional<T> getOptional() { public @NotNull Optional<T> getOptional() {
return Optional.of(result); return Optional.ofNullable(result);
} }
@Override @Override
@@ -72,26 +103,42 @@ public interface Result<T, E> {
public void ifError(@NotNull Consumer<Error<T, E>> handler) { public void ifError(@NotNull Consumer<Error<T, E>> handler) {
} }
@Override
public <U> Result<U, E> map(Function<T, U> mapper) {
return new Success<>(mapper.apply(result));
}
@Override
public <U> Result<U, E> flatMap(Function<T, Result<U, E>> mapper) {
return mapper.apply(result);
}
@Override
public <U> Result<T, U> mapError(Function<E, U> mapper) {
return new Success<>(result);
}
} }
record Error<T, E>(@NotNull E message) implements Result<T, E> { record Error<T, E>(E message) implements Result<T, E> {
@Override @Override
public boolean isSuccess() { public boolean isSuccess() {
return false; return false;
} }
@Override @Override
public @NotNull T getOrDefault(@NotNull T defaultValue) { public T getOrDefault(
T defaultValue) {
return defaultValue; return defaultValue;
} }
@Override @Override
public @NotNull T getOrThrow() { public T getOrThrow() {
throw new RuntimeException("Result was an error: " + message); throw new RuntimeException("Result was an error: " + message);
} }
@Override @Override
public @NotNull E getErrorOrThrow() { public E getErrorOrThrow() {
return message; return message;
} }
@@ -100,6 +147,11 @@ public interface Result<T, E> {
return Optional.empty(); return Optional.empty();
} }
@Override
public @NotNull Optional<E> getOptionalError() {
return Optional.ofNullable(message);
}
@Override @Override
public void ifSuccess(@NotNull Consumer<T> handler) { public void ifSuccess(@NotNull Consumer<T> handler) {
@@ -109,6 +161,21 @@ public interface Result<T, E> {
public void ifError(@NotNull Consumer<Error<T, E>> handler) { public void ifError(@NotNull Consumer<Error<T, E>> handler) {
handler.accept(this); handler.accept(this);
} }
@Override
public <U> Result<U, E> map(Function<T, U> mapper) {
return new Error<>(message);
}
@Override
public <U> Result<U, E> flatMap(Function<T, Result<U, E>> mapper) {
return new Error<>(message);
}
@Override
public <U> Result<T, U> mapError(Function<E, U> mapper) {
return new Error<>(mapper.apply(message));
}
} }
interface UnsafeOperation<T> { interface UnsafeOperation<T> {
@@ -1,91 +0,0 @@
package io.github.skippyall.minions.gui.input;
import eu.pb4.sgui.api.elements.GuiElementBuilder;
import eu.pb4.sgui.api.gui.AnvilInputGui;
import net.minecraft.item.Items;
import net.minecraft.screen.AnvilScreenHandler;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
public class TextInput<T> extends AnvilInputGui {
private final GuiElementBuilder valid = new GuiElementBuilder()
.setItem(Items.EMERALD_BLOCK)
.setName(Text.literal("OK"))
.setCallback(this::onConfirm);
private final GuiElementBuilder invalid = new GuiElementBuilder()
.setItem(Items.REDSTONE_BLOCK);
private final Function<String, CompletableFuture<Result<T, Text>>> parser;
private final CompletableFuture<T> future;
private Result<T, Text> result;
private boolean isConfirm;
public TextInput(ServerPlayerEntity player, Text title, String defaultValue, Function<String, CompletableFuture<Result<T, Text>>> parser, CompletableFuture<T> future) {
super(player, false);
setTitle(title);
setDefaultInputValue(defaultValue);
this.parser = parser;
this.future = future;
updateConfirmButton(defaultValue);
}
public static <T> CompletableFuture<T> inputSync(ServerPlayerEntity player, Text title, String defaultValue, Function<String, Result<T, Text>> parser) {
return input(player, title, defaultValue, (String string) -> CompletableFuture.completedFuture(parser.apply(string)));
}
public static <T> CompletableFuture<T> input(ServerPlayerEntity player, Text title, String defaultValue, Function<String, CompletableFuture<Result<T, Text>>> parser) {
CompletableFuture<T> future = new CompletableFuture<>();
new TextInput<>(player, title, defaultValue, parser, future).open();
return future;
}
public static CompletableFuture<String> inputString(ServerPlayerEntity player, Text title, String defaultValue) {
return inputSync(player, title, defaultValue, Result.Success::new);
}
public static CompletableFuture<Long> inputLong(ServerPlayerEntity player, Text title, String defaultValue) {
return inputSync(player, title, defaultValue, string -> Result.wrapCustomError(() -> Long.valueOf(string), Text.translatable("minions.command.input.int.fail")));
}
public static CompletableFuture<Double> inputDouble(ServerPlayerEntity player, Text title, String defaultValue) {
return inputSync(player, title, defaultValue, string -> Result.wrapCustomError(() -> Double.valueOf(string), Text.translatable("minions.command.input.float.fail")));
}
@Override
public void onInput(String input) {
updateConfirmButton(input);
}
public void updateConfirmButton(String input) {
parser.apply(input).thenAccept(result -> {
this.result = result;
if(result.isSuccess()) {
setSlot(AnvilScreenHandler.OUTPUT_ID, valid);
} else {
Text text = result.getErrorOrThrow();
setSlot(AnvilScreenHandler.OUTPUT_ID, invalid.setName(text));
}
});
}
@Override
public void onClose() {
if(!future.isDone() && !isConfirm) {
future.cancel(false);
}
}
public void onConfirm() {
if(result != null) {
result.ifSuccess(success -> {
isConfirm = true;
close();
future.complete(success);
});
}
}
}
@@ -0,0 +1,222 @@
package io.github.skippyall.minions.gui.input
import eu.pb4.sgui.api.elements.GuiElementBuilder
import eu.pb4.sgui.api.gui.AnvilInputGui
import io.github.skippyall.minions.gui.MinionsGui
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.launch
import net.minecraft.network.chat.Component
import net.minecraft.world.inventory.AnvilMenu
import net.minecraft.world.item.Items
import java.util.concurrent.CompletableFuture
class TextInput<T>(
parent: MinionsGui,
val title: Component,
val defaultValue: String,
val parser: suspend (String) -> Result<T, Component>
) : MinionsGui(parent) {
private val valid: GuiElementBuilder = GuiElementBuilder()
.setItem(Items.EMERALD_BLOCK)
.setName(Component.literal("OK"))
.setCallback(Runnable { this.onConfirm() })
private val invalid: GuiElementBuilder = GuiElementBuilder()
.setItem(Items.REDSTONE_BLOCK)
private lateinit var gui: AnvilInputGui
private var result: Result<T, Component>? = null
private var isConfirm = false
val job = Job()
init {
updateConfirmButton(defaultValue)
open()
}
override fun open() {
gui = object : AnvilInputGui(viewer, false) {
override fun onInput(input: String) {
updateConfirmButton(input)
}
override fun onPlayerClose(success: Boolean) {
onBackingClosed()
if (job.isActive && !isConfirm) {
job.cancel()
}
}
}
gui.setTitle(title)
gui.setDefaultInputValue(defaultValue)
gui.open()
}
override fun closeBacking() {
gui.close()
}
fun updateConfirmButton(input: String) {
scope.launch {
val result = parser(input)
this@TextInput.result = result
if (result.isSuccess()) {
gui.setSlot(AnvilMenu.RESULT_SLOT, valid)
} else {
val text = result.getErrorOrThrow()
gui.setSlot(AnvilMenu.RESULT_SLOT, invalid.setName(text))
}
}
}
fun onConfirm() {
result?.ifSuccess { _: T? ->
isConfirm = true
}
job.complete()
}
companion object {
@JvmStatic
suspend fun <T>input(
gui: MinionsGui,
title: Component,
defaultValue: String,
parser: suspend (String) -> Result<T, Component>,
): T? {
val input = TextInput(
parent = gui,
title = title,
defaultValue = defaultValue,
parser = parser,
)
input.job.join()
return input.result?.getOrDefault(null)
}
@JvmStatic
fun <T>inputFuture(
gui: MinionsGui,
title: Component,
defaultValue: String,
parser: (String) -> Result<T, Component>,
): CompletableFuture<T?> {
return gui.scope.async {
val input = TextInput(
parent = gui,
title = title,
defaultValue = defaultValue,
parser = parser,
)
input.job.join()
return@async input.result?.getOrDefault(null)
}.asCompletableFuture()
}
@JvmStatic
suspend fun inputString(
gui: MinionsGui,
title: Component,
defaultValue: String,
): String? {
return input<String>(
gui = gui,
title = title,
defaultValue = defaultValue,
parser = { result: String? -> Result.Success<String, Component>(result) },
)
}
@JvmStatic
fun inputStringFuture(
gui: MinionsGui,
title: Component,
defaultValue: String,
): CompletableFuture<String?> {
return gui.scope.async {
inputString(
gui,
title,
defaultValue
)
}.asCompletableFuture()
}
@JvmStatic
suspend fun inputLong(
gui: MinionsGui,
title: Component,
defaultValue: Long,
): Long? {
return input<Long>(
gui = gui,
title = title,
defaultValue = defaultValue.toString(),
parser = { string ->
Result.wrapCustomError<Long, Component>(
{ string.toLong() },
Component.translatable("minions.command.input.int.fail")
)
},
)
}
@JvmStatic
fun inputLongFuture(
gui: MinionsGui,
title: Component,
defaultValue: Long,
): CompletableFuture<Long?> {
return gui.scope.async {
inputLong(
gui,
title,
defaultValue
)
}.asCompletableFuture()
}
@JvmStatic
suspend fun inputDouble(
gui: MinionsGui,
title: Component,
defaultValue: Double,
): Double? {
return input<Double>(
gui = gui,
title = title,
defaultValue = defaultValue.toString(),
parser = { string ->
Result.wrapCustomError<Double, Component>(
{ string.toDouble() },
Component.translatable("minions.command.input.int.fail")
)
},
)
}
@JvmStatic
fun inputDoubleFuture(
gui: MinionsGui,
title: Component,
defaultValue: Double,
): CompletableFuture<Double?> {
return gui.scope.async {
inputDouble(
gui,
title,
defaultValue
)
}.asCompletableFuture()
}
}
}
@@ -0,0 +1,157 @@
package io.github.skippyall.minions.gui.instruction;
import eu.pb4.sgui.api.elements.GuiElementBuilder;
import eu.pb4.sgui.api.gui.SimpleGui;
import io.github.skippyall.minions.gui.GuiDisplay;
import io.github.skippyall.minions.gui.MinionsGui;
import io.github.skippyall.minions.gui.PaginatedList;
import io.github.skippyall.minions.gui.minion.GuiContext;
import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.program.instruction.ConfiguredInstruction;
import io.github.skippyall.minions.program.supplier.Parameter;
import io.github.skippyall.minions.program.supplier.ValueSupplier;
import io.github.skippyall.minions.program.supplier.ValueSupplierList;
import io.github.skippyall.minions.program.supplier.ValueSupplierType;
import io.github.skippyall.minions.registration.MinionRegistries;
import io.github.skippyall.minions.util.TranslationUtil;
import net.minecraft.network.chat.Component;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import org.jetbrains.annotations.Nullable;
public class ArgumentGui extends MinionsGui {
private final GuiContext.ValueSupplier context;
private final ConfiguredInstruction<MinionRuntime> instruction;
private final Parameter<?> parameter;
private SimpleGui gui;
private @Nullable ValueSupplierType<MinionRuntime> argumentType;
private @Nullable ValueSupplierList.ValueSupplierEntry<?, MinionRuntime> entry;
public ArgumentGui(MinionsGui parent, GuiContext.ValueSupplier context) {
super(parent);
instruction = context.getInstruction();
this.parameter = context.getParameter();
this.context = context;
this.entry = instruction.getArguments().getEntry(parameter);
if(entry != null) {
this.argumentType = entry.getSupplier().getType();
}
open();
}
public String getInstructionName() {
return context.getName();
}
public @Nullable ValueSupplier<?, MinionRuntime> getArgument() {
if(entry != null) {
return entry.getSupplier();
}
return null;
}
@Override
protected void open() {
gui = new SimpleGui(MenuType.GENERIC_3x3, viewer, false) {
@Override
public void onPlayerClose(boolean success) {
onBackingClosed();
}
};
gui.setTitle(Component.translatable(
"minions.gui.instruction.argument.title",
parameter.name(),
Component.translatable(TranslationUtil.getTranslationKey(parameter.type(), MinionRegistries.VALUE_TYPES))
));
gui.setSlot(2, backButton());
updateTypeConfiguration();
updateArgumentConfiguration();
updateConverterConfiguration();
gui.open();
}
private void updateTypeConfiguration() {
ItemStack displayStack;
if(argumentType != null) {
displayStack = GuiDisplay.getDisplayStack(MinionRegistries.VALUE_SUPPLIER_TYPES, argumentType, viewer.registryAccess());
} else {
displayStack = new ItemStack(Items.BARRIER);
}
gui.setSlot(3, new GuiElementBuilder(displayStack)
.setName(Component.translatable("minions.gui.instruction.argument.configure.type"))
.addLoreLine(Component.translatable(TranslationUtil.getTranslationKey(
argumentType,
MinionRegistries.VALUE_SUPPLIER_TYPES,
"minions.gui.not_set"
)))
.setCallback(this::selectArgumentType)
);
}
private void updateArgumentConfiguration() {
if(argumentType != null) {
gui.setSlot(4, new GuiElementBuilder(Items.STRUCTURE_VOID)
.setName(Component.translatable("minions.gui.instruction.argument.configure.data"))
.addLoreLine(getArgument() != null ? getArgument().getDisplayText() : Component.translatable("minions.gui.not_set"))
.setCallback(() -> argumentType.openConfiguration(this, parameter.type(), getArgument())
.thenAccept(this::setArgument)
)
);
}
}
private void updateConverterConfiguration() {
if(entry != null) {
gui.setSlot(5, new GuiElementBuilder(Items.CRAFTER)
.setName(Component.translatable("minions.gui.instruction.converters"))
.setCallback(this::configureConvertersMenu)
);
}
}
@Override
protected void closeBacking() {
gui.close();
}
public void setArgumentType(ValueSupplierType<MinionRuntime> type) {
this.argumentType = type;
if(entry != null && getArgument().getType() != argumentType) {
instruction.getArguments().removeEntry(parameter);
entry = null;
}
updateTypeConfiguration();
}
public void setArgument(ValueSupplier<?, MinionRuntime> argument) {
if(entry != null) {
entry.setSupplier(argument);
} else {
entry = instruction.getArguments().createEntry(parameter, argument);
}
}
public void selectArgumentType() {
PaginatedList.createList(this, Component.translatable("minions.gui.instruction.argument.configure.type.title"), MinionRegistries.VALUE_SUPPLIER_TYPES, (type, me) ->
new GuiElementBuilder(GuiDisplay.getDisplayStackWithName(MinionRegistries.VALUE_SUPPLIER_TYPES, type, viewer.registryAccess()))
.setCallback(() -> {
setArgumentType(type);
me.goBack();
})
);
}
public void configureConvertersMenu() {
if(entry != null) {
new ConverterListGui(this, entry.getConverters(), entry.getSupplier().getValueType(), entry.getParameter().type());
}
}
}
@@ -0,0 +1,144 @@
package io.github.skippyall.minions.gui.instruction;
import eu.pb4.sgui.api.elements.GuiElementBuilder;
import eu.pb4.sgui.api.gui.SimpleGui;
import io.github.skippyall.minions.clipboard.ClipboardItem;
import io.github.skippyall.minions.gui.MinionsGui;
import io.github.skippyall.minions.gui.input.ChoiceInput;
import io.github.skippyall.minions.gui.minion.GuiContext;
import io.github.skippyall.minions.minion.MinionListener;
import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.program.instruction.ConfiguredInstruction;
import io.github.skippyall.minions.program.instruction.ConfiguredInstructionListener;
import io.github.skippyall.minions.program.supplier.Parameter;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundSoundEntityPacket;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.Items;
public class ConfigureInstructionGui extends MinionsGui implements ConfiguredInstructionListener, MinionListener {
private String name;
private final ConfiguredInstruction<MinionRuntime> instruction;
private final MinionFakePlayer minion;
private final GuiContext.Instruction context;
private SimpleGui gui;
public ConfigureInstructionGui(MinionsGui parent, GuiContext.Instruction context) {
super(parent);
this.name = context.getName();
this.instruction = context.getInstruction();
this.minion = context.getMinion();
this.context = context;
minion.addMinionListener(this);
instruction.addListener(this);
open();
}
@Override
protected void open() {
gui = new SimpleGui(MenuType.GENERIC_9x3, viewer, false) {
@Override
public void onPlayerClose(boolean success) {
onBackingClosed();
}
};
gui.setTitle(Component.literal(name));
gui.setSlot(6, new GuiElementBuilder(Items.ANVIL)
.setName(Component.translatable("minions.gui.instruction.configure.rename"))
.setCallback(() -> InstructionGui.inputInstructionName(this, context, name).thenAccept(newName -> {
minion.getInstructionManager().setInstructionName(name, newName);
reopen();
}))
);
gui.setSlot(7, new GuiElementBuilder(Items.LAVA_BUCKET)
.setName(Component.translatable("minions.gui.instruction.configure.delete"))
.setCallback(() -> ChoiceInput.confirm(this, Component.translatable("minions.gui.instruction.configure.delete.confirm", name))
.thenAccept((confirmed) -> {
if(confirmed) {
minion.getInstructionManager().removeInstruction(name);
goBack();
}
}))
);
gui.setSlot(8, backButton());
updateSuppliers();
gui.setSlot(13, InstructionGui.createInstructionElement(instruction.getInstruction(), viewer.registryAccess()));
gui.setSlot(25, new GuiElementBuilder(Items.FEATHER)
.setName(Component.translatable("minions.gui.instruction.configure.copy"))
.addLoreLine(Component.translatable("minions.gui.instruction.configure.copy.description"))
.setCallback(() -> {
viewer.getInventory().placeItemBackInInventory(ClipboardItem.createInstructionReference(minion, name), true);
viewer.connection.send(new ClientboundSoundEntityPacket(SoundEvents.NOTE_BLOCK_CHIME, SoundSource.BLOCKS, viewer, 1, 1, 0));
})
);
updateRunSlot();
gui.open();
}
@Override
protected void closeBacking() {
gui.close();
minion.removeMinionListener(this);
instruction.removeListener(this);
}
@Override
public void onInstructionRename(MinionFakePlayer minion, ConfiguredInstruction<?> instruction, String oldName, String newName) {
gui.setTitle(Component.literal(newName));
name = newName;
context.setName(newName);
}
@Override
public void onRun(ConfiguredInstruction<?> instruction) {
updateRunSlot();
}
@Override
public void onStop(ConfiguredInstruction<?> instruction) {
updateRunSlot();
}
@Override
public void onSupplierChange(ConfiguredInstruction<?> instruction, Parameter<?> parameter) {
updateSuppliers();
}
private void updateRunSlot() {
if(!instruction.isRunning()) {
gui.setSlot(26, new GuiElementBuilder(Items.ARROW)
.setName(Component.translatable("minions.gui.instruction.run"))
.setCallback(() -> instruction.run(minion.getInstructionManager()))
);
} else {
gui.setSlot(26, new GuiElementBuilder(Items.BARRIER)
.setName(Component.translatable("minions.gui.instruction.stop"))
.setCallback(() -> instruction.stop(minion.getInstructionManager()))
);
}
}
private void updateSuppliers() {
int slot = 12;
for(Parameter<?> parameter : instruction.getInstruction().getParameters().reversed()) {
gui.setSlot(slot, InstructionGui.createParameterElement(parameter, instruction.getArguments().getArgument(parameter), viewer.registryAccess())
.setCallback(() -> new ArgumentGui(this, GuiContext.ValueSupplier.create(context, parameter)))
);
slot--;
}
}
}
@@ -0,0 +1,122 @@
package io.github.skippyall.minions.gui.instruction;
import eu.pb4.sgui.api.elements.GuiElementBuilder;
import eu.pb4.sgui.api.gui.SimpleGui;
import io.github.skippyall.minions.gui.GuiDisplay;
import io.github.skippyall.minions.gui.MinionsGui;
import io.github.skippyall.minions.gui.PaginatedList;
import io.github.skippyall.minions.program.conversion.ConverterList;
import io.github.skippyall.minions.program.conversion.ValueConverter;
import io.github.skippyall.minions.program.conversion.ValueConverterType;
import io.github.skippyall.minions.program.value.ValueType;
import io.github.skippyall.minions.registration.MinionRegistries;
import net.minecraft.core.RegistryAccess;
import net.minecraft.network.chat.Component;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.Items;
import org.jetbrains.annotations.Nullable;
public class ConverterGui extends MinionsGui {
private @Nullable ValueConverterType<?> valueConverterType;
private @Nullable ValueConverter<?,?> converter;
private ConverterList list;
private ValueType<?> from, to;
private SimpleGui gui;
private boolean isNew;
private int index;
public ConverterGui(MinionsGui parent, @Nullable ValueConverter<?,?> converter, ValueType<?> from, ValueType<?> to, ConverterList list, boolean isNew, int index) {
super(parent);
open();
this.converter = converter;
if(converter != null) {
this.valueConverterType = converter.getType();
}
this.from = from;
this.to = to;
this.list = list;
this.isNew = isNew;
this.index = index;
}
@Override
protected void open() {
gui = new SimpleGui(MenuType.GENERIC_3x3, viewer, false) {
@Override
public void onPlayerClose(boolean success) {
onBackingClosed();
}
};
gui.setTitle(Component.translatable("minions.gui.instruction.converter.title"));
updateTypeDisplay();
updateConverterDisplay();
gui.setSlot(8, backButton());
gui.open();
}
private void updateTypeDisplay() {
gui.setSlot(3, new GuiElementBuilder(GuiDisplay.getDisplayStackWithName(MinionRegistries.VALUE_CONVERTER_TYPES, valueConverterType, viewer.registryAccess()))
.setCallback(this::configureType)
);
}
private void updateConverterDisplay() {
gui.setSlot(5, new GuiElementBuilder(Items.STRUCTURE_VOID)
.setName(Component.translatable("minions.gui.instruction.converter.title"))
.addLoreLine(converter == null ? Component.translatable("minions.gui.not_set") : converter.getDisplayText())
.setCallback(this::configureData)
);
}
@Override
protected void closeBacking() {
gui.close();
}
private void setType(ValueConverterType<?> valueConverterType) {
this.valueConverterType = valueConverterType;
updateTypeDisplay();
}
public void setConverter(ValueConverter<?,?> converter) {
this.converter = converter;
if(isNew) {
list.getConverters().add(index, converter);
isNew = false;
} else {
list.getConverters().set(index, converter);
}
updateConverterDisplay();
}
private void configureType() {
PaginatedList.createList(
this,
Component.translatable("minions.gui.instruction.converter.type.title"),
MinionRegistries.VALUE_CONVERTER_TYPES,
(type, me) -> new GuiElementBuilder(
GuiDisplay.getDisplayStackWithName(MinionRegistries.VALUE_CONVERTER_TYPES, type, viewer.registryAccess())
).setCallback(() -> {
setType(type);
me.goBack();
})
);
}
private void configureData() {
if(valueConverterType != null) {
valueConverterType.configure(this, from, to, converter)
.thenAccept(this::setConverter);
}
}
public static GuiElementBuilder createConverterElement(ValueConverter<?,?> converter, RegistryAccess manager) {
GuiElementBuilder builder = new GuiElementBuilder(GuiDisplay.getDisplayStack(MinionRegistries.VALUE_CONVERTER_TYPES, converter.getType(), manager));
builder.addLoreLine(converter.getDisplayText());
return builder;
}
}
@@ -0,0 +1,111 @@
package io.github.skippyall.minions.gui.instruction;
import eu.pb4.sgui.api.elements.GuiElementBuilder;
import eu.pb4.sgui.api.gui.SimpleGui;
import io.github.skippyall.minions.gui.GuiDisplay;
import io.github.skippyall.minions.gui.MinionsGui;
import io.github.skippyall.minions.program.conversion.ConverterList;
import io.github.skippyall.minions.program.conversion.ValueConverter;
import io.github.skippyall.minions.program.value.ValueType;
import io.github.skippyall.minions.registration.MinionRegistries;
import io.github.skippyall.minions.util.TranslationUtil;
import net.minecraft.network.chat.Component;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.Items;
public class ConverterListGui extends MinionsGui {
private ConverterList converters;
private SimpleGui gui;
private int page = 0;
private ValueType<?> inputType;
private ValueType<?> outputType;
public ConverterListGui(MinionsGui parent, ConverterList converters, ValueType<?> inputType, ValueType<?> outputType) {
super(parent);
this.converters = converters;
this.inputType = inputType;
this.outputType = outputType;
open();
}
@Override
protected void open() {
gui = new SimpleGui(MenuType.GENERIC_9x3, viewer, false) {
@Override
public void onPlayerClose(boolean success) {
onBackingClosed();
}
};
gui.setTitle(Component.translatable("minions.gui.instruction.converters"));
updateConverters();
gui.setSlot(21, new GuiElementBuilder(Items.SPECTRAL_ARROW)
.setCallback(() -> {
if(page > 0) {
page--;
updateConverters();
}
})
);
gui.setSlot(23, new GuiElementBuilder(Items.ARROW)
.setCallback(() -> {
if(page * 4 + 4 < converters.getConverters().size()) {
page++;
updateConverters();
}
})
);
gui.setSlot(8, backButton());
gui.open();
}
public void updateConverters() {
int lastConverter = Math.min(5, converters.getConverters().size() + 2 - page * 4);
for(int i = 0; i < lastConverter; i++) {
//Each page has 5 converters, but the last is displayed on the next page as well
int converterIndex = page * 4 + i;
//without input element
int actualConverterIndex = converterIndex - 1;
int slot = 9 + 2 * i;
if(converterIndex == 0) {
gui.setSlot(slot, new GuiElementBuilder(Items.DROPPER));
} else if(converterIndex == converters.getConverters().size() + 1) {
gui.setSlot(slot, new GuiElementBuilder(Items.HOPPER));
} else {
ValueConverter<?, ?> converter = converters.getConverters().get(actualConverterIndex);
ValueType<?> fromType = actualConverterIndex >= 1 ? converters.getConverters().get(actualConverterIndex - 1).getTo() : inputType;
ValueType<?> toType = actualConverterIndex < converters.getConverters().size() - 1 ? converters.getConverters().get(actualConverterIndex + 1).getFrom() : outputType;
gui.setSlot(slot, new GuiElementBuilder(GuiDisplay.getDisplayStackWithName(MinionRegistries.VALUE_CONVERTER_TYPES, converter.getType(), viewer.registryAccess()))
.addLoreLine(converter.getDisplayText())
.setCallback(() -> new ConverterGui(this, converter, fromType, toType, converters, false, actualConverterIndex))
);
}
if(i != 4 && actualConverterIndex != converters.getConverters().size()) {
ValueType<?> fromType = actualConverterIndex >= 0 ? converters.getConverters().get(actualConverterIndex).getTo() : inputType;
ValueType<?> toType = actualConverterIndex < converters.getConverters().size() - 1 ? converters.getConverters().get(actualConverterIndex + 1).getFrom() : outputType;
gui.setSlot(slot + 1, new GuiElementBuilder(Items.MAGENTA_GLAZED_TERRACOTTA)
.setName(Component.translatable(
"minions.gui.instruction.converters.cast",
Component.translatable(TranslationUtil.getTranslationKey(fromType, MinionRegistries.VALUE_TYPES)),
Component.translatable(TranslationUtil.getTranslationKey(toType, MinionRegistries.VALUE_TYPES))
))
.setCallback(() -> new ConverterGui(this, null, fromType, toType, converters, true, actualConverterIndex + 1))
);
}
}
}
@Override
protected void closeBacking() {
gui.close();
}
}
@@ -0,0 +1,185 @@
package io.github.skippyall.minions.gui.instruction;
import eu.pb4.sgui.api.elements.GuiElementBuilder;
import eu.pb4.sgui.api.gui.SimpleGui;
import io.github.skippyall.minions.gui.GuiDisplay;
import io.github.skippyall.minions.gui.MinionsGui;
import io.github.skippyall.minions.gui.input.Result;
import io.github.skippyall.minions.gui.input.TextInput;
import io.github.skippyall.minions.gui.minion.GuiContext;
import io.github.skippyall.minions.gui.minion.SimpleMinionsGui;
import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.module.MinionModule;
import io.github.skippyall.minions.program.instruction.ConfiguredInstruction;
import io.github.skippyall.minions.program.instruction.InstructionType;
import io.github.skippyall.minions.program.supplier.Parameter;
import io.github.skippyall.minions.program.supplier.ValueSupplier;
import io.github.skippyall.minions.registration.MinionComponentTypes;
import io.github.skippyall.minions.registration.MinionRegistries;
import io.github.skippyall.minions.util.TranslationUtil;
import net.minecraft.core.RegistryAccess;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import org.jetbrains.annotations.Nullable;
import java.util.NoSuchElementException;
import java.util.concurrent.CompletableFuture;
public class InstructionGui {
public static void openInstructionMainMenu(MinionsGui parent, GuiContext.Minion context) {
new SimpleMinionsGui(parent, (onClose, me) -> {
ServerPlayer player = parent.viewer;
SimpleGui gui = new SimpleGui(MenuType.GENERIC_3x3, player, false) {
@Override
public void onPlayerClose(boolean success) {
onClose.run();
}
};
gui.setTitle(Component.translatable("minions.gui.instruction.title"));
gui.setSlot(2, me.backButton());
gui.setSlot(3, new GuiElementBuilder()
.setItem(Items.BOOK)
.setName(Component.translatable("minions.gui.instruction.list"))
.setCallback(() -> new InstructionListGui(me, context))
);
gui.setSlot(5, new GuiElementBuilder()
.setItem(Items.WRITABLE_BOOK)
.setName(Component.translatable("minions.gui.instruction.create"))
.setCallback(() -> createNewInstruction(me, context))
);
gui.open();
return gui;
});
}
public static void createNewInstruction(MinionsGui parent, GuiContext.Minion context) {
MinionFakePlayer minion = context.getMinion();
ServerPlayer viewer = parent.viewer;
selectInstructionModuleMenu(parent, context).thenAccept(instructionType ->
inputInstructionName(parent, context, "Instruction").thenAccept(name -> {
if (!minion.isRemoved() && !minion.hasDisconnected()) {
ConfiguredInstruction<MinionRuntime> configuredInstruction = minion.getInstructionManager().createInstruction(name, instructionType);
new ConfigureInstructionGui(parent, GuiContext.Instruction.create(context, configuredInstruction, name));
}
})
);
}
public static CompletableFuture<String> inputInstructionName(MinionsGui parent, GuiContext.Minion context, String defaultValue) {
return TextInput.inputFuture(parent, Component.translatable("minions.gui.instruction.enter_name"), defaultValue, name -> {
if (context.getMinion().getInstructionManager().hasInstruction(name)) {
return new Result.Error<>(Component.translatable("minions.gui.instruction.name_already_used"));
}
return new Result.Success<>(name);
});
}
public static boolean checkInstructionExists(String name, ConfiguredInstruction<?> instruction, MinionFakePlayer minion, ServerPlayer player) {
boolean stillExists = !minion.isRemoved() && !minion.hasDisconnected() && minion.getInstructionManager().getInstruction(name) == instruction;
if (!stillExists) {
player.closeContainer();
player.sendSystemMessage(Component.translatable("minions.gui.instruction.removed"));
}
return stillExists;
}
public static CompletableFuture<InstructionType<MinionRuntime>> selectInstructionModuleMenu(MinionsGui parent, GuiContext.Minion context) {
MinionFakePlayer minion = context.getMinion();
ServerPlayer viewer = parent.viewer;
if (minion.getModuleInventory().getModules().isEmpty()) {
viewer.sendSystemMessage(Component.translatable("minions.gui.instruction.no_modules"));
return CompletableFuture.failedFuture(new NoSuchElementException("No modules"));
}
CompletableFuture<InstructionType<MinionRuntime>> future = new CompletableFuture<>();
new SimpleMinionsGui(parent, (closeHandler, me) -> {
SimpleGui gui = new SimpleGui(MenuType.GENERIC_9x4, viewer, false) {
@Override
public void onPlayerClose(boolean success) {
if (!future.isDone()) {
future.cancel(false);
}
closeHandler.run();
}
};
gui.setTitle(Component.translatable("minions.gui.instruction.select_instruction"));
gui.setSlot(8, me.backButton());
for (int i = 0; i < minion.getModuleInventory().getContainerSize(); i++) {
ItemStack moduleItem = minion.getModuleInventory().getItem(i);
MinionModule module = moduleItem.get(MinionComponentTypes.MODULE);
if (module != null && !module.instructions().isEmpty()) {
gui.setSlot(i + 9, new GuiElementBuilder(moduleItem)
.setCallback(() -> selectInstructionMenu(parent, context, module)
.thenApply(future::complete)
)
);
}
}
gui.open();
return gui;
});
return future;
}
public static CompletableFuture<InstructionType<MinionRuntime>> selectInstructionMenu(MinionsGui parent, GuiContext.Minion context, MinionModule module) {
CompletableFuture<InstructionType<MinionRuntime>> future = new CompletableFuture<>();
new SimpleMinionsGui(parent, (closeHandler, me) -> {
SimpleGui gui = new SimpleGui(MenuType.GENERIC_9x4, parent.viewer, false) {
@Override
public void onPlayerClose(boolean success) {
if (!future.isDone()) {
future.cancel(false);
}
closeHandler.run();
}
};
gui.setTitle(Component.translatable("minions.gui.instruction.select_instruction"));
gui.setSlot(8, me.backButton());
int slot = 9;
for (InstructionType<MinionRuntime> instructionType : module.instructions()) {
gui.setSlot(slot, createInstructionElement(instructionType, parent.viewer.registryAccess())
.setCallback(() -> future.complete(instructionType))
);
slot++;
}
gui.open();
return gui;
});
return future;
}
public static GuiElementBuilder createInstructionElement(InstructionType<MinionRuntime> instructionType, RegistryAccess manager) {
GuiElementBuilder instructionBuilder;
if (instructionType != null) {
instructionBuilder = new GuiElementBuilder(GuiDisplay.getDisplayStackWithName(MinionRegistries.INSTRUCTION_TYPES, instructionType, manager));
} else {
instructionBuilder = new GuiElementBuilder(Items.RED_WOOL)
.setName(Component.translatable("minions.gui.instruction.no_instruction_set"));
}
return instructionBuilder;
}
public static GuiElementBuilder createParameterElement(Parameter<?> parameter, @Nullable ValueSupplier<?,?> valueSupplier, RegistryAccess manager) {
GuiElementBuilder builder = new GuiElementBuilder(GuiDisplay.getDisplayStack(MinionRegistries.VALUE_TYPES, parameter.type(), manager))
.setName(Component.translatable("minions.gui.instruction.parameter", parameter.name(), Component.translatable(TranslationUtil.getTranslationKey(parameter.type(), MinionRegistries.VALUE_TYPES))));
if(valueSupplier != null) {
builder.addLoreLine(Component.translatable("minions.gui.instruction.argument", valueSupplier.getDisplayText()));
}
return builder;
}
}
@@ -0,0 +1,66 @@
package io.github.skippyall.minions.gui.instruction;
import eu.pb4.sgui.api.elements.GuiElementBuilder;
import eu.pb4.sgui.api.gui.SimpleGui;
import io.github.skippyall.minions.gui.GuiDisplay;
import io.github.skippyall.minions.gui.MinionsGui;
import io.github.skippyall.minions.gui.minion.GuiContext;
import io.github.skippyall.minions.minion.MinionListener;
import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.program.instruction.ConfiguredInstruction;
import io.github.skippyall.minions.registration.MinionRegistries;
import net.minecraft.network.chat.Component;
import net.minecraft.world.inventory.MenuType;
public class InstructionListGui extends MinionsGui implements MinionListener {
private final GuiContext.Minion context;
private final MinionFakePlayer minion;
private SimpleGui gui;
public InstructionListGui(MinionsGui parent, GuiContext.Minion context) {
super(parent);
this.context = context;
this.minion = context.getMinion();
open();
}
@Override
public void onInstructionsUpdate(MinionFakePlayer minion) {
resetInstructionList();
}
@Override
protected void open() {
minion.addMinionListener(this);
gui = new SimpleGui(MenuType.GENERIC_9x4, viewer, false) {
@Override
public void onPlayerClose(boolean success) {
onBackingClosed();
}
};
gui.setTitle(Component.translatable("minions.gui.instruction.title"));
gui.setSlot(8, backButton());
resetInstructionList();
gui.open();
}
@Override
protected void closeBacking() {
minion.removeMinionListener(this);
gui.close();
}
private void resetInstructionList() {
int i = 9;
for (String instructionName : minion.getInstructionManager().getInstructionNames()) {
ConfiguredInstruction<MinionRuntime> instruction = minion.getInstructionManager().getInstruction(instructionName);
gui.setSlot(i, new GuiElementBuilder(GuiDisplay.getGuiDisplayFor(MinionRegistries.INSTRUCTION_TYPES, instruction.getInstruction(), viewer.registryAccess()).createItemStack())
.setName(Component.literal(instructionName))
.setCallback(() -> new ConfigureInstructionGui(this, GuiContext.Instruction.create(context, instruction, instructionName)))
);
i++;
}
}
}
@@ -0,0 +1,43 @@
package io.github.skippyall.minions.gui.minion;
import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.program.instruction.ConfiguredInstruction;
import io.github.skippyall.minions.program.supplier.Parameter;
import net.minecraft.server.level.ServerPlayer;
public interface GuiContext {
ServerPlayer getViewer();
static GuiContext create(ServerPlayer viewer) {
return new GuiContextImpl(viewer);
}
interface Minion extends GuiContext {
MinionFakePlayer getMinion();
static GuiContext.Minion create(GuiContext context, MinionFakePlayer minion) {
return new GuiContextImpl.MinionImpl(context, minion);
}
}
interface Instruction extends Minion {
ConfiguredInstruction<MinionRuntime> getInstruction();
String getName();
void setName(String name);
static GuiContext.Instruction create(GuiContext.Minion context, ConfiguredInstruction<MinionRuntime> instruction, String name) {
return new GuiContextImpl.InstructionImpl(context, instruction, name);
}
}
interface ValueSupplier extends Instruction {
Parameter<?> getParameter();
static GuiContext.ValueSupplier create(GuiContext.Instruction context, Parameter<?> parameter) {
return new GuiContextImpl.ValueSupplierImpl(context, parameter);
}
}
}
@@ -0,0 +1,120 @@
package io.github.skippyall.minions.gui.minion;
import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.program.instruction.ConfiguredInstruction;
import io.github.skippyall.minions.program.supplier.Parameter;
import net.minecraft.server.level.ServerPlayer;
//If only this mod was kotlin
public class GuiContextImpl implements GuiContext {
private final ServerPlayer viewer;
public GuiContextImpl(ServerPlayer viewer) {
this.viewer = viewer;
}
@Override
public ServerPlayer getViewer() {
return viewer;
}
public static class MinionImpl extends DelegatingGuiContextImpl<GuiContext> implements GuiContext.Minion {
private final MinionFakePlayer minion;
public MinionImpl(GuiContext context, MinionFakePlayer minion) {
super(context instanceof DelegatingGuiContextImpl<?> impl ? impl.context : context);
this.minion = minion;
}
@Override
public MinionFakePlayer getMinion() {
return minion;
}
}
public static class InstructionImpl extends DelegatingMinionImpl<GuiContext.Minion> implements GuiContext.Instruction {
private final ConfiguredInstruction<MinionRuntime> instruction;
private String name;
public InstructionImpl(GuiContext.Minion context, ConfiguredInstruction<MinionRuntime> instruction, String name) {
super(context instanceof DelegatingMinionImpl<?> impl ? impl.context : context);
this.instruction = instruction;
this.name = name;
}
@Override
public ConfiguredInstruction<MinionRuntime> getInstruction() {
return instruction;
}
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
}
public static class ValueSupplierImpl extends DelegatingInstructionImpl<GuiContext.Instruction> implements GuiContext.ValueSupplier {
private final Parameter<?> parameter;
public ValueSupplierImpl(GuiContext.Instruction context, Parameter<?> parameter) {
super(context instanceof DelegatingInstructionImpl<?> impl ? impl.context : context);
this.parameter = parameter;
}
@Override
public Parameter<?> getParameter() {
return parameter;
}
}
public static class DelegatingGuiContextImpl<C extends GuiContext> implements GuiContext {
protected final C context;
public DelegatingGuiContextImpl(C context) {
this.context = context;
}
@Override
public ServerPlayer getViewer() {
return context.getViewer();
}
}
public static class DelegatingMinionImpl<C extends GuiContext.Minion> extends DelegatingGuiContextImpl<C> implements GuiContext.Minion {
public DelegatingMinionImpl(C context) {
super(context);
}
@Override
public MinionFakePlayer getMinion() {
return context.getMinion();
}
}
public static class DelegatingInstructionImpl<C extends GuiContext.Instruction> extends DelegatingMinionImpl<C> implements GuiContext.Instruction {
public DelegatingInstructionImpl(C context) {
super(context);
}
@Override
public ConfiguredInstruction<MinionRuntime> getInstruction() {
return context.getInstruction();
}
@Override
public String getName() {
return context.getName();
}
@Override
public void setName(String name) {
context.setName(name);
}
}
}
@@ -0,0 +1,78 @@
package io.github.skippyall.minions.gui.minion;
import eu.pb4.sgui.api.elements.GuiElementBuilder;
import eu.pb4.sgui.api.gui.SimpleGui;
import io.github.skippyall.minions.gui.MinionsGui;
import io.github.skippyall.minions.gui.instruction.InstructionGui;
import io.github.skippyall.minions.minion.MinionListener;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.module.ModuleInventory;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.Items;
public class MinionGui extends MinionsGui implements MinionListener {
private final MinionFakePlayer minion;
private SimpleGui gui;
public MinionGui(ServerPlayer viewer, MinionFakePlayer minion) {
super(viewer);
this.minion = minion;
minion.addMinionListener(this);
open();
}
public MinionFakePlayer getMinion() {
return minion;
}
@Override
protected void open() {
gui = new SimpleGui(MenuType.GENERIC_3x3, viewer, false) {
@Override
public void onPlayerClose(boolean success) {
onBackingClosed();
}
};
gui.setTitle(minion.getName());
gui.setSlot(1, new GuiElementBuilder()
.setItem(Items.COMMAND_BLOCK)
.setName(Component.translatable("minions.gui.main.instructions"))
.setCallback(() -> {
InstructionGui.openInstructionMainMenu(this, GuiContext.Minion.create(GuiContext.create(viewer), minion));
})
);
gui.setSlot(3, new GuiElementBuilder()
.setItem(Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE)
.setName(Component.translatable("minions.gui.main.modules"))
.setCallback(() -> {
ModuleInventory.openModuleInventory(viewer, minion);
})
);
gui.setSlot(5, new GuiElementBuilder()
.setItem(Items.CHEST)
.setName(Component.translatable("minions.gui.main.inventory"))
.setCallback(() -> new MinionInventoryGui(this))
);
gui.setSlot(7, new GuiElementBuilder()
.setItem(Items.BARRIER)
.setName(Component.translatable("minions.gui.main.pickup"))
.setCallback(() -> minion.kill(minion.level()))
);
gui.open();
}
@Override
protected void closeBacking() {
gui.close();
minion.removeMinionListener(this);
}
@Override
public void onMinionRemove(MinionFakePlayer minion) {
close();
}
}
@@ -0,0 +1,70 @@
package io.github.skippyall.minions.gui.minion;
import eu.pb4.sgui.api.gui.SimpleGui;
import io.github.skippyall.minions.gui.MinionsGui;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.inventory.ArmorSlot;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
public class MinionInventoryGui extends MinionsGui {
protected final MinionGui parent;
private final MinionFakePlayer minion;
private SimpleGui gui;
public MinionInventoryGui(MinionGui parent) {
super(parent);
this.parent = parent;
this.minion = parent.getMinion();
open();
}
@Override
protected void open() {
gui = new SimpleGui(MenuType.GENERIC_9x6, viewer, false) {
@Override
public void onPlayerClose(boolean success) {
onBackingClosed();
}
};
gui.setTitle(Component.translatable("minions.gui.inventory.title"));
for(int i = 0; i < 18; i++) {
gui.setSlot(i, new ItemStack(Items.BARRIER));
}
gui.setSlot(2, new ItemStack(Items.LEATHER_HELMET));
gui.setSlot(3, new ItemStack(Items.LEATHER_CHESTPLATE));
gui.setSlot(4, new ItemStack(Items.LEATHER_LEGGINGS));
gui.setSlot(5, new ItemStack(Items.LEATHER_BOOTS));
gui.setSlot(6, new ItemStack(Items.SHIELD));
gui.setSlot(8, backButton());
gui.setSlot(2 + 9, new ArmorSlot(minion.getInventory(), minion, EquipmentSlot.HEAD, EquipmentSlot.HEAD.getIndex(Inventory.INVENTORY_SIZE), 0, 0, null));
gui.setSlot(3 + 9, new ArmorSlot(minion.getInventory(), minion, EquipmentSlot.CHEST, EquipmentSlot.CHEST.getIndex(Inventory.INVENTORY_SIZE), 0, 0, null));
gui.setSlot(4 + 9, new ArmorSlot(minion.getInventory(), minion, EquipmentSlot.LEGS, EquipmentSlot.LEGS.getIndex(Inventory.INVENTORY_SIZE), 0, 0, null));
gui.setSlot(5 + 9, new ArmorSlot(minion.getInventory(), minion, EquipmentSlot.FEET, EquipmentSlot.FEET.getIndex(Inventory.INVENTORY_SIZE), 0, 0, null));
gui.setSlot(6 + 9, new Slot(minion.getInventory(), Inventory.SLOT_OFFHAND, 0, 0));
for (int i = Inventory.SELECTION_SIZE; i < Inventory.INVENTORY_SIZE; i++) {
gui.setSlot(i + 9, new Slot(minion.getInventory(), i, 0, 0));
}
for (int i = 0; i < Inventory.SELECTION_SIZE; i++) {
gui.setSlot(i + 45, new Slot(minion.getInventory(), i, 0, 0));
}
gui.open();
}
@Override
protected void closeBacking() {
gui.close();
}
}
@@ -0,0 +1,27 @@
package io.github.skippyall.minions.gui.minion;
import eu.pb4.sgui.api.gui.GuiLike;
import io.github.skippyall.minions.gui.MinionsGui;
import java.util.function.BiFunction;
public class SimpleMinionsGui extends MinionsGui {
private GuiLike gui;
private final BiFunction<Runnable, SimpleMinionsGui, GuiLike> guiFactory;
public SimpleMinionsGui(MinionsGui parent, BiFunction<Runnable, SimpleMinionsGui, GuiLike> guiFactory) {
super(parent);
this.guiFactory = guiFactory;
open();
}
@Override
protected void open() {
gui = guiFactory.apply(this::onBackingClosed, this);
}
@Override
protected void closeBacking() {
gui.close();
}
}
@@ -1,36 +0,0 @@
package io.github.skippyall.minions.instruction.move;
import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.program.supplier.Parameter;
import io.github.skippyall.minions.program.supplier.ValueSupplierList;
import io.github.skippyall.minions.registration.ValueTypes;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec2f;
import net.minecraft.util.math.Vec3d;
public class TurnVectorExecution extends AbstractTurnExecution {
public static final Parameter<Double> X = new Parameter<>("x", ValueTypes.DOUBLE);
public static final Parameter<Double> Y = new Parameter<>("y", ValueTypes.DOUBLE);
public static final Parameter<Double> Z = new Parameter<>("z", ValueTypes.DOUBLE);
@Override
public void readArguments(ValueSupplierList<MinionRuntime> arguments, MinionRuntime runtime) {
double x = arguments.getValue(X, runtime);
double y = arguments.getValue(Y, runtime);
double z = arguments.getValue(Z, runtime);
Vec3d vector = new Vec3d(x, y, z);
Vec2f rotation = vectorToRotation(vector);
targetYaw = rotation.x;
targetPitch = rotation.y;
}
//copied from Entity#lookAt (why no helper, Mojang?)
public static Vec2f vectorToRotation(Vec3d vector) {
double g = Math.sqrt(vector.x * vector.x + vector.z * vector.z);
float pitch = MathHelper.wrapDegrees((float)(-(MathHelper.atan2(vector.y, g) * 180.0F / (float)Math.PI)));
float yaw = MathHelper.wrapDegrees((float)(MathHelper.atan2(vector.z, vector.x) * 180.0F / (float)Math.PI) - 90.0F);
return new Vec2f(yaw, pitch);
}
}
@@ -2,18 +2,17 @@ package io.github.skippyall.minions.listener;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.program.instruction.ConfiguredInstructionListener; import io.github.skippyall.minions.program.instruction.ConfiguredInstructionListener;
import net.minecraft.block.entity.BlockEntity; import net.minecraft.core.BlockPos;
import net.minecraft.block.entity.BlockEntityType; import net.minecraft.resources.ResourceKey;
import net.minecraft.registry.RegistryKey;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.util.math.BlockPos; import net.minecraft.world.level.Level;
import net.minecraft.world.World; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
public abstract class BlockEntityMinionInstructionListener<E extends BlockEntity> extends BlockEntityMinionListener<E> { public abstract class BlockEntityMinionInstructionListener<E extends BlockEntity> extends BlockEntityMinionListener<E> {
protected BlockEntityMinionInstructionListener(RegistryKey<World> worldKey, BlockPos pos, UUID minionUuid, BlockEntityType<E> type) { protected BlockEntityMinionInstructionListener(ResourceKey<Level> worldKey, BlockPos pos, UUID minionUuid, BlockEntityType<E> type) {
super(worldKey, pos, minionUuid, type); super(worldKey, pos, minionUuid, type);
} }
@@ -26,7 +25,7 @@ public abstract class BlockEntityMinionInstructionListener<E extends BlockEntity
} }
@Override @Override
protected void add(MinecraftServer server) { public void add(MinecraftServer server) {
super.add(server); super.add(server);
if(minion != null) { if(minion != null) {
registerInstructionListeners(); registerInstructionListeners();
@@ -6,26 +6,26 @@ import com.mojang.serialization.codecs.RecordCodecBuilder;
import io.github.skippyall.minions.minion.MinionListener; import io.github.skippyall.minions.minion.MinionListener;
import io.github.skippyall.minions.minion.MinionPersistentState; import io.github.skippyall.minions.minion.MinionPersistentState;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import net.minecraft.block.entity.BlockEntity; import net.minecraft.core.BlockPos;
import net.minecraft.block.entity.BlockEntityType; import net.minecraft.core.UUIDUtil;
import net.minecraft.registry.RegistryKey; import net.minecraft.resources.ResourceKey;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.util.Uuids; import net.minecraft.world.level.Level;
import net.minecraft.util.math.BlockPos; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.World; import net.minecraft.world.level.block.entity.BlockEntityType;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
public abstract class BlockEntityMinionListener<E extends BlockEntity> implements MinionListener { public abstract class BlockEntityMinionListener<E extends BlockEntity> implements MinionListener {
protected RegistryKey<World> worldKey; protected ResourceKey<Level> worldKey;
protected BlockPos pos; protected BlockPos pos;
protected UUID minionUuid; protected UUID minionUuid;
protected BlockEntityType<E> type; protected BlockEntityType<E> type;
protected @Nullable MinionFakePlayer minion; protected @Nullable MinionFakePlayer minion;
protected BlockEntityMinionListener(RegistryKey<World> worldKey, BlockPos pos, UUID minionUuid, BlockEntityType<E> type) { protected BlockEntityMinionListener(ResourceKey<Level> worldKey, BlockPos pos, UUID minionUuid, BlockEntityType<E> type) {
this.worldKey = worldKey; this.worldKey = worldKey;
this.pos = pos; this.pos = pos;
this.minionUuid = minionUuid; this.minionUuid = minionUuid;
@@ -45,10 +45,10 @@ public abstract class BlockEntityMinionListener<E extends BlockEntity> implement
this.minion = null; this.minion = null;
} }
public static <T extends BlockEntityMinionListener<?>> T getListener(World world, BlockPos pos, UUID minionUuid, Class<T> clazz) { public static <T extends BlockEntityMinionListener<?>> T getListener(Level world, BlockPos pos, UUID minionUuid, Class<T> clazz) {
if(minionUuid != null) { if(minionUuid != null) {
for (MinionListener listener : MinionPersistentState.get(world.getServer()).getMinionData(minionUuid).listeners()) { for (MinionListener listener : MinionPersistentState.get(world.getServer()).getMinionData(minionUuid).listeners()) {
if (listener instanceof BlockEntityMinionListener<?> tl && tl.pos.equals(pos) && tl.worldKey.equals(world.getRegistryKey()) && clazz.isInstance(tl)) { if (listener instanceof BlockEntityMinionListener<?> tl && tl.pos.equals(pos) && tl.worldKey.equals(world.dimension()) && clazz.isInstance(tl)) {
return clazz.cast(tl); return clazz.cast(tl);
} }
} }
@@ -56,18 +56,18 @@ public abstract class BlockEntityMinionListener<E extends BlockEntity> implement
return null; return null;
} }
public static <L extends BlockEntityMinionListener<?>> Codec<L> getCodec(Function3<RegistryKey<World>, BlockPos, UUID, L> constructor) { public static <L extends BlockEntityMinionListener<?>> Codec<L> getCodec(Function3<ResourceKey<Level>, BlockPos, UUID, L> constructor) {
return RecordCodecBuilder.create(instance -> return RecordCodecBuilder.create(instance ->
instance.group( instance.group(
World.CODEC.fieldOf("world").forGetter(listener -> listener.worldKey), Level.RESOURCE_KEY_CODEC.fieldOf("world").forGetter(listener -> listener.worldKey),
BlockPos.CODEC.fieldOf("pos").forGetter(listener -> listener.pos), BlockPos.CODEC.fieldOf("pos").forGetter(listener -> listener.pos),
Uuids.CODEC.fieldOf("minionUuid").forGetter(listener -> listener.minionUuid) UUIDUtil.AUTHLIB_CODEC.fieldOf("minionUuid").forGetter(listener -> listener.minionUuid)
).apply(instance, constructor)); ).apply(instance, constructor));
} }
private BlockEntityState getBlockEntityState(MinecraftServer server) { private BlockEntityState getBlockEntityState(MinecraftServer server) {
World world = server.getWorld(worldKey); Level world = server.getLevel(worldKey);
if(world == null || !world.isPosLoaded(pos)) { if(world == null || !world.isLoaded(pos)) {
return BlockEntityState.UNLOADED; return BlockEntityState.UNLOADED;
} }
@@ -79,8 +79,8 @@ public abstract class BlockEntityMinionListener<E extends BlockEntity> implement
} }
public Optional<E> getBlockEntity(MinecraftServer server) { public Optional<E> getBlockEntity(MinecraftServer server) {
World world = server.getWorld(worldKey); Level world = server.getLevel(worldKey);
if(world != null && world.isPosLoaded(pos)) { if(world != null && world.isLoaded(pos)) {
return world.getBlockEntity(pos, type); return world.getBlockEntity(pos, type);
} }
return Optional.empty(); return Optional.empty();
@@ -94,15 +94,15 @@ public abstract class BlockEntityMinionListener<E extends BlockEntity> implement
return false; return false;
} }
protected void add(MinecraftServer server) { public void add(MinecraftServer server) {
MinionPersistentState.get(server).getMinionData(minionUuid).listeners().addListener(this); MinionPersistentState.get(server).getMinionData(minionUuid).listeners().addListener(this);
MinionPersistentState.get(server).markDirty(); MinionPersistentState.get(server).setDirty();
this.minion = (MinionFakePlayer) server.getPlayerManager().getPlayer(minionUuid); this.minion = (MinionFakePlayer) server.getPlayerList().getPlayer(minionUuid);
} }
public void remove(MinecraftServer server) { public void remove(MinecraftServer server) {
MinionPersistentState.get(server).getMinionData(minionUuid).listeners().removeListener(this); MinionPersistentState.get(server).getMinionData(minionUuid).listeners().removeListener(this);
MinionPersistentState.get(server).markDirty(); MinionPersistentState.get(server).setDirty();
} }
public enum BlockEntityState { public enum BlockEntityState {
@@ -3,17 +3,17 @@ package io.github.skippyall.minions.listener;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet;
public class ListenerManager<T> implements Iterable<T> { public class ListenerManager<T> implements Iterable<T> {
protected final List<T> listeners; protected final Set<T> listeners;
public ListenerManager() { public ListenerManager() {
this(new CopyOnWriteArrayList<>()); this(new CopyOnWriteArraySet<>());
} }
protected ListenerManager(List<T> listeners) { protected ListenerManager(Set<T> listeners) {
this.listeners = listeners; this.listeners = listeners;
} }
@@ -23,34 +23,11 @@ public class ListenerManager<T> implements Iterable<T> {
public void removeListener(T listener) { public void removeListener(T listener) {
listeners.remove(listener); listeners.remove(listener);
onRemove(listener);
} }
protected void onRemove(T listener) {}
@Override @Override
public @NotNull Iterator<T> iterator() { public @NotNull Iterator<T> iterator() {
return new Iterator<>() { return listeners.iterator();
final Iterator<T> backing = listeners.iterator();
T last;
@Override
public boolean hasNext() {
return backing.hasNext();
}
@Override
public T next() {
last = backing.next();
return last;
}
@Override
public void remove() {
backing.remove();
onRemove(last);
}
};
} }
@Override @Override
@@ -1,34 +1,31 @@
package io.github.skippyall.minions.listener; package io.github.skippyall.minions.listener;
import com.mojang.serialization.Codec; import com.mojang.serialization.Codec;
import net.minecraft.registry.Registry; import com.mojang.serialization.MapCodec;
import net.minecraft.storage.ReadView; import net.minecraft.core.Registry;
import net.minecraft.storage.WriteView; import net.minecraft.resources.Identifier;
import net.minecraft.util.Identifier;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
public class SerializableListenerManager<T extends SerializableListenerManager.SerializableListener> extends ListenerManager<T> { public class SerializableListenerManager<T extends SerializableListenerManager.SerializableListener> extends ListenerManager<T> {
private final Registry<Codec<? extends T>> registry; public SerializableListenerManager() {
super();
public SerializableListenerManager(Registry<Codec<? extends T>> registry) {
this.registry = registry;
} }
public SerializableListenerManager(Registry<Codec<? extends T>> registry, List<T> listeners) { protected SerializableListenerManager(Set<T> listeners) {
super(listeners); super(listeners);
this.registry = registry;
} }
public static <T extends SerializableListener> Codec<SerializableListenerManager<T>> getCodec(Registry<Codec<? extends T>> registry) { public static <T extends SerializableListener> Codec<SerializableListenerManager<T>> getCodec(Registry<Codec<? extends T>> registry) {
return registry.getCodec().<T>dispatch( return registry.byNameCodec().<T>dispatch(
listener -> listener.getCodecId().map(registry::get).orElse(Codec.unit(null)), listener -> listener.getCodecId().map(registry::getValue).orElse(MapCodec.unitCodec(null)),
codec -> codec.fieldOf("data") codec -> codec.fieldOf("data")
).listOf().xmap( ).listOf().xmap(
list -> new SerializableListenerManager<>(registry, new CopyOnWriteArrayList<>(list)), list -> new SerializableListenerManager<>(new CopyOnWriteArraySet<>(list)),
manager -> { manager -> {
List<T> serializableListeners = new ArrayList<>(); List<T> serializableListeners = new ArrayList<>();
for(T listener : manager.listeners) { for(T listener : manager.listeners) {
@@ -41,37 +38,6 @@ public class SerializableListenerManager<T extends SerializableListenerManager.S
); );
} }
public void save(WriteView view) {
WriteView.ListView listView = view.getList("listeners");
for (T listener : listeners) {
if(listener.getCodecId().isPresent()) {
WriteView listenerView = listView.add();
Codec<? extends T> codec = registry.get(listener.getCodecId().get());
listenerView.put("id", Identifier.CODEC, listener.getCodecId().get());
//noinspection unchecked
listenerView.put("data", (Codec<T>) codec, listener);
}
}
}
public void load(ReadView view) {
ReadView.ListReadView listView = view.getListReadView("listeners");
for (ReadView listenerView : listView) {
Optional<Identifier> id = listenerView.read("id", Identifier.CODEC);
if(id.isEmpty()) {
continue;
}
Codec<? extends T> codec = registry.get(id.get());
Optional<? extends T> listener = listenerView.read("data", codec);
if(listener.isPresent()) {
listeners.add(listener.get());
}
}
}
public interface SerializableListener { public interface SerializableListener {
default Optional<Identifier> getCodecId() { default Optional<Identifier> getCodecId() {
return Optional.empty(); return Optional.empty();
@@ -2,7 +2,7 @@ package io.github.skippyall.minions.minion;
import com.mojang.serialization.Codec; import com.mojang.serialization.Codec;
import io.github.skippyall.minions.registration.MinionRegistries; import io.github.skippyall.minions.registration.MinionRegistries;
import net.minecraft.util.Identifier; import net.minecraft.resources.Identifier;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -10,7 +10,7 @@ import java.util.Objects;
public class MinionConfig { public class MinionConfig {
public static final Codec<MinionConfig> CODEC = Codec.<Option<?>, Object>dispatchedMap( public static final Codec<MinionConfig> CODEC = Codec.<Option<?>, Object>dispatchedMap(
MinionRegistries.MINION_CONFIG_OPTIONS.getCodec(), MinionRegistries.MINION_CONFIG_OPTIONS.byNameCodec(),
Option::codec Option::codec
).xmap(MinionConfig::new, config -> config.values); ).xmap(MinionConfig::new, config -> config.values);
@@ -3,12 +3,11 @@ package io.github.skippyall.minions.minion;
import com.mojang.authlib.properties.PropertyMap; import com.mojang.authlib.properties.PropertyMap;
import com.mojang.serialization.Codec; import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder; import com.mojang.serialization.codecs.RecordCodecBuilder;
import io.github.skippyall.minions.registration.MinionRegistries;
import io.github.skippyall.minions.listener.SerializableListenerManager; import io.github.skippyall.minions.listener.SerializableListenerManager;
import io.github.skippyall.minions.registration.MinionRegistries;
import net.minecraft.core.UUIDUtil;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.util.Uuids; import net.minecraft.util.ExtraCodecs;
import net.minecraft.util.dynamic.Codecs;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@@ -22,12 +21,12 @@ public record MinionData(
) { ) {
public static final Codec<MinionData> CODEC = RecordCodecBuilder.create(instance -> public static final Codec<MinionData> CODEC = RecordCodecBuilder.create(instance ->
instance.group( instance.group(
Uuids.CODEC.fieldOf("uuid").forGetter(MinionData::uuid), UUIDUtil.AUTHLIB_CODEC.fieldOf("uuid").forGetter(MinionData::uuid),
Codec.STRING.fieldOf("name").forGetter(MinionData::name), Codec.STRING.fieldOf("name").forGetter(MinionData::name),
Codecs.GAME_PROFILE_PROPERTY_MAP.optionalFieldOf("skin").forGetter(MinionData::skin), ExtraCodecs.PROPERTY_MAP.optionalFieldOf("skin").forGetter(MinionData::skin),
Codec.BOOL.optionalFieldOf("isSpawned", false).forGetter(MinionData::isSpawned), Codec.BOOL.optionalFieldOf("isSpawned", false).forGetter(MinionData::isSpawned),
SerializableListenerManager.getCodec(MinionRegistries.MINION_LISTENER_CODECS).optionalFieldOf("listeners").xmap( SerializableListenerManager.getCodec(MinionRegistries.MINION_LISTENER_CODECS).optionalFieldOf("listeners").xmap(
optional -> optional.orElseGet(() -> new SerializableListenerManager<>(MinionRegistries.MINION_LISTENER_CODECS)), optional -> optional.orElseGet(SerializableListenerManager::new),
Optional::of Optional::of
).forGetter(MinionData::listeners), ).forGetter(MinionData::listeners),
MinionConfig.CODEC.optionalFieldOf("config", new MinionConfig()).forGetter(MinionData::config) MinionConfig.CODEC.optionalFieldOf("config", new MinionConfig()).forGetter(MinionData::config)
@@ -40,7 +39,7 @@ public record MinionData(
MinionProfileUtils.newDefaultMinionName(server), MinionProfileUtils.newDefaultMinionName(server),
Optional.empty(), Optional.empty(),
false, false,
new SerializableListenerManager<>(MinionRegistries.MINION_LISTENER_CODECS), new SerializableListenerManager<>(),
new MinionConfig() new MinionConfig()
); );
} }
@@ -1,39 +1,39 @@
package io.github.skippyall.minions.minion; package io.github.skippyall.minions.minion;
import eu.pb4.polymer.core.api.item.PolymerItem; import eu.pb4.polymer.core.api.item.PolymerItem;
import eu.pb4.polymer.core.api.item.PolymerItemUtils;
import io.github.skippyall.minions.gui.MinionLookGui; import io.github.skippyall.minions.gui.MinionLookGui;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.registration.MinionComponentTypes; import io.github.skippyall.minions.registration.MinionComponentTypes;
import net.minecraft.component.DataComponentTypes; import net.fabricmc.fabric.api.networking.v1.context.PacketContext;
import net.minecraft.component.type.TooltipDisplayComponent; import net.minecraft.core.HolderLookup;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.core.component.DataComponents;
import net.minecraft.item.Item; import net.minecraft.network.chat.Component;
import net.minecraft.item.ItemStack; import net.minecraft.resources.Identifier;
import net.minecraft.item.ItemUsageContext;
import net.minecraft.item.Items;
import net.minecraft.item.tooltip.TooltipType;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.text.Text; import net.minecraft.world.InteractionHand;
import net.minecraft.util.ActionResult; import net.minecraft.world.InteractionResult;
import net.minecraft.util.Hand; import net.minecraft.world.entity.player.Player;
import net.minecraft.util.Identifier; import net.minecraft.world.item.Item;
import net.minecraft.util.math.Vec2f; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.World; import net.minecraft.world.item.Items;
import net.minecraft.world.item.TooltipFlag;
import net.minecraft.world.item.component.TooltipDisplay;
import net.minecraft.world.item.context.UseOnContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec2;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import xyz.nucleoid.packettweaker.PacketContext;
import java.util.function.Consumer; import java.util.function.Consumer;
public class MinionItem extends Item implements PolymerItem { public class MinionItem extends Item implements PolymerItem {
public MinionItem(Settings settings) { public MinionItem(Properties settings) {
super(settings); super(settings);
} }
@Override @Override
public @Nullable Identifier getPolymerItemModel(ItemStack stack, PacketContext context) { public @Nullable Identifier getPolymerItemModel(ItemStack stack, PacketContext context, HolderLookup.Provider lookup) {
return null; return null;
} }
@@ -43,39 +43,39 @@ public class MinionItem extends Item implements PolymerItem {
} }
@Override @Override
public ItemStack getPolymerItemStack(ItemStack stack, TooltipType tooltipType, PacketContext player) { public ItemStack getPolymerItemStack(ItemStack stack, TooltipFlag tooltipType, PacketContext player, HolderLookup.Provider lookup) {
ItemStack out = PolymerItemUtils.createItemStack(stack, tooltipType, player); ItemStack out = PolymerItem.super.getPolymerItemStack(stack, tooltipType, player, lookup);
out.set(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, true); out.set(DataComponents.ENCHANTMENT_GLINT_OVERRIDE, true);
return out; return out;
} }
@Override @Override
public void appendTooltip(ItemStack stack, TooltipContext context, TooltipDisplayComponent component, Consumer<Text> tooltip, TooltipType type) { public void appendHoverText(ItemStack stack, TooltipContext context, TooltipDisplay component, Consumer<Component> tooltip, TooltipFlag type) {
MinionData data = null /*getData(stack)*/; //MinionData data = getData(stack);
if(data != null) { //if(data != null) {
tooltip.accept(Text.translatable("minions.minion_item.tooltip", data.name())); // tooltip.accept(Component.translatable("minions.minion_item.tooltip", data.name()));
} //}
} }
@Override @Override
public ActionResult use(World world, PlayerEntity user, Hand hand) { public InteractionResult use(Level world, Player user, InteractionHand hand) {
if(user instanceof ServerPlayerEntity serverPlayer) { if(user instanceof ServerPlayer serverPlayer) {
ItemStack stack = user.getStackInHand(hand); ItemStack stack = user.getItemInHand(hand);
MinionLookGui.open(serverPlayer, stack); new MinionLookGui(serverPlayer, stack);
return ActionResult.SUCCESS; return InteractionResult.SUCCESS;
} }
return ActionResult.SUCCESS_SERVER; return InteractionResult.SUCCESS_SERVER;
} }
@Override @Override
public ActionResult useOnBlock(ItemUsageContext context) { public InteractionResult useOn(UseOnContext context) {
if(!context.getWorld().isClient) { if(!context.getLevel().isClientSide()) {
MinionData data = getDataOrDefault(context.getWorld().getServer(), context.getStack()); MinionData data = getDataOrDefault(context.getLevel().getServer(), context.getItemInHand());
MinionFakePlayer.spawnMinion(data, (ServerWorld) context.getWorld(), context.getBlockPos().toCenterPos().add(0,0.5,0), new Vec2f(0, 0)); MinionFakePlayer.spawnMinion(data, (ServerLevel) context.getLevel(), context.getClickedPos().getCenter().add(0,0.5,0), new Vec2(0, 0));
} }
context.getStack().decrement(1); context.getItemInHand().shrink(1);
return ActionResult.SUCCESS; return InteractionResult.SUCCESS;
} }
public static void setData(MinecraftServer server, MinionData data, ItemStack item) { public static void setData(MinecraftServer server, MinionData data, ItemStack item) {
@@ -85,7 +85,7 @@ public class MinionItem extends Item implements PolymerItem {
@Nullable @Nullable
public static MinionData getData(MinecraftServer server, ItemStack item) { public static MinionData getData(MinecraftServer server, ItemStack item) {
if(item.contains(MinionComponentTypes.MINION_DATA)) { if(item.has(MinionComponentTypes.MINION_DATA)) {
return MinionPersistentState.get(server).getMinionData(item.get(MinionComponentTypes.MINION_DATA)); return MinionPersistentState.get(server).getMinionData(item.get(MinionComponentTypes.MINION_DATA));
} }
return null; return null;
@@ -101,6 +101,6 @@ public class MinionItem extends Item implements PolymerItem {
} }
public static boolean containsData(ItemStack item) { public static boolean containsData(ItemStack item) {
return item.contains(MinionComponentTypes.MINION_DATA); return item.has(MinionComponentTypes.MINION_DATA);
} }
} }
@@ -1,8 +1,8 @@
package io.github.skippyall.minions.minion; package io.github.skippyall.minions.minion;
import io.github.skippyall.minions.listener.SerializableListenerManager;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.program.instruction.ConfiguredInstruction; import io.github.skippyall.minions.program.instruction.ConfiguredInstruction;
import io.github.skippyall.minions.listener.SerializableListenerManager;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
public interface MinionListener extends SerializableListenerManager.SerializableListener { public interface MinionListener extends SerializableListenerManager.SerializableListener {
@@ -1,21 +1,27 @@
package io.github.skippyall.minions.minion; package io.github.skippyall.minions.minion;
import com.mojang.serialization.Codec; import com.mojang.serialization.Codec;
import io.github.skippyall.minions.Minions;
import net.minecraft.resources.Identifier;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.world.PersistentState; import net.minecraft.world.level.Level;
import net.minecraft.world.PersistentStateType; import net.minecraft.world.level.saveddata.SavedData;
import net.minecraft.world.World; import net.minecraft.world.level.saveddata.SavedDataType;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
public class MinionPersistentState extends PersistentState { public class MinionPersistentState extends SavedData {
public static final Codec<MinionPersistentState> CODEC = MinionData.CODEC.listOf().xmap(MinionPersistentState::new, MinionPersistentState::getMinionDataList); public static final Codec<MinionPersistentState> CODEC = MinionData.CODEC.listOf().xmap(MinionPersistentState::new, MinionPersistentState::getMinionDataList);
public static PersistentStateType<MinionPersistentState> TYPE = new PersistentStateType<>("minion", MinionPersistentState::new, MinionPersistentState.CODEC, null); public static SavedDataType<MinionPersistentState> TYPE = new SavedDataType<>(
Identifier.fromNamespaceAndPath(Minions.MOD_ID, "minion"),
MinionPersistentState::new,
MinionPersistentState.CODEC,
null
);
private final Map<UUID, MinionData> minionData = new HashMap<>(); private final Map<UUID, MinionData> minionData = new HashMap<>();
@@ -43,7 +49,7 @@ public class MinionPersistentState extends PersistentState {
public void updateMinionData(MinionData data) { public void updateMinionData(MinionData data) {
minionData.put(data.uuid(), data); minionData.put(data.uuid(), data);
markDirty(); setDirty();
} }
public boolean isMinion(UUID uuid) { public boolean isMinion(UUID uuid) {
@@ -61,6 +67,6 @@ public class MinionPersistentState extends PersistentState {
} }
public static MinionPersistentState get(MinecraftServer server) { public static MinionPersistentState get(MinecraftServer server) {
return server.getWorld(World.OVERWORLD).getPersistentStateManager().getOrCreate(TYPE); return server.getLevel(Level.OVERWORLD).getDataStorage().computeIfAbsent(TYPE);
} }
} }
@@ -5,10 +5,9 @@ import com.mojang.authlib.properties.PropertyMap;
import com.mojang.brigadier.StringReader; import com.mojang.brigadier.StringReader;
import io.github.skippyall.minions.MinionsConfig; import io.github.skippyall.minions.MinionsConfig;
import io.github.skippyall.minions.gui.input.Result; import io.github.skippyall.minions.gui.input.Result;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.text.Text; import net.minecraft.util.StringUtil;
import net.minecraft.util.StringHelper;
import java.util.UUID; import java.util.UUID;
import static io.github.skippyall.minions.Minions.LOGGER; import static io.github.skippyall.minions.Minions.LOGGER;
@@ -23,31 +22,28 @@ public class MinionProfileUtils {
uuidMinion = UUID.randomUUID(); uuidMinion = UUID.randomUUID();
} }
GameProfile newProfile = new GameProfile(uuidMinion, username); GameProfile newProfile = new GameProfile(uuidMinion, username, skin != null ? skin : PropertyMap.EMPTY);
if (skin != null) {
newProfile.getProperties().putAll(skin);
}
LOGGER.info("Minion Profile: {}", newProfile); LOGGER.info("Minion Profile: {}", newProfile);
return newProfile; return newProfile;
} }
public static Result<String, Text> checkMinionNameWithoutPrefix(MinecraftServer server, String name) { public static Result<String, Component> checkMinionNameWithoutPrefix(MinecraftServer server, String name) {
for(char c : name.toCharArray()) { for(char c : name.toCharArray()) {
if(!StringReader.isAllowedInUnquotedString(c)) { if(!StringReader.isAllowedInUnquotedString(c)) {
return new Result.Error<>(Text.translatable("minions.generic.name.invalid_char")); return new Result.Error<>(Component.translatable("minions.generic.name.invalid_char"));
} }
} }
if((getPrefix() + name).length() > 16) { if((getPrefix() + name).length() > 16) {
return new Result.Error<>(Text.translatable("minions.generic.name.too_long")); return new Result.Error<>(Component.translatable("minions.generic.name.too_long"));
} }
if(!StringHelper.isValidPlayerName(getPrefix() + name)) { if(!StringUtil.isValidPlayerName(getPrefix() + name)) {
return new Result.Error<>(Text.translatable("minions.generic.name.invalid")); return new Result.Error<>(Component.translatable("minions.generic.name.invalid"));
} }
if(MinionPersistentState.get(server).isMinionNameTaken(getPrefix() + name)) { if(MinionPersistentState.get(server).isMinionNameTaken(getPrefix() + name)) {
return new Result.Error<>(Text.translatable("minions.generic.name.taken")); return new Result.Error<>(Component.translatable("minions.generic.name.taken"));
} }
return new Result.Success<>(name); return new Result.Success<>(name);
@@ -1,16 +1,16 @@
package io.github.skippyall.minions.minion; package io.github.skippyall.minions.minion;
import io.github.skippyall.minions.registration.MinionRegistries;
import io.github.skippyall.minions.Minions; import io.github.skippyall.minions.Minions;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.program.InstructionRuntime; import io.github.skippyall.minions.program.InstructionRuntime;
import io.github.skippyall.minions.program.supplier.ValueSupplierType; import io.github.skippyall.minions.program.consumer.ValueConsumerType;
import io.github.skippyall.minions.program.instruction.ConfiguredInstruction; import io.github.skippyall.minions.program.instruction.ConfiguredInstruction;
import io.github.skippyall.minions.program.instruction.InstructionType; import io.github.skippyall.minions.program.instruction.InstructionType;
import io.github.skippyall.minions.program.consumer.ValueConsumerType; import io.github.skippyall.minions.program.supplier.ValueSupplierType;
import net.minecraft.registry.Registry; import io.github.skippyall.minions.registration.MinionRegistries;
import net.minecraft.storage.ReadView; import net.minecraft.core.Registry;
import net.minecraft.storage.WriteView; import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.world.level.storage.ValueOutput;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -102,21 +102,21 @@ public class MinionRuntime implements InstructionRuntime<MinionRuntime> {
} }
} }
public void save(WriteView view) { public void save(ValueOutput view) {
WriteView.ListView list = view.getList("configuredInstructions"); ValueOutput.ValueOutputList list = view.childrenList("configuredInstructions");
for (Map.Entry<String, ConfiguredInstruction<MinionRuntime>> instruction : configuredInstructions.entrySet()) { for (Map.Entry<String, ConfiguredInstruction<MinionRuntime>> instruction : configuredInstructions.entrySet()) {
WriteView inner = list.add(); ValueOutput inner = list.addChild();
inner.putString("name", instruction.getKey()); inner.putString("name", instruction.getKey());
instruction.getValue().save(inner, this); instruction.getValue().save(inner, this);
} }
} }
public void load(ReadView view) { public void load(ValueInput view) {
ReadView.ListReadView list = view.getListReadView("configuredInstructions"); ValueInput.ValueInputList list = view.childrenListOrEmpty("configuredInstructions");
for (ReadView inner : list) { for (ValueInput inner : list) {
Optional<String> name = inner.getOptionalString("name"); Optional<String> name = inner.getString("name");
if(name.isEmpty()) { if(name.isEmpty()) {
Minions.LOGGER.error("Tried deserializing configured instruction without a name of minion \"{}\":", minion.getGameProfile().getName()); Minions.LOGGER.error("Tried deserializing configured instruction without a name of minion \"{}\":", minion.getGameProfile().name());
continue; continue;
} }
@@ -124,7 +124,7 @@ public class MinionRuntime implements InstructionRuntime<MinionRuntime> {
ConfiguredInstruction<MinionRuntime> instruction = ConfiguredInstruction.load(inner, this); ConfiguredInstruction<MinionRuntime> instruction = ConfiguredInstruction.load(inner, this);
configuredInstructions.put(name.get(), instruction); configuredInstructions.put(name.get(), instruction);
} catch (Exception e) { } catch (Exception e) {
Minions.LOGGER.error("Could not deserialize configured instruction \"{}\" of minion \"{}\":", name.get(), minion.getGameProfile().getName(), e); Minions.LOGGER.error("Could not deserialize configured instruction \"{}\" of minion \"{}\":", name.get(), minion.getGameProfile().name(), e);
} }
} }
} }
@@ -2,36 +2,36 @@
package io.github.skippyall.minions.minion.fakeplayer; package io.github.skippyall.minions.minion.fakeplayer;
import io.github.skippyall.minions.mixins.EntityAccessor;
import net.minecraft.commands.arguments.EntityAnchorArgument;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.network.protocol.game.ClientboundSetHeldSlotPacket;
import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.util.Mth;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.animal.equine.AbstractHorse;
import net.minecraft.world.entity.decoration.ItemFrame;
import net.minecraft.world.entity.player.Inventory;
import net.minecraft.world.entity.vehicle.boat.Boat;
import net.minecraft.world.entity.vehicle.minecart.Minecart;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec2;
import net.minecraft.world.phys.Vec3;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import io.github.skippyall.minions.mixins.EntityAccessor;
import net.minecraft.block.BlockState;
import net.minecraft.command.argument.EntityAnchorArgumentType;
import net.minecraft.entity.Entity;
import net.minecraft.entity.decoration.ItemFrameEntity;
import net.minecraft.entity.passive.AbstractHorseEntity;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.entity.vehicle.BoatEntity;
import net.minecraft.entity.vehicle.MinecartEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket;
import net.minecraft.network.packet.s2c.play.UpdateSelectedSlotS2CPacket;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.EntityHitResult;
import net.minecraft.util.hit.HitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec2f;
import net.minecraft.util.math.Vec3d;
public class EntityPlayerActionPack public class EntityPlayerActionPack
{ {
private final MinionFakePlayer player; private final MinionFakePlayer player;
@@ -100,7 +100,7 @@ public class EntityPlayerActionPack
public EntityPlayerActionPack setSneaking(boolean doSneak) public EntityPlayerActionPack setSneaking(boolean doSneak)
{ {
sneaking = doSneak; sneaking = doSneak;
player.setSneaking(doSneak); player.setShiftKeyDown(doSneak);
if (sprinting && sneaking) if (sprinting && sneaking)
setSprinting(false); setSprinting(false);
return this; return this;
@@ -132,35 +132,35 @@ public class EntityPlayerActionPack
case SOUTH -> look(0, 0); case SOUTH -> look(0, 0);
case EAST -> look(-90, 0); case EAST -> look(-90, 0);
case WEST -> look(90, 0); case WEST -> look(90, 0);
case UP -> look(player.getYaw(), -90); case UP -> look(player.getYRot(), -90);
case DOWN -> look(player.getYaw(), 90); case DOWN -> look(player.getYRot(), 90);
}; };
} }
public EntityPlayerActionPack look(Vec2f rotation) public EntityPlayerActionPack look(Vec2 rotation)
{ {
return look(rotation.x, rotation.y); return look(rotation.x, rotation.y);
} }
public EntityPlayerActionPack look(float yaw, float pitch) public EntityPlayerActionPack look(float yaw, float pitch)
{ {
player.setYaw(yaw % 360); //setYaw player.setYRot(yaw % 360); //setYaw
player.setPitch(MathHelper.clamp(pitch, -90, 90)); // setPitch player.setXRot(Mth.clamp(pitch, -90, 90)); // setPitch
// maybe player.moveTo(player.getX(), player.getY(), player.getZ(), yaw, Mth.clamp(pitch,-90.0F, 90.0F)); // maybe player.moveTo(player.getX(), player.getY(), player.getZ(), yaw, Mth.clamp(pitch,-90.0F, 90.0F));
return this; return this;
} }
public EntityPlayerActionPack lookAt(Vec3d position) public EntityPlayerActionPack lookAt(Vec3 position)
{ {
player.lookAt(EntityAnchorArgumentType.EntityAnchor.EYES, position); player.lookAt(EntityAnchorArgument.Anchor.EYES, position);
return this; return this;
} }
public EntityPlayerActionPack turn(float yaw, float pitch) public EntityPlayerActionPack turn(float yaw, float pitch)
{ {
return look(player.getYaw() + yaw, player.getPitch() + pitch); return look(player.getYRot() + yaw, player.getXRot() + pitch);
} }
public EntityPlayerActionPack turn(Vec2f rotation) public EntityPlayerActionPack turn(Vec2 rotation)
{ {
return turn(rotation.x, rotation.y); return turn(rotation.x, rotation.y);
} }
@@ -189,12 +189,12 @@ public class EntityPlayerActionPack
List<Entity> entities; List<Entity> entities;
if (onlyRideables) if (onlyRideables)
{ {
entities = player.getWorld().getOtherEntities(player, player.getBoundingBox().expand(3.0D, 1.0D, 3.0D), entities = player.level().getEntities(player, player.getBoundingBox().inflate(3.0D, 1.0D, 3.0D),
e -> (e instanceof MinecartEntity || e instanceof BoatEntity || e instanceof AbstractHorseEntity) && ((EntityAccessor)e).invokeCanAddPassenger(player)); e -> (e instanceof Minecart || e instanceof Boat || e instanceof AbstractHorse) && ((EntityAccessor)e).minions$canAddPassenger(player));
} }
else else
{ {
entities = player.getWorld().getOtherEntities(player, player.getBoundingBox().expand(3.0D, 1.0D, 3.0D)); entities = player.level().getEntities(player, player.getBoundingBox().inflate(3.0D, 1.0D, 3.0D));
} }
if (entities.size()==0) if (entities.size()==0)
return this; return this;
@@ -205,7 +205,7 @@ public class EntityPlayerActionPack
{ {
if (e == player || (currentVehicle == e)) if (e == player || (currentVehicle == e))
continue; continue;
double dd = player.squaredDistanceTo(e); double dd = player.distanceToSqr(e);
if (dd<distance) if (dd<distance)
{ {
distance = dd; distance = dd;
@@ -213,10 +213,10 @@ public class EntityPlayerActionPack
} }
} }
if (closest == null) return this; if (closest == null) return this;
if (closest instanceof AbstractHorseEntity && onlyRideables) if (closest instanceof AbstractHorse && onlyRideables)
((AbstractHorseEntity) closest).interactMob(player, Hand.MAIN_HAND); ((AbstractHorse) closest).mobInteract(player, InteractionHand.MAIN_HAND);
else else
player.startRiding(closest, !onlyRideables); player.startRiding(closest, !onlyRideables, true);
return this; return this;
} }
public EntityPlayerActionPack dismount() public EntityPlayerActionPack dismount()
@@ -256,10 +256,10 @@ public class EntityPlayerActionPack
float vel = sneaking?0.3F:1.0F; float vel = sneaking?0.3F:1.0F;
// The != 0.0F checks are needed given else real players can't control minecarts, however it works with fakes and else they don't stop immediately // The != 0.0F checks are needed given else real players can't control minecarts, however it works with fakes and else they don't stop immediately
if (forward != 0.0F || player instanceof MinionFakePlayer) { if (forward != 0.0F || player instanceof MinionFakePlayer) {
player.forwardSpeed = forward * vel; player.zza = forward * vel;
} }
if (strafing != 0.0F || player instanceof MinionFakePlayer) { if (strafing != 0.0F || player instanceof MinionFakePlayer) {
player.sidewaysSpeed = strafing * vel; player.xxa = strafing * vel;
} }
if(blockHitDelay > 0) { if(blockHitDelay > 0) {
@@ -267,27 +267,27 @@ public class EntityPlayerActionPack
} }
} }
public static HitResult getTarget(ServerPlayerEntity player) public static HitResult getTarget(ServerPlayer player)
{ {
double reach = player.interactionManager.isCreative() ? 5 : 4.5f; double reach = player.gameMode.isCreative() ? 5 : 4.5f;
return Tracer.rayTrace(player, 1, reach, false); return Tracer.rayTrace(player, 1, reach, false);
} }
private void dropItemFromSlot(int slot, boolean dropAll) private void dropItemFromSlot(int slot, boolean dropAll)
{ {
PlayerInventory inv = player.getInventory(); // getInventory; Inventory inv = player.getInventory(); // getInventory;
if (!inv.getStack(slot).isEmpty()) if (!inv.getItem(slot).isEmpty())
player.dropItem(inv.removeStack(slot, player.drop(inv.removeItem(slot,
dropAll ? inv.getStack(slot).getCount() : 1 dropAll ? inv.getItem(slot).getCount() : 1
), false, true); // scatter, keep owner ), false, true); // scatter, keep owner
} }
public void drop(int selectedSlot, boolean dropAll) public void drop(int selectedSlot, boolean dropAll)
{ {
PlayerInventory inv = player.getInventory(); // getInventory; Inventory inv = player.getInventory(); // getInventory;
if (selectedSlot == -2) // all if (selectedSlot == -2) // all
{ {
for (int i = inv.size(); i >= 0; i--) for (int i = inv.getContainerSize(); i >= 0; i--)
dropItemFromSlot(i, dropAll); dropItemFromSlot(i, dropAll);
} }
else // one slot else // one slot
@@ -301,7 +301,7 @@ public class EntityPlayerActionPack
public void setSlot(int slot) public void setSlot(int slot)
{ {
player.getInventory().setSelectedSlot(slot-1); player.getInventory().setSelectedSlot(slot-1);
player.networkHandler.sendPacket(new UpdateSelectedSlotS2CPacket(slot-1)); player.connection.send(new ClientboundSetHeldSlotPacket(slot-1));
} }
public enum ActionType public enum ActionType
@@ -322,23 +322,23 @@ public class EntityPlayerActionPack
return true; return true;
} }
HitResult hit = getTarget(player); HitResult hit = getTarget(player);
for (Hand hand : Hand.values()) for (InteractionHand hand : InteractionHand.values())
{ {
switch (hit.getType()) switch (hit.getType())
{ {
case BLOCK: case BLOCK:
{ {
player.updateLastActionTime(); player.resetLastActionTime();
ServerWorld world = player.getWorld(); ServerLevel world = player.level();
BlockHitResult blockHit = (BlockHitResult) hit; BlockHitResult blockHit = (BlockHitResult) hit;
BlockPos pos = blockHit.getBlockPos(); BlockPos pos = blockHit.getBlockPos();
Direction side = blockHit.getSide(); Direction side = blockHit.getDirection();
if (pos.getY() < player.getWorld().getTopYInclusive() - (side == Direction.UP ? 1 : 0) && world.canEntityModifyAt(player, pos)) if (pos.getY() < player.level().getMaxY() - (side == Direction.UP ? 1 : 0) && world.mayInteract(player, pos))
{ {
ActionResult result = player.interactionManager.interactBlock(player, world, player.getStackInHand(hand), hand, blockHit); InteractionResult result = player.gameMode.useItemOn(player, world, player.getItemInHand(hand), hand, blockHit);
if (result instanceof ActionResult.Success success) if (result instanceof InteractionResult.Success success)
{ {
if (success.swingSource() == ActionResult.SwingSource.SERVER) player.swingHand(hand); if (success.swingSource() == InteractionResult.SwingSource.SERVER) player.swing(hand);
ap.itemUseCooldown = 3; ap.itemUseCooldown = 3;
return true; return true;
} }
@@ -347,19 +347,19 @@ public class EntityPlayerActionPack
} }
case ENTITY: case ENTITY:
{ {
player.updateLastActionTime(); player.resetLastActionTime();
EntityHitResult entityHit = (EntityHitResult) hit; EntityHitResult entityHit = (EntityHitResult) hit;
Entity entity = entityHit.getEntity(); Entity entity = entityHit.getEntity();
boolean handWasEmpty = player.getStackInHand(hand).isEmpty(); boolean handWasEmpty = player.getItemInHand(hand).isEmpty();
boolean itemFrameEmpty = (entity instanceof ItemFrameEntity) && ((ItemFrameEntity) entity).getHeldItemStack().isEmpty(); boolean itemFrameEmpty = (entity instanceof ItemFrame) && ((ItemFrame) entity).getItem().isEmpty();
Vec3d relativeHitPos = entityHit.getPos().subtract(entity.getX(), entity.getY(), entity.getZ()); Vec3 relativeHitPos = entityHit.getLocation().subtract(entity.getX(), entity.getY(), entity.getZ());
if (entity.interactAt(player, relativeHitPos, hand).isAccepted()) if (entity.interact(player, hand, relativeHitPos).consumesAction())
{ {
ap.itemUseCooldown = 3; ap.itemUseCooldown = 3;
return true; return true;
} }
// fix for SS itemframe always returns CONSUME even if no action is performed // fix for SS itemframe always returns CONSUME even if no action is performed
if (player.interact(entity, hand).isAccepted() && !(handWasEmpty && itemFrameEmpty)) if (player.interactOn(entity, hand, relativeHitPos).consumesAction() && !(handWasEmpty && itemFrameEmpty))
{ {
ap.itemUseCooldown = 3; ap.itemUseCooldown = 3;
return true; return true;
@@ -367,8 +367,8 @@ public class EntityPlayerActionPack
break; break;
} }
} }
ItemStack handItem = player.getStackInHand(hand); ItemStack handItem = player.getItemInHand(hand);
if (player.interactionManager.interactItem(player, player.getWorld(), handItem, hand).isAccepted()) if (player.gameMode.useItem(player, player.level(), handItem, hand).consumesAction())
{ {
ap.itemUseCooldown = 3; ap.itemUseCooldown = 3;
return true; return true;
@@ -382,7 +382,7 @@ public class EntityPlayerActionPack
{ {
EntityPlayerActionPack ap = player.getMinionActionPack(); EntityPlayerActionPack ap = player.getMinionActionPack();
ap.itemUseCooldown = 0; ap.itemUseCooldown = 0;
player.stopUsingItem(); player.releaseUsingItem();
} }
}, },
ATTACK(true) { ATTACK(true) {
@@ -395,10 +395,10 @@ public class EntityPlayerActionPack
if (!action.isContinuous || action.first) if (!action.isContinuous || action.first)
{ {
player.attack(entityHit.getEntity()); player.attack(entityHit.getEntity());
player.swingHand(Hand.MAIN_HAND); player.swing(InteractionHand.MAIN_HAND);
} }
player.resetLastAttackedTicks(); player.resetAttackStrengthTicker();
player.updateLastActionTime(); player.resetLastActionTime();
return true; return true;
} }
case BLOCK: { case BLOCK: {
@@ -409,18 +409,18 @@ public class EntityPlayerActionPack
} }
BlockHitResult blockHit = (BlockHitResult) hit; BlockHitResult blockHit = (BlockHitResult) hit;
BlockPos pos = blockHit.getBlockPos(); BlockPos pos = blockHit.getBlockPos();
Direction side = blockHit.getSide(); Direction side = blockHit.getDirection();
if (player.isBlockBreakingRestricted(player.getWorld(), pos, player.interactionManager.getGameMode())) return false; if (player.blockActionRestricted(player.level(), pos, player.gameMode.getGameModeForPlayer())) return false;
if (ap.currentBlock != null && player.getWorld().getBlockState(ap.currentBlock).isAir()) if (ap.currentBlock != null && player.level().getBlockState(ap.currentBlock).isAir())
{ {
ap.currentBlock = null; ap.currentBlock = null;
return false; return false;
} }
BlockState state = player.getWorld().getBlockState(pos); BlockState state = player.level().getBlockState(pos);
boolean blockBroken = false; boolean blockBroken = false;
if (player.interactionManager.getGameMode().isCreative()) if (player.gameMode.getGameModeForPlayer().isCreative())
{ {
player.interactionManager.processBlockBreakingAction(pos, PlayerActionC2SPacket.Action.START_DESTROY_BLOCK, side, player.getWorld().getTopYInclusive(), -1); player.gameMode.handleBlockBreakAction(pos, ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK, side, player.level().getMaxY(), -1);
ap.blockHitDelay = 5; ap.blockHitDelay = 5;
blockBroken = true; blockBroken = true;
} }
@@ -428,15 +428,15 @@ public class EntityPlayerActionPack
{ {
if (ap.currentBlock != null) if (ap.currentBlock != null)
{ {
player.interactionManager.processBlockBreakingAction(ap.currentBlock, PlayerActionC2SPacket.Action.ABORT_DESTROY_BLOCK, side, player.getWorld().getTopYInclusive(), -1); player.gameMode.handleBlockBreakAction(ap.currentBlock, ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK, side, player.level().getMaxY(), -1);
} }
player.interactionManager.processBlockBreakingAction(pos, PlayerActionC2SPacket.Action.START_DESTROY_BLOCK, side, player.getWorld().getTopYInclusive(), -1); player.gameMode.handleBlockBreakAction(pos, ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK, side, player.level().getMaxY(), -1);
boolean notAir = !state.isAir(); boolean notAir = !state.isAir();
if (notAir && ap.curBlockDamageMP == 0) if (notAir && ap.curBlockDamageMP == 0)
{ {
state.onBlockBreakStart(player.getWorld(), pos, player); state.attack(player.level(), pos, player);
} }
if (notAir && state.calcBlockBreakingDelta(player, player.getWorld(), pos) >= 1) if (notAir && state.getDestroyProgress(player, player.level(), pos) >= 1)
{ {
ap.currentBlock = null; ap.currentBlock = null;
//instamine?? //instamine??
@@ -450,19 +450,19 @@ public class EntityPlayerActionPack
} }
else else
{ {
ap.curBlockDamageMP += state.calcBlockBreakingDelta(player, player.getWorld(), pos); ap.curBlockDamageMP += state.getDestroyProgress(player, player.level(), pos);
if (ap.curBlockDamageMP >= 1) if (ap.curBlockDamageMP >= 1)
{ {
player.interactionManager.processBlockBreakingAction(pos, PlayerActionC2SPacket.Action.STOP_DESTROY_BLOCK, side, player.getWorld().getTopYInclusive(), -1); player.gameMode.handleBlockBreakAction(pos, ServerboundPlayerActionPacket.Action.STOP_DESTROY_BLOCK, side, player.level().getMaxY(), -1);
ap.currentBlock = null; ap.currentBlock = null;
ap.blockHitDelay = 5; ap.blockHitDelay = 5;
blockBroken = true; blockBroken = true;
} }
player.getWorld().setBlockBreakingInfo(-1, pos, (int) (ap.curBlockDamageMP * 10)); player.level().destroyBlockProgress(-1, pos, (int) (ap.curBlockDamageMP * 10));
} }
player.updateLastActionTime(); player.resetLastActionTime();
player.swingHand(Hand.MAIN_HAND); player.swing(InteractionHand.MAIN_HAND);
return blockBroken; return blockBroken;
} }
} }
@@ -474,8 +474,8 @@ public class EntityPlayerActionPack
{ {
EntityPlayerActionPack ap = player.getMinionActionPack(); EntityPlayerActionPack ap = player.getMinionActionPack();
if (ap.currentBlock == null) return; if (ap.currentBlock == null) return;
player.getWorld().setBlockBreakingInfo(-1, ap.currentBlock, -1); player.level().destroyBlockProgress(-1, ap.currentBlock, -1);
player.interactionManager.processBlockBreakingAction(ap.currentBlock, PlayerActionC2SPacket.Action.ABORT_DESTROY_BLOCK, Direction.DOWN, player.getWorld().getTopYInclusive(), -1); player.gameMode.handleBlockBreakAction(ap.currentBlock, ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK, Direction.DOWN, player.level().getMaxY(), -1);
ap.currentBlock = null; ap.currentBlock = null;
} }
}, },
@@ -486,7 +486,8 @@ public class EntityPlayerActionPack
{ {
if (action.limit == 1) if (action.limit == 1)
{ {
if (player.isOnGround()) player.jump(); // onGround if (player.onGround()) player.jumpFromGround();
else if (!player.onClimbable()) player.tryToStartFallFlying();
} }
else else
{ {
@@ -506,8 +507,8 @@ public class EntityPlayerActionPack
@Override @Override
boolean execute(MinionFakePlayer player, Action action) boolean execute(MinionFakePlayer player, Action action)
{ {
player.updateLastActionTime(); player.resetLastActionTime();
player.dropSelectedItem(false); // dropSelectedItem player.drop(false); // dropSelectedItem
return false; return false;
} }
}, },
@@ -516,8 +517,8 @@ public class EntityPlayerActionPack
@Override @Override
boolean execute(MinionFakePlayer player, Action action) boolean execute(MinionFakePlayer player, Action action)
{ {
player.updateLastActionTime(); player.resetLastActionTime();
player.dropSelectedItem(true); // dropSelectedItem player.drop(true); // dropSelectedItem
return false; return false;
} }
}, },
@@ -526,10 +527,10 @@ public class EntityPlayerActionPack
@Override @Override
boolean execute(MinionFakePlayer player, Action action) boolean execute(MinionFakePlayer player, Action action)
{ {
player.updateLastActionTime(); player.resetLastActionTime();
ItemStack itemStack_1 = player.getStackInHand(Hand.OFF_HAND); ItemStack itemStack_1 = player.getItemInHand(InteractionHand.OFF_HAND);
player.setStackInHand(Hand.OFF_HAND, player.getStackInHand(Hand.MAIN_HAND)); player.setItemInHand(InteractionHand.OFF_HAND, player.getItemInHand(InteractionHand.MAIN_HAND));
player.setStackInHand(Hand.MAIN_HAND, itemStack_1); player.setItemInHand(InteractionHand.MAIN_HAND, itemStack_1);
return false; return false;
} }
}; };
@@ -1,14 +1,17 @@
//code from https://github.com/gnembon/fabric-carpet //code from https://github.com/gnembon/fabric-carpet
package io.github.skippyall.minions.minion.fakeplayer; package io.github.skippyall.minions.minion.fakeplayer;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.embedded.EmbeddedChannel; import io.netty.channel.embedded.EmbeddedChannel;
import net.minecraft.network.ClientConnection; import net.minecraft.network.Connection;
import net.minecraft.network.NetworkSide; import net.minecraft.network.PacketListener;
import net.minecraft.network.state.NetworkState; import net.minecraft.network.ProtocolInfo;
import net.minecraft.network.listener.PacketListener; import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.PacketFlow;
import org.jspecify.annotations.Nullable;
public class FakeClientConnection extends ClientConnection { public class FakeClientConnection extends Connection {
public FakeClientConnection(NetworkSide p) public FakeClientConnection(PacketFlow p)
{ {
super(p); super(p);
// compat with adventure-platform-fabric. This does NOT trigger other vanilla handlers for establishing a channel // compat with adventure-platform-fabric. This does NOT trigger other vanilla handlers for establishing a channel
@@ -17,7 +20,12 @@ public class FakeClientConnection extends ClientConnection {
} }
@Override @Override
public void tryDisableAutoRead() public void send(Packet<?> packet, @Nullable ChannelFutureListener listener, boolean flush) {
}
@Override
public void setReadOnly()
{ {
} }
@@ -26,11 +34,11 @@ public class FakeClientConnection extends ClientConnection {
} }
@Override @Override
public void setInitialPacketListener(PacketListener packetListener) public void setListenerForServerboundHandshake(PacketListener packetListener)
{ {
} }
@Override @Override
public <T extends PacketListener> void transitionInbound(NetworkState<T> state, T packetListener) { public <T extends PacketListener> void setupInboundProtocol(ProtocolInfo<T> state, T packetListener) {
} }
} }
@@ -3,55 +3,61 @@ package io.github.skippyall.minions.minion.fakeplayer;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.PropertyMap; import com.mojang.authlib.properties.PropertyMap;
import io.github.skippyall.minions.registration.MinionConfigOptions; import io.github.skippyall.minions.Minions;
import io.github.skippyall.minions.registration.MinionItems; import io.github.skippyall.minions.gui.minion.MinionGui;
import io.github.skippyall.minions.minion.MinionListener; import io.github.skippyall.minions.listener.SerializableListenerManager;
import io.github.skippyall.minions.minion.MinionData; import io.github.skippyall.minions.minion.MinionData;
import io.github.skippyall.minions.gui.MinionGui;
import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.minion.MinionItem; import io.github.skippyall.minions.minion.MinionItem;
import io.github.skippyall.minions.minion.MinionListener;
import io.github.skippyall.minions.minion.MinionPersistentState; import io.github.skippyall.minions.minion.MinionPersistentState;
import io.github.skippyall.minions.minion.MinionProfileUtils; import io.github.skippyall.minions.minion.MinionProfileUtils;
import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.module.ModuleInventory; import io.github.skippyall.minions.module.ModuleInventory;
import io.github.skippyall.minions.registration.MinionConfigOptions;
import io.github.skippyall.minions.registration.MinionItems;
import io.github.skippyall.minions.registration.SpecialAbilities; import io.github.skippyall.minions.registration.SpecialAbilities;
import io.github.skippyall.minions.listener.SerializableListenerManager; import net.fabricmc.fabric.impl.networking.context.PacketContextImpl;
import net.minecraft.block.BlockState; import net.minecraft.core.BlockPos;
import net.minecraft.entity.Entity; import net.minecraft.network.DisconnectionDetails;
import net.minecraft.entity.EquipmentSlot; import net.minecraft.network.chat.Component;
import net.minecraft.entity.ItemEntity; import net.minecraft.network.chat.contents.TranslatableContents;
import net.minecraft.entity.attribute.EntityAttributes; import net.minecraft.network.protocol.PacketFlow;
import net.minecraft.entity.damage.DamageSource; import net.minecraft.network.protocol.game.ClientboundEntityPositionSyncPacket;
import net.minecraft.entity.player.HungerManager; import net.minecraft.network.protocol.game.ClientboundRotateHeadPacket;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.network.protocol.game.ServerboundClientCommandPacket;
import net.minecraft.item.ItemStack;
import net.minecraft.network.DisconnectionInfo;
import net.minecraft.network.NetworkSide;
import net.minecraft.network.packet.c2s.common.SyncedClientOptions;
import net.minecraft.network.packet.c2s.play.ClientStatusC2SPacket;
import net.minecraft.network.packet.s2c.play.EntityPositionSyncS2CPacket;
import net.minecraft.network.packet.s2c.play.EntitySetHeadYawS2CPacket;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.ServerTask; import net.minecraft.server.TickTask;
import net.minecraft.server.network.ConnectedClientData; import net.minecraft.server.level.ClientInformation;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.world.ServerWorld; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.storage.ReadView; import net.minecraft.server.network.CommonListenerCookie;
import net.minecraft.storage.WriteView; import net.minecraft.util.ProblemReporter;
import net.minecraft.text.Text; import net.minecraft.world.InteractionHand;
import net.minecraft.text.TranslatableTextContent; import net.minecraft.world.InteractionResult;
import net.minecraft.util.ActionResult; import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.util.Hand; import net.minecraft.world.entity.Entity;
import net.minecraft.util.math.BlockPos; import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.util.math.Vec2f; import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.util.math.Vec3d; import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.GameMode; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.TeleportTarget; import net.minecraft.world.entity.vehicle.boat.AbstractBoat;
import net.minecraft.world.food.FoodData;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.portal.TeleportTransition;
import net.minecraft.world.level.storage.TagValueInput;
import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.world.level.storage.ValueOutput;
import net.minecraft.world.phys.Vec2;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
public class MinionFakePlayer extends ServerPlayerEntity { public class MinionFakePlayer extends ServerPlayer {
public Runnable fixStartingPosition = () -> {}; public Runnable fixStartingPosition = () -> {};
private EntityPlayerActionPack actionPack; private EntityPlayerActionPack actionPack;
@@ -59,53 +65,77 @@ public class MinionFakePlayer extends ServerPlayerEntity {
private final ModuleInventory moduleInventory = new ModuleInventory(this); private final ModuleInventory moduleInventory = new ModuleInventory(this);
private final MinionRuntime instructionManager = new MinionRuntime(this); private final MinionRuntime instructionManager = new MinionRuntime(this);
public static void spawnMinion(MinionData data, ServerWorld level, @Nullable Vec3d pos, @Nullable Vec2f rot) { public static void spawnMinion(MinionData data, ServerLevel level, @Nullable Vec3 pos, @Nullable Vec2 rot) {
spawnMinion(data, level, pos, rot, false); spawnMinion(data, level, pos, rot, false);
} }
public static void spawnMinion(MinionData data, ServerWorld level, @Nullable Vec3d pos, @Nullable Vec2f rot, boolean force) { public static void spawnMinion(MinionData data, ServerLevel level, @Nullable Vec3 pos, @Nullable Vec2 rot, boolean force) {
if(!data.isSpawned() || force) { if(!data.isSpawned() || force) {
MinecraftServer server = level.getServer(); MinecraftServer server = level.getServer();
PropertyMap skin = data.skin().orElse(null); PropertyMap skin = data.skin().orElse(null);
GameProfile profile = MinionProfileUtils.makeNewMinionProfile(data.uuid(), data.name(), skin); GameProfile profile = MinionProfileUtils.makeNewMinionProfile(data.uuid(), data.name(), skin);
server.send(server.createTask(() -> doSpawn(data, profile, server, level, pos, rot))); server.schedule(server.wrapRunnable(() -> doSpawn(data, profile, server, level, pos, rot)));
} }
} }
private static void doSpawn(MinionData data, GameProfile profile, MinecraftServer server, ServerWorld level, @Nullable Vec3d pos, @Nullable Vec2f rot) { private static void doSpawn(MinionData data, GameProfile profile, MinecraftServer server, ServerLevel level, @Nullable Vec3 pos, @Nullable Vec2 rot) {
MinionFakePlayer instance = new MinionFakePlayer(server, level, profile, SyncedClientOptions.createDefault()); MinionFakePlayer instance = new MinionFakePlayer(server, level, profile, ClientInformation.createDefault());
MinionPersistentState.get(server).updateMinionData(data.withSpawned(true)); MinionPersistentState.get(server).updateMinionData(data.withSpawned(true));
if(pos != null && rot != null) { if(pos != null && rot != null) {
instance.fixStartingPosition = () -> instance.refreshPositionAndAngles(pos.x, pos.y, pos.z, rot.x, rot.y); instance.fixStartingPosition = () -> instance.snapTo(pos.x, pos.y, pos.z, rot.x, rot.y);
} }
server.getPlayerManager().onPlayerConnect(new FakeClientConnection(NetworkSide.SERVERBOUND), instance, new ConnectedClientData(profile, 0, instance.getClientOptions(), false)); FakeClientConnection connection = new FakeClientConnection(PacketFlow.SERVERBOUND);
System.out.println(instance.getPos()); //noinspection UnstableApiUsage
connection.getPacketContext().set(PacketContextImpl.REGISTRY_ACCESS, server.registryAccess());
//noinspection UnstableApiUsage
connection.getPacketContext().set(PacketContextImpl.SERVER_INSTANCE, server);
//noinspection UnstableApiUsage
connection.getPacketContext().set(PacketContextImpl.GAME_PROFILE, profile);
server.getPlayerList().placeNewPlayer(connection, instance, new CommonListenerCookie(profile, 0, instance.clientInformation(), false));
loadPlayerData(instance);
instance.stopRiding(); // otherwise the created fake player will be on the vehicle
System.out.println(instance.position());
if(pos != null && rot != null) { if(pos != null && rot != null) {
instance.teleport(level, pos.x, pos.y, pos.z, Set.of(), rot.x, rot.y, true); instance.teleportTo(level, pos.x, pos.y, pos.z, Set.of(), rot.x, rot.y, true);
} }
instance.setVelocity(0,0,0); instance.setDeltaMovement(0,0,0);
instance.setHealth(20.0F); instance.setHealth(20.0F);
instance.unsetRemoved(); instance.unsetRemoved();
instance.getAttributeInstance(EntityAttributes.STEP_HEIGHT).setBaseValue(0.6F); instance.getAttribute(Attributes.STEP_HEIGHT).setBaseValue(0.6F);
instance.interactionManager.changeGameMode(GameMode.SURVIVAL); instance.getAttribute(Attributes.WAYPOINT_TRANSMIT_RANGE).setBaseValue(0);
server.getPlayerManager().sendToDimension(new EntitySetHeadYawS2CPacket(instance, (byte) (instance.headYaw * 256 / 360)), level.getRegistryKey()); instance.gameMode.changeGameModeForPlayer(GameType.SURVIVAL);
server.getPlayerManager().sendToDimension(EntityPositionSyncS2CPacket.create(instance), level.getRegistryKey()); server.getPlayerList().broadcastAll(new ClientboundRotateHeadPacket(instance, (byte) (instance.yHeadRot * 256 / 360)), level.dimension());
instance.getWorld().getChunkManager().updatePosition(instance); server.getPlayerList().broadcastAll(ClientboundEntityPositionSyncPacket.of(instance), level.dimension());
instance.dataTracker.set(PLAYER_MODEL_PARTS, (byte) 0x7f); // show all model layers (incl. capes) instance.level().getChunkSource().move(instance);
instance.entityData.set(DATA_PLAYER_MODE_CUSTOMISATION, (byte) 0x7f); // show all model layers (incl. capes)
instance.getAbilities().flying = false; instance.getAbilities().flying = false;
instance.listeners().forEach(listener -> listener.onMinionSpawn(instance)); instance.listeners().forEach(listener -> listener.onMinionSpawn(instance));
} }
public static MinionFakePlayer respawnFake(MinecraftServer server, ServerWorld level, GameProfile profile, SyncedClientOptions cli) private static void loadPlayerData(MinionFakePlayer player)
{
try (ProblemReporter.ScopedCollector scopedCollector = new ProblemReporter.ScopedCollector(player.problemPath(), Minions.LOGGER))
{
Optional<ValueInput> optional = player.level().getServer().getPlayerList().loadPlayerData(player.nameAndId()).map((compoundTag) -> TagValueInput.create(scopedCollector, player.registryAccess(), compoundTag));
optional.ifPresent( valueInput -> {
player.load(valueInput);
player.loadAndSpawnEnderPearls(valueInput);
player.loadAndSpawnParentVehicle(valueInput);
});
}
}
public static MinionFakePlayer respawnFake(MinecraftServer server, ServerLevel level, GameProfile profile, ClientInformation cli)
{ {
return new MinionFakePlayer(server, level, profile, cli); return new MinionFakePlayer(server, level, profile, cli);
} }
private MinionFakePlayer(MinecraftServer server, ServerWorld worldIn, GameProfile profile, SyncedClientOptions cli) private MinionFakePlayer(MinecraftServer server, ServerLevel worldIn, GameProfile profile, ClientInformation cli)
{ {
super(server, worldIn, profile, cli); super(server, worldIn, profile, cli);
actionPack = new EntityPlayerActionPack(this); actionPack = new EntityPlayerActionPack(this);
@@ -124,7 +154,7 @@ public class MinionFakePlayer extends ServerPlayerEntity {
} }
public MinionData getData() { public MinionData getData() {
return MinionPersistentState.get(getServer()).getMinionData(getUuid()); return MinionPersistentState.get(getServer()).getMinionData(getUUID());
} }
public SerializableListenerManager<MinionListener> listeners() { public SerializableListenerManager<MinionListener> listeners() {
@@ -151,36 +181,35 @@ public class MinionFakePlayer extends ServerPlayerEntity {
return canSpawnMobs(); return canSpawnMobs();
} }
public MinecraftServer getServer() {
return level().getServer();
}
@Override @Override
public ActionResult interact(PlayerEntity player, Hand hand) { public InteractionResult interact(Player player, InteractionHand hand, Vec3 location) {
if(player instanceof ServerPlayerEntity spe) { if(player instanceof ServerPlayer spe) {
MinionGui.openInventory(spe, this); new MinionGui(spe, this);
} }
return ActionResult.CONSUME; return InteractionResult.CONSUME;
} }
@Override @Override
public ActionResult interactAt(PlayerEntity player, Vec3d hitPos, Hand hand) { public void onEquipItem(final EquipmentSlot slot, final ItemStack previous, final ItemStack stack)
return interact(player, hand);
}
@Override
public void onEquipStack(final EquipmentSlot slot, final ItemStack previous, final ItemStack stack)
{ {
if (!isUsingItem()) super.onEquipStack(slot, previous, stack); if (!isUsingItem()) super.onEquipItem(slot, previous, stack);
} }
public void kill(Text reason) public void kill(Component reason)
{ {
listeners().forEach(listener -> listener.onMinionRemove(this)); listeners().forEach(listener -> listener.onMinionRemove(this));
shakeOff(); shakeOff();
if (reason.getContent() instanceof TranslatableTextContent text && text.getKey().equals("multiplayer.disconnect.duplicate_login")) { if (reason.getContents() instanceof TranslatableContents text && text.getKey().equals("multiplayer.disconnect.duplicate_login")) {
this.networkHandler.onDisconnected(new DisconnectionInfo(reason)); this.connection.onDisconnect(new DisconnectionDetails(reason));
} else { } else {
this.getServer().send(new ServerTask(this.getServer().getTicks(), () -> { this.getServer().schedule(new TickTask(this.getServer().getTickCount(), () -> {
this.networkHandler.onDisconnected(new DisconnectionInfo(reason)); this.connection.onDisconnect(new DisconnectionDetails(reason));
})); }));
} }
@@ -191,15 +220,15 @@ public class MinionFakePlayer extends ServerPlayerEntity {
public void tick() public void tick()
{ {
actionPack.onUpdate(); actionPack.onUpdate();
if (this.getServer().getTicks() % 10 == 0) if (this.getServer().getTickCount() % 10 == 0)
{ {
this.networkHandler.syncWithPlayerPosition(); this.connection.resetPosition();
this.getWorld().getChunkManager().updatePosition(this); this.level().getChunkSource().move(this);
} }
try try
{ {
super.tick(); super.tick();
this.playerTick(); this.doTick();
instructionManager.tick(); instructionManager.tick();
} }
catch (NullPointerException ignored) catch (NullPointerException ignored)
@@ -210,64 +239,79 @@ public class MinionFakePlayer extends ServerPlayerEntity {
} }
@Override
public boolean startRiding(Entity entityToRide, boolean force, boolean sendEventAndTriggers) {
if (super.startRiding(entityToRide, force, sendEventAndTriggers)) {
// from ClientPacketListener.handleSetEntityPassengersPacket
if (entityToRide instanceof AbstractBoat) {
this.yRotO = entityToRide.getYRot();
this.setYRot(entityToRide.getYRot());
this.setYHeadRot(entityToRide.getYHeadRot());
}
return true;
} else {
return false;
}
}
private void shakeOff() private void shakeOff()
{ {
if (getVehicle() instanceof PlayerEntity) stopRiding(); if (getVehicle() instanceof Player) stopRiding();
for (Entity passenger : getPassengersDeep()) for (Entity passenger : getIndirectPassengers())
{ {
if (passenger instanceof PlayerEntity) passenger.stopRiding(); if (passenger instanceof Player) passenger.stopRiding();
} }
} }
@Override @Override
public void onDeath(DamageSource cause) public void die(DamageSource cause)
{ {
shakeOff(); shakeOff();
super.onDeath(cause); super.die(cause);
setHealth(20); setHealth(20);
this.hungerManager = new HungerManager(); this.foodData = new FoodData();
kill(this.getDamageTracker().getDeathMessage()); kill(this.getCombatTracker().getDeathMessage());
} }
@Override @Override
public String getIp() public String getIpAddress()
{ {
return "127.0.0.1"; return "127.0.0.1";
} }
@Override @Override
public boolean allowsServerListing() { public boolean allowsListing() {
return false; return false;
} }
@Override @Override
protected void fall(double y, boolean onGround, BlockState state, BlockPos pos) { protected void checkFallDamage(double y, boolean onGround, BlockState state, BlockPos pos) {
handleFall(0.0, y, 0.0, onGround); doCheckFallDamage(0.0, y, 0.0, onGround);
} }
@Override @Override
public ServerPlayerEntity teleportTo(TeleportTarget target) public ServerPlayer teleport(TeleportTransition target)
{ {
super.teleportTo(target); super.teleport(target);
if (notInAnyWorld) { if (wonGame) {
ClientStatusC2SPacket p = new ClientStatusC2SPacket(ClientStatusC2SPacket.Mode.PERFORM_RESPAWN); ServerboundClientCommandPacket p = new ServerboundClientCommandPacket(ServerboundClientCommandPacket.Action.PERFORM_RESPAWN);
networkHandler.onClientStatus(p); connection.handleClientCommand(p);
} }
// If above branch was taken, *this* has been removed and replaced, the new instance has been set // If above branch was taken, *this* has been removed and replaced, the new instance has been set
// on 'our' connection (which is now theirs, but we still have a ref). // on 'our' connection (which is now theirs, but we still have a ref).
if (networkHandler.player.isInTeleportationState()) { if (connection.player.isChangingDimension()) {
networkHandler.player.onTeleportationDone(); connection.player.hasChangedDimension();
} }
return networkHandler.player; return connection.player;
} }
@Override @Override
public void drop(ServerWorld world, DamageSource damageSource) { public void dropAllDeathLoot(ServerLevel world, DamageSource damageSource) {
super.drop(world, damageSource); super.dropAllDeathLoot(world, damageSource);
ItemEntity entity = dropStack(world, toItemStack(getServer())); ItemEntity entity = drop(toItemStack(world.getServer()), true, false);
if (entity != null) { if (entity != null) {
entity.setNeverDespawn(); entity.setUnlimitedLifetime();
} }
} }
@@ -278,16 +322,16 @@ public class MinionFakePlayer extends ServerPlayerEntity {
} }
@Override @Override
public void writeCustomData(WriteView view) { public void addAdditionalSaveData(ValueOutput view) {
super.writeCustomData(view); super.addAdditionalSaveData(view);
moduleInventory.writeData(view.get("modules")); moduleInventory.writeData(view.child("modules"));
instructionManager.save(view.get("instructionManager")); instructionManager.save(view.child("instructionManager"));
} }
@Override @Override
public void readCustomData(ReadView view) { public void readAdditionalSaveData(ValueInput view) {
super.readCustomData(view); super.readAdditionalSaveData(view);
moduleInventory.readData(view.getReadView("modules")); moduleInventory.readData(view.childOrEmpty("modules"));
instructionManager.load(view.getReadView("instructionManager")); instructionManager.load(view.childOrEmpty("instructionManager"));
} }
} }
@@ -1,46 +1,41 @@
//code from https://github.com/gnembon/fabric-carpet //code from https://github.com/gnembon/fabric-carpet
package io.github.skippyall.minions.minion.fakeplayer; package io.github.skippyall.minions.minion.fakeplayer;
import net.minecraft.entity.player.PlayerPosition; import net.minecraft.network.Connection;
import net.minecraft.network.ClientConnection; import net.minecraft.network.chat.Component;
import net.minecraft.network.packet.Packet; import net.minecraft.network.chat.contents.TranslatableContents;
import net.minecraft.network.packet.s2c.play.PositionFlag;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ConnectedClientData; import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.CommonListenerCookie;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.network.ServerGamePacketListenerImpl;
import net.minecraft.text.Text; import net.minecraft.world.entity.PositionMoveRotation;
import net.minecraft.text.TranslatableTextContent; import net.minecraft.world.entity.Relative;
import java.util.Set; import java.util.Set;
public class NetHandlerPlayServerFake extends ServerPlayNetworkHandler public class NetHandlerPlayServerFake extends ServerGamePacketListenerImpl
{ {
public NetHandlerPlayServerFake(final MinecraftServer minecraftServer, final ClientConnection connection, final ServerPlayerEntity serverPlayer, final ConnectedClientData i) public NetHandlerPlayServerFake(final MinecraftServer minecraftServer, final Connection connection, final ServerPlayer serverPlayer, final CommonListenerCookie i)
{ {
super(minecraftServer, connection, serverPlayer, i); super(minecraftServer, connection, serverPlayer, i);
} }
@Override @Override
public void sendPacket(final Packet<?> packetIn) public void disconnect(Component message)
{ {
} if (message.getContents() instanceof TranslatableContents text && (text.getKey().equals("multiplayer.disconnect.idling") || text.getKey().equals("multiplayer.disconnect.duplicate_login")))
@Override
public void disconnect(Text message)
{
if (message.getContent() instanceof TranslatableTextContent text && (text.getKey().equals("multiplayer.disconnect.idling") || text.getKey().equals("multiplayer.disconnect.duplicate_login")))
{ {
((MinionFakePlayer) player).kill(message); ((MinionFakePlayer) player).kill(message);
} }
} }
@Override @Override
public void requestTeleport(PlayerPosition pos, Set<PositionFlag> set) public void teleport(PositionMoveRotation pos, Set<Relative> set)
{ {
super.requestTeleport(pos, set); super.teleport(pos, set);
if (player.getWorld().getPlayerByUuid(player.getUuid()) != null) { if (player.level().getPlayerByUUID(player.getUUID()) != null) {
syncWithPlayerPosition(); resetPosition();
player.getWorld().getChunkManager().updatePosition(player); player.level().getChunkSource().move(player);
} }
} }
@@ -1,16 +1,17 @@
//code from https://github.com/gnembon/fabric-carpet //code from https://github.com/gnembon/fabric-carpet
package io.github.skippyall.minions.minion.fakeplayer; package io.github.skippyall.minions.minion.fakeplayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import java.util.Optional; import java.util.Optional;
import java.util.function.Predicate; import java.util.function.Predicate;
import net.minecraft.entity.Entity;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.hit.EntityHitResult;
import net.minecraft.util.hit.HitResult;
import net.minecraft.util.math.Box;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.RaycastContext;
import net.minecraft.world.World;
public class Tracer public class Tracer
{ {
@@ -20,7 +21,7 @@ public class Tracer
double maxSqDist = reach * reach; double maxSqDist = reach * reach;
if (blockHit != null) if (blockHit != null)
{ {
maxSqDist = blockHit.getPos().squaredDistanceTo(source.getCameraPosVec(partialTicks)); maxSqDist = blockHit.getLocation().distanceToSqr(source.getEyePosition(partialTicks));
} }
EntityHitResult entityHit = rayTraceEntities(source, partialTicks, reach, maxSqDist); EntityHitResult entityHit = rayTraceEntities(source, partialTicks, reach, maxSqDist);
return entityHit == null ? blockHit : entityHit; return entityHit == null ? blockHit : entityHit;
@@ -28,31 +29,31 @@ public class Tracer
public static BlockHitResult rayTraceBlocks(Entity source, float partialTicks, double reach, boolean fluids) public static BlockHitResult rayTraceBlocks(Entity source, float partialTicks, double reach, boolean fluids)
{ {
Vec3d pos = source.getCameraPosVec(partialTicks); Vec3 pos = source.getEyePosition(partialTicks);
Vec3d rotation = source.getRotationVec(partialTicks); Vec3 rotation = source.getViewVector(partialTicks);
Vec3d reachEnd = pos.add(rotation.x * reach, rotation.y * reach, rotation.z * reach); Vec3 reachEnd = pos.add(rotation.x * reach, rotation.y * reach, rotation.z * reach);
return source.getWorld().raycast(new RaycastContext(pos, reachEnd, RaycastContext.ShapeType.OUTLINE, fluids ? return source.level().clip(new ClipContext(pos, reachEnd, ClipContext.Block.OUTLINE, fluids ?
RaycastContext.FluidHandling.ANY : RaycastContext.FluidHandling.NONE, source)); ClipContext.Fluid.ANY : ClipContext.Fluid.NONE, source));
} }
public static EntityHitResult rayTraceEntities(Entity source, float partialTicks, double reach, double maxSqDist) public static EntityHitResult rayTraceEntities(Entity source, float partialTicks, double reach, double maxSqDist)
{ {
Vec3d pos = source.getCameraPosVec(partialTicks); Vec3 pos = source.getEyePosition(partialTicks);
Vec3d reachVec = source.getRotationVec(partialTicks).multiply(reach); Vec3 reachVec = source.getViewVector(partialTicks).scale(reach);
Box box = source.getBoundingBox().stretch(reachVec).expand(1); AABB box = source.getBoundingBox().expandTowards(reachVec).inflate(1);
return rayTraceEntities(source, pos, pos.add(reachVec), box, e -> !e.isSpectator() && e.canHit(), maxSqDist); return rayTraceEntities(source, pos, pos.add(reachVec), box, e -> !e.isSpectator() && e.isPickable(), maxSqDist);
} }
public static EntityHitResult rayTraceEntities(Entity source, Vec3d start, Vec3d end, Box box, Predicate<Entity> predicate, double maxSqDistance) public static EntityHitResult rayTraceEntities(Entity source, Vec3 start, Vec3 end, AABB box, Predicate<Entity> predicate, double maxSqDistance)
{ {
World world = source.getWorld(); Level world = source.level();
double targetDistance = maxSqDistance; double targetDistance = maxSqDistance;
Entity target = null; Entity target = null;
Vec3d targetHitPos = null; Vec3 targetHitPos = null;
for (Entity current : world.getOtherEntities(source, box, predicate)) for (Entity current : world.getEntities(source, box, predicate))
{ {
Box currentBox = current.getBoundingBox().expand(current.getTargetingMargin()); AABB currentBox = current.getBoundingBox().inflate(current.getPickRadius());
Optional<Vec3d> currentHit = currentBox.raycast(start, end); Optional<Vec3> currentHit = currentBox.clip(start, end);
if (currentBox.contains(start)) if (currentBox.contains(start))
{ {
if (targetDistance >= 0) if (targetDistance >= 0)
@@ -64,8 +65,8 @@ public class Tracer
} }
else if (currentHit.isPresent()) else if (currentHit.isPresent())
{ {
Vec3d currentHitPos = currentHit.get(); Vec3 currentHitPos = currentHit.get();
double currentDistance = start.squaredDistanceTo(currentHitPos); double currentDistance = start.distanceToSqr(currentHitPos);
if (currentDistance < targetDistance || targetDistance == 0) if (currentDistance < targetDistance || targetDistance == 0)
{ {
if (current.getRootVehicle() == source.getRootVehicle()) if (current.getRootVehicle() == source.getRootVehicle())
@@ -1,12 +1,12 @@
package io.github.skippyall.minions.instruction; package io.github.skippyall.minions.minion.program.instruction;
import io.github.skippyall.minions.minion.MinionRuntime; import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.minion.fakeplayer.EntityPlayerActionPack; import io.github.skippyall.minions.minion.fakeplayer.EntityPlayerActionPack;
import io.github.skippyall.minions.program.consumer.ValueConsumerList; import io.github.skippyall.minions.program.consumer.ValueConsumerList;
import io.github.skippyall.minions.program.instruction.execution.ContinuousInstructionExecution; import io.github.skippyall.minions.program.instruction.execution.ContinuousInstructionExecution;
import io.github.skippyall.minions.program.supplier.ValueSupplierList; import io.github.skippyall.minions.program.supplier.ParameterValueList;
import net.minecraft.storage.ReadView; import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.storage.WriteView; import net.minecraft.world.level.storage.ValueOutput;
public class ActionExecution implements ContinuousInstructionExecution<MinionRuntime> { public class ActionExecution implements ContinuousInstructionExecution<MinionRuntime> {
private final EntityPlayerActionPack.ActionType action; private final EntityPlayerActionPack.ActionType action;
@@ -39,13 +39,13 @@ public class ActionExecution implements ContinuousInstructionExecution<MinionRun
} }
@Override @Override
public void readArguments(ValueSupplierList<MinionRuntime> parameters, MinionRuntime minion) {} public void readArguments(ParameterValueList parameters, MinionRuntime minion) {}
@Override @Override
public void save(WriteView view, MinionRuntime minion) {} public void save(ValueOutput view, MinionRuntime minion) {}
@Override @Override
public void load(ReadView view, MinionRuntime runtime) { public void load(ValueInput view, MinionRuntime runtime) {
runtime.getMinion().getMinionActionPack().start(action, EntityPlayerActionPack.Action.continuous()); runtime.getMinion().getMinionActionPack().start(action, EntityPlayerActionPack.Action.continuous());
} }
} }
@@ -1,21 +1,21 @@
//partially code from https://github.com/gnembon/fabric-carpet (EntityPlayerActionPack) //partially code from https://github.com/gnembon/fabric-carpet (EntityPlayerActionPack)
package io.github.skippyall.minions.instruction; package io.github.skippyall.minions.minion.program.instruction;
import io.github.skippyall.minions.minion.MinionRuntime; import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.minion.fakeplayer.EntityPlayerActionPack; import io.github.skippyall.minions.minion.fakeplayer.EntityPlayerActionPack;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.program.consumer.ValueConsumerList; import io.github.skippyall.minions.program.consumer.ValueConsumerList;
import io.github.skippyall.minions.program.instruction.InstructionExecution; import io.github.skippyall.minions.program.instruction.InstructionExecution;
import io.github.skippyall.minions.program.supplier.ValueSupplierList; import io.github.skippyall.minions.program.supplier.ParameterValueList;
import net.minecraft.block.BlockState; import net.minecraft.core.BlockPos;
import net.minecraft.network.packet.c2s.play.PlayerActionC2SPacket; import net.minecraft.core.Direction;
import net.minecraft.storage.ReadView; import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
import net.minecraft.storage.WriteView; import net.minecraft.world.InteractionHand;
import net.minecraft.util.Hand; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.util.hit.BlockHitResult; import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.util.hit.HitResult; import net.minecraft.world.level.storage.ValueOutput;
import net.minecraft.util.math.BlockPos; import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.util.math.Direction; import net.minecraft.world.phys.HitResult;
public class MineBlockExecution implements InstructionExecution<MinionRuntime> { public class MineBlockExecution implements InstructionExecution<MinionRuntime> {
private BlockPos currentBlock; private BlockPos currentBlock;
@@ -36,7 +36,7 @@ public class MineBlockExecution implements InstructionExecution<MinionRuntime> {
done = true; done = true;
return; return;
} }
if (player.isBlockBreakingRestricted(player.getWorld(), hit.getBlockPos(), player.interactionManager.getGameMode())) { if (player.blockActionRestricted(player.level(), hit.getBlockPos(), player.gameMode.getGameModeForPlayer())) {
done = true; done = true;
return; return;
} }
@@ -66,37 +66,37 @@ public class MineBlockExecution implements InstructionExecution<MinionRuntime> {
return; return;
} }
if (player.getWorld().getBlockState(currentBlock).isAir()) { if (player.level().getBlockState(currentBlock).isAir()) {
done = true; done = true;
return; return;
} }
BlockState state = player.getWorld().getBlockState(currentBlock); BlockState state = player.level().getBlockState(currentBlock);
boolean blockBroken = false; boolean blockBroken = false;
if (first) { if (first) {
first = false; first = false;
player.interactionManager.processBlockBreakingAction(currentBlock, PlayerActionC2SPacket.Action.START_DESTROY_BLOCK, newBlockHit.getSide(), player.getWorld().getTopYInclusive(), -1); player.gameMode.handleBlockBreakAction(currentBlock, ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK, newBlockHit.getDirection(), player.level().getMaxY(), -1);
boolean notAir = !state.isAir(); boolean notAir = !state.isAir();
if (notAir) if (notAir)
{ {
state.onBlockBreakStart(player.getWorld(), currentBlock, player); state.attack(player.level(), currentBlock, player);
} }
if (notAir && state.calcBlockBreakingDelta(player, player.getWorld(), currentBlock) >= 1) if (notAir && state.getDestroyProgress(player, player.level(), currentBlock) >= 1)
{ {
//instamine?? //instamine??
blockBroken = true; blockBroken = true;
} }
} else { } else {
currentBlockDamage += state.calcBlockBreakingDelta(player, player.getWorld(), currentBlock); currentBlockDamage += state.getDestroyProgress(player, player.level(), currentBlock);
if (currentBlockDamage >= 1) { if (currentBlockDamage >= 1) {
player.interactionManager.processBlockBreakingAction(currentBlock, PlayerActionC2SPacket.Action.STOP_DESTROY_BLOCK, newBlockHit.getSide(), player.getWorld().getTopYInclusive(), -1); player.gameMode.handleBlockBreakAction(currentBlock, ServerboundPlayerActionPacket.Action.STOP_DESTROY_BLOCK, newBlockHit.getDirection(), player.level().getMaxY(), -1);
ap.blockHitDelay = 5; ap.blockHitDelay = 5;
blockBroken = true; blockBroken = true;
} }
player.getWorld().setBlockBreakingInfo(-1, currentBlock, (int) (currentBlockDamage * 10)); player.level().destroyBlockProgress(-1, currentBlock, (int) (currentBlockDamage * 10));
} }
player.updateLastActionTime(); player.resetLastActionTime();
player.swingHand(Hand.MAIN_HAND); player.swing(InteractionHand.MAIN_HAND);
if(blockBroken) { if(blockBroken) {
done = true; done = true;
@@ -115,26 +115,26 @@ public class MineBlockExecution implements InstructionExecution<MinionRuntime> {
EntityPlayerActionPack ap = player.getMinionActionPack(); EntityPlayerActionPack ap = player.getMinionActionPack();
if(currentBlock != null) { if(currentBlock != null) {
player.getWorld().setBlockBreakingInfo(-1, currentBlock, -1); player.level().destroyBlockProgress(-1, currentBlock, -1);
player.interactionManager.processBlockBreakingAction(currentBlock, PlayerActionC2SPacket.Action.ABORT_DESTROY_BLOCK, Direction.DOWN, player.getWorld().getTopYInclusive(), -1); player.gameMode.handleBlockBreakAction(currentBlock, ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK, Direction.DOWN, player.level().getMaxY(), -1);
} }
} }
@Override @Override
public void readArguments(ValueSupplierList<MinionRuntime> arguments, MinionRuntime runtime) { public void readArguments(ParameterValueList arguments, MinionRuntime runtime) {
} }
@Override @Override
public void save(WriteView view, MinionRuntime runtime) { public void save(ValueOutput view, MinionRuntime runtime) {
view.put("currentBlock", BlockPos.CODEC, currentBlock); view.store("currentBlock", BlockPos.CODEC, currentBlock);
view.putFloat("currentBlockDamage", currentBlockDamage); view.putFloat("currentBlockDamage", currentBlockDamage);
} }
@Override @Override
public void load(ReadView view, MinionRuntime runtime) { public void load(ValueInput view, MinionRuntime runtime) {
currentBlock = view.read("currentBlock", BlockPos.CODEC).orElse(null); currentBlock = view.read("currentBlock", BlockPos.CODEC).orElse(null);
currentBlockDamage = view.getFloat("currentBlockDamage", 0); currentBlockDamage = view.getFloatOr("currentBlockDamage", 0);
if(currentBlock == null) { if(currentBlock == null) {
done = true; done = true;
} }
@@ -1,18 +1,18 @@
package io.github.skippyall.minions.instruction.inventory; package io.github.skippyall.minions.minion.program.instruction.inventory;
import io.github.skippyall.minions.minion.MinionRuntime; import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer; import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.program.consumer.ValueConsumerList; import io.github.skippyall.minions.program.consumer.ValueConsumerList;
import io.github.skippyall.minions.program.instruction.InstructionExecution; import io.github.skippyall.minions.program.instruction.InstructionExecution;
import io.github.skippyall.minions.program.supplier.Parameter; import io.github.skippyall.minions.program.supplier.Parameter;
import io.github.skippyall.minions.program.supplier.ValueSupplierList; import io.github.skippyall.minions.program.supplier.ParameterValueList;
import io.github.skippyall.minions.registration.ValueTypes; import io.github.skippyall.minions.registration.ValueTypes;
import net.minecraft.item.ItemStack; import net.minecraft.world.inventory.AbstractContainerMenu;
import net.minecraft.screen.ScreenHandler; import net.minecraft.world.inventory.ContainerInput;
import net.minecraft.screen.slot.Slot; import net.minecraft.world.inventory.Slot;
import net.minecraft.screen.slot.SlotActionType; import net.minecraft.world.item.ItemStack;
import net.minecraft.storage.ReadView; import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.storage.WriteView; import net.minecraft.world.level.storage.ValueOutput;
public class SwapItemExecution implements InstructionExecution<MinionRuntime> { public class SwapItemExecution implements InstructionExecution<MinionRuntime> {
public static final Parameter<Long> FROM_SLOT = new Parameter<>("from_slot", ValueTypes.LONG); public static final Parameter<Long> FROM_SLOT = new Parameter<>("from_slot", ValueTypes.LONG);
@@ -31,7 +31,7 @@ public class SwapItemExecution implements InstructionExecution<MinionRuntime> {
public void start(MinionRuntime runtime) { public void start(MinionRuntime runtime) {
MinionFakePlayer minion = runtime.getMinion(); MinionFakePlayer minion = runtime.getMinion();
if((fromScreen || toScreen) && minion.currentScreenHandler == null) { if((fromScreen || toScreen) && minion.containerMenu == null) {
return; return;
} }
if(!(checkBounds(minion, fromSlot, fromScreen) && checkBounds(minion, toSlot, toScreen))) { if(!(checkBounds(minion, fromSlot, fromScreen) && checkBounds(minion, toSlot, toScreen))) {
@@ -48,14 +48,14 @@ public class SwapItemExecution implements InstructionExecution<MinionRuntime> {
simulateClick(minion, fromSlot, fromScreen); simulateClick(minion, fromSlot, fromScreen);
simulateClick(minion, toSlot, toScreen); simulateClick(minion, toSlot, toScreen);
simulateClick(minion, fromSlot, fromScreen); simulateClick(minion, fromSlot, fromScreen);
minion.getInventory().offerOrDrop(cursor); minion.getInventory().placeItemBackInInventory(cursor);
} }
private ScreenHandler getScreen(MinionFakePlayer minion, boolean screen) { private AbstractContainerMenu getScreen(MinionFakePlayer minion, boolean screen) {
if(screen) { if(screen) {
return minion.currentScreenHandler; return minion.containerMenu;
} else { } else {
return minion.playerScreenHandler; return minion.inventoryMenu;
} }
} }
@@ -64,16 +64,16 @@ public class SwapItemExecution implements InstructionExecution<MinionRuntime> {
} }
private ItemStack getStack(MinionFakePlayer minion, int slot, boolean screen) { private ItemStack getStack(MinionFakePlayer minion, int slot, boolean screen) {
return getScreen(minion, screen).getSlot(slot).getStack(); return getScreen(minion, screen).getSlot(slot).getItem();
} }
private boolean canExchange(MinionFakePlayer minion, int slotIndex, boolean screen, ItemStack newStack) { private boolean canExchange(MinionFakePlayer minion, int slotIndex, boolean screen, ItemStack newStack) {
ScreenHandler screenHandler = getScreen(minion, screen); AbstractContainerMenu screenHandler = getScreen(minion, screen);
Slot slot = screenHandler.getSlot(slotIndex); Slot slot = screenHandler.getSlot(slotIndex);
if(!slot.getStack().isEmpty() && !slot.canTakeItems(minion)) { if(!slot.getItem().isEmpty() && !slot.mayPickup(minion)) {
return false; return false;
} }
if(!newStack.isEmpty() && !slot.canInsert(newStack)) { if(!newStack.isEmpty() && !slot.mayPlace(newStack)) {
return false; return false;
} }
/*else { /*else {
@@ -90,12 +90,12 @@ public class SwapItemExecution implements InstructionExecution<MinionRuntime> {
} }
private void simulateClick(MinionFakePlayer minion, int slotIndex, boolean screen) { private void simulateClick(MinionFakePlayer minion, int slotIndex, boolean screen) {
ScreenHandler screenHandler = getScreen(minion, screen); AbstractContainerMenu screenHandler = getScreen(minion, screen);
ItemStack previousCursor = screenHandler.getCursorStack(); ItemStack previousCursor = screenHandler.getCarried();
screenHandler.setCursorStack(cursor); screenHandler.setCarried(cursor);
screenHandler.onSlotClick(slotIndex, 0, SlotActionType.SWAP, minion); screenHandler.clicked(slotIndex, 0, ContainerInput.SWAP, minion);
cursor = screenHandler.getCursorStack(); cursor = screenHandler.getCarried();
screenHandler.setCursorStack(previousCursor); screenHandler.setCarried(previousCursor);
} }
@Override @Override
@@ -114,20 +114,20 @@ public class SwapItemExecution implements InstructionExecution<MinionRuntime> {
} }
@Override @Override
public void readArguments(ValueSupplierList<MinionRuntime> arguments, MinionRuntime runtime) { public void readArguments(ParameterValueList arguments, MinionRuntime runtime) {
fromSlot = Math.clamp(arguments.getValue(FROM_SLOT, runtime), 0, Integer.MAX_VALUE); fromSlot = Math.clamp(arguments.getValue(FROM_SLOT), 0, Integer.MAX_VALUE);
fromScreen = arguments.getValue(FROM_SCREEN, runtime); fromScreen = arguments.getValue(FROM_SCREEN);
toSlot = Math.clamp(arguments.getValue(TO_SLOT, runtime), 0, Integer.MAX_VALUE); toSlot = Math.clamp(arguments.getValue(TO_SLOT), 0, Integer.MAX_VALUE);
toScreen = arguments.getValue(TO_SCREEN, runtime); toScreen = arguments.getValue(TO_SCREEN);
} }
@Override @Override
public void save(WriteView view, MinionRuntime runtime) { public void save(ValueOutput view, MinionRuntime runtime) {
} }
@Override @Override
public void load(ReadView view, MinionRuntime runtime) { public void load(ValueInput view, MinionRuntime runtime) {
} }
} }
@@ -1,9 +1,9 @@
package io.github.skippyall.minions.instruction.move; package io.github.skippyall.minions.minion.program.instruction.move;
import io.github.skippyall.minions.minion.MinionRuntime; import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.program.instruction.InstructionExecution; import io.github.skippyall.minions.program.instruction.InstructionExecution;
import net.minecraft.storage.ReadView; import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.storage.WriteView; import net.minecraft.world.level.storage.ValueOutput;
public abstract class AbstractTurnExecution implements InstructionExecution<MinionRuntime> { public abstract class AbstractTurnExecution implements InstructionExecution<MinionRuntime> {
protected float targetYaw; protected float targetYaw;
@@ -13,26 +13,26 @@ public abstract class AbstractTurnExecution implements InstructionExecution<Mini
@Override @Override
public void tick(MinionRuntime minion) { public void tick(MinionRuntime minion) {
float rotateYaw = targetYaw - minion.getMinion().getYaw(); float rotateYaw = targetYaw - minion.getMinion().getYRot();
float rotatePitch = targetPitch - minion.getMinion().getPitch(); float rotatePitch = targetPitch - minion.getMinion().getXRot();
minion.getMinion().getMinionActionPack().turn(Math.min(rotateYaw, anglePerTick), Math.min(rotatePitch, anglePerTick)); minion.getMinion().getMinionActionPack().turn(Math.min(rotateYaw, anglePerTick), Math.min(rotatePitch, anglePerTick));
} }
@Override @Override
public boolean isDone(MinionRuntime minion) { public boolean isDone(MinionRuntime minion) {
return Math.abs(targetYaw - minion.getMinion().getYaw()) < 0.001F && Math.abs(targetPitch - minion.getMinion().getPitch()) < 0.001F; return Math.abs(targetYaw - minion.getMinion().getYRot()) < 0.001F && Math.abs(targetPitch - minion.getMinion().getXRot()) < 0.001F;
} }
@Override @Override
public void save(WriteView view, MinionRuntime runtime) { public void save(ValueOutput view, MinionRuntime runtime) {
view.putFloat("targetYaw", targetYaw); view.putFloat("targetYaw", targetYaw);
view.putFloat("targetPitch", targetPitch); view.putFloat("targetPitch", targetPitch);
} }
@Override @Override
public void load(ReadView view, MinionRuntime runtime) { public void load(ValueInput view, MinionRuntime runtime) {
targetYaw = view.getFloat("targetYaw", 0); targetYaw = view.getFloatOr("targetYaw", 0);
targetPitch = view.getFloat("targetPitch", 0); targetPitch = view.getFloatOr("targetPitch", 0);
} }
} }
@@ -1,13 +1,13 @@
package io.github.skippyall.minions.instruction.move; package io.github.skippyall.minions.minion.program.instruction.move;
import io.github.skippyall.minions.minion.MinionRuntime; import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.program.instruction.InstructionExecution; import io.github.skippyall.minions.program.instruction.InstructionExecution;
import io.github.skippyall.minions.program.instruction.execution.ContinuousInstructionExecution; import io.github.skippyall.minions.program.instruction.execution.ContinuousInstructionExecution;
import net.minecraft.entity.MovementType; import net.minecraft.world.entity.MoverType;
public class ContinuousWalkExecution implements ContinuousInstructionExecution<MinionRuntime>, InstructionExecution.Stateless<MinionRuntime> { public class ContinuousWalkExecution implements ContinuousInstructionExecution<MinionRuntime>, InstructionExecution.Stateless<MinionRuntime> {
@Override @Override
public void tick(MinionRuntime minion) { public void tick(MinionRuntime minion) {
minion.getMinion().move(MovementType.SELF, minion.getMinion().getHorizontalFacing().getDoubleVector().normalize().multiply(minion.getMinion().getMovementSpeed())); minion.getMinion().move(MoverType.SELF, minion.getMinion().getDirection().getUnitVec3().normalize().scale(minion.getMinion().getSpeed()));
} }
} }
@@ -1,19 +1,19 @@
package io.github.skippyall.minions.instruction.move; package io.github.skippyall.minions.minion.program.instruction.move;
import com.mojang.serialization.Codec; import com.mojang.serialization.Codec;
import io.github.skippyall.minions.gui.Displayable; import io.github.skippyall.minions.gui.Displayable;
import io.github.skippyall.minions.gui.GuiDisplay; import io.github.skippyall.minions.gui.GuiDisplay;
import net.minecraft.util.StringIdentifiable; import net.minecraft.util.StringRepresentable;
import java.util.UUID; import java.util.UUID;
public enum TurnDirection implements StringIdentifiable, Displayable { public enum TurnDirection implements StringRepresentable, Displayable {
LEFT("left", -1, 0), LEFT("left", -1, 0),
UP("up", 0, -1), UP("up", 0, -1),
RIGHT("right", 1, 0), RIGHT("right", 1, 0),
DOWN("down", 0, 1); DOWN("down", 0, 1);
public static final Codec<TurnDirection> CODEC = StringIdentifiable.createCodec(TurnDirection::values); public static final Codec<TurnDirection> CODEC = StringRepresentable.fromEnum(TurnDirection::values);
private static final UUID MHF_ArrowLeft = UUID.fromString("a68f0b64-8d14-4000-a95f-4b9ba14f8df9"); private static final UUID MHF_ArrowLeft = UUID.fromString("a68f0b64-8d14-4000-a95f-4b9ba14f8df9");
private static final UUID MHF_ArrowUp = UUID.fromString("fef039ef-e6cd-4987-9c84-26a3e6134277"); private static final UUID MHF_ArrowUp = UUID.fromString("fef039ef-e6cd-4987-9c84-26a3e6134277");
@@ -31,7 +31,7 @@ public enum TurnDirection implements StringIdentifiable, Displayable {
} }
@Override @Override
public String asString() { public String getSerializedName() {
return name; return name;
} }
@@ -1,8 +1,8 @@
package io.github.skippyall.minions.instruction.move; package io.github.skippyall.minions.minion.program.instruction.move;
import io.github.skippyall.minions.minion.MinionRuntime; import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.program.supplier.ValueSupplierList;
import io.github.skippyall.minions.program.supplier.Parameter; import io.github.skippyall.minions.program.supplier.Parameter;
import io.github.skippyall.minions.program.supplier.ParameterValueList;
import io.github.skippyall.minions.registration.ValueTypes; import io.github.skippyall.minions.registration.ValueTypes;
public class TurnExecution extends AbstractTurnExecution { public class TurnExecution extends AbstractTurnExecution {
@@ -10,14 +10,14 @@ public class TurnExecution extends AbstractTurnExecution {
public static final Parameter<TurnDirection> DIRECTION = new Parameter<>("direction", ValueTypes.TURN_DIRECTION); public static final Parameter<TurnDirection> DIRECTION = new Parameter<>("direction", ValueTypes.TURN_DIRECTION);
@Override @Override
public void readArguments(ValueSupplierList<MinionRuntime> arguments, MinionRuntime minion) { public void readArguments(ParameterValueList arguments, MinionRuntime minion) {
float maxAngle = arguments.getValue(ANGLE, minion).floatValue(); float maxAngle = arguments.getValue(ANGLE).floatValue();
TurnDirection direction = arguments.getValue(DIRECTION, minion); TurnDirection direction = arguments.getValue(DIRECTION);
float turnYaw = maxAngle * direction.xFactor; float turnYaw = maxAngle * direction.xFactor;
float turnPitch = maxAngle * direction.yFactor; float turnPitch = maxAngle * direction.yFactor;
targetYaw = minion.getMinion().getYaw() + turnYaw; targetYaw = minion.getMinion().getYRot() + turnYaw;
targetPitch = minion.getMinion().getPitch() + turnPitch; targetPitch = minion.getMinion().getXRot() + turnPitch;
} }
} }
@@ -0,0 +1,36 @@
package io.github.skippyall.minions.minion.program.instruction.move;
import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.program.supplier.Parameter;
import io.github.skippyall.minions.program.supplier.ParameterValueList;
import io.github.skippyall.minions.registration.ValueTypes;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec2;
import net.minecraft.world.phys.Vec3;
public class TurnVectorExecution extends AbstractTurnExecution {
public static final Parameter<Double> X = new Parameter<>("x", ValueTypes.DOUBLE);
public static final Parameter<Double> Y = new Parameter<>("y", ValueTypes.DOUBLE);
public static final Parameter<Double> Z = new Parameter<>("z", ValueTypes.DOUBLE);
@Override
public void readArguments(ParameterValueList arguments, MinionRuntime runtime) {
double x = arguments.getValue(X);
double y = arguments.getValue(Y);
double z = arguments.getValue(Z);
Vec3 vector = new Vec3(x, y, z);
Vec2 rotation = vectorToRotation(vector);
targetYaw = rotation.x;
targetPitch = rotation.y;
}
//copied from Entity#lookAt (why no helper, Mojang?)
public static Vec2 vectorToRotation(Vec3 vector) {
double g = Math.sqrt(vector.x * vector.x + vector.z * vector.z);
float pitch = Mth.wrapDegrees((float)(-(Mth.atan2(vector.y, g) * 180.0F / (float)Math.PI)));
float yaw = Mth.wrapDegrees((float)(Mth.atan2(vector.z, vector.x) * 180.0F / (float)Math.PI) - 90.0F);
return new Vec2(yaw, pitch);
}
}
@@ -1,13 +1,13 @@
package io.github.skippyall.minions.instruction.move; package io.github.skippyall.minions.minion.program.instruction.move;
import io.github.skippyall.minions.minion.MinionRuntime; import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.program.instruction.InstructionExecution; import io.github.skippyall.minions.program.instruction.InstructionExecution;
import io.github.skippyall.minions.program.supplier.Parameter; import io.github.skippyall.minions.program.supplier.Parameter;
import io.github.skippyall.minions.program.supplier.ValueSupplierList; import io.github.skippyall.minions.program.supplier.ParameterValueList;
import io.github.skippyall.minions.registration.ValueTypes; import io.github.skippyall.minions.registration.ValueTypes;
import net.minecraft.entity.MovementType; import net.minecraft.world.entity.MoverType;
import net.minecraft.storage.ReadView; import net.minecraft.world.level.storage.ValueInput;
import net.minecraft.storage.WriteView; import net.minecraft.world.level.storage.ValueOutput;
public class WalkExecution implements InstructionExecution<MinionRuntime> { public class WalkExecution implements InstructionExecution<MinionRuntime> {
public static final Parameter<Double> blocksToMoveParam = new Parameter<>("blocksToMove", ValueTypes.DOUBLE); public static final Parameter<Double> blocksToMoveParam = new Parameter<>("blocksToMove", ValueTypes.DOUBLE);
@@ -18,8 +18,8 @@ public class WalkExecution implements InstructionExecution<MinionRuntime> {
@Override @Override
public void tick(MinionRuntime minion) { public void tick(MinionRuntime minion) {
double speed = Math.min(minion.getMinion().getMovementSpeed(), totalBlocksToMove - blocksMoved); double speed = Math.min(minion.getMinion().getSpeed(), totalBlocksToMove - blocksMoved);
minion.getMinion().move(MovementType.SELF, minion.getMinion().getHorizontalFacing().getDoubleVector().normalize().multiply(speed)); minion.getMinion().move(MoverType.SELF, minion.getMinion().getDirection().getUnitVec3().normalize().scale(speed));
blocksMoved += speed; blocksMoved += speed;
} }
@@ -29,20 +29,20 @@ public class WalkExecution implements InstructionExecution<MinionRuntime> {
} }
@Override @Override
public void readArguments(ValueSupplierList<MinionRuntime> parameters, MinionRuntime minion) { public void readArguments(ParameterValueList parameters, MinionRuntime minion) {
totalBlocksToMove = parameters.getValue(blocksToMoveParam, minion).floatValue(); totalBlocksToMove = parameters.getValue(blocksToMoveParam).floatValue();
blocksMoved = 0; blocksMoved = 0;
} }
@Override @Override
public void save(WriteView view, MinionRuntime minion) { public void save(ValueOutput view, MinionRuntime minion) {
view.putDouble("totalBlocksToMove", totalBlocksToMove); view.putDouble("totalBlocksToMove", totalBlocksToMove);
view.putDouble("blocksMoved", blocksMoved); view.putDouble("blocksMoved", blocksMoved);
} }
@Override @Override
public void load(ReadView view, MinionRuntime minion) { public void load(ValueInput view, MinionRuntime minion) {
totalBlocksToMove = view.getDouble("totalBlocksToMove", 0F); totalBlocksToMove = view.getDoubleOr("totalBlocksToMove", 0F);
blocksMoved = view.getDouble("blocksMoved", 0F); blocksMoved = view.getDoubleOr("blocksMoved", 0F);
} }
} }
@@ -0,0 +1,107 @@
package io.github.skippyall.minions.minion.program.supplier;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import eu.pb4.sgui.api.elements.GuiElementBuilder;
import eu.pb4.sgui.api.gui.SimpleGui;
import io.github.skippyall.minions.clipboard.BlockPosClipboard;
import io.github.skippyall.minions.gui.MinionsGui;
import io.github.skippyall.minions.gui.minion.SimpleMinionsGui;
import io.github.skippyall.minions.minion.MinionRuntime;
import io.github.skippyall.minions.program.supplier.ValueSupplier;
import io.github.skippyall.minions.program.supplier.ValueSupplierType;
import io.github.skippyall.minions.program.value.ValueType;
import io.github.skippyall.minions.registration.MinionBlocks;
import io.github.skippyall.minions.registration.MinionComponentTypes;
import io.github.skippyall.minions.registration.MinionItems;
import io.github.skippyall.minions.registration.ValueSuppliers;
import io.github.skippyall.minions.registration.ValueTypes;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.CompletableFuture;
public class AnalogInputSupplier implements ValueSupplier<Long, MinionRuntime> {
public static final Codec<AnalogInputSupplier> CODEC = RecordCodecBuilder.create(instance ->
instance.group(
Level.RESOURCE_KEY_CODEC.fieldOf("analogInputWorld").forGetter(s -> s.analogInputWorld),
BlockPos.CODEC.fieldOf("analogInputPos").forGetter(s -> s.analogInputPos)
).apply(instance, AnalogInputSupplier::new));
private final ResourceKey<Level> analogInputWorld;
private final BlockPos analogInputPos;
public AnalogInputSupplier(ResourceKey<Level> analogInputWorld, BlockPos analogInputPos) {
this.analogInputWorld = analogInputWorld;
this.analogInputPos = analogInputPos;
}
@Override
public Long resolve(MinionRuntime minion) {
Level world = minion.getMinion().getServer().getLevel(analogInputWorld);
if(world != null && world.isLoaded(analogInputPos) && world.getBlockState(analogInputPos).is(MinionBlocks.ANALOG_INPUT_BLOCK)) {
return (long) world.getBestNeighborSignal(analogInputPos);
} else {
return 0L;
}
}
@Override
public ValueType<Long> getValueType() {
return ValueTypes.LONG;
}
@Override
public ValueSupplierType<MinionRuntime> getType() {
return ValueSuppliers.ANALOG_INPUT;
}
@Override
public Component getDisplayText() {
return Component.translatable("value_supplier.minions.analog_input.display", analogInputPos.toShortString(), analogInputWorld.identifier().toString());
}
public static class AnalogInputSupplierType extends ValueSupplierType<MinionRuntime> {
@Override
public <T> Codec<? extends ValueSupplier<T, MinionRuntime>> getCodec(ValueType<T> type) {
if(type == ValueTypes.LONG) {
return ValueSupplier.castCodec(CODEC, ValueTypes.LONG, type);
}
return null;
}
@Override
public <T> CompletableFuture<ValueSupplier<?, MinionRuntime>> openConfiguration(MinionsGui parent, ValueType<T> valueType, @Nullable ValueSupplier<?, MinionRuntime> previous) {
CompletableFuture<ValueSupplier<?, MinionRuntime>> future = new CompletableFuture<>();
new SimpleMinionsGui(parent, (onClose, me) -> {
SimpleGui gui = new SimpleGui(MenuType.GENERIC_3x3, parent.viewer, false) {
@Override
public void onPlayerClose(boolean success) {
onClose.run();
}
};
gui.setTitle(Component.translatable("value_supplier.minions.analog_input"));
gui.setSlot(2, me.backButton());
gui.setSlot(4, new GuiElementBuilder(MinionItems.REFERENCE_ITEM)
.setCallback(() -> {
ItemStack cursor = parent.viewer.containerMenu.getCarried();
if (cursor.is(MinionItems.REFERENCE_ITEM) && cursor.get(MinionComponentTypes.REFERENCE) instanceof BlockPosClipboard pos) {
future.complete(new AnalogInputSupplier(pos.world(), pos.pos()));
me.goBack();
}
})
.setItemName(Component.translatable("value_supplier.minions.analog_input.config.click_with_reference"))
);
gui.open();
return gui;
});
return future;
}
}
}
@@ -1,25 +1,29 @@
package io.github.skippyall.minions.minion.skin; package io.github.skippyall.minions.minion.skin;
import com.google.common.collect.ImmutableMultimap;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property; import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap; import com.mojang.authlib.properties.PropertyMap;
import io.github.skippyall.minions.Minions; import io.github.skippyall.minions.Minions;
import net.minecraft.dialog.AfterAction; import io.github.skippyall.minions.gui.MinionsGui;
import net.minecraft.dialog.DialogActionButtonData; import net.fabricmc.fabric.api.entity.FakePlayer;
import net.minecraft.dialog.DialogButtonData; import net.minecraft.core.Holder;
import net.minecraft.dialog.DialogCommonData; import net.minecraft.core.registries.Registries;
import net.minecraft.dialog.action.DynamicCustomDialogAction; import net.minecraft.nbt.CompoundTag;
import net.minecraft.dialog.input.TextInputControl; import net.minecraft.nbt.Tag;
import net.minecraft.dialog.type.Dialog; import net.minecraft.network.chat.Component;
import net.minecraft.dialog.type.DialogInput; import net.minecraft.resources.Identifier;
import net.minecraft.dialog.type.NoticeDialog; import net.minecraft.resources.ResourceKey;
import net.minecraft.nbt.NbtCompound; import net.minecraft.server.dialog.ActionButton;
import net.minecraft.nbt.NbtElement; import net.minecraft.server.dialog.CommonButtonData;
import net.minecraft.registry.RegistryKey; import net.minecraft.server.dialog.CommonDialogData;
import net.minecraft.registry.RegistryKeys; import net.minecraft.server.dialog.Dialog;
import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.server.dialog.DialogAction;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.dialog.Input;
import net.minecraft.text.Text; import net.minecraft.server.dialog.NoticeDialog;
import net.minecraft.util.Identifier; import net.minecraft.server.dialog.action.CustomAll;
import net.minecraft.server.dialog.input.TextInput;
import net.minecraft.world.item.component.ResolvableProfile;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@@ -28,53 +32,54 @@ import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public class Base64SkinProvider implements SkinProvider { public class Base64SkinProvider implements SkinProvider {
public static final RegistryKey<Dialog> DIALOG = RegistryKey.of(RegistryKeys.DIALOG, Identifier.of(Minions.MOD_ID, "base_64_input")); public static final ResourceKey<Dialog> DIALOG = ResourceKey.create(Registries.DIALOG, Identifier.fromNamespaceAndPath(Minions.MOD_ID, "base_64_input"));
public static final Identifier CUSTOM_DIALOG_ACTION = Identifier.of(Minions.MOD_ID, "base_64_submit"); public static final Identifier CUSTOM_DIALOG_ACTION = Identifier.fromNamespaceAndPath(Minions.MOD_ID, "base_64_submit");
private static long dialogIdCounter = 0; private static long dialogIdCounter = 0;
private static Map<Long, CompletableFuture<Optional<PropertyMap>>> futures = new HashMap<>(); private static Map<Long, CompletableFuture<ResolvableProfile>> futures = new HashMap<>();
@Override @Override
public CompletableFuture<Optional<PropertyMap>> openSkinMenu(ServerPlayerEntity player) { public CompletableFuture<ResolvableProfile> openSkinMenu(MinionsGui parent) {
dialogIdCounter++; dialogIdCounter++;
player.openDialog(getDialog()); parent.viewer.openDialog(getDialog());
CompletableFuture<Optional<PropertyMap>> future = new CompletableFuture<>(); CompletableFuture<ResolvableProfile> future = new CompletableFuture<>();
futures.put(dialogIdCounter, future); futures.put(dialogIdCounter, future);
return future; return future;
} }
public static void onCustomDialogAction(Optional<NbtElement> element) { public static void onCustomDialogAction(Optional<Tag> element) {
if(element.isPresent() && element.get() instanceof NbtCompound compound) { if(element.isPresent() && element.get() instanceof CompoundTag compound) {
Optional<Long> id = compound.getLong("dialog_id"); Optional<Long> id = compound.getLong("dialog_id");
Optional<String> base64 = compound.getString("base_64"); Optional<String> base64 = compound.getString("base_64");
if(id.isPresent() && base64.isPresent() && !base64.get().isBlank()) { if(id.isPresent() && base64.isPresent() && !base64.get().isBlank()) {
if(futures.containsKey(id.get())) { if(futures.containsKey(id.get())) {
PropertyMap map = new PropertyMap(); PropertyMap map = new PropertyMap(ImmutableMultimap.of(
map.put("textures", new Property("textures", base64.get().strip())); "textures", new Property("textures", base64.get().strip())
));
futures.get(id.get()).complete(Optional.of(map)); futures.get(id.get()).complete(ResolvableProfile.createResolved(new GameProfile(FakePlayer.DEFAULT_UUID, "", map)));
futures.remove(id.get()); futures.remove(id.get());
} }
} }
} }
} }
private static RegistryEntry<Dialog> getDialog() { private static Holder<Dialog> getDialog() {
NbtCompound additionalData = new NbtCompound(); CompoundTag additionalData = new CompoundTag();
additionalData.putLong("dialog_id", dialogIdCounter); additionalData.putLong("dialog_id", dialogIdCounter);
return RegistryEntry.of( return Holder.direct(
new NoticeDialog( new NoticeDialog(
new DialogCommonData( new CommonDialogData(
Text.translatable("minions.gui.look.skin.base64.title"), Component.translatable("minions.gui.look.skin.base64.title"),
Optional.empty(), Optional.empty(),
true, true,
false, false,
AfterAction.CLOSE, DialogAction.CLOSE,
List.of(), List.of(),
List.of( List.of(
new DialogInput("base_64", new TextInputControl( new Input("base_64", new TextInput(
200, 200,
Text.empty(), Component.empty(),
false, false,
"", "",
2000, 2000,
@@ -82,13 +87,13 @@ public class Base64SkinProvider implements SkinProvider {
)) ))
) )
), ),
new DialogActionButtonData( new ActionButton(
new DialogButtonData( new CommonButtonData(
Text.translatable("gui.ok"), Component.translatable("gui.ok"),
150 150
), ),
Optional.of( Optional.of(
new DynamicCustomDialogAction( new CustomAll(
CUSTOM_DIALOG_ACTION, CUSTOM_DIALOG_ACTION,
Optional.of( Optional.of(
additionalData additionalData
@@ -101,7 +106,7 @@ public class Base64SkinProvider implements SkinProvider {
} }
@Override @Override
public Text getDisplayName() { public Component getDisplayName() {
return Text.translatable("minions.gui.look.skin.base64"); return Component.translatable("minions.gui.look.skin.base64");
} }
} }
@@ -1,25 +1,21 @@
package io.github.skippyall.minions.minion.skin; package io.github.skippyall.minions.minion.skin;
import com.mojang.authlib.GameProfile; import io.github.skippyall.minions.gui.MinionsGui;
import com.mojang.authlib.properties.PropertyMap;
import io.github.skippyall.minions.gui.input.TextInput; import io.github.skippyall.minions.gui.input.TextInput;
import net.minecraft.block.entity.SkullBlockEntity; import net.minecraft.network.chat.Component;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.world.item.component.ResolvableProfile;
import net.minecraft.text.Text;
import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public class NameSkinProvider implements SkinProvider { public class NameSkinProvider implements SkinProvider {
@Override @Override
public CompletableFuture<Optional<PropertyMap>> openSkinMenu(ServerPlayerEntity player) { public CompletableFuture<ResolvableProfile> openSkinMenu(MinionsGui parent) {
return TextInput.inputString(player, Text.translatable("minions.gui.look.skin.name.title"), "") return TextInput.inputStringFuture(parent, Component.translatable("minions.gui.look.skin.name.title"), "")
.thenCompose(SkullBlockEntity::fetchProfileByName) .thenApply(name -> name != null ? ResolvableProfile.createUnresolved(name) : null);
.thenApply(gameProfile -> gameProfile.map(GameProfile::getProperties));
} }
@Override @Override
public Text getDisplayName() { public Component getDisplayName() {
return Text.translatable("minions.gui.look.skin.name"); return Component.translatable("minions.gui.look.skin.name");
} }
} }
@@ -1,14 +1,13 @@
package io.github.skippyall.minions.minion.skin; package io.github.skippyall.minions.minion.skin;
import com.mojang.authlib.properties.PropertyMap; import io.github.skippyall.minions.gui.MinionsGui;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.network.chat.Component;
import net.minecraft.text.Text; import net.minecraft.world.item.component.ResolvableProfile;
import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public interface SkinProvider { public interface SkinProvider {
CompletableFuture<Optional<PropertyMap>> openSkinMenu(ServerPlayerEntity player); CompletableFuture<ResolvableProfile> openSkinMenu(MinionsGui parent);
Text getDisplayName(); Component getDisplayName();
} }
@@ -1,26 +1,21 @@
package io.github.skippyall.minions.minion.skin; package io.github.skippyall.minions.minion.skin;
import com.mojang.authlib.GameProfile; import io.github.skippyall.minions.gui.MinionsGui;
import com.mojang.authlib.properties.PropertyMap;
import io.github.skippyall.minions.gui.input.TextInput; import io.github.skippyall.minions.gui.input.TextInput;
import net.minecraft.block.entity.SkullBlockEntity; import net.minecraft.network.chat.Component;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.world.item.component.ResolvableProfile;
import net.minecraft.text.Text;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public class UUIDSkinProvider implements SkinProvider { public class UUIDSkinProvider implements SkinProvider {
@Override @Override
public CompletableFuture<Optional<PropertyMap>> openSkinMenu(ServerPlayerEntity player) { public CompletableFuture<ResolvableProfile> openSkinMenu(MinionsGui parent) {
return TextInput.inputString(player, Text.translatable("minions.gui.look.skin.uuid.title"), "") return TextInput.inputStringFuture(parent, Component.translatable("minions.gui.look.skin.uuid.title"), "")
.thenCompose(uuidString -> SkullBlockEntity.fetchProfileByUuid(UUID.fromString(uuidString))) .thenApply(name -> name != null ? ResolvableProfile.createUnresolved(name) : null);
.thenApply(gameProfile -> gameProfile.map(GameProfile::getProperties));
} }
@Override @Override
public Text getDisplayName() { public Component getDisplayName() {
return Text.translatable("minions.gui.look.skin.uuid"); return Component.translatable("minions.gui.look.skin.uuid");
} }
} }
@@ -1,6 +1,6 @@
package io.github.skippyall.minions.mixinhelper; package io.github.skippyall.minions.mixinhelper;
import net.minecraft.entity.Entity; import net.minecraft.world.entity.Entity;
import java.util.function.Predicate; import java.util.function.Predicate;
@@ -1,11 +1,11 @@
package io.github.skippyall.minions.mixinhelper.antimobcap; package io.github.skippyall.minions.mixinhelper.antimobcap;
import it.unimi.dsi.fastutil.objects.ObjectSet; import it.unimi.dsi.fastutil.objects.ObjectSet;
import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.level.DistanceManager;
import net.minecraft.server.world.ChunkLevelManager; import net.minecraft.server.level.ServerPlayer;
public interface ChunkLevelManagerAccessor { public interface ChunkLevelManagerAccessor {
ObjectSet<ServerPlayerEntity> minions$getPlayers(long chunkpos); ObjectSet<ServerPlayer> minions$getPlayers(long chunkpos);
ChunkLevelManager.DistanceFromNearestPlayerTracker minions$getMinionless(); DistanceManager.FixedPlayerDistanceChunkTracker minions$getMinionless();
} }
@@ -3,20 +3,20 @@ package io.github.skippyall.minions.mixins;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import it.unimi.dsi.fastutil.objects.ObjectSet; import it.unimi.dsi.fastutil.objects.ObjectSet;
import net.minecraft.server.world.ChunkLevelManager; import net.minecraft.server.level.DistanceManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
@Mixin(value = ChunkLevelManager.class) @Mixin(value = DistanceManager.class)
public class ChunkTicketManagerFixMixin { public class ChunkTicketManagerFixMixin {
@Shadow @Shadow
@Final @Final
private static Logger LOGGER; private static Logger LOGGER;
@WrapOperation(method = "handleChunkLeave", at = @At(value = "INVOKE", target = "Lit/unimi/dsi/fastutil/objects/ObjectSet;remove(Ljava/lang/Object;)Z", remap = false)) @WrapOperation(method = "removePlayer", at = @At(value = "INVOKE", target = "Lit/unimi/dsi/fastutil/objects/ObjectSet;remove(Ljava/lang/Object;)Z", remap = false))
public boolean filterIfNull(ObjectSet instance, Object o, Operation<Boolean> original) { public boolean filterIfNull(ObjectSet instance, Object o, Operation<Boolean> original) {
if (instance != null) { if (instance != null) {
return original.call(instance, o); return original.call(instance, o);
@@ -27,7 +27,7 @@ public class ChunkTicketManagerFixMixin {
return false;//Unused return false;//Unused
} }
@WrapOperation(method = "handleChunkLeave", at = @At(value = "INVOKE", target = "Lit/unimi/dsi/fastutil/objects/ObjectSet;isEmpty()Z", remap = false)) @WrapOperation(method = "removePlayer", at = @At(value = "INVOKE", target = "Lit/unimi/dsi/fastutil/objects/ObjectSet;isEmpty()Z", remap = false))
public boolean filterIfNull(ObjectSet instance, Operation<Boolean> original) { public boolean filterIfNull(ObjectSet instance, Operation<Boolean> original) {
if (instance != null) { if (instance != null) {
return original.call(instance); return original.call(instance);
@@ -0,0 +1,22 @@
package io.github.skippyall.minions.mixins;
import com.llamalad7.mixinextras.sugar.Local;
import io.github.skippyall.minions.minion.fakeplayer.MinionFakePlayer;
import io.github.skippyall.minions.registration.MinionConfigOptions;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.server.level.ServerPlayer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
@Mixin(ClientboundPlayerInfoUpdatePacket.Entry.class)
public class ClientboundPlayerInfoUpdatePacket$EntryMixin {
@ModifyArg(method = "<init>(Lnet/minecraft/server/level/ServerPlayer;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket$Entry;<init>(Ljava/util/UUID;Lcom/mojang/authlib/GameProfile;ZILnet/minecraft/world/level/GameType;Lnet/minecraft/network/chat/Component;ZILnet/minecraft/network/chat/RemoteChatSession$Data;)V"), index = 2)
private static boolean removeMinionFromTabList(boolean original, @Local(argsOnly = true) ServerPlayer player) {
if(player instanceof MinionFakePlayer minion && !minion.getData().config().getOption(MinionConfigOptions.showInTabList)) {
return false;
}
return original;
}
}
@@ -3,11 +3,11 @@ package io.github.skippyall.minions.mixins;
import io.github.skippyall.minions.minion.fakeplayer.ClientConnectionInterface; import io.github.skippyall.minions.minion.fakeplayer.ClientConnectionInterface;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import net.minecraft.network.ClientConnection; import net.minecraft.network.Connection;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor; import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(ClientConnection.class) @Mixin(Connection.class)
public abstract class ConnectionMixin implements ClientConnectionInterface { public abstract class ConnectionMixin implements ClientConnectionInterface {
@Override @Override
@Accessor //Compat with adventure-platform-fabric @Accessor //Compat with adventure-platform-fabric

Some files were not shown because too many files have changed in this diff Show More