Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8b301457f | ||
|
|
40edf05109 | ||
|
|
4e5a92aff7 | ||
|
|
a09e6058a9 | ||
|
|
e774dd5e10 | ||
|
|
0866dbee98 | ||
|
|
208f7ffc41 | ||
|
|
88a6021546 | ||
|
|
a05267a566 | ||
|
|
dd3413dcaa | ||
|
|
34394ef8a5 | ||
|
|
7c0d3f4bd6 | ||
|
|
cd4f3617be | ||
|
|
256bc60d35 | ||
|
|
70426ab7b3 | ||
|
|
0030c02e9a | ||
|
|
7382db1bbd | ||
|
|
b669cbecf1 | ||
|
|
6283931667 | ||
|
|
3b3bc4b9ad | ||
|
|
bef4fdd949 | ||
|
|
4a06c8f95b | ||
|
|
241bed1989 | ||
|
|
f5594c25db | ||
|
|
7cda901ae5 | ||
|
|
538459eab5 | ||
|
|
3a9ec8b6ac | ||
|
|
330a13271b | ||
|
|
bf5dd5566b | ||
|
|
b9d0143245 | ||
|
|
43fc39c99b | ||
|
|
6337536dc3 | ||
|
|
9b96560119 | ||
|
|
3ce1c877e3 | ||
|
|
3d150d95cb | ||
|
|
03e84e3d37 | ||
|
|
547833bedb | ||
|
|
61e0d4b7e7 | ||
|
|
6867ed50e6 | ||
|
|
e7a47c199b | ||
|
|
b2bc474382 | ||
|
|
eef6baea4b | ||
|
|
efd49701e1 | ||
|
|
a7f0d6be72 |
No files matched your search
@@ -0,0 +1,17 @@
|
||||
name: Optimize images
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master, main, release/** ]
|
||||
paths:
|
||||
- "**/*.png"
|
||||
|
||||
push:
|
||||
branches: [ master, main, release/** ]
|
||||
paths:
|
||||
- "**/*.png"
|
||||
|
||||
jobs:
|
||||
optimize-images:
|
||||
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/optimize-images.yml@master
|
||||
secrets: inherit
|
||||
@@ -27,3 +27,4 @@ whitelist.json
|
||||
*.iws
|
||||
src/main/resources/mixins.*.json
|
||||
*.bat
|
||||
layout.json
|
||||
@@ -1,9 +1,10 @@
|
||||
// Add your dependencies here
|
||||
|
||||
dependencies {
|
||||
api("com.github.GTNewHorizons:GTNHLib:0.0.13:dev")
|
||||
runtimeOnly("com.github.GTNewHorizons:NotEnoughItems:2.5.3-GTNH:dev")
|
||||
api("com.github.GTNewHorizons:GTNHLib:0.9.47:dev")
|
||||
implementation("com.github.GTNewHorizons:NotEnoughItems:2.8.84-GTNH:dev")
|
||||
|
||||
testImplementation(platform('org.junit:junit-bom:5.9.2'))
|
||||
testImplementation('org.junit.jupiter:junit-jupiter')
|
||||
testRuntimeOnly ('org.junit.platform:junit-platform-launcher')
|
||||
}
|
||||
@@ -1,63 +1,238 @@
|
||||
# ExampleMod tag to use as Blowdryer (Spotless, etc.) settings version, leave empty to disable.
|
||||
# LOCAL to test local config updates.
|
||||
gtnh.settings.blowdryerTag = 0.2.0
|
||||
|
||||
# Human-readable mod name, available for mcmod.info population.
|
||||
modName = StructureLib
|
||||
|
||||
# This is a case-sensitive string to identify your mod. Convention is to use lower case.
|
||||
# Case-sensitive identifier string, available for mcmod.info population and used for automatic mixin JSON generation.
|
||||
# Conventionally lowercase.
|
||||
modId = structurelib
|
||||
|
||||
# Root package of the mod, used to find various classes in other properties,
|
||||
# mcmod.info substitution, enabling assertions in run tasks, etc.
|
||||
modGroup = com.gtnewhorizon.structurelib
|
||||
|
||||
# WHY is there no version field?
|
||||
# The build script relies on git to provide a version via tags. It is super easy and will enable you to always know the
|
||||
# code base or your binary. Check out this tutorial: https://blog.mattclemente.com/2017/10/13/versioning-with-git-tags/
|
||||
# Whether to use modGroup as the maven publishing group.
|
||||
# When false, com.github.GTNewHorizons is used.
|
||||
useModGroupForPublishing = false
|
||||
|
||||
# Will update your build.gradle automatically whenever an update is available
|
||||
# Updates your build.gradle and settings.gradle automatically whenever an update is available.
|
||||
autoUpdateBuildScript = false
|
||||
|
||||
# Version of Minecraft to target
|
||||
minecraftVersion = 1.7.10
|
||||
|
||||
# Version of Minecraft Forge to target
|
||||
forgeVersion = 10.13.4.1614
|
||||
|
||||
# Select a username for testing your mod with breakpoints. You may leave this empty for a random user name each time you
|
||||
# restart Minecraft in development. Choose this dependent on your mod:
|
||||
# Do you need consistent player progressing (for example Thaumcraft)? -> Select a name
|
||||
# Do you need to test how your custom blocks interacts with a player that is not the owner? -> leave name empty
|
||||
developmentEnvironmentUserName = "Developer"
|
||||
# Specify an MCP channel for dependency deobfuscation and the deobfParams task.
|
||||
channel = stable
|
||||
|
||||
# Define a source file of your project with:
|
||||
# public static final String VERSION = "GRADLETOKEN_VERSION";
|
||||
# The string's content will be replaced with your mods version when compiled. You should use this to specify your mod's
|
||||
# version in @Mod([...], version = VERSION, [...])
|
||||
# Leave these properties empty to skip individual token replacements
|
||||
replaceGradleTokenInFile = StructureLib.java
|
||||
# Specify an MCP mappings version for dependency deobfuscation and the deobfParams task.
|
||||
mappingsVersion = 12
|
||||
|
||||
# Defines other MCP mappings for dependency deobfuscation.
|
||||
remoteMappings = https\://raw.githubusercontent.com/MinecraftForge/FML/1.7.10/conf/
|
||||
|
||||
# Select a default username for testing your mod. You can always override this per-run by running
|
||||
# `./gradlew runClient --username=AnotherPlayer`, or configuring this command in your IDE.
|
||||
developmentEnvironmentUserName = Developer
|
||||
|
||||
# Enables modern Java syntax support. Valid values:
|
||||
# - false: No modern syntax, Java 8 only
|
||||
# - jabel: Jabel syntax-only support, compiles to J8 bytecode
|
||||
# - jvmDowngrader: Full modern Java via JVM Downgrader (syntax + stdlib APIs)
|
||||
# - modern: Native modern Java bytecode, no downgrading
|
||||
enableModernJavaSyntax = jabel
|
||||
|
||||
# If set, ignores the above setting and compiles with the given toolchain. This may cause unexpected issues,
|
||||
# and should *not* be used in most situations. -1 disables this.
|
||||
# forceToolchainVersion = -1
|
||||
|
||||
# Target JVM version for JVM Downgrader bytecode downgrading.
|
||||
# Only used when enableModernJavaSyntax = jvmDowngrader
|
||||
# downgradeTargetVersion = 8
|
||||
|
||||
# Comma-separated list of Java versions for multi-release jar support (JVM Downgrader only).
|
||||
# Classes will be available in META-INF/versions/N/ for each version N in this list.
|
||||
# Default: "21,25" (J25+ gets native classes, J21-24 gets partial downgrade, J8-20 gets full downgrade).
|
||||
# jvmDowngraderMultiReleaseVersions = 21,25
|
||||
|
||||
# Specifies how JVM Downgrader API stubs are provided. Options:
|
||||
# - shade: Shade minimized stubs into the jar
|
||||
# - gtnhlib: GTNHLib provides stubs at runtime (adds version constraint)
|
||||
# - external: Another dependency provides stubs (no constraint, no warning)
|
||||
# - (empty): Warning reminding you to configure stubs
|
||||
# Note: 'shade' option requires you to verify license compliance, see: https://github.com/unimined/JvmDowngrader/blob/main/LICENSE.md
|
||||
# jvmDowngraderStubsProvider =
|
||||
|
||||
# Enables injecting missing generics into the decompiled source code for a better coding experience.
|
||||
# Turns most publicly visible List, Map, etc. into proper List<E>, Map<K, V> types.
|
||||
enableGenericInjection = true
|
||||
|
||||
# Generate a class with a String field for the mod version named as defined below.
|
||||
# If generateGradleTokenClass is empty or not missing, no such class will be generated.
|
||||
# If gradleTokenVersion is empty or missing, the field will not be present in the class.
|
||||
generateGradleTokenClass = com.gtnewhorizon.structurelib.Tags
|
||||
|
||||
# Name of the token containing the project's current version to generate/replace.
|
||||
gradleTokenVersion = VERSION
|
||||
|
||||
# [DEPRECATED] Mod ID replacement token.
|
||||
gradleTokenModId =
|
||||
|
||||
# [DEPRECATED] Mod name replacement token.
|
||||
gradleTokenModName =
|
||||
gradleTokenVersion = GRADLETOKEN_VERSION
|
||||
|
||||
# [DEPRECATED] Mod Group replacement token.
|
||||
gradleTokenGroupName =
|
||||
|
||||
# In case your mod provides an API for other mods to implement you may declare its package here. Otherwise you can
|
||||
# [DEPRECATED]
|
||||
# Multiple source files can be defined here by providing a comma-separated list: Class1.java,Class2.java,Class3.java
|
||||
# public static final String VERSION = "GRADLETOKEN_VERSION";
|
||||
# The string's content will be replaced with your mod's version when compiled. You should use this to specify your mod's
|
||||
# version in @Mod([...], version = VERSION, [...]).
|
||||
# Leave these properties empty to skip individual token replacements.
|
||||
replaceGradleTokenInFile =
|
||||
|
||||
# In case your mod provides an API for other mods to implement you may declare its package here. Otherwise, you can
|
||||
# leave this property empty.
|
||||
# Example value: apiPackage = api + modGroup = com.myname.mymodid -> com.myname.mymodid.api
|
||||
# Example value: (apiPackage = api) + (modGroup = com.myname.mymodid) -> com.myname.mymodid.api
|
||||
apiPackage =
|
||||
|
||||
# Specify the configuration file for Forge's access transformers here. I must be placed into /src/main/resources/META-INF/
|
||||
# Example value: mymodid_at.cfg
|
||||
# Specify the configuration file for Forge's access transformers here. It must be placed into /src/main/resources/META-INF/
|
||||
# There can be multiple files in a space-separated list.
|
||||
# Example value: mymodid_at.cfg nei_at.cfg
|
||||
accessTransformersFile =
|
||||
|
||||
# Provides setup for Mixins if enabled. If you don't know what mixins are: Keep it disabled!
|
||||
usesMixins = false
|
||||
usesMixins = true
|
||||
|
||||
# Set to a non-empty string to configure mixins in a separate source set under src/VALUE, instead of src/main.
|
||||
# This can speed up compile times thanks to not running the mixin annotation processor on all input sources.
|
||||
# Mixin classes will have access to "main" classes, but not the other way around.
|
||||
separateMixinSourceSet =
|
||||
|
||||
# Adds some debug arguments like verbose output and class export.
|
||||
usesMixinDebug = false
|
||||
|
||||
# Specify the location of your implementation of IMixinConfigPlugin. Leave it empty otherwise.
|
||||
mixinPlugin =
|
||||
# Specify the package that contains all of your Mixins. You may only place Mixins in this package or the build will fail!
|
||||
mixinsPackage =
|
||||
|
||||
# Specify the package that contains all of your Mixins. The package must exist or
|
||||
# the build will fail. If you have a package property defined in your mixins.<modid>.json,
|
||||
# it must match with this or the build will fail.
|
||||
mixinsPackage = mixins
|
||||
|
||||
# Specify the core mod entry class if you use a core mod. This class must implement IFMLLoadingPlugin!
|
||||
# This parameter is for legacy compatability only
|
||||
# Example value: coreModClass = asm.FMLPlugin + modGroup = com.myname.mymodid -> com.myname.mymodid.asm.FMLPlugin
|
||||
coreModClass =
|
||||
# This parameter is for legacy compatibility only
|
||||
# Example value: (coreModClass = asm.FMLPlugin) + (modGroup = com.myname.mymodid) -> com.myname.mymodid.asm.FMLPlugin
|
||||
coreModClass = core.StructureLibCore
|
||||
|
||||
# If your project is only a consolidation of mixins or a core mod and does NOT contain a 'normal' mod ( = some class
|
||||
# that is annotated with @Mod) you want this to be true. When in doubt: leave it on false!
|
||||
containsMixinsAndOrCoreModOnly = false
|
||||
|
||||
# If enabled, you may use 'shadowImplementation' for dependencies. They will be integrated in your jar. It is your
|
||||
# responsibility check the licence and request permission for distribution, if required.
|
||||
# Enables Mixins even if this mod doesn't use them, useful if one of the dependencies uses mixins.
|
||||
forceEnableMixins = false
|
||||
|
||||
# If enabled, you may use 'shadowCompile' for dependencies. They will be integrated into your jar. It is your
|
||||
# responsibility to check the license and request permission for distribution if required.
|
||||
usesShadowedDependencies = false
|
||||
|
||||
# TODO: Remove when you modify this repository, it's been put in for automatic buildscript updates
|
||||
disableCheckstyle = true
|
||||
# If disabled, won't remove unused classes from shadowed dependencies. Some libraries use reflection to access
|
||||
# their own classes, making the minimization unreliable.
|
||||
minimizeShadowedDependencies = true
|
||||
|
||||
# If disabled, won't rename the shadowed classes.
|
||||
relocateShadowedDependencies = true
|
||||
|
||||
# Adds CurseMaven, Modrinth, and some more well-known 1.7.10 repositories.
|
||||
includeWellKnownRepositories = true
|
||||
|
||||
# A list of repositories to exclude from the includeWellKnownRepositories setting. Should be a space separated
|
||||
# list of strings, with the acceptable keys being(case does not matter):
|
||||
# cursemaven
|
||||
# modrinth
|
||||
excludeWellKnownRepositories =
|
||||
|
||||
# Change these to your Maven coordinates if you want to publish to a custom Maven repository instead of the default GTNH Maven.
|
||||
# Authenticate with the MAVEN_USER and MAVEN_PASSWORD environment variables.
|
||||
# If you need a more complex setup disable maven publishing here and add a publishing repository to addon.gradle.
|
||||
usesMavenPublishing = true
|
||||
|
||||
# Maven repository to publish the mod to.
|
||||
# mavenPublishUrl = https\://nexus.gtnewhorizons.com/repository/releases/
|
||||
|
||||
# Publishing to Modrinth requires you to set the MODRINTH_TOKEN environment variable to your current Modrinth API token.
|
||||
#
|
||||
# The project's ID on Modrinth. Can be either the slug or the ID.
|
||||
# Leave this empty if you don't want to publish to Modrinth.
|
||||
modrinthProjectId =
|
||||
|
||||
# The project's relations on Modrinth. You can use this to refer to other projects on Modrinth.
|
||||
# Syntax: scope1-type1:name1;scope2-type2:name2;...
|
||||
# Where scope can be one of [required, optional, incompatible, embedded],
|
||||
# type can be one of [project, version],
|
||||
# and the name is the Modrinth project or version slug/id of the other mod.
|
||||
# Example: required-project:fplib;optional-project:gasstation;incompatible-project:gregtech
|
||||
# Note: UniMixins is automatically set as a required dependency if usesMixins = true.
|
||||
modrinthRelations =
|
||||
|
||||
# Publishing to CurseForge requires you to set the CURSEFORGE_TOKEN environment variable to one of your CurseForge API tokens.
|
||||
#
|
||||
# The project's numeric ID on CurseForge. You can find this in the About Project box.
|
||||
# Leave this empty if you don't want to publish on CurseForge.
|
||||
curseForgeProjectId =
|
||||
|
||||
# The project's relations on CurseForge. You can use this to refer to other projects on CurseForge.
|
||||
# Syntax: type1:name1;type2:name2;...
|
||||
# Where type can be one of [requiredDependency, embeddedLibrary, optionalDependency, tool, incompatible],
|
||||
# and the name is the CurseForge project slug of the other mod.
|
||||
# Example: requiredDependency:railcraft;embeddedLibrary:cofhlib;incompatible:buildcraft
|
||||
# Note: UniMixins is automatically set as a required dependency if usesMixins = true.
|
||||
curseForgeRelations =
|
||||
|
||||
# Optional parameter to customize the produced artifacts. Use this to preserve artifact naming when migrating older
|
||||
# projects. New projects should not use this parameter.
|
||||
# customArchiveBaseName =
|
||||
|
||||
# Optional parameter to customize the default working directory used by the runClient* tasks. Relative to the project directory.
|
||||
# runClientWorkingDirectory = run/client
|
||||
|
||||
# Optional parameter to customize the default working directory used by the runServer* tasks. Relative to the project directory.
|
||||
# runServerWorkingDirectory = run/server
|
||||
|
||||
# Optional parameter to have the build automatically fail if an illegal version is used.
|
||||
# This can be useful if you e.g. only want to allow versions in the form of '1.1.xxx'.
|
||||
# The check is ONLY performed if the version is a git tag.
|
||||
# Note: the specified string must be escaped, so e.g. 1\\.1\\.\\d+ instead of 1\.1\.\d+
|
||||
# versionPattern =
|
||||
|
||||
# Uncomment to prevent the source code from being published.
|
||||
# noPublishedSources = true
|
||||
|
||||
# Uncomment this to disable Spotless checks.
|
||||
# This should only be uncommented to keep it easier to sync with upstream/other forks.
|
||||
# That is, if there is no other active fork/upstream, NEVER change this.
|
||||
# disableSpotless = true
|
||||
|
||||
# Uncomment this to disable Checkstyle checks (currently wildcard import check).
|
||||
# disableCheckstyle = true
|
||||
|
||||
# Override the IDEA build type. Valid values are: "" (leave blank, do not override), "idea" (force use native IDEA build), "gradle"
|
||||
# (force use delegated build).
|
||||
# This is meant to be set in $HOME/.gradle/gradle.properties.
|
||||
# e.g. add "systemProp.org.gradle.project.ideaOverrideBuildType=idea" will override the build type to be native build.
|
||||
# WARNING: If you do use this option, it will overwrite whatever you have in your existing projects. This might not be what you want!
|
||||
# Usually there is no need to uncomment this here as other developers do not necessarily use the same build type as you.
|
||||
# ideaOverrideBuildType = idea
|
||||
|
||||
# Whether IDEA should run spotless checks when pressing the Build button.
|
||||
# This is meant to be set in $HOME/.gradle/gradle.properties.
|
||||
# ideaCheckSpotlessOnBuild = true
|
||||
|
||||
# Non-GTNH properties
|
||||
org.gradle.configuration-cache = true
|
||||
org.gradle.parallel = true
|
||||
@@ -0,0 +1,12 @@
|
||||
#This file is generated by updateDaemonJvm
|
||||
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/1630f7ebef05444cb27a2709ea0249b3/redirect
|
||||
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
|
||||
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/1630f7ebef05444cb27a2709ea0249b3/redirect
|
||||
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
|
||||
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/d4fd992c9557644e637ebe98263e0ae7/redirect
|
||||
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/faa12903720d410b387cc69ccafb1a74/redirect
|
||||
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/1630f7ebef05444cb27a2709ea0249b3/redirect
|
||||
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/cd495626d2ee49a75447e3fdc6afb287/redirect
|
||||
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/a4c09dd2e2d7079373d30e524bbc2829/redirect
|
||||
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/8e1d9ee5d0f13e442218f6884a306da1/redirect
|
||||
toolchainVersion=25
|
||||
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
# 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.
|
||||
@@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -55,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -84,7 +86,7 @@ done
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
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
|
||||
@@ -112,7 +114,6 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -170,7 +171,6 @@ fi
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
@@ -203,15 +203,14 @@ fi
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# * 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" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
@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 ##########################################################################
|
||||
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
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
|
||||
|
||||
@@ -57,22 +59,21 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
"%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
|
||||
|
||||
@@ -6,6 +6,7 @@ pluginManagement {
|
||||
name "GTNH Maven"
|
||||
url "https://nexus.gtnewhorizons.com/repository/public/"
|
||||
mavenContent {
|
||||
includeGroup("com.gtnewhorizons")
|
||||
includeGroupByRegex("com\\.gtnewhorizons\\..+")
|
||||
}
|
||||
}
|
||||
@@ -16,12 +17,7 @@ pluginManagement {
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'com.diffplug.blowdryerSetup' version '1.7.1'
|
||||
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0' // Provides java toolchains
|
||||
id 'com.gtnewhorizons.gtnhsettingsconvention' version '2.0.24'
|
||||
}
|
||||
|
||||
blowdryerSetup {
|
||||
repoSubfolder 'gtnhShared'
|
||||
github('GTNewHorizons/ExampleMod1.7.10', 'tag', '0.2.0')
|
||||
//devLocal '.' // Use this when testing config updates locally
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.gtnewhorizon.structurelib;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.util.map.ItemStackMap;
|
||||
|
||||
import cpw.mods.fml.common.Loader;
|
||||
|
||||
// this is not an API! go check StructureLibAPI instead!
|
||||
public class ChannelDescription {
|
||||
|
||||
private static final ItemStackMap<Set<ChannelDescription>> itemToChannels = new ItemStackMap<>(true);
|
||||
private static final Map<String, ChannelDescription> registry = new HashMap<>();
|
||||
private final String channel;
|
||||
private final Map<String, String> descriptions = new HashMap<>();
|
||||
private final ItemStackMap<Integer> items = new ItemStackMap<>(true);
|
||||
|
||||
public static ChannelDescription get(final String channel) {
|
||||
if (StructureLibAPI.isDebugEnabled() && !channel.toLowerCase(Locale.ROOT).equals(channel))
|
||||
throw new IllegalArgumentException("Channel name can be lower case ONLY");
|
||||
return registry.computeIfAbsent(channel, ChannelDescription::new);
|
||||
}
|
||||
|
||||
public static boolean has(final String channel) {
|
||||
if (StructureLibAPI.isDebugEnabled() && !channel.toLowerCase(Locale.ROOT).equals(channel))
|
||||
throw new IllegalArgumentException("Channel name can be lower case ONLY");
|
||||
return registry.containsKey(channel);
|
||||
}
|
||||
|
||||
public static Collection<Map.Entry<String, Integer>> iterate(final ItemStack block) {
|
||||
Set<ChannelDescription> channels = itemToChannels.get(block);
|
||||
if (channels == null) return Collections.emptySet();
|
||||
return channels.stream().map(d -> new AbstractMap.SimpleImmutableEntry<>(d.channel, d.items.get(block)))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static void set(final String channel, final String modid, final String description) {
|
||||
if (StructureLibAPI.isDebugEnabled() && !channel.toLowerCase(Locale.ROOT).equals(channel))
|
||||
throw new IllegalArgumentException("Channel name can be lower case ONLY");
|
||||
registry.computeIfAbsent(channel, ChannelDescription::new).add(modid, description);
|
||||
}
|
||||
|
||||
public static void item(final String channel, int channelValue, ItemStack stack) {
|
||||
if (StructureLibAPI.isDebugEnabled()) {
|
||||
if (!channel.toLowerCase(Locale.ROOT).equals(channel)) {
|
||||
throw new IllegalArgumentException("Channel name can be lower case ONLY");
|
||||
}
|
||||
if (channelValue <= 0) {
|
||||
throw new IllegalArgumentException("Channel value must be greater than 0");
|
||||
}
|
||||
}
|
||||
ChannelDescription inst = registry.computeIfAbsent(channel, ChannelDescription::new);
|
||||
inst.items.put(stack, channelValue);
|
||||
itemToChannels.computeIfAbsent(stack, s -> new HashSet<>()).add(inst);
|
||||
}
|
||||
|
||||
public static Map<String, ChannelDescription> getAll() {
|
||||
return Collections.unmodifiableMap(registry);
|
||||
}
|
||||
|
||||
private ChannelDescription(String channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
public String getChannel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
public ChannelDescription add(String modid, String description) {
|
||||
if (Loader.instance().getIndexedModList().get(modid) == null) {
|
||||
throw new IllegalArgumentException("Mod not found: " + modid);
|
||||
}
|
||||
descriptions.put(modid, description);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, String> getDescriptions() {
|
||||
return Collections.unmodifiableMap(descriptions);
|
||||
}
|
||||
|
||||
public Map<ItemStack, Integer> getItems() {
|
||||
return Collections.unmodifiableMap(items);
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,24 @@ package com.gtnewhorizon.structurelib;
|
||||
|
||||
import static com.gtnewhorizon.structurelib.StructureLib.RANDOM;
|
||||
|
||||
import java.util.*;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.entity.EntityClientPlayerMP;
|
||||
import net.minecraft.client.gui.GuiNewChat;
|
||||
import net.minecraft.client.particle.EntityFX;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.client.renderer.culling.Frustrum;
|
||||
import net.minecraft.client.renderer.texture.TextureMap;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
@@ -23,18 +32,27 @@ import net.minecraft.util.Vec3;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.client.event.RenderWorldLastEvent;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.common.config.ConfigElement;
|
||||
import net.minecraftforge.event.entity.EntityJoinWorldEvent;
|
||||
import net.minecraftforge.event.world.WorldEvent;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import com.gtnewhorizon.structurelib.entity.fx.WeightlessParticleFX;
|
||||
import com.gtnewhorizon.structurelib.net.RegistryOrderSyncMessage;
|
||||
import com.gtnewhorizon.structurelib.net.SetChannelDataMessage;
|
||||
|
||||
import cpw.mods.fml.client.config.GuiConfig;
|
||||
import cpw.mods.fml.client.event.ConfigChangedEvent;
|
||||
import cpw.mods.fml.common.FMLCommonHandler;
|
||||
import cpw.mods.fml.common.event.FMLLoadCompleteEvent;
|
||||
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
|
||||
import cpw.mods.fml.common.eventhandler.EventPriority;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import cpw.mods.fml.common.gameevent.TickEvent.ClientTickEvent;
|
||||
import cpw.mods.fml.common.gameevent.TickEvent.Phase;
|
||||
import cpw.mods.fml.common.network.FMLNetworkEvent;
|
||||
|
||||
public class ClientProxy extends CommonProxy {
|
||||
|
||||
@@ -246,6 +264,47 @@ public class ClientProxy extends CommonProxy {
|
||||
((IStructureCompat) StructureLib.COMPAT).markTextureUsed(icon);
|
||||
}
|
||||
|
||||
static void sendRegistryOrderToServer() {
|
||||
if (FMLEventHandler.configSent) return;
|
||||
FMLEventHandler.configSent = true;
|
||||
for (String s : SortedRegistry.ALL_REGISTRIES.keySet()) {
|
||||
Pair<List<String>, List<String>> registryOrder = ConfigurationHandler.INSTANCE.getRegistryOrder(s);
|
||||
if (registryOrder == null || registryOrder.getKey().isEmpty() && registryOrder.getValue().isEmpty())
|
||||
continue;
|
||||
StructureLib.net
|
||||
.sendToServer(new RegistryOrderSyncMessage(s, registryOrder.getKey(), registryOrder.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadComplete(FMLLoadCompleteEvent evt) {
|
||||
super.loadComplete(evt);
|
||||
for (Map.Entry<String, WeakReference<SortedRegistry<?>>> e : SortedRegistry.ALL_REGISTRIES.entrySet()) {
|
||||
SortedRegistry<?> r = e.getValue().get();
|
||||
if (r == null) continue;
|
||||
Pair<List<String>, List<String>> p = ConfigurationHandler.INSTANCE.getRegistryOrder(e.getKey());
|
||||
r.registerOrdering(getSPPlayerUUID(), p.getKey(), p.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public UUID getSPPlayerUUID() {
|
||||
return EntityPlayer.func_146094_a(Minecraft.getMinecraft().getSession().func_148256_e());
|
||||
}
|
||||
|
||||
public void displayConfigGUI(String category) {
|
||||
ConfigElement<Object> element = new ConfigElement<>(
|
||||
ConfigurationHandler.INSTANCE.getConfig().getCategory(category));
|
||||
GuiConfig guiConfig = new GuiConfig(
|
||||
null,
|
||||
element.getChildElements(),
|
||||
StructureLibAPI.MOD_ID,
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
I18n.format(element.getLanguageKey()));
|
||||
Minecraft.getMinecraft().displayGuiScreen(guiConfig);
|
||||
}
|
||||
|
||||
private static class HintGroup {
|
||||
|
||||
private final List<HintParticleInfo> hints = new LinkedList<>();
|
||||
@@ -434,12 +493,34 @@ public class ClientProxy extends CommonProxy {
|
||||
|
||||
public static class FMLEventHandler {
|
||||
|
||||
static boolean connected = false;
|
||||
static boolean configSent = false;
|
||||
|
||||
private void resetPlayerLocation() {
|
||||
lastPlayerPos.xCoord = Minecraft.getMinecraft().thePlayer.posX;
|
||||
lastPlayerPos.yCoord = Minecraft.getMinecraft().thePlayer.posY;
|
||||
lastPlayerPos.zCoord = Minecraft.getMinecraft().thePlayer.posZ;
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onPlayerLogIn(FMLNetworkEvent.ClientConnectedToServerEvent e) {
|
||||
connected = true;
|
||||
configSent = false;
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onPlayerLogOut(FMLNetworkEvent.ClientDisconnectionFromServerEvent e) {
|
||||
connected = false;
|
||||
}
|
||||
|
||||
@SubscribeEvent(priority = EventPriority.LOWEST)
|
||||
public void onConfigChange(ConfigChangedEvent.PostConfigChangedEvent e) {
|
||||
if (connected && e.modID.equals(StructureLibAPI.MOD_ID)) {
|
||||
configSent = false;
|
||||
sendRegistryOrderToServer();
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onClientTick(ClientTickEvent e) {
|
||||
if (e.phase == Phase.END && Minecraft.getMinecraft().thePlayer != null) {
|
||||
@@ -483,6 +564,13 @@ public class ClientProxy extends CommonProxy {
|
||||
|
||||
public static class ForgeEventHandler {
|
||||
|
||||
@SubscribeEvent
|
||||
public void onEntityJoinWorld(EntityJoinWorldEvent e) {
|
||||
if (e.world.isRemote && e.entity instanceof EntityClientPlayerMP) {
|
||||
sendRegistryOrderToServer();
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onWorldLoad(WorldEvent.Load e) {
|
||||
if (e.world.isRemote) {
|
||||
|
||||
@@ -12,14 +12,23 @@ import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.util.IChatComponent;
|
||||
import net.minecraft.util.IIcon;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
|
||||
import com.gtnewhorizon.structurelib.net.ErrorHintParticleMessage;
|
||||
import com.gtnewhorizon.structurelib.net.UpdateHintParticleMessage;
|
||||
|
||||
import cpw.mods.fml.common.event.FMLLoadCompleteEvent;
|
||||
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import cpw.mods.fml.common.gameevent.TickEvent;
|
||||
|
||||
public class CommonProxy {
|
||||
|
||||
public CommonProxy() {
|
||||
super();
|
||||
MinecraftForge.EVENT_BUS.register(new ForgeEventHandler());
|
||||
}
|
||||
|
||||
public void hintParticleTinted(World w, int x, int y, int z, IIcon[] icons, short[] RGBa) {}
|
||||
|
||||
public void hintParticleTinted(World w, int x, int y, int z, Block block, int meta, short[] RGBa) {}
|
||||
@@ -70,6 +79,10 @@ public class CommonProxy {
|
||||
}
|
||||
}
|
||||
|
||||
public void loadComplete(FMLLoadCompleteEvent e) {
|
||||
ConfigurationHandler.INSTANCE.loadRegistryOrder();
|
||||
}
|
||||
|
||||
private final Map<EntityPlayerMP, Map<Object, Long>> throttleMap = new WeakHashMap<>();
|
||||
|
||||
public void addThrottledChat(Object throttleKey, EntityPlayer player, IChatComponent text, short intervalRequired,
|
||||
@@ -94,4 +107,22 @@ public class CommonProxy {
|
||||
if (!forceUpdateLastSend) submap.put(throttleKey, now);
|
||||
}
|
||||
}
|
||||
|
||||
public void displayConfigGUI(String category) {}
|
||||
|
||||
public static class ForgeEventHandler {
|
||||
|
||||
private int ticksSinceLastPurge;
|
||||
|
||||
@SubscribeEvent
|
||||
public void onServerTickEnd(TickEvent.ServerTickEvent e) {
|
||||
if (e.phase != TickEvent.Phase.END) return;
|
||||
if (++ticksSinceLastPurge < 20 * 60 * 10) return;
|
||||
ticksSinceLastPurge = 0;
|
||||
MinecraftServer server = MinecraftServer.getServer();
|
||||
// it's hard to tell why server could be null in a server tick handler, but who knows?
|
||||
if (server == null) return;
|
||||
SortedRegistry.cleanup(server);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,26 @@
|
||||
package com.gtnewhorizon.structurelib;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.minecraftforge.common.config.ConfigCategory;
|
||||
import net.minecraftforge.common.config.Configuration;
|
||||
import net.minecraftforge.common.config.Property;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import cpw.mods.fml.client.event.ConfigChangedEvent;
|
||||
import cpw.mods.fml.common.FMLCommonHandler;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import cpw.mods.fml.relauncher.FMLLaunchHandler;
|
||||
|
||||
public enum ConfigurationHandler {
|
||||
|
||||
@@ -22,6 +33,7 @@ public enum ConfigurationHandler {
|
||||
private int hintTransparency;
|
||||
private int autoPlaceBudget;
|
||||
private int autoPlaceInterval;
|
||||
private Map<String, Pair<List<String>, List<String>>> registryOrders;
|
||||
|
||||
ConfigurationHandler() {
|
||||
FMLCommonHandler.instance().bus().register(this);
|
||||
@@ -86,8 +98,50 @@ public enum ConfigurationHandler {
|
||||
+ "Note this relates to the wall clock, not in game ticks.\n"
|
||||
+ "Value smaller than default is likely to be perceived as no minimal interval whatsoever.");
|
||||
|
||||
loadRegistryOrder();
|
||||
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
void loadRegistryOrder() {
|
||||
loadRegistryOrderImpl();
|
||||
setLanguageKeys();
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
private void loadRegistryOrderImpl() {
|
||||
registryOrders = new HashMap<>();
|
||||
for (Map.Entry<String, WeakReference<SortedRegistry<?>>> e : SortedRegistry.ALL_REGISTRIES.entrySet()) {
|
||||
SortedRegistry<?> r = e.getValue().get();
|
||||
if (r == null) continue;
|
||||
String category = "registries." + e.getKey();
|
||||
if (FMLLaunchHandler.side().isClient()) {
|
||||
// only meaningful on client, and would crash on server
|
||||
config.setCategoryConfigEntryClass(category, RegistryOrderEntry.class);
|
||||
}
|
||||
Property pOrder = config.get(
|
||||
category,
|
||||
"ordering",
|
||||
Iterables.toArray(r.getCurrentOrdering(), String.class),
|
||||
"stuff not in this list will be automatically available after all entries listed here in their natural order, unless explicitly disabled in disabled config below.");
|
||||
Property pDisable = config.get(category, "disabled", new String[0], "stuff in this list will be disabled");
|
||||
List<String> all = Lists.newArrayList(r.getCurrentOrdering());
|
||||
List<String> curVal = new ArrayList<>(Arrays.asList(pOrder.getStringList()));
|
||||
List<String> disabled = new ArrayList<>(Arrays.asList(pDisable.getStringList()));
|
||||
curVal.removeAll(disabled);
|
||||
all.removeAll(disabled);
|
||||
curVal.removeIf(s -> !all.remove(s));
|
||||
curVal.addAll(all);
|
||||
pOrder.set(curVal.toArray(new String[0]));
|
||||
registryOrders.put(e.getKey(), Pair.of(curVal, disabled));
|
||||
}
|
||||
saveConfig();
|
||||
}
|
||||
|
||||
private void saveConfig() {
|
||||
if (config.hasChanged()) {
|
||||
config.save();
|
||||
config.load();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +176,12 @@ public enum ConfigurationHandler {
|
||||
return autoPlaceInterval;
|
||||
}
|
||||
|
||||
public Pair<List<String>, List<String>> getRegistryOrder(String name) {
|
||||
return registryOrders.get(name);
|
||||
}
|
||||
|
||||
Configuration getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package com.gtnewhorizon.structurelib;
|
||||
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
|
||||
import cpw.mods.fml.common.event.FMLInterModComms;
|
||||
|
||||
public class DevelopHelper {
|
||||
|
||||
private static final boolean DEBUG = Boolean.getBoolean("structurelib.develop_helper");
|
||||
|
||||
public static void onPreInit() {
|
||||
if (!DEBUG) return;
|
||||
StructureLib.LOGGER.info("Running develop helper");
|
||||
StructureLibAPI.registerChannelDescription("sandstone", StructureLibAPI.MOD_ID, "variants of Sandstone");
|
||||
StructureLibAPI.registerChannelItem("sandstone", StructureLibAPI.MOD_ID, 1, new ItemStack(Blocks.sandstone));
|
||||
StructureLibAPI
|
||||
.registerChannelItem("sandstone", StructureLibAPI.MOD_ID, 2, new ItemStack(Blocks.sandstone, 1, 1));
|
||||
StructureLibAPI
|
||||
.registerChannelItem("sandstone", StructureLibAPI.MOD_ID, 3, new ItemStack(Blocks.sandstone, 1, 2));
|
||||
StructureLibAPI.registerChannelDescription("sandbricks", StructureLibAPI.MOD_ID, "variants of Sandbricks");
|
||||
StructureLibAPI.registerChannelDescription("apple", StructureLibAPI.MOD_ID, "apple");
|
||||
StructureLibAPI.registerChannelDescription("bright", StructureLibAPI.MOD_ID, "bright");
|
||||
StructureLibAPI.registerChannelDescription("apple_blight", StructureLibAPI.MOD_ID, "apple_blight");
|
||||
StructureLibAPI.registerChannelDescription("app_eagle", StructureLibAPI.MOD_ID, "app_eagle");
|
||||
}
|
||||
|
||||
public void x() {
|
||||
NBTTagCompound command = new NBTTagCompound();
|
||||
NBTTagList items = new NBTTagList();
|
||||
int[] metas = { 0, 1, 2 };
|
||||
for (int meta : metas) {
|
||||
NBTTagCompound tag = new NBTTagCompound();
|
||||
tag.setTag("Item", new ItemStack(Blocks.sandstone, 1, meta).writeToNBT(new NBTTagCompound()));
|
||||
NBTTagList channels = new NBTTagList();
|
||||
NBTTagCompound channel = new NBTTagCompound();
|
||||
channel.setString("Channel", "sandstone");
|
||||
channel.setInteger("Value", meta + 1);
|
||||
channels.appendTag(channel);
|
||||
tag.setTag("Channels", channels);
|
||||
items.appendTag(tag);
|
||||
}
|
||||
command.setTag("Items", items);
|
||||
FMLInterModComms.sendMessage("structurelib", "register_channel_item", command);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,702 @@
|
||||
/*
|
||||
* Forge Mod Loader Copyright (c) 2012-2014 cpw. All rights reserved. This program and the accompanying materials are
|
||||
* made available under the terms of the GNU Lesser Public License v2.1 which accompanies this distribution, and is
|
||||
* available at http://www.gnu.org/licenses/old-licenses/gpl-2.0.html Contributors (this class): bspkrs - implementation
|
||||
*/
|
||||
|
||||
package com.gtnewhorizon.structurelib;
|
||||
|
||||
import static cpw.mods.fml.client.config.GuiUtils.RESET_CHAR;
|
||||
import static cpw.mods.fml.client.config.GuiUtils.UNDO_CHAR;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import net.minecraft.client.gui.GuiListExtended;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import net.minecraft.client.gui.ScaledResolution;
|
||||
import net.minecraft.client.renderer.OpenGlHelper;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
import net.minecraft.util.EnumChatFormatting;
|
||||
import net.minecraft.util.MathHelper;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
import org.lwjgl.input.Keyboard;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import cpw.mods.fml.client.config.GuiButtonExt;
|
||||
import cpw.mods.fml.client.config.GuiConfig;
|
||||
import cpw.mods.fml.client.config.GuiUnicodeGlyphButton;
|
||||
import cpw.mods.fml.client.config.GuiUtils;
|
||||
import cpw.mods.fml.client.config.HoverChecker;
|
||||
import cpw.mods.fml.client.config.IConfigElement;
|
||||
import cpw.mods.fml.client.event.ConfigChangedEvent;
|
||||
import cpw.mods.fml.client.event.ConfigChangedEvent.OnConfigChangedEvent;
|
||||
import cpw.mods.fml.client.event.ConfigChangedEvent.PostConfigChangedEvent;
|
||||
import cpw.mods.fml.common.FMLCommonHandler;
|
||||
import cpw.mods.fml.common.Loader;
|
||||
import cpw.mods.fml.common.eventhandler.Event.Result;
|
||||
|
||||
/**
|
||||
* This class is the base GuiScreen for all config GUI screens. It can be extended by mods to provide the top-level
|
||||
* config screen that will be called when the Config button is clicked from the Main Menu Mods list.
|
||||
*
|
||||
* @author bspkrs
|
||||
*/
|
||||
public class GuiEditRegistryConfig extends GuiScreen {
|
||||
|
||||
public static final String OMITTED = "..";
|
||||
public static final int LIST_WINDOW_PADDING = 20;
|
||||
public static final int MID_SECTION_WIDTH = 10;
|
||||
/**
|
||||
* A reference to the screen object that created this. Used for navigating between screens.
|
||||
*/
|
||||
public final GuiScreen parentScreen;
|
||||
private final IConfigElement<String> disabledElement;
|
||||
private final String[] beforeDisabledValue;
|
||||
private final IConfigElement<String> orderElement;
|
||||
private final String[] beforeOrderValue;
|
||||
final Collection<EntriesList> lists;
|
||||
private final String registryName;
|
||||
EntriesList disabledEntries;
|
||||
EntriesList orderEntries;
|
||||
public String title = "Config GUI";
|
||||
public String titleLine2;
|
||||
private GuiButtonExt btnDefault;
|
||||
private GuiButtonExt btnUndoAll;
|
||||
public final String modID;
|
||||
/**
|
||||
* When set to a non-null value the OnConfigChanged and PostConfigChanged events will be posted when the Done button
|
||||
* is pressed if any configElements were changed (includes child screens). If not defined, the events will be posted
|
||||
* if the parent gui is null or if the parent gui is not an instance of GuiConfig.
|
||||
*/
|
||||
public final String configID;
|
||||
public final boolean isWorldRunning;
|
||||
public final boolean allRequireWorldRestart;
|
||||
public final boolean allRequireMcRestart;
|
||||
public boolean needsRefresh = true;
|
||||
private HoverChecker undoHoverChecker;
|
||||
private HoverChecker resetHoverChecker;
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public GuiEditRegistryConfig(GuiScreen parentScreen, IConfigElement disabled, IConfigElement order,
|
||||
String registryName, String modID, String configID, boolean allRequireWorldRestart,
|
||||
boolean allRequireMcRestart, String title, String titleLine2) {
|
||||
this.mc = parentScreen.mc;
|
||||
this.lists = Collections.newSetFromMap(new WeakHashMap<>());
|
||||
this.disabledElement = disabled;
|
||||
this.beforeDisabledValue = (String[]) this.disabledElement.getList();
|
||||
this.orderElement = order;
|
||||
this.beforeOrderValue = (String[]) this.orderElement.getList();
|
||||
this.parentScreen = parentScreen;
|
||||
this.allRequireWorldRestart = allRequireWorldRestart;
|
||||
this.allRequireMcRestart = allRequireMcRestart;
|
||||
this.registryName = registryName;
|
||||
this.modID = modID;
|
||||
this.configID = configID;
|
||||
this.isWorldRunning = mc.theWorld != null;
|
||||
if (title != null) this.title = title;
|
||||
this.titleLine2 = titleLine2;
|
||||
if (this.titleLine2 != null && this.titleLine2.startsWith(" > "))
|
||||
this.titleLine2 = this.titleLine2.replaceFirst(" > ", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the buttons (and other controls) to the screen in question.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void initGui() {
|
||||
Keyboard.enableRepeatEvents(true);
|
||||
if (this.disabledEntries == null || this.needsRefresh) {
|
||||
this.disabledEntries = new EntriesList(
|
||||
(width - MID_SECTION_WIDTH) / 2 - LIST_WINDOW_PADDING,
|
||||
LIST_WINDOW_PADDING,
|
||||
I18n.format("structurelib.configgui.disabled"));
|
||||
this.disabledEntries.initEntries(Arrays.asList(disabledElement.getList()));
|
||||
} else {
|
||||
this.disabledEntries.reinit((width - MID_SECTION_WIDTH) / 2 - LIST_WINDOW_PADDING, LIST_WINDOW_PADDING);
|
||||
}
|
||||
if (this.orderEntries == null || this.needsRefresh) {
|
||||
this.orderEntries = new EntriesList(
|
||||
(width - MID_SECTION_WIDTH) / 2 - LIST_WINDOW_PADDING,
|
||||
(width + MID_SECTION_WIDTH) / 2,
|
||||
I18n.format("structurelib.configgui.enabled"));
|
||||
this.orderEntries.initEntries(Arrays.asList(orderElement.getList()));
|
||||
} else {
|
||||
this.orderEntries
|
||||
.reinit((width - MID_SECTION_WIDTH) / 2 - LIST_WINDOW_PADDING, (width + MID_SECTION_WIDTH) / 2);
|
||||
}
|
||||
this.needsRefresh = false;
|
||||
|
||||
int undoGlyphWidth = mc.fontRenderer.getStringWidth(UNDO_CHAR) * 2;
|
||||
int resetGlyphWidth = mc.fontRenderer.getStringWidth(RESET_CHAR) * 2;
|
||||
int doneWidth = Math.max(mc.fontRenderer.getStringWidth(I18n.format("gui.done")) + 20, 100);
|
||||
int undoWidth = mc.fontRenderer.getStringWidth(" " + I18n.format("fml.configgui.tooltip.undoChanges"))
|
||||
+ undoGlyphWidth
|
||||
+ 20;
|
||||
int resetWidth = mc.fontRenderer.getStringWidth(" " + I18n.format("fml.configgui.tooltip.resetToDefault"))
|
||||
+ resetGlyphWidth
|
||||
+ 20;
|
||||
int buttonWidthHalf = (doneWidth + 5 + undoWidth + 5 + resetWidth + 5) / 2;
|
||||
this.buttonList.add(
|
||||
new GuiButtonExt(
|
||||
2000,
|
||||
this.width / 2 - buttonWidthHalf,
|
||||
this.height - 29,
|
||||
doneWidth,
|
||||
20,
|
||||
I18n.format("gui.done")));
|
||||
this.buttonList.add(
|
||||
this.btnDefault = new GuiUnicodeGlyphButton(
|
||||
2001,
|
||||
this.width / 2 - buttonWidthHalf + doneWidth + 5 + undoWidth + 5,
|
||||
this.height - 29,
|
||||
resetWidth,
|
||||
20,
|
||||
" " + I18n.format("fml.configgui.tooltip.resetToDefault"),
|
||||
RESET_CHAR,
|
||||
2.0F));
|
||||
this.buttonList.add(
|
||||
btnUndoAll = new GuiUnicodeGlyphButton(
|
||||
2002,
|
||||
this.width / 2 - buttonWidthHalf + doneWidth + 5,
|
||||
this.height - 29,
|
||||
undoWidth,
|
||||
20,
|
||||
" " + I18n.format("fml.configgui.tooltip.undoChanges"),
|
||||
UNDO_CHAR,
|
||||
2.0F));
|
||||
|
||||
this.undoHoverChecker = new HoverChecker(this.btnUndoAll, 800);
|
||||
this.resetHoverChecker = new HoverChecker(this.btnDefault, 800);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the screen is unloaded. Used to disable keyboard repeat events
|
||||
*/
|
||||
@Override
|
||||
public void onGuiClosed() {
|
||||
if (this.configID != null && this.parentScreen instanceof GuiConfig) {
|
||||
GuiConfig parentGuiConfig = (GuiConfig) this.parentScreen;
|
||||
parentGuiConfig.needsRefresh = true;
|
||||
parentGuiConfig.initGui();
|
||||
}
|
||||
|
||||
if (!(this.parentScreen instanceof GuiConfig)) Keyboard.enableRepeatEvents(false);
|
||||
}
|
||||
|
||||
public boolean saveConfigElements() {
|
||||
if (isChanged()) {
|
||||
disabledElement.set(disabledEntries.entries.stream().map(e -> e.name).toArray(String[]::new));
|
||||
orderElement.set(orderEntries.entries.stream().map(e -> e.name).toArray(String[]::new));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void actionPerformed(GuiButton button) {
|
||||
if (button.id == 2000) {
|
||||
try {
|
||||
if ((configID != null || this.parentScreen == null || !(this.parentScreen instanceof GuiConfig))
|
||||
&& isChanged()) {
|
||||
saveConfigElements();
|
||||
if (Loader.isModLoaded(modID)) {
|
||||
ConfigChangedEvent event = new OnConfigChangedEvent(modID, configID, isWorldRunning, false);
|
||||
FMLCommonHandler.instance().bus().post(event);
|
||||
if (!event.getResult().equals(Result.DENY)) FMLCommonHandler.instance().bus()
|
||||
.post(new PostConfigChangedEvent(modID, configID, isWorldRunning, false));
|
||||
|
||||
if (this.parentScreen instanceof GuiConfig) ((GuiConfig) this.parentScreen).needsRefresh = true;
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
this.mc.displayGuiScreen(this.parentScreen);
|
||||
} else if (button.id == 2001) {
|
||||
setToDefault();
|
||||
} else if (button.id == 2002) {
|
||||
undoChanges();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInitialized() {
|
||||
return disabledEntries != null && orderEntries != null;
|
||||
}
|
||||
|
||||
public void undoChanges() {
|
||||
disabledEntries.initEntries(Arrays.asList(beforeDisabledValue));
|
||||
orderEntries.initEntries(Arrays.asList(beforeOrderValue));
|
||||
}
|
||||
|
||||
public void setToDefault() {
|
||||
disabledEntries.initEntries(Arrays.asList(disabledElement.getList()));
|
||||
orderEntries.initEntries(Arrays.asList(orderElement.getList()));
|
||||
}
|
||||
|
||||
public boolean isDefault() {
|
||||
return !orderEntries.areContentEqual(orderElement.getDefaults())
|
||||
|| !disabledEntries.areContentEqual(disabledElement.getDefaults());
|
||||
}
|
||||
|
||||
public boolean isChanged() {
|
||||
return !orderEntries.areContentEqual(beforeOrderValue) || !disabledEntries.areContentEqual(beforeDisabledValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse is clicked.
|
||||
*/
|
||||
@Override
|
||||
protected void mouseClicked(int x, int y, int mouseEvent) {
|
||||
if (mouseEvent != 0 || !this.disabledEntries.func_148179_a(x, y, mouseEvent)
|
||||
&& !this.orderEntries.func_148179_a(x, y, mouseEvent)) {
|
||||
super.mouseClicked(x, y, mouseEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the mouse is moved or a mouse button is released. Signature: (mouseX, mouseY, which) which==-1 is
|
||||
* mouseMove, which==0 or which==1 is mouseUp
|
||||
*/
|
||||
@Override
|
||||
protected void mouseMovedOrUp(int x, int y, int mouseEvent) {
|
||||
if (mouseEvent != 0 || !this.disabledEntries.func_148181_b(x, y, mouseEvent)
|
||||
&& !this.orderEntries.func_148181_b(x, y, mouseEvent)) {
|
||||
super.mouseMovedOrUp(x, y, mouseEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired when a key is typed. This is the equivalent of KeyListener.keyTyped(KeyEvent e).
|
||||
*/
|
||||
@Override
|
||||
protected void keyTyped(char eventChar, int eventKey) {
|
||||
if (eventKey == Keyboard.KEY_ESCAPE) this.mc.displayGuiScreen(parentScreen);
|
||||
}
|
||||
|
||||
String trimStringToWidth(String str, int width) {
|
||||
int strwidth = mc.fontRenderer.getStringWidth(str);
|
||||
int elipsisWidth = mc.fontRenderer.getStringWidth(OMITTED);
|
||||
if (strwidth > width - elipsisWidth && elipsisWidth > strwidth) {
|
||||
return mc.fontRenderer.trimStringToWidth(str, width - elipsisWidth).trim() + OMITTED;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the screen and all the components in it.
|
||||
*/
|
||||
@Override
|
||||
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
|
||||
for (EntriesList list : lists) {
|
||||
list.handleDrag(mouseX, mouseY, partialTicks);
|
||||
}
|
||||
this.drawDefaultBackground();
|
||||
for (EntriesList list : lists) {
|
||||
list.drawScreen(mouseX, mouseY, partialTicks);
|
||||
}
|
||||
this.drawCenteredString(this.fontRendererObj, this.title, this.width / 2, 8, 16777215);
|
||||
String title2 = this.titleLine2;
|
||||
|
||||
if (title2 != null) {
|
||||
title2 = trimStringToWidth(title2, width - 6);
|
||||
this.drawCenteredString(this.fontRendererObj, title2, this.width / 2, 18, 16777215);
|
||||
}
|
||||
|
||||
this.btnUndoAll.enabled = isChanged();
|
||||
this.btnDefault.enabled = isDefault();
|
||||
super.drawScreen(mouseX, mouseY, partialTicks);
|
||||
for (EntriesList list : lists) {
|
||||
list.drawScreenPost(mouseX, mouseY, partialTicks);
|
||||
}
|
||||
if (this.undoHoverChecker.checkHover(mouseX, mouseY)) this.drawToolTip(
|
||||
this.mc.fontRenderer.listFormattedStringToWidth(I18n.format("fml.configgui.tooltip.undoAll"), 300),
|
||||
mouseX,
|
||||
mouseY);
|
||||
if (this.resetHoverChecker.checkHover(mouseX, mouseY)) this.drawToolTip(
|
||||
this.mc.fontRenderer.listFormattedStringToWidth(I18n.format("fml.configgui.tooltip.resetAll"), 300),
|
||||
mouseX,
|
||||
mouseY);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public void drawToolTip(List stringList, int x, int y) {
|
||||
this.func_146283_a(stringList, x, y);
|
||||
}
|
||||
|
||||
private class EntriesList extends GuiListExtended {
|
||||
|
||||
final String heading;
|
||||
final List<ListEntry> entries = new ArrayList<>();
|
||||
int controlX;
|
||||
|
||||
public EntriesList(int width, int left, String heading) {
|
||||
super(
|
||||
mc,
|
||||
width,
|
||||
GuiEditRegistryConfig.this.height,
|
||||
titleLine2 != null ? 33 : 23,
|
||||
GuiEditRegistryConfig.this.height - 32,
|
||||
20);
|
||||
this.heading = heading;
|
||||
if (heading != null) {
|
||||
setHasListHeader(true, mc.fontRenderer.FONT_HEIGHT + 4);
|
||||
}
|
||||
setSlotXBoundsFromLeft(left);
|
||||
this.controlX = this.getScrollBarX() - 20;
|
||||
lists.add(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawScreen(int p_148128_1_, int p_148128_2_, float p_148128_3_) {
|
||||
GL11.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
ScaledResolution scaledresolution = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
GL11.glScissor(
|
||||
(left - 4) * scaledresolution.getScaleFactor(),
|
||||
(int) ((scaledresolution.getScaledHeight_double() - (bottom + 4))
|
||||
* scaledresolution.getScaleFactor()),
|
||||
(width + 8) * scaledresolution.getScaleFactor(),
|
||||
(height + 8) * scaledresolution.getScaleFactor());
|
||||
super.drawScreen(p_148128_1_, p_148128_2_, p_148128_3_);
|
||||
GL11.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void overlayBackground(int p_148136_1_, int p_148136_2_, int p_148136_3_, int p_148136_4_) {}
|
||||
|
||||
@Override
|
||||
protected void drawContainerBackground(Tessellator tessellator) {
|
||||
GL11.glEnable(GL11.GL_BLEND);
|
||||
OpenGlHelper.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, 0, 1);
|
||||
GL11.glDisable(GL11.GL_ALPHA_TEST);
|
||||
GL11.glShadeModel(GL11.GL_SMOOTH);
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
tessellator.startDrawingQuads();
|
||||
tessellator.setColorRGBA_I(0, 64);
|
||||
tessellator.addVertex(left, bottom, 0);
|
||||
tessellator.addVertex(right, bottom, 0);
|
||||
tessellator.addVertex(right, top, 0);
|
||||
tessellator.addVertex(left, top, 0);
|
||||
tessellator.draw();
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GL11.glShadeModel(GL11.GL_FLAT);
|
||||
GL11.glEnable(GL11.GL_ALPHA_TEST);
|
||||
GL11.glDisable(GL11.GL_BLEND);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawListHeader(int p_148129_1_, int p_148129_2_, Tessellator p_148129_3_) {
|
||||
if (heading == null) {
|
||||
return;
|
||||
}
|
||||
FontRenderer fr = mc.fontRenderer;
|
||||
fr.drawString(
|
||||
this.heading,
|
||||
p_148129_1_ + this.width / 2 - fr.getStringWidth(this.heading) / 2,
|
||||
Math.min(this.top + 3, p_148129_2_),
|
||||
0xffffff);
|
||||
}
|
||||
|
||||
void reinit(int width, int left) {
|
||||
this.width = width;
|
||||
this.bottom = GuiEditRegistryConfig.this.height - 32;
|
||||
this.setSlotXBoundsFromLeft(left);
|
||||
this.controlX = this.getScrollBarX() - 20;
|
||||
}
|
||||
|
||||
void initEntries(Iterable<?> entries) {
|
||||
this.controlX = this.getScrollBarX() - 20;
|
||||
this.entries.clear();
|
||||
for (Object entry : entries) {
|
||||
addEntry(new ListEntry(entry.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
void addEntry(ListEntry entry) {
|
||||
entry.entriesList = this;
|
||||
this.entries.add(entry);
|
||||
this.controlX = Math.min(this.controlX, this.getScrollBarX() - entry.getControlSetWidth() - 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IGuiListEntry getListEntry(int i) {
|
||||
return entries.get(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSize() {
|
||||
return entries.size();
|
||||
}
|
||||
|
||||
int getLabelX() {
|
||||
return left + 2;
|
||||
}
|
||||
|
||||
int getControlX() {
|
||||
return controlX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getListWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getScrollBarX() {
|
||||
return left + getListWidth() - 6;
|
||||
}
|
||||
|
||||
public void handleDrag(int mouseX, int mouseY, float partialTicks) {
|
||||
for (int i = 0, entriesSize = entries.size(); i < entriesSize; i++) {
|
||||
ListEntry entry = entries.get(i);
|
||||
if (entry.handleDrag(i, mouseX, mouseY, partialTicks)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void drawScreenPost(int mouseX, int mouseY, float partialTicks) {
|
||||
for (ListEntry entry : entries) {
|
||||
entry.drawToolTip(mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean areContentEqual(Object[] vals) {
|
||||
if (vals.length != entries.size()) return false;
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
ListEntry entry = entries.get(i);
|
||||
if (!entry.name.equals(vals[i])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class ListEntry implements GuiListExtended.IGuiListEntry {
|
||||
|
||||
private EntriesList entriesList;
|
||||
private final HoverChecker hoverCheckerText = new HoverChecker(0, 0, 0, 0, 0);
|
||||
final String name;
|
||||
final String localizedName;
|
||||
private final String tooltip;
|
||||
public boolean modified = false;
|
||||
|
||||
private final GuiButtonExt btnDrag = new GuiButtonDrag(1, 0, 0, 20, 20);
|
||||
private final HoverChecker hoverCheckerBtnDrag = new HoverChecker(btnDrag, 800);
|
||||
private boolean isDragging = false;
|
||||
private long lastScroll = 0;
|
||||
|
||||
ListEntry(String name) {
|
||||
this.name = name;
|
||||
String nameKey = "structurelib.sortedregistry." + registryName + "." + name;
|
||||
this.localizedName = trans(nameKey, name);
|
||||
|
||||
String tooltipKey = nameKey + ".tooltip";
|
||||
String tooltip = trans(tooltipKey, null);
|
||||
StringBuilder tooltipBuilder = new StringBuilder();
|
||||
tooltipBuilder.append(EnumChatFormatting.GREEN);
|
||||
tooltipBuilder.append(this.localizedName);
|
||||
if (tooltip != null) {
|
||||
tooltipBuilder.append('\n').append(EnumChatFormatting.YELLOW).append(tooltip);
|
||||
}
|
||||
tooltipBuilder.append('\n');
|
||||
tooltipBuilder.append(EnumChatFormatting.DARK_GRAY);
|
||||
tooltipBuilder.append(this.name);
|
||||
this.tooltip = tooltipBuilder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawEntry(int slotIndex, int x, int y, int listWidth, int slotHeight, Tessellator tessellator,
|
||||
int mouseX, int mouseY, boolean isSelected) {
|
||||
drawOutlineBox(x, y, listWidth, slotHeight, tessellator);
|
||||
drawLabel(y, slotHeight);
|
||||
drawControlSet(y, slotHeight, mouseX, mouseY);
|
||||
}
|
||||
|
||||
private void drawOutlineBox(int x, int y, int listWidth, int slotHeight, Tessellator tessellator) {
|
||||
if (isDragging) {
|
||||
// left x, right x, top y, bottom y
|
||||
int lx = x, rx = entriesList.getScrollBarX() - 4, ty = y, by = y + slotHeight;
|
||||
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
tessellator.startDrawingQuads();
|
||||
tessellator.setColorOpaque_I(0x808080);
|
||||
tessellator.addVertexWithUV(lx - 4, by + 4, 0.0D, 0.0D, 1.0D);
|
||||
tessellator.addVertexWithUV(rx + 4, by + 4, 0.0D, 1.0D, 1.0D);
|
||||
tessellator.addVertexWithUV(rx + 4, ty - 4, 0.0D, 1.0D, 0.0D);
|
||||
tessellator.addVertexWithUV(lx - 4, ty - 4, 0.0D, 0.0D, 0.0D);
|
||||
tessellator.setColorOpaque_I(0x202020);
|
||||
tessellator.addVertexWithUV(lx - 2, by + 2, 0.0D, 0.0D, 1.0D);
|
||||
tessellator.addVertexWithUV(rx + 2, by + 2, 0.0D, 1.0D, 1.0D);
|
||||
tessellator.addVertexWithUV(rx + 2, ty - 2, 0.0D, 1.0D, 0.0D);
|
||||
tessellator.addVertexWithUV(lx - 2, ty - 2, 0.0D, 0.0D, 0.0D);
|
||||
tessellator.draw();
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawControlSet(int y, int slotHeight, int mouseX, int mouseY) {
|
||||
btnDrag.xPosition = entriesList.getControlX();
|
||||
btnDrag.yPosition = y + (slotHeight - btnDrag.height) / 2;
|
||||
btnDrag.drawButton(mc, mouseX, mouseY);
|
||||
}
|
||||
|
||||
private void drawLabel(int y, int slotHeight) {
|
||||
String label = modified ? EnumChatFormatting.ITALIC + localizedName : localizedName;
|
||||
int allowedWidth = entriesList.getControlX() - entriesList.getLabelX() - 4;
|
||||
// probably not very wise to recompute this every render tick, but it doesn't matter for now
|
||||
String toDraw = trimStringToWidth(label, allowedWidth);
|
||||
hoverCheckerText.updateBounds(
|
||||
y + slotHeight / 2 - mc.fontRenderer.FONT_HEIGHT / 2,
|
||||
y + slotHeight / 2 + mc.fontRenderer.FONT_HEIGHT / 2,
|
||||
entriesList.getLabelX(),
|
||||
entriesList.getLabelX() + mc.fontRenderer.getStringWidth(toDraw));
|
||||
|
||||
mc.fontRenderer.drawString(
|
||||
toDraw,
|
||||
entriesList.getLabelX(),
|
||||
y + slotHeight / 2 - mc.fontRenderer.FONT_HEIGHT / 2,
|
||||
getLabelColor());
|
||||
}
|
||||
|
||||
void drawToolTip(int mouseX, int mouseY) {
|
||||
// ensure checkHover() is always called, or the hover threshold might fail under certain corner cases
|
||||
if (hoverCheckerText.checkHover(mouseX, mouseY)) {
|
||||
GuiEditRegistryConfig.this.drawToolTip(
|
||||
mc.fontRenderer.listFormattedStringToWidth(tooltip, entriesList.getListWidth()),
|
||||
mouseX,
|
||||
mouseY);
|
||||
}
|
||||
if (hoverCheckerBtnDrag.checkHover(mouseX, mouseY) && !isDragging) {
|
||||
GuiEditRegistryConfig.this.drawToolTip(
|
||||
mc.fontRenderer.listFormattedStringToWidth(
|
||||
I18n.format("structurelib.configgui.drag.tooltip"),
|
||||
entriesList.getListWidth()),
|
||||
mouseX,
|
||||
mouseY);
|
||||
}
|
||||
}
|
||||
|
||||
protected int getLabelColor() {
|
||||
return isDragging ? 0x888888 : 0xffffff;
|
||||
}
|
||||
|
||||
int getControlSetWidth() {
|
||||
return this.btnDrag.getButtonWidth();
|
||||
}
|
||||
|
||||
public boolean handleDrag(int index, int mouseX, int mouseY, float partialTicks) {
|
||||
if (!isDragging) {
|
||||
return false;
|
||||
}
|
||||
for (EntriesList list : lists) {
|
||||
if (list == entriesList) continue;
|
||||
if (mouseX > list.left && mouseX < list.right && mouseY > list.top && mouseY < list.bottom) {
|
||||
int l;
|
||||
if (list.entries.isEmpty()) l = 0;
|
||||
else l = list.func_148124_c(mouseX, mouseY);
|
||||
if (l < 0) {
|
||||
if (mouseY < (list.bottom + list.top) / 2) {
|
||||
l = 0;
|
||||
} else {
|
||||
l = list.entries.size();
|
||||
}
|
||||
}
|
||||
entriesList.func_148143_b(true);
|
||||
entriesList.entries.remove(index);
|
||||
list.func_148143_b(false);
|
||||
list.entries.add(l, this);
|
||||
this.entriesList = list;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - lastScroll > 20) {
|
||||
if (mouseY < entriesList.top) {
|
||||
entriesList.scrollBy(-3);
|
||||
lastScroll = now;
|
||||
} else if (mouseY > entriesList.bottom) {
|
||||
entriesList.scrollBy(3);
|
||||
lastScroll = now;
|
||||
}
|
||||
}
|
||||
int l = entriesList.func_148124_c(
|
||||
MathHelper.clamp_int(mouseX, entriesList.left, entriesList.right),
|
||||
MathHelper.clamp_int(mouseY, entriesList.top, entriesList.bottom));
|
||||
if (l < 0 || l == index) return true;
|
||||
entriesList.entries.remove(index);
|
||||
entriesList.entries.add(l, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(int index, int x, int y, int mouseEvent, int relativeX, int relativeY) {
|
||||
isDragging = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mousePressed(int index, int x, int y, int mouseEvent, int relativeX, int relativeY) {
|
||||
if (btnDrag.mousePressed(mc, x, y)) {
|
||||
isDragging = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static String trans(String transKey, String fallback) {
|
||||
String translated = I18n.format(transKey);
|
||||
return translated.equals(transKey) ? fallback : translated;
|
||||
}
|
||||
|
||||
private static class GuiButtonDrag extends GuiButtonExt {
|
||||
|
||||
public GuiButtonDrag(int id, int xPos, int yPos, int width, int height) {
|
||||
super(id, xPos, yPos, width, height, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawButton(Minecraft mc, int mouseX, int mouseY) {
|
||||
if (this.visible) {
|
||||
this.field_146123_n = mouseX >= this.xPosition && mouseY >= this.yPosition
|
||||
&& mouseX < this.xPosition + this.width
|
||||
&& mouseY < this.yPosition + this.height;
|
||||
int k = this.getHoverState(this.field_146123_n);
|
||||
GuiUtils.drawContinuousTexturedBox(
|
||||
buttonTextures,
|
||||
this.xPosition,
|
||||
this.yPosition,
|
||||
0,
|
||||
46 + k * 20,
|
||||
this.width,
|
||||
this.height,
|
||||
200,
|
||||
20,
|
||||
2,
|
||||
3,
|
||||
2,
|
||||
2,
|
||||
this.zLevel);
|
||||
mc.renderEngine.bindTexture(new ResourceLocation("structurelib", "textures/gui/fourwayarrow2.png"));
|
||||
int x = this.xPosition + (this.width - 12) / 2;
|
||||
int y = this.yPosition + (this.height - 12) / 2;
|
||||
Tessellator tessellator = Tessellator.instance;
|
||||
tessellator.startDrawingQuads();
|
||||
tessellator.addVertexWithUV(x, y + 12, zLevel, 0, 1);
|
||||
tessellator.addVertexWithUV(x + 12, y + 12, zLevel, 1, 1);
|
||||
tessellator.addVertexWithUV(x + 12, y, zLevel, 1, 0);
|
||||
tessellator.addVertexWithUV(x, y, zLevel, 0, 0);
|
||||
tessellator.draw();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.gtnewhorizon.structurelib;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import com.gtnewhorizon.structurelib.gui.EmptyContainer;
|
||||
import com.gtnewhorizon.structurelib.gui.GuiScreenConfigureChannels;
|
||||
|
||||
import cpw.mods.fml.common.network.IGuiHandler;
|
||||
@@ -11,14 +12,19 @@ public class GuiHandler implements IGuiHandler {
|
||||
|
||||
@Override
|
||||
public Object getServerGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
|
||||
return null;
|
||||
switch (ID) {
|
||||
case 0:
|
||||
return new EmptyContainer(player);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getClientGuiElement(int ID, EntityPlayer player, World world, int x, int y, int z) {
|
||||
switch (ID) {
|
||||
case 0:
|
||||
return new GuiScreenConfigureChannels(player.inventory.getStackInSlot(x));
|
||||
return new GuiScreenConfigureChannels(new EmptyContainer(player), player.inventory.getStackInSlot(x));
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.gtnewhorizon.structurelib;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
|
||||
import cpw.mods.fml.client.config.GuiConfig;
|
||||
import cpw.mods.fml.client.config.GuiConfigEntries;
|
||||
import cpw.mods.fml.client.config.IConfigElement;
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
public final class RegistryOrderEntry extends GuiConfigEntries.CategoryEntry {
|
||||
|
||||
public RegistryOrderEntry(GuiConfig owningScreen, GuiConfigEntries owningEntryList,
|
||||
IConfigElement<?> configElement) {
|
||||
super(owningScreen, owningEntryList, configElement);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GuiScreen buildChildScreen() {
|
||||
List<IConfigElement<?>> childElements = this.configElement.getChildElements();
|
||||
IConfigElement<?> disabled = null, order = null;
|
||||
for (IConfigElement<?> e : childElements) {
|
||||
if (e.getName().equals("disabled")) disabled = e;
|
||||
else if (e.getName().equals("ordering")) order = e;
|
||||
}
|
||||
if (disabled == null || order == null) {
|
||||
throw new IllegalArgumentException("Did not find expected config entires");
|
||||
}
|
||||
return new GuiEditRegistryConfig(
|
||||
this.owningScreen,
|
||||
disabled,
|
||||
order,
|
||||
configElement.getName(),
|
||||
this.owningScreen.modID,
|
||||
null,
|
||||
owningScreen.allRequireWorldRestart || this.configElement.requiresWorldRestart(),
|
||||
owningScreen.allRequireMcRestart || this.configElement.requiresMcRestart(),
|
||||
this.owningScreen.title,
|
||||
((this.owningScreen.titleLine2 == null ? "" : this.owningScreen.titleLine2) + " > " + this.name));
|
||||
}
|
||||
|
||||
private GuiEditRegistryConfig getChildScreen() {
|
||||
return (GuiEditRegistryConfig) childScreen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean saveConfigElement() {
|
||||
GuiEditRegistryConfig c = getChildScreen();
|
||||
if (c != null && c.isInitialized()) return c.saveConfigElements();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isChanged() {
|
||||
GuiEditRegistryConfig c = getChildScreen();
|
||||
return c != null && c.isInitialized() && c.isChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDefault() {
|
||||
GuiEditRegistryConfig c = getChildScreen();
|
||||
return c != null && c.isInitialized() && c.isDefault();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setToDefault() {
|
||||
GuiEditRegistryConfig c = getChildScreen();
|
||||
if (c != null && c.isInitialized()) c.setToDefault();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undoChanges() {
|
||||
GuiEditRegistryConfig c = getChildScreen();
|
||||
if (c != null && c.isInitialized()) c.undoChanges();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package com.gtnewhorizon.structurelib;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.Set;
|
||||
import java.util.Spliterator;
|
||||
import java.util.TreeMap;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.gtnewhorizon.structurelib.net.RegistryOrderSyncMessage;
|
||||
|
||||
import cpw.mods.fml.common.FMLCommonHandler;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import cpw.mods.fml.common.gameevent.PlayerEvent;
|
||||
|
||||
public class SortedRegistry<V> implements Iterable<V> {
|
||||
|
||||
static final ConcurrentHashMap<String, WeakReference<SortedRegistry<?>>> ALL_REGISTRIES = new ConcurrentHashMap<>();
|
||||
static {
|
||||
RegistryOrderSyncMessage.Handler.setAllRegistries(ALL_REGISTRIES);
|
||||
}
|
||||
private final NavigableMap<String, V> store = new TreeMap<>();
|
||||
/**
|
||||
* Store stuff in an array for faster iteration access than a red black tree
|
||||
*/
|
||||
private List<V> baked = Collections.emptyList();
|
||||
|
||||
private final Map<UUID, Pair<List<String>, List<String>>> playerOrdering = new HashMap<>();
|
||||
private final Map<UUID, List<V>> playerBaked = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Construct an unsynchronized registry
|
||||
*/
|
||||
public SortedRegistry() {}
|
||||
|
||||
/**
|
||||
* Construct a synchronized registry whose ordering will be synced to the server
|
||||
*
|
||||
* @param name network name
|
||||
*/
|
||||
|
||||
public SortedRegistry(String name) {
|
||||
WeakReference<SortedRegistry<?>> oldValue = ALL_REGISTRIES.put(name, new WeakReference<>(this));
|
||||
if (oldValue != null && oldValue.get() != null) {
|
||||
throw new IllegalArgumentException("duplicate name");
|
||||
}
|
||||
FMLCommonHandler.instance().bus().register(this);
|
||||
}
|
||||
|
||||
public static Set<String> getRegistryNames() {
|
||||
return ALL_REGISTRIES.keySet();
|
||||
}
|
||||
|
||||
public static SortedRegistry<?> getRegistry(String name) {
|
||||
WeakReference<SortedRegistry<?>> ref = ALL_REGISTRIES.get(name);
|
||||
return ref != null ? ref.get() : null;
|
||||
}
|
||||
|
||||
static void cleanup(MinecraftServer server) {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<EntityPlayerMP> players = server.getConfigurationManager().playerEntityList;
|
||||
Set<UUID> onlinePlayerIDs = players.stream().map(EntityPlayerMP::getUniqueID).collect(Collectors.toSet());
|
||||
int removed = 0;
|
||||
for (WeakReference<SortedRegistry<?>> ref : ALL_REGISTRIES.values()) {
|
||||
SortedRegistry<?> registry = ref.get();
|
||||
if (registry == null) continue;
|
||||
for (Iterator<UUID> iterator = registry.playerOrdering.keySet().iterator(); iterator.hasNext();) {
|
||||
UUID uuid = iterator.next();
|
||||
if (!onlinePlayerIDs.contains(uuid)) {
|
||||
iterator.remove();
|
||||
registry.playerBaked.remove(uuid);
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (removed > 0) {
|
||||
StructureLib.LOGGER.debug("Removed {} registry record for logged out players", removed);
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onPlayerLogOut(PlayerEvent.PlayerLoggedOutEvent e) {
|
||||
playerOrdering.remove(e.player.getUniqueID());
|
||||
playerBaked.remove(e.player.getUniqueID());
|
||||
}
|
||||
|
||||
public void register(String key, V val) {
|
||||
if (key == null || val == null) throw new NullPointerException();
|
||||
V old = store.putIfAbsent(key, val);
|
||||
if (old != null) {
|
||||
throw new IllegalArgumentException("Duplicate key: " + key);
|
||||
}
|
||||
baked = new ArrayList<>(store.values());
|
||||
for (Map.Entry<UUID, Pair<List<String>, List<String>>> e : playerOrdering.entrySet()) {
|
||||
makePlayerBaked(e.getKey(), e.getValue().getKey(), e.getValue().getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public void registerOrdering(EntityPlayerMP player, List<String> ordering, List<String> disabled) {
|
||||
registerOrdering(player.getUniqueID(), ordering, disabled);
|
||||
}
|
||||
|
||||
public void registerOrdering(UUID playerID, List<String> ordering, List<String> disabled) {
|
||||
playerOrdering.put(playerID, Pair.of(ordering, disabled));
|
||||
makePlayerBaked(playerID, ordering, disabled);
|
||||
}
|
||||
|
||||
private void makePlayerBaked(UUID player, List<String> ordering, List<String> disabled) {
|
||||
TreeMap<String, V> copy = new TreeMap<>(store);
|
||||
for (String s : disabled) {
|
||||
copy.remove(s);
|
||||
}
|
||||
List<V> b = new ArrayList<>();
|
||||
for (String k : ordering) {
|
||||
V v = copy.remove(k);
|
||||
if (v != null) {
|
||||
b.add(v);
|
||||
}
|
||||
}
|
||||
b.addAll(copy.values());
|
||||
playerBaked.put(player, b);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return store.size();
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return store.isEmpty();
|
||||
}
|
||||
|
||||
public boolean containsKey(String key) {
|
||||
return store.containsKey(key);
|
||||
}
|
||||
|
||||
public boolean containsValue(V value) {
|
||||
return store.containsValue(value);
|
||||
}
|
||||
|
||||
public V get(String key) {
|
||||
return store.get(key);
|
||||
}
|
||||
|
||||
public Iterable<V> getPlayerOrdering(@Nullable EntityPlayerMP player) {
|
||||
if (player == null) return baked;
|
||||
List<V> playerPreference = playerBaked.get(player.getUniqueID());
|
||||
if (playerPreference == null) return baked;
|
||||
return playerPreference;
|
||||
}
|
||||
|
||||
public Stream<String> getPlayerOrderingKeys(@Nullable EntityPlayerMP player) {
|
||||
if (player == null) return store.keySet().stream();
|
||||
List<V> playerOrder = playerBaked.get(player.getUniqueID());
|
||||
if (playerOrder == null) return store.keySet().stream();
|
||||
|
||||
// rebuild the order from the baked map
|
||||
Map<V, String> reverseMap = new IdentityHashMap<>();
|
||||
store.forEach((k, v) -> reverseMap.put(v, k));
|
||||
return playerOrder.stream().map(reverseMap::get);
|
||||
}
|
||||
|
||||
Iterable<String> getCurrentOrdering() {
|
||||
return store.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<V> iterator() {
|
||||
return baked.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(Consumer<? super V> action) {
|
||||
baked.forEach(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<V> spliterator() {
|
||||
return baked.spliterator();
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,40 @@
|
||||
package com.gtnewhorizon.structurelib;
|
||||
|
||||
import static com.gtnewhorizon.structurelib.StructureLibAPI.CHANNEL_SHOW_ERROR;
|
||||
import static com.gtnewhorizon.structurelib.StructureLibAPI.MOD_ID;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.creativetab.CreativeTabs;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemBlock;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
import net.minecraftforge.common.util.Constants;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import com.gtnewhorizon.structurelib.block.BlockHint;
|
||||
import com.gtnewhorizon.structurelib.command.CommandConfigureChannels;
|
||||
import com.gtnewhorizon.structurelib.command.CommandRegistryDebug;
|
||||
import com.gtnewhorizon.structurelib.item.ItemBlockHint;
|
||||
import com.gtnewhorizon.structurelib.item.ItemConstructableTrigger;
|
||||
import com.gtnewhorizon.structurelib.item.ItemFrontRotationTool;
|
||||
import com.gtnewhorizon.structurelib.net.AlignmentMessage;
|
||||
import com.gtnewhorizon.structurelib.net.ErrorHintParticleMessage;
|
||||
import com.gtnewhorizon.structurelib.net.RegistryOrderSyncMessage;
|
||||
import com.gtnewhorizon.structurelib.net.SetChannelDataMessage;
|
||||
import com.gtnewhorizon.structurelib.net.UpdateHintParticleMessage;
|
||||
import com.gtnewhorizon.structurelib.util.InventoryUtility;
|
||||
import com.gtnewhorizon.structurelib.util.XSTR;
|
||||
|
||||
import cpw.mods.fml.common.Loader;
|
||||
import cpw.mods.fml.common.Mod;
|
||||
import cpw.mods.fml.common.SidedProxy;
|
||||
import cpw.mods.fml.common.event.FMLInterModComms;
|
||||
import cpw.mods.fml.common.event.FMLLoadCompleteEvent;
|
||||
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
|
||||
import cpw.mods.fml.common.event.FMLServerStartingEvent;
|
||||
import cpw.mods.fml.common.network.NetworkRegistry;
|
||||
@@ -36,9 +47,9 @@ import cpw.mods.fml.relauncher.SideOnly;
|
||||
* This class does not contain a stable API. Refrain from using this class.
|
||||
*/
|
||||
@Mod(
|
||||
modid = StructureLibAPI.MOD_ID,
|
||||
modid = MOD_ID,
|
||||
name = "StructureLib",
|
||||
version = "GRADLETOKEN_VERSION",
|
||||
version = Tags.VERSION,
|
||||
acceptableRemoteVersions = "*",
|
||||
guiFactory = "com.gtnewhorizon.structurelib.GuiFactory")
|
||||
public class StructureLib {
|
||||
@@ -53,7 +64,7 @@ public class StructureLib {
|
||||
clientSide = "com.gtnewhorizon.structurelib.ClientProxy")
|
||||
static CommonProxy proxy;
|
||||
|
||||
static final SimpleNetworkWrapper net = NetworkRegistry.INSTANCE.newSimpleChannel(StructureLibAPI.MOD_ID);
|
||||
static final SimpleNetworkWrapper net = NetworkRegistry.INSTANCE.newSimpleChannel(MOD_ID);
|
||||
|
||||
static {
|
||||
net.registerMessage(
|
||||
@@ -65,6 +76,7 @@ public class StructureLib {
|
||||
net.registerMessage(UpdateHintParticleMessage.Handler.class, UpdateHintParticleMessage.class, 2, Side.CLIENT);
|
||||
net.registerMessage(SetChannelDataMessage.Handler.class, SetChannelDataMessage.class, 3, Side.SERVER);
|
||||
net.registerMessage(ErrorHintParticleMessage.Handler.class, ErrorHintParticleMessage.class, 4, Side.CLIENT);
|
||||
net.registerMessage(RegistryOrderSyncMessage.Handler.class, RegistryOrderSyncMessage.class, 5, Side.SERVER);
|
||||
|
||||
try {
|
||||
DEBUG_MODE = Boolean.parseBoolean(System.getProperty("structurelib.debug"));
|
||||
@@ -79,7 +91,7 @@ public class StructureLib {
|
||||
|
||||
@Mod.Instance
|
||||
static StructureLib INSTANCE;
|
||||
|
||||
@Mod.Instance(STRUCTURECOMPAT_MODID)
|
||||
static Object COMPAT;
|
||||
|
||||
static Block blockHint;
|
||||
@@ -109,14 +121,64 @@ public class StructureLib {
|
||||
proxy.preInit(e);
|
||||
NetworkRegistry.INSTANCE.registerGuiHandler(instance(), new GuiHandler());
|
||||
|
||||
if (Loader.isModLoaded(STRUCTURECOMPAT_MODID)) {
|
||||
COMPAT = Loader.instance().getIndexedModList().get(STRUCTURECOMPAT_MODID).getMod();
|
||||
}
|
||||
InventoryUtility.init();
|
||||
|
||||
ChannelDescription.set(CHANNEL_SHOW_ERROR, MOD_ID, "channels.structurelib.show_errors");
|
||||
|
||||
DevelopHelper.onPreInit();
|
||||
}
|
||||
|
||||
@Mod.EventHandler
|
||||
public void loadComplete(FMLLoadCompleteEvent e) {
|
||||
proxy.loadComplete(e);
|
||||
}
|
||||
|
||||
@Mod.EventHandler
|
||||
public void serverStarting(FMLServerStartingEvent e) {
|
||||
e.registerServerCommand(new CommandConfigureChannels());
|
||||
e.registerServerCommand(new CommandRegistryDebug());
|
||||
}
|
||||
|
||||
@Mod.EventHandler
|
||||
public void handleIMC(FMLInterModComms.IMCEvent event) {
|
||||
for (FMLInterModComms.IMCMessage message : event.getMessages()) {
|
||||
switch (message.key) {
|
||||
case "register_channel":
|
||||
processRegisterChannel(message);
|
||||
break;
|
||||
case "register_channel_item":
|
||||
processRegisterChannelItem(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processRegisterChannel(FMLInterModComms.IMCMessage message) {
|
||||
NBTTagList tags = message.getNBTValue().getTagList("Channels", Constants.NBT.TAG_COMPOUND);
|
||||
for (int i = 0; i < tags.tagCount(); i++) {
|
||||
NBTTagCompound tag = tags.getCompoundTagAt(i);
|
||||
StructureLibAPI.registerChannelDescription(
|
||||
tag.getString("Channel"),
|
||||
message.getSender(),
|
||||
tag.getString("Description"));
|
||||
}
|
||||
}
|
||||
|
||||
private void processRegisterChannelItem(FMLInterModComms.IMCMessage message) {
|
||||
NBTTagList items = message.getNBTValue().getTagList("Items", Constants.NBT.TAG_COMPOUND);
|
||||
for (int i = 0; i < items.tagCount(); i++) {
|
||||
NBTTagCompound itemTag = items.getCompoundTagAt(i);
|
||||
ItemStack item = ItemStack.loadItemStackFromNBT(itemTag.getCompoundTag("Item"));
|
||||
NBTTagList tags = itemTag.getTagList("Channels", Constants.NBT.TAG_COMPOUND);
|
||||
for (int j = 0; j < tags.tagCount(); j++) {
|
||||
NBTTagCompound tag = tags.getCompoundTagAt(j);
|
||||
StructureLibAPI.registerChannelItem(
|
||||
tag.getString("Channel"),
|
||||
message.getSender(),
|
||||
tag.getInteger("Value"),
|
||||
item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void addClientSideChatMessages(String... messages) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import net.minecraft.util.IChatComponent;
|
||||
import net.minecraft.util.IIcon;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.util.AnimatedTooltipHandler;
|
||||
import com.gtnewhorizon.structurelib.alignment.IAlignment;
|
||||
import com.gtnewhorizon.structurelib.alignment.IAlignmentProvider;
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
|
||||
@@ -43,6 +44,7 @@ public class StructureLibAPI {
|
||||
public static final int HINT_BLOCK_META_AIR = 13;
|
||||
public static final int HINT_BLOCK_META_NOT_AIR = 14;
|
||||
public static final int HINT_BLOCK_META_ERROR = 15;
|
||||
public static final String CHANNEL_SHOW_ERROR = "show_error";
|
||||
static final ThreadLocal<Object> instrument = new ThreadLocal<>();
|
||||
|
||||
/**
|
||||
@@ -72,7 +74,7 @@ public class StructureLibAPI {
|
||||
*/
|
||||
public static void enableInstrument(Object identifier) {
|
||||
if (isInstrumentEnabled()) throw new IllegalStateException();
|
||||
instrument.set(instrument);
|
||||
instrument.set(identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -350,4 +352,37 @@ public class StructureLibAPI {
|
||||
short intervalRequired, boolean forceUpdateLastSend) {
|
||||
proxy.addThrottledChat(throttleKey, player, text, intervalRequired, forceUpdateLastSend);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a channel description here. The description should describe how a channel will be used inside your mod.
|
||||
*
|
||||
* @param channel channel key
|
||||
* @param modid your modid
|
||||
* @param description localization key for your description
|
||||
*/
|
||||
public static void registerChannelDescription(final String channel, final String modid, final String description) {
|
||||
ChannelDescription.set(channel, modid, description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an item stack as an indicator for a (channel, value) pair.
|
||||
*
|
||||
* @param channel channel key
|
||||
* @param modid your modid
|
||||
* @param channelValue value
|
||||
* @param stack item stack. having a metadata of
|
||||
* {@link net.minecraftforge.oredict.OreDictionary#WILDCARD_VALUE} will match without comparing
|
||||
* NBT or metadata value. having a tag with {@code tag.getBoolean("*") == true} will match
|
||||
* without comparing NBT.
|
||||
*/
|
||||
public static void registerChannelItem(final String channel, final String modid, final int channelValue,
|
||||
final ItemStack stack) {
|
||||
ChannelDescription.item(channel, channelValue, stack);
|
||||
AnimatedTooltipHandler.addItemTooltip(
|
||||
stack,
|
||||
AnimatedTooltipHandler.translatedText("structurelib.tooltip.channelvalue", channelValue, channel));
|
||||
AnimatedTooltipHandler.addItemTooltip(
|
||||
stack,
|
||||
AnimatedTooltipHandler.translatedText("structurelib.tooltip.indicator_dnd", channelValue, channel));
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,28 @@ import com.gtnewhorizon.structurelib.alignment.enumerable.Rotation;
|
||||
|
||||
public interface IAlignmentLimits {
|
||||
|
||||
/**
|
||||
* Can be placed in any orientation.
|
||||
*/
|
||||
IAlignmentLimits UNLIMITED = (direction, rotation, flip) -> true;
|
||||
|
||||
/**
|
||||
* Must be standing upright; i.e., top side of the block facing up.
|
||||
*/
|
||||
IAlignmentLimits UPRIGHT = (direction, rotation, flip) -> direction.offsetY == 0
|
||||
&& (flip.isVerticallyFliped() ? rotation.isUpsideDown() : rotation.isNotRotated());
|
||||
|
||||
/**
|
||||
* Can be in any orientation except upside down; i.e., top side of the block facing down.
|
||||
*/
|
||||
IAlignmentLimits NOT_UPSIDE_DOWN = (direction, rotation, flip) -> direction.offsetY != 0
|
||||
|| (flip.isVerticallyFliped() ? !rotation.isNotRotated() : !rotation.isUpsideDown());
|
||||
|
||||
/**
|
||||
* Front side of the block must be facing up.
|
||||
*/
|
||||
IAlignmentLimits FACING_UP = (direction, rotation, flip) -> direction == ForgeDirection.UP;
|
||||
|
||||
boolean isNewExtendedFacingValid(ForgeDirection direction, Rotation rotation, Flip flip);
|
||||
|
||||
default boolean isNewExtendedFacingValid(ExtendedFacing alignment) {
|
||||
|
||||
@@ -4,9 +4,18 @@ import static com.gtnewhorizon.structurelib.alignment.IAlignment.FLIPS_COUNT;
|
||||
import static com.gtnewhorizon.structurelib.alignment.IAlignment.ROTATIONS_COUNT;
|
||||
import static java.lang.Math.abs;
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.stream.Collectors.*;
|
||||
import static java.util.stream.Collectors.collectingAndThen;
|
||||
import static java.util.stream.Collectors.groupingBy;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import net.minecraft.util.StatCollector;
|
||||
import net.minecraft.util.Vec3;
|
||||
|
||||
@@ -50,6 +50,10 @@ public class BlockHint extends Block {
|
||||
|
||||
@Override
|
||||
public IIcon getIcon(int aSide, int aMeta) {
|
||||
// For numbered hints, top and bottom are blank
|
||||
if (aSide <= 1 && aMeta <= 11) {
|
||||
return hint[13];
|
||||
}
|
||||
return hint[aMeta];
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ public class CommandConfigureChannels extends CommandBase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<?> addTabCompletionOptions(ICommandSender p_71516_1_, String[] p_71516_2_) {
|
||||
public List<String> addTabCompletionOptions(ICommandSender p_71516_1_, String[] p_71516_2_) {
|
||||
if (p_71516_2_.length == 1)
|
||||
return getListOfStringsMatchingLastWord(p_71516_2_, "get", "set", "unset", "wipe", "getall");
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.gtnewhorizon.structurelib.command;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.command.CommandBase;
|
||||
import net.minecraft.command.ICommandSender;
|
||||
import net.minecraft.command.WrongUsageException;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.util.ChatComponentText;
|
||||
|
||||
import com.gtnewhorizon.structurelib.SortedRegistry;
|
||||
|
||||
public class CommandRegistryDebug extends CommandBase {
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return "sl_registry_debug";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandUsage(ICommandSender p_71518_1_) {
|
||||
return "/sl_registry_debug <registry name>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRequiredPermissionLevel() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> addTabCompletionOptions(ICommandSender p_71516_1_, String[] p_71516_2_) {
|
||||
if (p_71516_2_.length == 1) return getListOfStringsMatchingLastWord(
|
||||
p_71516_2_,
|
||||
SortedRegistry.getRegistryNames().toArray(new String[0]));
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processCommand(ICommandSender sender, String[] args) {
|
||||
if (args.length != 1) throw new WrongUsageException(getCommandUsage(sender));
|
||||
|
||||
if (!(sender instanceof EntityPlayerMP)) throw new WrongUsageException("must be a player");
|
||||
|
||||
EntityPlayerMP player = (EntityPlayerMP) sender;
|
||||
|
||||
SortedRegistry<?> registry = SortedRegistry.getRegistry(args[0]);
|
||||
if (registry == null) throw new WrongUsageException("registry not found");
|
||||
|
||||
player.addChatMessage(new ChatComponentText("Current order:"));
|
||||
registry.getPlayerOrderingKeys(player).map(ChatComponentText::new).forEach(player::addChatMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.gtnewhorizon.structurelib.core;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.gtnewhorizon.gtnhmixins.IEarlyMixinLoader;
|
||||
|
||||
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
|
||||
|
||||
@IFMLLoadingPlugin.MCVersion("1.7.10")
|
||||
public class StructureLibCore implements IFMLLoadingPlugin, IEarlyMixinLoader {
|
||||
|
||||
@Override
|
||||
public String[] getASMTransformerClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModContainerClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSetupClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectData(Map<String, Object> data) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccessTransformerClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMixinConfig() {
|
||||
return "mixins.structurelib.early.json";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getMixins(Set<String> loadedCoreMods) {
|
||||
return Arrays.asList("blockChangeNotifier.MixinForgeHooks", "blockChangeNotifier.MixinWorld");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.gtnewhorizon.structurelib.event;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
|
||||
public interface BlockChangeListener {
|
||||
|
||||
void onBlockChange(World world, Chunk chunk, int x, int y, int z, Block oldBlock, Block newBlock, int oldMeta,
|
||||
int newMeta);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.gtnewhorizon.structurelib.event;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
|
||||
@SuppressWarnings("ForLoopReplaceableByForEach")
|
||||
public class BlockChangeNotifier {
|
||||
|
||||
private static final List<BlockChangeListener> listeners = new ArrayList<>();
|
||||
|
||||
public static synchronized void addListener(BlockChangeListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public static void onBlockChange(World world, Chunk chunk, int x, int y, int z, Block oldBlock, Block newBlock,
|
||||
int oldMeta, int newMeta) {
|
||||
if (oldBlock == newBlock && oldMeta == newMeta) return;
|
||||
|
||||
if (chunk == null) {
|
||||
chunk = world.getChunkFromBlockCoords(x, z);
|
||||
}
|
||||
|
||||
final int numListeners = listeners.size();
|
||||
for (int i = 0; i < numListeners; i++) {
|
||||
final BlockChangeListener listener = listeners.get(i);
|
||||
listener.onBlockChange(world, chunk, x, y, z, oldBlock, newBlock, oldMeta, newMeta);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.gtnewhorizon.structurelib.gui;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.inventory.Container;
|
||||
|
||||
public class EmptyContainer extends Container {
|
||||
|
||||
private final EntityPlayer player;
|
||||
|
||||
public EmptyContainer(EntityPlayer player) {
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canInteractWith(EntityPlayer player) {
|
||||
return player == this.player;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +1,82 @@
|
||||
package com.gtnewhorizon.structurelib.gui;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import net.minecraft.client.gui.GuiTextField;
|
||||
import net.minecraft.client.gui.inventory.GuiContainer;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
import net.minecraft.inventory.Container;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.EnumChatFormatting;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.StatCollector;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.lwjgl.input.Keyboard;
|
||||
import org.lwjgl.input.Mouse;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.gtnewhorizon.structurelib.ChannelDescription;
|
||||
import com.gtnewhorizon.structurelib.StructureLib;
|
||||
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
|
||||
import com.gtnewhorizon.structurelib.gui.GuiScrollableList.IGuiScreen;
|
||||
|
||||
public class GuiScreenConfigureChannels extends GuiScreen implements IGuiScreen {
|
||||
import codechicken.nei.VisiblityData;
|
||||
import codechicken.nei.api.INEIGuiHandler;
|
||||
import codechicken.nei.api.TaggedInventoryArea;
|
||||
import cpw.mods.fml.common.Loader;
|
||||
import cpw.mods.fml.common.Optional;
|
||||
import gnu.trove.map.TIntObjectMap;
|
||||
import gnu.trove.map.hash.TIntObjectHashMap;
|
||||
|
||||
@Optional.Interface(iface = "codechicken.nei.api.INEIGuiHandler", modid = "NotEnoughItems")
|
||||
public class GuiScreenConfigureChannels extends GuiContainer implements IGuiScreen, INEIGuiHandler {
|
||||
|
||||
private static final String I18N_PREFIX = "item.structurelib.constructableTrigger.gui.";
|
||||
|
||||
private static final int ADD_BTN = 0;
|
||||
private static final int UNSET_BTN = 1;
|
||||
private static final int WIPE_BTN = 2;
|
||||
|
||||
private static final int KEY_MAX_WIDTH = 50;
|
||||
private static final int INFO_BOX_SIZE = 11;
|
||||
private final ItemStack trigger;
|
||||
private final GuiChannelsList list;
|
||||
private final GuiAutoCompleteList autoCompleteList;
|
||||
private final Map<String, List<String>> tooltipSplitCache = new HashMap<>();
|
||||
private final TIntObjectMap<Map<String, String>> trimCache = new TIntObjectHashMap<>();
|
||||
private GuiTextField key, value;
|
||||
protected int guiTop, guiLeft;
|
||||
private List<String> info;
|
||||
|
||||
public GuiScreenConfigureChannels(Container container, ItemStack trigger) {
|
||||
super(container);
|
||||
|
||||
public GuiScreenConfigureChannels(ItemStack trigger) {
|
||||
this.trigger = trigger;
|
||||
list = new GuiChannelsList(152, 100, 12, 12, 14);
|
||||
list = new GuiChannelsList(152, 91, 12, 21, 14);
|
||||
list.addSelectionListener((list, selectedIndex) -> {
|
||||
Entry<String, Integer> e = list.getElementAt(selectedIndex);
|
||||
key.setText(e.getKey());
|
||||
value.setText(e.getValue().toString());
|
||||
updateButtons();
|
||||
});
|
||||
autoCompleteList = new GuiAutoCompleteList(151 + 12 - 45 - 2, 50, 45, 119 + 12, 14);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -125,33 +162,20 @@ public class GuiScreenConfigureChannels extends GuiScreen implements IGuiScreen
|
||||
};
|
||||
|
||||
list.onGuiInit(this);
|
||||
autoCompleteList.onGuiInit(this);
|
||||
|
||||
addButton(
|
||||
new GuiButton(
|
||||
0,
|
||||
guiLeft + 12,
|
||||
guiTop + 157,
|
||||
47,
|
||||
20,
|
||||
I18n.format("item.structurelib.constructableTrigger.gui.add")));
|
||||
addButton(
|
||||
new GuiButton(
|
||||
1,
|
||||
guiLeft + 65,
|
||||
guiTop + 157,
|
||||
47,
|
||||
20,
|
||||
I18n.format("item.structurelib.constructableTrigger.gui.unset")));
|
||||
addButton(
|
||||
new GuiButton(
|
||||
2,
|
||||
guiLeft + 118,
|
||||
guiTop + 157,
|
||||
47,
|
||||
20,
|
||||
I18n.format("item.structurelib.constructableTrigger.gui.wipe")));
|
||||
addButton(new GuiButton(ADD_BTN, guiLeft + 12, guiTop + 157, 47, 20, I18n.format(I18N_PREFIX + "add")));
|
||||
addButton(new GuiButton(UNSET_BTN, guiLeft + 65, guiTop + 157, 47, 20, I18n.format(I18N_PREFIX + "unset")));
|
||||
addButton(new GuiButton(WIPE_BTN, guiLeft + 118, guiTop + 157, 47, 20, I18n.format(I18N_PREFIX + "wipe")));
|
||||
|
||||
updateButtons();
|
||||
tooltipSplitCache.clear();
|
||||
info = StreamSupport
|
||||
.stream(
|
||||
Splitter.on("\\n").split(StatCollector.translateToLocal(I18N_PREFIX + "info")).spliterator(),
|
||||
false)
|
||||
.flatMap(line -> fontRendererObj.listFormattedStringToWidth(line, width * 1 / 3).stream())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -194,30 +218,54 @@ public class GuiScreenConfigureChannels extends GuiScreen implements IGuiScreen
|
||||
actionPerformed(but);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<GuiButton> getButtonList() {
|
||||
public List<GuiButton> getButtonList() {
|
||||
return buttonList;
|
||||
}
|
||||
|
||||
private boolean isMouseOverValue() {
|
||||
int mx = Mouse.getEventX() * this.width / this.mc.displayWidth;
|
||||
int my = this.height - Mouse.getEventY() * this.height / this.mc.displayHeight - 1;
|
||||
return mx >= value.xPosition && mx < value.xPosition + value.width
|
||||
&& my >= value.yPosition
|
||||
&& my < value.yPosition + value.height;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMouseInput() {
|
||||
int delta = Mouse.getEventDWheel();
|
||||
if (delta != 0) list.handleDWheel(delta);
|
||||
if (delta != 0) {
|
||||
if (isMouseOverValue()) {
|
||||
if (delta > 0) {
|
||||
value.setText(String.valueOf(getValue() + 1));
|
||||
} else {
|
||||
value.setText(String.valueOf(Math.max(getValue() - 1, 1)));
|
||||
}
|
||||
} else {
|
||||
list.handleDWheel(delta);
|
||||
}
|
||||
}
|
||||
super.handleMouseInput();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void mouseClicked(int mX, int mY, int button) {
|
||||
super.mouseClicked(mX, mY, button);
|
||||
key.mouseClicked(mX, mY, button);
|
||||
value.mouseClicked(mX, mY, button);
|
||||
super.mouseClicked(mX, mY, button);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void keyTyped(char aChar, int aKey) {
|
||||
GuiScrollableList<?> l = key.isFocused() ? autoCompleteList : list;
|
||||
switch (aKey) {
|
||||
case Keyboard.KEY_TAB:
|
||||
if (key.isFocused()) {
|
||||
if (autoCompleteList.getSelectedElement() != null) {
|
||||
key.setText(autoCompleteList.getSelectedElement());
|
||||
return;
|
||||
}
|
||||
|
||||
key.setFocused(false);
|
||||
value.setFocused(true);
|
||||
} else {
|
||||
@@ -227,21 +275,38 @@ public class GuiScreenConfigureChannels extends GuiScreen implements IGuiScreen
|
||||
return;
|
||||
case Keyboard.KEY_RETURN:
|
||||
case Keyboard.KEY_NUMPADENTER:
|
||||
GuiButton add = getButtonList().get(0);
|
||||
if (key.isFocused() && autoCompleteList.getSelectedElement() != null) {
|
||||
key.setText(autoCompleteList.getSelectedElement());
|
||||
key.setFocused(false);
|
||||
value.setFocused(true);
|
||||
value.setText("1");
|
||||
value.setSelectionPos(0);
|
||||
return;
|
||||
}
|
||||
GuiButton add = getButtonList().get(ADD_BTN);
|
||||
if (add.enabled) doActionPerformed(add);
|
||||
return;
|
||||
case Keyboard.KEY_UP:
|
||||
if (list.selectedIndex > 0) list.setSelection(list.selectedIndex - 1);
|
||||
if (l.selectedIndex > 0) l.setSelection(l.selectedIndex - 1);
|
||||
return;
|
||||
case Keyboard.KEY_DOWN:
|
||||
if (list.selectedIndex < list.getNumElements() - 1) list.setSelection(list.selectedIndex + 1);
|
||||
if (l.selectedIndex < l.getNumElements() - 1) l.setSelection(l.selectedIndex + 1);
|
||||
return;
|
||||
case Keyboard.KEY_ESCAPE:
|
||||
if (key.isFocused()) {
|
||||
if (autoCompleteList.getSelectedElement() != null) {
|
||||
autoCompleteList.reset();
|
||||
} else {
|
||||
key.setFocused(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (key.textboxKeyTyped(aChar, aKey)) {
|
||||
if (key.textboxKeyTyped(aChar, aKey) || value.textboxKeyTyped(aChar, aKey)) {
|
||||
updateButtons();
|
||||
return;
|
||||
}
|
||||
if (value.textboxKeyTyped(aChar, aKey)) return;
|
||||
super.keyTyped(aChar, aKey);
|
||||
}
|
||||
|
||||
@@ -250,10 +315,14 @@ public class GuiScreenConfigureChannels extends GuiScreen implements IGuiScreen
|
||||
// STACKOVERFLOW!
|
||||
String keyText = key.getText();
|
||||
boolean existing = !StringUtils.isEmpty(keyText) && ChannelDataAccessor.hasSubChannel(trigger, keyText);
|
||||
getButtonList().get(0).displayString = existing ? I18n.format("item.structurelib.constructableTrigger.gui.set")
|
||||
: I18n.format("item.structurelib.constructableTrigger.gui.add");
|
||||
getButtonList().get(0).enabled = !StringUtils.isBlank(value.getText());
|
||||
getButtonList().get(1).enabled = existing && !StringUtils.isBlank(value.getText());
|
||||
getButtonList().get(ADD_BTN).displayString = existing ? I18n.format(I18N_PREFIX + "set")
|
||||
: I18n.format(I18N_PREFIX + "add");
|
||||
getButtonList().get(ADD_BTN).enabled = !StringUtils.isBlank(key.getText())
|
||||
&& !StringUtils.isBlank(value.getText())
|
||||
&& Integer.parseInt(value.getText()) > 0;
|
||||
getButtonList().get(UNSET_BTN).enabled = existing && !StringUtils.isBlank(value.getText());
|
||||
|
||||
autoCompleteList.reset();
|
||||
}
|
||||
|
||||
private int getValue() {
|
||||
@@ -268,18 +337,23 @@ public class GuiScreenConfigureChannels extends GuiScreen implements IGuiScreen
|
||||
protected void actionPerformed(GuiButton btn) {
|
||||
if (btn == null) return;
|
||||
switch (btn.id) {
|
||||
case 0:
|
||||
case ADD_BTN:
|
||||
int value = getValue();
|
||||
if (value <= 0) return;
|
||||
ChannelDataAccessor.setChannelData(trigger, key.getText(), value);
|
||||
break;
|
||||
case 1:
|
||||
case UNSET_BTN:
|
||||
ChannelDataAccessor.unsetChannelData(trigger, key.getText());
|
||||
break;
|
||||
case 2:
|
||||
case WIPE_BTN:
|
||||
ChannelDataAccessor.wipeChannelData(trigger);
|
||||
key.setText("");
|
||||
this.value.setText("");
|
||||
break;
|
||||
}
|
||||
|
||||
updateButtons();
|
||||
|
||||
super.actionPerformed(btn);
|
||||
}
|
||||
|
||||
@@ -298,32 +372,234 @@ public class GuiScreenConfigureChannels extends GuiScreen implements IGuiScreen
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doesGuiPauseGame() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawScreen(int mX, int mY, float partialTick) {
|
||||
public void drawGuiContainerBackgroundLayer(float partialTick, int mX, int mY) {
|
||||
mc.renderEngine.bindTexture(new ResourceLocation("structurelib", "textures/gui/channels.png"));
|
||||
int topLeftX = (this.width - this.getXSize()) / 2;
|
||||
int topLeftY = (this.height - this.getYSize()) / 2;
|
||||
drawTexturedModalRect(topLeftX, topLeftY, 0, 0, getXSize(), getYSize());
|
||||
super.drawScreen(mX, mY, partialTick);
|
||||
list.drawScreen(mX, mY, partialTick);
|
||||
fontRendererObj.drawString(
|
||||
I18n.format("item.structurelib.constructableTrigger.gui.key"),
|
||||
guiLeft + 12,
|
||||
guiTop + 122,
|
||||
0);
|
||||
fontRendererObj.drawString(I18n.format(I18N_PREFIX + "title"), guiLeft + 12, guiTop + 9, 0);
|
||||
fontRendererObj.drawString(I18n.format(I18N_PREFIX + "key"), guiLeft + 12, guiTop + 122, 0);
|
||||
key.drawTextBox();
|
||||
fontRendererObj.drawString(
|
||||
I18n.format("item.structurelib.constructableTrigger.gui.value"),
|
||||
guiLeft + 12,
|
||||
guiTop + 142,
|
||||
0);
|
||||
fontRendererObj.drawString(I18n.format(I18N_PREFIX + "value"), guiLeft + 12, guiTop + 142, 0);
|
||||
value.drawTextBox();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawGuiContainerForegroundLayer(int mouseX, int mouseY) {
|
||||
if (key.isFocused() && autoCompleteList.getNumElements() > 0) {
|
||||
autoCompleteList.drawScreen(mouseX, mouseY, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawScreen(int mouseX, int mouseY, float partialTicks) {
|
||||
super.drawScreen(mouseX, mouseY, partialTicks);
|
||||
String highlight = null;
|
||||
if (key.isFocused() && autoCompleteList.getNumElements() > 0) {
|
||||
highlight = autoCompleteList.getSelectedElement();
|
||||
}
|
||||
if (highlight == null && ChannelDescription.has(key.getText())) {
|
||||
highlight = key.getText();
|
||||
}
|
||||
if (highlight != null) {
|
||||
drawChannelDescriptionTooltip(highlight);
|
||||
}
|
||||
if (mouseX > xSize + guiLeft - INFO_BOX_SIZE - 12 && mouseX < xSize + guiLeft - 12
|
||||
&& mouseY > guiTop + 7
|
||||
&& mouseY < guiTop + INFO_BOX_SIZE + 7) {
|
||||
drawHoveringText(info, mouseX, mouseY, fontRendererObj);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawChannelDescriptionTooltip(String candidate) {
|
||||
List<String> tooltip = tooltipSplitCache.computeIfAbsent(candidate, this::getChannelDescriptionTooltip);
|
||||
drawHoveringText(tooltip, guiLeft + 151 + 12, guiTop + 122, fontRendererObj);
|
||||
}
|
||||
|
||||
private List<String> getChannelDescriptionTooltip(String candidate) {
|
||||
List<String> tooltip = new ArrayList<>();
|
||||
int maxLine = width - (guiLeft + 151 + 12) - 12;
|
||||
for (Entry<String, String> e : ChannelDescription.get(candidate).getDescriptions().entrySet()) {
|
||||
tooltip.addAll(
|
||||
fontRendererObj.listFormattedStringToWidth(
|
||||
StatCollector.translateToLocalFormatted(
|
||||
I18N_PREFIX + "channels.from",
|
||||
Loader.instance().getIndexedModList().get(e.getKey()).getName()),
|
||||
maxLine));
|
||||
tooltip.addAll(
|
||||
fontRendererObj.listFormattedStringToWidth(StatCollector.translateToLocal(e.getValue()), maxLine));
|
||||
}
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
public String trim(String str, int keyMaxWidth) {
|
||||
Map<String, String> cache = trimCache.get(keyMaxWidth);
|
||||
if (cache == null) {
|
||||
trimCache.put(keyMaxWidth, cache = new HashMap<>());
|
||||
}
|
||||
return cache.computeIfAbsent(str, e -> {
|
||||
String s = fontRendererObj.trimStringToWidth(e, keyMaxWidth);
|
||||
if (s.length() != e.length()) {
|
||||
StringBuilder buffer = new StringBuilder(s).deleteCharAt(s.length() - 1).append("...");
|
||||
while ((s = fontRendererObj.trimStringToWidth(buffer.toString(), keyMaxWidth)).length()
|
||||
!= buffer.length())
|
||||
// drop last original char
|
||||
buffer.deleteCharAt(s.length() - 4);
|
||||
do {
|
||||
buffer.append('.');
|
||||
} while (fontRendererObj.getStringWidth(buffer.toString()) <= keyMaxWidth);
|
||||
s = buffer.deleteCharAt(s.length() - 1).insert(s.length() - 3, EnumChatFormatting.GRAY).toString();
|
||||
}
|
||||
return s;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Optional.Method(modid = "NotEnoughItems")
|
||||
public VisiblityData modifyVisiblity(GuiContainer gui, VisiblityData currentVisibility) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Integer> getItemSpawnSlots(GuiContainer gui, ItemStack item) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TaggedInventoryArea> getInventoryAreas(GuiContainer gui) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleDragNDrop(GuiContainer gui, int mousex, int mousey, ItemStack draggedStack, int button) {
|
||||
if (button != 0 || mousex <= guiLeft
|
||||
|| mousey <= guiTop
|
||||
|| mousex >= guiLeft + getXSize()
|
||||
|| mousey >= guiTop + getYSize()) {
|
||||
return false;
|
||||
}
|
||||
Collection<Entry<String, Integer>> channels = ChannelDescription.iterate(draggedStack);
|
||||
switch (channels.size()) {
|
||||
case 0:
|
||||
return false;
|
||||
case 1:
|
||||
default: // TODO implement a GUI to select which pair to use, instead of blindly using the first
|
||||
Entry<String, Integer> e = channels.iterator().next();
|
||||
ChannelDataAccessor.setChannelData(trigger, e.getKey(), e.getValue());
|
||||
key.setText(e.getKey());
|
||||
value.setText(e.getValue().toString());
|
||||
draggedStack.stackSize = 0;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hideItemPanelSlot(GuiContainer gui, int x, int y, int w, int h) {
|
||||
return false;
|
||||
}
|
||||
|
||||
private class GuiAutoCompleteList extends GuiScrollableList<String> {
|
||||
|
||||
private List<Entry<String, String>> cache; // <key, display string>
|
||||
|
||||
public GuiAutoCompleteList(int width, int height, int originX, int originY, int slotHeight) {
|
||||
super(width, height, originX, originY, slotHeight);
|
||||
margin = 2;
|
||||
}
|
||||
|
||||
private List<Entry<String, String>> getOptions() {
|
||||
if (cache == null) {
|
||||
cache = search();
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
private List<Entry<String, String>> search() {
|
||||
List<Entry<String, String>> cache = new ArrayList<>();
|
||||
Set<String> added = new HashSet<>();
|
||||
String term = key.getText();
|
||||
String[] subjects = ChannelDescription.getAll().keySet().toArray(new String[0]);
|
||||
Arrays.sort(subjects);
|
||||
if (StringUtils.isBlank(term)) {
|
||||
// nothing. just gives out everything
|
||||
for (String subject : subjects) {
|
||||
cache.add(new AbstractMap.SimpleImmutableEntry<>(subject, EnumChatFormatting.GRAY + subject));
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
// prefix match
|
||||
for (String s : subjects) {
|
||||
if (s.startsWith(term)) {
|
||||
cache.add(
|
||||
new AbstractMap.SimpleImmutableEntry<>(
|
||||
s,
|
||||
term + EnumChatFormatting.GRAY + s.substring(term.length())));
|
||||
added.add(s);
|
||||
}
|
||||
}
|
||||
// subsequence match
|
||||
for (String subject : subjects) {
|
||||
if (!added.contains(subject) && subject.length() > term.length()) {
|
||||
int i = 0, j = 0;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean mode = true;
|
||||
sb.append(EnumChatFormatting.WHITE);
|
||||
while (i < term.length() && j < subject.length()) {
|
||||
if (term.codePointAt(i) == subject.codePointAt(j)) {
|
||||
if (!mode) {
|
||||
sb.append(EnumChatFormatting.WHITE);
|
||||
mode = true;
|
||||
}
|
||||
sb.appendCodePoint(term.codePointAt(i));
|
||||
i++;
|
||||
j++;
|
||||
} else {
|
||||
if (mode) {
|
||||
sb.append(EnumChatFormatting.GRAY);
|
||||
mode = false;
|
||||
}
|
||||
sb.appendCodePoint(subject.codePointAt(j));
|
||||
j++;
|
||||
}
|
||||
}
|
||||
if (i == term.length()) {
|
||||
if (j < subject.length()) {
|
||||
sb.append(EnumChatFormatting.GRAY);
|
||||
sb.append(subject, j, subject.length());
|
||||
}
|
||||
cache.add(new AbstractMap.SimpleImmutableEntry<>(subject, sb.toString()));
|
||||
added.add(subject);
|
||||
}
|
||||
}
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
cache = search();
|
||||
selectedIndex = cache.isEmpty() ? -1 : 0;
|
||||
resetScroll();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getElementAt(int index) {
|
||||
return index >= 0 && index < cache.size() ? getOptions().get(index).getKey() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumElements() {
|
||||
return getOptions().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawElement(int elementIndex, int x, int y, int height, Tessellator tessellator,
|
||||
boolean isHovering) {
|
||||
int color = elementIndex == selectedIndex ? 0xffffff : 0xcccccc;
|
||||
fontRendererObj.drawString(getOptions().get(elementIndex).getValue(), x + margin, y + margin / 2, color);
|
||||
}
|
||||
}
|
||||
|
||||
private class GuiChannelsList extends GuiScrollableList<Entry<String, Integer>> {
|
||||
|
||||
private List<Entry<String, Integer>> cache;
|
||||
@@ -350,6 +626,7 @@ public class GuiScreenConfigureChannels extends GuiScreen implements IGuiScreen
|
||||
Entry<String, Integer> e = getElementAt(elementIndex);
|
||||
if (e != null) {
|
||||
ChannelDataAccessor.unsetChannelData(trigger, e.getKey());
|
||||
updateButtons();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -390,22 +667,5 @@ public class GuiScreenConfigureChannels extends GuiScreen implements IGuiScreen
|
||||
// args x1, y1, y2, color
|
||||
drawVerticalLine(minX + margin + margin / 2 + KEY_MAX_WIDTH * 2, minY + 1, maxY - 1, 0xffaaaaaa);
|
||||
}
|
||||
|
||||
public String trim(String e, int keyMaxWidth) {
|
||||
// TODO optimize this to not create a billion string, or maybe cache this
|
||||
String s = fontRendererObj.trimStringToWidth(e, keyMaxWidth);
|
||||
if (s.length() != e.length()) {
|
||||
StringBuilder buffer = new StringBuilder(s).deleteCharAt(s.length() - 1).append("...");
|
||||
while ((s = fontRendererObj.trimStringToWidth(buffer.toString(), keyMaxWidth)).length()
|
||||
!= buffer.length())
|
||||
// drop last original char
|
||||
buffer.deleteCharAt(s.length() - 4);
|
||||
do {
|
||||
buffer.append('.');
|
||||
} while (fontRendererObj.getStringWidth(buffer.toString()) <= keyMaxWidth);
|
||||
s = buffer.deleteCharAt(s.length() - 1).insert(s.length() - 3, EnumChatFormatting.GRAY).toString();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import org.lwjgl.opengl.GL11;
|
||||
* <p>
|
||||
* As the code has been released into public domain, it is now LGPL as part of this library as a whole. You can go to
|
||||
* EnderCore's repository to obtain a public domain copy though
|
||||
*
|
||||
* <p>
|
||||
* Major edits
|
||||
* <ul>
|
||||
* <li>Separate dwheel handling to handleDWheel. The next event calls often cause the mouse click event to not fire on
|
||||
@@ -167,10 +167,17 @@ public abstract class GuiScrollableList<T> {
|
||||
}
|
||||
}
|
||||
|
||||
protected void resetScroll() {
|
||||
amountScrolled = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* draws the slot to the screen, pass in mouse's current x and y and partial ticks
|
||||
*/
|
||||
public void drawScreen(int mX, int mY, float partialTick) {
|
||||
GL11.glPushMatrix();
|
||||
GL11.glLoadIdentity();
|
||||
GL11.glTranslatef(0.0F, 0.0F, -2000.0F);
|
||||
this.mouseX = mX;
|
||||
this.mouseY = mY;
|
||||
|
||||
@@ -199,13 +206,26 @@ public abstract class GuiScrollableList<T> {
|
||||
int contentYOffset = this.minY + margin - amountScrolled;
|
||||
|
||||
boolean hoveringPrecondition = mX >= minX && mX <= maxX && mY >= minY && mY <= maxY;
|
||||
int slotHeight = this.slotHeight - margin;
|
||||
|
||||
if (selectedIndex != -1) {
|
||||
int elementY = contentYOffset + selectedIndex * this.slotHeight;
|
||||
if (elementY + slotHeight > maxY) {
|
||||
this.amountScrolled = (selectedIndex + 1) * this.slotHeight - this.height + margin;
|
||||
amountScrolled = (int) this.amountScrolled;
|
||||
contentYOffset = this.minY + margin - amountScrolled;
|
||||
} else if (elementY < minY) {
|
||||
this.amountScrolled = selectedIndex * this.slotHeight;
|
||||
amountScrolled = (int) this.amountScrolled;
|
||||
contentYOffset = this.minY + margin - amountScrolled;
|
||||
}
|
||||
}
|
||||
|
||||
prepareDrawElements();
|
||||
|
||||
for (int i = 0; i < getNumElements(); ++i) {
|
||||
|
||||
int elementY = contentYOffset + i * this.slotHeight;
|
||||
int slotHeight = this.slotHeight - margin;
|
||||
|
||||
if (elementY <= maxY && elementY + slotHeight >= minY) {
|
||||
|
||||
@@ -264,6 +284,7 @@ public abstract class GuiScrollableList<T> {
|
||||
GL11.glShadeModel(GL11.GL_FLAT);
|
||||
GL11.glEnable(GL11.GL_ALPHA_TEST);
|
||||
GL11.glDisable(GL11.GL_BLEND);
|
||||
GL11.glPopMatrix();
|
||||
}
|
||||
|
||||
protected void prepareDrawElements() {}
|
||||
@@ -412,7 +433,7 @@ public abstract class GuiScrollableList<T> {
|
||||
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
tess.startDrawingQuads();
|
||||
tess.setColorOpaque_I(2105376);
|
||||
tess.setColorOpaque_I(0x202020);
|
||||
tess.addVertex(minX, maxY, 0.0D);
|
||||
tess.addVertex(maxX, maxY, 0.0D);
|
||||
tess.addVertex(maxX, minY, 0.0D);
|
||||
|
||||
@@ -28,8 +28,14 @@ public class ItemConstructableTrigger extends Item {
|
||||
|
||||
@Override
|
||||
public ItemStack onItemRightClick(ItemStack stack, World world, EntityPlayer player) {
|
||||
if (world.isRemote && getMovingObjectPositionFromPlayer(world, player, true) == null)
|
||||
player.openGui(StructureLib.instance(), 0, world, player.inventory.currentItem, 0, 0);
|
||||
if (getMovingObjectPositionFromPlayer(world, player, true) == null) {
|
||||
// channel gui is opened from server and config gui from client
|
||||
if (!world.isRemote && !player.isSneaking()) {
|
||||
player.openGui(StructureLib.instance(), 0, world, player.inventory.currentItem, 0, 0);
|
||||
} else if (world.isRemote && player.isSneaking()) {
|
||||
StructureLib.instance().proxy().displayConfigGUI("registries");
|
||||
}
|
||||
}
|
||||
return super.onItemRightClick(stack, world, player);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ public class ItemFrontRotationTool extends Item {
|
||||
|
||||
@Override
|
||||
@SideOnly(Side.CLIENT)
|
||||
public void addInformation(ItemStack p_77624_1_, EntityPlayer p_77624_2_, List aList, boolean p_77624_4_) {
|
||||
public void addInformation(ItemStack p_77624_1_, EntityPlayer p_77624_2_, List<String> aList, boolean p_77624_4_) {
|
||||
aList.add(translateToLocal("item.structurelib.frontRotationTool.desc.0")); // Triggers Front Rotation Interface
|
||||
aList.add(EnumChatFormatting.BLUE + translateToLocal("item.structurelib.frontRotationTool.desc.1")); // Rotates
|
||||
// only the
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.gtnewhorizon.structurelib.mixins;
|
||||
|
||||
import cpw.mods.fml.common.Mod;
|
||||
|
||||
public enum TargetedMod {
|
||||
|
||||
VANILLA("Minecraft", null);
|
||||
|
||||
/** The "name" in the {@link Mod @Mod} annotation */
|
||||
public final String modName;
|
||||
/** Class that implements the IFMLLoadingPlugin interface */
|
||||
public final String coreModClass;
|
||||
/** The "modid" in the {@link Mod @Mod} annotation */
|
||||
public final String modId;
|
||||
|
||||
TargetedMod(String modName, String coreModClass) {
|
||||
this(modName, coreModClass, null);
|
||||
}
|
||||
|
||||
TargetedMod(String modName, String coreModClass, String modId) {
|
||||
this.modName = modName;
|
||||
this.coreModClass = coreModClass;
|
||||
this.modId = modId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TargetedMod{modName='" + modName + "', coreModClass='" + coreModClass + "', modId='" + modId + "'}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.gtnewhorizon.structurelib.mixins.early.blockChangeNotifier;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.ForgeHooks;
|
||||
import net.minecraftforge.common.util.BlockSnapshot;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import com.gtnewhorizon.structurelib.event.BlockChangeNotifier;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
|
||||
@Mixin(ForgeHooks.class)
|
||||
public class MixinForgeHooks {
|
||||
|
||||
@Inject(
|
||||
method = "onPlaceItemIntoWorld",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/World;markAndNotifyBlock(IIILnet/minecraft/world/chunk/Chunk;Lnet/minecraft/block/Block;Lnet/minecraft/block/Block;I)V",
|
||||
shift = At.Shift.AFTER),
|
||||
remap = false)
|
||||
private static void onBlockChange(ItemStack itemstack, EntityPlayer player, World world, int x, int y, int z,
|
||||
int side, float hitX, float hitY, float hitZ, CallbackInfoReturnable<Boolean> cir,
|
||||
@Local(name = "blocksnapshot") BlockSnapshot blocksnapshot, @Local(name = "newBlock") Block newBlock,
|
||||
@Local(name = "metadata") int newMetadata) {
|
||||
BlockChangeNotifier.onBlockChange(
|
||||
world,
|
||||
null,
|
||||
blocksnapshot.x,
|
||||
blocksnapshot.y,
|
||||
blocksnapshot.z,
|
||||
blocksnapshot.replacedBlock,
|
||||
newBlock,
|
||||
blocksnapshot.meta,
|
||||
newMetadata);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package com.gtnewhorizon.structurelib.mixins.early.blockChangeNotifier;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraft.world.chunk.Chunk;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import com.gtnewhorizon.structurelib.event.BlockChangeNotifier;
|
||||
import com.llamalad7.mixinextras.sugar.Local;
|
||||
import com.llamalad7.mixinextras.sugar.Share;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalIntRef;
|
||||
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
|
||||
|
||||
@Mixin(World.class)
|
||||
public class MixinWorld {
|
||||
|
||||
@Inject(
|
||||
method = "Lnet/minecraft/world/World;setBlock(IIILnet/minecraft/block/Block;II)Z",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/World;markAndNotifyBlock(IIILnet/minecraft/world/chunk/Chunk;Lnet/minecraft/block/Block;Lnet/minecraft/block/Block;I)V",
|
||||
shift = At.Shift.AFTER,
|
||||
remap = false))
|
||||
public void setBlockObserve(int x, int y, int z, Block newBlock, int newMeta, int flags,
|
||||
CallbackInfoReturnable<Boolean> cir, @Local(name = "block1") Block originalBlock,
|
||||
@Share("originalMeta") LocalIntRef originalMeta, @Share("chunk") LocalRef<Chunk> chunkLocalRef) {
|
||||
BlockChangeNotifier.onBlockChange(
|
||||
(World) ((Object) (this)),
|
||||
chunkLocalRef.get(),
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
originalBlock,
|
||||
newBlock,
|
||||
originalMeta.get(),
|
||||
newMeta);
|
||||
}
|
||||
|
||||
@Inject(
|
||||
method = "Lnet/minecraft/world/World;setBlock(IIILnet/minecraft/block/Block;II)Z",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/world/chunk/Chunk;func_150807_a(IIILnet/minecraft/block/Block;I)Z",
|
||||
shift = At.Shift.BEFORE))
|
||||
public void captureChunkAndOriginalMetadata(int x, int y, int z, Block blockIn, int metadataIn, int flags,
|
||||
CallbackInfoReturnable<Boolean> cir, @Local(name = "chunk") Chunk chunk,
|
||||
@Share("originalMeta") LocalIntRef originalMeta, @Share("chunk") LocalRef<Chunk> chunkLocalRef) {
|
||||
chunkLocalRef.set(chunk);
|
||||
originalMeta.set(chunk.getBlockMetadata(x & 15, y, z & 15));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.gtnewhorizon.structurelib.net;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.gtnewhorizon.structurelib.SortedRegistry;
|
||||
|
||||
import cpw.mods.fml.common.network.ByteBufUtils;
|
||||
import cpw.mods.fml.common.network.simpleimpl.IMessage;
|
||||
import cpw.mods.fml.common.network.simpleimpl.IMessageHandler;
|
||||
import cpw.mods.fml.common.network.simpleimpl.MessageContext;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class RegistryOrderSyncMessage implements IMessage {
|
||||
|
||||
private String name;
|
||||
private List<String> order;
|
||||
private List<String> disabled;
|
||||
|
||||
public RegistryOrderSyncMessage() {}
|
||||
|
||||
public RegistryOrderSyncMessage(String name, List<String> order, List<String> disabled) {
|
||||
this.name = name;
|
||||
this.order = order;
|
||||
this.disabled = disabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fromBytes(ByteBuf buf) {
|
||||
name = ByteBufUtils.readUTF8String(buf);
|
||||
int size = ByteBufUtils.readVarInt(buf, 2);
|
||||
order = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
order.add(ByteBufUtils.readUTF8String(buf));
|
||||
}
|
||||
size = ByteBufUtils.readVarInt(buf, 2);
|
||||
disabled = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
disabled.add(ByteBufUtils.readUTF8String(buf));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(ByteBuf buf) {
|
||||
ByteBufUtils.writeUTF8String(buf, name);
|
||||
ByteBufUtils.writeVarInt(buf, order.size(), 2);
|
||||
for (String s : order) {
|
||||
ByteBufUtils.writeUTF8String(buf, s);
|
||||
}
|
||||
ByteBufUtils.writeVarInt(buf, disabled.size(), 2);
|
||||
for (String s : disabled) {
|
||||
ByteBufUtils.writeUTF8String(buf, s);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Handler implements IMessageHandler<RegistryOrderSyncMessage, IMessage> {
|
||||
|
||||
private static ConcurrentHashMap<String, WeakReference<SortedRegistry<?>>> ALL_REGISTRIES;
|
||||
|
||||
public static void setAllRegistries(ConcurrentHashMap<String, WeakReference<SortedRegistry<?>>> r) {
|
||||
if (ALL_REGISTRIES == null) ALL_REGISTRIES = r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IMessage onMessage(RegistryOrderSyncMessage message, MessageContext ctx) {
|
||||
if (ALL_REGISTRIES == null) return null;
|
||||
WeakReference<SortedRegistry<?>> ref = ALL_REGISTRIES.get(message.name);
|
||||
if (ref == null) return null;
|
||||
SortedRegistry<?> r = ref.get();
|
||||
if (r == null) return null;
|
||||
r.registerOrdering(ctx.getServerHandler().playerEntity, message.order, message.disabled);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import net.minecraft.world.World;
|
||||
|
||||
import com.gtnewhorizon.structurelib.StructureLib;
|
||||
import com.gtnewhorizon.structurelib.StructureLibAPI;
|
||||
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
|
||||
import com.gtnewhorizon.structurelib.alignment.constructable.ISurvivalConstructable;
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
|
||||
|
||||
@@ -50,7 +51,7 @@ import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
|
||||
* itself.</li>
|
||||
* <li>The "C" position would be the number of blocks between you and controller, not counting controller itself.</li>
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
* @param <T> Type of the context object. Usually this will be the multiblock controller.
|
||||
*/
|
||||
public interface IStructureDefinition<T> {
|
||||
@@ -72,7 +73,7 @@ public interface IStructureDefinition<T> {
|
||||
|
||||
/**
|
||||
* Run a structure check.
|
||||
*
|
||||
*
|
||||
* @param object context object. usually multiblock controller.
|
||||
* @param piece the structure piece's string identifier.
|
||||
* @param world the world object this check takes place in.
|
||||
@@ -107,7 +108,7 @@ public interface IStructureDefinition<T> {
|
||||
|
||||
/**
|
||||
* Spawn hint particles. Should not be called on server side.
|
||||
*
|
||||
*
|
||||
* @param object context object. usually multiblock controller.
|
||||
* @param piece the structure piece's string identifier.
|
||||
* @param world the world object this check takes place in.
|
||||
@@ -318,9 +319,25 @@ public interface IStructureDefinition<T> {
|
||||
return walker.getBuilt();
|
||||
}
|
||||
|
||||
default void iterate(String piece, World world, ExtendedFacing extendedFacing, int basePositionX, int basePositionY,
|
||||
int basePositionZ, int basePositionA, int basePositionB, int basePositionC, IStructureWalker<T> predicate) {
|
||||
StructureUtility.iterateV2(
|
||||
getStructureFor(piece),
|
||||
world,
|
||||
extendedFacing,
|
||||
basePositionX,
|
||||
basePositionY,
|
||||
basePositionZ,
|
||||
basePositionA,
|
||||
basePositionB,
|
||||
basePositionC,
|
||||
predicate,
|
||||
"customIterate");
|
||||
}
|
||||
|
||||
/**
|
||||
* Low level utility.
|
||||
*
|
||||
*
|
||||
* @param object context object. usually multiblock controller.
|
||||
* @param trigger The trigger item that contains channel data.
|
||||
* @param elements the structure piece
|
||||
@@ -394,6 +411,10 @@ public interface IStructureDefinition<T> {
|
||||
basePositionC,
|
||||
ignoreBlockUnloaded((e, w, x, y, z, a, b, c) -> {
|
||||
e.spawnHint(object, world, x, y, z, trigger);
|
||||
if (ChannelDataAccessor.hasSubChannel(trigger, StructureLibAPI.CHANNEL_SHOW_ERROR)
|
||||
&& !e.couldBeValid(object, world, x, y, z, trigger)) {
|
||||
StructureLibAPI.markHintParticleError(StructureLib.getCurrentPlayer(), world, x, y, z);
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
"spawnHint");
|
||||
@@ -420,7 +441,7 @@ public interface IStructureDefinition<T> {
|
||||
|
||||
/**
|
||||
* Create a new instance of builder.
|
||||
*
|
||||
*
|
||||
* @param <T> type of context object
|
||||
*/
|
||||
static <T> StructureDefinition.Builder<T> builder() {
|
||||
|
||||
@@ -5,6 +5,7 @@ import static com.gtnewhorizon.structurelib.StructureLib.PANIC_MODE;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@@ -32,6 +33,38 @@ public interface IStructureElement<T> {
|
||||
|
||||
boolean check(T t, World world, int x, int y, int z);
|
||||
|
||||
/**
|
||||
* Pure (stateless and side effect free) function to test if current block could be valid. Used to give user hints
|
||||
* about incorrectly placed blocks given a controller and trigger item. If couldBeValid(...) == false for a set of
|
||||
* channels, with those same tiers, check(...) == false. Not required, defaults to true which is always safe but
|
||||
* won't give the user error hints.
|
||||
*/
|
||||
default boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a human-readable description of what block(s) this element accepts. Used for diagnostic messages when
|
||||
* structure checks fail (e.g. "expected Input Bus at (1, 2, 3)").
|
||||
* <p>
|
||||
* Unlike {@link #getBlocksToPlace}, which returns the full set of concrete block variants for autoplacing (e.g.
|
||||
* every tier of Input Bus), this method returns <b>category-level</b> descriptions suitable for display to the
|
||||
* player (e.g. just "Input Bus"). It also requires no parameters, no world, trigger item, or environment, making it
|
||||
* usable in contexts where those are unavailable.
|
||||
* <p>
|
||||
* Implementations should return <b>lang keys</b> (e.g. {@code "tile.blockGlass.name"}) rather than pre-translated
|
||||
* display names. The caller is responsible for translating these keys on the client side via
|
||||
* {@code StatCollector.translateToLocal()}. If a key has no matching translation, {@code translateToLocal} returns
|
||||
* the input string unchanged, so pre-translated fallback strings also work.
|
||||
*
|
||||
* @return a list of accepted block descriptions as lang keys (or display names as fallback), or null if no
|
||||
* description is available
|
||||
*/
|
||||
@Nullable
|
||||
default List<String> getDescription() {
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger);
|
||||
|
||||
boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger);
|
||||
@@ -185,10 +218,21 @@ public interface IStructureElement<T> {
|
||||
return IStructureElement.this.check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return IStructureElement.this.couldBeValid(t, world, x, y, z, trigger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return IStructureElement.this.spawnHint(t, world, x, y, z, trigger);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> getDescription() {
|
||||
return IStructureElement.this.getDescription();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@@ -31,6 +33,16 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
for (IStructureElement<T> fallback : fallbacks()) {
|
||||
if (fallback.couldBeValid(t, world, x, y, z, trigger)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
for (IStructureElement<T> fallback : fallbacks()) {
|
||||
@@ -51,6 +63,19 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
default List<String> getDescription() {
|
||||
Set<String> descriptions = new LinkedHashSet<>();
|
||||
for (IStructureElement<T> fallback : fallbacks()) {
|
||||
List<String> desc = fallback.getDescription();
|
||||
if (desc != null) {
|
||||
descriptions.addAll(desc);
|
||||
}
|
||||
}
|
||||
return descriptions.isEmpty() ? null : new ArrayList<>(descriptions);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
default BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
|
||||
@@ -62,7 +87,8 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
|
||||
if (e == null) continue;
|
||||
if (predicate == null) predicate = e.getPredicate();
|
||||
else predicate = predicate.or(e.getPredicate());
|
||||
is.add(e.getStacks());
|
||||
Iterable<ItemStack> stacks = e.getStacks();
|
||||
if (stacks != null) is.add(stacks);
|
||||
}
|
||||
if (predicate == null) return null;
|
||||
return new BlocksToPlace(predicate, Iterables.concat(is));
|
||||
|
||||
@@ -14,6 +14,11 @@ public interface IStructureElementCheckOnly<T> extends IStructureElement<T> {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return false;
|
||||
|
||||
@@ -17,6 +17,11 @@ interface IStructureNavigate<T> extends IStructureElement<T> {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return true;
|
||||
|
||||
@@ -2,8 +2,25 @@ package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
import net.minecraft.world.World;
|
||||
|
||||
interface IStructureWalker<T> {
|
||||
/**
|
||||
* A functional interface that is used to iterate over an arbitrary structure.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface IStructureWalker<T> {
|
||||
|
||||
/**
|
||||
* Called once per structure element.
|
||||
*
|
||||
* @param element The current element
|
||||
* @param world The current world
|
||||
* @param x The absolute X position of the element in world coordinates
|
||||
* @param y The absolute Y position of the element in world coordinates
|
||||
* @param z The absolute Z position of the element in world coordinates
|
||||
* @param a The relative A position of the element in structure-local coordinates (pre transform)
|
||||
* @param b The relative B position of the element in structure-local coordinates (pre transform)
|
||||
* @param c The relative C position of the element in structure-local coordinates (pre transform)
|
||||
* @return False to stop iterating, true to continue.
|
||||
*/
|
||||
boolean visit(IStructureElement<T> element, World world, int x, int y, int z, int a, int b, int c);
|
||||
|
||||
default boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z, int a, int b,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
@@ -32,6 +33,11 @@ class LazyStructureElement<T> implements IStructureElementDeferred<T> {
|
||||
return get(t).check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return get(t).couldBeValid(t, world, x, y, z, trigger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return get(t).placeBlock(t, world, x, y, z, trigger);
|
||||
@@ -60,4 +66,10 @@ class LazyStructureElement<T> implements IStructureElementDeferred<T> {
|
||||
AutoPlaceEnvironment env) {
|
||||
return get(t).survivalPlaceBlock(t, world, x, y, z, trigger, env);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> getDescription() {
|
||||
return elem != null ? elem.getDescription() : null;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,15 @@
|
||||
package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
import static com.gtnewhorizon.structurelib.structure.StructureUtility.*;
|
||||
import static com.gtnewhorizon.structurelib.structure.StructureUtility.isAir;
|
||||
import static com.gtnewhorizon.structurelib.structure.StructureUtility.notAir;
|
||||
import static com.gtnewhorizon.structurelib.structure.StructureUtility.step;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.gtnewhorizon.structurelib.util.Vec3Impl;
|
||||
@@ -141,15 +148,32 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
|
||||
occupiedSpaces.put(name, occupiedSpace);
|
||||
|
||||
String built = builder.toString().replaceAll("[\\uA000\\uB000\\uC000]", "");
|
||||
boolean hasPlus = false;
|
||||
boolean hasMinus = false;
|
||||
|
||||
if (built.contains("+")) {
|
||||
int write = 0;
|
||||
for (int read = 0; read < builder.length(); read++) {
|
||||
char ch = builder.charAt(read);
|
||||
if (ch == A || ch == B || ch == C) {
|
||||
continue;
|
||||
}
|
||||
if (ch == '+') {
|
||||
hasPlus = true;
|
||||
} else if (ch == '-') {
|
||||
hasMinus = true;
|
||||
}
|
||||
if (read != write) builder.setCharAt(write, ch);
|
||||
write++;
|
||||
}
|
||||
builder.setLength(write);
|
||||
|
||||
if (hasPlus) {
|
||||
addElement('+', notAir());
|
||||
}
|
||||
if (built.contains("-")) {
|
||||
if (hasMinus) {
|
||||
addElement('-', isAir());
|
||||
}
|
||||
shapes.put(name, built);
|
||||
shapes.put(name, builder.toString());
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,29 @@
|
||||
package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
import static com.gtnewhorizon.structurelib.StructureLib.LOGGER;
|
||||
import static com.gtnewhorizon.structurelib.StructureLib.PANIC_MODE;
|
||||
import static java.lang.Integer.MIN_VALUE;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.*;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -17,7 +36,10 @@ import net.minecraft.item.ItemBlock;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.ChatComponentText;
|
||||
import net.minecraft.util.ChatComponentTranslation;
|
||||
import net.minecraft.util.ChatStyle;
|
||||
import net.minecraft.util.EnumChatFormatting;
|
||||
import net.minecraft.util.IChatComponent;
|
||||
import net.minecraft.util.IIcon;
|
||||
import net.minecraft.world.World;
|
||||
@@ -138,6 +160,11 @@ public class StructureUtility {
|
||||
return world.isAirBlock(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(Object t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean spawnHint(Object o, World world, int x, int y, int z, ItemStack trigger) {
|
||||
StructureLibAPI.hintParticle(world, x, y, z, StructureLibAPI.getBlockHint(), 13);
|
||||
@@ -169,6 +196,11 @@ public class StructureUtility {
|
||||
return !world.isAirBlock(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(Object t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean spawnHint(Object o, World world, int x, int y, int z, ItemStack trigger) {
|
||||
StructureLibAPI.hintParticle(world, x, y, z, StructureLibAPI.getBlockHint(), 14);
|
||||
@@ -208,6 +240,11 @@ public class StructureUtility {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(Object t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean spawnHint(Object o, World world, int x, int y, int z, ItemStack trigger) {
|
||||
StructureLibAPI.hintParticle(world, x, y, z, StructureLibAPI.getBlockHint(), 15);
|
||||
@@ -301,18 +338,22 @@ public class StructureUtility {
|
||||
int itemMeta = itemBlock instanceof ISpecialItemBlock
|
||||
? ((ISpecialItemBlock) itemBlock).getItemMetaFromBlockMeta(block, meta)
|
||||
: meta;
|
||||
if (!s.takeOne(new ItemStack(itemBlock, 1, itemMeta), false)) {
|
||||
ItemStack stack = new ItemStack(itemBlock, 1, itemMeta);
|
||||
if (!s.takeOne(stack, true)) {
|
||||
if (chatter != null) chatter.accept(
|
||||
new ChatComponentTranslation(
|
||||
"structurelib.autoplace.error.no_simple_block",
|
||||
new ItemStack(itemBlock, 1, itemMeta).func_151000_E()));
|
||||
new ChatComponentTranslation("structurelib.autoplace.missing_block", stack.func_151000_E()));
|
||||
return PlaceResult.REJECT;
|
||||
}
|
||||
if (block instanceof ICustomBlockSetting) {
|
||||
ICustomBlockSetting block2 = (ICustomBlockSetting) block;
|
||||
block2.setBlock(world, x, y, z, meta);
|
||||
} else {
|
||||
world.setBlock(x, y, z, block, meta, 2);
|
||||
ICustomBlockSetting blockCustom = (ICustomBlockSetting) block;
|
||||
blockCustom.setBlock(world, x, y, z, meta);
|
||||
} else if (!stack.copy()
|
||||
.tryPlaceItemIntoWorld(actor, world, x, y, z, ForgeDirection.UP.ordinal(), 0.5f, 0.5f, 0.5f)) {
|
||||
return PlaceResult.REJECT;
|
||||
}
|
||||
if (!s.takeOne(stack, false)) {
|
||||
// rollback
|
||||
world.setBlockToAir(x, y, z);
|
||||
}
|
||||
return PlaceResult.ACCEPT;
|
||||
}
|
||||
@@ -407,7 +448,7 @@ public class StructureUtility {
|
||||
if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actor)) return PlaceResult.REJECT;
|
||||
if (!assumeStackPresent && !s.takeOne(stack, true)) {
|
||||
if (chatter != null) chatter.accept(
|
||||
new ChatComponentTranslation("structurelib.autoplace.error.no_item_stack", stack.func_151000_E()));
|
||||
new ChatComponentTranslation("structurelib.autoplace.missing_block", stack.func_151000_E()));
|
||||
return PlaceResult.REJECT;
|
||||
}
|
||||
if (!stack.copy().tryPlaceItemIntoWorld(actor, world, x, y, z, ForgeDirection.UP.ordinal(), 0.5f, 0.5f, 0.5f))
|
||||
@@ -462,6 +503,11 @@ public class StructureUtility {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
StructureLibAPI.hintParticle(world, x, y, z, StructureLibAPI.getBlockHint(), meta);
|
||||
@@ -482,6 +528,11 @@ public class StructureUtility {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
StructureLibAPI.hintParticle(world, x, y, z, icons.get());
|
||||
@@ -502,6 +553,11 @@ public class StructureUtility {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
StructureLibAPI.hintParticleTinted(world, x, y, z, icons.get(), RGBa);
|
||||
@@ -539,7 +595,7 @@ public class StructureUtility {
|
||||
TIER current = getter.apply(t);
|
||||
if (Objects.equals(notSet, current)) {
|
||||
if (Objects.equals(notSet, tier)) {
|
||||
if (StructureLib.PANIC_MODE) {
|
||||
if (PANIC_MODE) {
|
||||
throw new AssertionError("tierExtractor should never return notSet: " + notSet);
|
||||
} else {
|
||||
StructureLib.LOGGER.error("#########################################");
|
||||
@@ -559,32 +615,35 @@ public class StructureUtility {
|
||||
|
||||
/**
|
||||
* Element representing a component with different tiers. Your multi will accept any of them (as long as player does
|
||||
* not mix them (*)), but would like to know which is used. Player can use more blueprint to get hints denoting more
|
||||
* not mix tier (*)), but would like to know which is used. Player can use more blueprint to get hints denoting more
|
||||
* advanced components.
|
||||
* <p>
|
||||
* (*): Implementation wise, this structure element will only accept a block if its tier is the same as existing
|
||||
* tier or if existing tier is unset (i.e. value of third argument)
|
||||
* <p>
|
||||
* Above all else, you need to decide what tier you are going to use. For simpler cases, Integer is a good choice.
|
||||
* You can also use an existing Enum. Tier can never be null though. It can also be a String or a
|
||||
* Above all else, you need to decide what type of tier you are going to use. For simpler cases, Integer is a good
|
||||
* choice. You can also use an existing Enum. It can also be a String or a
|
||||
* {@link net.minecraft.util.ResourceLocation}, or basically anything. It doesn't even have to implement
|
||||
* {@link Comparable}.
|
||||
* <p>
|
||||
* This assumes you will reset the backing storage, and on the first occurrence getter would return notSet. You can
|
||||
* also make getter to return other value to forcefully select a certain tier, but you're probably better off using
|
||||
* block adders or {@link #ofBlock(Block, int, Block, int)} and its overloads
|
||||
* If your tier extractor function returns null on any (Block, meta) pair, then it means this block is rejected.
|
||||
* This also implies null can never be a valid tier.
|
||||
* <p>
|
||||
* Implementation wise, this allows a block only when we don't yet have a tier, or the block at that location has
|
||||
* the same tier as we already have.
|
||||
* This structure element require you will reset the backing storage of getter in your own code, such that in every
|
||||
* round of structure checking, the first call to getter would always return notSet. You can also make getter to
|
||||
* return other value on that occasion to forcefully select a certain tier, but then you'd probably be better off
|
||||
* using block adders or {@link #ofBlock(Block, int, Block, int)} and its overloads
|
||||
* <p>
|
||||
* There is yet no TileEntity counterpart of this utility. Feel free to submit a PR to add it.
|
||||
* <p>
|
||||
* For most typical use cases, you will NOT want to return notSet from your tierExtractor. If you do so, we will
|
||||
* have this kind of chain of events:
|
||||
* (*): Implementation wise, this structure element will only accept a block if its tier is the same as existing
|
||||
* tier or if existing tier is unset (i.e. value of third argument). If there are multiple (block, tier) tuple for a
|
||||
* given tier, then player will be allowed to use multiple kinds of blocks, but their tier is guaranteed to be the
|
||||
* same.
|
||||
* <p>
|
||||
* <h3>WARNING</h3> <b>You SHOULD NOT return notSet from your tierExtractor.</b> If you do so, we will have this
|
||||
* chain of events:
|
||||
* <ul>
|
||||
* <li>at check start, tier is reset to notSet
|
||||
* <li>at first occurrence, ofBlocksTiered finds your current tier is notSet, and the block's tier is notSet, so
|
||||
* it's accepted
|
||||
* <li>the tier extractor returns notSet for first block, so since ofBlocksTiered finds your current tier is notSet,
|
||||
* and the new block's tier is the same as that, so it's accepted
|
||||
* <li>at 2nd, 3rd..., same thing in 2 happens
|
||||
* <li>at last occurrence, player placed a valid block with a tier not notSet. ofBlocksTiered now sets your current
|
||||
* tier to this tier, and accept the block
|
||||
@@ -592,10 +651,10 @@ public class StructureUtility {
|
||||
* doing its business
|
||||
* <li>Player enjoy a (probably) hilariously shaped multi and (probably) reduced build cost.
|
||||
* </ul>
|
||||
* <h3>Example Implementation</h3>
|
||||
* <p>
|
||||
* <b>Example:</b>
|
||||
* <p>
|
||||
* Assume you have 16 tier, each map to one block's 16 different meta. You will usually want something like this
|
||||
* Assume you have 16 tier, each map to one particular block's 16 different meta. You will usually want something
|
||||
* like this
|
||||
*
|
||||
* <pre>
|
||||
* public class Tile extends TileEntity {
|
||||
@@ -619,22 +678,48 @@ public class StructureUtility {
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* Important bits of this example implementation:
|
||||
* <ul>
|
||||
* <li>The supplied ITierConverter returns null for an invalid block</li>
|
||||
* <li>Possible tier returned by ITierConverter is Integer object 0 to 15.</li>
|
||||
* <li>notSet is set to -1. tier got reset to this value by caller before calling into
|
||||
* {@link IStructureDefinition#check(Object, String, World, ExtendedFacing, int, int, int, int, int, int, boolean)}</li>
|
||||
* <li>notSet is never a tier returned by ITierConverter.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* As a friendly reminder, the above characteristics are only for this simply demo, not as a general restriction
|
||||
* imposed by ofBlocksTiered.
|
||||
*
|
||||
* @param tierExtractor a function to extract tier info from a block. This function can return null and will never
|
||||
* be passed a null block or an invalid block meta.
|
||||
* be passed a null block or an invalid block meta. If this function returns null, then the
|
||||
* block would be considered invalid.
|
||||
* @param allKnownTiers A list of all known tiers as of calling. Can be empty or null. No hint will be spawned if
|
||||
* empty or null. Cannot have null elements. First element denotes the most primitive tier.
|
||||
* Last element denotes the most advanced tier. If not all tiers are available at definition
|
||||
* construction time, use {@link #lazy(Supplier)} or its overloads to delay a bit. This list is
|
||||
* only for hint particle spawning and survival build. The hint/autoplace code will choose 1st
|
||||
* pair to spawn/place if trigger item has master channel data of 1, 2nd pair if 2, and so on.
|
||||
* @param notSet The value returned from {@code getter} when there were no tier info found in T yet. Can be
|
||||
* null.
|
||||
* @param notSet The value returned from {@code getter} when there were no tier info found in T yet. Cannot
|
||||
* be null.
|
||||
* @param getter a function to retrieve the current tier from context object
|
||||
* @param setter a function to set the current tier into context object
|
||||
*/
|
||||
public static <T, TIER> IStructureElement<T> ofBlocksTiered(ITierConverter<TIER> tierExtractor,
|
||||
@Nullable List<Pair<Block, Integer>> allKnownTiers, @Nullable TIER notSet, BiConsumer<T, TIER> setter,
|
||||
Function<T, TIER> getter) {
|
||||
return ofBlocksTiered(tierExtractor, allKnownTiers, notSet, setter, getter, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link #ofBlocksTiered(ITierConverter, List, Object, BiConsumer, Function)}, but with an explicit
|
||||
* description that will be returned by {@link IStructureElement#getDescription()}.
|
||||
*
|
||||
* @param description the description lang keys to attach to this element, or null for no description
|
||||
* @see #ofBlocksTiered(ITierConverter, List, Object, BiConsumer, Function)
|
||||
*/
|
||||
public static <T, TIER> IStructureElement<T> ofBlocksTiered(ITierConverter<TIER> tierExtractor,
|
||||
@Nullable List<Pair<Block, Integer>> allKnownTiers, @Nullable TIER notSet, BiConsumer<T, TIER> setter,
|
||||
Function<T, TIER> getter, @Nullable List<String> description) {
|
||||
List<Pair<Block, Integer>> hints = allKnownTiers == null ? Collections.emptyList() : allKnownTiers;
|
||||
if (hints.stream().anyMatch(Objects::isNull)) throw new IllegalArgumentException();
|
||||
IStructureElementCheckOnly<T> check = ofBlocksTiered(tierExtractor, notSet, setter, getter);
|
||||
@@ -646,9 +731,23 @@ public class StructureUtility {
|
||||
}
|
||||
|
||||
private Pair<Block, Integer> getHint(ItemStack trigger) {
|
||||
if (hints.isEmpty()) return null;
|
||||
return hints.get(Math.min(Math.max(trigger.stackSize, 1), hints.size()) - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
Pair<Block, Integer> hint = getHint(trigger);
|
||||
if (hint == null) return true;
|
||||
TIER hintTier = tierExtractor.convert(hint.getKey(), hint.getValue());
|
||||
Block block = world.getBlock(x, y, z);
|
||||
int meta = world.getBlockMetadata(x, y, z);
|
||||
TIER worldTier = tierExtractor.convert(block, meta);
|
||||
|
||||
// if the block is in the same tier as the hint block, this could be valid
|
||||
return Objects.equals(hintTier, worldTier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
Pair<Block, Integer> hint = getHint(trigger);
|
||||
@@ -699,6 +798,13 @@ public class StructureUtility {
|
||||
env.getActor(),
|
||||
env.getChatter());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@@ -746,6 +852,11 @@ public class StructureUtility {
|
||||
return world.getBlock(x, y, z) == getBlock() && (wildcard || world.getBlockMetadata(x, y, z) == meta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
if (getBlock() == null) return error().spawnHint(t, world, x, y, z, trigger);
|
||||
@@ -820,10 +931,16 @@ public class StructureUtility {
|
||||
|
||||
@Override
|
||||
public boolean check(T t, World world, int x, int y, int z) {
|
||||
if (init()) return world.getBlock(x, y, z) != block && world.getBlockMetadata(x, y, z) == meta;
|
||||
if (init()) return world.getBlock(x, y, z) == block && world.getBlockMetadata(x, y, z) == meta;
|
||||
else return fallback.check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
if (init()) return world.getBlock(x, y, z) == block && world.getBlockMetadata(x, y, z) == meta;
|
||||
else return fallback.couldBeValid(t, world, x, y, z, trigger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
if (init()) {
|
||||
@@ -900,6 +1017,11 @@ public class StructureUtility {
|
||||
return blocsMap.getOrDefault(worldBlock, MIN_VALUE) == worldBlock.getDamageValue(world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
StructureLibAPI.hintParticle(world, x, y, z, hintBlock, hintMeta);
|
||||
@@ -937,6 +1059,11 @@ public class StructureUtility {
|
||||
.contains(worldBlock.getDamageValue(world, x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
StructureLibAPI.hintParticle(world, x, y, z, hintBlock, hintMeta);
|
||||
@@ -961,6 +1088,11 @@ public class StructureUtility {
|
||||
return block == worldBlock && meta == worldBlock.getDamageValue(world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
StructureLibAPI.hintParticle(world, x, y, z, hintBlock, hintMeta);
|
||||
@@ -995,6 +1127,13 @@ public class StructureUtility {
|
||||
return iBlockAdder.apply(t, worldBlock, worldBlock.getDamageValue(world, x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
// calling iBlockAdder can potentially modify external state
|
||||
// therefore we assume this can always be valid.
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
StructureLibAPI.hintParticle(world, x, y, z, hintBlock, hintMeta);
|
||||
@@ -1028,6 +1167,11 @@ public class StructureUtility {
|
||||
return blocsMap.getOrDefault(worldBlock, MIN_VALUE) == worldBlock.getDamageValue(world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
((ICustomBlockSetting) defaultBlock).setBlock(world, x, y, z, defaultMeta);
|
||||
@@ -1072,6 +1216,11 @@ public class StructureUtility {
|
||||
return blocsMap.getOrDefault(worldBlock, MIN_VALUE) == worldBlock.getDamageValue(world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
world.setBlock(x, y, z, defaultBlock, defaultMeta, 2);
|
||||
@@ -1137,6 +1286,11 @@ public class StructureUtility {
|
||||
.contains(worldBlock.getDamageValue(world, x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
((ICustomBlockSetting) defaultBlock).setBlock(world, x, y, z, defaultMeta);
|
||||
@@ -1182,6 +1336,11 @@ public class StructureUtility {
|
||||
.contains(worldBlock.getDamageValue(world, x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
world.setBlock(x, y, z, defaultBlock, defaultMeta, 2);
|
||||
@@ -1230,6 +1389,14 @@ public class StructureUtility {
|
||||
if (block == null || defaultBlock == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
if (block == Blocks.air) {
|
||||
if (PANIC_MODE) {
|
||||
throw new IllegalArgumentException("ofBlock() does not accept air. use isAir() instead");
|
||||
} else {
|
||||
LOGGER.warn("ofBlock() does not accept air. use isAir() instead");
|
||||
return isAir();
|
||||
}
|
||||
}
|
||||
if (block instanceof ICustomBlockSetting) {
|
||||
return new IStructureElement<T>() {
|
||||
|
||||
@@ -1239,6 +1406,11 @@ public class StructureUtility {
|
||||
return block == worldBlock && meta == worldBlock.getDamageValue(world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
((ICustomBlockSetting) defaultBlock).setBlock(world, x, y, z, defaultMeta);
|
||||
@@ -1256,16 +1428,76 @@ public class StructureUtility {
|
||||
AutoPlaceEnvironment env) {
|
||||
return BlocksToPlace.create(block, meta);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> getDescription() {
|
||||
Item item = Item.getItemFromBlock(block);
|
||||
if (item == null) return null;
|
||||
return Collections.singletonList(new ItemStack(item, 1, meta).getUnlocalizedName() + ".name");
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return new IStructureElement<T>() {
|
||||
|
||||
@Override
|
||||
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
|
||||
AutoPlaceEnvironment env) {
|
||||
BlocksToPlace e = getBlocksToPlace(t, world, x, y, z, trigger, env);
|
||||
IItemSource source = env.getSource();
|
||||
EntityPlayer actor = env.getActor();
|
||||
Consumer<IChatComponent> chatter = env.getChatter();
|
||||
if (check(t, world, x, y, z)) return PlaceResult.SKIP;
|
||||
if (e.getStacks() == null) {
|
||||
ItemStack taken = source.takeOne(e.getPredicate(), true);
|
||||
return StructureUtility.survivalPlaceBlock(
|
||||
taken,
|
||||
NBTMode.EXACT,
|
||||
taken.stackTagCompound,
|
||||
false,
|
||||
world,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
source,
|
||||
actor,
|
||||
chatter);
|
||||
}
|
||||
for (ItemStack stack : e.getStacks()) {
|
||||
if (!source.takeOne(stack, true)) {
|
||||
IChatComponent name = new ChatComponentText(stack.getDisplayName());
|
||||
name.setChatStyle(new ChatStyle().setColor(EnumChatFormatting.YELLOW));
|
||||
env.getChatter()
|
||||
.accept(new ChatComponentTranslation("structurelib.autoplace.missing_block", name));
|
||||
continue;
|
||||
}
|
||||
return StructureUtility.survivalPlaceBlock(
|
||||
stack,
|
||||
NBTMode.EXACT,
|
||||
stack.stackTagCompound,
|
||||
false,
|
||||
world,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
source,
|
||||
actor,
|
||||
chatter);
|
||||
}
|
||||
return PlaceResult.REJECT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check(T t, World world, int x, int y, int z) {
|
||||
Block worldBlock = world.getBlock(x, y, z);
|
||||
return block == worldBlock && meta == worldBlock.getDamageValue(world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
world.setBlock(x, y, z, defaultBlock, defaultMeta, 2);
|
||||
@@ -1283,6 +1515,14 @@ public class StructureUtility {
|
||||
AutoPlaceEnvironment env) {
|
||||
return BlocksToPlace.create(block, meta);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> getDescription() {
|
||||
Item item = Item.getItemFromBlock(block);
|
||||
if (item == null) return null;
|
||||
return Collections.singletonList(new ItemStack(item, 1, meta).getUnlocalizedName() + ".name");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1302,6 +1542,11 @@ public class StructureUtility {
|
||||
return block == world.getBlock(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
((ICustomBlockSetting) defaultBlock).setBlock(world, x, y, z, defaultMeta);
|
||||
@@ -1320,6 +1565,14 @@ public class StructureUtility {
|
||||
// there is getSubItems on ItemBlock, but it's client only
|
||||
return BlocksToPlace.create(defaultBlock, defaultMeta);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> getDescription() {
|
||||
Item item = Item.getItemFromBlock(block);
|
||||
if (item == null) return null;
|
||||
return Collections.singletonList(new ItemStack(item, 1, 0).getUnlocalizedName() + ".name");
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return new IStructureElement<T>() {
|
||||
@@ -1329,6 +1582,11 @@ public class StructureUtility {
|
||||
return block == world.getBlock(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
world.setBlock(x, y, z, defaultBlock, defaultMeta, 2);
|
||||
@@ -1347,6 +1605,14 @@ public class StructureUtility {
|
||||
// there is getSubItems on ItemBlock, but it's client only
|
||||
return BlocksToPlace.create(defaultBlock, defaultMeta);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> getDescription() {
|
||||
Item item = Item.getItemFromBlock(block);
|
||||
if (item == null) return null;
|
||||
return Collections.singletonList(new ItemStack(item, 1, 0).getUnlocalizedName() + ".name");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1395,6 +1661,13 @@ public class StructureUtility {
|
||||
return iBlockAdder.apply(t, worldBlock, worldBlock.getDamageValue(world, x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
// calling iBlockAdder can potentially modify external state
|
||||
// therefore we assume this can always be valid.
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
((ICustomBlockSetting) defaultBlock).setBlock(world, x, y, z, defaultMeta);
|
||||
@@ -1434,6 +1707,13 @@ public class StructureUtility {
|
||||
return iBlockAdder.apply(t, worldBlock, worldBlock.getDamageValue(world, x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
// calling iBlockAdder can potentially modify external state
|
||||
// therefore we assume this can always be valid.
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
world.setBlock(x, y, z, defaultBlock, defaultMeta, 2);
|
||||
@@ -1489,6 +1769,13 @@ public class StructureUtility {
|
||||
return iTileAdder.apply(t, tileEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
// calling iTileAdder can potentially modify external state
|
||||
// therefore we assume this can always be valid.
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
StructureLibAPI.hintParticle(world, x, y, z, hintBlock, hintMeta);
|
||||
@@ -1515,6 +1802,13 @@ public class StructureUtility {
|
||||
return tileClass.isInstance(tileEntity) && iTileAdder.test(t, tileClass.cast(tileEntity));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
// calling iTileAdder can potentially modify external state
|
||||
// therefore we assume this can always be valid.
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
StructureLibAPI.hintParticle(world, x, y, z, hintBlock, hintMeta);
|
||||
@@ -1548,6 +1842,11 @@ public class StructureUtility {
|
||||
return check;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return element.couldBeValid(t, world, x, y, z, trigger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return element.placeBlock(t, world, x, y, z, trigger);
|
||||
@@ -1577,6 +1876,71 @@ public class StructureUtility {
|
||||
AutoPlaceEnvironment env) {
|
||||
return element.survivalPlaceBlock(t, world, x, y, z, trigger, env);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> getDescription() {
|
||||
return element.getDescription();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps an element and overrides its description. Useful for providing human-readable names to elements whose
|
||||
* underlying implementation doesn't provide a description (e.g. tiered block elements).
|
||||
*
|
||||
* @param description the description to use, or null to clear any existing description
|
||||
* @param element the element to wrap
|
||||
*/
|
||||
public static <B extends IStructureElement<T>, T> IStructureElement<T> withDescription(
|
||||
@Nullable List<String> description, B element) {
|
||||
return new IStructureElement<T>() {
|
||||
|
||||
@Override
|
||||
public boolean check(T t, World world, int x, int y, int z) {
|
||||
return element.check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return element.couldBeValid(t, world, x, y, z, trigger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return element.placeBlock(t, world, x, y, z, trigger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return element.spawnHint(t, world, x, y, z, trigger);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
|
||||
AutoPlaceEnvironment env) {
|
||||
return element.getBlocksToPlace(t, world, x, y, z, trigger, env);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
|
||||
IItemSource s, EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
|
||||
return element.survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
|
||||
AutoPlaceEnvironment env) {
|
||||
return element.survivalPlaceBlock(t, world, x, y, z, trigger, env);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> getDescription() {
|
||||
return description;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1599,6 +1963,11 @@ public class StructureUtility {
|
||||
return check;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return element.couldBeValid(t, world, x, y, z, trigger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return element.placeBlock(t, world, x, y, z, trigger);
|
||||
@@ -1628,6 +1997,12 @@ public class StructureUtility {
|
||||
AutoPlaceEnvironment env) {
|
||||
return element.survivalPlaceBlock(t, world, x, y, z, trigger, env);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> getDescription() {
|
||||
return element.getDescription();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1657,6 +2032,13 @@ public class StructureUtility {
|
||||
return predicate.test(t) && downstream.check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
// this COULD potentially impact external state
|
||||
// but predicate really should not have side effects
|
||||
return predicate.test(t) && downstream.couldBeValid(t, world, x, y, z, trigger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return predicate.test(t) && downstream.spawnHint(t, world, x, y, z, trigger);
|
||||
@@ -1690,6 +2072,12 @@ public class StructureUtility {
|
||||
if (predicate.test(t)) return downstream.survivalPlaceBlock(t, world, x, y, z, trigger, env);
|
||||
return placeResultWhenDisabled;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> getDescription() {
|
||||
return downstream.getDescription();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1753,6 +2141,11 @@ public class StructureUtility {
|
||||
return elem.check(t.getCurrentContext(), world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return elem.couldBeValid(t.getCurrentContext(), world, x, y, z, trigger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return elem.spawnHint(t.getCurrentContext(), world, x, y, z, trigger);
|
||||
@@ -1782,6 +2175,12 @@ public class StructureUtility {
|
||||
AutoPlaceEnvironment env) {
|
||||
return elem.survivalPlaceBlock(t.getCurrentContext(), world, x, y, z, trigger, env);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> getDescription() {
|
||||
return elem.getDescription();
|
||||
}
|
||||
};
|
||||
}
|
||||
// endregion
|
||||
@@ -1840,6 +2239,11 @@ public class StructureUtility {
|
||||
return to.get().check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return to.get().couldBeValid(t, world, x, y, z, trigger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return to.get().placeBlock(t, world, x, y, z, trigger);
|
||||
@@ -1893,6 +2297,11 @@ public class StructureUtility {
|
||||
return to.apply(t).check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return to.apply(t).couldBeValid(t, world, x, y, z, trigger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return to.apply(t).placeBlock(t, world, x, y, z, trigger);
|
||||
@@ -2105,6 +2514,11 @@ public class StructureUtility {
|
||||
return to.apply(t, null).check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return to.apply(t, trigger).couldBeValid(t, world, x, y, z, trigger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return to.apply(t, trigger).placeBlock(t, world, x, y, z, trigger);
|
||||
@@ -2297,6 +2711,11 @@ public class StructureUtility {
|
||||
return toCheck.apply(t).check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return to.apply(t, trigger).couldBeValid(t, world, x, y, z, trigger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return to.apply(t, trigger).placeBlock(t, world, x, y, z, trigger);
|
||||
@@ -2541,6 +2960,11 @@ public class StructureUtility {
|
||||
return backing.check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
ItemStack newTrigger = ChannelDataAccessor.withChannel(trigger, channel);
|
||||
return backing.couldBeValid(t, world, x, y, z, newTrigger);
|
||||
}
|
||||
|
||||
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
ItemStack newTrigger = ChannelDataAccessor.withChannel(trigger, channel);
|
||||
// We know spawnHint will only be called on client side, so the lack of player isn't quite an issue
|
||||
@@ -2594,6 +3018,12 @@ public class StructureUtility {
|
||||
warnNoExplicitSubChannel(env.getActor());
|
||||
return backing.survivalPlaceBlock(t, world, x, y, z, newTrigger, env);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> getDescription() {
|
||||
return backing.getDescription();
|
||||
}
|
||||
};
|
||||
}
|
||||
// endregion
|
||||
|
||||
@@ -14,7 +14,10 @@
|
||||
*/
|
||||
package com.gtnewhorizon.structurelib.util;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
@@ -30,6 +33,7 @@ import net.minecraft.inventory.InventoryEnderChest;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import com.gtnewhorizon.gtnhlib.util.map.ItemStackMap;
|
||||
import com.gtnewhorizon.structurelib.SortedRegistry;
|
||||
import com.gtnewhorizon.structurelib.util.InventoryUtility.ItemStackExtractor.APIType;
|
||||
import com.gtnewhorizon.structurelib.util.ItemStackPredicate.NBTMode;
|
||||
|
||||
@@ -38,12 +42,13 @@ import com.gtnewhorizon.structurelib.util.ItemStackPredicate.NBTMode;
|
||||
*/
|
||||
public class InventoryUtility {
|
||||
|
||||
private static final SortedRegistry<ItemStackExtractor> stackExtractors = new SortedRegistry<>();
|
||||
private static final SortedRegistry<ItemStackExtractor> stackExtractors = new SortedRegistry<>("stackExtractors");
|
||||
private static final List<Predicate<? super EntityPlayerMP>> enableEnder = new CopyOnWriteArrayList<>();
|
||||
/**
|
||||
* The remove() of the Iterable returned must be implemented!
|
||||
*/
|
||||
private static final SortedRegistry<InventoryProvider<?>> inventoryProviders = new SortedRegistry<>();
|
||||
private static final SortedRegistry<InventoryProvider<?>> inventoryProviders = new SortedRegistry<>(
|
||||
"inventoryProviders");
|
||||
|
||||
static {
|
||||
inventoryProviders.register("5000-main-inventory", new InventoryProvider<InventoryIterable<InventoryPlayer>>() {
|
||||
@@ -102,8 +107,8 @@ public class InventoryUtility {
|
||||
registerInventoryProvider(key, newInventoryProvider(extractor));
|
||||
}
|
||||
|
||||
public static Iterator<? extends ItemStackExtractor> getStackExtractors() {
|
||||
return stackExtractors.iterator();
|
||||
public static Iterator<? extends ItemStackExtractor> getStackExtractors(EntityPlayerMP player) {
|
||||
return stackExtractors.getPlayerOrdering(player).iterator();
|
||||
}
|
||||
|
||||
public static <Inv extends IInventory> InventoryProvider<InventoryIterable<Inv>> newInventoryProvider(
|
||||
@@ -165,7 +170,7 @@ public class InventoryUtility {
|
||||
boolean simulate, int count) {
|
||||
ItemStackCounterImpl store = new ItemStackCounterImpl();
|
||||
int sum = 0;
|
||||
for (InventoryProvider<?> provider : inventoryProviders) {
|
||||
for (InventoryProvider<?> provider : inventoryProviders.getPlayerOrdering(player)) {
|
||||
sum += takeFromPlayer(player, predicate, simulate, count - sum, store, provider, null);
|
||||
if (sum >= count) return store.getStore();
|
||||
}
|
||||
@@ -186,7 +191,7 @@ public class InventoryUtility {
|
||||
int count = filter.stackSize;
|
||||
ItemStackPredicate predicate = ItemStackPredicate.from(filter, NBTMode.EXACT);
|
||||
ItemStackCounterImpl store = new ItemStackCounterImpl();
|
||||
for (InventoryProvider<?> provider : inventoryProviders) {
|
||||
for (InventoryProvider<?> provider : inventoryProviders.getPlayerOrdering(player)) {
|
||||
sum += takeFromPlayer(player, predicate, simulate, count - sum, store, provider, filter);
|
||||
if (sum >= count) return sum;
|
||||
}
|
||||
@@ -212,7 +217,7 @@ public class InventoryUtility {
|
||||
* @param predicate item stack filter
|
||||
* @param simulate whether to do removal
|
||||
* @param count let's hope int size is enough...
|
||||
* @param recursive do recursive lookup using {@link #getStackExtractors() stack extractors}
|
||||
* @param recursive do recursive lookup using {@link #getStackExtractors(EntityPlayerMP) stack extractors}
|
||||
* @return amount taken. never negative nor bigger than count...
|
||||
*/
|
||||
public static Map<ItemStack, Integer> takeFromInventory(Iterable<ItemStack> inv, Predicate<ItemStack> predicate,
|
||||
@@ -252,7 +257,9 @@ public class InventoryUtility {
|
||||
if (found == count) return count;
|
||||
}
|
||||
if (!recursive) continue;
|
||||
for (ItemStackExtractor f : stackExtractors) {
|
||||
for (ItemStackExtractor f : stackExtractors.getPlayerOrdering(player)) {
|
||||
boolean end = f.isAPIImplemented(APIType.IS_VALID_SOURCE);
|
||||
if (!f.isValidSource(stack, player)) continue;
|
||||
if (filter != null && f.isAPIImplemented(APIType.EXTRACT_ONE_STACK)) {
|
||||
copiedFilter.stackSize = count - found;
|
||||
found += f.getItem(stack, copiedFilter, simulate, player);
|
||||
@@ -260,11 +267,16 @@ public class InventoryUtility {
|
||||
found += f.takeFromStack(predicate, simulate, count - found, store, stack, filter, player);
|
||||
}
|
||||
if (found >= count) return found;
|
||||
if (end) break;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
// dummy method to init static final fields
|
||||
}
|
||||
|
||||
public interface OptimizedExtractor {
|
||||
|
||||
/**
|
||||
@@ -294,10 +306,15 @@ public class InventoryUtility {
|
||||
enum APIType {
|
||||
MAIN,
|
||||
EXTRACT_ONE_STACK,
|
||||
IS_VALID_SOURCE,
|
||||
}
|
||||
|
||||
boolean isAPIImplemented(APIType type);
|
||||
|
||||
default boolean isValidSource(ItemStack is, EntityPlayerMP player) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a particular type of item. The extractor can choose to not return all items contained within this
|
||||
* item as long as it makes sense, but the author should inform the player of this.
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
package com.gtnewhorizon.structurelib.util;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.Spliterator;
|
||||
import java.util.TreeMap;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class SortedRegistry<V> implements Iterable<V> {
|
||||
|
||||
@@ -178,6 +178,80 @@ StructureLib API documentation.
|
||||
|
||||
Just give me a better name and I'd do the renaming.
|
||||
|
||||
<h3>Channel Descriptions</h3>
|
||||
<p>
|
||||
Each channel can have one description associated with it per mod.
|
||||
Channel description should briefly describe how a specific channel is used for inside a specific mod.
|
||||
They will be displayed to player in channel editor GUI that comes with StructureLib.
|
||||
The registration process can be done either via direct method call to {@link com.gtnewhorizon.structurelib.StructureLibAPI#registerChannelDescription(String, String, String)},
|
||||
or by sending an IMC message.
|
||||
After registration, the channel will also show up as autocomplete options when players are editing channels.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Here is an example of sending an IMC message to register two channels and their description.
|
||||
In this example, we assume your modid is <code>my_mod</code> and your mod will ship with localization entries for
|
||||
localization key <code>channels.my_mod.channel_1</code> and <code>channels.my_mod.channel_2</code>
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
NBTTagCompound command = new NBTTagCompound();
|
||||
NBTTagList tags = new NBTTagList();
|
||||
String[] channels = {"channel_1", "channel_2"};
|
||||
for (String channel : channels) {
|
||||
NBTTagCompound tag = new NBTTagCompound();
|
||||
tag.setString("Channel", channel);
|
||||
tag.setString("Description", "channels.my_mod." + channel);
|
||||
tags.appendTag(tag);
|
||||
}
|
||||
command.setTag("Channels", tags);
|
||||
FMLInterModComms.sendMessage("structurelib", "register_channel", command);
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
There is no strict rules over whether you should do direct API method call or send IMC messages.
|
||||
You can choose whichever you prefer.
|
||||
</p>
|
||||
|
||||
<h3>Channel Indicator Items</h3>
|
||||
<p>
|
||||
You can associate an ItemStack with a given (channel, value) pair.
|
||||
It is usually (but not required to be) the ItemBlock for the blocks to be placed by this (channel, value) pair.
|
||||
Player will be able to drag and drop this item to the builtin channel configuration GUI.
|
||||
The registration process can be done either via direct method call to {@link com.gtnewhorizon.structurelib.StructureLibAPI#registerChannelItem(String, String, int, net.minecraft.item.ItemStack)},
|
||||
or by sending an IMC message.
|
||||
After registration, the channel will also show up as autocomplete options when players are editing channels.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Here is an example of sending an IMC message to register 3 vanilla sandstone as different indicator for channel "sandstone", with block metadata plus 1 as channel value.
|
||||
In this example, we assume your modid is <code>my_mod</code>.
|
||||
</p>
|
||||
|
||||
<pre>
|
||||
NBTTagCompound command = new NBTTagCompound();
|
||||
NBTTagList items = new NBTTagList();
|
||||
int[] metas = {0, 1, 2};
|
||||
for (int meta : metas) {
|
||||
NBTTagCompound tag = new NBTTagCompound();
|
||||
tag.setTag("Item", new ItemStack(Blocks.sandstone, 1, meta).writeToNBT(new NBTTagCompound()));
|
||||
NBTTagList channels = new NBTTagList();
|
||||
NBTTagCompound channel = new NBTTagCompound();
|
||||
channel.setString("Channel", "sandstone");
|
||||
channel.setInteger("Value", meta + 1);
|
||||
channels.appendTag(channel);
|
||||
tag.setTag("Channels", channels);
|
||||
items.appendTag(tag);
|
||||
}
|
||||
command.setTag("Items", items);
|
||||
FMLInterModComms.sendMessage("structurelib", "register_channel_item", command);
|
||||
</pre>
|
||||
|
||||
<p>
|
||||
There is no strict rules over whether you should do direct API method call or send IMC messages.
|
||||
You can choose whichever you prefer.
|
||||
</p>
|
||||
|
||||
<h3>Examples</h3>
|
||||
|
||||
<h4>For Structure Definition Author</h4>
|
||||
@@ -251,5 +325,42 @@ The code for placeBlock() and spawnHint() will be left as an exercise for the re
|
||||
<p>
|
||||
Please refer to {@link com.gtnewhorizon.structurelib.StructureEvent} to see what events are available.
|
||||
</p>
|
||||
<h1>Extending Inventory Utility</h1>
|
||||
{@link com.gtnewhorizon.structurelib.util.InventoryUtility} is how structurelib survival autoplace pulls item for autoplacing.
|
||||
It has two key concepts: inventory providers and stack extractors.
|
||||
<p>
|
||||
Inventory Providers are classes that retrieve an Iterable of ItemStacks for examination, given an EntityPlayerMP as identifier.
|
||||
They are also responsible for marking the relevant item container as dirty, so changes can be commited to disk properly when world save happens.
|
||||
Examples include: main player inventory, vanilla ender chest, baubles inventory. StructureLib comes with main player inventory and vanilla ender chest built in.
|
||||
</p>
|
||||
<p>
|
||||
Stack Extractors are classes that retrieve items from another item.
|
||||
Examples include: Forestry backpacks, ME network over ME Wireless Terminals.
|
||||
</p>
|
||||
<p>
|
||||
Both of these are managed internally by {@link com.gtnewhorizon.structurelib.SortedRegistry}.
|
||||
By default, elements registered will be sorted in lexicographical order in ascending order.
|
||||
However, facilities in StructureLib will allow an end user to tweak these registries as their wish.
|
||||
They can disable, reorder, reenable any element within the registry, either in game, at main menu or with the game stopped,
|
||||
Their changes will be synced to server as well, should they be playing on a dedicated server.
|
||||
</p>
|
||||
<p>
|
||||
This has the following important implications
|
||||
</p>
|
||||
<ul>
|
||||
<li>Not all inventory providers or stack extractors may be called during a survival autoplace session</li>
|
||||
<li>One player could have stack extractor 001 ordered before 002, while the other having a completely reversed order</li>
|
||||
</ul>
|
||||
Therefore, if your stack extractor can somehow correlate with aonther one, make sure it still works if the calling order is inverted.
|
||||
At the very least, inform the player about this ordering constraint.
|
||||
A future StructrueLib refactor to specify this dependency info is possible, but the chance is low.
|
||||
<h2>Localization</h2>
|
||||
Both of these would show up in config GUI of StructureLib. Structurelib uses the following localization keys in that GUI.
|
||||
<ul>
|
||||
<li><code>structurelib.sortedregistry.inventoryproviders.<registry_key></code>: This is the name shown on the button for inventory providers. If this is not provided, then registry_key will be used instead.</li>
|
||||
<li><code>structurelib.sortedregistry.inventoryproviders.<registry_key>.tooltip</code>: This is the tooltip shown when user hover over the button for inventory providers. If this is not provided, then no explicit tooltip will be shown.</li>
|
||||
<li><code>structurelib.sortedregistry.stackextractors.<registry_key></code>: This is the name shown on the button for stack extractors. If this is not provided, then registry_key will be used instead.</li>
|
||||
<li><code>structurelib.sortedregistry.stackextractors.<registry_key>.tooltip</code>: This is the tooltip shown when user hover over the button for stack extractors. If this is not provided, then no explicit tooltip will be shown.</li>
|
||||
</ul>
|
||||
</BODY>
|
||||
</HTML>
|
||||
@@ -1,3 +1,4 @@
|
||||
tile.structurelib.blockhint.name=Hint
|
||||
tile.structurelib.blockhint.0.name=Hint 1 dot
|
||||
tile.structurelib.blockhint.1.name=Hint 2 dots
|
||||
tile.structurelib.blockhint.2.name=Hint 3 dots
|
||||
@@ -29,12 +30,20 @@ item.structurelib.constructableTrigger.desc.4=Quantity of schematics in item sta
|
||||
item.structurelib.constructableTrigger.desc.5=Hold §l[LSHIFT]§r to display channel data
|
||||
item.structurelib.constructableTrigger.desc.lshift.0=Contain %d sub channel.
|
||||
item.structurelib.constructableTrigger.too_fast=Please wait %d milliseconds before another autoplace...
|
||||
item.structurelib.constructableTrigger.gui.title=Sub Channels
|
||||
item.structurelib.constructableTrigger.gui.key=Key:
|
||||
item.structurelib.constructableTrigger.gui.value=Value:
|
||||
item.structurelib.constructableTrigger.gui.add=Add
|
||||
item.structurelib.constructableTrigger.gui.set=Set
|
||||
item.structurelib.constructableTrigger.gui.unset=Unset
|
||||
item.structurelib.constructableTrigger.gui.wipe=§4Wipe
|
||||
item.structurelib.constructableTrigger.gui.set=Update
|
||||
item.structurelib.constructableTrigger.gui.unset=Remove
|
||||
item.structurelib.constructableTrigger.gui.wipe=§4Clear
|
||||
item.structurelib.constructableTrigger.gui.channels.from=§rMod: §9§o%s
|
||||
item.structurelib.constructableTrigger.gui.info=Many multiblocks support some degree of variation in their structure and a sub channel allows indication of which variant to use.\n All sub channels have a sequence of lowercase characters as key and a positive integer as value. Most known sub channels should be available as typeahead completions.\n You can also drag and drop items from NEI to set the corresponding sub channel value. These items all have a tooltip of "§oIndicates§r §ovalue§r §oX§r §ofor§r §osub§r §ochannel§r §oY§r".
|
||||
|
||||
channels.structurelib.show_errors=When set, will highlight parts of the multiblock that is definitely wrong
|
||||
|
||||
structurelib.tooltip.channelvalue=§7Indicates value §6%d§r§7 for sub channel §6%s
|
||||
structurelib.tooltip.indicator_dnd=Drop into GUI to select this value for this channel
|
||||
|
||||
structurelib.autoplace.built_stat=Built %s blocks
|
||||
structurelib.autoplace.complete=§aThis multiblock has completed!
|
||||
@@ -42,6 +51,7 @@ structurelib.autoplace.error.not_enabled=§cThis multiblock does not support aut
|
||||
structurelib.autoplace.error.no_simple_block=§cSuggested to place §r%s§c but none was found
|
||||
structurelib.autoplace.error.no_item_stack=§cSuggested to place block by using §r%s§c but none was found
|
||||
structurelib.autoplace.warning.no_explicit_channel=§6This structure depends on nonexistent sub channel §r%s§6. Using master channel instead.
|
||||
structurelib.autoplace.missing_block=§cMissing block: %s
|
||||
|
||||
structurelib.blockhint.desc.0=Helps while building
|
||||
structurelib.blockhint.0.name=Hint 1 dot
|
||||
@@ -172,3 +182,19 @@ structurelib.config.client.hologram.maxCoexisting=Max Coexisting Holograms
|
||||
structurelib.config.client.hologram.maxCoexisting.tooltip=Number of holograms that can coexist. Usually all hints generated by one right click belongs to one hologram. An attempt will be made to prune old holograms when a new hologram is about to be projected and this limit would be exceeded.
|
||||
structurelib.config.client.hologram.removeColliding=Remove Colliding Holograms
|
||||
structurelib.config.client.hologram.removeColliding.tooltip=Whether an attempt will be made to remove any existing hologram that collides with a new hologram.
|
||||
|
||||
structurelib.config.registries=Sorted Registries
|
||||
structurelib.config.registries.tooltip=Sorted Registry is how StructureLib internally keep track of certain features. Each individual elements may be reordered, disabled and enabled at user's command. Changes will be synced to server if needed.\nThis gui can also be accessed in game by shift right clicking air with Multiblock Structure Hologram Projector.
|
||||
structurelib.config.registries.stackextractors=Stack Extractors
|
||||
structurelib.config.registries.stackextractors.tooltip=Controls which inventory-from-stack functions are enabled during survival autoplace
|
||||
structurelib.config.registries.inventoryproviders=Inventory Providers
|
||||
structurelib.config.registries.inventoryproviders.tooltip=Controls which inventories functions are enabled during survival autoplace
|
||||
|
||||
structurelib.configgui.enabled=Enabled Entry
|
||||
structurelib.configgui.disabled=Disabled Entry
|
||||
structurelib.configgui.drag.tooltip=Drag this button to reorder this element.
|
||||
|
||||
structurelib.sortedregistry.inventoryproviders.5000-main-inventory=Main Player Inventory
|
||||
structurelib.sortedregistry.inventoryproviders.5000-main-inventory.tooltip=36 slots of item in your main inventory. Armor slots excluded.
|
||||
structurelib.sortedregistry.inventoryproviders.7000-ender-inventory=Ender Chest Inventory
|
||||
structurelib.sortedregistry.inventoryproviders.7000-ender-inventory.tooltip=Links to your vanilla ender chest inventory. Require at least one "ender backpack like" item in main inventory to work.
|
||||
@@ -1,3 +1,4 @@
|
||||
tile.structurelib.blockhint.name=提示方块
|
||||
tile.structurelib.blockhint.0.name=提示方块:1号
|
||||
tile.structurelib.blockhint.1.name=提示方块:2号
|
||||
tile.structurelib.blockhint.2.name=提示方块:3号
|
||||
@@ -29,12 +30,20 @@ item.structurelib.constructableTrigger.desc.4=此物品堆叠数量会影响显
|
||||
item.structurelib.constructableTrigger.desc.5=按下 §l[左SHIFT]§r 以显示子信道
|
||||
item.structurelib.constructableTrigger.desc.lshift.0=包含 %d 个子信道
|
||||
item.structurelib.constructableTrigger.too_fast=请在下一次自动搭建前等待 %d 毫秒...
|
||||
item.structurelib.constructableTrigger.gui.title=子信道
|
||||
item.structurelib.constructableTrigger.gui.key=信道名:
|
||||
item.structurelib.constructableTrigger.gui.value=值:
|
||||
item.structurelib.constructableTrigger.gui.add=添加
|
||||
item.structurelib.constructableTrigger.gui.set=替换
|
||||
item.structurelib.constructableTrigger.gui.unset=删除
|
||||
item.structurelib.constructableTrigger.gui.wipe=§4全部清空
|
||||
item.structurelib.constructableTrigger.gui.channels.from=§r模组: §9§o%s
|
||||
item.structurelib.constructableTrigger.gui.info=许多多方块的结构都是可变的,在自动放置时,可以使用子信道来向多方块指明该使用哪一个变体。\n子信道由一个字符串标识、并拥有一个正整数作为值。这个GUI中的信道名支持自动补全到绝大多数已知的子信道。\n你也可以将部分物品从NEI中拖拽至GUI内来设置子信道值为该物品表示的值。这些物品的物品说明中都带有§o表示子信道 X 为 Y§r。
|
||||
|
||||
channels.structurelib.show_errors=添加后,在投影时会高亮标出必然错误的部分
|
||||
|
||||
structurelib.tooltip.channelvalue=§7表示子信道 §6%2$s§r§7 为 §6%d§r
|
||||
structurelib.tooltip.indicator_dnd=拖入GUI中来选择该值
|
||||
|
||||
structurelib.autoplace.built_stat=已放置 %s 个方块
|
||||
structurelib.autoplace.complete=§a这个多方块结构已经完成自动搭建!
|
||||
@@ -172,3 +181,19 @@ structurelib.config.client.hologram.maxCoexisting=最多可以同时存在的全
|
||||
structurelib.config.client.hologram.maxCoexisting.tooltip=这决定最多可以同时存在多少台机器的全息投影。一般一次使用全息投影仪右键生成的所有机器结构细节被视为一个全息投影。当同时存在多个全息投影,并超过此数量时,再次使用全息投影仪会移除旧有的全息投影。
|
||||
structurelib.config.client.hologram.removeColliding=移除冲突的全息投影
|
||||
structurelib.config.client.hologram.removeColliding.tooltip=当新的全息投影与已存在的全息投影冲突时,开启此选项时会移除已存在的全息投影。
|
||||
|
||||
structurelib.config.registries=注册表
|
||||
structurelib.config.registries.tooltip=StructureLib 使用注册表来管理其部分功能。每个注册表中的每一个元素都可以被单独排序、禁用。需要时,注册表的内容将被同步至服务端。
|
||||
structurelib.config.registries.stackextractors=物品提取器
|
||||
structurelib.config.registries.stackextractors.tooltip=该注册表控制在生存模式自动放置时,如何从物品(如林业背包)提取库存。
|
||||
structurelib.config.registries.inventoryproviders=物品栏定位
|
||||
structurelib.config.registries.inventoryproviders.tooltip=该注册表控制在生存模式自动放置时,可用的物品栏来源。
|
||||
|
||||
structurelib.configgui.enabled=已启用
|
||||
structurelib.configgui.disabled=已禁用
|
||||
structurelib.configgui.drag.tooltip=拖动该按钮以重排。
|
||||
|
||||
structurelib.sortedregistry.inventoryproviders.5000-main-inventory=玩家物品栏
|
||||
structurelib.sortedregistry.inventoryproviders.5000-main-inventory.tooltip=不包括护甲栏以外的36格。
|
||||
structurelib.sortedregistry.inventoryproviders.7000-ender-inventory=末影箱
|
||||
structurelib.sortedregistry.inventoryproviders.7000-ender-inventory.tooltip=当前玩家的末影箱。需要在主物品栏中存在类似于末影背包的东西才会实际生效。
|
||||
|
Before Width: | Height: | Size: 273 B After Width: | Height: | Size: 126 B |
|
Before Width: | Height: | Size: 279 B After Width: | Height: | Size: 154 B |
|
Before Width: | Height: | Size: 296 B After Width: | Height: | Size: 138 B |
|
Before Width: | Height: | Size: 301 B After Width: | Height: | Size: 160 B |
|
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 137 B |
|
Before Width: | Height: | Size: 281 B After Width: | Height: | Size: 138 B |
|
Before Width: | Height: | Size: 285 B After Width: | Height: | Size: 131 B |
|
Before Width: | Height: | Size: 289 B After Width: | Height: | Size: 141 B |
|
Before Width: | Height: | Size: 293 B After Width: | Height: | Size: 132 B |
|
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 138 B |
|
Before Width: | Height: | Size: 296 B After Width: | Height: | Size: 139 B |
|
Before Width: | Height: | Size: 297 B After Width: | Height: | Size: 143 B |
|
Before Width: | Height: | Size: 255 B After Width: | Height: | Size: 99 B |
|
Before Width: | Height: | Size: 301 B After Width: | Height: | Size: 132 B |
|
Before Width: | Height: | Size: 306 B After Width: | Height: | Size: 146 B |
|
Before Width: | Height: | Size: 281 B After Width: | Height: | Size: 115 B |
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 276 B |
|
Before Width: | Height: | Size: 667 B After Width: | Height: | Size: 606 B |
|
Before Width: | Height: | Size: 439 B After Width: | Height: | Size: 361 B |
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"required": true,
|
||||
"minVersion": "0.8.5-GTNH",
|
||||
"package": "com.gtnewhorizon.structurelib.mixins.early",
|
||||
"refmap": "mixins.structurelib.refmap.json",
|
||||
"target": "@env(DEFAULT)",
|
||||
"compatibilityLevel": "JAVA_8"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"required": true,
|
||||
"minVersion": "0.8.5-GTNH",
|
||||
"package": "com.gtnewhorizon.structurelib.mixins",
|
||||
"refmap": "mixins.structurelib.refmap.json",
|
||||
"target": "@env(DEFAULT)",
|
||||
"compatibilityLevel": "JAVA_8"
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"required": true,
|
||||
"minVersion": "0.8.5-GTNH",
|
||||
"package": "com.gtnewhorizon.structurelib.mixins.late",
|
||||
"refmap": "mixins.structurelib.refmap.json",
|
||||
"target": "@env(DEFAULT)",
|
||||
"compatibilityLevel": "JAVA_8"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
public net.minecraft.client.gui.GuiSlot func_148136_c(IIII)V # overlayBackground
|
||||