Compare commits

...
42 Commits
Author SHA1 Message Date
UltraProdigy a05267a566 Replace Dot Hints With Number Hints (#52)
As title says, makes the holograms much more readable.

Textures By: bapy_
Original PR By: kstvr32

Supercedes: https://github.com/GTNewHorizons/StructureLib/pull/32

Examples:
<img width="2534" height="1351" alt="image"
src="https://github.com/user-attachments/assets/c24f60b2-d654-4b15-b917-4003141a2b69"
/>
<img width="2538" height="1350" alt="image"
src="https://github.com/user-attachments/assets/a5ca3f42-ca96-4675-8e80-c89fba359fbf"
/>
2025-12-17 08:04:19 +01:00
Wilhelm Schuster dd3413dcaa Don't enable buttons in channel configuration when channel key is empty (#51)
The channel configuration GUI would allow pressing the "Add" button even
though the channel key field was empty, leading to a crash.

fixes
https://github.com/GTNewHorizons/GT-New-Horizons-Modpack/issues/22838
2025-12-15 09:33:15 +08:00
Martin Robertz 34394ef8a5 update 2025-10-28 21:07:20 +01:00
RecursivePineapple 7c0d3f4bd6 Expose ability to iterate over structures (#49) 2025-10-28 12:52:40 +08:00
Martin Robertz cd4f3617be update 2025-09-20 15:44:09 +02:00
Wilhelm Schuster 256bc60d35 Fix crash trying to open Hologram Projector GUI on java 8 (#48) 2025-09-19 00:16:47 +08:00
Martin Robertz 70426ab7b3 update 2025-09-17 13:53:22 +02:00
Wilhelm Schuster 0030c02e9a Small improvements to the channel config GUI (#47) 2025-09-15 22:06:29 +02:00
Wilhelm Schuster 7382db1bbd Fix Hologram Projector Shift-Use opening both GUIs (#46) 2025-09-14 19:00:59 +02:00
Wilhelm Schuster b669cbecf1 Fix Hologram Projector config GUI not releasing cursor (#45) 2025-09-14 10:21:21 +02:00
Dream Master 6283931667 update 2025-08-04 10:28:35 +07:00
Abdiel Kavash 3b3bc4b9ad Add preset IAlignmentLimits constants. (#43)
Many multiblocks can only be placed or rotated in certain orientations.
This has been always done ad-hoc, with individual checks for each
controller. This is inconsistent and sometimes it results in bugs where
certain orientations are not allowed for no discernible reason; for
example
https://github.com/GTNewHorizons/GT-New-Horizons-Modpack/issues/14206.

This PR introduces a short list of named constants to
`IAlignmentLimits`, which describe common multiplboc orientations. These
are currently:

* `UNLIMITED`: Any orientation is allowed. Most multiblocks.
* `UPRIGHT`: The multiblock must be in an upright position, with the top
side facing up. Example: Distillation Tower, Ore Washing Plant.
* `NOT_UPSIDE_DOWN`: The multiblock can be in any orientation, except
for the top side facing directly downwards. This is currently only the
Large Heat Exchanger and Integrated Ore Factory.
* `FACING_UP`: The front of the controller must face up. Example:
Flotation Cell, XL Turbines.

Currently, these constants describe the allowed facings of *all* GT5U
multiblocks.

A multiblock can then simply return one of these constants from
`getInitialAlignmentLimits()` to describe its valid orientations,
instead of implementing the logic itself. This concentrates the code to
one place, improves readability, and fixes any bugs connected with these
checks being done wrong.

Of course, even though the list is currently complete, if any future
multiblock wants to implement its own strange restrictions on
orientation, it can still implement its own check instead of returning
one of the constants.

A follow-up PR to GT5U implements this for all multiblocks which
restrict valid orientations:
https://github.com/GTNewHorizons/GT5-Unofficial/pull/4595
2025-08-01 10:54:59 +07:00
Alexdoru bef4fdd949 Delete overkill mixin registration system (#44) 2025-07-14 04:31:17 +08:00
Martin Robertz 4a06c8f95b update 2025-06-19 21:09:58 +02:00
koolkrafter5 241bed1989 Update dev username (#41) 2025-06-16 10:53:32 +08:00
Martin Robertz f5594c25db update 2025-04-30 16:05:28 +02:00
Glease 7cda901ae5 add channel desc and channel indicator item (#39)
Add channel desc and channel indicator item. Its meaning and development
examples has been added to overview page in javadoc.
Also removes gt_no_hatch and show_error special button from GUI as they
are now a candidate in the dropdown menu.

one day we will move this to MUI2, but for now let's stick to existing
infra that is known to work



https://github.com/user-attachments/assets/814edbb6-c4b4-4309-8798-787d9ed1aeef

Note: channels in demo video are from GT5. if you boot the pack without
the modified gt5 you won't see most of the channels.

---------

Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>
2025-04-30 15:55:19 +02:00
538459eab5 channel configure ui tweaks (#35)
Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>
Co-authored-by: Martin Robertz <dream-master@gmx.net>
Co-authored-by: Maya <10861407+serenibyss@users.noreply.github.com>
2025-03-11 09:41:57 -05:00
Maya 3a9ec8b6ac update 2025-03-10 16:27:58 -05:00
Glease 330a13271b fix unbreakable warded glass (#37)
fix
https://github.com/GTNewHorizons/GT-New-Horizons-Modpack/issues/18849

@Ruling-0 since your glass refactor is slightly related to this

---------

Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>
2025-03-10 16:26:36 -05:00
glowredmanandGlease bf5dd5566b Add Localization (#36)
This adds some missing localizations for unlocalized names returned by
the ItemStack-insensitive version of `Item.getUnlocalizedName()`.
Relevant for the Item/Block stats introduced in
https://github.com/GTNewHorizons/Hodgepodge/pull/398.

---------

Co-authored-by: Glease <4586901+Glease@users.noreply.github.com>
2025-01-02 11:40:59 +08:00
Martin Robertz b9d0143245 update 2024-12-14 20:12:25 +01:00
43fc39c99b Add error hint when block is invalid (#31)
Co-authored-by: kstvr32 <kstvr32@gmail.com>
Co-authored-by: Martin Robertz <dream-master@gmx.net>
2024-12-14 19:13:34 +01:00
Lyft 6337536dc3 Send correct identifier when instrumenting (#34) 2024-11-19 10:20:47 +08:00
Lyft 9b96560119 Fix IStructureElementChain returning an invalid Iterable (#33) 2024-11-09 15:46:49 +01:00
Dream Master 3ce1c877e3 update 2024-08-07 15:42:01 +07:00
GleaseandDream Master 3d150d95cb customizable registries (#25)
* add a bit javadoc

Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>

* customizable registries

Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>

* add gui for customizing registries

* add a bit javadoc

Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>

* spotless

Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>

* remove debug statements

Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>

* add notes on localization

Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>

* update

* fix

* fix

---------

Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>
Co-authored-by: Dream Master <dream-master@gmx.net>
2024-08-07 15:37:30 +07:00
Dream Master 03e84e3d37 update 2024-07-10 11:12:56 +07:00
Jason Mitchell 547833bedb Bump NEI dep
Bump GTNHLib dep for CoordinatePacker

Bump deps
2024-07-09 09:39:45 -07:00
Jason Mitchell 61e0d4b7e7 Add basic BlockChangeNotifier & BlockChangeListener 2024-07-09 09:39:45 -07:00
Jason Mitchell 6867ed50e6 Add mixins 2024-07-09 09:39:45 -07:00
Jason Mitchell e7a47c199b Enable checkstyle 2024-07-09 09:39:45 -07:00
Jason Mitchell b2bc474382 Update BS, enable Jabel & Generic injection 2024-07-09 09:39:45 -07:00
Martin Robertz eef6baea4b update 2024-04-16 22:13:22 +02:00
Glease efd49701e1 add a new api to stack extractors
Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>
2024-04-16 17:32:31 +08:00
Raven Szewczyk a7f0d6be72 [ci skip] upgraded build system 2024-02-04 20:01:59 +00:00
Glease 0ff38c2da9 revise javadoc publish (#27)
* fix deprecation

* add cleanup
2024-01-09 22:52:41 +01:00
chochem 307bb1714c Fix broken mod (#26)
* clear out repositories

* update bs

* update deps

* spotless
2024-01-09 11:42:10 +08:00
Raven Szewczyk 7d8e6a0ea5 [ci skip] upgraded build system 2024-01-08 21:29:25 +00:00
Glease b6d2af2170 add new api to check for rotation/flip changes (#24)
* add new api to check for rotation/flip changes

Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>

* add a bit javadoc

Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>

---------

Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>
2023-09-13 14:29:39 +02:00
Glease 8309567076 fix cube faces rendering order (#22)
* fix cube faces rendering order

Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>

* spotless

Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>

* address review

Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>

---------

Signed-off-by: Glease <4586901+Glease@users.noreply.github.com>
2023-07-13 22:05:35 +02:00
kstvr32 23312198a6 Allow autoplace channels to work in survival mode (#20)
* allow autoplace channels to work in survival mode

* bump NEI dependency version
2023-05-04 17:40:03 +02:00
79 changed files with 3247 additions and 1507 deletions

No files matched your search

+34 -4
View File
@@ -7,6 +7,7 @@ on:
- feature/doc
tags:
- '*'
delete:
permissions:
contents: write
@@ -14,7 +15,38 @@ permissions:
id-token: write
jobs:
prune:
if: github.event_name == 'delete'
concurrency: javadoc-publish
runs-on: ubuntu-latest
outputs:
result_hash: ${{ steps.deploy.outputs.hash }}
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
ref: gh-pages
- name: Deploy 🚀
id: deploy
shell: bash
run: |
if git rm -r $TARGET_FOLDER ; then
echo Nothing to clean up
fi
find . -name javadoc -type d -print | sed 's#^\./\(.*\)/javadoc$#\1#' | sort --version-sort --reverse > index.txt
git add index.txt
git config user.name "${GITHUB_ACTOR}"
git config user.email "${GITHUB_ACTOR_ID}-${GITHUB_ACTOR}@users.noreply.github.com"
git commit -m "Clean up javadoc after ref $TARGET_FOLDER is deleted"
git push
echo "hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
env:
TARGET_FOLDER: ${{ github.ref_name }}
javadoc:
if: github.event_name == 'push'
concurrency: javadoc-publish
runs-on: ubuntu-latest
outputs:
@@ -57,11 +89,9 @@ jobs:
find . -name javadoc -type d -print | sed 's#^\./\(.*\)/javadoc$#\1#' | sort --version-sort --reverse > index.txt
git add index.txt
git config user.name "${GITHUB_ACTOR}"
git config user.email "${GITHUB_ACTOR}@users.noreply.github.com"
git config user.email "${GITHUB_ACTOR_ID}-${GITHUB_ACTOR}@users.noreply.github.com"
git commit -m "Deploy javadoc from ref $TARGET_FOLDER"
git push
echo "::set-output name=hash::$(git rev-parse HEAD)"
echo "hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
env:
TARGET_FOLDER: ${{ github.ref_name }}
COMMIT_AUTHOR: ${{ github.pusher.name }}
COMMIT_EMAIL: ${{ github.pusher.email }}
+1
View File
@@ -27,3 +27,4 @@ whitelist.json
*.iws
src/main/resources/mixins.*.json
*.bat
layout.json
+165
View File
@@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
+2
View File
@@ -32,3 +32,5 @@ The project is developed using IDEA. Please manually configure the build to not
Due to me adding in ItemStackMap, which was derived from code from CodeChicken's LGPL mod NEI. This mod is effectively considered
licensed under LGPL in its entirety since commit ebef584b8c518f8202cfe0984fb28a6d23e52b3d or any squash commit containing it, e.g.
609e50428ee4e293f65f67f554a70b9fcfae97c4.
Note while the LICENSE.txt is added by a much later commit, all code on previous commits are covered by this as well.
+2 -1259
View File
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -1,8 +1,8 @@
// Add your dependencies here
dependencies {
api("com.github.GTNewHorizons:GTNHLib:0.0.10:dev")
runtimeOnly("com.github.GTNewHorizons:NotEnoughItems:2.3.25-GTNH:dev")
api("com.github.GTNewHorizons:GTNHLib:0.7.1:dev")
implementation("com.github.GTNewHorizons:NotEnoughItems:2.8.21-GTNH:dev")
testImplementation(platform('org.junit:junit-bom:5.9.2'))
testImplementation('org.junit.jupiter:junit-jupiter')
+174 -29
View File
@@ -1,60 +1,205 @@
# 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 using modern Java syntax (up to version 17) via Jabel, while still targeting JVM 8.
# See https://github.com/bsideup/jabel for details on how this works.
enableModernJavaSyntax = true
# 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
# 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 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
Binary file not shown.
+2 -1
View File
@@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+20 -13
View File
@@ -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/HEAD/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/.
@@ -83,10 +85,8 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -133,10 +133,13 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
@@ -144,7 +147,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@@ -152,7 +155,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -197,11 +200,15 @@ if "$cygwin" || "$msys" ; then
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
Vendored
+12 -10
View File
@@ -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,11 +59,11 @@ 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
-4
View File
@@ -1,8 +1,4 @@
// Add any additional repositiroes for your dependencies here
repositories {
maven {
name = "GTNH"
url = "http://jenkins.usrv.eu:8081/nexus/content/groups/public/"
}
}
+5 -10
View File
@@ -4,10 +4,10 @@ pluginManagement {
maven {
// RetroFuturaGradle
name "GTNH Maven"
url "http://jenkins.usrv.eu:8081/nexus/content/groups/public/"
allowInsecureProtocol = true
url "https://nexus.gtnewhorizons.com/repository/public/"
mavenContent {
includeGroup("com.gtnewhorizons.retrofuturagradle")
includeGroup("com.gtnewhorizons")
includeGroupByRegex("com\\.gtnewhorizons\\..+")
}
}
gradlePluginPortal()
@@ -17,12 +17,7 @@ pluginManagement {
}
plugins {
id 'com.diffplug.blowdryerSetup' version '1.6.0'
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.4.0' // Provides java toolchains
id 'com.gtnewhorizons.gtnhsettingsconvention' version '1.0.47'
}
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 {
@@ -46,7 +64,6 @@ public class ClientProxy extends CommonProxy {
*/
private static final List<HintGroup> allGroups = new ArrayList<>();
public static int HOLOGRAM_LIFETIME = 200;
/**
* Current batch of hints. Belongs to the same logical multiblock.
*/
@@ -247,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<>();
@@ -335,26 +393,32 @@ public class ClientProxy extends CommonProxy {
double X = (x - eyeXint) + 0.25;
double Y = (y - eyeYint) + 0.25;
double Z = (z - eyeZint) + 0.25;
double worldX = x + 0.25;
double worldY = y + 0.25;
double worldZ = z + 0.25;
for (int i = 0; i < 6; i++) {
if (icons[i] == null) continue;
// this rendering code is independently written by glee8e on July 10th, 2023
// and is released as part of StructureLib under LGPL terms, just like everything else in this project
// cube is a very special model. its facings can be rendered correctly by viewer distance without using
// surface normals and view vector
// here we do a 2 pass render.
// first pass we draw obstructed faces (i.e. faces that are further away from player)
// second pass we draw unobstructed faces
for (int j = 0; j < 2; j++) {
boolean unobstructedPass = j == 1;
for (int i = 0; i < 6; i++) {
if (icons[i] == null) continue;
markTextureUsed(icons[i]);
markTextureUsed(icons[i]);
double u = icons[i].getMinU();
double U = icons[i].getMaxU();
double v = icons[i].getMinV();
double V = icons[i].getMaxV();
// cube is a very special model. its facings can be rendered correctly by viewer distance without using
// surface normals and view vector
// here we do a 2 pass render.
// first pass we draw obstructed faces (i.e. faces that are further away from player)
// second pass we draw unobstructed faces
for (int j = 0; j < 2; j++) {
double u = icons[i].getMinU();
double U = icons[i].getMaxU();
double v = icons[i].getMinV();
double V = icons[i].getMaxV();
switch (i) { // {DOWN, UP, NORTH, SOUTH, WEST, EAST}
case 0:
if ((Y >= eyeY) != (j == 1)) continue;
// all these ifs is in form if ((is face unobstructed) != (is in unobstructred pass))
if ((worldY >= eyeY) != unobstructedPass) continue;
tes.setNormal(0, -1, 0);
tes.addVertexWithUV(X, Y, Z + size, u, V);
tes.addVertexWithUV(X, Y, Z, u, v);
@@ -364,7 +428,7 @@ public class ClientProxy extends CommonProxy {
tes.addVertexWithUV(X, Y, Z + size, u, V);
break;
case 1:
if ((Y + size <= eyeY) != (j == 1)) continue;
if ((worldY + size <= eyeY) != unobstructedPass) continue;
tes.setNormal(0, 1, 0);
tes.addVertexWithUV(X, Y + size, Z, u, v);
tes.addVertexWithUV(X, Y + size, Z + size, u, V);
@@ -374,7 +438,7 @@ public class ClientProxy extends CommonProxy {
tes.addVertexWithUV(X, Y + size, Z, u, v);
break;
case 2:
if ((Z >= eyeZ) != (j == 1)) continue;
if ((worldZ >= eyeZ) != unobstructedPass) continue;
tes.setNormal(0, 0, -1);
tes.addVertexWithUV(X, Y, Z, U, V);
tes.addVertexWithUV(X, Y + size, Z, U, v);
@@ -384,7 +448,7 @@ public class ClientProxy extends CommonProxy {
tes.addVertexWithUV(X, Y, Z, U, V);
break;
case 3:
if ((Z <= eyeZ) != (j == 1)) continue;
if ((worldZ + size <= eyeZ) != unobstructedPass) continue;
tes.setNormal(0, 0, 1);
tes.addVertexWithUV(X + size, Y, Z + size, U, V);
tes.addVertexWithUV(X + size, Y + size, Z + size, U, v);
@@ -394,7 +458,7 @@ public class ClientProxy extends CommonProxy {
tes.addVertexWithUV(X + size, Y, Z + size, U, V);
break;
case 4:
if ((X >= eyeX) != (j == 1)) continue;
if ((worldX >= eyeX) != unobstructedPass) continue;
tes.setNormal(-1, 0, 0);
tes.addVertexWithUV(X, Y, Z + size, U, V);
tes.addVertexWithUV(X, Y + size, Z + size, U, v);
@@ -404,7 +468,7 @@ public class ClientProxy extends CommonProxy {
tes.addVertexWithUV(X, Y, Z + size, U, V);
break;
case 5:
if ((X + size <= eyeX) != (j == 1)) continue;
if ((worldX + size <= eyeX) != unobstructedPass) continue;
tes.setNormal(1, 0, 0);
tes.addVertexWithUV(X + size, Y, Z, U, V);
tes.addVertexWithUV(X + size, Y + size, Z, U, v);
@@ -429,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) {
@@ -478,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) {
@@ -550,7 +643,6 @@ public class ClientProxy extends CommonProxy {
tes.draw();
tes.startDrawing(GL11.GL_TRIANGLES);
}
// TODO verify if we need to add eyeHeight
hint.draw(tes, d0, d1, d2, i0, i1, i2);
}
p.endStartSection("Draw");
@@ -12,13 +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) {}
@@ -69,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,
@@ -93,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,21 +3,28 @@ 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;
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();
}
}
@@ -4,6 +4,7 @@ import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import com.gtnewhorizon.structurelib.structure.IStructureElement;
import cpw.mods.fml.common.eventhandler.Event;
/**
@@ -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,12 +11,14 @@ 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;
import com.gtnewhorizon.structurelib.net.AlignmentMessage;
import com.gtnewhorizon.structurelib.structure.AutoPlaceEnvironment;
import com.gtnewhorizon.structurelib.structure.IStructureDefinition;
import cpw.mods.fml.common.network.NetworkRegistry;
/**
@@ -42,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<>();
/**
@@ -71,7 +74,7 @@ public class StructureLibAPI {
*/
public static void enableInstrument(Object identifier) {
if (isInstrumentEnabled()) throw new IllegalStateException();
instrument.set(instrument);
instrument.set(identifier);
}
/**
@@ -349,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));
}
}
@@ -190,4 +190,28 @@ public interface IAlignment extends IAlignmentLimits, IAlignmentProvider {
return getAlignmentLimits()
.isNewExtendedFacingValid(alignment.getDirection(), alignment.getRotation(), alignment.getFlip());
}
/**
* Check if this object support a flip change, assuming both direction and rotation is not changed.
*/
default boolean isFlipChangeAllowed() {
ExtendedFacing facing = getExtendedFacing();
for (Flip flip : Flip.VALUES) {
if (flip == getFlip()) continue;
if (isNewExtendedFacingValid(facing.with(flip))) return true;
}
return false;
}
/**
* Check if this object support a rotation change, assuming both direction and flip is not changed.
*/
default boolean isRotationChangeAllowed() {
ExtendedFacing facing = getExtendedFacing();
for (Rotation rotation : Rotation.VALUES) {
if (rotation == getRotation()) continue;
if (isNewExtendedFacingValid(facing.with(rotation))) return true;
}
return false;
}
}
@@ -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,27 +218,46 @@ 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()) {
@@ -227,21 +270,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 +310,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 +332,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 +367,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 +621,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 +662,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() {
@@ -32,6 +32,16 @@ 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;
}
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,6 +195,11 @@ 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);
@@ -31,6 +31,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()) {
@@ -62,7 +72,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,
@@ -32,6 +32,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);
@@ -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;
@@ -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;
@@ -38,6 +57,7 @@ import com.gtnewhorizon.structurelib.structure.adders.ITileAdder;
import com.gtnewhorizon.structurelib.util.ItemStackPredicate;
import com.gtnewhorizon.structurelib.util.ItemStackPredicate.NBTMode;
import com.gtnewhorizon.structurelib.util.Vec3Impl;
import cpw.mods.fml.common.registry.GameRegistry;
/**
@@ -137,6 +157,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);
@@ -168,6 +193,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);
@@ -207,6 +237,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);
@@ -300,18 +335,24 @@ 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()));
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;
}
@@ -461,6 +502,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);
@@ -481,6 +527,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());
@@ -501,6 +552,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);
@@ -538,7 +594,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("#########################################");
@@ -558,32 +614,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
@@ -591,10 +650,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 {
@@ -618,16 +677,29 @@ 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
*/
@@ -645,9 +717,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);
@@ -745,6 +831,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);
@@ -819,10 +910,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()) {
@@ -899,6 +996,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);
@@ -936,6 +1038,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);
@@ -960,6 +1067,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);
@@ -994,6 +1106,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);
@@ -1027,6 +1146,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);
@@ -1071,6 +1195,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);
@@ -1136,6 +1265,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);
@@ -1181,6 +1315,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);
@@ -1229,6 +1368,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>() {
@@ -1238,6 +1385,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);
@@ -1265,6 +1417,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) {
world.setBlock(x, y, z, defaultBlock, defaultMeta, 2);
@@ -1301,6 +1458,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);
@@ -1328,6 +1490,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);
@@ -1394,6 +1561,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);
@@ -1433,6 +1607,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);
@@ -1488,6 +1669,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);
@@ -1514,6 +1702,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);
@@ -1547,6 +1742,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);
@@ -1598,6 +1798,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);
@@ -1656,6 +1861,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);
@@ -1752,6 +1964,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);
@@ -1839,6 +2056,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);
@@ -1892,6 +2114,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);
@@ -2104,6 +2331,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);
@@ -2296,6 +2528,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);
@@ -2540,6 +2777,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
@@ -2591,7 +2833,7 @@ public class StructureUtility {
// we will bypass the chatter filter here, as this is a warning that player definitively want to see
// instead of some false positive error messages like item not find
warnNoExplicitSubChannel(env.getActor());
return backing.survivalPlaceBlock(t, world, x, y, z, trigger, env);
return backing.survivalPlaceBlock(t, world, x, y, z, newTrigger, env);
}
};
}
@@ -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> {
+111
View File
@@ -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.&lt;registry_key&gt;</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.&lt;registry_key&gt;.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.&lt;registry_key&gt;</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.&lt;registry_key&gt;.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!
@@ -172,3 +181,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=当前玩家的末影箱。需要在主物品栏中存在类似于末影背包的东西才会实际生效。
Binary file not shown.

Before

Width:  |  Height:  |  Size: 273 B

After

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 B

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 B

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 B

After

Width:  |  Height:  |  Size: 234 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 281 B

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 B

After

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 289 B

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 293 B

After

Width:  |  Height:  |  Size: 184 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 B

After

Width:  |  Height:  |  Size: 204 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 297 B

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 255 B

After

Width:  |  Height:  |  Size: 133 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 B

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 306 B

After

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 281 B

After

Width:  |  Height:  |  Size: 164 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 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"
}
+1
View File
@@ -0,0 +1 @@
public net.minecraft.client.gui.GuiSlot func_148136_c(IIII)V # overlayBackground