mirror of
https://github.com/GTNewHorizons/StructureLib.git
synced 2026-11-09 15:04:56 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fdea65537 | ||
|
|
89057ebb2d | ||
|
|
d829ae466b | ||
|
|
137f651486 | ||
|
|
ff88cbf2d7 | ||
|
|
4922760b46 | ||
|
|
45bd25401f | ||
|
|
050f0b5a5c | ||
|
|
2c5fa02a9e | ||
|
|
287e8317c9 | ||
|
|
95d1533342 | ||
|
|
19179ec894 | ||
|
|
d8b301457f | ||
|
|
40edf05109 | ||
|
|
4e5a92aff7 | ||
|
|
a09e6058a9 | ||
|
|
e774dd5e10 | ||
|
|
0866dbee98 | ||
|
|
208f7ffc41 | ||
|
|
88a6021546 | ||
|
|
a05267a566 | ||
|
|
dd3413dcaa | ||
|
|
34394ef8a5 | ||
|
|
7c0d3f4bd6 | ||
|
|
cd4f3617be | ||
|
|
256bc60d35 | ||
|
|
70426ab7b3 | ||
|
|
0030c02e9a | ||
|
|
7382db1bbd | ||
|
|
b669cbecf1 | ||
|
|
6283931667 | ||
|
|
3b3bc4b9ad | ||
|
|
bef4fdd949 | ||
|
|
4a06c8f95b | ||
|
|
241bed1989 | ||
|
|
f5594c25db | ||
|
|
7cda901ae5 | ||
|
|
538459eab5 | ||
|
|
3a9ec8b6ac | ||
|
|
330a13271b | ||
|
|
bf5dd5566b | ||
|
|
b9d0143245 | ||
|
|
43fc39c99b | ||
|
|
6337536dc3 | ||
|
|
9b96560119 | ||
|
|
3ce1c877e3 | ||
|
|
3d150d95cb | ||
|
|
03e84e3d37 | ||
|
|
547833bedb | ||
|
|
61e0d4b7e7 | ||
|
|
6867ed50e6 | ||
|
|
e7a47c199b | ||
|
|
b2bc474382 | ||
|
|
eef6baea4b | ||
|
|
efd49701e1 | ||
|
|
a7f0d6be72 | ||
|
|
0ff38c2da9 | ||
|
|
307bb1714c | ||
|
|
7d8e6a0ea5 | ||
|
|
b6d2af2170 | ||
|
|
8309567076 | ||
|
|
23312198a6 | ||
|
|
fa5e2ae4ca | ||
|
|
a8a1547845 | ||
|
|
113e05b7e6 | ||
|
|
080785ad05 | ||
|
|
6c14ca0a1d | ||
|
|
ea38c4e1d6 | ||
|
|
50e8c97aa3 | ||
|
|
9a82239f2c | ||
|
|
8ed2be38eb | ||
|
|
582f59e73d | ||
|
|
464e44de85 | ||
|
|
392d2ace8a | ||
|
|
978938d450 | ||
|
|
7a2328c294 | ||
|
|
d2cd45e07b | ||
|
|
8a24900623 | ||
|
|
15a0034e03 | ||
|
|
7aae6dc31c | ||
|
|
875bc1990e | ||
|
|
74aefd22be | ||
|
|
72ff51d093 | ||
|
|
7d74a65ec0 | ||
|
|
0ac8908b37 | ||
|
|
2dd4349e54 | ||
|
|
f813104ec4 | ||
|
|
4fe9eb6ea5 | ||
|
|
c0814274ec | ||
|
|
23605fc22d | ||
|
|
8ee90a7338 | ||
|
|
9bd19b9254 | ||
|
|
df4c54ea8d | ||
|
|
e71edd50b0 | ||
|
|
1b7cee7c4e | ||
|
|
03f2c31191 | ||
|
|
9b318f68fb | ||
|
|
adc91bb1dd | ||
|
|
21198c4d39 | ||
|
|
e70ea765f8 | ||
|
|
56a083ce6f | ||
|
|
7389f349db | ||
|
|
5b6b51f80c | ||
|
|
1481b68133 | ||
|
|
ebf2d6f0e1 | ||
|
|
7e7064e006 | ||
|
|
ebcf62fd97 | ||
|
|
74dde96905 | ||
|
|
d73d2bffc2 | ||
|
|
94055bbd6a | ||
|
|
dc1b1fc4db | ||
|
|
fd38dfc31f | ||
|
|
cb82b73e78 | ||
|
|
91d4a8022e | ||
|
|
a12722c4b0 | ||
|
|
b2624a3d36 | ||
|
|
b34e901a93 |
No files matched your search
@@ -0,0 +1,2 @@
|
||||
# Ignore spotlessApply reformat
|
||||
464e44de8541a46833f50c3bd363d50e1677a685
|
||||
+42
-20
@@ -1,22 +1,44 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
* text eol=lf
|
||||
|
||||
# Custom for Visual Studio
|
||||
*.cs diff=csharp
|
||||
*.sln merge=union
|
||||
*.csproj merge=union
|
||||
*.vbproj merge=union
|
||||
*.fsproj merge=union
|
||||
*.dbproj merge=union
|
||||
*.[jJ][aA][rR] binary
|
||||
|
||||
# Standard to msysgit
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
*.[pP][nN][gG] binary
|
||||
*.[jJ][pP][gG] binary
|
||||
*.[jJ][pP][eE][gG] binary
|
||||
*.[gG][iI][fF] binary
|
||||
*.[tT][iI][fF] binary
|
||||
*.[tT][iI][fF][fF] binary
|
||||
*.[iI][cC][oO] binary
|
||||
*.[sS][vV][gG] text
|
||||
*.[eE][pP][sS] binary
|
||||
*.[xX][cC][fF] binary
|
||||
|
||||
*.[kK][aA][rR] binary
|
||||
*.[mM]4[aA] binary
|
||||
*.[mM][iI][dD] binary
|
||||
*.[mM][iI][dD][iI] binary
|
||||
*.[mM][pP]3 binary
|
||||
*.[oO][gG][gG] binary
|
||||
*.[rR][aA] binary
|
||||
|
||||
*.7[zZ] binary
|
||||
*.[gG][zZ] binary
|
||||
*.[tT][aA][rR] binary
|
||||
*.[tT][gG][zZ] binary
|
||||
*.[zZ][iI][pP] binary
|
||||
|
||||
*.[tT][cC][nN] binary
|
||||
*.[sS][oO] binary
|
||||
*.[dD][lL][lL] binary
|
||||
*.[dD][yY][lL][iI][bB] binary
|
||||
*.[pP][sS][dD] binary
|
||||
*.[tT][tT][fF] binary
|
||||
*.[oO][tT][fF] binary
|
||||
|
||||
*.[pP][aA][tT][cC][hH] -text
|
||||
|
||||
*.[bB][aA][tT] text eol=crlf
|
||||
*.[cC][mM][dD] text eol=crlf
|
||||
*.[pP][sS]1 text eol=crlf
|
||||
|
||||
*[aA][uU][tT][oO][gG][eE][nN][eE][rR][aA][tT][eE][dD]* binary
|
||||
@@ -0,0 +1,17 @@
|
||||
name: Optimize images
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ master, main, release/** ]
|
||||
paths:
|
||||
- "**/*.png"
|
||||
|
||||
push:
|
||||
branches: [ master, main, release/** ]
|
||||
paths:
|
||||
- "**/*.png"
|
||||
|
||||
jobs:
|
||||
optimize-images:
|
||||
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/optimize-images.yml@master
|
||||
secrets: inherit
|
||||
@@ -0,0 +1,98 @@
|
||||
name: Deploy Javadoc
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- feature/doc
|
||||
- '**/*-javadoc'
|
||||
tags:
|
||||
- '*'
|
||||
delete:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pages: write
|
||||
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:
|
||||
result_hash: ${{ steps.deploy.outputs.hash }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: |
|
||||
8
|
||||
11
|
||||
17
|
||||
distribution: 'temurin'
|
||||
cache: gradle
|
||||
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
|
||||
- name: Generate Javadoc
|
||||
run: ./gradlew --no-daemon spotlessCheck javadoc
|
||||
|
||||
- name: Deploy 🚀
|
||||
id: deploy
|
||||
shell: bash
|
||||
run: |
|
||||
mv build/docs $RUNNER_TEMP/docs_output
|
||||
rm build -rf
|
||||
git checkout gh-pages
|
||||
mkdir -p $TARGET_FOLDER
|
||||
rm -rf $TARGET_FOLDER
|
||||
mv $RUNNER_TEMP/docs_output $TARGET_FOLDER -v
|
||||
git add $TARGET_FOLDER
|
||||
if git diff --cached --exit-code > /dev/null ; then
|
||||
echo Nothing to deploy
|
||||
exit 0
|
||||
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 "Deploy javadoc from ref $TARGET_FOLDER"
|
||||
git push
|
||||
echo "hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
TARGET_FOLDER: ${{ github.ref_name }}
|
||||
@@ -27,3 +27,4 @@ whitelist.json
|
||||
*.iws
|
||||
src/main/resources/mixins.*.json
|
||||
*.bat
|
||||
layout.json
|
||||
@@ -0,0 +1,674 @@
|
||||
GNU 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.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU 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
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
+165
@@ -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.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,41 @@
|
||||
javadoc {
|
||||
javadocTool = javaToolchains.javadocToolFor {
|
||||
// Last version with frames, but it also has search
|
||||
languageVersion = JavaLanguageVersion.of(25)
|
||||
vendor = JvmVendorSpec.ADOPTIUM
|
||||
}
|
||||
|
||||
inputs.file "src/main/javadoc/overview.html"
|
||||
|
||||
include "com/gtnewhorizon/structurelib/alignment/**"
|
||||
include "com/gtnewhorizon/structurelib/constructable/**"
|
||||
include "com/gtnewhorizon/structurelib/structure/**"
|
||||
include "com/gtnewhorizon/structurelib/util/**"
|
||||
include "com/gtnewhorizon/structurelib/StructureLibAPI.java"
|
||||
include "com/gtnewhorizon/structurelib/StructureEvent.java"
|
||||
|
||||
classpath += sourceSets.patchedMc.output
|
||||
|
||||
options {
|
||||
overview "src/main/javadoc/overview.html"
|
||||
links 'https://docs.oracle.com/en/java/javase/25/docs/api/'
|
||||
links 'https://skmedix.github.io/ForgeJavaDocs/javadoc/forge/1.7.10-10.13.4.1614/'
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register('javadocJar', Jar) {
|
||||
dependsOn javadoc
|
||||
archiveClassifier = 'javadoc'
|
||||
from javadoc.destinationDir
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives javadocJar
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed"
|
||||
}
|
||||
}
|
||||
+2
-933
@@ -1,936 +1,5 @@
|
||||
//version: 1659977784
|
||||
/*
|
||||
DO NOT CHANGE THIS FILE!
|
||||
Also, you may replace this file at any time if there is an update available.
|
||||
Please check https://github.com/GTNewHorizons/ExampleMod1.7.10/blob/main/build.gradle for updates.
|
||||
*/
|
||||
//version: 1707058017
|
||||
|
||||
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ConfigureShadowRelocation
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
import org.gradle.internal.logging.text.StyledTextOutput.Style
|
||||
import org.gradle.internal.logging.text.StyledTextOutputFactory
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
maven {
|
||||
name 'forge'
|
||||
url 'https://maven.minecraftforge.net'
|
||||
}
|
||||
maven {
|
||||
name 'sonatype'
|
||||
url 'https://oss.sonatype.org/content/repositories/snapshots/'
|
||||
}
|
||||
maven {
|
||||
name 'Scala CI dependencies'
|
||||
url 'https://repo1.maven.org/maven2/'
|
||||
}
|
||||
maven {
|
||||
name 'jitpack'
|
||||
url 'https://jitpack.io'
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.github.GTNewHorizons:ForgeGradle:1.2.7'
|
||||
}
|
||||
}
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'idea'
|
||||
id 'eclipse'
|
||||
id 'scala'
|
||||
id 'maven-publish'
|
||||
id 'org.jetbrains.kotlin.jvm' version '1.5.30' apply false
|
||||
id 'org.jetbrains.kotlin.kapt' version '1.5.30' apply false
|
||||
id 'com.google.devtools.ksp' version '1.5.30-1.0.0' apply false
|
||||
id 'org.ajoberstar.grgit' version '4.1.1'
|
||||
id 'com.github.johnrengelman.shadow' version '4.0.4'
|
||||
id 'com.palantir.git-version' version '0.13.0' apply false
|
||||
id 'de.undercouch.download' version '5.0.1'
|
||||
id 'com.github.gmazzo.buildconfig' version '3.0.3' apply false
|
||||
id "com.diffplug.spotless" version "6.7.2"
|
||||
}
|
||||
verifySettingsGradle()
|
||||
|
||||
dependencies {
|
||||
implementation 'com.diffplug:blowdryer:1.6.0'
|
||||
}
|
||||
|
||||
apply plugin: 'com.diffplug.blowdryer'
|
||||
|
||||
if (project.file('.git/HEAD').isFile()) {
|
||||
apply plugin: 'com.palantir.git-version'
|
||||
}
|
||||
|
||||
def out = services.get(StyledTextOutputFactory).create('an-output')
|
||||
|
||||
apply plugin: 'forge'
|
||||
|
||||
def projectJavaVersion = JavaLanguageVersion.of(8)
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(projectJavaVersion)
|
||||
}
|
||||
}
|
||||
|
||||
idea {
|
||||
module {
|
||||
inheritOutputDirs = true
|
||||
downloadJavadoc = true
|
||||
downloadSources = true
|
||||
}
|
||||
}
|
||||
apply from: Blowdryer.file('spotless.gradle')
|
||||
|
||||
if(JavaVersion.current() != JavaVersion.VERSION_1_8) {
|
||||
throw new GradleException("This project requires Java 8, but it's running on " + JavaVersion.current())
|
||||
}
|
||||
|
||||
checkPropertyExists("modName")
|
||||
checkPropertyExists("modId")
|
||||
checkPropertyExists("modGroup")
|
||||
checkPropertyExists("autoUpdateBuildScript")
|
||||
checkPropertyExists("minecraftVersion")
|
||||
checkPropertyExists("forgeVersion")
|
||||
checkPropertyExists("replaceGradleTokenInFile")
|
||||
checkPropertyExists("gradleTokenModId")
|
||||
checkPropertyExists("gradleTokenModName")
|
||||
checkPropertyExists("gradleTokenVersion")
|
||||
checkPropertyExists("gradleTokenGroupName")
|
||||
checkPropertyExists("apiPackage")
|
||||
checkPropertyExists("accessTransformersFile")
|
||||
checkPropertyExists("usesMixins")
|
||||
checkPropertyExists("mixinPlugin")
|
||||
checkPropertyExists("mixinsPackage")
|
||||
checkPropertyExists("coreModClass")
|
||||
checkPropertyExists("containsMixinsAndOrCoreModOnly")
|
||||
checkPropertyExists("usesShadowedDependencies")
|
||||
checkPropertyExists("developmentEnvironmentUserName")
|
||||
|
||||
boolean noPublishedSources = project.hasProperty("noPublishedSources") ? project.noPublishedSources.toBoolean() : false
|
||||
boolean usesMixinDebug = project.hasProperty('usesMixinDebug') ?: project.usesMixins.toBoolean()
|
||||
boolean forceEnableMixins = project.hasProperty('forceEnableMixins') ? project.forceEnableMixins.toBoolean() : false
|
||||
String channel = project.hasProperty('channel') ? project.channel : 'stable'
|
||||
String mappingsVersion = project.hasProperty('mappingsVersion') ? project.mappingsVersion : '12'
|
||||
String javaSourceDir = "src/main/java/"
|
||||
String scalaSourceDir = "src/main/scala/"
|
||||
String kotlinSourceDir = "src/main/kotlin/"
|
||||
|
||||
String targetPackageJava = javaSourceDir + modGroup.toString().replaceAll("\\.", "/")
|
||||
String targetPackageScala = scalaSourceDir + modGroup.toString().replaceAll("\\.", "/")
|
||||
String targetPackageKotlin = kotlinSourceDir + modGroup.toString().replaceAll("\\.", "/")
|
||||
if(!(getFile(targetPackageJava).exists() || getFile(targetPackageScala).exists() || getFile(targetPackageKotlin).exists())) {
|
||||
throw new GradleException("Could not resolve \"modGroup\"! Could not find " + targetPackageJava + " or " + targetPackageScala + " or " + targetPackageKotlin)
|
||||
}
|
||||
|
||||
if(apiPackage) {
|
||||
targetPackageJava = javaSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + apiPackage.toString().replaceAll("\\.", "/")
|
||||
targetPackageScala = scalaSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + apiPackage.toString().replaceAll("\\.", "/")
|
||||
targetPackageKotlin = kotlinSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + apiPackage.toString().replaceAll("\\.", "/")
|
||||
if(!(getFile(targetPackageJava).exists() || getFile(targetPackageScala).exists() || getFile(targetPackageKotlin).exists())) {
|
||||
throw new GradleException("Could not resolve \"apiPackage\"! Could not find " + targetPackageJava + " or " + targetPackageScala + " or " + targetPackageKotlin)
|
||||
}
|
||||
}
|
||||
|
||||
if(accessTransformersFile) {
|
||||
String targetFile = "src/main/resources/META-INF/" + accessTransformersFile
|
||||
if(!getFile(targetFile).exists()) {
|
||||
throw new GradleException("Could not resolve \"accessTransformersFile\"! Could not find " + targetFile)
|
||||
}
|
||||
}
|
||||
|
||||
if(usesMixins.toBoolean()) {
|
||||
if(mixinsPackage.isEmpty() || mixinPlugin.isEmpty()) {
|
||||
throw new GradleException("\"mixinPlugin\" requires \"mixinsPackage\" and \"mixinPlugin\" to be set!")
|
||||
}
|
||||
|
||||
targetPackageJava = javaSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + mixinsPackage.toString().replaceAll("\\.", "/")
|
||||
targetPackageScala = scalaSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + mixinsPackage.toString().replaceAll("\\.", "/")
|
||||
targetPackageKotlin = kotlinSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + mixinsPackage.toString().replaceAll("\\.", "/")
|
||||
if(!(getFile(targetPackageJava).exists() || getFile(targetPackageScala).exists() || getFile(targetPackageKotlin).exists())) {
|
||||
throw new GradleException("Could not resolve \"mixinsPackage\"! Could not find " + targetPackageJava + " or " + targetPackageScala + " or " + targetPackageKotlin)
|
||||
}
|
||||
|
||||
String targetFileJava = javaSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + mixinPlugin.toString().replaceAll("\\.", "/") + ".java"
|
||||
String targetFileScala = scalaSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + mixinPlugin.toString().replaceAll("\\.", "/") + ".scala"
|
||||
String targetFileScalaJava = scalaSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + mixinPlugin.toString().replaceAll("\\.", "/") + ".java"
|
||||
String targetFileKotlin = kotlinSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + mixinPlugin.toString().replaceAll("\\.", "/") + ".kt"
|
||||
if(!(getFile(targetFileJava).exists() || getFile(targetFileScala).exists() || getFile(targetFileScalaJava).exists() || getFile(targetFileKotlin).exists())) {
|
||||
throw new GradleException("Could not resolve \"mixinPlugin\"! Could not find " + targetFileJava + " or " + targetFileScala + " or " + targetFileScalaJava + " or " + targetFileKotlin)
|
||||
}
|
||||
}
|
||||
|
||||
if(coreModClass) {
|
||||
String targetFileJava = javaSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + coreModClass.toString().replaceAll("\\.", "/") + ".java"
|
||||
String targetFileScala = scalaSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + coreModClass.toString().replaceAll("\\.", "/") + ".scala"
|
||||
String targetFileScalaJava = scalaSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + coreModClass.toString().replaceAll("\\.", "/") + ".java"
|
||||
String targetFileKotlin = kotlinSourceDir + modGroup.toString().replaceAll("\\.", "/") + "/" + coreModClass.toString().replaceAll("\\.", "/") + ".kt"
|
||||
if(!(getFile(targetFileJava).exists() || getFile(targetFileScala).exists() || getFile(targetFileScalaJava).exists() || getFile(targetFileKotlin).exists())) {
|
||||
throw new GradleException("Could not resolve \"coreModClass\"! Could not find " + targetFileJava + " or " + targetFileScala + " or " + targetFileScalaJava + " or " + targetFileKotlin)
|
||||
}
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy.cacheChangingModulesFor(0, TimeUnit.SECONDS)
|
||||
|
||||
// Make sure GregTech build won't time out
|
||||
System.setProperty("org.gradle.internal.http.connectionTimeout", 120000 as String)
|
||||
System.setProperty("org.gradle.internal.http.socketTimeout", 120000 as String)
|
||||
}
|
||||
|
||||
// Fix Jenkins' Git: chmod a file should not be detected as a change and append a '.dirty' to the version
|
||||
try {
|
||||
'git config core.fileMode false'.execute()
|
||||
}
|
||||
catch (Exception ignored) {
|
||||
out.style(Style.Failure).println("git isn't installed at all")
|
||||
}
|
||||
|
||||
// Pulls version first from the VERSION env and then git tag
|
||||
String identifiedVersion
|
||||
String versionOverride = System.getenv("VERSION") ?: null
|
||||
try {
|
||||
identifiedVersion = versionOverride == null ? gitVersion() : versionOverride
|
||||
}
|
||||
catch (Exception ignored) {
|
||||
out.style(Style.Failure).text(
|
||||
'This mod must be version controlled by Git AND the repository must provide at least one tag,\n' +
|
||||
'or the VERSION override must be set! ').style(Style.SuccessHeader).text('(Do NOT download from GitHub using the ZIP option, instead\n' +
|
||||
'clone the repository, see ').style(Style.Info).text('https://gtnh.miraheze.org/wiki/Development').style(Style.SuccessHeader).println(' for details.)'
|
||||
)
|
||||
versionOverride = 'NO-GIT-TAG-SET'
|
||||
identifiedVersion = versionOverride
|
||||
}
|
||||
version = minecraftVersion + '-' + identifiedVersion
|
||||
ext {
|
||||
modVersion = identifiedVersion
|
||||
}
|
||||
|
||||
if(identifiedVersion == versionOverride) {
|
||||
out.style(Style.Failure).text('Override version to ').style(Style.Identifier).text(modVersion).style(Style.Failure).println('!\7')
|
||||
}
|
||||
|
||||
group = modGroup
|
||||
if(project.hasProperty("customArchiveBaseName") && customArchiveBaseName) {
|
||||
archivesBaseName = customArchiveBaseName
|
||||
}
|
||||
else {
|
||||
archivesBaseName = modId
|
||||
}
|
||||
|
||||
def arguments = []
|
||||
def jvmArguments = []
|
||||
|
||||
if (usesMixins.toBoolean() || forceEnableMixins) {
|
||||
arguments += [
|
||||
"--tweakClass org.spongepowered.asm.launch.MixinTweaker"
|
||||
]
|
||||
if (usesMixinDebug.toBoolean()) {
|
||||
jvmArguments += [
|
||||
"-Dmixin.debug.countInjections=true",
|
||||
"-Dmixin.debug.verbose=true",
|
||||
"-Dmixin.debug.export=true"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
minecraft {
|
||||
version = minecraftVersion + '-' + forgeVersion + '-' + minecraftVersion
|
||||
runDir = 'run'
|
||||
|
||||
if (replaceGradleTokenInFile) {
|
||||
replaceIn replaceGradleTokenInFile
|
||||
if(gradleTokenModId) {
|
||||
replace gradleTokenModId, modId
|
||||
}
|
||||
if(gradleTokenModName) {
|
||||
replace gradleTokenModName, modName
|
||||
}
|
||||
if(gradleTokenVersion) {
|
||||
replace gradleTokenVersion, modVersion
|
||||
}
|
||||
if(gradleTokenGroupName) {
|
||||
replace gradleTokenGroupName, modGroup
|
||||
}
|
||||
}
|
||||
|
||||
clientIntellijRun {
|
||||
args(arguments)
|
||||
jvmArgs(jvmArguments)
|
||||
|
||||
if(developmentEnvironmentUserName) {
|
||||
args("--username", developmentEnvironmentUserName)
|
||||
}
|
||||
}
|
||||
|
||||
serverIntellijRun {
|
||||
args(arguments)
|
||||
jvmArgs(jvmArguments)
|
||||
}
|
||||
}
|
||||
|
||||
if(file('addon.gradle').exists()) {
|
||||
apply from: 'addon.gradle'
|
||||
}
|
||||
|
||||
apply from: 'repositories.gradle'
|
||||
|
||||
configurations {
|
||||
implementation.extendsFrom(shadowImplementation) // TODO: remove after all uses are refactored
|
||||
implementation.extendsFrom(shadowCompile)
|
||||
implementation.extendsFrom(shadeCompile)
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name 'Overmind forge repo mirror'
|
||||
url 'https://gregtech.overminddl1.com/'
|
||||
}
|
||||
if(usesMixins.toBoolean() || forceEnableMixins) {
|
||||
maven {
|
||||
name 'sponge'
|
||||
url 'https://repo.spongepowered.org/repository/maven-public'
|
||||
}
|
||||
maven {
|
||||
url 'https://jitpack.io'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
if(usesMixins.toBoolean()) {
|
||||
annotationProcessor('org.ow2.asm:asm-debug-all:5.0.3')
|
||||
annotationProcessor('com.google.guava:guava:24.1.1-jre')
|
||||
annotationProcessor('com.google.code.gson:gson:2.8.6')
|
||||
annotationProcessor('org.spongepowered:mixin:0.8-SNAPSHOT')
|
||||
}
|
||||
if(usesMixins.toBoolean() || forceEnableMixins) {
|
||||
// using 0.8 to workaround a issue in 0.7 which fails mixin application
|
||||
compile('com.github.GTNewHorizons:SpongePoweredMixin:0.7.12-GTNH') {
|
||||
// Mixin includes a lot of dependencies that are too up-to-date
|
||||
exclude module: 'launchwrapper'
|
||||
exclude module: 'guava'
|
||||
exclude module: 'gson'
|
||||
exclude module: 'commons-io'
|
||||
exclude module: 'log4j-core'
|
||||
}
|
||||
compile('com.github.GTNewHorizons:SpongeMixins:1.5.0')
|
||||
}
|
||||
}
|
||||
|
||||
apply from: 'dependencies.gradle'
|
||||
|
||||
def mixingConfigRefMap = 'mixins.' + modId + '.refmap.json'
|
||||
def refMap = "${tasks.compileJava.temporaryDir}" + File.separator + mixingConfigRefMap
|
||||
def mixinSrg = "${tasks.reobf.temporaryDir}" + File.separator + "mixins.srg"
|
||||
|
||||
task generateAssets {
|
||||
if (usesMixins.toBoolean()) {
|
||||
def mixinConfigFile = getFile("/src/main/resources/mixins." + modId + ".json");
|
||||
if (!mixinConfigFile.exists()) {
|
||||
mixinConfigFile.text = """{
|
||||
"required": true,
|
||||
"minVersion": "0.7.11",
|
||||
"package": "${modGroup}.${mixinsPackage}",
|
||||
"plugin": "${modGroup}.${mixinPlugin}",
|
||||
"refmap": "${mixingConfigRefMap}",
|
||||
"target": "@env(DEFAULT)",
|
||||
"compatibilityLevel": "JAVA_8",
|
||||
"mixins": [],
|
||||
"client": [],
|
||||
"server": []
|
||||
}
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task relocateShadowJar(type: ConfigureShadowRelocation) {
|
||||
target = tasks.shadowJar
|
||||
prefix = modGroup + ".shadow"
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
project.configurations.shadeCompile.each { dep ->
|
||||
from(project.zipTree(dep)) {
|
||||
exclude 'META-INF', 'META-INF/**'
|
||||
}
|
||||
}
|
||||
|
||||
manifest {
|
||||
attributes(getManifestAttributes())
|
||||
}
|
||||
|
||||
minimize() // This will only allow shading for actually used classes
|
||||
configurations = [
|
||||
project.configurations.shadowImplementation,
|
||||
project.configurations.shadowCompile
|
||||
]
|
||||
dependsOn(relocateShadowJar)
|
||||
}
|
||||
|
||||
jar {
|
||||
project.configurations.shadeCompile.each { dep ->
|
||||
from(project.zipTree(dep)) {
|
||||
exclude 'META-INF', 'META-INF/**'
|
||||
}
|
||||
}
|
||||
|
||||
manifest {
|
||||
attributes(getManifestAttributes())
|
||||
}
|
||||
|
||||
if(usesShadowedDependencies.toBoolean()) {
|
||||
dependsOn(shadowJar)
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
reobf {
|
||||
if(usesMixins.toBoolean()) {
|
||||
addExtraSrgFile mixinSrg
|
||||
}
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
if(usesMixins.toBoolean()) {
|
||||
tasks.compileJava {
|
||||
options.compilerArgs += [
|
||||
"-AreobfSrgFile=${tasks.reobf.srg}",
|
||||
"-AoutSrgFile=${mixinSrg}",
|
||||
"-AoutRefMapFile=${refMap}",
|
||||
// Elan: from what I understand they are just some linter configs so you get some warning on how to properly code
|
||||
"-XDenableSunApiLintControl",
|
||||
"-XDignore.symbol.file"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
runClient {
|
||||
if(developmentEnvironmentUserName) {
|
||||
arguments += [
|
||||
"--username",
|
||||
developmentEnvironmentUserName
|
||||
]
|
||||
}
|
||||
|
||||
args(arguments)
|
||||
jvmArgs(jvmArguments)
|
||||
}
|
||||
|
||||
runServer {
|
||||
args(arguments)
|
||||
jvmArgs(jvmArguments)
|
||||
}
|
||||
|
||||
tasks.withType(JavaExec).configureEach {
|
||||
javaLauncher.set(
|
||||
javaToolchains.launcherFor {
|
||||
languageVersion = projectJavaVersion
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
processResources {
|
||||
// this will ensure that this task is redone when the versions change.
|
||||
inputs.property "version", project.version
|
||||
inputs.property "mcversion", project.minecraft.version
|
||||
exclude("spotless.gradle")
|
||||
|
||||
// replace stuff in mcmod.info, nothing else
|
||||
from(sourceSets.main.resources.srcDirs) {
|
||||
include 'mcmod.info'
|
||||
|
||||
// replace modVersion and minecraftVersion
|
||||
expand "minecraftVersion": project.minecraft.version,
|
||||
"modVersion": modVersion,
|
||||
"modId": modId,
|
||||
"modName": modName
|
||||
}
|
||||
|
||||
if(usesMixins.toBoolean()) {
|
||||
from refMap
|
||||
}
|
||||
|
||||
// copy everything else that's not the mcmod.info
|
||||
from(sourceSets.main.resources.srcDirs) {
|
||||
exclude 'mcmod.info'
|
||||
exclude 'spotless.gradle'
|
||||
}
|
||||
}
|
||||
|
||||
def getManifestAttributes() {
|
||||
def manifestAttributes = [:]
|
||||
if(!containsMixinsAndOrCoreModOnly.toBoolean() && (usesMixins.toBoolean() || coreModClass)) {
|
||||
manifestAttributes += ["FMLCorePluginContainsFMLMod": true]
|
||||
}
|
||||
|
||||
if(accessTransformersFile) {
|
||||
manifestAttributes += ["FMLAT" : accessTransformersFile.toString()]
|
||||
}
|
||||
|
||||
if(coreModClass) {
|
||||
manifestAttributes += ["FMLCorePlugin": modGroup + "." + coreModClass]
|
||||
}
|
||||
|
||||
if(usesMixins.toBoolean()) {
|
||||
manifestAttributes += [
|
||||
"TweakClass" : "org.spongepowered.asm.launch.MixinTweaker",
|
||||
"MixinConfigs" : "mixins." + modId + ".json",
|
||||
"ForceLoadAsMod" : !containsMixinsAndOrCoreModOnly.toBoolean()
|
||||
]
|
||||
}
|
||||
return manifestAttributes
|
||||
}
|
||||
|
||||
task sourcesJar(type: Jar) {
|
||||
from (sourceSets.main.allSource)
|
||||
from (file("$projectDir/LICENSE"))
|
||||
getArchiveClassifier().set('sources')
|
||||
}
|
||||
|
||||
task shadowDevJar(type: ShadowJar) {
|
||||
project.configurations.shadeCompile.each { dep ->
|
||||
from(project.zipTree(dep)) {
|
||||
exclude 'META-INF', 'META-INF/**'
|
||||
}
|
||||
}
|
||||
|
||||
from sourceSets.main.output
|
||||
getArchiveClassifier().set("dev")
|
||||
|
||||
manifest {
|
||||
attributes(getManifestAttributes())
|
||||
}
|
||||
|
||||
minimize() // This will only allow shading for actually used classes
|
||||
configurations = [
|
||||
project.configurations.shadowImplementation,
|
||||
project.configurations.shadowCompile
|
||||
]
|
||||
}
|
||||
|
||||
task relocateShadowDevJar(type: ConfigureShadowRelocation) {
|
||||
target = tasks.shadowDevJar
|
||||
prefix = modGroup + ".shadow"
|
||||
}
|
||||
|
||||
task circularResolverJar(type: Jar) {
|
||||
dependsOn(relocateShadowDevJar)
|
||||
dependsOn(shadowDevJar)
|
||||
enabled = false
|
||||
}
|
||||
|
||||
task devJar(type: Jar) {
|
||||
project.configurations.shadeCompile.each { dep ->
|
||||
from(project.zipTree(dep)) {
|
||||
exclude 'META-INF', 'META-INF/**'
|
||||
}
|
||||
}
|
||||
|
||||
from sourceSets.main.output
|
||||
getArchiveClassifier().set("dev")
|
||||
|
||||
manifest {
|
||||
attributes(getManifestAttributes())
|
||||
}
|
||||
|
||||
if(usesShadowedDependencies.toBoolean()) {
|
||||
dependsOn(circularResolverJar)
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
task apiJar(type: Jar) {
|
||||
from (sourceSets.main.allSource) {
|
||||
include modGroup.toString().replaceAll("\\.", "/") + "/" + apiPackage.toString().replaceAll("\\.", "/") + '/**'
|
||||
}
|
||||
|
||||
from (sourceSets.main.output) {
|
||||
include modGroup.toString().replaceAll("\\.", "/") + "/" + apiPackage.toString().replaceAll("\\.", "/") + '/**'
|
||||
}
|
||||
|
||||
from (sourceSets.main.resources.srcDirs) {
|
||||
include("LICENSE")
|
||||
}
|
||||
|
||||
getArchiveClassifier().set('api')
|
||||
}
|
||||
|
||||
artifacts {
|
||||
if(!noPublishedSources) {
|
||||
archives sourcesJar
|
||||
}
|
||||
archives devJar
|
||||
if(apiPackage) {
|
||||
archives apiJar
|
||||
}
|
||||
}
|
||||
|
||||
// The gradle metadata includes all of the additional deps that we disabled from POM generation (including forgeBin with no groupID),
|
||||
// and isn't strictly needed with the POM so just disable it.
|
||||
tasks.withType(GenerateModuleMetadata) {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
// workaround variable hiding in pom processing
|
||||
def projectConfigs = project.configurations
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
maven(MavenPublication) {
|
||||
from components.java
|
||||
if(usesShadowedDependencies.toBoolean()) {
|
||||
artifact source: shadowJar, classifier: ""
|
||||
}
|
||||
if(!noPublishedSources) {
|
||||
artifact source: sourcesJar, classifier: "sources"
|
||||
}
|
||||
artifact source: usesShadowedDependencies.toBoolean() ? shadowDevJar : devJar, classifier: "dev"
|
||||
if (apiPackage) {
|
||||
artifact source: apiJar, classifier: "api"
|
||||
}
|
||||
|
||||
groupId = System.getenv("ARTIFACT_GROUP_ID") ?: "com.github.GTNewHorizons"
|
||||
artifactId = System.getenv("ARTIFACT_ID") ?: project.name
|
||||
// Using the identified version, not project.version as it has the prepended 1.7.10
|
||||
version = System.getenv("RELEASE_VERSION") ?: identifiedVersion
|
||||
|
||||
// remove extra garbage from minecraft and minecraftDeps configuration
|
||||
pom.withXml {
|
||||
def badArtifacts = [:].withDefault {[] as Set<String>}
|
||||
for (configuration in [
|
||||
projectConfigs.minecraft,
|
||||
projectConfigs.minecraftDeps
|
||||
]) {
|
||||
for (dependency in configuration.allDependencies) {
|
||||
badArtifacts[dependency.group == null ? "" : dependency.group] += dependency.name
|
||||
}
|
||||
}
|
||||
// example for specifying extra stuff to ignore
|
||||
// badArtifacts["org.example.group"] += "artifactName"
|
||||
|
||||
Node pomNode = asNode()
|
||||
pomNode.dependencies.'*'.findAll() {
|
||||
badArtifacts[it.groupId.text()].contains(it.artifactId.text())
|
||||
}.each() {
|
||||
it.parent().remove(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url = "http://jenkins.usrv.eu:8081/nexus/content/repositories/releases"
|
||||
credentials {
|
||||
username = System.getenv("MAVEN_USER") ?: "NONE"
|
||||
password = System.getenv("MAVEN_PASSWORD") ?: "NONE"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Updating
|
||||
task updateBuildScript {
|
||||
doLast {
|
||||
if (performBuildScriptUpdate(projectDir.toString())) return
|
||||
|
||||
print("Build script already up-to-date!")
|
||||
}
|
||||
}
|
||||
|
||||
if (!project.getGradle().startParameter.isOffline() && isNewBuildScriptVersionAvailable(projectDir.toString())) {
|
||||
if (autoUpdateBuildScript.toBoolean()) {
|
||||
performBuildScriptUpdate(projectDir.toString())
|
||||
} else {
|
||||
out.style(Style.SuccessHeader).println("Build script update available! Run 'gradle updateBuildScript'")
|
||||
}
|
||||
}
|
||||
|
||||
static URL availableBuildScriptUrl() {
|
||||
new URL("https://raw.githubusercontent.com/GTNewHorizons/ExampleMod1.7.10/main/build.gradle")
|
||||
}
|
||||
static URL exampleSettingsGradleUrl() {
|
||||
new URL("https://raw.githubusercontent.com/GTNewHorizons/ExampleMod1.7.10/main/settings.gradle.example")
|
||||
}
|
||||
|
||||
|
||||
def verifySettingsGradle() {
|
||||
def settingsFile = getFile("settings.gradle")
|
||||
if (!settingsFile.exists()) {
|
||||
println("Downloading default settings.gradle")
|
||||
exampleSettingsGradleUrl().withInputStream { i -> settingsFile.withOutputStream { it << i } }
|
||||
throw new GradleException("Settings.gradle has been updated, please re-run task.")
|
||||
}
|
||||
}
|
||||
|
||||
boolean performBuildScriptUpdate(String projectDir) {
|
||||
if (isNewBuildScriptVersionAvailable(projectDir)) {
|
||||
def buildscriptFile = getFile("build.gradle")
|
||||
availableBuildScriptUrl().withInputStream { i -> buildscriptFile.withOutputStream { it << i } }
|
||||
out.style(Style.Success).print("Build script updated. Please REIMPORT the project or RESTART your IDE!")
|
||||
verifySettingsGradle()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
boolean isNewBuildScriptVersionAvailable(String projectDir) {
|
||||
Map parameters = ["connectTimeout": 2000, "readTimeout": 2000]
|
||||
|
||||
String currentBuildScript = getFile("build.gradle").getText()
|
||||
String currentBuildScriptHash = getVersionHash(currentBuildScript)
|
||||
String availableBuildScript = availableBuildScriptUrl().newInputStream(parameters).getText()
|
||||
String availableBuildScriptHash = getVersionHash(availableBuildScript)
|
||||
|
||||
boolean isUpToDate = currentBuildScriptHash.empty || availableBuildScriptHash.empty || currentBuildScriptHash == availableBuildScriptHash
|
||||
return !isUpToDate
|
||||
}
|
||||
|
||||
static String getVersionHash(String buildScriptContent) {
|
||||
String versionLine = buildScriptContent.find("^//version: [a-z0-9]*")
|
||||
if(versionLine != null) {
|
||||
return versionLine.split(": ").last()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
configure(updateBuildScript) {
|
||||
group = 'forgegradle'
|
||||
description = 'Updates the build script to the latest version'
|
||||
}
|
||||
|
||||
// Parameter Deobfuscation
|
||||
|
||||
task deobfParams {
|
||||
doLast {
|
||||
|
||||
String mcpDir = "$project.gradle.gradleUserHomeDir/caches/minecraft/de/oceanlabs/mcp/mcp_$channel/$mappingsVersion"
|
||||
String mcpZIP = "$mcpDir/mcp_$channel-$mappingsVersion-${minecraftVersion}.zip"
|
||||
String paramsCSV = "$mcpDir/params.csv"
|
||||
|
||||
download.run {
|
||||
src "https://maven.minecraftforge.net/de/oceanlabs/mcp/mcp_$channel/$mappingsVersion-$minecraftVersion/mcp_$channel-$mappingsVersion-${minecraftVersion}.zip"
|
||||
dest mcpZIP
|
||||
overwrite false
|
||||
}
|
||||
|
||||
if(!file(paramsCSV).exists()) {
|
||||
println("Extracting MCP archive ...")
|
||||
unzip(mcpZIP, mcpDir)
|
||||
}
|
||||
|
||||
println("Parsing params.csv ...")
|
||||
Map<String, String> params = new HashMap<>()
|
||||
Files.lines(Paths.get(paramsCSV)).forEach{line ->
|
||||
String[] cells = line.split(",")
|
||||
if(cells.length > 2 && cells[0].matches("p_i?\\d+_\\d+_")) {
|
||||
params.put(cells[0], cells[1])
|
||||
}
|
||||
}
|
||||
|
||||
out.style(Style.Success).println("Modified ${replaceParams(file("$projectDir/src/main/java"), params)} files!")
|
||||
out.style(Style.Failure).println("Don't forget to verify that the code still works as before!\n It could be broken due to duplicate variables existing now\n or parameters taking priority over other variables.")
|
||||
}
|
||||
}
|
||||
|
||||
static int replaceParams(File file, Map<String, String> params) {
|
||||
int fileCount = 0
|
||||
|
||||
if(file.isDirectory()) {
|
||||
for(File f : file.listFiles()) {
|
||||
fileCount += replaceParams(f, params)
|
||||
}
|
||||
return fileCount
|
||||
}
|
||||
println("Visiting ${file.getName()} ...")
|
||||
try {
|
||||
String content = new String(Files.readAllBytes(file.toPath()))
|
||||
int hash = content.hashCode()
|
||||
params.forEach{key, value ->
|
||||
content = content.replaceAll(key, value)
|
||||
}
|
||||
if(hash != content.hashCode()) {
|
||||
Files.write(file.toPath(), content.getBytes("UTF-8"))
|
||||
return 1
|
||||
}
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Credit: bitsnaps (https://gist.github.com/bitsnaps/00947f2dce66f4bbdabc67d7e7b33681)
|
||||
static unzip(String zipFileName, String outputDir) {
|
||||
byte[] buffer = new byte[16384]
|
||||
ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFileName))
|
||||
ZipEntry zipEntry = zis.getNextEntry()
|
||||
while (zipEntry != null) {
|
||||
File newFile = new File(outputDir + File.separator, zipEntry.name)
|
||||
if (zipEntry.isDirectory()) {
|
||||
if (!newFile.isDirectory() && !newFile.mkdirs()) {
|
||||
throw new IOException("Failed to create directory $newFile")
|
||||
}
|
||||
} else {
|
||||
// fix for Windows-created archives
|
||||
File parent = newFile.parentFile
|
||||
if (!parent.isDirectory() && !parent.mkdirs()) {
|
||||
throw new IOException("Failed to create directory $parent")
|
||||
}
|
||||
// write file content
|
||||
FileOutputStream fos = new FileOutputStream(newFile)
|
||||
int len = 0
|
||||
while ((len = zis.read(buffer)) > 0) {
|
||||
fos.write(buffer, 0, len)
|
||||
}
|
||||
fos.close()
|
||||
}
|
||||
zipEntry = zis.getNextEntry()
|
||||
}
|
||||
zis.closeEntry()
|
||||
zis.close()
|
||||
}
|
||||
|
||||
configure(deobfParams) {
|
||||
group = 'forgegradle'
|
||||
description = 'Rename all obfuscated parameter names inherited from Minecraft classes'
|
||||
}
|
||||
|
||||
// Dependency Deobfuscation
|
||||
|
||||
def deobf(String sourceURL) {
|
||||
try {
|
||||
URL url = new URL(sourceURL)
|
||||
String fileName = url.getFile()
|
||||
|
||||
//get rid of directories:
|
||||
int lastSlash = fileName.lastIndexOf("/")
|
||||
if(lastSlash > 0) {
|
||||
fileName = fileName.substring(lastSlash + 1)
|
||||
}
|
||||
//get rid of extension:
|
||||
if(fileName.endsWith(".jar") || fileName.endsWith(".litemod")) {
|
||||
fileName = fileName.substring(0, fileName.lastIndexOf("."))
|
||||
}
|
||||
|
||||
String hostName = url.getHost()
|
||||
if(hostName.startsWith("www.")) {
|
||||
hostName = hostName.substring(4)
|
||||
}
|
||||
List parts = Arrays.asList(hostName.split("\\."))
|
||||
Collections.reverse(parts)
|
||||
hostName = String.join(".", parts)
|
||||
|
||||
return deobf(sourceURL, "$hostName/$fileName")
|
||||
} catch(Exception e) {
|
||||
return deobf(sourceURL, "deobf/${sourceURL.hashCode()}")
|
||||
}
|
||||
}
|
||||
|
||||
// The method above is to be preferred. Use this method if the filename is not at the end of the URL.
|
||||
def deobf(String sourceURL, String rawFileName) {
|
||||
String bon2Version = "2.5.1"
|
||||
String fileName = URLDecoder.decode(rawFileName, "UTF-8")
|
||||
String cacheDir = "$project.gradle.gradleUserHomeDir/caches"
|
||||
String bon2Dir = "$cacheDir/forge_gradle/deobf"
|
||||
String bon2File = "$bon2Dir/BON2-${bon2Version}.jar"
|
||||
String obfFile = "$cacheDir/modules-2/files-2.1/${fileName}.jar"
|
||||
String deobfFile = "$cacheDir/modules-2/files-2.1/${fileName}-deobf.jar"
|
||||
|
||||
if(file(deobfFile).exists()) {
|
||||
return files(deobfFile)
|
||||
}
|
||||
|
||||
String mappingsVer
|
||||
String remoteMappings = project.hasProperty('remoteMappings') ? project.remoteMappings : 'https://raw.githubusercontent.com/MinecraftForge/FML/1.7.10/conf/'
|
||||
if(remoteMappings) {
|
||||
String id = "${forgeVersion.split("\\.")[3]}-$minecraftVersion"
|
||||
String mappingsZIP = "$cacheDir/forge_gradle/maven_downloader/de/oceanlabs/mcp/mcp_snapshot_nodoc/$id/mcp_snapshot_nodoc-${id}.zip"
|
||||
|
||||
zipMappings(mappingsZIP, remoteMappings, bon2Dir)
|
||||
|
||||
mappingsVer = "snapshot_$id"
|
||||
} else {
|
||||
mappingsVer = "${channel}_$mappingsVersion"
|
||||
}
|
||||
|
||||
download.run {
|
||||
src "http://jenkins.usrv.eu:8081/nexus/content/repositories/releases/com/github/parker8283/BON2/$bon2Version-CUSTOM/BON2-$bon2Version-CUSTOM-all.jar"
|
||||
dest bon2File
|
||||
quiet true
|
||||
overwrite false
|
||||
}
|
||||
|
||||
download.run {
|
||||
src sourceURL
|
||||
dest obfFile
|
||||
quiet true
|
||||
overwrite false
|
||||
}
|
||||
|
||||
exec {
|
||||
commandLine 'java', '-jar', bon2File, '--inputJar', obfFile, '--outputJar', deobfFile, '--mcVer', minecraftVersion, '--mappingsVer', mappingsVer, '--notch'
|
||||
workingDir bon2Dir
|
||||
standardOutput = new FileOutputStream("${deobfFile}.log")
|
||||
}
|
||||
|
||||
return files(deobfFile)
|
||||
}
|
||||
|
||||
def zipMappings(String zipPath, String url, String bon2Dir) {
|
||||
File zipFile = new File(zipPath)
|
||||
if(zipFile.exists()) {
|
||||
return
|
||||
}
|
||||
|
||||
String fieldsCache = "$bon2Dir/data/fields.csv"
|
||||
String methodsCache = "$bon2Dir/data/methods.csv"
|
||||
|
||||
download.run {
|
||||
src "${url}fields.csv"
|
||||
dest fieldsCache
|
||||
quiet true
|
||||
}
|
||||
download.run {
|
||||
src "${url}methods.csv"
|
||||
dest methodsCache
|
||||
quiet true
|
||||
}
|
||||
|
||||
zipFile.getParentFile().mkdirs()
|
||||
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))
|
||||
|
||||
zos.putNextEntry(new ZipEntry("fields.csv"))
|
||||
Files.copy(Paths.get(fieldsCache), zos)
|
||||
zos.closeEntry()
|
||||
|
||||
zos.putNextEntry(new ZipEntry("methods.csv"))
|
||||
Files.copy(Paths.get(methodsCache), zos)
|
||||
zos.closeEntry()
|
||||
|
||||
zos.close()
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
|
||||
def checkPropertyExists(String propertyName) {
|
||||
if (!project.hasProperty(propertyName)) {
|
||||
throw new GradleException("This project requires a property \"" + propertyName + "\"! Please add it your \"gradle.properties\". You can find all properties and their description here: https://github.com/GTNewHorizons/ExampleMod1.7.10/blob/main/gradle.properties")
|
||||
}
|
||||
}
|
||||
|
||||
def getFile(String relativePath) {
|
||||
return new File(projectDir, relativePath)
|
||||
id 'com.gtnewhorizons.gtnhconvention'
|
||||
}
|
||||
+5
-1
@@ -1,5 +1,9 @@
|
||||
// Add your dependencies here
|
||||
|
||||
dependencies {
|
||||
compile("com.github.GTNewHorizons:GTNHLib:0.0.1:dev")
|
||||
api("com.github.GTNewHorizons:GTNHLib:0.11.21:dev")
|
||||
implementation("com.github.GTNewHorizons:NotEnoughItems:2.8.110-GTNH:dev")
|
||||
testImplementation(platform('org.junit:junit-bom:5.9.2'))
|
||||
testImplementation('org.junit.jupiter:junit-jupiter')
|
||||
testRuntimeOnly ('org.junit.platform:junit-platform-launcher')
|
||||
}
|
||||
+207
-29
@@ -1,60 +1,238 @@
|
||||
# ExampleMod tag to use as Blowdryer (Spotless, etc.) settings version, leave empty to disable.
|
||||
# LOCAL to test local config updates.
|
||||
gtnh.settings.blowdryerTag = 0.2.0
|
||||
|
||||
# Human-readable mod name, available for mcmod.info population.
|
||||
modName = StructureLib
|
||||
|
||||
# This is a case-sensitive string to identify your mod. Convention is to use lower case.
|
||||
# Case-sensitive identifier string, available for mcmod.info population and used for automatic mixin JSON generation.
|
||||
# Conventionally lowercase.
|
||||
modId = structurelib
|
||||
|
||||
# Root package of the mod, used to find various classes in other properties,
|
||||
# mcmod.info substitution, enabling assertions in run tasks, etc.
|
||||
modGroup = com.gtnewhorizon.structurelib
|
||||
|
||||
# WHY is there no version field?
|
||||
# The build script relies on git to provide a version via tags. It is super easy and will enable you to always know the
|
||||
# code base or your binary. Check out this tutorial: https://blog.mattclemente.com/2017/10/13/versioning-with-git-tags/
|
||||
# Whether to use modGroup as the maven publishing group.
|
||||
# When false, com.github.GTNewHorizons is used.
|
||||
useModGroupForPublishing = false
|
||||
|
||||
# Will update your build.gradle automatically whenever an update is available
|
||||
# Updates your build.gradle and settings.gradle automatically whenever an update is available.
|
||||
autoUpdateBuildScript = false
|
||||
|
||||
# Version of Minecraft to target
|
||||
minecraftVersion = 1.7.10
|
||||
|
||||
# Version of Minecraft Forge to target
|
||||
forgeVersion = 10.13.4.1614
|
||||
|
||||
# Select a username for testing your mod with breakpoints. You may leave this empty for a random user name each time you
|
||||
# restart Minecraft in development. Choose this dependent on your mod:
|
||||
# Do you need consistent player progressing (for example Thaumcraft)? -> Select a name
|
||||
# Do you need to test how your custom blocks interacts with a player that is not the owner? -> leave name empty
|
||||
developmentEnvironmentUserName = "Developer"
|
||||
# Specify an MCP channel for dependency deobfuscation and the deobfParams task.
|
||||
channel = stable
|
||||
|
||||
# Define a source file of your project with:
|
||||
# public static final String VERSION = "GRADLETOKEN_VERSION";
|
||||
# The string's content will be replaced with your mods version when compiled. You should use this to specify your mod's
|
||||
# version in @Mod([...], version = VERSION, [...])
|
||||
# Leave these properties empty to skip individual token replacements
|
||||
replaceGradleTokenInFile = StructureLib.java
|
||||
# Specify an MCP mappings version for dependency deobfuscation and the deobfParams task.
|
||||
mappingsVersion = 12
|
||||
|
||||
# Defines other MCP mappings for dependency deobfuscation.
|
||||
remoteMappings = https\://raw.githubusercontent.com/MinecraftForge/FML/1.7.10/conf/
|
||||
|
||||
# Select a default username for testing your mod. You can always override this per-run by running
|
||||
# `./gradlew runClient --username=AnotherPlayer`, or configuring this command in your IDE.
|
||||
developmentEnvironmentUserName = Developer
|
||||
|
||||
# Enables modern Java syntax support. Valid values:
|
||||
# - false: No modern syntax, Java 8 only
|
||||
# - jabel: Jabel syntax-only support, compiles to J8 bytecode
|
||||
# - jvmDowngrader: Full modern Java via JVM Downgrader (syntax + stdlib APIs)
|
||||
# - modern: Native modern Java bytecode, no downgrading
|
||||
enableModernJavaSyntax = jabel
|
||||
|
||||
# If set, ignores the above setting and compiles with the given toolchain. This may cause unexpected issues,
|
||||
# and should *not* be used in most situations. -1 disables this.
|
||||
# forceToolchainVersion = -1
|
||||
|
||||
# Target JVM version for JVM Downgrader bytecode downgrading.
|
||||
# Only used when enableModernJavaSyntax = jvmDowngrader
|
||||
# downgradeTargetVersion = 8
|
||||
|
||||
# Comma-separated list of Java versions for multi-release jar support (JVM Downgrader only).
|
||||
# Classes will be available in META-INF/versions/N/ for each version N in this list.
|
||||
# Default: "21,25" (J25+ gets native classes, J21-24 gets partial downgrade, J8-20 gets full downgrade).
|
||||
# jvmDowngraderMultiReleaseVersions = 21,25
|
||||
|
||||
# Specifies how JVM Downgrader API stubs are provided. Options:
|
||||
# - shade: Shade minimized stubs into the jar
|
||||
# - gtnhlib: GTNHLib provides stubs at runtime (adds version constraint)
|
||||
# - external: Another dependency provides stubs (no constraint, no warning)
|
||||
# - (empty): Warning reminding you to configure stubs
|
||||
# Note: 'shade' option requires you to verify license compliance, see: https://github.com/unimined/JvmDowngrader/blob/main/LICENSE.md
|
||||
# jvmDowngraderStubsProvider =
|
||||
|
||||
# Enables injecting missing generics into the decompiled source code for a better coding experience.
|
||||
# Turns most publicly visible List, Map, etc. into proper List<E>, Map<K, V> types.
|
||||
enableGenericInjection = true
|
||||
|
||||
# Generate a class with a String field for the mod version named as defined below.
|
||||
# If generateGradleTokenClass is empty or not missing, no such class will be generated.
|
||||
# If gradleTokenVersion is empty or missing, the field will not be present in the class.
|
||||
generateGradleTokenClass = com.gtnewhorizon.structurelib.Tags
|
||||
|
||||
# Name of the token containing the project's current version to generate/replace.
|
||||
gradleTokenVersion = VERSION
|
||||
|
||||
# [DEPRECATED] Mod ID replacement token.
|
||||
gradleTokenModId =
|
||||
|
||||
# [DEPRECATED] Mod name replacement token.
|
||||
gradleTokenModName =
|
||||
gradleTokenVersion = GRADLETOKEN_VERSION
|
||||
|
||||
# [DEPRECATED] Mod Group replacement token.
|
||||
gradleTokenGroupName =
|
||||
|
||||
# In case your mod provides an API for other mods to implement you may declare its package here. Otherwise you can
|
||||
# [DEPRECATED]
|
||||
# Multiple source files can be defined here by providing a comma-separated list: Class1.java,Class2.java,Class3.java
|
||||
# public static final String VERSION = "GRADLETOKEN_VERSION";
|
||||
# The string's content will be replaced with your mod's version when compiled. You should use this to specify your mod's
|
||||
# version in @Mod([...], version = VERSION, [...]).
|
||||
# Leave these properties empty to skip individual token replacements.
|
||||
replaceGradleTokenInFile =
|
||||
|
||||
# In case your mod provides an API for other mods to implement you may declare its package here. Otherwise, you can
|
||||
# leave this property empty.
|
||||
# Example value: apiPackage = api + modGroup = com.myname.mymodid -> com.myname.mymodid.api
|
||||
# Example value: (apiPackage = api) + (modGroup = com.myname.mymodid) -> com.myname.mymodid.api
|
||||
apiPackage =
|
||||
|
||||
# Specify the configuration file for Forge's access transformers here. I must be placed into /src/main/resources/META-INF/
|
||||
# Example value: mymodid_at.cfg
|
||||
# Specify the configuration file for Forge's access transformers here. It must be placed into /src/main/resources/META-INF/
|
||||
# There can be multiple files in a space-separated list.
|
||||
# Example value: mymodid_at.cfg nei_at.cfg
|
||||
accessTransformersFile =
|
||||
|
||||
# Provides setup for Mixins if enabled. If you don't know what mixins are: Keep it disabled!
|
||||
usesMixins = false
|
||||
usesMixins = true
|
||||
|
||||
# Set to a non-empty string to configure mixins in a separate source set under src/VALUE, instead of src/main.
|
||||
# This can speed up compile times thanks to not running the mixin annotation processor on all input sources.
|
||||
# Mixin classes will have access to "main" classes, but not the other way around.
|
||||
separateMixinSourceSet =
|
||||
|
||||
# Adds some debug arguments like verbose output and class export.
|
||||
usesMixinDebug = false
|
||||
|
||||
# Specify the location of your implementation of IMixinConfigPlugin. Leave it empty otherwise.
|
||||
mixinPlugin =
|
||||
# Specify the package that contains all of your Mixins. You may only place Mixins in this package or the build will fail!
|
||||
mixinsPackage =
|
||||
|
||||
# Specify the package that contains all of your Mixins. The package must exist or
|
||||
# the build will fail. If you have a package property defined in your mixins.<modid>.json,
|
||||
# it must match with this or the build will fail.
|
||||
mixinsPackage = mixins
|
||||
|
||||
# Specify the core mod entry class if you use a core mod. This class must implement IFMLLoadingPlugin!
|
||||
# This parameter is for legacy compatability only
|
||||
# Example value: coreModClass = asm.FMLPlugin + modGroup = com.myname.mymodid -> com.myname.mymodid.asm.FMLPlugin
|
||||
coreModClass =
|
||||
# This parameter is for legacy compatibility only
|
||||
# Example value: (coreModClass = asm.FMLPlugin) + (modGroup = com.myname.mymodid) -> com.myname.mymodid.asm.FMLPlugin
|
||||
coreModClass = core.StructureLibCore
|
||||
|
||||
# If your project is only a consolidation of mixins or a core mod and does NOT contain a 'normal' mod ( = some class
|
||||
# that is annotated with @Mod) you want this to be true. When in doubt: leave it on false!
|
||||
containsMixinsAndOrCoreModOnly = false
|
||||
|
||||
# If enabled, you may use 'shadowImplementation' for dependencies. They will be integrated in your jar. It is your
|
||||
# responsibility check the licence and request permission for distribution, if required.
|
||||
# Enables Mixins even if this mod doesn't use them, useful if one of the dependencies uses mixins.
|
||||
forceEnableMixins = false
|
||||
|
||||
# If enabled, you may use 'shadowCompile' for dependencies. They will be integrated into your jar. It is your
|
||||
# responsibility to check the license and request permission for distribution if required.
|
||||
usesShadowedDependencies = false
|
||||
|
||||
# If disabled, won't remove unused classes from shadowed dependencies. Some libraries use reflection to access
|
||||
# their own classes, making the minimization unreliable.
|
||||
minimizeShadowedDependencies = true
|
||||
|
||||
# If disabled, won't rename the shadowed classes.
|
||||
relocateShadowedDependencies = true
|
||||
|
||||
# Adds CurseMaven, Modrinth, and some more well-known 1.7.10 repositories.
|
||||
includeWellKnownRepositories = true
|
||||
|
||||
# A list of repositories to exclude from the includeWellKnownRepositories setting. Should be a space separated
|
||||
# list of strings, with the acceptable keys being(case does not matter):
|
||||
# cursemaven
|
||||
# modrinth
|
||||
excludeWellKnownRepositories =
|
||||
|
||||
# Change these to your Maven coordinates if you want to publish to a custom Maven repository instead of the default GTNH Maven.
|
||||
# Authenticate with the MAVEN_USER and MAVEN_PASSWORD environment variables.
|
||||
# If you need a more complex setup disable maven publishing here and add a publishing repository to addon.gradle.
|
||||
usesMavenPublishing = true
|
||||
|
||||
# Maven repository to publish the mod to.
|
||||
# mavenPublishUrl = https\://nexus.gtnewhorizons.com/repository/releases/
|
||||
|
||||
# Publishing to Modrinth requires you to set the MODRINTH_TOKEN environment variable to your current Modrinth API token.
|
||||
#
|
||||
# The project's ID on Modrinth. Can be either the slug or the ID.
|
||||
# Leave this empty if you don't want to publish to Modrinth.
|
||||
modrinthProjectId =
|
||||
|
||||
# The project's relations on Modrinth. You can use this to refer to other projects on Modrinth.
|
||||
# Syntax: scope1-type1:name1;scope2-type2:name2;...
|
||||
# Where scope can be one of [required, optional, incompatible, embedded],
|
||||
# type can be one of [project, version],
|
||||
# and the name is the Modrinth project or version slug/id of the other mod.
|
||||
# Example: required-project:fplib;optional-project:gasstation;incompatible-project:gregtech
|
||||
# Note: UniMixins is automatically set as a required dependency if usesMixins = true.
|
||||
modrinthRelations =
|
||||
|
||||
# Publishing to CurseForge requires you to set the CURSEFORGE_TOKEN environment variable to one of your CurseForge API tokens.
|
||||
#
|
||||
# The project's numeric ID on CurseForge. You can find this in the About Project box.
|
||||
# Leave this empty if you don't want to publish on CurseForge.
|
||||
curseForgeProjectId =
|
||||
|
||||
# The project's relations on CurseForge. You can use this to refer to other projects on CurseForge.
|
||||
# Syntax: type1:name1;type2:name2;...
|
||||
# Where type can be one of [requiredDependency, embeddedLibrary, optionalDependency, tool, incompatible],
|
||||
# and the name is the CurseForge project slug of the other mod.
|
||||
# Example: requiredDependency:railcraft;embeddedLibrary:cofhlib;incompatible:buildcraft
|
||||
# Note: UniMixins is automatically set as a required dependency if usesMixins = true.
|
||||
curseForgeRelations =
|
||||
|
||||
# Optional parameter to customize the produced artifacts. Use this to preserve artifact naming when migrating older
|
||||
# projects. New projects should not use this parameter.
|
||||
# customArchiveBaseName =
|
||||
|
||||
# Optional parameter to customize the default working directory used by the runClient* tasks. Relative to the project directory.
|
||||
# runClientWorkingDirectory = run/client
|
||||
|
||||
# Optional parameter to customize the default working directory used by the runServer* tasks. Relative to the project directory.
|
||||
# runServerWorkingDirectory = run/server
|
||||
|
||||
# Optional parameter to have the build automatically fail if an illegal version is used.
|
||||
# This can be useful if you e.g. only want to allow versions in the form of '1.1.xxx'.
|
||||
# The check is ONLY performed if the version is a git tag.
|
||||
# Note: the specified string must be escaped, so e.g. 1\\.1\\.\\d+ instead of 1\.1\.\d+
|
||||
# versionPattern =
|
||||
|
||||
# Uncomment to prevent the source code from being published.
|
||||
# noPublishedSources = true
|
||||
|
||||
# Uncomment this to disable Spotless checks.
|
||||
# This should only be uncommented to keep it easier to sync with upstream/other forks.
|
||||
# That is, if there is no other active fork/upstream, NEVER change this.
|
||||
# disableSpotless = true
|
||||
|
||||
# Uncomment this to disable Checkstyle checks (currently wildcard import check).
|
||||
# disableCheckstyle = true
|
||||
|
||||
# Override the IDEA build type. Valid values are: "" (leave blank, do not override), "idea" (force use native IDEA build), "gradle"
|
||||
# (force use delegated build).
|
||||
# This is meant to be set in $HOME/.gradle/gradle.properties.
|
||||
# e.g. add "systemProp.org.gradle.project.ideaOverrideBuildType=idea" will override the build type to be native build.
|
||||
# WARNING: If you do use this option, it will overwrite whatever you have in your existing projects. This might not be what you want!
|
||||
# Usually there is no need to uncomment this here as other developers do not necessarily use the same build type as you.
|
||||
# ideaOverrideBuildType = idea
|
||||
|
||||
# Whether IDEA should run spotless checks when pressing the Build button.
|
||||
# This is meant to be set in $HOME/.gradle/gradle.properties.
|
||||
# ideaCheckSpotlessOnBuild = true
|
||||
|
||||
# Non-GTNH properties
|
||||
org.gradle.configuration-cache = true
|
||||
org.gradle.parallel = true
|
||||
@@ -0,0 +1,12 @@
|
||||
#This file is generated by updateDaemonJvm
|
||||
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/cf726b4a1c84b50457225f9bba6d7650/redirect
|
||||
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/fa1e318c287360478e3c83a9a3ef1007/redirect
|
||||
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/cf726b4a1c84b50457225f9bba6d7650/redirect
|
||||
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/fa1e318c287360478e3c83a9a3ef1007/redirect
|
||||
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c2dd35c9d0aaf0ba6ad0791320f99dfc/redirect
|
||||
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/e5810bd7fd1f8a586644409d395a7e55/redirect
|
||||
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/cf726b4a1c84b50457225f9bba6d7650/redirect
|
||||
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/fa1e318c287360478e3c83a9a3ef1007/redirect
|
||||
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/7b3c4877c0749019e6805bb61e421497/redirect
|
||||
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/d76df094a9cbbabd3b08251f9e61444a/redirect
|
||||
toolchainVersion=25
|
||||
Vendored
BIN
Binary file not shown.
+3
-1
@@ -1,5 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
# Copyright © 2015 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -15,80 +15,114 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# 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"'
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@@ -97,92 +131,118 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
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.
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
Vendored
+27
-34
@@ -13,8 +13,10 @@
|
||||
@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
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@@ -25,10 +27,14 @@
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@@ -37,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
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
|
||||
|
||||
@@ -51,48 +57,35 @@ goto fail
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
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
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
@@ -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/"
|
||||
}
|
||||
}
|
||||
+19
-6
@@ -1,10 +1,23 @@
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
maven {
|
||||
// RetroFuturaGradle
|
||||
name "GTNH Maven"
|
||||
url "https://nexus.gtnewhorizons.com/repository/public/"
|
||||
mavenContent {
|
||||
includeGroup("com.gtnewhorizons")
|
||||
includeGroupByRegex("com\\.gtnewhorizons\\..+")
|
||||
}
|
||||
}
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
mavenLocal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'com.diffplug.blowdryerSetup' version '1.6.0'
|
||||
id 'com.gtnewhorizons.gtnhsettingsconvention' version '2.0.26'
|
||||
}
|
||||
|
||||
apply plugin: 'com.diffplug.blowdryerSetup'
|
||||
|
||||
blowdryerSetup {
|
||||
github('GTNewHorizons/ExampleMod1.7.10', 'tag', '0.1.4')
|
||||
//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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.gtnewhorizon.structurelib;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.ChatComponentTranslation;
|
||||
import net.minecraft.util.MovingObjectPosition;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
|
||||
import com.gtnewhorizon.structurelib.net.SetChannelDataMessage;
|
||||
|
||||
/**
|
||||
* Handler for middle-click channel picking. When the player holds a hologram projector on the cursor and middle-clicks
|
||||
* a registered indicator item, the corresponding channel is set on the projector. The same applies in world when the
|
||||
* projector is held in hand and a registered indicator block is middle-clicked. Shift+middle-click wipes all channels.
|
||||
* Was made side neutral to support server-side validation at some point if necessary.
|
||||
*/
|
||||
public final class ChannelPickHandler {
|
||||
|
||||
private ChannelPickHandler() {}
|
||||
|
||||
/**
|
||||
* Handle a middle-click event while holding a hologram projector on the cursor.
|
||||
*/
|
||||
public static boolean handleMiddleClick(EntityPlayer player, ItemStack holoStack, @Nullable ItemStack targetStack,
|
||||
final boolean isSneaking) {
|
||||
return handlePick(player, holoStack, targetStack, isSneaking, true);
|
||||
}
|
||||
|
||||
public static boolean handleWorldPick(World world, EntityPlayer player, ItemStack holoStack,
|
||||
@Nullable MovingObjectPosition hit, final boolean isSneaking) {
|
||||
ItemStack targetStack = null;
|
||||
|
||||
if (hit != null) {
|
||||
Block block = world.getBlock(hit.blockX, hit.blockY, hit.blockZ);
|
||||
if (!block.isAir(world, hit.blockX, hit.blockY, hit.blockZ)) {
|
||||
targetStack = block.getPickBlock(hit, world, hit.blockX, hit.blockY, hit.blockZ, player);
|
||||
}
|
||||
}
|
||||
|
||||
// A null target still goes through so that shift+middle-click wipes even when aiming at nothing
|
||||
return handlePick(player, holoStack, targetStack, isSneaking, false);
|
||||
}
|
||||
|
||||
private static boolean handlePick(EntityPlayer player, ItemStack holoStack, @Nullable ItemStack targetStack,
|
||||
final boolean isSneaking, final boolean onCursor) {
|
||||
// Clear channels on shift+middle-click. The "press again to confirm" gesture is gated client-side
|
||||
// (see ChannelWipeConfirmation) before this method is ever called.
|
||||
if (isSneaking) {
|
||||
ChannelDataAccessor.wipeChannelData(holoStack);
|
||||
sendChannelData(player, holoStack, onCursor);
|
||||
player.addChatMessage(new ChatComponentTranslation("structurelib.pickchannel.cleared"));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (targetStack == null) return false; // nothing hovered, cancel silently
|
||||
|
||||
Collection<Map.Entry<String, Integer>> channels = ChannelDescription.iterate(targetStack);
|
||||
if (channels.isEmpty()) {
|
||||
player.addChatMessage(new ChatComponentTranslation("structurelib.pickchannel.nomapping"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Assuming there's only 1 channel per item for now (same behavior as drag and drop)
|
||||
Map.Entry<String, Integer> first = channels.iterator().next();
|
||||
ChannelDataAccessor.setChannelData(holoStack, first.getKey(), first.getValue());
|
||||
sendChannelData(player, holoStack, onCursor);
|
||||
player.addChatMessage(
|
||||
new ChatComponentTranslation("structurelib.pickchannel.success", first.getKey(), first.getValue()));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Player is the one triggering this update, so should already be in-sync.
|
||||
*/
|
||||
private static void sendChannelData(EntityPlayer player, ItemStack holoStack, boolean onCursor) {
|
||||
if (!(player instanceof EntityPlayerMP)) {
|
||||
StructureLib.net.sendToServer(new SetChannelDataMessage(holoStack, onCursor));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.gtnewhorizon.structurelib;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.util.ChatComponentTranslation;
|
||||
|
||||
/**
|
||||
* Client-side-only confirmation gate for "wipe all channels" shift+middle-click gesture.
|
||||
*/
|
||||
public final class ChannelWipeConfirmation {
|
||||
|
||||
private static final long CONFIRM_WINDOW_MS = 5000;
|
||||
private static long armedAtMs = -1;
|
||||
|
||||
private ChannelWipeConfirmation() {}
|
||||
|
||||
/**
|
||||
* Call this before invoking {@link ChannelPickHandler} whenever the wipe gesture (shift+middle-click) is detected.
|
||||
*
|
||||
* @return true if this is a confirming second press within the window and the wipe should proceed now; false if
|
||||
* this press only armed the confirmation.
|
||||
*/
|
||||
public static boolean confirm(EntityPlayer player) {
|
||||
long now = Minecraft.getSystemTime();
|
||||
if (now - armedAtMs > CONFIRM_WINDOW_MS) {
|
||||
armedAtMs = now;
|
||||
player.addChatMessage(new ChatComponentTranslation("structurelib.pickchannel.confirmwipe"));
|
||||
return false;
|
||||
}
|
||||
armedAtMs = -1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +1,130 @@
|
||||
package com.gtnewhorizon.structurelib;
|
||||
|
||||
import com.google.common.collect.BiMap;
|
||||
import com.google.common.collect.HashBiMap;
|
||||
import com.gtnewhorizon.structurelib.entity.fx.EntityFXBlockHint;
|
||||
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
import static com.gtnewhorizon.structurelib.StructureLib.RANDOM;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
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;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.profiler.Profiler;
|
||||
import net.minecraft.util.ChatComponentText;
|
||||
import net.minecraft.util.IChatComponent;
|
||||
import net.minecraft.util.IIcon;
|
||||
import net.minecraft.util.Vec3;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.client.event.MouseEvent;
|
||||
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.item.ItemConstructableTrigger;
|
||||
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;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
|
||||
public class ClientProxy extends CommonProxy {
|
||||
|
||||
private static final short[] NO_TINT = {255, 255, 255, 0};
|
||||
private static final short[] RGBA_NO_TINT = { 255, 255, 255, 255 };
|
||||
private static final short[] RGBA_RED_TINT = { 255, 128, 128, 0 };
|
||||
private static final Map<HintParticleInfo, HintGroup> allHints = new HashMap<>();
|
||||
/**
|
||||
* All batches of hints.
|
||||
*/
|
||||
private static final List<HintGroup> allGroups = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Current batch of hints. Belongs to the same logical multiblock.
|
||||
*/
|
||||
private static HintGroup currentHints;
|
||||
/**
|
||||
* Collection of all hints. Sorted from farthest (index 0) to nearest (index size - 1)
|
||||
* <p>
|
||||
* 10k should be more than enough, if not a bit wasteful
|
||||
*/
|
||||
private static final List<HintParticleInfo> allHintsForRender = new ArrayList<>(10000);
|
||||
/**
|
||||
* If the diff to current player position is too great sort the allHints. Initial value is very far off below y=0
|
||||
* but not huge enough to make a NaN.
|
||||
* <p>
|
||||
* We are using this supposedly immutable object as a mutable object here. So be aware.
|
||||
*/
|
||||
private static final Vec3 lastPlayerPos = Vec3.createVectorHelper(0, -1e30, 0);
|
||||
/**
|
||||
* if true rebuild the allHints list from all hintOwners (and sort it)
|
||||
*/
|
||||
private static boolean allHintsDirty = false;
|
||||
/**
|
||||
* counter keeping track of how many renderThrough hint particle we have. enable optimization if no renderThrough
|
||||
*/
|
||||
private static int renderThrough;
|
||||
/**
|
||||
* A throttle map for local player. Integrated server will use the throttle map in super class instead.
|
||||
*/
|
||||
private static final Map<Object, Long> localThrottleMap = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void hintParticleTinted(World w, int x, int y, int z, IIcon[] icons, short[] RGBa) {
|
||||
EntityFXBlockHint hint = new EntityFXBlockHint(w, x, y, z, icons).withColorTint(RGBa);
|
||||
Minecraft.getMinecraft().effectRenderer.addEffect(hint);
|
||||
ensureHinting();
|
||||
HintParticleInfo info = new HintParticleInfo(w, x, y, z, icons, RGBa);
|
||||
|
||||
// check and remove colliding holograms
|
||||
if (ConfigurationHandler.INSTANCE.isRemoveCollidingHologram()) {
|
||||
HintParticleInfo info = new HintParticleInfo(x, y, z, currentHints);
|
||||
EntityFXBlockHint dupe = allHints.inverse().get(info);
|
||||
if (dupe != null) {
|
||||
List<EntityFXBlockHint> owner = allHints.get(dupe).owner;
|
||||
// in case someone forget to call endHinting
|
||||
if (owner != currentHints) {
|
||||
hintOwners.remove(owner);
|
||||
owner.forEach(EntityFXBlockHint::setDead);
|
||||
owner.clear();
|
||||
}
|
||||
HintGroup dupe = allHints.get(info);
|
||||
if (dupe != null && dupe != currentHints) {
|
||||
allGroups.remove(dupe);
|
||||
removeGroup(dupe);
|
||||
}
|
||||
allHints.forcePut(hint, info);
|
||||
}
|
||||
currentHints.add(hint);
|
||||
allHints.put(info, currentHints);
|
||||
currentHints.getHints().add(info);
|
||||
allHintsDirty = true;
|
||||
|
||||
EntityFX particle = new WeightlessParticleFX(
|
||||
w,
|
||||
x + RANDOM.nextFloat() * 0.5F,
|
||||
y + RANDOM.nextFloat() * 0.5F,
|
||||
z + RANDOM.nextFloat() * 0.5F,
|
||||
0,
|
||||
0,
|
||||
0);
|
||||
particle.setRBGColorF(0, 0.6F * RANDOM.nextFloat(), 0.8f);
|
||||
Minecraft.getMinecraft().effectRenderer.addEffect(particle);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -53,25 +134,67 @@ public class ClientProxy extends CommonProxy {
|
||||
|
||||
@Override
|
||||
public void hintParticle(World w, int x, int y, int z, IIcon[] icons) {
|
||||
hintParticleTinted(w, x, y, z, icons, NO_TINT);
|
||||
hintParticleTinted(w, x, y, z, icons, RGBA_NO_TINT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hintParticle(World w, int x, int y, int z, Block block, int meta) {
|
||||
hintParticleTinted(w, x, y, z, createIIconFromBlock(block, meta), NO_TINT);
|
||||
hintParticleTinted(w, x, y, z, createIIconFromBlock(block, meta), RGBA_NO_TINT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean updateHintParticleTint(EntityPlayer player, World w, int x, int y, int z, short[] rgBa) {
|
||||
if (player instanceof EntityPlayerMP) return super.updateHintParticleTint(player, w, x, y, z, rgBa);
|
||||
if (player != getCurrentPlayer()) return false; // how?
|
||||
HintParticleInfo info = new HintParticleInfo(x, y, z, null);
|
||||
EntityFXBlockHint existing = allHints.inverse().get(info);
|
||||
if (existing != null) {
|
||||
existing.withColorTint(rgBa);
|
||||
return true;
|
||||
HintParticleInfo hint = getHintParticleInfo(w, x, y, z);
|
||||
if (hint == null) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
hint.setTint(rgBa);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static HintParticleInfo getHintParticleInfo(World w, int x, int y, int z) {
|
||||
HintParticleInfo info = new HintParticleInfo(w, x, y, z, null, null);
|
||||
HintGroup existing = allHints.get(info);
|
||||
if (existing != null) {
|
||||
for (HintParticleInfo hint : existing.getHints()) {
|
||||
if (hint.equals(info)) {
|
||||
return hint;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean markHintParticleError(EntityPlayer player, World w, int x, int y, int z) {
|
||||
if (player instanceof EntityPlayerMP) return super.markHintParticleError(player, w, x, y, z);
|
||||
if (player != getCurrentPlayer()) return false; // how?
|
||||
HintParticleInfo hint = getHintParticleInfo(w, x, y, z);
|
||||
if (hint == null) {
|
||||
return false;
|
||||
}
|
||||
hint.setTint(RGBA_RED_TINT);
|
||||
hint.setRenderThrough();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addThrottledChat(Object throttleKey, EntityPlayer player, IChatComponent text, short intervalRequired,
|
||||
boolean forceUpdateLastSend) {
|
||||
if (player instanceof EntityPlayerMP)
|
||||
super.addThrottledChat(throttleKey, player, text, intervalRequired, forceUpdateLastSend);
|
||||
else if (player != null)
|
||||
addThrottledChat(throttleKey, player, text, intervalRequired, forceUpdateLastSend, localThrottleMap);
|
||||
}
|
||||
|
||||
static void removeGroup(HintGroup group) {
|
||||
for (HintParticleInfo hintParticleInfo : group.getHints()) {
|
||||
allHints.remove(hintParticleInfo);
|
||||
if (hintParticleInfo.renderThrough) renderThrough--;
|
||||
}
|
||||
allHintsDirty = true;
|
||||
}
|
||||
|
||||
private static IIcon[] createIIconFromBlock(Block block, int meta) {
|
||||
@@ -100,76 +223,148 @@ public class ClientProxy extends CommonProxy {
|
||||
return player == Minecraft.getMinecraft().thePlayer;
|
||||
}
|
||||
|
||||
private static final BiMap<EntityFXBlockHint, HintParticleInfo> allHints = HashBiMap.create();
|
||||
private static final List<List<EntityFXBlockHint>> hintOwners = new ArrayList<>();
|
||||
private static List<EntityFXBlockHint> currentHints;
|
||||
|
||||
public static void onHintDead(EntityFXBlockHint fx) {
|
||||
if (ConfigurationHandler.INSTANCE.isRemoveCollidingHologram()) allHints.remove(fx);
|
||||
for (Iterator<List<EntityFXBlockHint>> iterator = hintOwners.iterator(); iterator.hasNext(); ) {
|
||||
List<EntityFXBlockHint> list = iterator.next();
|
||||
if (list.remove(fx)) {
|
||||
if (list.isEmpty()) iterator.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startHinting(World w) {
|
||||
if (!w.isRemote) return;
|
||||
if (currentHints != null) hintOwners.add(currentHints);
|
||||
currentHints = new LinkedList<>();
|
||||
if (currentHints != null) endHinting(w);
|
||||
currentHints = new HintGroup();
|
||||
}
|
||||
|
||||
private void ensureHinting() {
|
||||
if (currentHints == null) currentHints = new LinkedList<>();
|
||||
if (currentHints == null) currentHints = new HintGroup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endHinting(World w) {
|
||||
if (!w.isRemote) return;
|
||||
while (!hintOwners.isEmpty() && hintOwners.size() >= ConfigurationHandler.INSTANCE.getMaxCoexistingHologram()) {
|
||||
List<EntityFXBlockHint> list = hintOwners.remove(0);
|
||||
list.forEach(EntityFXBlockHint::setDead);
|
||||
list.clear();
|
||||
if (!w.isRemote || currentHints == null) return;
|
||||
while (!allGroups.isEmpty() && allGroups.size() >= ConfigurationHandler.INSTANCE.getMaxCoexistingHologram()) {
|
||||
allGroups.remove(0);
|
||||
}
|
||||
if (!currentHints.isEmpty()) hintOwners.add(currentHints);
|
||||
if (!currentHints.getHints().isEmpty()) allGroups.add(currentHints);
|
||||
// we use the player existence time here as some worlds don't really advance the time ticker
|
||||
currentHints.setCreationTime(getCurrentPlayer().ticksExisted);
|
||||
currentHints = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preInit(FMLPreInitializationEvent e) {
|
||||
MinecraftForge.EVENT_BUS.register(new ForgeEventHandler());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getOverworldTime() {
|
||||
// there is no overworld, better just hope current world time is ok...
|
||||
return Minecraft.getMinecraft().theWorld.getTotalWorldTime();
|
||||
}
|
||||
|
||||
private static class HintParticleInfo {
|
||||
private final int x, y, z;
|
||||
private final List<EntityFXBlockHint> owner;
|
||||
@Override
|
||||
public void uploadChannels(ItemStack trigger) {
|
||||
StructureLib.net.sendToServer(new SetChannelDataMessage(trigger));
|
||||
}
|
||||
|
||||
public HintParticleInfo(int x, int y, int z, List<EntityFXBlockHint> owner) {
|
||||
@Override
|
||||
public void preInit(FMLPreInitializationEvent e) {
|
||||
FMLCommonHandler.instance().bus().register(new FMLEventHandler());
|
||||
MinecraftForge.EVENT_BUS.register(new ForgeEventHandler());
|
||||
}
|
||||
|
||||
static void markTextureUsed(IIcon icon) {
|
||||
if (StructureLib.COMPAT instanceof IStructureCompat)
|
||||
((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<>();
|
||||
private int creationTime = -1;
|
||||
|
||||
public List<HintParticleInfo> getHints() {
|
||||
return hints;
|
||||
}
|
||||
|
||||
public void setCreationTime(int creationTime) {
|
||||
this.creationTime = creationTime;
|
||||
}
|
||||
|
||||
public int getCreationTime() {
|
||||
return creationTime;
|
||||
}
|
||||
}
|
||||
|
||||
private static class HintParticleInfo {
|
||||
|
||||
private final World w;
|
||||
// these are the block coordinate for e.g. w.getBlock()
|
||||
private final int x, y, z;
|
||||
private final IIcon[] icons;
|
||||
private short[] tint;
|
||||
private boolean renderThrough;
|
||||
|
||||
private final long creationTime = System.currentTimeMillis(); // use tick time instead maybe
|
||||
|
||||
public HintParticleInfo(World w, int x, int y, int z, IIcon[] icons, short[] tint) {
|
||||
this.w = w;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.owner = owner;
|
||||
this.icons = icons;
|
||||
this.tint = tint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (!(o instanceof HintParticleInfo that)) return false;
|
||||
|
||||
HintParticleInfo that = (HintParticleInfo) o;
|
||||
return x == that.x && y == that.y && z == that.z;
|
||||
}
|
||||
|
||||
if (x != that.x) return false;
|
||||
if (y != that.y) return false;
|
||||
return z == that.z;
|
||||
public void setTint(short[] tint) {
|
||||
this.tint = tint;
|
||||
}
|
||||
|
||||
public void setRenderThrough() {
|
||||
if (!this.renderThrough) {
|
||||
ClientProxy.renderThrough += 1;
|
||||
allHintsDirty = true;
|
||||
}
|
||||
this.renderThrough = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -179,15 +374,313 @@ public class ClientProxy extends CommonProxy {
|
||||
result = 31 * result + z;
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean isInFrustrum(Frustrum frustrum) {
|
||||
return frustrum.isBoxInFrustum(x + 0.25, y + 0.25, z + 0.25, x + 0.75, y + 0.75, z + 0.75);
|
||||
}
|
||||
|
||||
public void draw(Tessellator tes, double eyeX, double eyeY, double eyeZ, int eyeXint, int eyeYint,
|
||||
int eyeZint) {
|
||||
double size = 0.5;
|
||||
|
||||
int brightness = w.blockExists(x, 0, z) ? w.getLightBrightnessForSkyBlocks(x, y, z, 0) : 0;
|
||||
tes.setBrightness(brightness);
|
||||
|
||||
tes.setColorRGBA(
|
||||
(int) (tint[0] * .9F),
|
||||
(int) (tint[1] * .95F),
|
||||
(int) (tint[2] * 1F),
|
||||
ConfigurationHandler.INSTANCE.getHintTransparency());
|
||||
|
||||
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;
|
||||
|
||||
// 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]);
|
||||
|
||||
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:
|
||||
// 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);
|
||||
tes.addVertexWithUV(X + size, Y, Z, U, v);
|
||||
tes.addVertexWithUV(X + size, Y, Z, U, v);
|
||||
tes.addVertexWithUV(X + size, Y, Z + size, U, V);
|
||||
tes.addVertexWithUV(X, Y, Z + size, u, V);
|
||||
break;
|
||||
case 1:
|
||||
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);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z + size, U, V);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z + size, U, V);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z, U, v);
|
||||
tes.addVertexWithUV(X, Y + size, Z, u, v);
|
||||
break;
|
||||
case 2:
|
||||
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);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z, u, v);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z, u, v);
|
||||
tes.addVertexWithUV(X + size, Y, Z, u, V);
|
||||
tes.addVertexWithUV(X, Y, Z, U, V);
|
||||
break;
|
||||
case 3:
|
||||
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);
|
||||
tes.addVertexWithUV(X, Y + size, Z + size, u, v);
|
||||
tes.addVertexWithUV(X, Y + size, Z + size, u, v);
|
||||
tes.addVertexWithUV(X, Y, Z + size, u, V);
|
||||
tes.addVertexWithUV(X + size, Y, Z + size, U, V);
|
||||
break;
|
||||
case 4:
|
||||
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);
|
||||
tes.addVertexWithUV(X, Y + size, Z, u, v);
|
||||
tes.addVertexWithUV(X, Y + size, Z, u, v);
|
||||
tes.addVertexWithUV(X, Y, Z, u, V);
|
||||
tes.addVertexWithUV(X, Y, Z + size, U, V);
|
||||
break;
|
||||
case 5:
|
||||
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);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z + size, u, v);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z + size, u, v);
|
||||
tes.addVertexWithUV(X + size, Y, Z + size, u, V);
|
||||
tes.addVertexWithUV(X + size, Y, Z, U, V);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public long getCreationTime() {
|
||||
return creationTime;
|
||||
}
|
||||
|
||||
public double getSquareDistanceTo(Vec3 point) {
|
||||
return point.squareDistanceTo(x + 0.5, y + 0.5, z + 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ForgeEventHandler {
|
||||
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 onWorldLoad(WorldEvent.Load e) {
|
||||
if (e.world.isRemote) {
|
||||
allHints.clear();
|
||||
hintOwners.clear();
|
||||
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) {
|
||||
Vec3 playerPos = Minecraft.getMinecraft().thePlayer.getPosition(1);
|
||||
boolean sortRequired = false;
|
||||
// The allGroups is implicitly sorted by creationTime
|
||||
// here we exploit this ordering to reduce iteration size
|
||||
int deadline = Minecraft.getMinecraft().thePlayer.ticksExisted
|
||||
- ConfigurationHandler.INSTANCE.getHintLifespan();
|
||||
int i;
|
||||
for (i = 0; i < allGroups.size(); i++) {
|
||||
if (allGroups.get(i).getCreationTime() > deadline) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i != 0) {
|
||||
sortRequired = true;
|
||||
// clear this block and go no further
|
||||
List<HintGroup> toRemove = allGroups.subList(0, i);
|
||||
toRemove.forEach(ClientProxy::removeGroup);
|
||||
toRemove.clear();
|
||||
}
|
||||
if (allHintsDirty) {
|
||||
allHintsForRender.clear();
|
||||
// we need greatly efficient sequential access and mildly efficient sorting
|
||||
// rebuild is sort of rare, and it's expected to lag anyway
|
||||
// so no min-heap here
|
||||
for (HintGroup c : allGroups) allHintsForRender.addAll(c.getHints());
|
||||
sortRequired = true;
|
||||
}
|
||||
if (sortRequired || playerPos.squareDistanceTo(lastPlayerPos) > 1e-2) {
|
||||
// only redo sort if player moved some distance
|
||||
// default is 0.1 block
|
||||
// if there was a full rebuild, go sort it as well
|
||||
allHintsForRender.sort(Comparator.comparingDouble(info -> -info.getSquareDistanceTo(playerPos)));
|
||||
resetPlayerLocation();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ForgeEventHandler {
|
||||
|
||||
@SubscribeEvent
|
||||
@SideOnly(Side.CLIENT)
|
||||
public void onMouseEvent(MouseEvent event) {
|
||||
final EntityPlayer player = Minecraft.getMinecraft().thePlayer;
|
||||
|
||||
if (player == null || player.isDead) {
|
||||
return;
|
||||
}
|
||||
|
||||
final ItemStack heldItem = player.getHeldItem();
|
||||
|
||||
if (heldItem == null || !(heldItem.getItem() instanceof ItemConstructableTrigger)) return;
|
||||
|
||||
if (event.button != 2 || !event.buttonstate) return;
|
||||
|
||||
// Sink the middle click as soon as a projector is held, so vanilla pick block does not run
|
||||
event.setCanceled(true);
|
||||
|
||||
final boolean isSneaking = player.isSneaking();
|
||||
if (isSneaking && !ChannelWipeConfirmation.confirm(player)) return;
|
||||
|
||||
ChannelPickHandler.handleWorldPick(
|
||||
player.worldObj,
|
||||
player,
|
||||
heldItem,
|
||||
Minecraft.getMinecraft().objectMouseOver,
|
||||
isSneaking);
|
||||
}
|
||||
|
||||
@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) {
|
||||
// flush hints. we are in a different world now.
|
||||
allHintsForRender.clear();
|
||||
allGroups.clear();
|
||||
lastPlayerPos.yCoord = -1e30;
|
||||
renderThrough = 0;
|
||||
// clear throttles. hopefully a world switch is enough long as a cool down.
|
||||
localThrottleMap.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onRenderWorldLast(RenderWorldLastEvent e) {
|
||||
if (allHintsForRender.isEmpty()) return;
|
||||
|
||||
// seriously, I'm not a OpenGL expert, so I'm probably doing a lot of very stupid stuff here.
|
||||
// Please consider contributing patches if you find something to optimize.
|
||||
|
||||
Profiler p = Minecraft.getMinecraft().mcProfiler;
|
||||
|
||||
p.startSection("HintParticle");
|
||||
p.startSection("Prepare");
|
||||
Frustrum frustrum = new Frustrum();
|
||||
Entity entitylivingbase = Minecraft.getMinecraft().renderViewEntity;
|
||||
double d0 = entitylivingbase.lastTickPosX
|
||||
+ (entitylivingbase.posX - entitylivingbase.lastTickPosX) * e.partialTicks;
|
||||
double d1 = entitylivingbase.lastTickPosY
|
||||
+ (entitylivingbase.posY - entitylivingbase.lastTickPosY) * e.partialTicks;
|
||||
double d2 = entitylivingbase.lastTickPosZ
|
||||
+ (entitylivingbase.posZ - entitylivingbase.lastTickPosZ) * e.partialTicks;
|
||||
int i0 = (int) d0, i1 = (int) d1, i2 = (int) d2;
|
||||
frustrum.setPosition(d0, d1, d2);
|
||||
|
||||
GL11.glPushMatrix();
|
||||
GL11.glPushAttrib(GL11.GL_ENABLE_BIT | GL11.GL_COLOR_BUFFER_BIT); // TODO figure out original states
|
||||
// we need the back facing rendered because the thing is transparent
|
||||
GL11.glDisable(GL11.GL_CULL_FACE);
|
||||
GL11.glDisable(GL11.GL_ALPHA_TEST);
|
||||
GL11.glEnable(GL11.GL_BLEND); // enable blend so it is transparent
|
||||
GL11.glBlendFunc(GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_SRC_ALPHA);
|
||||
// depth test begin as enabled
|
||||
boolean renderThrough = false;
|
||||
Minecraft.getMinecraft().renderEngine.bindTexture(TextureMap.locationBlocksTexture);
|
||||
GL11.glTranslated(-d0 + i0, -d1 + i1, -d2 + i2);
|
||||
Tessellator tes = Tessellator.instance;
|
||||
tes.startDrawing(GL11.GL_TRIANGLES);
|
||||
for (int i = 0, allHintsForRenderSize = allHintsForRender.size(), cubeDrawn = 0; i
|
||||
< allHintsForRenderSize; i++, cubeDrawn++) {
|
||||
HintParticleInfo hint = allHintsForRender.get(i);
|
||||
if (!hint.isInFrustrum(frustrum)) continue;
|
||||
if (renderThrough != hint.renderThrough) {
|
||||
if (i > 0) {
|
||||
p.endStartSection("Draw");
|
||||
tes.draw();
|
||||
tes.startDrawing(GL11.GL_TRIANGLES);
|
||||
cubeDrawn = 0;
|
||||
p.endStartSection("Prepare");
|
||||
}
|
||||
if (hint.renderThrough) GL11.glDisable(GL11.GL_DEPTH_TEST);
|
||||
else GL11.glEnable(GL11.GL_DEPTH_TEST);
|
||||
renderThrough = hint.renderThrough;
|
||||
}
|
||||
// Minecraft Tessellator draw in batch of 4096. This plays nicely with quads, but for trigs
|
||||
// it might occasionally end up with one half in first batch and the other half in later batch.
|
||||
// so we manually flush the buffer here if that would happen.
|
||||
// 6 faces per particle, 2 trig per face, 3 vertex per trig
|
||||
if ((cubeDrawn + 1) >= 4096 / (6 * 2 * 3)) {
|
||||
tes.draw();
|
||||
tes.startDrawing(GL11.GL_TRIANGLES);
|
||||
}
|
||||
hint.draw(tes, d0, d1, d2, i0, i1, i2);
|
||||
}
|
||||
p.endStartSection("Draw");
|
||||
tes.draw();
|
||||
p.endSection();
|
||||
|
||||
GL11.glPopAttrib();
|
||||
GL11.glPopMatrix();
|
||||
p.endSection();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,35 @@
|
||||
package com.gtnewhorizon.structurelib;
|
||||
|
||||
import com.gtnewhorizon.structurelib.net.UpdateHintParticleMessage;
|
||||
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.ItemStack;
|
||||
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.FMLInitializationEvent;
|
||||
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) {}
|
||||
@@ -45,7 +65,67 @@ public class CommonProxy {
|
||||
|
||||
public void preInit(FMLPreInitializationEvent e) {}
|
||||
|
||||
public void init(FMLInitializationEvent e) {}
|
||||
|
||||
public long getOverworldTime() {
|
||||
return MinecraftServer.getServer().getEntityWorld().getTotalWorldTime();
|
||||
}
|
||||
|
||||
public void uploadChannels(ItemStack trigger) {}
|
||||
|
||||
public boolean markHintParticleError(EntityPlayer player, World w, int x, int y, int z) {
|
||||
if (player instanceof EntityPlayerMP) { // just in case
|
||||
StructureLib.net.sendTo(new ErrorHintParticleMessage(x, (short) y, z), (EntityPlayerMP) player);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
boolean forceUpdateLastSend) {
|
||||
if (player instanceof EntityPlayerMP) {
|
||||
Map<Object, Long> submap = throttleMap.computeIfAbsent((EntityPlayerMP) player, p -> new HashMap<>());
|
||||
addThrottledChat(throttleKey, player, text, intervalRequired, forceUpdateLastSend, submap);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void addThrottledChat(Object throttleKey, EntityPlayer player, IChatComponent text,
|
||||
short intervalRequired, boolean forceUpdateLastSend, Map<Object, Long> submap) {
|
||||
long now = System.currentTimeMillis();
|
||||
Long old;
|
||||
if (forceUpdateLastSend) {
|
||||
old = submap.put(throttleKey, now);
|
||||
} else {
|
||||
old = submap.get(throttleKey);
|
||||
}
|
||||
if (old == null || now - old >= intervalRequired) {
|
||||
player.addChatComponentMessage(text);
|
||||
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,20 +1,39 @@
|
||||
package com.gtnewhorizon.structurelib;
|
||||
|
||||
import cpw.mods.fml.client.event.ConfigChangedEvent;
|
||||
import cpw.mods.fml.common.FMLCommonHandler;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
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 {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
private Configuration config;
|
||||
private int maxCoexistingHologram;
|
||||
private boolean removeCollidingHologram;
|
||||
private int hintLifespan;
|
||||
private int hintTransparency;
|
||||
private int autoPlaceBudget;
|
||||
private int autoPlaceInterval;
|
||||
private Map<String, Pair<List<String>, List<String>>> registryOrders;
|
||||
|
||||
ConfigurationHandler() {
|
||||
FMLCommonHandler.instance().bus().register(this);
|
||||
@@ -50,9 +69,79 @@ public enum ConfigurationHandler {
|
||||
"client.hologram",
|
||||
true,
|
||||
"An attempt will be made to remove an existing hologram if it collides with a new hologram.");
|
||||
hintLifespan = config
|
||||
.getInt("hintLifespan", "client.hologram", 400, 1, 20000, "Ticks before a hologram disappears.");
|
||||
hintTransparency = config.getInt(
|
||||
"hintTransparency",
|
||||
"client.hologram",
|
||||
192,
|
||||
1,
|
||||
255,
|
||||
"Alpha value of hologram particles. Higher the value, the more \"ghostly\" the hologram will appear to be.");
|
||||
autoPlaceBudget = config.getInt(
|
||||
"autoPlaceBudget",
|
||||
"common.hologram",
|
||||
25,
|
||||
1,
|
||||
200,
|
||||
"Max number of elements can be placed in one round of auto place.\n"
|
||||
+ "As expected, server side settings will overrides client settings.\n"
|
||||
+ "Certain larger multi might increase these values beyond this configured value.");
|
||||
autoPlaceInterval = config.getInt(
|
||||
"autoPlaceInterval",
|
||||
"common.hologram",
|
||||
300,
|
||||
0,
|
||||
20000,
|
||||
"Unit: millisecond. Minimal interval between two auto place round.\n"
|
||||
+ "As expected, server side settings will overrides client settings.\n"
|
||||
+ "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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +160,28 @@ public enum ConfigurationHandler {
|
||||
return removeCollidingHologram;
|
||||
}
|
||||
|
||||
public int getHintLifespan() {
|
||||
return hintLifespan;
|
||||
}
|
||||
|
||||
public int getHintTransparency() {
|
||||
return hintTransparency;
|
||||
}
|
||||
|
||||
public int getAutoPlaceBudget() {
|
||||
return autoPlaceBudget;
|
||||
}
|
||||
|
||||
public int getAutoPlaceInterval() {
|
||||
return autoPlaceInterval;
|
||||
}
|
||||
|
||||
public Pair<List<String>, List<String>> getRegistryOrder(String name) {
|
||||
return registryOrders.get(name);
|
||||
}
|
||||
|
||||
Configuration getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,11 +2,15 @@ package com.gtnewhorizon.structurelib;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.minecraftforge.common.config.Configuration;
|
||||
|
||||
enum ConfigurationVersion {
|
||||
|
||||
V1 {
|
||||
|
||||
@Override
|
||||
protected void step(Configuration c) {}
|
||||
};
|
||||
@@ -30,10 +34,8 @@ enum ConfigurationVersion {
|
||||
}
|
||||
|
||||
public static ConfigurationVersion identify(Configuration c) {
|
||||
return Arrays.stream(VALUES)
|
||||
.filter(v -> Objects.equals(c.getLoadedConfigVersion(), v.getVersionMarker()))
|
||||
.findFirst()
|
||||
.orElse(V1);
|
||||
return Arrays.stream(VALUES).filter(v -> Objects.equals(c.getLoadedConfigVersion(), v.getVersionMarker()))
|
||||
.findFirst().orElse(V1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -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,695 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
@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 parentGuiConfig) {
|
||||
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.func_146283_a(
|
||||
this.mc.fontRenderer.listFormattedStringToWidth(I18n.format("fml.configgui.tooltip.undoAll"), 300),
|
||||
mouseX,
|
||||
mouseY);
|
||||
if (this.resetHoverChecker.checkHover(mouseX, mouseY)) this.func_146283_a(
|
||||
this.mc.fontRenderer.listFormattedStringToWidth(I18n.format("fml.configgui.tooltip.resetAll"), 300),
|
||||
mouseX,
|
||||
mouseY);
|
||||
}
|
||||
|
||||
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.func_146283_a(
|
||||
mc.fontRenderer.listFormattedStringToWidth(tooltip, entriesList.getListWidth()),
|
||||
mouseX,
|
||||
mouseY);
|
||||
}
|
||||
if (hoverCheckerBtnDrag.checkHover(mouseX, mouseY) && !isDragging) {
|
||||
GuiEditRegistryConfig.this.func_146283_a(
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
package com.gtnewhorizon.structurelib;
|
||||
|
||||
import cpw.mods.fml.client.IModGuiFactory;
|
||||
import java.util.Set;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
|
||||
import cpw.mods.fml.client.IModGuiFactory;
|
||||
|
||||
public class GuiFactory implements IModGuiFactory {
|
||||
|
||||
@Override
|
||||
public void initialize(Minecraft minecraftInstance) {}
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
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) {
|
||||
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(new EmptyContainer(player), player.inventory.getStackInSlot(x));
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
package com.gtnewhorizon.structurelib;
|
||||
|
||||
import cpw.mods.fml.client.config.GuiConfig;
|
||||
import cpw.mods.fml.client.config.IConfigElement;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import net.minecraftforge.common.config.ConfigElement;
|
||||
import net.minecraftforge.common.config.Configuration;
|
||||
|
||||
import cpw.mods.fml.client.config.GuiConfig;
|
||||
import cpw.mods.fml.client.config.IConfigElement;
|
||||
|
||||
public class GuiModConfig extends GuiConfig {
|
||||
|
||||
public GuiModConfig(GuiScreen guiScreen) {
|
||||
super(
|
||||
guiScreen,
|
||||
@@ -16,16 +19,13 @@ public class GuiModConfig extends GuiConfig {
|
||||
StructureLibAPI.MOD_ID,
|
||||
false,
|
||||
false,
|
||||
GuiConfig.getAbridgedConfigPath(
|
||||
ConfigurationHandler.INSTANCE.getConfig().toString()));
|
||||
GuiConfig.getAbridgedConfigPath(ConfigurationHandler.INSTANCE.getConfig().toString()));
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static List<IConfigElement> getConfigElements() {
|
||||
final Configuration config = ConfigurationHandler.INSTANCE.getConfig();
|
||||
return config.getCategoryNames().stream()
|
||||
.filter(name -> name.indexOf('.') == -1)
|
||||
.map(name -> new ConfigElement(config.getCategory(name)))
|
||||
.collect(Collectors.toList());
|
||||
return config.getCategoryNames().stream().filter(name -> name.indexOf('.') == -1)
|
||||
.map(name -> new ConfigElement(config.getCategory(name))).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.gtnewhorizon.structurelib;
|
||||
|
||||
import net.minecraft.util.IIcon;
|
||||
|
||||
public interface IStructureCompat {
|
||||
|
||||
void markTextureUsed(IIcon o);
|
||||
}
|
||||
@@ -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,197 @@
|
||||
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) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package com.gtnewhorizon.structurelib;
|
||||
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
|
||||
import com.gtnewhorizon.structurelib.structure.IStructureElement;
|
||||
|
||||
import cpw.mods.fml.common.eventhandler.Event;
|
||||
|
||||
/**
|
||||
* Subscribers are <b>required</b> to not keep reference to these events, nor to depend on values of these events after
|
||||
* the method call returns.
|
||||
* <p>
|
||||
* All of these events will be fired on the {@link net.minecraftforge.common.MinecraftForge#EVENT_BUS}, if they are
|
||||
* enabled by {@link StructureLibAPI#enableInstrument(Object)}.
|
||||
* <p>
|
||||
* All public stuff in this class and its enclosed classes are considered part of the public API. Backwards
|
||||
* compatibility is maintained to the maximum extend possible.
|
||||
*/
|
||||
public abstract class StructureEvent extends Event {
|
||||
|
||||
/*
|
||||
* In case the event is passed to another thread for processing.
|
||||
*/
|
||||
private final Object identifier = StructureLibAPI.instrument.get();
|
||||
|
||||
StructureEvent() {}
|
||||
|
||||
/**
|
||||
* The instrument identifier. Subscribers should filter by these identifiers to prevent listening to a structure
|
||||
* event for other people.
|
||||
*/
|
||||
public Object getInstrumentIdentifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired <b>just before</b> any structure element is visited.
|
||||
*/
|
||||
public static final class StructureElementVisitedEvent extends StructureEvent {
|
||||
|
||||
private final World world;
|
||||
private final int x, y, z;
|
||||
private final int a, b, c;
|
||||
private final IStructureElement<?> element;
|
||||
|
||||
/**
|
||||
* Fire this event on {@link MinecraftForge#EVENT_BUS} by populating it with given arguments. Do nothing if
|
||||
* instrumenting isn't enabled for current thread.
|
||||
* <p>
|
||||
* Any {@link Exception} thrown by event listeners will be logged and ignored. {@link Error} will be propagated
|
||||
* as is.
|
||||
*/
|
||||
public static void fireEvent(World world, int x, int y, int z, int a, int b, int c,
|
||||
IStructureElement<?> element) {
|
||||
if (StructureLibAPI.isInstrumentEnabled()) {
|
||||
try {
|
||||
MinecraftForge.EVENT_BUS.post(new StructureElementVisitedEvent(world, x, y, z, a, b, c, element));
|
||||
} catch (Exception e) {
|
||||
// logging is done by event bus, we just ignore the consequence here
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StructureElementVisitedEvent(World world, int x, int y, int z, int a, int b, int c,
|
||||
IStructureElement<?> element) {
|
||||
this.world = world;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
this.c = c;
|
||||
this.element = element;
|
||||
}
|
||||
|
||||
/**
|
||||
* A position in ABC coordinate, relative to (0,0,0) in the piece, i.e. without baseOffset
|
||||
* <p>
|
||||
* Submit a feature request if you need the baseOffset.
|
||||
*/
|
||||
public int getA() {
|
||||
return a;
|
||||
}
|
||||
|
||||
/**
|
||||
* B position in ABC coordinate, relative to (0,0,0) in the piece, i.e. without baseOffset
|
||||
* <p>
|
||||
* Submit a feature request if you need the baseOffset.
|
||||
*/
|
||||
public int getB() {
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* C position in ABC coordinate, relative to (0,0,0) in the piece, i.e. without baseOffset
|
||||
* <p>
|
||||
* Submit a feature request if you need the baseOffset.
|
||||
*/
|
||||
public int getC() {
|
||||
return c;
|
||||
}
|
||||
|
||||
/**
|
||||
* Element being visited.
|
||||
*/
|
||||
public IStructureElement<?> getElement() {
|
||||
return element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StructureElementVisitedEvent{" + "a="
|
||||
+ a
|
||||
+ ", b="
|
||||
+ b
|
||||
+ ", c="
|
||||
+ c
|
||||
+ ", element="
|
||||
+ element
|
||||
+ ", world="
|
||||
+ getWorld()
|
||||
+ ", x="
|
||||
+ getX()
|
||||
+ ", y="
|
||||
+ getY()
|
||||
+ ", z="
|
||||
+ getZ()
|
||||
+ '}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Location of this event.
|
||||
*/
|
||||
public World getWorld() {
|
||||
return world;
|
||||
}
|
||||
|
||||
/**
|
||||
* Location of this event. Absolute world coordinate.
|
||||
*/
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Location of this event. Absolute world coordinate.
|
||||
*/
|
||||
public int getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Location of this event. Absolute world coordinate.
|
||||
*/
|
||||
public int getZ() {
|
||||
return z;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +1,106 @@
|
||||
package com.gtnewhorizon.structurelib;
|
||||
|
||||
import com.gtnewhorizon.structurelib.block.BlockHint;
|
||||
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.UpdateHintParticleMessage;
|
||||
import com.gtnewhorizon.structurelib.util.XSTR;
|
||||
import cpw.mods.fml.common.Mod;
|
||||
import cpw.mods.fml.common.SidedProxy;
|
||||
import cpw.mods.fml.common.event.FMLPreInitializationEvent;
|
||||
import cpw.mods.fml.common.network.NetworkRegistry;
|
||||
import cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper;
|
||||
import cpw.mods.fml.common.registry.GameRegistry;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
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.Mod;
|
||||
import cpw.mods.fml.common.SidedProxy;
|
||||
import cpw.mods.fml.common.event.FMLInitializationEvent;
|
||||
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;
|
||||
import cpw.mods.fml.common.network.simpleimpl.SimpleNetworkWrapper;
|
||||
import cpw.mods.fml.common.registry.GameRegistry;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
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 {
|
||||
public static boolean DEBUG_MODE = Boolean.getBoolean("structurelib.debug");
|
||||
|
||||
private static final String STRUCTURECOMPAT_MODID = "structurecompat";
|
||||
public static boolean DEBUG_MODE;
|
||||
public static boolean PANIC_MODE = Boolean.getBoolean("structurelib.panic");
|
||||
public static Logger LOGGER = LogManager.getLogger("StructureLib");
|
||||
public static final Logger LOGGER = LogManager.getLogger("StructureLib");
|
||||
|
||||
@SidedProxy(
|
||||
serverSide = "com.gtnewhorizon.structurelib.CommonProxy",
|
||||
clientSide = "com.gtnewhorizon.structurelib.ClientProxy")
|
||||
static CommonProxy proxy;
|
||||
|
||||
static SimpleNetworkWrapper net = NetworkRegistry.INSTANCE.newSimpleChannel(StructureLibAPI.MOD_ID);
|
||||
static final SimpleNetworkWrapper net = NetworkRegistry.INSTANCE.newSimpleChannel(MOD_ID);
|
||||
|
||||
static {
|
||||
net.registerMessage(
|
||||
AlignmentMessage.ServerHandler.class, AlignmentMessage.AlignmentQuery.class, 0, Side.SERVER);
|
||||
AlignmentMessage.ServerHandler.class,
|
||||
AlignmentMessage.AlignmentQuery.class,
|
||||
0,
|
||||
Side.SERVER);
|
||||
net.registerMessage(AlignmentMessage.ClientHandler.class, AlignmentMessage.AlignmentData.class, 1, Side.CLIENT);
|
||||
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"));
|
||||
} catch (IllegalArgumentException | NullPointerException e) {
|
||||
// turn on debug by default in dev mode
|
||||
// this will be overridden if above property is present and set to false
|
||||
DEBUG_MODE = (Boolean) Launch.blackboard.get("fml.deobfuscatedEnvironment");
|
||||
}
|
||||
}
|
||||
|
||||
public static final XSTR RANDOM = new XSTR();
|
||||
|
||||
@Mod.Instance
|
||||
static StructureLib INSTANCE;
|
||||
@Mod.Instance(STRUCTURECOMPAT_MODID)
|
||||
static Object COMPAT;
|
||||
|
||||
static Block blockHint;
|
||||
static Item itemBlockHint;
|
||||
static Item itemFrontRotationTool;
|
||||
static Item itemConstructableTrigger;
|
||||
public static final CreativeTabs creativeTab = new CreativeTabs("structurelib") {
|
||||
|
||||
@Override
|
||||
@SideOnly(Side.CLIENT)
|
||||
public Item getTabIconItem() {
|
||||
@@ -71,11 +114,77 @@ public class StructureLib {
|
||||
GameRegistry.registerBlock(blockHint = new BlockHint(), ItemBlockHint.class, "blockhint");
|
||||
itemBlockHint = ItemBlock.getItemFromBlock(StructureLibAPI.getBlockHint());
|
||||
GameRegistry.registerItem(
|
||||
itemFrontRotationTool = new ItemFrontRotationTool(), itemFrontRotationTool.getUnlocalizedName());
|
||||
itemFrontRotationTool = new ItemFrontRotationTool(),
|
||||
itemFrontRotationTool.getUnlocalizedName());
|
||||
GameRegistry.registerItem(
|
||||
itemConstructableTrigger = new ItemConstructableTrigger(),
|
||||
itemConstructableTrigger.getUnlocalizedName());
|
||||
proxy.preInit(e);
|
||||
NetworkRegistry.INSTANCE.registerGuiHandler(instance(), new GuiHandler());
|
||||
|
||||
InventoryUtility.init();
|
||||
|
||||
ChannelDescription.set(CHANNEL_SHOW_ERROR, MOD_ID, "channels.structurelib.show_errors");
|
||||
|
||||
DevelopHelper.onPreInit();
|
||||
}
|
||||
|
||||
@Mod.EventHandler
|
||||
public void init(FMLInitializationEvent e) {
|
||||
proxy.init(e);
|
||||
}
|
||||
|
||||
@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) {
|
||||
@@ -93,4 +202,12 @@ public class StructureLib {
|
||||
public static long getOverworldTime() {
|
||||
return proxy.getOverworldTime();
|
||||
}
|
||||
|
||||
public static StructureLib instance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public CommonProxy proxy() {
|
||||
return proxy;
|
||||
}
|
||||
}
|
||||
@@ -2,23 +2,31 @@ package com.gtnewhorizon.structurelib;
|
||||
|
||||
import static com.gtnewhorizon.structurelib.StructureLib.proxy;
|
||||
|
||||
import com.gtnewhorizon.structurelib.alignment.IAlignmentProvider;
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
|
||||
import com.gtnewhorizon.structurelib.net.AlignmentMessage;
|
||||
import com.gtnewhorizon.structurelib.structure.IItemSource;
|
||||
import cpw.mods.fml.common.network.NetworkRegistry;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
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;
|
||||
|
||||
/**
|
||||
* A stable interface into the structure lib's internals. Backwards compatibility is maintained to the maximum extend possible.
|
||||
* A stable interface into the structure lib's internals. Backwards compatibility is maintained to the maximum extend
|
||||
* possible.
|
||||
*/
|
||||
public class StructureLibAPI {
|
||||
|
||||
public static final String MOD_ID = "structurelib";
|
||||
public static final int HINT_BLOCK_META_GENERIC_0 = 0;
|
||||
public static final int HINT_BLOCK_META_GENERIC_1 = 1;
|
||||
@@ -36,43 +44,153 @@ 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<>();
|
||||
|
||||
/**
|
||||
* Start a batch of hinting. All hints particles generated during one batch will be considered to belong to one hologram.
|
||||
* Start instrumenting <b>for the current thread only</b>. When instrumentation is enabled,
|
||||
* {@link IStructureDefinition}'s builtin iteration methods will send {@link StructureEvent} and its subclasses when
|
||||
* appropriate. Otherwise, these events will not be sent at all, regardless of whether a listener for these events
|
||||
* are registered.
|
||||
* <p>
|
||||
* You don't need to call this unless your constructable tool didn't call {@link com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility#handle(ItemStack, EntityPlayer, World, int, int, int, int)}
|
||||
* Identifiers are required to be value-comparable, i.e. overrides {@link Object#equals(Object)}. Suggested
|
||||
* identifier includes
|
||||
* <ul>
|
||||
* <li>Mod Instance or {@link cpw.mods.fml.common.ModContainer}</li>
|
||||
* <li>{@link com.gtnewhorizon.structurelib.alignment.constructable.IConstructable} and its friends, if you are
|
||||
* triggering structure check via this interface.</li>
|
||||
* <li>{@link net.minecraft.util.ResourceLocation}</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* It's an API abuse for normal {@link IStructureDefinition} users to depend on this feature. They have full control
|
||||
* and full knowledge of the {@link IStructureDefinition} they are using. Using events to instrument the structure
|
||||
* check/autoplace is completely unnecessary and does nothing but add overhead.
|
||||
* <p>
|
||||
* Instruments are required to call {@link #disableInstrument()} whenever it is done with its business, as there can
|
||||
* be only one instrument for any given thread.
|
||||
*
|
||||
* @param identifier an identifier sent along the actual event.
|
||||
* @throws IllegalStateException if instrument has already been enabled.
|
||||
*/
|
||||
public static void enableInstrument(Object identifier) {
|
||||
if (isInstrumentEnabled()) throw new IllegalStateException();
|
||||
instrument.set(identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop instrumenting.
|
||||
*
|
||||
* @throws IllegalStateException if instrumenting hasn't been enabled.
|
||||
*/
|
||||
public static void disableInstrument() {
|
||||
if (!isInstrumentEnabled()) throw new IllegalStateException();
|
||||
instrument.set(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if instrumenting is enabled.
|
||||
*
|
||||
* @return true if enabled, false otherwise
|
||||
*/
|
||||
public static boolean isInstrumentEnabled() {
|
||||
return instrument.get() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a batch of hinting. All hints particles generated during one batch will be considered to belong to one
|
||||
* hologram.
|
||||
* <p>
|
||||
* You don't need to call this unless your constructable tool didn't call
|
||||
* {@link com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility#handle(ItemStack, EntityPlayer, World, int, int, int, int)}
|
||||
*/
|
||||
public static void startHinting(World w) {
|
||||
proxy.startHinting(w);
|
||||
}
|
||||
|
||||
/**
|
||||
* End current batch of hinting. All hints particles generated during one batch will be considered to belong to one hologram.
|
||||
* End current batch of hinting. All hints particles generated during one batch will be considered to belong to one
|
||||
* hologram.
|
||||
* <p>
|
||||
* You don't need to call this unless your constructable tool didn't call {@link com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility#handle(ItemStack, EntityPlayer, World, int, int, int, int)}
|
||||
* You don't need to call this unless your constructable tool didn't call
|
||||
* {@link com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility#handle(ItemStack, EntityPlayer, World, int, int, int, int)}
|
||||
*/
|
||||
public static void endHinting(World w) {
|
||||
proxy.endHinting(w);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new hint particle on client side at given location using given textures with a tint.
|
||||
*
|
||||
* @param w World to spawn. Usually the client world.
|
||||
* @param x x coord
|
||||
* @param y y coord
|
||||
* @param z z coord
|
||||
* @param icons 6 texture. in forge direction order.
|
||||
* @param RGBa a 4 short array. tint in rgba form. currently alpha channel is ignored, but we might change this
|
||||
* later on.
|
||||
*/
|
||||
public static void hintParticleTinted(World w, int x, int y, int z, IIcon[] icons, short[] RGBa) {
|
||||
proxy.hintParticleTinted(w, x, y, z, icons, RGBa);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new hint particle on client side at given location using textures from given block with a tint.
|
||||
*
|
||||
* @param w World to spawn. Usually the client world.
|
||||
* @param x x coord
|
||||
* @param y y coord
|
||||
* @param z z coord
|
||||
* @param block block to take texture from
|
||||
* @param meta the meta of block to take texture from
|
||||
* @param RGBa a 4 short array. tint in rgba form. currently alpha channel is ignored, but we might change this
|
||||
* later on.
|
||||
*/
|
||||
public static void hintParticleTinted(World w, int x, int y, int z, Block block, int meta, short[] RGBa) {
|
||||
proxy.hintParticleTinted(w, x, y, z, block, meta, RGBa);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new hint particle on client side at given location using given textures.
|
||||
*
|
||||
* @param w World to spawn. Usually the client world.
|
||||
* @param x x coord
|
||||
* @param y y coord
|
||||
* @param z z coord
|
||||
* @param icons 6 texture. in forge direction order.
|
||||
*/
|
||||
public static void hintParticle(World w, int x, int y, int z, IIcon[] icons) {
|
||||
proxy.hintParticle(w, x, y, z, icons);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new hint particle on client side at given location using textures from given block.
|
||||
*
|
||||
* @param w World to spawn. Usually the client world.
|
||||
* @param x x coord
|
||||
* @param y y coord
|
||||
* @param z z coord
|
||||
* @param block block to take texture from
|
||||
* @param meta the meta of block to take texture from
|
||||
*/
|
||||
public static void hintParticle(World w, int x, int y, int z, Block block, int meta) {
|
||||
proxy.hintParticle(w, x, y, z, block, meta);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the tint of given hint particle. Do nothing if particle not found.
|
||||
* <p>
|
||||
* Can be called on either client side or server side. Server side will schedule a network message to update it for
|
||||
* this player only. Will do nothing if no hint particle is found at given location.
|
||||
*
|
||||
* @return false if nothing updated. true if updated or update instruction sent.
|
||||
*/
|
||||
public static boolean markHintParticleError(EntityPlayer player, World w, int x, int y, int z) {
|
||||
return proxy.markHintParticleError(player, w, x, y, z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the tint of given hint particle. Do nothing if particle not found.
|
||||
*
|
||||
* @return false if nothing updated. true if updated.
|
||||
*/
|
||||
public static boolean updateHintParticleTint(EntityPlayer player, World w, int x, int y, int z, short[] RGBa) {
|
||||
@@ -80,9 +198,13 @@ public class StructureLibAPI {
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the ExtendedFacing of this tile entity.
|
||||
* The ExtendedFacing will later be set onto given tile entity via {@link com.gtnewhorizon.structurelib.alignment.IAlignment#setExtendedFacing(ExtendedFacing)}
|
||||
* upon arrival of server reply.
|
||||
* Query the ExtendedFacing of this tile entity from client side. Can be sent only on client side. The
|
||||
* ExtendedFacing will later be set onto given tile entity via
|
||||
* {@link com.gtnewhorizon.structurelib.alignment.IAlignment#setExtendedFacing(ExtendedFacing)} upon arrival of
|
||||
* server reply.
|
||||
* <p>
|
||||
* The server side will query the {@link ExtendedFacing} of this provider using
|
||||
* {@link IAlignment#getExtendedFacing()}
|
||||
*
|
||||
* @throws IllegalArgumentException if is not tile entity or provided a null alignment
|
||||
*/
|
||||
@@ -92,6 +214,9 @@ public class StructureLibAPI {
|
||||
|
||||
/**
|
||||
* Send the ExtendedFacing of this Tile Entity to all players. Can be called on server side only.
|
||||
* <p>
|
||||
* The receiving tile entity will receive the {@link ExtendedFacing} via its
|
||||
* {@link com.gtnewhorizon.structurelib.alignment.IAlignment#setExtendedFacing(ExtendedFacing)} method.
|
||||
*
|
||||
* @throws IllegalArgumentException if is not tile entity or provided a null alignment
|
||||
*/
|
||||
@@ -101,6 +226,9 @@ public class StructureLibAPI {
|
||||
|
||||
/**
|
||||
* Send the ExtendedFacing of this Tile Entity to given player. Can be called on server side only.
|
||||
* <p>
|
||||
* The receiving tile entity will receive the {@link ExtendedFacing} via its
|
||||
* {@link com.gtnewhorizon.structurelib.alignment.IAlignment#setExtendedFacing(ExtendedFacing)} method.
|
||||
*
|
||||
* @throws IllegalArgumentException if is not tile entity or provided a null alignment
|
||||
*/
|
||||
@@ -109,7 +237,11 @@ public class StructureLibAPI {
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the ExtendedFacing of this Tile Entity to all players around target point. Can be called on server side only.
|
||||
* Send the ExtendedFacing of this Tile Entity to all players around target point. Can be called on server side
|
||||
* only.
|
||||
* <p>
|
||||
* The receiving tile entity will receive the {@link ExtendedFacing} via its
|
||||
* {@link com.gtnewhorizon.structurelib.alignment.IAlignment#setExtendedFacing(ExtendedFacing)} method.
|
||||
*
|
||||
* @throws IllegalArgumentException if is not tile entity or provided a null alignment
|
||||
*/
|
||||
@@ -119,6 +251,9 @@ public class StructureLibAPI {
|
||||
|
||||
/**
|
||||
* Send the ExtendedFacing of this Tile Entity to all players in that dimension. Can be called on server side only.
|
||||
* <p>
|
||||
* The receiving tile entity will receive the {@link ExtendedFacing} via its
|
||||
* {@link com.gtnewhorizon.structurelib.alignment.IAlignment#setExtendedFacing(ExtendedFacing)} method.
|
||||
*
|
||||
* @throws IllegalArgumentException if is not tile entity or provided a null alignment
|
||||
*/
|
||||
@@ -126,14 +261,32 @@ public class StructureLibAPI {
|
||||
StructureLib.net.sendToDimension(new AlignmentMessage.AlignmentData(provider), dimension.provider.dimensionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Block for StructureLib supplied hint block.
|
||||
*/
|
||||
public static Block getBlockHint() {
|
||||
return StructureLib.blockHint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ItemBlock for StructureLib supplied hint block.
|
||||
*/
|
||||
public static Item getItemBlockHint() {
|
||||
return StructureLib.itemBlockHint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hologram item.
|
||||
*/
|
||||
public static Item getDefaultHologramItem() {
|
||||
return StructureLib.itemConstructableTrigger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if structure debug mode is on.
|
||||
*
|
||||
* @return true if debug mode is on
|
||||
*/
|
||||
public static boolean isDebugEnabled() {
|
||||
return StructureLib.DEBUG_MODE;
|
||||
}
|
||||
@@ -143,13 +296,93 @@ public class StructureLibAPI {
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if given block can be replaced without much effort. The exact predicate clauses is not stable and
|
||||
* will be changed, but the general idea will always stay the same.
|
||||
*
|
||||
* Use this in your {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, java.util.function.Consumer)}
|
||||
* Determines if given block can be replaced without much effort. The exact predicate clauses is not stable and will
|
||||
* be changed, but the general idea will always stay the same.
|
||||
* <p>
|
||||
* Use this in your
|
||||
* {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean isBlockTriviallyReplaceable(World w, int x, int y, int z, EntityPlayerMP actor) {
|
||||
return isBlockTriviallyReplaceable(w, x, y, z, (EntityPlayer) actor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if given block can be replaced without much effort. The exact predicate clauses is not stable and will
|
||||
* be changed, but the general idea will always stay the same.
|
||||
* <p>
|
||||
* Use this in your
|
||||
* {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)}
|
||||
*/
|
||||
public static boolean isBlockTriviallyReplaceable(World w, int x, int y, int z, EntityPlayer actor) {
|
||||
// TODO extend this function a bit
|
||||
return w.getBlock(x, y, z).isReplaceable(w, x, y, z);
|
||||
Block block = w.getBlock(x, y, z);
|
||||
return block.isAir(w, x, y, z) || block.isReplaceable(w, x, y, z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send chat to player, but throttled.
|
||||
*
|
||||
* @param throttleKey throttle key. Must properly implement {@link Object#hashCode()} and
|
||||
* {@link Object#equals(Object)} to handle value equality.
|
||||
* @param player player to send chat to
|
||||
* @param text chat to send
|
||||
* @param intervalRequired interval required before last recorded time to actually send the message. unit in
|
||||
* millisecond. we purposefully chose to not use a bigger data type to limit how long this
|
||||
* interval can be
|
||||
*/
|
||||
public static void addThrottledChat(Object throttleKey, EntityPlayer player, IChatComponent text,
|
||||
short intervalRequired) {
|
||||
addThrottledChat(throttleKey, player, text, intervalRequired, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send chat to player, but throttled.
|
||||
*
|
||||
* @param throttleKey throttle key. Must properly implement {@link Object#hashCode()} and
|
||||
* {@link Object#equals(Object)} to handle value equality.
|
||||
* @param player player to send chat to
|
||||
* @param text chat to send
|
||||
* @param intervalRequired interval required before last recorded time to actually send the message. unit in
|
||||
* millisecond. we purposefully chose to not use a bigger data type to limit how long
|
||||
* this interval can be
|
||||
* @param forceUpdateLastSend if true, always update the last send time, even if the message isn't actually sent.
|
||||
*/
|
||||
public static void addThrottledChat(Object throttleKey, EntityPlayer player, IChatComponent text,
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package com.gtnewhorizon.structurelib.alignment;
|
||||
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.Flip;
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.Rotation;
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
|
||||
class AlignmentLimits implements IAlignmentLimits {
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.util.FakePlayer;
|
||||
|
||||
public class AlignmentUtility {
|
||||
|
||||
private AlignmentUtility() {}
|
||||
|
||||
public static boolean handle(EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ) {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package com.gtnewhorizon.structurelib.alignment;
|
||||
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.Direction;
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.Flip;
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.Rotation;
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
|
||||
public interface IAlignment extends IAlignmentLimits, IAlignmentProvider {
|
||||
|
||||
int DIRECTIONS_COUNT = Direction.VALUES.length;
|
||||
int ROTATIONS_COUNT = Rotation.VALUES.length;
|
||||
int FLIPS_COUNT = Flip.VALUES.length;
|
||||
@@ -15,8 +17,8 @@ public interface IAlignment extends IAlignmentLimits, IAlignmentProvider {
|
||||
ExtendedFacing getExtendedFacing();
|
||||
|
||||
/**
|
||||
* Set the facing without additional checks.
|
||||
* Tools altering facing should use {@link #toolSetExtendedFacing(ExtendedFacing)} instead.
|
||||
* Set the facing without additional checks. Tools altering facing should use
|
||||
* {@link #toolSetExtendedFacing(ExtendedFacing)} instead.
|
||||
*/
|
||||
void setExtendedFacing(ExtendedFacing alignment);
|
||||
|
||||
@@ -57,9 +59,8 @@ public interface IAlignment extends IAlignmentLimits, IAlignmentProvider {
|
||||
|
||||
default boolean toolSetDirection(ForgeDirection direction) {
|
||||
if (direction == null || direction == ForgeDirection.UNKNOWN) {
|
||||
for (int i = 0, j = getDirection().ordinal() + 1, valuesLength = Direction.VALUES.length;
|
||||
i < valuesLength;
|
||||
i++) {
|
||||
for (int i = 0, j = getDirection().ordinal() + 1, valuesLength = Direction.VALUES.length; i
|
||||
< valuesLength; i++) {
|
||||
if (toolSetDirection(Direction.VALUES[(j + i) % valuesLength].getForgeDirection())) {
|
||||
return true;
|
||||
}
|
||||
@@ -97,8 +98,11 @@ public interface IAlignment extends IAlignmentLimits, IAlignmentProvider {
|
||||
int rotations = Rotation.VALUES.length;
|
||||
for (int ii = 0, jj = getFlip().ordinal(); ii < flips; ii++) {
|
||||
for (int i = 1, j = getRotation().ordinal(); i < rotations; i++) {
|
||||
if (checkedSetExtendedFacing(ExtendedFacing.of(
|
||||
getDirection(), Rotation.VALUES[(j + i) % rotations], Flip.VALUES[(jj + ii) % flips]))) {
|
||||
if (checkedSetExtendedFacing(
|
||||
ExtendedFacing.of(
|
||||
getDirection(),
|
||||
Rotation.VALUES[(j + i) % rotations],
|
||||
Flip.VALUES[(jj + ii) % flips]))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -140,9 +144,8 @@ public interface IAlignment extends IAlignmentLimits, IAlignmentProvider {
|
||||
|
||||
default boolean toolSetExtendedFacing(ExtendedFacing extendedFacing) {
|
||||
if (extendedFacing == null) {
|
||||
for (int i = 0, j = getExtendedFacing().ordinal() + 1, valuesLength = ExtendedFacing.VALUES.length;
|
||||
i < valuesLength;
|
||||
i++) {
|
||||
for (int i = 0, j = getExtendedFacing().ordinal() + 1, valuesLength = ExtendedFacing.VALUES.length; i
|
||||
< valuesLength; i++) {
|
||||
if (checkedSetExtendedFacing(ExtendedFacing.VALUES[(j + i) % valuesLength])) {
|
||||
return true;
|
||||
}
|
||||
@@ -187,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;
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,40 @@ package com.gtnewhorizon.structurelib.alignment;
|
||||
|
||||
import static com.gtnewhorizon.structurelib.alignment.IAlignment.STATES_COUNT;
|
||||
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.Flip;
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.Rotation;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.Flip;
|
||||
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) {
|
||||
@@ -37,6 +59,7 @@ public interface IAlignmentLimits {
|
||||
}
|
||||
|
||||
class Builder {
|
||||
|
||||
protected final boolean[] validStates = new boolean[STATES_COUNT];
|
||||
|
||||
private Builder() {}
|
||||
@@ -54,15 +77,13 @@ public interface IAlignmentLimits {
|
||||
}
|
||||
|
||||
public Builder deny(ForgeDirection fd) {
|
||||
ExtendedFacing.getAllWith(fd).stream()
|
||||
.mapToInt(ExtendedFacing::getIndex)
|
||||
ExtendedFacing.getAllWith(fd).stream().mapToInt(ExtendedFacing::getIndex)
|
||||
.forEach(v -> validStates[v] = false);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder allow(ForgeDirection fd) {
|
||||
ExtendedFacing.getAllWith(fd).stream()
|
||||
.mapToInt(ExtendedFacing::getIndex)
|
||||
ExtendedFacing.getAllWith(fd).stream().mapToInt(ExtendedFacing::getIndex)
|
||||
.forEach(v -> validStates[v] = true);
|
||||
return this;
|
||||
}
|
||||
@@ -78,29 +99,25 @@ public interface IAlignmentLimits {
|
||||
}
|
||||
|
||||
public Builder deny(Rotation fd) {
|
||||
ExtendedFacing.getAllWith(fd).stream()
|
||||
.mapToInt(ExtendedFacing::getIndex)
|
||||
ExtendedFacing.getAllWith(fd).stream().mapToInt(ExtendedFacing::getIndex)
|
||||
.forEach(v -> validStates[v] = false);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder allow(Rotation fd) {
|
||||
ExtendedFacing.getAllWith(fd).stream()
|
||||
.mapToInt(ExtendedFacing::getIndex)
|
||||
ExtendedFacing.getAllWith(fd).stream().mapToInt(ExtendedFacing::getIndex)
|
||||
.forEach(v -> validStates[v] = true);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder deny(Flip fd) {
|
||||
ExtendedFacing.getAllWith(fd).stream()
|
||||
.mapToInt(ExtendedFacing::getIndex)
|
||||
ExtendedFacing.getAllWith(fd).stream().mapToInt(ExtendedFacing::getIndex)
|
||||
.forEach(v -> validStates[v] = false);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder allow(Flip fd) {
|
||||
ExtendedFacing.getAllWith(fd).stream()
|
||||
.mapToInt(ExtendedFacing::getIndex)
|
||||
ExtendedFacing.getAllWith(fd).stream().mapToInt(ExtendedFacing::getIndex)
|
||||
.forEach(v -> validStates[v] = true);
|
||||
return this;
|
||||
}
|
||||
@@ -122,7 +139,7 @@ public interface IAlignmentLimits {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefers rotation over flip, so both flip will get translated to opposite rotation and no flip
|
||||
* Prefers rotation to flip, so both flip will get translated to opposite rotation and no flip
|
||||
*
|
||||
* @param flip the preferred flip to be used Horizontal or vertical
|
||||
* @return this
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.gtnewhorizon.structurelib.alignment;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public interface IAlignmentProvider {
|
||||
|
||||
@Nullable
|
||||
IAlignment getAlignment();
|
||||
}
|
||||
@@ -2,12 +2,14 @@ package com.gtnewhorizon.structurelib.alignment;
|
||||
|
||||
import static java.lang.Math.abs;
|
||||
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.Direction;
|
||||
import com.gtnewhorizon.structurelib.util.Vec3Impl;
|
||||
import net.minecraft.util.Vec3;
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.Direction;
|
||||
import com.gtnewhorizon.structurelib.util.Vec3Impl;
|
||||
|
||||
public class IntegerAxisSwap {
|
||||
|
||||
private final Vec3Impl forFirstAxis;
|
||||
private final Vec3Impl forSecondAxis;
|
||||
private final Vec3Impl forThirdAxis;
|
||||
@@ -20,61 +22,49 @@ public class IntegerAxisSwap {
|
||||
|| abs(this.forFirstAxis.get1()) + abs(this.forSecondAxis.get1()) + abs(this.forThirdAxis.get1()) != 1
|
||||
|| abs(this.forFirstAxis.get2()) + abs(this.forSecondAxis.get2()) + abs(this.forThirdAxis.get2())
|
||||
!= 1) {
|
||||
throw new IllegalArgumentException("Axis are overlapping/missing! " + forFirstAxis.name()
|
||||
+ " " + forSecondAxis.name()
|
||||
+ " " + forThirdAxis.name());
|
||||
throw new IllegalArgumentException(
|
||||
"Axis are overlapping/missing! " + forFirstAxis
|
||||
.name() + " " + forSecondAxis.name() + " " + forThirdAxis.name());
|
||||
}
|
||||
}
|
||||
|
||||
public Vec3Impl translate(Vec3Impl point) {
|
||||
return new Vec3Impl(
|
||||
forFirstAxis.get0() * point.get0()
|
||||
+ forFirstAxis.get1() * point.get1()
|
||||
forFirstAxis.get0() * point.get0() + forFirstAxis.get1() * point.get1()
|
||||
+ forFirstAxis.get2() * point.get2(),
|
||||
forSecondAxis.get0() * point.get0()
|
||||
+ forSecondAxis.get1() * point.get1()
|
||||
forSecondAxis.get0() * point.get0() + forSecondAxis.get1() * point.get1()
|
||||
+ forSecondAxis.get2() * point.get2(),
|
||||
forThirdAxis.get0() * point.get0()
|
||||
+ forThirdAxis.get1() * point.get1()
|
||||
forThirdAxis.get0() * point.get0() + forThirdAxis.get1() * point.get1()
|
||||
+ forThirdAxis.get2() * point.get2());
|
||||
}
|
||||
|
||||
public Vec3Impl inverseTranslate(Vec3Impl point) {
|
||||
return new Vec3Impl(
|
||||
forFirstAxis.get0() * point.get0()
|
||||
+ forSecondAxis.get0() * point.get1()
|
||||
forFirstAxis.get0() * point.get0() + forSecondAxis.get0() * point.get1()
|
||||
+ forThirdAxis.get0() * point.get2(),
|
||||
forFirstAxis.get1() * point.get0()
|
||||
+ forSecondAxis.get1() * point.get1()
|
||||
forFirstAxis.get1() * point.get0() + forSecondAxis.get1() * point.get1()
|
||||
+ forThirdAxis.get1() * point.get2(),
|
||||
forFirstAxis.get2() * point.get0()
|
||||
+ forSecondAxis.get2() * point.get1()
|
||||
forFirstAxis.get2() * point.get0() + forSecondAxis.get2() * point.get1()
|
||||
+ forThirdAxis.get2() * point.get2());
|
||||
}
|
||||
|
||||
public Vec3 translate(Vec3 point) {
|
||||
return Vec3.createVectorHelper(
|
||||
forFirstAxis.get0() * point.xCoord
|
||||
+ forFirstAxis.get1() * point.yCoord
|
||||
forFirstAxis.get0() * point.xCoord + forFirstAxis.get1() * point.yCoord
|
||||
+ forFirstAxis.get2() * point.zCoord,
|
||||
forSecondAxis.get0() * point.xCoord
|
||||
+ forSecondAxis.get1() * point.yCoord
|
||||
forSecondAxis.get0() * point.xCoord + forSecondAxis.get1() * point.yCoord
|
||||
+ forSecondAxis.get2() * point.zCoord,
|
||||
forThirdAxis.get0() * point.xCoord
|
||||
+ forThirdAxis.get1() * point.yCoord
|
||||
forThirdAxis.get0() * point.xCoord + forThirdAxis.get1() * point.yCoord
|
||||
+ forThirdAxis.get2() * point.zCoord);
|
||||
}
|
||||
|
||||
public Vec3 inverseTranslate(Vec3 point) {
|
||||
return Vec3.createVectorHelper(
|
||||
forFirstAxis.get0() * point.xCoord
|
||||
+ forSecondAxis.get0() * point.yCoord
|
||||
forFirstAxis.get0() * point.xCoord + forSecondAxis.get0() * point.yCoord
|
||||
+ forThirdAxis.get0() * point.zCoord,
|
||||
forFirstAxis.get1() * point.xCoord
|
||||
+ forSecondAxis.get1() * point.yCoord
|
||||
forFirstAxis.get1() * point.xCoord + forSecondAxis.get1() * point.yCoord
|
||||
+ forThirdAxis.get1() * point.zCoord,
|
||||
forFirstAxis.get2() * point.xCoord
|
||||
+ forSecondAxis.get2() * point.yCoord
|
||||
forFirstAxis.get2() * point.xCoord + forSecondAxis.get2() * point.yCoord
|
||||
+ forThirdAxis.get2() * point.zCoord);
|
||||
}
|
||||
|
||||
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
package com.gtnewhorizon.structurelib.alignment.constructable;
|
||||
|
||||
import static com.gtnewhorizon.structurelib.util.MiscUtils.getTagKeys;
|
||||
import static net.minecraftforge.common.util.Constants.NBT.TAG_COMPOUND;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import net.minecraft.init.Items;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraftforge.common.util.Constants.NBT;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.tuple.ImmutablePair;
|
||||
|
||||
import com.gtnewhorizon.structurelib.StructureLibAPI;
|
||||
|
||||
/**
|
||||
* Provide accessor methods for channel data from trigger item.
|
||||
*
|
||||
* <a href="{@docRoot}/overview-summary.html#channels">Channels section on overview</a>
|
||||
*/
|
||||
public class ChannelDataAccessor {
|
||||
|
||||
private static final String SECONDARY_HINT_TAG = "channels";
|
||||
|
||||
private ChannelDataAccessor() {}
|
||||
|
||||
/**
|
||||
* Return a trigger item that
|
||||
* <ul>
|
||||
* <li>Still carry all the subchannel in some unspecified ways</li>
|
||||
* <li>Have its master channel data replaced with specified subchannel's data</li>
|
||||
* </ul>
|
||||
*
|
||||
* If given subchannel does not exist, masterStack may or may not be returned to save performance. Code within
|
||||
* StructureLib can depend on this behavior, you cannot.
|
||||
*
|
||||
* @param masterStack trigger item. can be the return value of another {@link #withChannel(ItemStack, String)} call
|
||||
* @param channel subchannel to use. will use current master channel data instead if this channel does not exist
|
||||
* Note: all channel identifiers are supposed to be lower case and not empty.
|
||||
* @return a trigger item with new master channel
|
||||
*/
|
||||
public static ItemStack withChannel(ItemStack masterStack, String channel) {
|
||||
if (StringUtils.isEmpty(channel) || masterStack == null) throw new IllegalArgumentException();
|
||||
if (StructureLibAPI.isDebugEnabled() && !channel.toLowerCase(Locale.ROOT).equals(channel))
|
||||
throw new IllegalArgumentException("Channel name can be lower case ONLY");
|
||||
if (!masterStack.hasTagCompound() || !masterStack.stackTagCompound.hasKey(SECONDARY_HINT_TAG, TAG_COMPOUND)
|
||||
|| !masterStack.stackTagCompound.getCompoundTag(SECONDARY_HINT_TAG).hasKey(channel, NBT.TAG_INT))
|
||||
return masterStack;
|
||||
ItemStack ret = new ItemStack(
|
||||
masterStack.getItem(),
|
||||
masterStack.stackTagCompound.getCompoundTag(SECONDARY_HINT_TAG).getInteger(channel),
|
||||
Items.feather.getDamage(masterStack));
|
||||
ret.setTagCompound(masterStack.stackTagCompound);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given trigger item contains any subchannel
|
||||
*
|
||||
* @param masterStack trigger stack to check
|
||||
* @return true if contains any subchannel
|
||||
*/
|
||||
public static boolean hasSubChannel(ItemStack masterStack) {
|
||||
if (masterStack == null) throw new IllegalArgumentException();
|
||||
return masterStack.hasTagCompound() && masterStack.stackTagCompound.hasKey(SECONDARY_HINT_TAG, TAG_COMPOUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given trigger item contains specified subchannel
|
||||
*
|
||||
* @param masterStack trigger stack to check
|
||||
* @param channel channel identifier. Note: all channel identifiers are supposed to be lower case and not empty.
|
||||
* @return true if contains specified subchannel
|
||||
*/
|
||||
public static boolean hasSubChannel(ItemStack masterStack, String channel) {
|
||||
if (StringUtils.isEmpty(channel) || masterStack == null) throw new IllegalArgumentException();
|
||||
if (StructureLibAPI.isDebugEnabled() && !channel.toLowerCase(Locale.ROOT).equals(channel))
|
||||
throw new IllegalArgumentException("Channel name can be lower case ONLY");
|
||||
return !channel.isEmpty() && masterStack.hasTagCompound()
|
||||
&& masterStack.stackTagCompound.hasKey(SECONDARY_HINT_TAG, TAG_COMPOUND)
|
||||
&& masterStack.stackTagCompound.getCompoundTag(SECONDARY_HINT_TAG).hasKey(channel, NBT.TAG_INT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the subchannel data from given trigger item. Will use master channel instead if not present.
|
||||
*
|
||||
* @param masterStack trigger stack to query from
|
||||
* @param channel channel identifier. Note: all channel identifiers are supposed to be lower case and not empty.
|
||||
* @return channel data
|
||||
*/
|
||||
public static int getChannelData(ItemStack masterStack, String channel) {
|
||||
if (StringUtils.isEmpty(channel) || masterStack == null) throw new IllegalArgumentException();
|
||||
if (StructureLibAPI.isDebugEnabled() && !channel.toLowerCase(Locale.ROOT).equals(channel))
|
||||
throw new IllegalArgumentException("Channel name can be lower case ONLY");
|
||||
if (!masterStack.hasTagCompound() || !masterStack.stackTagCompound.hasKey(SECONDARY_HINT_TAG, TAG_COMPOUND)
|
||||
|| !masterStack.stackTagCompound.getCompoundTag(SECONDARY_HINT_TAG).hasKey(channel, NBT.TAG_INT))
|
||||
return masterStack.stackSize;
|
||||
return masterStack.stackTagCompound.getCompoundTag(SECONDARY_HINT_TAG).getInteger(channel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the subchannel data on given trigger item
|
||||
*
|
||||
* @param masterStack trigger stack to check
|
||||
* @param channel channel identifier. Note: all channel identifiers are supposed to be lower case and not empty.
|
||||
* @param data subchannel data. should always be a positive value
|
||||
*/
|
||||
public static void setChannelData(ItemStack masterStack, String channel, int data) {
|
||||
if (StringUtils.isEmpty(channel) || masterStack == null) throw new IllegalArgumentException();
|
||||
if (StructureLibAPI.isDebugEnabled() && !channel.toLowerCase(Locale.ROOT).equals(channel))
|
||||
throw new IllegalArgumentException("Channel name can be lower case ONLY");
|
||||
if (data <= 0) throw new IllegalArgumentException();
|
||||
if (masterStack.stackTagCompound == null) masterStack.stackTagCompound = new NBTTagCompound();
|
||||
NBTTagCompound main = masterStack.stackTagCompound;
|
||||
if (!main.hasKey(SECONDARY_HINT_TAG, TAG_COMPOUND)) main.setTag(SECONDARY_HINT_TAG, new NBTTagCompound());
|
||||
main.getCompoundTag(SECONDARY_HINT_TAG).setInteger(channel, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the given subchannel from given trigger item, if it exists
|
||||
*
|
||||
* @param masterStack trigger stack to unset
|
||||
* @param channel channel identifier. Note: all channel identifiers are supposed to be lower case and not empty.
|
||||
*/
|
||||
public static void unsetChannelData(ItemStack masterStack, String channel) {
|
||||
if (StringUtils.isEmpty(channel) || masterStack == null) throw new IllegalArgumentException();
|
||||
if (StructureLibAPI.isDebugEnabled() && !channel.toLowerCase(Locale.ROOT).equals(channel))
|
||||
throw new IllegalArgumentException("Channel name can be lower case ONLY");
|
||||
if (masterStack.stackTagCompound == null) masterStack.stackTagCompound = new NBTTagCompound();
|
||||
NBTTagCompound main = masterStack.stackTagCompound;
|
||||
if (!main.hasKey(SECONDARY_HINT_TAG, TAG_COMPOUND)) main.setTag(SECONDARY_HINT_TAG, new NBTTagCompound());
|
||||
NBTTagCompound tag = main.getCompoundTag(SECONDARY_HINT_TAG);
|
||||
tag.removeTag(channel);
|
||||
if (tag.hasNoTags()) main.removeTag(SECONDARY_HINT_TAG);
|
||||
if (main.hasNoTags()) masterStack.stackTagCompound = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wipe all subchannel data on given trigger item
|
||||
*
|
||||
* @param masterStack trigger stack to wipe
|
||||
*/
|
||||
public static void wipeChannelData(ItemStack masterStack) {
|
||||
if (masterStack == null) throw new IllegalArgumentException();
|
||||
if (masterStack.stackTagCompound != null) {
|
||||
masterStack.stackTagCompound.removeTag(SECONDARY_HINT_TAG);
|
||||
if (masterStack.stackTagCompound.hasNoTags()) {
|
||||
masterStack.stackTagCompound = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over all subchannel data on trigger stack. Does not include master channel!!
|
||||
*
|
||||
* @param masterStack trigger stack to check
|
||||
* @return A java8 stream of pairs. Key is channel identifier and value is channel data. Pairs do not support
|
||||
* mutation nor removal. Can still cause {@link java.util.ConcurrentModificationException} if underlying
|
||||
* channel data is modified while stream is working.
|
||||
*/
|
||||
public static Stream<Entry<String, Integer>> iterateChannelData(ItemStack masterStack) {
|
||||
if (!hasSubChannel(masterStack)) return Stream.empty();
|
||||
NBTTagCompound tag = masterStack.stackTagCompound.getCompoundTag(SECONDARY_HINT_TAG);
|
||||
return getTagKeys(tag).stream().map(s -> new ImmutablePair<>(s, tag.getInteger(s)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of subchannels present on given trigger item. Does not include master channel.
|
||||
*
|
||||
* @param masterStack trigger stack to query from
|
||||
* @return subchannel count
|
||||
*/
|
||||
public static int countChannelData(ItemStack masterStack) {
|
||||
if (!hasSubChannel(masterStack)) return 0;
|
||||
NBTTagCompound tag = masterStack.stackTagCompound.getCompoundTag(SECONDARY_HINT_TAG);
|
||||
return tag.func_150296_c().size();
|
||||
}
|
||||
}
|
||||
+41
-33
@@ -1,39 +1,41 @@
|
||||
package com.gtnewhorizon.structurelib.alignment.constructable;
|
||||
|
||||
import com.gtnewhorizon.structurelib.StructureLib;
|
||||
import com.gtnewhorizon.structurelib.StructureLibAPI;
|
||||
import com.gtnewhorizon.structurelib.alignment.IAlignment;
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
|
||||
import com.gtnewhorizon.structurelib.structure.IItemSource;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.ChatComponentTranslation;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.util.FakePlayer;
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
|
||||
import com.gtnewhorizon.structurelib.ConfigurationHandler;
|
||||
import com.gtnewhorizon.structurelib.StructureLib;
|
||||
import com.gtnewhorizon.structurelib.StructureLibAPI;
|
||||
import com.gtnewhorizon.structurelib.alignment.IAlignment;
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
|
||||
import com.gtnewhorizon.structurelib.structure.IItemSource;
|
||||
import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment;
|
||||
|
||||
public class ConstructableUtility {
|
||||
|
||||
// TODO make these configurable and expose this to external world
|
||||
public static final int COOLDOWN = 5;
|
||||
private static final int BUDGET = 25;
|
||||
public static boolean redBrokenOne = true; // TODO this is horrible
|
||||
private static final WeakHashMap<EntityPlayerMP, Long> lastUse = new WeakHashMap<>();
|
||||
private static long clientSideLastUse = 0;
|
||||
|
||||
private ConstructableUtility() {}
|
||||
|
||||
public static boolean handle(
|
||||
ItemStack aStack, EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ, int aSide) {
|
||||
public static boolean handle(ItemStack aStack, EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ,
|
||||
int aSide) {
|
||||
StructureLibAPI.startHinting(aWorld);
|
||||
boolean ret = handle0(aStack, aPlayer, aWorld, aX, aY, aZ, aSide);
|
||||
StructureLibAPI.endHinting(aWorld);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static boolean handle0(
|
||||
ItemStack aStack, EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ, int aSide) {
|
||||
private static boolean handle0(ItemStack aStack, EntityPlayer aPlayer, World aWorld, int aX, int aY, int aZ,
|
||||
int aSide) {
|
||||
TileEntity tTileEntity = aWorld.getTileEntity(aX, aY, aZ);
|
||||
if (tTileEntity == null || aPlayer instanceof FakePlayer) {
|
||||
return aPlayer instanceof EntityPlayerMP;
|
||||
@@ -42,10 +44,12 @@ public class ConstructableUtility {
|
||||
// not sneaking - client side will generate hologram. nothing to do on server side
|
||||
if (!aPlayer.isSneaking()) return true;
|
||||
|
||||
long timePast = StructureLib.getOverworldTime() - getLastUseTick(aStack);
|
||||
if (timePast < COOLDOWN) {
|
||||
aPlayer.addChatComponentMessage(new ChatComponentTranslation(
|
||||
"item.structurelib.constructableTrigger.too_fast", COOLDOWN - timePast));
|
||||
long timePast = System.currentTimeMillis() - getLastUseMilis(aPlayer);
|
||||
if (timePast < ConfigurationHandler.INSTANCE.getAutoPlaceInterval()) {
|
||||
aPlayer.addChatComponentMessage(
|
||||
new ChatComponentTranslation(
|
||||
"item.structurelib.constructableTrigger.too_fast",
|
||||
ConfigurationHandler.INSTANCE.getAutoPlaceInterval() - timePast));
|
||||
return true;
|
||||
}
|
||||
} else if (!StructureLib.isCurrentPlayer(aPlayer)) {
|
||||
@@ -58,8 +62,7 @@ public class ConstructableUtility {
|
||||
else if (tTileEntity instanceof IConstructable) {
|
||||
constructable = (IConstructable) tTileEntity;
|
||||
} else if (IMultiblockInfoContainer.contains(tTileEntity.getClass())) {
|
||||
ExtendedFacing facing = tTileEntity instanceof IAlignment
|
||||
? ((IAlignment) tTileEntity).getExtendedFacing()
|
||||
ExtendedFacing facing = tTileEntity instanceof IAlignment ? ((IAlignment) tTileEntity).getExtendedFacing()
|
||||
: ExtendedFacing.of(ForgeDirection.getOrientation(aSide));
|
||||
constructable = IMultiblockInfoContainer.<TileEntity>get(tTileEntity.getClass())
|
||||
.toConstructable(tTileEntity, facing);
|
||||
@@ -67,21 +70,22 @@ public class ConstructableUtility {
|
||||
|
||||
if (constructable == null) return false;
|
||||
|
||||
if (aPlayer instanceof EntityPlayerMP) {
|
||||
EntityPlayerMP playerMP = (EntityPlayerMP) aPlayer;
|
||||
if (aPlayer instanceof EntityPlayerMP playerMP) {
|
||||
// server side and sneaking (already checked above)
|
||||
// do construct
|
||||
if (aPlayer.capabilities.isCreativeMode) {
|
||||
constructable.construct(aStack, false);
|
||||
} else if (constructable instanceof ISurvivalConstructable) {
|
||||
int built = ((ISurvivalConstructable) constructable)
|
||||
.survivalConstruct(aStack, BUDGET, IItemSource.fromPlayer(playerMP), playerMP);
|
||||
int built = ((ISurvivalConstructable) constructable).survivalConstruct(
|
||||
aStack,
|
||||
ConfigurationHandler.INSTANCE.getAutoPlaceBudget(),
|
||||
ISurvivalBuildEnvironment.create(IItemSource.fromPlayer(playerMP), playerMP));
|
||||
if (built > 0) {
|
||||
playerMP.addChatMessage(new ChatComponentTranslation("structurelib.autoplace.built_stat", built));
|
||||
} else if (built == -1) {
|
||||
playerMP.addChatMessage(new ChatComponentTranslation("structurelib.autoplace.complete"));
|
||||
}
|
||||
setLastUseTickToStackTag(aStack);
|
||||
setLastUseMilis(aPlayer);
|
||||
} else {
|
||||
playerMP.addChatMessage(new ChatComponentTranslation("structurelib.autoplace.error.not_enabled"));
|
||||
}
|
||||
@@ -90,19 +94,23 @@ public class ConstructableUtility {
|
||||
// client side
|
||||
// particles and text
|
||||
constructable.construct(aStack, true);
|
||||
if (getLastUseTick(aStack) == 0)
|
||||
if (System.currentTimeMillis() - getLastUseMilis(aPlayer) >= 300)
|
||||
StructureLib.addClientSideChatMessages(constructable.getStructureDescription(aStack));
|
||||
setLastUseMilis(aPlayer);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static long getLastUseTick(ItemStack aStack) {
|
||||
return aStack.hasTagCompound() ? aStack.getTagCompound().getLong("LastUse") : 0;
|
||||
private static void setLastUseMilis(EntityPlayer aPlayer) {
|
||||
if (!(aPlayer instanceof EntityPlayerMP))
|
||||
// assume client side
|
||||
clientSideLastUse = System.currentTimeMillis();
|
||||
else lastUse.put((EntityPlayerMP) aPlayer, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
private static void setLastUseTickToStackTag(ItemStack aStack) {
|
||||
NBTTagCompound tag = aStack.stackTagCompound;
|
||||
if (tag == null) tag = aStack.stackTagCompound = new NBTTagCompound();
|
||||
// here we force use the overworld tick time, in case the current world is over
|
||||
tag.setLong("LastUse", StructureLib.getOverworldTime());
|
||||
private static long getLastUseMilis(EntityPlayer aPlayer) {
|
||||
if (!(aPlayer instanceof EntityPlayerMP))
|
||||
// assume client side
|
||||
return clientSideLastUse;
|
||||
return lastUse.getOrDefault(aPlayer, 0L);
|
||||
}
|
||||
}
|
||||
+12
-1
@@ -1,15 +1,26 @@
|
||||
package com.gtnewhorizon.structurelib.alignment.constructable;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import com.gtnewhorizon.structurelib.structure.IStructureDefinition;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
/**
|
||||
* Created by Tec on 24.03.2017.
|
||||
*/
|
||||
public interface IConstructable {
|
||||
|
||||
void construct(ItemStack stackSize, boolean hintsOnly);
|
||||
|
||||
/**
|
||||
* Get the structure definition used for this constructable. Can be null if this constructable is not backed by one.
|
||||
*/
|
||||
default IStructureDefinition<?> getStructureDefinition() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
String[] getStructureDescription(ItemStack stackSize);
|
||||
}
|
||||
+1
@@ -6,6 +6,7 @@ import javax.annotation.Nullable;
|
||||
* Implement this interface if this tile entity MIGHT be constructable
|
||||
*/
|
||||
public interface IConstructableProvider {
|
||||
|
||||
/**
|
||||
* @return null if not constructable, an instance otherwise.
|
||||
*/
|
||||
|
||||
+35
-41
@@ -1,19 +1,25 @@
|
||||
package com.gtnewhorizon.structurelib.alignment.constructable;
|
||||
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
|
||||
import com.gtnewhorizon.structurelib.structure.IItemSource;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import java.util.HashMap;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
|
||||
import com.gtnewhorizon.structurelib.structure.AutoPlaceEnvironment;
|
||||
import com.gtnewhorizon.structurelib.structure.IItemSource;
|
||||
import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
|
||||
/**
|
||||
* To implement IConstructable on not own TileEntities
|
||||
*/
|
||||
public interface IMultiblockInfoContainer<T> {
|
||||
|
||||
HashMap<String, IMultiblockInfoContainer<?>> MULTIBLOCK_MAP = new HashMap<>();
|
||||
|
||||
/**
|
||||
@@ -36,28 +42,37 @@ public interface IMultiblockInfoContainer<T> {
|
||||
void construct(ItemStack stackSize, boolean hintsOnly, T tileEntity, ExtendedFacing aSide);
|
||||
|
||||
/**
|
||||
* Construct the structure using {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, java.util.function.Consumer)}
|
||||
* Construct the structure using
|
||||
* {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)}
|
||||
*
|
||||
* @return -1 if done, a helping pointer
|
||||
*/
|
||||
default int survivalConstruct(
|
||||
ItemStack stackSize,
|
||||
int elementBudge,
|
||||
IItemSource source,
|
||||
EntityPlayerMP actorProfile,
|
||||
T tileEntity,
|
||||
ExtendedFacing aSide) {
|
||||
return -1;
|
||||
}
|
||||
int survivalConstruct(ItemStack stackSize, int elementBudge, ISurvivalBuildEnvironment env, T tileEntity,
|
||||
ExtendedFacing aSide);
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
String[] getDescription(ItemStack stackSize);
|
||||
|
||||
/**
|
||||
* Override this and return {@link #toConstructable(IMultiblockInfoContainer, Object, ExtendedFacing)} if
|
||||
* {@link #survivalConstruct(ItemStack, int, IItemSource, EntityPlayerMP, Object, ExtendedFacing)} isn't a stub
|
||||
*/
|
||||
default IConstructable toConstructable(T tileEntity, ExtendedFacing aSide) {
|
||||
return new IConstructable() {
|
||||
default ISurvivalConstructable toConstructable(T tileEntity, ExtendedFacing aSide) {
|
||||
return new ISurvivalConstructable() {
|
||||
|
||||
@Override
|
||||
public int survivalConstruct(ItemStack stackSize, int elementBudget, ISurvivalBuildEnvironment env) {
|
||||
return IMultiblockInfoContainer.this
|
||||
.survivalConstruct(stackSize, elementBudget, env, tileEntity, aSide);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int survivalConstruct(ItemStack stackSize, int elementBudget, IItemSource source,
|
||||
EntityPlayerMP actor) {
|
||||
return IMultiblockInfoContainer.this.survivalConstruct(
|
||||
stackSize,
|
||||
elementBudget,
|
||||
ISurvivalBuildEnvironment.create(source, actor),
|
||||
tileEntity,
|
||||
aSide);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void construct(ItemStack stackSize, boolean hintsOnly) {
|
||||
IMultiblockInfoContainer.this.construct(stackSize, hintsOnly, tileEntity, aSide);
|
||||
@@ -69,25 +84,4 @@ public interface IMultiblockInfoContainer<T> {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static <T> ISurvivalConstructable toConstructable(
|
||||
IMultiblockInfoContainer<T> thiz, T tileEntity, ExtendedFacing aSide) {
|
||||
return new ISurvivalConstructable() {
|
||||
@Override
|
||||
public int survivalConstruct(
|
||||
ItemStack stackSize, int elementBudget, IItemSource source, EntityPlayerMP actor) {
|
||||
return thiz.survivalConstruct(stackSize, elementBudget, source, actor, tileEntity, aSide);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void construct(ItemStack stackSize, boolean hintsOnly) {
|
||||
thiz.construct(stackSize, hintsOnly, tileEntity, aSide);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getStructureDescription(ItemStack stackSize) {
|
||||
return thiz.getDescription(stackSize);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+73
-5
@@ -1,17 +1,85 @@
|
||||
package com.gtnewhorizon.structurelib.alignment.constructable;
|
||||
|
||||
import com.gtnewhorizon.structurelib.structure.IItemSource;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
|
||||
import com.gtnewhorizon.structurelib.structure.AutoPlaceEnvironment;
|
||||
import com.gtnewhorizon.structurelib.structure.IItemSource;
|
||||
import com.gtnewhorizon.structurelib.structure.ISurvivalBuildEnvironment;
|
||||
|
||||
/**
|
||||
* An extension to {@link IConstructable}. Tile Entities (or delegates returned by
|
||||
* {@link IConstructableProvider#getConstructable()} should implement this interface to allow third party tools and
|
||||
* StructureLib builtin constructable tools to trigger autoplace in survival mode.
|
||||
* <p>
|
||||
* While not originally planned, a creative player can also use this interface to trigger autoplace by using an
|
||||
* {@link IItemSource} with every possible items in the game.
|
||||
*/
|
||||
public interface ISurvivalConstructable extends IConstructable {
|
||||
|
||||
/**
|
||||
* Construct the structure using {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, java.util.function.Consumer)}
|
||||
* End player might want to disable this hack if it turns out to be too crashy
|
||||
*/
|
||||
boolean DISABLE_HACKY_MIGRATION_CODE = Boolean.getBoolean("structurelib.disable_isc_migration_hack");
|
||||
|
||||
/**
|
||||
* Construct the structure using
|
||||
* {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, java.util.function.Consumer)}
|
||||
* or
|
||||
* {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition#survivalBuild(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int, int, IItemSource, EntityPlayerMP, boolean)}
|
||||
*
|
||||
* @param elementBudget The server configured element budget. You can tune this up a bit if the structure is too big, but
|
||||
* generally should not be a 4 digits number to not overwhelm the server
|
||||
* @param elementBudget The server configured element budget. The implementor can choose to tune this up a bit if
|
||||
* the structure is too big, but generally should not be a 4 digits number to not overwhelm the
|
||||
* server
|
||||
* @return -1 if done, otherwise number of elements placed this round
|
||||
*/
|
||||
int survivalConstruct(ItemStack stackSize, int elementBudget, IItemSource source, EntityPlayerMP actor);
|
||||
@Deprecated
|
||||
default int survivalConstruct(ItemStack stackSize, int elementBudget, IItemSource source, EntityPlayerMP actor) {
|
||||
return survivalConstruct(stackSize, elementBudget, ISurvivalBuildEnvironment.create(source, actor));
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the structure using
|
||||
* {@link com.gtnewhorizon.structurelib.structure.IStructureElement#survivalPlaceBlock(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)}
|
||||
* or
|
||||
* {@link com.gtnewhorizon.structurelib.structure.IStructureDefinition#survivalBuild(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int, int, ISurvivalBuildEnvironment, boolean)}
|
||||
*
|
||||
* @param elementBudget The server configured element budget. The implementor can choose to tune this up a bit if
|
||||
* the structure is too big, but generally should not be a 4 digits number to not overwhelm the
|
||||
* server
|
||||
* @return -1 if done, -2 if not supported (e.g. due to an incompatible change in the API of env, otherwise number
|
||||
* of elements placed this round
|
||||
*/
|
||||
default int survivalConstruct(ItemStack stackSize, int elementBudget, ISurvivalBuildEnvironment env) {
|
||||
EntityPlayer actor = env.getActor();
|
||||
if (actor instanceof EntityPlayerMP)
|
||||
return survivalConstruct(stackSize, elementBudget, env.getSource(), (EntityPlayerMP) actor);
|
||||
else if (!DISABLE_HACKY_MIGRATION_CODE && __get_player() == null) {
|
||||
// as far as I know, no implementor would actually do anything to actor beyond passing it down to
|
||||
// IStructureDefinition, so it's probably fine to proceed like this
|
||||
GlobalStates.entityPlayer.set(env.getActor());
|
||||
try {
|
||||
return survivalConstruct(stackSize, elementBudget, env.getSource(), null);
|
||||
} finally {
|
||||
GlobalStates.entityPlayer.remove();
|
||||
}
|
||||
}
|
||||
return -2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal hack. Do not use.
|
||||
*/
|
||||
@Deprecated
|
||||
static EntityPlayer __get_player() {
|
||||
return GlobalStates.entityPlayer.get();
|
||||
}
|
||||
}
|
||||
|
||||
class GlobalStates {
|
||||
|
||||
public static final ThreadLocal<EntityPlayer> entityPlayer = new ThreadLocal<>();
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* This package contains interfaces and utility methods for autobuilding a structure.
|
||||
*/
|
||||
package com.gtnewhorizon.structurelib.alignment.constructable;
|
||||
@@ -1,9 +1,16 @@
|
||||
package com.gtnewhorizon.structurelib.alignment.enumerable;
|
||||
|
||||
import com.gtnewhorizon.structurelib.util.Vec3Impl;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
|
||||
import com.gtnewhorizon.structurelib.util.Vec3Impl;
|
||||
|
||||
public enum Direction {
|
||||
|
||||
DOWN(ForgeDirection.DOWN),
|
||||
UP(ForgeDirection.UP),
|
||||
NORTH(ForgeDirection.NORTH),
|
||||
@@ -14,10 +21,12 @@ public enum Direction {
|
||||
private final ForgeDirection forgeDirection;
|
||||
private final Vec3Impl axisVector;
|
||||
public static final Direction[] VALUES = values();
|
||||
private static final Map<Vec3Impl, Direction> reverseMap = Arrays.stream(VALUES)
|
||||
.collect(Collectors.toMap(d -> d.axisVector, Function.identity()));
|
||||
|
||||
Direction(ForgeDirection forgeDirection) {
|
||||
this.forgeDirection = forgeDirection;
|
||||
axisVector = new Vec3Impl(forgeDirection.offsetX, forgeDirection.offsetY, forgeDirection.offsetZ);
|
||||
axisVector = Vec3Impl.getFromPool(forgeDirection.offsetX, forgeDirection.offsetY, forgeDirection.offsetZ);
|
||||
}
|
||||
|
||||
public ForgeDirection getForgeDirection() {
|
||||
@@ -31,4 +40,8 @@ public enum Direction {
|
||||
public static Vec3Impl getAxisVector(ForgeDirection forgeDirection) {
|
||||
return VALUES[forgeDirection.ordinal()].axisVector;
|
||||
}
|
||||
|
||||
public static Direction getByAxisVector(Vec3Impl vec3) {
|
||||
return reverseMap.get(vec3);
|
||||
}
|
||||
}
|
||||
+62
-28
@@ -4,18 +4,30 @@ 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.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;
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.gtnewhorizon.structurelib.alignment.IAlignment;
|
||||
import com.gtnewhorizon.structurelib.alignment.IntegerAxisSwap;
|
||||
import com.gtnewhorizon.structurelib.util.Vec3Impl;
|
||||
import java.util.*;
|
||||
import net.minecraft.util.StatCollector;
|
||||
import net.minecraft.util.Vec3;
|
||||
import net.minecraftforge.common.util.ForgeDirection;
|
||||
|
||||
public enum ExtendedFacing {
|
||||
|
||||
DOWN_NORMAL_NONE("down normal none"),
|
||||
DOWN_NORMAL_HORIZONTAL("down normal horizontal"),
|
||||
DOWN_NORMAL_VERTICAL("down normal vertical"),
|
||||
@@ -119,31 +131,31 @@ public enum ExtendedFacing {
|
||||
public static final int STATES_COUNT = VALUES.length;
|
||||
|
||||
static {
|
||||
stream(values())
|
||||
.forEach(extendedFacing ->
|
||||
FOR_FACING.compute(extendedFacing.direction, ((forgeDirection, extendedFacings) -> {
|
||||
if (extendedFacings == null) {
|
||||
extendedFacings = new ArrayList<>();
|
||||
}
|
||||
extendedFacings.add(extendedFacing);
|
||||
return extendedFacings;
|
||||
})));
|
||||
stream(values()).forEach(
|
||||
extendedFacing -> FOR_FACING.compute(extendedFacing.direction, ((forgeDirection, extendedFacings) -> {
|
||||
if (extendedFacings == null) {
|
||||
extendedFacings = new ArrayList<>();
|
||||
}
|
||||
extendedFacings.add(extendedFacing);
|
||||
return extendedFacings;
|
||||
})));
|
||||
}
|
||||
|
||||
private static final Map<String, ExtendedFacing> NAME_LOOKUP =
|
||||
stream(VALUES).collect(toMap(ExtendedFacing::getName2, (extendedFacing) -> extendedFacing));
|
||||
private static final Map<String, ExtendedFacing> NAME_LOOKUP = stream(VALUES)
|
||||
.collect(toMap(ExtendedFacing::getName2, (extendedFacing) -> extendedFacing));
|
||||
private static final EnumMap<ForgeDirection, ImmutableSet<ExtendedFacing>> LOOKUP_BY_DIRECTION = stream(VALUES)
|
||||
.collect(groupingBy(
|
||||
ExtendedFacing::getDirection,
|
||||
() -> new EnumMap<>(ForgeDirection.class),
|
||||
collectingAndThen(toSet(), ImmutableSet::copyOf)));
|
||||
private static final EnumMap<Rotation, ImmutableSet<ExtendedFacing>> LOOKUP_BY_ROTATION = stream(VALUES)
|
||||
.collect(groupingBy(
|
||||
.collect(
|
||||
groupingBy(
|
||||
ExtendedFacing::getDirection,
|
||||
() -> new EnumMap<>(ForgeDirection.class),
|
||||
collectingAndThen(toSet(), ImmutableSet::copyOf)));
|
||||
private static final EnumMap<Rotation, ImmutableSet<ExtendedFacing>> LOOKUP_BY_ROTATION = stream(VALUES).collect(
|
||||
groupingBy(
|
||||
ExtendedFacing::getRotation,
|
||||
() -> new EnumMap<>(Rotation.class),
|
||||
collectingAndThen(toSet(), ImmutableSet::copyOf)));
|
||||
private static final EnumMap<Flip, ImmutableSet<ExtendedFacing>> LOOKUP_BY_FLIP = stream(VALUES)
|
||||
.collect(groupingBy(
|
||||
private static final EnumMap<Flip, ImmutableSet<ExtendedFacing>> LOOKUP_BY_FLIP = stream(VALUES).collect(
|
||||
groupingBy(
|
||||
ExtendedFacing::getFlip,
|
||||
() -> new EnumMap<>(Flip.class),
|
||||
collectingAndThen(toSet(), ImmutableSet::copyOf)));
|
||||
@@ -333,9 +345,9 @@ public enum ExtendedFacing {
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates relative to front facing offset to world offset
|
||||
* Translates relative to front facing offset to world offset
|
||||
*
|
||||
* @param abcOffset A,B,C offset (facing relative L-->R,U-->D,F-->B)
|
||||
* @param abcOffset A,B,C offset (facing relative {@code L-->R,U-->D,F-->B})
|
||||
* @return X, Y, Z offset in world
|
||||
*/
|
||||
public Vec3 getWorldOffset(Vec3 abcOffset) {
|
||||
@@ -355,10 +367,32 @@ public enum ExtendedFacing {
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates world offset to relative front facing offset
|
||||
* do note the ABC coordinate system is (X, -Y, Z).
|
||||
*/
|
||||
public ForgeDirection getWorldDirection(ForgeDirection abcDirection) {
|
||||
switch (abcDirection) {
|
||||
case NORTH:
|
||||
return getRelativeForwardInWorld();
|
||||
case SOUTH:
|
||||
return getRelativeBackInWorld();
|
||||
case UP:
|
||||
return getRelativeDownInWorld();
|
||||
case DOWN:
|
||||
return getRelativeUpInWorld();
|
||||
case EAST:
|
||||
return getRelativeLeftInWorld();
|
||||
case WEST:
|
||||
return getRelativeRightInWorld();
|
||||
default:
|
||||
return ForgeDirection.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates world offset to relative front facing offset
|
||||
*
|
||||
* @param xyzOffset X,Y,Z offset in world
|
||||
* @return A, B, C offset (facing relative L-->R,U-->D,F-->B)
|
||||
* @return A, B, C offset (facing relative {@code L-->R,U-->D,F-->B})
|
||||
*/
|
||||
public Vec3 getOffsetABC(Vec3 xyzOffset) {
|
||||
return integerAxisSwap.translate(xyzOffset);
|
||||
|
||||
@@ -7,9 +7,11 @@ import static java.util.stream.Collectors.toMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public enum Flip {
|
||||
|
||||
NONE(3, "none"),
|
||||
HORIZONTAL(2, "horizontal"),
|
||||
VERTICAL(1, "vertical"),
|
||||
|
||||
@@ -7,9 +7,11 @@ import static java.util.stream.Collectors.toMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public enum Rotation {
|
||||
|
||||
NORMAL(2, "normal"),
|
||||
CLOCKWISE(3, "clockwise"),
|
||||
UPSIDE_DOWN(0, "upside down"),
|
||||
@@ -19,8 +21,8 @@ public enum Rotation {
|
||||
private final String name;
|
||||
|
||||
public static final Rotation[] VALUES = values();
|
||||
private static final Map<String, Rotation> NAME_LOOKUP =
|
||||
stream(VALUES).collect(toMap(Rotation::getName2, (rotation) -> rotation));
|
||||
private static final Map<String, Rotation> NAME_LOOKUP = stream(VALUES)
|
||||
.collect(toMap(Rotation::getName2, (rotation) -> rotation));
|
||||
|
||||
Rotation(int oppositeIn, String nameIn) {
|
||||
this.opposite = oppositeIn;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/**
|
||||
* All public stuff in this package and its subpackages are considered part of the public API.
|
||||
* Backwards compatibility is maintained to the maximum extend possible.
|
||||
* This package provide a framework for rotating a given block, or a multiblock structure.
|
||||
* <p>
|
||||
* All public stuff in this package and its subpackages are considered part of the public API. Backwards compatibility
|
||||
* is maintained to the maximum extend possible.
|
||||
*/
|
||||
package com.gtnewhorizon.structurelib.alignment;
|
||||
@@ -1,10 +1,7 @@
|
||||
package com.gtnewhorizon.structurelib.block;
|
||||
|
||||
import com.gtnewhorizon.structurelib.StructureLib;
|
||||
import com.gtnewhorizon.structurelib.StructureLibAPI;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.material.Material;
|
||||
import net.minecraft.client.renderer.texture.IIconRegister;
|
||||
@@ -14,7 +11,14 @@ import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.IIcon;
|
||||
import net.minecraft.world.IBlockAccess;
|
||||
|
||||
import com.gtnewhorizon.structurelib.StructureLib;
|
||||
import com.gtnewhorizon.structurelib.StructureLibAPI;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
|
||||
public class BlockHint extends Block {
|
||||
|
||||
private static final IIcon[] hint = new IIcon[16];
|
||||
|
||||
public BlockHint() {
|
||||
@@ -46,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];
|
||||
}
|
||||
|
||||
@@ -57,10 +65,9 @@ public class BlockHint extends Block {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void getSubBlocks(Item aItem, CreativeTabs par2CreativeTabs, List aList) {
|
||||
public void getSubBlocks(Item item, CreativeTabs tab, List<ItemStack> list) {
|
||||
for (int i = 0; i <= 15; i++) {
|
||||
aList.add(new ItemStack(aItem, 1, i));
|
||||
list.add(new ItemStack(item, 1, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package com.gtnewhorizon.structurelib.command;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import net.minecraft.command.CommandBase;
|
||||
import net.minecraft.command.ICommandSender;
|
||||
import net.minecraft.command.WrongUsageException;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.ChatComponentText;
|
||||
import net.minecraft.util.ChatComponentTranslation;
|
||||
|
||||
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
|
||||
import com.gtnewhorizon.structurelib.item.ItemConstructableTrigger;
|
||||
|
||||
public class CommandConfigureChannels extends CommandBase {
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return "sl_channel";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandUsage(ICommandSender p_71518_1_) {
|
||||
return "/sl_channel <wipe/getall>\n" + "/sl_channel <get/unset> <channel name>\n"
|
||||
+ "/sl_channel <set> <channel name> <value>\n"
|
||||
+ "<channel name> can have space in it. <value> must be positive integer";
|
||||
}
|
||||
|
||||
@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_, "get", "set", "unset", "wipe", "getall");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processCommand(ICommandSender sender, String[] args) {
|
||||
if (args.length == 0) throw new WrongUsageException(getCommandUsage(sender));
|
||||
|
||||
if (!(sender instanceof EntityPlayerMP player)) throw new WrongUsageException("must be a player");
|
||||
|
||||
ItemStack heldItem = player.getHeldItem();
|
||||
if (heldItem == null || !(heldItem.getItem() instanceof ItemConstructableTrigger)) {
|
||||
throw new WrongUsageException("must hold a hologram");
|
||||
}
|
||||
|
||||
String channel;
|
||||
|
||||
switch (args[0]) {
|
||||
case "getall":
|
||||
if (!ChannelDataAccessor.hasSubChannel(heldItem)) {
|
||||
sender.addChatMessage(new ChatComponentText("No subchannel"));
|
||||
} else {
|
||||
sender.addChatMessage(
|
||||
new ChatComponentTranslation(
|
||||
"item.structurelib.constructableTrigger.desc.lshift.0",
|
||||
ChannelDataAccessor.countChannelData(heldItem)));
|
||||
ChannelDataAccessor.iterateChannelData(heldItem).map(e -> e.getKey() + ": " + e.getValue())
|
||||
.map(ChatComponentText::new).forEach(player::addChatMessage);
|
||||
}
|
||||
return;
|
||||
case "wipe":
|
||||
ChannelDataAccessor.wipeChannelData(heldItem);
|
||||
return;
|
||||
case "get":
|
||||
case "unset":
|
||||
if (args.length < 2) throw new WrongUsageException(getCommandUsage(sender));
|
||||
channel = args.length == 2 ? args[1] : Arrays.stream(args).skip(1).collect(Collectors.joining());
|
||||
break;
|
||||
case "set":
|
||||
if (args.length < 3) throw new WrongUsageException(getCommandUsage(sender));
|
||||
channel = args.length == 3 ? args[1]
|
||||
: Arrays.stream(args).skip(1).limit(args.length - 2).collect(Collectors.joining());
|
||||
break;
|
||||
default:
|
||||
throw new WrongUsageException(getCommandUsage(sender));
|
||||
}
|
||||
|
||||
switch (args[0]) {
|
||||
case "get":
|
||||
if (ChannelDataAccessor.hasSubChannel(heldItem, channel)) sender.addChatMessage(
|
||||
new ChatComponentText(
|
||||
channel + " value: " + ChannelDataAccessor.getChannelData(heldItem, channel)));
|
||||
else sender.addChatMessage(new ChatComponentText(channel + " value: N/A"));
|
||||
break;
|
||||
case "clear":
|
||||
if (ChannelDataAccessor.hasSubChannel(heldItem, channel))
|
||||
sender.addChatMessage(new ChatComponentText(channel + " no value"));
|
||||
else {
|
||||
ChannelDataAccessor.unsetChannelData(heldItem, channel);
|
||||
sender.addChatMessage(new ChatComponentText(channel + " cleared"));
|
||||
player.inventoryContainer.detectAndSendChanges();
|
||||
}
|
||||
break;
|
||||
case "set":
|
||||
int value;
|
||||
try {
|
||||
value = Integer.parseInt(args[args.length - 1]);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new WrongUsageException("%s not a valid value", args[args.length - 1]);
|
||||
}
|
||||
|
||||
if (value <= 0) throw new WrongUsageException("%s not a valid value", args[args.length - 1]);
|
||||
|
||||
ChannelDataAccessor.setChannelData(heldItem, channel, value);
|
||||
player.inventoryContainer.detectAndSendChanges();
|
||||
sender.addChatMessage(new ChatComponentText(channel + " value: " + value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
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 player)) throw new WrongUsageException("must be a player");
|
||||
|
||||
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,57 @@
|
||||
package com.gtnewhorizon.structurelib.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
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.FMLLaunchHandler;
|
||||
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
|
||||
@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) {
|
||||
List<String> mixins = new ArrayList<>(
|
||||
Arrays.asList("blockChangeNotifier.MixinForgeHooks", "blockChangeNotifier.MixinWorld"));
|
||||
if (FMLLaunchHandler.side() == Side.CLIENT) {
|
||||
mixins.add("MixinGuiContainer_PickChannel");
|
||||
}
|
||||
return mixins;
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
package com.gtnewhorizon.structurelib.entity.fx;
|
||||
|
||||
import com.gtnewhorizon.structurelib.ClientProxy;
|
||||
import com.gtnewhorizon.structurelib.StructureLib;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.client.particle.EntityFX;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.init.Blocks;
|
||||
import net.minecraft.util.IIcon;
|
||||
import net.minecraft.world.World;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
public class EntityFXBlockHint extends EntityFX {
|
||||
private IIcon[] icons;
|
||||
private short[] mRGBa = {255, 255, 255, 0};
|
||||
|
||||
public EntityFXBlockHint(World world) {
|
||||
this(world, 0, 0, 0, Blocks.stone, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param world
|
||||
* @param x
|
||||
* @param y
|
||||
* @param z
|
||||
* @param icons DOWN, UP, NORTH, SOUTH, WEST, EAST
|
||||
*/
|
||||
public EntityFXBlockHint(World world, int x, int y, int z, IIcon[] icons) {
|
||||
super(world, x + .25, y + .5, z + .25);
|
||||
particleGravity = 0;
|
||||
prevPosX = posX;
|
||||
prevPosY = posY;
|
||||
prevPosZ = posZ;
|
||||
noClip = true;
|
||||
particleMaxAge = 2000 + StructureLib.RANDOM.nextInt(200);
|
||||
this.icons = icons;
|
||||
}
|
||||
|
||||
public EntityFXBlockHint(World world, int x, int y, int z, Block block, int meta) {
|
||||
super(world, x + .25, y + .5, z + .25);
|
||||
particleGravity = 0;
|
||||
prevPosX = posX;
|
||||
prevPosY = posY;
|
||||
prevPosZ = posZ;
|
||||
noClip = true;
|
||||
particleMaxAge = 2000 + StructureLib.RANDOM.nextInt(200);
|
||||
icons = new IIcon[6];
|
||||
for (int i = 0; i < 6; i++) {
|
||||
icons[i] = block.getIcon(i, meta);
|
||||
}
|
||||
}
|
||||
|
||||
public EntityFXBlockHint withColorTint(short[] coloure) {
|
||||
this.mRGBa = coloure;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderParticle(
|
||||
Tessellator tes,
|
||||
float subTickTime,
|
||||
float p_70539_3_,
|
||||
float p_70539_4_,
|
||||
float p_70539_5_,
|
||||
float p_70539_6_,
|
||||
float p_70539_7_) {
|
||||
float size = .5f;
|
||||
float X = (float) (prevPosX + (posX - prevPosX) * (double) subTickTime - EntityFX.interpPosX);
|
||||
float Y = (float) (prevPosY + (posY - prevPosY) * (double) subTickTime - EntityFX.interpPosY) - size / 2;
|
||||
float Z = (float) (prevPosZ + (posZ - prevPosZ) * (double) subTickTime - EntityFX.interpPosZ);
|
||||
GL11.glDisable(GL11.GL_CULL_FACE);
|
||||
GL11.glDepthMask(false);
|
||||
tes.setColorRGBA((int) (mRGBa[0] * .9F), (int) (mRGBa[1] * .95F), (int) (mRGBa[2] * 1F), 192);
|
||||
|
||||
// var8, var9 - X U
|
||||
// var 10, var 11 - Y V
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (icons[i] == null) {
|
||||
continue;
|
||||
}
|
||||
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:
|
||||
tes.addVertexWithUV(X, Y, Z + size, u, V);
|
||||
tes.addVertexWithUV(X, Y, Z, u, v);
|
||||
tes.addVertexWithUV(X + size, Y, Z, U, v);
|
||||
tes.addVertexWithUV(X + size, Y, Z + size, U, V);
|
||||
break;
|
||||
case 1:
|
||||
tes.addVertexWithUV(X, Y + size, Z, u, v);
|
||||
tes.addVertexWithUV(X, Y + size, Z + size, u, V);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z + size, U, V);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z, U, v);
|
||||
break;
|
||||
case 2:
|
||||
tes.addVertexWithUV(X, Y, Z, U, V);
|
||||
tes.addVertexWithUV(X, Y + size, Z, U, v);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z, u, v);
|
||||
tes.addVertexWithUV(X + size, Y, Z, u, V);
|
||||
break;
|
||||
case 3:
|
||||
tes.addVertexWithUV(X + size, Y, Z + size, U, V);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z + size, U, v);
|
||||
tes.addVertexWithUV(X, Y + size, Z + size, u, v);
|
||||
tes.addVertexWithUV(X, Y, Z + size, u, V);
|
||||
break;
|
||||
case 4:
|
||||
tes.addVertexWithUV(X, Y, Z + size, U, V);
|
||||
tes.addVertexWithUV(X, Y + size, Z + size, U, v);
|
||||
tes.addVertexWithUV(X, Y + size, Z, u, v);
|
||||
tes.addVertexWithUV(X, Y, Z, u, V);
|
||||
break;
|
||||
case 5:
|
||||
tes.addVertexWithUV(X + size, Y, Z, U, V);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z, U, v);
|
||||
tes.addVertexWithUV(X + size, Y + size, Z + size, u, v);
|
||||
tes.addVertexWithUV(X + size, Y, Z + size, u, V);
|
||||
break;
|
||||
}
|
||||
}
|
||||
GL11.glEnable(GL11.GL_CULL_FACE);
|
||||
GL11.glDepthMask(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFXLayer() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldRenderInPass(int pass) {
|
||||
return pass == 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDead() {
|
||||
super.setDead();
|
||||
ClientProxy.onHintDead(this);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,16 @@
|
||||
package com.gtnewhorizon.structurelib.entity.fx;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import net.minecraft.client.particle.EntityFX;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
|
||||
@SideOnly(Side.CLIENT)
|
||||
public class WeightlessParticleFX extends EntityFX {
|
||||
public WeightlessParticleFX(
|
||||
World p_i1205_1_,
|
||||
double p_i1205_2_,
|
||||
double p_i1205_4_,
|
||||
double p_i1205_6_,
|
||||
double p_i1205_8_,
|
||||
double p_i1205_10_,
|
||||
double p_i1205_12_) {
|
||||
|
||||
public WeightlessParticleFX(World p_i1205_1_, double p_i1205_2_, double p_i1205_4_, double p_i1205_6_,
|
||||
double p_i1205_8_, double p_i1205_10_, double p_i1205_12_) {
|
||||
super(p_i1205_1_, p_i1205_2_, p_i1205_4_, p_i1205_6_, p_i1205_8_, p_i1205_10_, p_i1205_12_);
|
||||
this.motionX = p_i1205_8_ + (double) ((float) (Math.random() * 2.0D - 1.0D) * 0.05F);
|
||||
this.motionY = p_i1205_10_ + (double) ((float) (Math.random() * 2.0D - 1.0D) * 0.05F);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,686 @@
|
||||
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.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;
|
||||
|
||||
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;
|
||||
private boolean wipePending = false;
|
||||
private long wipePendingTime = 0;
|
||||
private static final long WIPE_CONFIRM_TIMEOUT_MS = 3000;
|
||||
|
||||
public GuiScreenConfigureChannels(Container container, ItemStack trigger) {
|
||||
super(container);
|
||||
|
||||
this.trigger = trigger;
|
||||
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
|
||||
public void initGui() {
|
||||
super.initGui();
|
||||
// so you can keep holding backspace to delete a long sequence of chars
|
||||
Keyboard.enableRepeatEvents(true);
|
||||
|
||||
guiLeft = (this.width - this.getXSize()) / 2;
|
||||
guiTop = (this.height - this.getYSize()) / 2;
|
||||
|
||||
key = new GuiTextField(fontRendererObj, guiLeft + 45, guiTop + 119, 151 + 12 - 45, 12) {
|
||||
|
||||
@Override
|
||||
public void writeText(String text) {
|
||||
// force lower case
|
||||
super.writeText(text.toLowerCase(Locale.ROOT));
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(int p_146192_1_, int p_146192_2_, int p_146192_3_) {
|
||||
super.mouseClicked(p_146192_1_, p_146192_2_, p_146192_3_);
|
||||
boolean flag = p_146192_1_ >= this.xPosition && p_146192_1_ < this.xPosition + this.width
|
||||
&& p_146192_2_ >= this.yPosition
|
||||
&& p_146192_2_ < this.yPosition + this.height;
|
||||
if (flag && p_146192_3_ == 1) {
|
||||
key.setText("");
|
||||
value.setText("");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setText(String p_146180_1_) {
|
||||
super.setText(p_146180_1_);
|
||||
updateButtons();
|
||||
}
|
||||
};
|
||||
value = new GuiTextField(fontRendererObj, guiLeft + 45, guiTop + 139, 151 + 12 - 45, 12) {
|
||||
|
||||
@Override
|
||||
public void writeText(String text) {
|
||||
// ignore write requests containing non digit characters.
|
||||
// we don't accept decimals or negative numbers, so we can safely use isDigit instead of something more
|
||||
// advanced
|
||||
// use codePoints() in case someone pasted an emoji by mistake
|
||||
if (text != null && text.codePoints().allMatch(Character::isDigit)) {
|
||||
super.writeText(text);
|
||||
updateButtons();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFocused(boolean p_146195_1_) {
|
||||
if (!p_146195_1_ && isFocused() && !StringUtils.isBlank(getText())) {
|
||||
int result;
|
||||
try {
|
||||
result = Math.max(Integer.parseInt(getText()), 1);
|
||||
} catch (NumberFormatException e) {
|
||||
result = 1;
|
||||
}
|
||||
setText(String.valueOf(result));
|
||||
}
|
||||
super.setFocused(p_146195_1_);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(int p_146192_1_, int p_146192_2_, int p_146192_3_) {
|
||||
super.mouseClicked(p_146192_1_, p_146192_2_, p_146192_3_);
|
||||
boolean flag = p_146192_1_ >= this.xPosition && p_146192_1_ < this.xPosition + this.width
|
||||
&& p_146192_2_ >= this.yPosition
|
||||
&& p_146192_2_ < this.yPosition + this.height;
|
||||
if (flag && p_146192_3_ == 1) {
|
||||
setText("");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setText(String p_146180_1_) {
|
||||
super.setText(p_146180_1_);
|
||||
updateButtons();
|
||||
}
|
||||
};
|
||||
|
||||
list.onGuiInit(this);
|
||||
autoCompleteList.onGuiInit(this);
|
||||
|
||||
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
|
||||
public int getGuiLeft() {
|
||||
return guiLeft;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGuiTop() {
|
||||
return guiTop;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getXSize() {
|
||||
return 176;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getYSize() {
|
||||
return 188;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addButton(GuiButton button) {
|
||||
getButtonList().add(button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeButton(GuiButton button) {
|
||||
getButtonList().remove(button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOverlayOffsetX() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doActionPerformed(GuiButton but) {
|
||||
actionPerformed(but);
|
||||
}
|
||||
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void keyTyped(char aChar, int aKey) {
|
||||
GuiScrollableList<?> l = key.isFocused() ? autoCompleteList : list;
|
||||
switch (aKey) {
|
||||
case Keyboard.KEY_TAB:
|
||||
if (key.isFocused()) {
|
||||
if (autoCompleteList.getSelectedElement() != null) {
|
||||
key.setText(autoCompleteList.getSelectedElement());
|
||||
return;
|
||||
}
|
||||
|
||||
key.setFocused(false);
|
||||
value.setFocused(true);
|
||||
} else {
|
||||
key.setFocused(true);
|
||||
value.setFocused(false);
|
||||
}
|
||||
return;
|
||||
case Keyboard.KEY_RETURN:
|
||||
case Keyboard.KEY_NUMPADENTER:
|
||||
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 (l.selectedIndex > 0) l.setSelection(l.selectedIndex - 1);
|
||||
return;
|
||||
case Keyboard.KEY_DOWN:
|
||||
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) || value.textboxKeyTyped(aChar, aKey)) {
|
||||
updateButtons();
|
||||
return;
|
||||
}
|
||||
super.keyTyped(aChar, aKey);
|
||||
}
|
||||
|
||||
private void updateButtons() {
|
||||
// this will be called from setText of key and value. NEVER UPDATE THE VALUE OF THESE HERE OR GET A
|
||||
// STACKOVERFLOW!
|
||||
String keyText = key.getText();
|
||||
boolean existing = !StringUtils.isEmpty(keyText) && ChannelDataAccessor.hasSubChannel(trigger, keyText);
|
||||
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() {
|
||||
try {
|
||||
return Integer.parseInt(value.getText());
|
||||
} catch (NumberFormatException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void actionPerformed(GuiButton btn) {
|
||||
if (btn == null) return;
|
||||
switch (btn.id) {
|
||||
case ADD_BTN:
|
||||
int value = getValue();
|
||||
if (value <= 0) return;
|
||||
ChannelDataAccessor.setChannelData(trigger, key.getText(), value);
|
||||
break;
|
||||
case UNSET_BTN:
|
||||
ChannelDataAccessor.unsetChannelData(trigger, key.getText());
|
||||
break;
|
||||
case WIPE_BTN:
|
||||
if (!wipePending) {
|
||||
wipePending = true;
|
||||
wipePendingTime = System.currentTimeMillis();
|
||||
btn.displayString = I18n.format(I18N_PREFIX + "wipe.confirm");
|
||||
} else {
|
||||
wipePending = false;
|
||||
btn.displayString = I18n.format(I18N_PREFIX + "wipe");
|
||||
ChannelDataAccessor.wipeChannelData(trigger);
|
||||
key.setText("");
|
||||
this.value.setText("");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
updateButtons();
|
||||
|
||||
super.actionPerformed(btn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGuiClosed() {
|
||||
super.onGuiClosed();
|
||||
Keyboard.enableRepeatEvents(false);
|
||||
StructureLib.instance().proxy().uploadChannels(trigger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateScreen() {
|
||||
super.updateScreen();
|
||||
key.updateCursorCounter();
|
||||
value.updateCursorCounter();
|
||||
if (wipePending && System.currentTimeMillis() - wipePendingTime > WIPE_CONFIRM_TIMEOUT_MS) {
|
||||
wipePending = false;
|
||||
getButtonList().get(WIPE_BTN).displayString = I18n.format(I18N_PREFIX + "wipe");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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());
|
||||
list.drawScreen(mX, mY, partialTick);
|
||||
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(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;
|
||||
|
||||
public GuiChannelsList(int width, int height, int originX, int originY, int slotHeight) {
|
||||
super(width, height, originX, originY, slotHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<String, Integer> getElementAt(int index) {
|
||||
return ChannelDataAccessor.iterateChannelData(trigger).sorted().skip(index).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumElements() {
|
||||
return ChannelDataAccessor.countChannelData(trigger);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean elementClicked(int elementIndex, boolean doubleClick, int mXRelative, int mYRelative) {
|
||||
if (mXRelative >= margin + 1 && mXRelative <= margin + 5
|
||||
&& mYRelative >= margin / 2 + 1
|
||||
&& mYRelative <= margin / 2 + 5) {
|
||||
Entry<String, Integer> e = getElementAt(elementIndex);
|
||||
if (e != null) {
|
||||
ChannelDataAccessor.unsetChannelData(trigger, e.getKey());
|
||||
updateButtons();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return super.elementClicked(elementIndex, doubleClick, mXRelative, mYRelative);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void prepareDrawElements() {
|
||||
cache = ChannelDataAccessor.iterateChannelData(trigger).sorted().collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawElement(int elementIndex, int x, int y, int height, Tessellator tessellator,
|
||||
boolean isHovering) {
|
||||
if (elementIndex < 0 || elementIndex >= cache.size()) {
|
||||
return;
|
||||
}
|
||||
Entry<String, Integer> e = cache.get(elementIndex);
|
||||
if (e == null) return;
|
||||
if (elementIndex > 0) {
|
||||
// args x1, x2, y1, color
|
||||
drawHorizontalLine(minX + 1, maxX - 1, y - 2, 0xffaaaaaa);
|
||||
}
|
||||
int keyMaxWidth = KEY_MAX_WIDTH * 2;
|
||||
if (isHovering) {
|
||||
mc.renderEngine.bindTexture(new ResourceLocation("structurelib", "textures/gui/channels.png"));
|
||||
GL11.glColor4f(1, 1, 1, 1);
|
||||
drawTexturedModalRect(x + margin + 1, y + margin / 2 + 1, 251, 251, 5, 5);
|
||||
}
|
||||
fontRendererObj.drawString(trim(e.getKey(), keyMaxWidth - 9), x + margin + 8, y + margin / 2, 0xffffff);
|
||||
int valueX = x + margin + keyMaxWidth + margin;
|
||||
fontRendererObj.drawString(trim(e.getValue().toString(), maxX - valueX), valueX, y + margin / 2, 0xffffff);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawScreen(int mX, int mY, float partialTick) {
|
||||
super.drawScreen(mX, mY, partialTick);
|
||||
// args x1, y1, y2, color
|
||||
drawVerticalLine(minX + margin + margin / 2 + KEY_MAX_WIDTH * 2, minY + 1, maxY - 1, 0xffaaaaaa);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,468 @@
|
||||
package com.gtnewhorizon.structurelib.gui;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiButton;
|
||||
import net.minecraft.client.gui.ScaledResolution;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
|
||||
import org.lwjgl.input.Mouse;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
/**
|
||||
* Shamelessly stolen from EnderCore, with some amount of edit to fit in one file. Original code from CrazyPants.
|
||||
* <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
|
||||
* other widgets</li>
|
||||
* <li>scrollbar are now rendered within width</li>
|
||||
* <li>added prepareDrawElements</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author CrazyPants
|
||||
* @author glee8e
|
||||
*/
|
||||
public abstract class GuiScrollableList<T> {
|
||||
|
||||
protected final int slotHeight;
|
||||
private final Minecraft mc = Minecraft.getMinecraft();
|
||||
protected final int originX;
|
||||
protected final int originY;
|
||||
protected final int width;
|
||||
protected final int height;
|
||||
protected int minY;
|
||||
protected int maxY;
|
||||
protected int minX;
|
||||
protected int maxX;
|
||||
protected int mouseX;
|
||||
protected int mouseY;
|
||||
protected int selectedIndex = -1;
|
||||
protected int margin = 4;
|
||||
protected final List<ListSelectionListener<T>> listeners = new CopyOnWriteArrayList<>();
|
||||
private int scrollUpButtonID;
|
||||
private int scrollDownButtonID;
|
||||
private float initialClickY = -2.0F;
|
||||
private float scrollMultiplier;
|
||||
private float amountScrolled;
|
||||
private long lastClickedTime;
|
||||
|
||||
private boolean showSelectionBox = true;
|
||||
|
||||
public GuiScrollableList(int width, int height, int originX, int originY, int slotHeight) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.originX = originX;
|
||||
this.originY = originY;
|
||||
this.slotHeight = slotHeight;
|
||||
|
||||
minY = originY;
|
||||
maxY = minY + height;
|
||||
minX = originX;
|
||||
maxX = minX + width;
|
||||
}
|
||||
|
||||
public void onGuiInit(IGuiScreen gui) {
|
||||
minY = originY + gui.getGuiTop();
|
||||
maxY = minY + height;
|
||||
minX = originX + gui.getGuiLeft();
|
||||
maxX = minX + width;
|
||||
}
|
||||
|
||||
public void addSelectionListener(ListSelectionListener<T> listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeSelectionListener(ListSelectionListener<T> listener) {
|
||||
listeners.remove(listener);
|
||||
}
|
||||
|
||||
public T getSelectedElement() {
|
||||
return getElementAt(selectedIndex);
|
||||
}
|
||||
|
||||
public void setSelection(T selection) {
|
||||
setSelection(getIndexOf(selection));
|
||||
}
|
||||
|
||||
public void setSelection(int index) {
|
||||
if (index == selectedIndex) {
|
||||
return;
|
||||
}
|
||||
selectedIndex = index;
|
||||
for (ListSelectionListener<T> listener : listeners) {
|
||||
listener.selectionChanged(this, selectedIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public int getIndexOf(T element) {
|
||||
if (element == null) {
|
||||
return -1;
|
||||
}
|
||||
for (int i = 0; i < getNumElements(); i++) {
|
||||
if (element.equals(getElementAt(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public abstract T getElementAt(int index);
|
||||
|
||||
public abstract int getNumElements();
|
||||
|
||||
protected abstract void drawElement(int elementIndex, int x, int y, int height, Tessellator tessellator,
|
||||
boolean isHovering);
|
||||
|
||||
protected boolean elementClicked(int elementIndex, boolean doubleClick, int mXRelative, int mYRelative) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setShowSelectionBox(boolean val) {
|
||||
showSelectionBox = val;
|
||||
}
|
||||
|
||||
protected int getContentHeight() {
|
||||
return getNumElements() * slotHeight;
|
||||
}
|
||||
|
||||
public void setScrollButtonIds(int scrollUpButtonID, int scrollDownButtonID) {
|
||||
this.scrollUpButtonID = scrollUpButtonID;
|
||||
this.scrollDownButtonID = scrollDownButtonID;
|
||||
}
|
||||
|
||||
private void clampScrollToBounds() {
|
||||
int i = getContentOverhang();
|
||||
if (i < 0) {
|
||||
i *= -1;
|
||||
}
|
||||
if (amountScrolled < 0.0F) {
|
||||
amountScrolled = 0.0F;
|
||||
}
|
||||
if (amountScrolled > i) {
|
||||
amountScrolled = i;
|
||||
}
|
||||
}
|
||||
|
||||
public int getContentOverhang() {
|
||||
return getContentHeight() - (height - margin);
|
||||
}
|
||||
|
||||
public void actionPerformed(GuiButton b) {
|
||||
if (b.enabled) {
|
||||
if (b.id == scrollUpButtonID) {
|
||||
amountScrolled -= slotHeight * 2f / 3;
|
||||
initialClickY = -2.0F;
|
||||
clampScrollToBounds();
|
||||
} else if (b.id == scrollDownButtonID) {
|
||||
amountScrolled += slotHeight * 2f / 3;
|
||||
initialClickY = -2.0F;
|
||||
clampScrollToBounds();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
processMouseEvents();
|
||||
|
||||
if (getContentOverhang() > 0) maxX = minX + width - 6;
|
||||
else maxX = minX + width;
|
||||
|
||||
clampScrollToBounds();
|
||||
|
||||
GL11.glDisable(GL11.GL_LIGHTING);
|
||||
GL11.glDisable(GL11.GL_FOG);
|
||||
|
||||
ScaledResolution sr = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
|
||||
int sx = minX * sr.getScaleFactor();
|
||||
int sw = width * sr.getScaleFactor();
|
||||
int sy = mc.displayHeight - (maxY * sr.getScaleFactor());
|
||||
int sh = height * sr.getScaleFactor();
|
||||
GL11.glEnable(GL11.GL_SCISSOR_TEST);
|
||||
GL11.glScissor(sx, sy, sw, sh);
|
||||
|
||||
Tessellator tessellator = Tessellator.instance;
|
||||
drawContainerBackground(tessellator);
|
||||
|
||||
int amountScrolled = (int) this.amountScrolled;
|
||||
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;
|
||||
|
||||
if (elementY <= maxY && elementY + slotHeight >= minY) {
|
||||
|
||||
if (showSelectionBox && i == selectedIndex) {
|
||||
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
tessellator.startDrawingQuads();
|
||||
tessellator.setColorOpaque_I(8421504);
|
||||
tessellator.addVertexWithUV(minX, elementY + slotHeight + 2, 0.0D, 0.0D, 1.0D);
|
||||
tessellator.addVertexWithUV(maxX, elementY + slotHeight + 2, 0.0D, 1.0D, 1.0D);
|
||||
tessellator.addVertexWithUV(maxX, elementY - 2, 0.0D, 1.0D, 0.0D);
|
||||
tessellator.addVertexWithUV(minX, elementY - 2, 0.0D, 0.0D, 0.0D);
|
||||
tessellator.setColorOpaque_I(0);
|
||||
tessellator.addVertexWithUV(minX + 1, elementY + slotHeight + 1, 0.0D, 0.0D, 1.0D);
|
||||
tessellator.addVertexWithUV(maxX - 1, elementY + slotHeight + 1, 0.0D, 1.0D, 1.0D);
|
||||
tessellator.addVertexWithUV(maxX - 1, elementY - 1, 0.0D, 1.0D, 0.0D);
|
||||
tessellator.addVertexWithUV(minX + 1, elementY - 1, 0.0D, 0.0D, 0.0D);
|
||||
tessellator.draw();
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
}
|
||||
|
||||
boolean isHovering = hoveringPrecondition && elementY - 2 <= mY && mY < elementY + slotHeight + 2;
|
||||
drawElement(i, minX, elementY, slotHeight, tessellator, isHovering);
|
||||
}
|
||||
}
|
||||
|
||||
GL11.glDisable(GL11.GL_SCISSOR_TEST);
|
||||
|
||||
GL11.glDisable(GL11.GL_DEPTH_TEST);
|
||||
GL11.glEnable(GL11.GL_BLEND);
|
||||
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
GL11.glDisable(GL11.GL_ALPHA_TEST);
|
||||
GL11.glShadeModel(GL11.GL_SMOOTH);
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
|
||||
tessellator.startDrawingQuads();
|
||||
tessellator.setColorRGBA_I(0, 0);
|
||||
tessellator.addVertexWithUV(this.minX, this.minY + margin, 0.0D, 0.0D, 1.0D);
|
||||
tessellator.addVertexWithUV(this.maxX, this.minY + margin, 0.0D, 1.0D, 1.0D);
|
||||
tessellator.setColorRGBA_I(0, 255);
|
||||
tessellator.addVertexWithUV(this.maxX, this.minY, 0.0D, 1.0D, 0.0D);
|
||||
tessellator.addVertexWithUV(this.minX, this.minY, 0.0D, 0.0D, 0.0D);
|
||||
tessellator.draw();
|
||||
tessellator.startDrawingQuads();
|
||||
tessellator.setColorRGBA_I(0, 255);
|
||||
tessellator.addVertexWithUV(this.minX, this.maxY, 0.0D, 0.0D, 1.0D);
|
||||
tessellator.addVertexWithUV(this.maxX, this.maxY, 0.0D, 1.0D, 1.0D);
|
||||
tessellator.setColorRGBA_I(0, 0);
|
||||
tessellator.addVertexWithUV(this.maxX, this.maxY - margin, 0.0D, 1.0D, 0.0D);
|
||||
tessellator.addVertexWithUV(this.minX, this.maxY - margin, 0.0D, 0.0D, 0.0D);
|
||||
tessellator.draw();
|
||||
|
||||
renderScrollBar(tessellator);
|
||||
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GL11.glShadeModel(GL11.GL_FLAT);
|
||||
GL11.glEnable(GL11.GL_ALPHA_TEST);
|
||||
GL11.glDisable(GL11.GL_BLEND);
|
||||
GL11.glPopMatrix();
|
||||
}
|
||||
|
||||
protected void prepareDrawElements() {}
|
||||
|
||||
protected void renderScrollBar(Tessellator tessellator) {
|
||||
|
||||
int contentHeightOverBounds = getContentOverhang();
|
||||
if (contentHeightOverBounds > 0) {
|
||||
|
||||
int clear = (maxY - minY) * (maxY - minY) / getContentHeight();
|
||||
|
||||
if (clear < 32) {
|
||||
clear = 32;
|
||||
}
|
||||
|
||||
if (clear > maxY - minY - 8) {
|
||||
clear = maxY - minY - 8;
|
||||
}
|
||||
|
||||
int y = (int) this.amountScrolled * (maxY - minY - clear) / contentHeightOverBounds + minY;
|
||||
if (y < minY) {
|
||||
y = minY;
|
||||
}
|
||||
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
int scrollBarMinX = getScrollBarX();
|
||||
int scrollBarMaxX = scrollBarMinX + 6;
|
||||
tessellator.startDrawingQuads();
|
||||
|
||||
tessellator.setColorRGBA_I(0, 255);
|
||||
tessellator.addVertexWithUV(scrollBarMinX, maxY, 0.0D, 0.0D, 1.0D);
|
||||
tessellator.addVertexWithUV(scrollBarMaxX, maxY, 0.0D, 1.0D, 1.0D);
|
||||
tessellator.addVertexWithUV(scrollBarMaxX, minY, 0.0D, 1.0D, 0.0D);
|
||||
tessellator.addVertexWithUV(scrollBarMinX, minY, 0.0D, 0.0D, 0.0D);
|
||||
|
||||
tessellator.setColorRGBA_F(0.3f, 0.3f, 0.3f, 1);
|
||||
tessellator.addVertexWithUV(scrollBarMinX, (y + clear), 0.0D, 0.0D, 1.0D);
|
||||
tessellator.addVertexWithUV(scrollBarMaxX, (y + clear), 0.0D, 1.0D, 1.0D);
|
||||
tessellator.addVertexWithUV(scrollBarMaxX, y, 0.0D, 1.0D, 0.0D);
|
||||
tessellator.addVertexWithUV(scrollBarMinX, y, 0.0D, 0.0D, 0.0D);
|
||||
|
||||
tessellator.setColorRGBA_F(0.7f, 0.7f, 0.7f, 1);
|
||||
tessellator.addVertexWithUV(scrollBarMinX, (y + clear - 1), 0.0D, 0.0D, 1.0D);
|
||||
tessellator.addVertexWithUV((scrollBarMaxX - 1), (y + clear - 1), 0.0D, 1.0D, 1.0D);
|
||||
tessellator.addVertexWithUV((scrollBarMaxX - 1), y, 0.0D, 1.0D, 0.0D);
|
||||
tessellator.addVertexWithUV(scrollBarMinX, y, 0.0D, 0.0D, 0.0D);
|
||||
|
||||
tessellator.draw();
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
}
|
||||
}
|
||||
|
||||
private void processMouseEvents() {
|
||||
if (Mouse.isButtonDown(0)) {
|
||||
processMouseBown();
|
||||
} else {
|
||||
initialClickY = -1.0F;
|
||||
}
|
||||
}
|
||||
|
||||
public void handleDWheel(int mouseWheelDelta) {
|
||||
if (!Mouse.isButtonDown(0)) {
|
||||
if (mouseWheelDelta > 0) {
|
||||
amountScrolled -= slotHeight / 2f;
|
||||
} else /* if (mouseWheelDelta < 0) */ {
|
||||
amountScrolled += slotHeight / 2f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processMouseBown() {
|
||||
int contentHeightOverBounds;
|
||||
if (initialClickY == -1.0F) {
|
||||
|
||||
if (mouseY >= minY && mouseY <= maxY && mouseX >= minX && mouseX <= maxX + 6) {
|
||||
|
||||
boolean clickInBounds = true;
|
||||
|
||||
int y = mouseY - minY + (int) amountScrolled - margin;
|
||||
int mouseOverElement = y / slotHeight;
|
||||
|
||||
if (mouseX >= minX && mouseX <= maxX
|
||||
&& mouseOverElement >= 0
|
||||
&& y >= 0
|
||||
&& mouseOverElement < getNumElements()) {
|
||||
boolean doubleClick = mouseOverElement == selectedIndex
|
||||
&& Minecraft.getSystemTime() - lastClickedTime < 250L;
|
||||
if (elementClicked(
|
||||
mouseOverElement,
|
||||
doubleClick,
|
||||
mouseX - minX,
|
||||
y - slotHeight * mouseOverElement)) {
|
||||
setSelection(mouseOverElement);
|
||||
lastClickedTime = Minecraft.getSystemTime();
|
||||
}
|
||||
} else if (mouseX >= minX && mouseX <= maxX && y < 0) {
|
||||
clickInBounds = false;
|
||||
}
|
||||
|
||||
int scrollBarMinX = getScrollBarX();
|
||||
int scrollBarMaxX = scrollBarMinX + 6;
|
||||
if (mouseX >= scrollBarMinX && mouseX <= scrollBarMaxX) {
|
||||
|
||||
scrollMultiplier = -1.0F;
|
||||
contentHeightOverBounds = getContentOverhang();
|
||||
|
||||
if (contentHeightOverBounds < 1) {
|
||||
contentHeightOverBounds = 1;
|
||||
}
|
||||
|
||||
int empty = (int) ((float) ((maxY - minY) * (maxY - minY)) / (float) getContentHeight());
|
||||
if (empty < 32) {
|
||||
empty = 32;
|
||||
}
|
||||
if (empty > maxY - minY - 8) {
|
||||
empty = maxY - minY - 8;
|
||||
}
|
||||
scrollMultiplier /= (float) (maxY - minY - empty) / (float) contentHeightOverBounds;
|
||||
|
||||
} else {
|
||||
scrollMultiplier = 1.0F;
|
||||
}
|
||||
|
||||
if (clickInBounds) {
|
||||
initialClickY = mouseY;
|
||||
} else {
|
||||
initialClickY = -2.0F;
|
||||
}
|
||||
|
||||
} else {
|
||||
initialClickY = -2.0F;
|
||||
}
|
||||
|
||||
} else if (initialClickY >= 0.0F) {
|
||||
// Scrolling
|
||||
amountScrolled -= (mouseY - initialClickY) * scrollMultiplier;
|
||||
initialClickY = mouseY;
|
||||
}
|
||||
}
|
||||
|
||||
protected int getScrollBarX() {
|
||||
return maxX;
|
||||
}
|
||||
|
||||
protected void drawContainerBackground(Tessellator tess) {
|
||||
GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
tess.startDrawingQuads();
|
||||
tess.setColorOpaque_I(0x202020);
|
||||
tess.addVertex(minX, maxY, 0.0D);
|
||||
tess.addVertex(maxX, maxY, 0.0D);
|
||||
tess.addVertex(maxX, minY, 0.0D);
|
||||
tess.addVertex(minX, minY, 0.0D);
|
||||
tess.draw();
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
}
|
||||
|
||||
public interface IGuiScreen {
|
||||
|
||||
int getGuiLeft();
|
||||
|
||||
int getGuiTop();
|
||||
|
||||
int getXSize();
|
||||
|
||||
int getYSize();
|
||||
|
||||
void addButton(GuiButton button);
|
||||
|
||||
void removeButton(GuiButton button);
|
||||
|
||||
int getOverlayOffsetX();
|
||||
|
||||
void doActionPerformed(GuiButton but);
|
||||
}
|
||||
|
||||
public interface ListSelectionListener<T> {
|
||||
|
||||
void selectionChanged(GuiScrollableList<T> list, int selectedIndex);
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.gtnewhorizon.structurelib.item;
|
||||
import static net.minecraft.util.StatCollector.translateToLocal;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.creativetab.CreativeTabs;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
@@ -11,6 +12,7 @@ import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.EnumChatFormatting;
|
||||
|
||||
public class ItemBlockHint extends ItemBlock {
|
||||
|
||||
public ItemBlockHint(Block p_i45328_1_) {
|
||||
super(p_i45328_1_);
|
||||
setMaxDamage(0);
|
||||
@@ -19,10 +21,9 @@ public class ItemBlockHint extends ItemBlock {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void addInformation(ItemStack aStack, EntityPlayer aPlayer, List aList, boolean aF3_H) {
|
||||
aList.add(translateToLocal("structurelib.blockhint.desc.0")); // Helps while building
|
||||
switch (aStack.getItemDamage()) {
|
||||
public void addInformation(ItemStack stack, EntityPlayer player, List<String> tooltip, boolean adv) {
|
||||
tooltip.add(translateToLocal("structurelib.blockhint.desc.0")); // Helps while building
|
||||
switch (stack.getItemDamage()) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
@@ -35,33 +36,36 @@ public class ItemBlockHint extends ItemBlock {
|
||||
case 9:
|
||||
case 10:
|
||||
case 11:
|
||||
aList.add(EnumChatFormatting.AQUA.toString()
|
||||
+ EnumChatFormatting.BOLD
|
||||
+ translateToLocal("structurelib.blockhint.desc.1")); // Placeholder for a certain group.
|
||||
// Placeholder for a certain group.
|
||||
tooltip.add(
|
||||
EnumChatFormatting.AQUA.toString() + EnumChatFormatting.BOLD
|
||||
+ translateToLocal("structurelib.blockhint.desc.1"));
|
||||
break;
|
||||
case 12:
|
||||
aList.add(EnumChatFormatting.AQUA.toString()
|
||||
+ EnumChatFormatting.BOLD
|
||||
+ translateToLocal("structurelib.blockhint.desc.2")); // General placeholder.
|
||||
// General placeholder.
|
||||
tooltip.add(
|
||||
EnumChatFormatting.AQUA.toString() + EnumChatFormatting.BOLD
|
||||
+ translateToLocal("structurelib.blockhint.desc.2"));
|
||||
break;
|
||||
case 13:
|
||||
aList.add(EnumChatFormatting.AQUA.toString()
|
||||
+ EnumChatFormatting.BOLD
|
||||
+ translateToLocal("structurelib.blockhint.desc.3")); // Make sure it contains Air material.
|
||||
// Make sure it contains Air material.
|
||||
tooltip.add(
|
||||
EnumChatFormatting.AQUA.toString() + EnumChatFormatting.BOLD
|
||||
+ translateToLocal("structurelib.blockhint.desc.3"));
|
||||
break;
|
||||
case 14:
|
||||
aList.add(EnumChatFormatting.AQUA.toString()
|
||||
+ EnumChatFormatting.BOLD
|
||||
+ translateToLocal(
|
||||
"structurelib.blockhint.desc.4")); // Make sure it does not contain Air material.
|
||||
// Make sure it does not contain Air material.
|
||||
tooltip.add(
|
||||
EnumChatFormatting.AQUA.toString() + EnumChatFormatting.BOLD
|
||||
+ translateToLocal("structurelib.blockhint.desc.4"));
|
||||
break;
|
||||
case 15:
|
||||
aList.add(EnumChatFormatting.BLUE.toString()
|
||||
+ translateToLocal("structurelib.blockhint.desc.5")); // ERROR, what did u expect?
|
||||
// ERROR, what did u expect?
|
||||
tooltip.add(EnumChatFormatting.BLUE + translateToLocal("structurelib.blockhint.desc.5"));
|
||||
break;
|
||||
default: // WTF?
|
||||
aList.add("Damn son where did you get that!?");
|
||||
aList.add(EnumChatFormatting.BLUE.toString() + "From outer space... I guess...");
|
||||
tooltip.add("Damn son where did you get that!?");
|
||||
tooltip.add(EnumChatFormatting.BLUE + "From outer space... I guess...");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,18 +3,23 @@ package com.gtnewhorizon.structurelib.item;
|
||||
import static com.gtnewhorizon.structurelib.StructureLibAPI.MOD_ID;
|
||||
import static net.minecraft.util.EnumChatFormatting.BLUE;
|
||||
import static net.minecraft.util.StatCollector.translateToLocal;
|
||||
import static net.minecraft.util.StatCollector.translateToLocalFormatted;
|
||||
|
||||
import com.gtnewhorizon.structurelib.StructureLib;
|
||||
import com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility;
|
||||
import java.util.List;
|
||||
import net.minecraft.entity.Entity;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import org.lwjgl.input.Keyboard;
|
||||
|
||||
import com.gtnewhorizon.structurelib.StructureLib;
|
||||
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
|
||||
import com.gtnewhorizon.structurelib.alignment.constructable.ConstructableUtility;
|
||||
|
||||
public class ItemConstructableTrigger extends Item {
|
||||
|
||||
public ItemConstructableTrigger() {
|
||||
setUnlocalizedName("structurelib.constructableTrigger");
|
||||
setTextureName(MOD_ID + ":itemConstructableTrigger");
|
||||
@@ -22,51 +27,48 @@ public class ItemConstructableTrigger extends Item {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onItemUseFirst(
|
||||
ItemStack stack,
|
||||
EntityPlayer player,
|
||||
World world,
|
||||
int x,
|
||||
int y,
|
||||
int z,
|
||||
int side,
|
||||
float hitX,
|
||||
float hitY,
|
||||
float hitZ) {
|
||||
public ItemStack onItemRightClick(ItemStack stack, World world, EntityPlayer player) {
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onItemUseFirst(ItemStack stack, EntityPlayer player, World world, int x, int y, int z, int side,
|
||||
float hitX, float hitY, float hitZ) {
|
||||
return ConstructableUtility.handle(stack, player, world, x, y, z, side);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public void addInformation(ItemStack aStack, EntityPlayer ep, List aList, boolean boo) {
|
||||
aList.add(
|
||||
translateToLocal("item.structurelib.constructableTrigger.desc.0")); // Triggers Constructable Interface
|
||||
aList.add(BLUE
|
||||
+ translateToLocal(
|
||||
"item.structurelib.constructableTrigger.desc.1")); // Shows multiblock construction details,
|
||||
aList.add(BLUE
|
||||
+ translateToLocal(
|
||||
"item.structurelib.constructableTrigger.desc.2")); // just Use on a multiblock controller.
|
||||
aList.add(BLUE
|
||||
+ translateToLocal(
|
||||
"item.structurelib.constructableTrigger.desc.3")); // (Sneak Use in creative to build)
|
||||
aList.add(BLUE
|
||||
+ translateToLocal("item.structurelib.constructableTrigger.desc.4")); // Quantity affects tier/mode/type
|
||||
}
|
||||
public void addInformation(ItemStack stack, EntityPlayer player, List<String> tooltip, boolean adv) {
|
||||
if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT)) {
|
||||
tooltip.add(
|
||||
translateToLocalFormatted(
|
||||
"item.structurelib.constructableTrigger.desc.lshift.0",
|
||||
ChannelDataAccessor.countChannelData(stack)));
|
||||
ChannelDataAccessor.iterateChannelData(stack).map(e -> e.getKey() + ": " + e.getValue())
|
||||
.forEach(tooltip::add);
|
||||
} else {
|
||||
// Tooltip text:
|
||||
// Triggers Constructable Interface
|
||||
// Shows multiblock construction details,
|
||||
// just Use on a multiblock controller.
|
||||
// (Sneak Use in creative to build)
|
||||
// Quantity affects tier/mode/type
|
||||
|
||||
@Override
|
||||
public void onUpdate(
|
||||
ItemStack p_77663_1_, World p_77663_2_, Entity p_77663_3_, int p_77663_4_, boolean p_77663_5_) {
|
||||
// remove LastUse tags if it times out,
|
||||
// so it can stack with other trigger item stacks
|
||||
// don't remove it too soon though, to prevent chat spam
|
||||
// this might be perceived as a hack, but ok this is simple enough to implement
|
||||
if (p_77663_1_.hasTagCompound()
|
||||
&& p_77663_1_.getTagCompound().getLong("LastUse") + ConstructableUtility.COOLDOWN * 2
|
||||
< StructureLib.getOverworldTime()) {
|
||||
NBTTagCompound tag = p_77663_1_.getTagCompound();
|
||||
tag.removeTag("LastUse");
|
||||
if (tag.hasNoTags()) p_77663_1_.setTagCompound(null);
|
||||
tooltip.add(translateToLocal("item.structurelib.constructableTrigger.desc.0"));
|
||||
tooltip.add(BLUE + translateToLocal("item.structurelib.constructableTrigger.desc.1"));
|
||||
tooltip.add(BLUE + translateToLocal("item.structurelib.constructableTrigger.desc.2"));
|
||||
tooltip.add(BLUE + translateToLocal("item.structurelib.constructableTrigger.desc.3"));
|
||||
tooltip.add(BLUE + translateToLocal("item.structurelib.constructableTrigger.desc.4"));
|
||||
if (ChannelDataAccessor.hasSubChannel(stack))
|
||||
tooltip.add(translateToLocal("item.structurelib.constructableTrigger.desc.5"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,22 @@ package com.gtnewhorizon.structurelib.item;
|
||||
import static com.gtnewhorizon.structurelib.StructureLibAPI.MOD_ID;
|
||||
import static net.minecraft.util.StatCollector.translateToLocal;
|
||||
|
||||
import com.gtnewhorizon.structurelib.StructureLib;
|
||||
import com.gtnewhorizon.structurelib.alignment.AlignmentUtility;
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
import java.util.List;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.EnumChatFormatting;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import com.gtnewhorizon.structurelib.StructureLib;
|
||||
import com.gtnewhorizon.structurelib.alignment.AlignmentUtility;
|
||||
|
||||
import cpw.mods.fml.relauncher.Side;
|
||||
import cpw.mods.fml.relauncher.SideOnly;
|
||||
|
||||
public class ItemFrontRotationTool extends Item {
|
||||
|
||||
public ItemFrontRotationTool() {
|
||||
setMaxStackSize(1);
|
||||
setUnlocalizedName("structurelib.frontRotationTool");
|
||||
@@ -23,27 +27,21 @@ public class ItemFrontRotationTool extends Item {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onItemUseFirst(
|
||||
ItemStack stack,
|
||||
EntityPlayer player,
|
||||
World world,
|
||||
int x,
|
||||
int y,
|
||||
int z,
|
||||
int side,
|
||||
float hitX,
|
||||
float hitY,
|
||||
float hitZ) {
|
||||
public boolean onItemUseFirst(ItemStack stack, EntityPlayer player, World world, int x, int y, int z, int side,
|
||||
float hitX, float hitY, float hitZ) {
|
||||
return AlignmentUtility.handle(player, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SideOnly(Side.CLIENT)
|
||||
public void addInformation(ItemStack p_77624_1_, EntityPlayer p_77624_2_, List 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 front panel,
|
||||
aList.add(EnumChatFormatting.BLUE
|
||||
+ translateToLocal("item.structurelib.frontRotationTool.desc.2")); // which allows structure rotation.
|
||||
public void addInformation(ItemStack stack, EntityPlayer player, List<String> tooltip, boolean adv) {
|
||||
// Tooltip Text:
|
||||
// Triggers Front Rotation Interface
|
||||
// Rotates only the front panel,
|
||||
// which allows structure rotation.
|
||||
|
||||
tooltip.add(translateToLocal("item.structurelib.frontRotationTool.desc.0"));
|
||||
tooltip.add(EnumChatFormatting.BLUE + translateToLocal("item.structurelib.frontRotationTool.desc.1"));
|
||||
tooltip.add(EnumChatFormatting.BLUE + translateToLocal("item.structurelib.frontRotationTool.desc.2"));
|
||||
}
|
||||
}
|
||||
@@ -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 + "'}";
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package com.gtnewhorizon.structurelib.mixins.early;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import net.minecraft.client.gui.inventory.GuiContainer;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.inventory.Slot;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Invoker;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import com.gtnewhorizon.structurelib.ChannelPickHandler;
|
||||
import com.gtnewhorizon.structurelib.ChannelWipeConfirmation;
|
||||
import com.gtnewhorizon.structurelib.StructureLib;
|
||||
import com.gtnewhorizon.structurelib.item.ItemConstructableTrigger;
|
||||
|
||||
import codechicken.nei.NEIClientConfig;
|
||||
import codechicken.nei.guihook.GuiContainerManager;
|
||||
import cpw.mods.fml.common.Loader;
|
||||
|
||||
@Mixin(value = GuiContainer.class, priority = 100)
|
||||
public abstract class MixinGuiContainer_PickChannel {
|
||||
|
||||
@Inject(method = "mouseClicked(III)V", at = @At("HEAD"), cancellable = true)
|
||||
private void slib$onMouseClicked(int mouseX, int mouseY, int mouseButton, CallbackInfo ci) {
|
||||
if (mouseButton != 2) return;
|
||||
|
||||
EntityPlayer player = Minecraft.getMinecraft().thePlayer;
|
||||
if (player == null) return;
|
||||
|
||||
ItemStack cursorStack = player.inventory.getItemStack();
|
||||
if (cursorStack == null || !(cursorStack.getItem() instanceof ItemConstructableTrigger)) return;
|
||||
|
||||
// Sink middle click as soon as projector is picked up
|
||||
ci.cancel();
|
||||
|
||||
ItemStack hoveredStack = null;
|
||||
try {
|
||||
Slot slot = slib$invokeGetSlotAtPosition(mouseX, mouseY);
|
||||
if (slot != null) {
|
||||
hoveredStack = slot.getStack();
|
||||
}
|
||||
|
||||
// try NEI
|
||||
if (hoveredStack == null && Loader.isModLoaded("NotEnoughItems") && !NEIClientConfig.isHidden()) {
|
||||
hoveredStack = GuiContainerManager.getStackMouseOver((GuiContainer) (Object) this);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
StructureLib.LOGGER.error("Error while resolving hovered slot for channel pick", e);
|
||||
}
|
||||
|
||||
boolean isSneaking = GuiScreen.isShiftKeyDown();
|
||||
if (isSneaking && !ChannelWipeConfirmation.confirm(player)) return;
|
||||
|
||||
ChannelPickHandler.handleMiddleClick(player, cursorStack, hoveredStack, isSneaking);
|
||||
}
|
||||
|
||||
@Invoker("getSlotAtPosition")
|
||||
protected abstract Slot slib$invokeGetSlotAtPosition(int x, int y);
|
||||
}
|
||||
+44
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+55
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,23 @@
|
||||
package com.gtnewhorizon.structurelib.net;
|
||||
|
||||
import com.gtnewhorizon.structurelib.StructureLib;
|
||||
import com.gtnewhorizon.structurelib.alignment.IAlignment;
|
||||
import com.gtnewhorizon.structurelib.alignment.IAlignmentProvider;
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
|
||||
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;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.world.World;
|
||||
import net.minecraftforge.common.DimensionManager;
|
||||
|
||||
import com.gtnewhorizon.structurelib.StructureLib;
|
||||
import com.gtnewhorizon.structurelib.alignment.IAlignment;
|
||||
import com.gtnewhorizon.structurelib.alignment.IAlignmentProvider;
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
|
||||
|
||||
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 abstract class AlignmentMessage implements IMessage {
|
||||
|
||||
int mPosX;
|
||||
int mPosY;
|
||||
int mPosZ;
|
||||
@@ -66,6 +69,7 @@ public abstract class AlignmentMessage implements IMessage {
|
||||
}
|
||||
|
||||
public static class AlignmentQuery extends AlignmentMessage {
|
||||
|
||||
public AlignmentQuery() {}
|
||||
|
||||
public AlignmentQuery(IAlignmentProvider provider) {
|
||||
@@ -78,6 +82,7 @@ public abstract class AlignmentMessage implements IMessage {
|
||||
}
|
||||
|
||||
public static class AlignmentData extends AlignmentMessage {
|
||||
|
||||
public AlignmentData() {}
|
||||
|
||||
private AlignmentData(AlignmentQuery query) {
|
||||
@@ -98,11 +103,11 @@ public abstract class AlignmentMessage implements IMessage {
|
||||
}
|
||||
|
||||
public static class ClientHandler implements IMessageHandler<AlignmentData, IMessage> {
|
||||
|
||||
@Override
|
||||
public IMessage onMessage(AlignmentData pMessage, MessageContext pCtx) {
|
||||
if (StructureLib.getCurrentPlayer().worldObj.provider.dimensionId == pMessage.mPosD) {
|
||||
TileEntity te = StructureLib.getCurrentPlayer()
|
||||
.worldObj
|
||||
TileEntity te = StructureLib.getCurrentPlayer().worldObj
|
||||
.getTileEntity(pMessage.mPosX, pMessage.mPosY, pMessage.mPosZ);
|
||||
if (te instanceof IAlignmentProvider) {
|
||||
IAlignment alignment = ((IAlignmentProvider) te).getAlignment();
|
||||
@@ -116,6 +121,7 @@ public abstract class AlignmentMessage implements IMessage {
|
||||
}
|
||||
|
||||
public static class ServerHandler implements IMessageHandler<AlignmentQuery, AlignmentData> {
|
||||
|
||||
@Override
|
||||
public AlignmentData onMessage(AlignmentQuery pMessage, MessageContext pCtx) {
|
||||
World world = DimensionManager.getWorld(pMessage.mPosD);
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.gtnewhorizon.structurelib.net;
|
||||
|
||||
import static com.gtnewhorizon.structurelib.StructureLib.LOGGER;
|
||||
|
||||
import com.gtnewhorizon.structurelib.StructureLib;
|
||||
import com.gtnewhorizon.structurelib.StructureLibAPI;
|
||||
|
||||
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 ErrorHintParticleMessage implements IMessage {
|
||||
|
||||
private int x;
|
||||
private short y;
|
||||
private int z;
|
||||
|
||||
public ErrorHintParticleMessage() {}
|
||||
|
||||
public ErrorHintParticleMessage(int x, short y, int z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fromBytes(ByteBuf buf) {
|
||||
x = buf.readInt();
|
||||
y = buf.readShort();
|
||||
z = buf.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(ByteBuf buf) {
|
||||
buf.writeInt(x);
|
||||
buf.writeShort(y);
|
||||
buf.writeInt(z);
|
||||
}
|
||||
|
||||
public static class Handler implements IMessageHandler<ErrorHintParticleMessage, IMessage> {
|
||||
|
||||
@Override
|
||||
public IMessage onMessage(ErrorHintParticleMessage msg, MessageContext ctx) {
|
||||
boolean updateResult = StructureLibAPI.markHintParticleError(
|
||||
StructureLib.getCurrentPlayer(),
|
||||
StructureLib.getCurrentPlayer().worldObj,
|
||||
msg.x,
|
||||
msg.y,
|
||||
msg.z);
|
||||
if (StructureLibAPI.isDebugEnabled()) LOGGER.debug(
|
||||
"Server instructed to mark hint particle at ({}, {}, {}) error, result {}!",
|
||||
msg.x,
|
||||
msg.y,
|
||||
msg.z,
|
||||
updateResult);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.gtnewhorizon.structurelib.net;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import com.gtnewhorizon.structurelib.StructureLib;
|
||||
import com.gtnewhorizon.structurelib.alignment.constructable.ChannelDataAccessor;
|
||||
import com.gtnewhorizon.structurelib.item.ItemConstructableTrigger;
|
||||
|
||||
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 SetChannelDataMessage implements IMessage {
|
||||
|
||||
private final List<Map.Entry<String, Integer>> data = new ArrayList<>();
|
||||
private boolean onCursor;
|
||||
|
||||
public SetChannelDataMessage() {}
|
||||
|
||||
public SetChannelDataMessage(ItemStack trigger) {
|
||||
this(trigger, false);
|
||||
}
|
||||
|
||||
public SetChannelDataMessage(ItemStack trigger, final boolean onCursor) {
|
||||
this.onCursor = onCursor;
|
||||
ChannelDataAccessor.iterateChannelData(trigger).forEach(data::add);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fromBytes(ByteBuf buf) {
|
||||
onCursor = buf.readBoolean();
|
||||
data.clear();
|
||||
int size = ByteBufUtils.readVarShort(buf);
|
||||
for (int i = 0; i < size; i++) {
|
||||
data.add(Pair.of(ByteBufUtils.readUTF8String(buf), ByteBufUtils.readVarInt(buf, 4)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(ByteBuf buf) {
|
||||
buf.writeBoolean(onCursor);
|
||||
ByteBufUtils.writeVarShort(buf, data.size());
|
||||
for (Entry<String, Integer> e : data) {
|
||||
ByteBufUtils.writeUTF8String(buf, e.getKey());
|
||||
ByteBufUtils.writeVarInt(buf, e.getValue(), 4);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Handler implements IMessageHandler<SetChannelDataMessage, IMessage> {
|
||||
|
||||
@Override
|
||||
public IMessage onMessage(SetChannelDataMessage message, MessageContext ctx) {
|
||||
EntityPlayerMP player = ctx.getServerHandler().playerEntity;
|
||||
ItemStack target = message.onCursor ? player.inventory.getItemStack() : player.getHeldItem();
|
||||
if (target != null && target.getItem() instanceof ItemConstructableTrigger) {
|
||||
ChannelDataAccessor.wipeChannelData(target);
|
||||
for (Entry<String, Integer> e : message.data) {
|
||||
ChannelDataAccessor.setChannelData(target, e.getKey(), e.getValue());
|
||||
}
|
||||
// since this is a set all channel request from the client, we would assume client already know
|
||||
// what this would look like on the client, so no sync
|
||||
} else {
|
||||
StructureLib.LOGGER.warn(
|
||||
"{} trying to set channel data on {}, which is not a hologram projector!",
|
||||
player.getUniqueID(),
|
||||
target);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,14 @@ import static com.gtnewhorizon.structurelib.StructureLib.LOGGER;
|
||||
|
||||
import com.gtnewhorizon.structurelib.StructureLib;
|
||||
import com.gtnewhorizon.structurelib.StructureLibAPI;
|
||||
|
||||
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 UpdateHintParticleMessage implements IMessage {
|
||||
|
||||
private int x;
|
||||
private short y;
|
||||
private int z;
|
||||
@@ -53,6 +55,7 @@ public class UpdateHintParticleMessage implements IMessage {
|
||||
}
|
||||
|
||||
public static class Handler implements IMessageHandler<UpdateHintParticleMessage, IMessage> {
|
||||
|
||||
@Override
|
||||
public IMessage onMessage(UpdateHintParticleMessage msg, MessageContext ctx) {
|
||||
boolean updateResult = StructureLibAPI.updateHintParticleTint(
|
||||
@@ -61,16 +64,13 @@ public class UpdateHintParticleMessage implements IMessage {
|
||||
msg.x,
|
||||
msg.y,
|
||||
msg.z,
|
||||
new short[] {
|
||||
msg.r, msg.g, msg.b, msg.a,
|
||||
});
|
||||
if (StructureLib.DEBUG_MODE)
|
||||
LOGGER.debug(
|
||||
"Server instructed to update hint particle at ({}, {}, {}), result {}!",
|
||||
msg.x,
|
||||
msg.y,
|
||||
msg.z,
|
||||
updateResult);
|
||||
new short[] { msg.r, msg.g, msg.b, msg.a, });
|
||||
if (StructureLibAPI.isDebugEnabled()) LOGGER.debug(
|
||||
"Server instructed to update hint particle at ({}, {}, {}), result {}!",
|
||||
msg.x,
|
||||
msg.y,
|
||||
msg.z,
|
||||
updateResult);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.IChatComponent;
|
||||
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
|
||||
|
||||
/**
|
||||
* Represent the environment in which autoplace of a single element took place.
|
||||
*/
|
||||
public class AutoPlaceEnvironment {
|
||||
|
||||
private IItemSource source;
|
||||
private final EntityPlayer actor;
|
||||
private final Consumer<IChatComponent> chatter;
|
||||
private final IStructureDefinition<?> definition;
|
||||
private final String piece;
|
||||
private final ExtendedFacing facing;
|
||||
final int[] offsetABC;
|
||||
private final int[] baseOffsetABC;
|
||||
|
||||
public static AutoPlaceEnvironment fromLegacy(IItemSource source, EntityPlayer actor,
|
||||
Consumer<IChatComponent> chatter) {
|
||||
if (source instanceof WrappedIItemSource) {
|
||||
AutoPlaceEnvironment original = ((WrappedIItemSource) source).container;
|
||||
// feels like this is extremely likely to cause issues, but this does allow us to recover lost info.
|
||||
// TODO probably should remove this once all addons migrate over
|
||||
if (actor != original.actor || chatter != original.chatter) {
|
||||
AutoPlaceEnvironment newEnv = original;
|
||||
if (actor != original.actor) newEnv = newEnv.withActor(actor);
|
||||
if (chatter != original.chatter) newEnv = newEnv.withChatter(chatter);
|
||||
return newEnv;
|
||||
}
|
||||
return original;
|
||||
}
|
||||
return new AutoPlaceEnvironment(source, actor, chatter, null, null, null, null, null);
|
||||
}
|
||||
|
||||
AutoPlaceEnvironment(EntityPlayer actor, Consumer<IChatComponent> chatter, IStructureDefinition<?> definition,
|
||||
String piece, ExtendedFacing facing, int[] baseOffsetABC) {
|
||||
this.source = null;
|
||||
this.actor = actor;
|
||||
this.chatter = chatter;
|
||||
this.definition = definition;
|
||||
this.piece = piece;
|
||||
this.facing = facing;
|
||||
this.offsetABC = new int[3];
|
||||
this.baseOffsetABC = baseOffsetABC;
|
||||
}
|
||||
|
||||
AutoPlaceEnvironment(IItemSource source, EntityPlayer actor, Consumer<IChatComponent> chatter,
|
||||
IStructureDefinition<?> definition, String piece, ExtendedFacing facing, int[] offsetABC,
|
||||
int[] baseOffsetABC) {
|
||||
this.source = definition != null && !(source instanceof WrappedIItemSource)
|
||||
? new WrappedIItemSource(this, source)
|
||||
: source;
|
||||
this.actor = actor;
|
||||
this.chatter = chatter;
|
||||
this.definition = definition;
|
||||
this.piece = piece;
|
||||
this.facing = facing;
|
||||
this.offsetABC = offsetABC;
|
||||
this.baseOffsetABC = baseOffsetABC;
|
||||
}
|
||||
|
||||
protected AutoPlaceEnvironment(AutoPlaceEnvironment parent) {
|
||||
this(
|
||||
parent.getSource(),
|
||||
parent.getActor(),
|
||||
parent.getChatter(),
|
||||
parent.definition,
|
||||
parent.piece,
|
||||
parent.facing,
|
||||
parent.offsetABC,
|
||||
parent.baseOffsetABC);
|
||||
}
|
||||
|
||||
void setSource(IItemSource source) {
|
||||
this.source = definition != null && !(source instanceof WrappedIItemSource)
|
||||
? new WrappedIItemSource(this, source)
|
||||
: source;
|
||||
}
|
||||
|
||||
public APILevel getAPILevel() {
|
||||
return definition == null ? actor instanceof EntityPlayerMP ? APILevel.Legacy : APILevel.LegacyRelaxed
|
||||
: APILevel.V2;
|
||||
}
|
||||
|
||||
/**
|
||||
* From where survival autoplace will drain resources.
|
||||
*/
|
||||
public IItemSource getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* The initiator of actions. for very critical errors you can also just send the error messages here, bypassing any
|
||||
* filter that {@link #getChatter()} might have. You might want to use
|
||||
* {@link com.gtnewhorizon.structurelib.StructureLibAPI#addThrottledChat(Object, EntityPlayer, IChatComponent, short)}
|
||||
* to help reduce spam.
|
||||
*/
|
||||
public EntityPlayer getActor() {
|
||||
return actor;
|
||||
}
|
||||
|
||||
/**
|
||||
* send error messages here. Caller will choose an appropriate way to forward it to player if the other fallbacks
|
||||
* also fails.
|
||||
*/
|
||||
public Consumer<IChatComponent> getChatter() {
|
||||
return chatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if given location is contained within the current piece.
|
||||
*
|
||||
* @param offsetA offset in A direction <b>relative to current element</b>
|
||||
* @param offsetB offset in B direction <b>relative to current element</b>
|
||||
* @param offsetC offset in C direction <b>relative to current element</b>
|
||||
* @return true if contained, false otherwise.
|
||||
*/
|
||||
public boolean isContainedInPiece(int offsetA, int offsetB, int offsetC) {
|
||||
if (definition == null) return false;
|
||||
return definition.isContainedInStructure(
|
||||
piece,
|
||||
offsetABC[0] + offsetA + baseOffsetABC[0],
|
||||
offsetABC[1] + offsetB + baseOffsetABC[1],
|
||||
offsetABC[2] + offsetC + baseOffsetABC[2]);
|
||||
}
|
||||
|
||||
public ExtendedFacing getFacing() {
|
||||
return facing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new instance with source modified to given value.
|
||||
*
|
||||
* @param source new source
|
||||
* @return new instance
|
||||
*/
|
||||
public AutoPlaceEnvironment withSource(IItemSource source) {
|
||||
return new AutoPlaceEnvironment(source, actor, chatter, definition, piece, facing, offsetABC, baseOffsetABC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new instance with actor modified to given value.
|
||||
*
|
||||
* @param actor new actor
|
||||
* @return new instance
|
||||
*/
|
||||
public AutoPlaceEnvironment withActor(EntityPlayer actor) {
|
||||
return new AutoPlaceEnvironment(source, actor, chatter, definition, piece, facing, offsetABC, baseOffsetABC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new instance with chatter modified to given value.
|
||||
*
|
||||
* @param chatter new chatter
|
||||
* @return new instance
|
||||
*/
|
||||
public AutoPlaceEnvironment withChatter(Consumer<IChatComponent> chatter) {
|
||||
return new AutoPlaceEnvironment(source, actor, chatter, definition, piece, facing, offsetABC, baseOffsetABC);
|
||||
}
|
||||
|
||||
private static class WrappedIItemSource implements IItemSource {
|
||||
|
||||
final AutoPlaceEnvironment container;
|
||||
final IItemSource delegate;
|
||||
|
||||
public WrappedIItemSource(AutoPlaceEnvironment container, IItemSource delegate) {
|
||||
this.container = container;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nonnull
|
||||
public Map<ItemStack, Integer> take(Predicate<ItemStack> predicate, boolean simulate, int count) {
|
||||
return delegate.take(predicate, simulate, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack takeOne(Predicate<ItemStack> predicate, boolean simulate) {
|
||||
return delegate.takeOne(predicate, simulate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean takeAll(Predicate<ItemStack> predicate, boolean simulate, int count) {
|
||||
return delegate.takeAll(predicate, simulate, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean takeOne(ItemStack stack, boolean simulate) {
|
||||
return delegate.takeOne(stack, simulate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean takeAll(ItemStack stack, boolean simulate) {
|
||||
return delegate.takeAll(stack, simulate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the various API level an {@link AutoPlaceEnvironment} has implemented.
|
||||
* <p>
|
||||
* Enum constants are defined in chronological order.
|
||||
*/
|
||||
public enum APILevel {
|
||||
/**
|
||||
* Implements {@link #getChatter()}, {@link #getActor()} and {@link #getSource()}. {@link #getActor()} is
|
||||
* guaranteed to be an {@link net.minecraft.entity.player.EntityPlayerMP}
|
||||
*/
|
||||
Legacy,
|
||||
/**
|
||||
* Implements {@link #getChatter()}, {@link #getActor()} and {@link #getSource()}. {@link #getActor()} is
|
||||
* <b>NOT</b> guaranteed to be an {@link net.minecraft.entity.player.EntityPlayerMP}
|
||||
*/
|
||||
LegacyRelaxed,
|
||||
/**
|
||||
* Implements everything so far we have defined.
|
||||
*/
|
||||
V2,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
class ChatThrottleKey {
|
||||
|
||||
static class NoExplicitChannel {
|
||||
|
||||
private static final int hashOffset = NoExplicitChannel.class.hashCode();
|
||||
private final String subchannel;
|
||||
|
||||
public NoExplicitChannel(String subchannel) {
|
||||
this.subchannel = subchannel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof NoExplicitChannel that)) return false;
|
||||
|
||||
return subchannel.equals(that.subchannel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
// as this one will be placed within a map along with instances of different classes, it'd be best to add
|
||||
// an offset based off class type to avoid collision
|
||||
return subchannel.hashCode() + hashOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
|
||||
class DefaultSurvivalBuildEnvironment implements ISurvivalBuildEnvironment {
|
||||
|
||||
private final IItemSource source;
|
||||
private final EntityPlayer actor;
|
||||
|
||||
public DefaultSurvivalBuildEnvironment(IItemSource source, EntityPlayer actor) {
|
||||
this.source = source;
|
||||
this.actor = actor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IItemSource getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityPlayer getActor() {
|
||||
return actor;
|
||||
}
|
||||
}
|
||||
@@ -3,5 +3,6 @@ package com.gtnewhorizon.structurelib.structure;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public interface IBlockPosConsumer {
|
||||
|
||||
void consume(World world, int x, int y, int z);
|
||||
}
|
||||
@@ -4,9 +4,10 @@ import net.minecraft.block.Block;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public interface ICustomBlockSetting {
|
||||
|
||||
/**
|
||||
* Default block setting calls {@link World#setBlock(int x, int y, int z, Block block, int meta, int updateType)} like:
|
||||
* {@code world.setBlock(x,y,z,this/block,meta,2)} where updateType 2 means to update lighting and stuff
|
||||
* Default block setting calls {@link World#setBlock(int x, int y, int z, Block block, int meta, int updateType)}
|
||||
* like: {@code world.setBlock(x,y,z,this/block,meta,2)} where updateType 2 means to update lighting and stuff
|
||||
*
|
||||
* @param world world that should be affected
|
||||
* @param x x position to set
|
||||
|
||||
@@ -1,29 +1,61 @@
|
||||
package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.inventory.IInventory;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
import com.gtnewhorizon.structurelib.util.InventoryIterable;
|
||||
import com.gtnewhorizon.structurelib.util.InventoryUtility;
|
||||
import com.gtnewhorizon.structurelib.util.ItemStackPredicate;
|
||||
import com.gtnewhorizon.structurelib.util.ItemStackPredicate.NBTMode;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
import javax.annotation.Nonnull;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.inventory.IInventory;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
/**
|
||||
* Represent an item source. Take only, cannot be placed back.
|
||||
*/
|
||||
public interface IItemSource {
|
||||
|
||||
/**
|
||||
* We are probably not going to need to get a lot of items from this method
|
||||
* so I don't care about the overhead of storing all fetched items even if downstream code doesn't need it
|
||||
* Retrieve some amount of items from this item source matching given filter.
|
||||
* <p>
|
||||
* We probably don't need to get a lot of items from this method, so I don't care about the overhead of storing all
|
||||
* fetched items even if downstream code doesn't need it
|
||||
*
|
||||
* @param predicate filtering predicate. should return true for ItemStacks that should be added to result collection
|
||||
* @param simulate whether to actually commit the modification. true for dry run, false otherwise.
|
||||
* @param count how many items to extract.
|
||||
* @return A nonnull map reflecting the result of extraction. Note this map is NBT and metadata aware.
|
||||
*/
|
||||
@Nonnull
|
||||
Map<ItemStack, Integer> take(Predicate<ItemStack> predicate, boolean simulate, int count);
|
||||
|
||||
/**
|
||||
* Take exactly one item that matches the predicate.
|
||||
*
|
||||
* @param predicate filtering predicate. should return true for ItemStacks that should be added to result collection
|
||||
* @param simulate whether to actually commit the modification. true for dry run, false otherwise.
|
||||
* @return item stack, or null if none matches
|
||||
* @throws IllegalArgumentException if given predicate is null.
|
||||
*/
|
||||
default ItemStack takeOne(Predicate<ItemStack> predicate, boolean simulate) {
|
||||
if (predicate == null) throw new IllegalArgumentException();
|
||||
Map<ItemStack, Integer> take = take(predicate, simulate, 1);
|
||||
return take.isEmpty() ? null : take.keySet().iterator().next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Take exactly count amount of items matching predicate.
|
||||
*
|
||||
* @param predicate filtering predicate. should return true for ItemStacks that should be added to result collection
|
||||
* @param simulate whether to actually commit the modification. true for dry run, false otherwise.
|
||||
* @param count how many items to extract.
|
||||
* @return true if at least count amount of items can be/is taken.
|
||||
* @throws IllegalArgumentException if given predicate is null
|
||||
*/
|
||||
default boolean takeAll(Predicate<ItemStack> predicate, boolean simulate, int count) {
|
||||
// fast path for 1 item take requests
|
||||
if (count == 1) return takeOne(predicate, simulate) != null;
|
||||
@@ -33,24 +65,56 @@ public interface IItemSource {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take exactly one item. ItemStack given must have a stack size of 1.
|
||||
* <p>
|
||||
* This is meant to be an optimized version of {@link #takeOne(Predicate, boolean)}. Implementations that support
|
||||
* item storage indexed by ItemStack can use this to speed up calculation.
|
||||
*
|
||||
* @param stack what item to extract. The NBT tag of this ItemStack is siginifcant and will be considered during
|
||||
* matching. must have a stack size of 1.
|
||||
* @param simulate whether to actually commit the modification. true for dry run, false otherwise.
|
||||
* @return true if found. false otherwise
|
||||
* @throws IllegalArgumentException if given stack is invalid, or has a stack size other than 1.
|
||||
*/
|
||||
default boolean takeOne(ItemStack stack, boolean simulate) {
|
||||
if (stack == null || stack.getItem() == null || stack.stackSize != 1) throw new IllegalArgumentException();
|
||||
ItemStack took = takeOne(ItemStackPredicate.from(stack, NBTMode.EXACT), simulate);
|
||||
return took == null || took.stackSize <= 0;
|
||||
return took != null && took.stackSize > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take some item. Will not take item if it cannot find enough items to take.
|
||||
* <p>
|
||||
* This is meant to be an optimized version of {@link #takeOne(Predicate, boolean)}. Implementations that support
|
||||
* indexing off ItemStack can use this to speed up calculation.
|
||||
*
|
||||
* @param stack what item and how many to extract. The NBT tag of this ItemStack is siginifcant and will be
|
||||
* considered during matching
|
||||
* @param simulate whether to actually commit the modification. true for dry run, false otherwise.
|
||||
* @return true if enough item can be/has been extracted. false otherwise
|
||||
* @throws IllegalArgumentException if given stack is invalid
|
||||
*/
|
||||
default boolean takeAll(ItemStack stack, boolean simulate) {
|
||||
if (stack == null || stack.getItem() == null) throw new IllegalArgumentException();
|
||||
// fast path for 1 item take requests
|
||||
if (stack.stackSize == 1) return takeOne(stack, simulate);
|
||||
ItemStackPredicate predicate = ItemStackPredicate.from(stack, NBTMode.EXACT);
|
||||
Map<ItemStack, Integer> have = take(predicate, true, stack.stackSize);
|
||||
if (have.values().stream().mapToInt(Integer::intValue).sum() < stack.stackSize) return false;
|
||||
take(predicate, simulate, stack.stackSize);
|
||||
if (simulate) return true;
|
||||
take(predicate, false, stack.stackSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an IItemSource from inventories associated with given player.
|
||||
* <p>
|
||||
* This will be backed by {@link InventoryUtility}
|
||||
*/
|
||||
static IItemSource fromPlayer(EntityPlayerMP player) {
|
||||
return new IItemSource() {
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public Map<ItemStack, Integer> take(Predicate<ItemStack> p, boolean s, int c) {
|
||||
@@ -71,6 +135,11 @@ public interface IItemSource {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an IItemSource from given inventory
|
||||
* <p>
|
||||
* This will be backed by {@link InventoryUtility}
|
||||
*/
|
||||
static IItemSource fromInventory(IInventory inv) {
|
||||
return (p, s, c) -> InventoryUtility.takeFromInventory(new InventoryIterable<>(inv), p, s, c, true);
|
||||
}
|
||||
|
||||
@@ -3,5 +3,6 @@ package com.gtnewhorizon.structurelib.structure;
|
||||
import net.minecraft.block.Block;
|
||||
|
||||
public interface ISpecialItemBlock {
|
||||
|
||||
int getItemMetaFromBlockMeta(Block block, int blockMeta);
|
||||
}
|
||||
@@ -1,38 +1,94 @@
|
||||
package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
import static com.gtnewhorizon.structurelib.StructureLib.DEBUG_MODE;
|
||||
import static com.gtnewhorizon.structurelib.structure.IStructureWalker.ignoreBlockUnloaded;
|
||||
import static com.gtnewhorizon.structurelib.structure.IStructureWalker.skipBlockUnloaded;
|
||||
|
||||
import com.gtnewhorizon.structurelib.StructureLib;
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
|
||||
import java.util.function.Function;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.ItemStack;
|
||||
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;
|
||||
|
||||
/**
|
||||
* This is the structure definition of your multi. You will have one of these for each multi.
|
||||
*
|
||||
* <h2>Cache the instances of this class</h2>
|
||||
* <p>
|
||||
* You will construct this definition once per multi, either at class load or at first call to your variant of
|
||||
* {@code getStructureDefinition()}. You can use a {@link ClassValue ClassValue<IStructureDefinition<?>>} if you
|
||||
* have a template for subclass. You can also use {@link StructureUtility#defer(Function) defer()} or its overloads for
|
||||
* simpler cases, but it is slightly less performant as it will allocate new objects on each structure check, whereas
|
||||
* {@link ClassValue} will allocate once and cache it onwards.
|
||||
* <p>
|
||||
* <h2>Constructing</h2>
|
||||
* <p>
|
||||
* {@linkplain IStructureDefinition} is constructed using {@link IStructureDefinition#builder()}
|
||||
* <p>
|
||||
* Builder has two main methods:
|
||||
* <ul>
|
||||
* <li>{@link com.gtnewhorizon.structurelib.structure.StructureDefinition.Builder#addShape(String, String[][])
|
||||
* addShape()} is a 2d string array describing the layout of structure. You can optionally use
|
||||
* `StructureUtility#transpose()` to transpose the array to make the structure look nicer in source code.
|
||||
* <li>{@link com.gtnewhorizon.structurelib.structure.StructureDefinition.Builder#addElement(Character, IStructureElement)
|
||||
* addElement()}. It takes a char identifier (mapped to a char in any of
|
||||
* {@link com.gtnewhorizon.structurelib.structure.StructureDefinition.Builder#addShape(String, String[][]) addShape()}
|
||||
* calls) and a {@link IStructureElement}.
|
||||
* </ul>
|
||||
* <h2>Position A, B and C</h2>
|
||||
*
|
||||
* Imagine you stand in front of the controller, with controller facing towards you not rotated or flipped.
|
||||
* <ul>
|
||||
* <li>The "A" position would be the number of blocks on the left side of the controller, not counting controller
|
||||
* itself.</li>
|
||||
* <li>The "B" position would be the number of blocks on the top side of the controller, not counting controller
|
||||
* 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> {
|
||||
|
||||
/**
|
||||
* Used internally
|
||||
* <p>
|
||||
* Might, but is not required, to throw {@link java.util.NoSuchElementException} if the given structure piece is not found.
|
||||
* Might, but is not required, to throw {@link java.util.NoSuchElementException} if the given structure piece is not
|
||||
* found.
|
||||
*
|
||||
* @param name same name as for other methods here
|
||||
* @return the array of elements to process
|
||||
* @throws java.util.NoSuchElementException if the given structure piece is not found and the moon phase perfectly matches
|
||||
* @throws java.util.NoSuchElementException if the given structure piece is not found and the moon phase perfectly
|
||||
* matches
|
||||
*/
|
||||
IStructureElement<T>[] getStructureFor(String name);
|
||||
|
||||
default boolean check(
|
||||
T object,
|
||||
String piece,
|
||||
World world,
|
||||
ExtendedFacing extendedFacing,
|
||||
int basePositionX,
|
||||
int basePositionY,
|
||||
int basePositionZ,
|
||||
int basePositionA,
|
||||
int basePositionB,
|
||||
int basePositionC,
|
||||
boolean isContainedInStructure(String name, int offsetA, int offsetB, int offsetC);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param extendedFacing the current structure's orientation.
|
||||
* @param basePositionX X location of the structure
|
||||
* @param basePositionY Y location of the structure
|
||||
* @param basePositionZ Z location of the structure
|
||||
* @param basePositionA see class javadoc
|
||||
* @param basePositionB see class javadoc
|
||||
* @param basePositionC see class javadoc
|
||||
* @param forceCheckAllBlocks check all location even if chunk not currently loaded.
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
default boolean check(T object, String piece, World world, ExtendedFacing extendedFacing, int basePositionX,
|
||||
int basePositionY, int basePositionZ, int basePositionA, int basePositionB, int basePositionC,
|
||||
boolean forceCheckAllBlocks) {
|
||||
return iterate(
|
||||
object,
|
||||
@@ -50,17 +106,23 @@ public interface IStructureDefinition<T> {
|
||||
forceCheckAllBlocks);
|
||||
}
|
||||
|
||||
default boolean hints(
|
||||
T object,
|
||||
ItemStack trigger,
|
||||
String piece,
|
||||
World world,
|
||||
ExtendedFacing extendedFacing,
|
||||
int basePositionX,
|
||||
int basePositionY,
|
||||
int basePositionZ,
|
||||
int basePositionA,
|
||||
int basePositionB,
|
||||
/**
|
||||
* 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.
|
||||
* @param extendedFacing the current structure's orientation.
|
||||
* @param basePositionX X location of the structure
|
||||
* @param basePositionY Y location of the structure
|
||||
* @param basePositionZ Z location of the structure
|
||||
* @param basePositionA see class javadoc
|
||||
* @param basePositionB see class javadoc
|
||||
* @param basePositionC see class javadoc
|
||||
* @return true if successful, false otherwise
|
||||
*/
|
||||
default boolean hints(T object, ItemStack trigger, String piece, World world, ExtendedFacing extendedFacing,
|
||||
int basePositionX, int basePositionY, int basePositionZ, int basePositionA, int basePositionB,
|
||||
int basePositionC) {
|
||||
return iterate(
|
||||
object,
|
||||
@@ -78,17 +140,25 @@ public interface IStructureDefinition<T> {
|
||||
null);
|
||||
}
|
||||
|
||||
default boolean build(
|
||||
T object,
|
||||
ItemStack trigger,
|
||||
String piece,
|
||||
World world,
|
||||
ExtendedFacing extendedFacing,
|
||||
int basePositionX,
|
||||
int basePositionY,
|
||||
int basePositionZ,
|
||||
int basePositionA,
|
||||
int basePositionB,
|
||||
/**
|
||||
* Build the multi in creative mode on a best-effort basis. Does not guarantee the structure is correct after a
|
||||
* successful call.
|
||||
*
|
||||
* @param object context object. usually multiblock controller.
|
||||
* @param trigger The trigger item that contains channel data.
|
||||
* @param piece the structure piece's string identifier.
|
||||
* @param world the world object this check takes place in.
|
||||
* @param extendedFacing the current structure's orientation.
|
||||
* @param basePositionX X location of the structure
|
||||
* @param basePositionY Y location of the structure
|
||||
* @param basePositionZ Z location of the structure
|
||||
* @param basePositionA see class javadoc
|
||||
* @param basePositionB see class javadoc
|
||||
* @param basePositionC see class javadoc
|
||||
* @return true if sucessful, false otherwise
|
||||
*/
|
||||
default boolean build(T object, ItemStack trigger, String piece, World world, ExtendedFacing extendedFacing,
|
||||
int basePositionX, int basePositionY, int basePositionZ, int basePositionA, int basePositionB,
|
||||
int basePositionC) {
|
||||
return iterate(
|
||||
object,
|
||||
@@ -106,19 +176,30 @@ public interface IStructureDefinition<T> {
|
||||
null);
|
||||
}
|
||||
|
||||
default boolean buildOrHints(
|
||||
T object,
|
||||
ItemStack trigger,
|
||||
String piece,
|
||||
World world,
|
||||
ExtendedFacing extendedFacing,
|
||||
int basePositionX,
|
||||
int basePositionY,
|
||||
int basePositionZ,
|
||||
int basePositionA,
|
||||
int basePositionB,
|
||||
int basePositionC,
|
||||
boolean hintsOnly) {
|
||||
/**
|
||||
* Call {@link #build(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int)} if
|
||||
* {@code hintsOnly}, call
|
||||
* {@link #hints(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int)} otherwise.
|
||||
*
|
||||
* @param object context object. usually multiblock controller.
|
||||
* @param trigger The trigger item that contains channel data.
|
||||
* @param piece the structure piece's string identifier.
|
||||
* @param world the world object this check takes place in.
|
||||
* @param extendedFacing the current structure's orientation.
|
||||
* @param basePositionX X location of the structure
|
||||
* @param basePositionY Y location of the structure
|
||||
* @param basePositionZ Z location of the structure
|
||||
* @param basePositionA see class javadoc
|
||||
* @param basePositionB see class javadoc
|
||||
* @param basePositionC see class javadoc
|
||||
* @param hintsOnly whether to spawn hints or do creative build.
|
||||
* @return true if sucessful, false otherwise
|
||||
* @see #hints(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int)
|
||||
* @see #build(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int)
|
||||
*/
|
||||
default boolean buildOrHints(T object, ItemStack trigger, String piece, World world, ExtendedFacing extendedFacing,
|
||||
int basePositionX, int basePositionY, int basePositionZ, int basePositionA, int basePositionB,
|
||||
int basePositionC, boolean hintsOnly) {
|
||||
return iterate(
|
||||
object,
|
||||
trigger,
|
||||
@@ -136,34 +217,93 @@ public interface IStructureDefinition<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param elementBudget build up to this many elements
|
||||
* @param source from where to drain resource
|
||||
* @param actor source of action
|
||||
* @param check whether {@link IStructureElement#check(Object, World, int, int, int)} should be called if anything
|
||||
* would be placed. use with caution.
|
||||
* if in doubt, call {@link #check(Object, String, World, ExtendedFacing, int, int, int, int, int, int, boolean)}
|
||||
* after this call and set this parameter to false
|
||||
* Cause a survival build.
|
||||
*
|
||||
* @param object context object. usually multiblock controller.
|
||||
* @param trigger The trigger item that contains channel data.
|
||||
* @param piece the structure piece's string identifier.
|
||||
* @param world the world object this check takes place in.
|
||||
* @param extendedFacing the current structure's orientation.
|
||||
* @param basePositionX X location of the structure
|
||||
* @param basePositionY Y location of the structure
|
||||
* @param basePositionZ Z location of the structure
|
||||
* @param basePositionA see class javadoc
|
||||
* @param basePositionB see class javadoc
|
||||
* @param basePositionC see class javadoc
|
||||
* @param elementBudget build up to this many elements
|
||||
* @param source from where to drain resource
|
||||
* @param actor source of action
|
||||
* @param check whether {@link IStructureElement#check(Object, World, int, int, int)} should be called if
|
||||
* anything would be placed. use with caution. if in doubt, call
|
||||
* {@link #check(Object, String, World, ExtendedFacing, int, int, int, int, int, int, boolean)}
|
||||
* after this call and set this parameter to false
|
||||
* @return number of elements built, or -1 if structure done
|
||||
* @see #build(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int)
|
||||
*/
|
||||
default int survivalBuild(
|
||||
T object,
|
||||
ItemStack trigger,
|
||||
String piece,
|
||||
World world,
|
||||
ExtendedFacing extendedFacing,
|
||||
int basePositionX,
|
||||
int basePositionY,
|
||||
int basePositionZ,
|
||||
int basePositionA,
|
||||
int basePositionB,
|
||||
int basePositionC,
|
||||
int elementBudget,
|
||||
IItemSource source,
|
||||
EntityPlayerMP actor,
|
||||
boolean check) {
|
||||
SurvivalBuildStructureWalker<T> walker =
|
||||
new SurvivalBuildStructureWalker<>(object, trigger, source, actor, elementBudget, check);
|
||||
@Deprecated
|
||||
default int survivalBuild(T object, ItemStack trigger, String piece, World world, ExtendedFacing extendedFacing,
|
||||
int basePositionX, int basePositionY, int basePositionZ, int basePositionA, int basePositionB,
|
||||
int basePositionC, int elementBudget, IItemSource source, EntityPlayerMP actor, boolean check) {
|
||||
EntityPlayer realActor;
|
||||
if (actor == null) {
|
||||
realActor = ISurvivalConstructable.__get_player();
|
||||
if (realActor == null) throw new IllegalArgumentException();
|
||||
} else {
|
||||
realActor = actor;
|
||||
}
|
||||
return survivalBuild(
|
||||
object,
|
||||
trigger,
|
||||
piece,
|
||||
world,
|
||||
extendedFacing,
|
||||
basePositionX,
|
||||
basePositionY,
|
||||
basePositionZ,
|
||||
basePositionA,
|
||||
basePositionB,
|
||||
basePositionC,
|
||||
elementBudget,
|
||||
ISurvivalBuildEnvironment.create(source, realActor),
|
||||
check);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cause a survival build.
|
||||
*
|
||||
* @param object context object. usually multiblock controller.
|
||||
* @param trigger The trigger item that contains channel data.
|
||||
* @param piece the structure piece's string identifier.
|
||||
* @param world the world object this check takes place in.
|
||||
* @param extendedFacing the current structure's orientation.
|
||||
* @param basePositionX X location of the structure
|
||||
* @param basePositionY Y location of the structure
|
||||
* @param basePositionZ Z location of the structure
|
||||
* @param basePositionA see class javadoc
|
||||
* @param basePositionB see class javadoc
|
||||
* @param basePositionC see class javadoc
|
||||
* @param elementBudget build up to this many elements
|
||||
* @param env build environment.
|
||||
* @param check whether {@link IStructureElement#check(Object, World, int, int, int)} should be called if
|
||||
* anything would be placed. use with caution. if in doubt, call
|
||||
* {@link IStructureDefinition#check(Object, String, World, ExtendedFacing, int, int, int, int, int, int, boolean)}
|
||||
* after this call and set this parameter to false
|
||||
* @return number of elements built, or -1 if structure done
|
||||
* @see #build(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int)
|
||||
*/
|
||||
default int survivalBuild(T object, ItemStack trigger, String piece, World world, ExtendedFacing extendedFacing,
|
||||
int basePositionX, int basePositionY, int basePositionZ, int basePositionA, int basePositionB,
|
||||
int basePositionC, int elementBudget, ISurvivalBuildEnvironment env, boolean check) {
|
||||
SurvivalBuildStructureWalker<T> walker = new SurvivalBuildStructureWalker<>(
|
||||
object,
|
||||
trigger,
|
||||
elementBudget,
|
||||
env,
|
||||
this,
|
||||
piece,
|
||||
extendedFacing,
|
||||
new int[] { basePositionA, basePositionB, basePositionC },
|
||||
check);
|
||||
StructureUtility.iterateV2(
|
||||
getStructureFor(piece),
|
||||
world,
|
||||
@@ -179,21 +319,47 @@ public interface IStructureDefinition<T> {
|
||||
return walker.getBuilt();
|
||||
}
|
||||
|
||||
static <T> boolean iterate(
|
||||
T object,
|
||||
ItemStack trigger,
|
||||
IStructureElement<T>[] elements,
|
||||
World world,
|
||||
ExtendedFacing extendedFacing,
|
||||
int basePositionX,
|
||||
int basePositionY,
|
||||
int basePositionZ,
|
||||
int basePositionA,
|
||||
int basePositionB,
|
||||
int basePositionC,
|
||||
boolean hintsOnly,
|
||||
Boolean checkBlocksIfNotNullForceCheckAllIfTrue) {
|
||||
if (world.isRemote ^ hintsOnly) {
|
||||
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
|
||||
* @param world the world object this check takes place in.
|
||||
* @param extendedFacing the current structure's orientation.
|
||||
* @param basePositionX X location of the structure
|
||||
* @param basePositionY Y location of the structure
|
||||
* @param basePositionZ Z location of the structure
|
||||
* @param basePositionA see class javadoc
|
||||
* @param basePositionB see class javadoc
|
||||
* @param basePositionC see class javadoc
|
||||
* @param hintsOnly whether to spawn hints or do creative build. no effect if
|
||||
* {@code checkBlocksIfNotNullForceCheckAllIfTrue} is not null.
|
||||
* @param checkBlocksIfNotNullForceCheckAllIfTrue creative build or spawn hints if null. Force check not loaded
|
||||
* chunk if true. Check only loaded chunk is false.
|
||||
* @return true if iteration completed successfully.
|
||||
* @param <T> type of context object.
|
||||
*/
|
||||
static <T> boolean iterate(T object, ItemStack trigger, IStructureElement<T>[] elements, World world,
|
||||
ExtendedFacing extendedFacing, int basePositionX, int basePositionY, int basePositionZ, int basePositionA,
|
||||
int basePositionB, int basePositionC, boolean hintsOnly, Boolean checkBlocksIfNotNullForceCheckAllIfTrue) {
|
||||
if (!world.isRemote && hintsOnly) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -210,7 +376,7 @@ public interface IStructureDefinition<T> {
|
||||
basePositionA,
|
||||
basePositionB,
|
||||
basePositionC,
|
||||
(e, w, x, y, z) -> e.check(object, w, x, y, z),
|
||||
(e, w, x, y, z, a, b, c) -> e.check(object, w, x, y, z),
|
||||
"check");
|
||||
} else {
|
||||
success = StructureUtility.iterateV2(
|
||||
@@ -223,12 +389,12 @@ public interface IStructureDefinition<T> {
|
||||
basePositionA,
|
||||
basePositionB,
|
||||
basePositionC,
|
||||
skipBlockUnloaded((e, w, x, y, z) -> e.check(object, w, x, y, z)),
|
||||
skipBlockUnloaded((e, w, x, y, z, a, b, c) -> e.check(object, w, x, y, z)),
|
||||
"check force");
|
||||
}
|
||||
if (DEBUG_MODE && success) {
|
||||
StructureLib.LOGGER.info(
|
||||
"Multi [" + basePositionX + ", " + basePositionY + ", " + basePositionZ + "] pass");
|
||||
if (StructureLibAPI.isDebugEnabled() && success) {
|
||||
StructureLib.LOGGER
|
||||
.info("Multi [" + basePositionX + ", " + basePositionY + ", " + basePositionZ + "] pass");
|
||||
}
|
||||
return success;
|
||||
} else {
|
||||
@@ -243,8 +409,12 @@ public interface IStructureDefinition<T> {
|
||||
basePositionA,
|
||||
basePositionB,
|
||||
basePositionC,
|
||||
ignoreBlockUnloaded((e, w, x, y, z) -> {
|
||||
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");
|
||||
@@ -259,7 +429,7 @@ public interface IStructureDefinition<T> {
|
||||
basePositionA,
|
||||
basePositionB,
|
||||
basePositionC,
|
||||
ignoreBlockUnloaded((e, w, x, y, z) -> {
|
||||
ignoreBlockUnloaded((e, w, x, y, z, a, b, c) -> {
|
||||
e.placeBlock(object, world, x, y, z, trigger);
|
||||
return true;
|
||||
}),
|
||||
@@ -269,6 +439,11 @@ public interface IStructureDefinition<T> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance of builder.
|
||||
*
|
||||
* @param <T> type of context object
|
||||
*/
|
||||
static <T> StructureDefinition.Builder<T> builder() {
|
||||
return StructureDefinition.builder();
|
||||
}
|
||||
|
||||
@@ -1,50 +1,212 @@
|
||||
package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
import static com.gtnewhorizon.structurelib.StructureLib.DEBUG_MODE;
|
||||
import static com.gtnewhorizon.structurelib.StructureLib.LOGGER;
|
||||
import static com.gtnewhorizon.structurelib.StructureLib.PANIC_MODE;
|
||||
|
||||
import com.gtnewhorizon.structurelib.StructureLibAPI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.Item;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.util.IChatComponent;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import com.gtnewhorizon.structurelib.StructureLibAPI;
|
||||
import com.gtnewhorizon.structurelib.util.ItemStackPredicate;
|
||||
import com.gtnewhorizon.structurelib.util.ItemStackPredicate.NBTMode;
|
||||
|
||||
/**
|
||||
* Use StructureUtility to instantiate
|
||||
* Use StructureUtility to instantiate. These are the building blocks for your {@link IStructureDefinition}. It
|
||||
* represents what a particular element can be and how this particular element should be autoplaced.
|
||||
*/
|
||||
public interface IStructureElement<T> {
|
||||
|
||||
boolean check(T t, World world, int x, int y, int z);
|
||||
|
||||
/**
|
||||
* Pure (stateless and side effect free) function to test if current block could be valid. Used to give user hints
|
||||
* about incorrectly placed blocks given a controller and trigger item. If couldBeValid(...) == false for a set of
|
||||
* channels, with those same tiers, check(...) == false. Not required, defaults to true which is always safe but
|
||||
* won't give the user error hints.
|
||||
*/
|
||||
default boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a human-readable description of what block(s) this element accepts. Used for diagnostic messages when
|
||||
* structure checks fail (e.g. "expected Input Bus at (1, 2, 3)").
|
||||
* <p>
|
||||
* Unlike {@link #getBlocksToPlace}, which returns the full set of concrete block variants for autoplacing (e.g.
|
||||
* every tier of Input Bus), this method returns <b>category-level</b> descriptions suitable for display to the
|
||||
* player (e.g. just "Input Bus"). It also requires no parameters, no world, trigger item, or environment, making it
|
||||
* usable in contexts where those are unavailable.
|
||||
* <p>
|
||||
* Implementations should return <b>lang keys</b> (e.g. {@code "tile.blockGlass.name"}) rather than pre-translated
|
||||
* display names. The caller is responsible for translating these keys on the client side via
|
||||
* {@code StatCollector.translateToLocal()}. If a key has no matching translation, {@code translateToLocal} returns
|
||||
* the input string unchanged, so pre-translated fallback strings also work.
|
||||
*
|
||||
* @param context the context object (e.g. the multiblock controller), used to determine state-dependent
|
||||
* descriptions
|
||||
* @return a list of accepted block descriptions as lang keys (or display names as fallback), or null if no
|
||||
* description is available
|
||||
*/
|
||||
@Nullable
|
||||
default List<String> getDescription(T context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger);
|
||||
|
||||
boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger);
|
||||
|
||||
/**
|
||||
* Try place the block by taking resource from given IItemSource.
|
||||
* <p>
|
||||
* You might want to use
|
||||
* {@link StructureUtility#survivalPlaceBlock(Block, int, World, int, int, int, IItemSource, net.minecraft.entity.player.EntityPlayer)}
|
||||
* or its overloads to implement this.
|
||||
*
|
||||
* @param trigger trigger item
|
||||
* @param s drain resources from this place
|
||||
* @param actor source of action.
|
||||
* @param chatter
|
||||
* @param actor source of action. for very critical errors you can also just send the error messages here,
|
||||
* bypassing any filter that chatter might have.
|
||||
* @param chatter send error messages here. Caller will choose an appropriate way to forward it to player if the
|
||||
* other fallbacks also fails.
|
||||
* @deprecated caller should call the non deprecated overload. implementor for reusable {@link IStructureElement}
|
||||
* should still implement this. implementor for private implementations can freely ignore this as long
|
||||
* as you know the caller will not call this overload.
|
||||
*/
|
||||
default PlaceResult survivalPlaceBlock(
|
||||
T t,
|
||||
World world,
|
||||
int x,
|
||||
int y,
|
||||
int z,
|
||||
ItemStack trigger,
|
||||
IItemSource s,
|
||||
EntityPlayerMP actor,
|
||||
Consumer<IChatComponent> chatter) {
|
||||
@Deprecated
|
||||
default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s,
|
||||
EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
|
||||
if (PANIC_MODE) throw new RuntimeException("Panic Tripwire hit");
|
||||
if (DEBUG_MODE)
|
||||
LOGGER.error(
|
||||
"Default implementation of survivalPlaceBlock hit! Things aren't going to work well! IStructureElement class: {}",
|
||||
getClass().getName());
|
||||
if (StructureLibAPI.isDebugEnabled()) LOGGER.error(
|
||||
"Default implementation of survivalPlaceBlock hit! Things aren't going to work well! IStructureElement class: {}",
|
||||
getClass().getName());
|
||||
if (!StructureLibAPI.isBlockTriviallyReplaceable(world, x, y, z, actor)) return PlaceResult.REJECT;
|
||||
return PlaceResult.SKIP;
|
||||
return PlaceResult.REJECT_CONTINUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simplified version of survivalPlaceBlock. Return null if not implemented.
|
||||
* <p>
|
||||
* <b>BY OVERRIDING THIS TO NULL, YOU ACKNOWLEDGE THAT THE {@link #check(Object, World, int, int, int)} OF THIS
|
||||
* CLASS IS SIDE EFFECT FREE AND CAN HAVE ITS CHECK CALLED WITHOUT BREAKING ANYTHING. OTHERWISE OVERRIDE THE
|
||||
* {@link #survivalPlaceBlock(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)} TO PROVIDE A
|
||||
* SIDEEFFECT FREE {@link PlaceResult#SKIP}.</b>
|
||||
* <p>
|
||||
* It should be noticed that this filter is only advisory and the actual
|
||||
* {@link #survivalPlaceBlock(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)} or
|
||||
* {@link #check(Object, World, int, int, int)} are free to reject/accept/place blocks not contained in this list,
|
||||
* e.g. when the element can be placed anywhere in the structure, but no more than 3 overall; when the structure
|
||||
* element cannot know all blocks it will accept at the time of calling; when this is backed by a legacy
|
||||
* {@link IStructureElement} that just didn't implement this API.
|
||||
*
|
||||
* @param trigger trigger item
|
||||
* @param env autoplacing environment
|
||||
* @return null if not implemented, otherwise an instance describing what kind of blocks will be placed by
|
||||
* {@link #survivalPlaceBlock(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)}
|
||||
*/
|
||||
@Nullable
|
||||
default BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
|
||||
AutoPlaceEnvironment env) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try place the block by taking resource from given {@link IItemSource}.
|
||||
* <p>
|
||||
* You might want to use
|
||||
* {@link StructureUtility#survivalPlaceBlock(Block, int, World, int, int, int, IItemSource, net.minecraft.entity.player.EntityPlayer)}
|
||||
* or its overloads to implement this.
|
||||
* <p>
|
||||
* The default implementation provided will
|
||||
* <ol>
|
||||
* <li>Get {@link BlocksToPlace} via
|
||||
* {@link #getBlocksToPlace(Object, World, int, int, int, ItemStack, AutoPlaceEnvironment)}. If not null
|
||||
* <ol>
|
||||
* <li>call {@link #check(Object, World, int, int, int)}. If this returns {@code true}, {@link PlaceResult#SKIP}
|
||||
* will be returned without further action.</li>
|
||||
* <li>Use the {@link BlocksToPlace} retrieved earlier and passed in {@link IItemSource} to determine an item to
|
||||
* place</li>
|
||||
* <li>Hand over control to
|
||||
* {@link StructureUtility#survivalPlaceBlock(ItemStack, NBTMode, NBTTagCompound, boolean, World, int, int, int, IItemSource, EntityPlayer, Consumer)}</li>
|
||||
* </ol>
|
||||
* </li>
|
||||
* <li>Call legacy
|
||||
* {@link #survivalPlaceBlock(Object, World, int, int, int, ItemStack, IItemSource, EntityPlayerMP, Consumer)} if
|
||||
* {@code env.getActor()} contains an {@link EntityPlayerMP}</li>
|
||||
* <li>Emit warning under debug mode (or throw exception under panic mode), then return
|
||||
* {@link PlaceResult#SKIP}</li>
|
||||
* </ol>
|
||||
* It should be noticed that the default implementation is likely to cause unexpected issues if the
|
||||
* {@link #check(Object, World, int, int, int)} is not side effect free, or is not idempotent. E.g. the
|
||||
* {@link #check(Object, World, int, int, int)} will add current coord to a {@link java.util.List}. As this method
|
||||
* might be invoked at a location that is already accepted, this will cause the machine to have multiple of this
|
||||
* coord in its list.
|
||||
*
|
||||
* @param trigger trigger item
|
||||
* @param env autoplacing environment
|
||||
*/
|
||||
default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
|
||||
AutoPlaceEnvironment env) {
|
||||
BlocksToPlace e = getBlocksToPlace(t, world, x, y, z, trigger, env);
|
||||
IItemSource source = env.getSource();
|
||||
EntityPlayer actor = env.getActor();
|
||||
Consumer<IChatComponent> chatter = env.getChatter();
|
||||
if (e != null) {
|
||||
if (check(t, world, x, y, z)) return PlaceResult.SKIP;
|
||||
if (e.getStacks() == null) {
|
||||
ItemStack taken = source.takeOne(e.getPredicate(), true);
|
||||
return StructureUtility.survivalPlaceBlock(
|
||||
taken,
|
||||
NBTMode.EXACT,
|
||||
taken.stackTagCompound,
|
||||
true,
|
||||
world,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
source,
|
||||
actor,
|
||||
chatter);
|
||||
}
|
||||
for (ItemStack stack : e.getStacks()) {
|
||||
if (!source.takeOne(stack, true)) continue;
|
||||
return StructureUtility.survivalPlaceBlock(
|
||||
stack,
|
||||
NBTMode.EXACT,
|
||||
stack.stackTagCompound,
|
||||
true,
|
||||
world,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
source,
|
||||
actor,
|
||||
chatter);
|
||||
}
|
||||
return PlaceResult.REJECT;
|
||||
}
|
||||
if (actor instanceof EntityPlayerMP)
|
||||
return survivalPlaceBlock(t, world, x, y, z, trigger, source, (EntityPlayerMP) actor, chatter);
|
||||
if (PANIC_MODE) throw new RuntimeException("Panic Tripwire hit");
|
||||
if (StructureLibAPI.isDebugEnabled()) LOGGER.info(
|
||||
"Fallback shim code of survivalPlaceBlock hit! Things aren't going to work well! IStructureElement class: {}",
|
||||
getClass().getName());
|
||||
return PlaceResult.REJECT_CONTINUE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,10 +220,21 @@ public interface IStructureElement<T> {
|
||||
return IStructureElement.this.check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return IStructureElement.this.couldBeValid(t, world, x, y, z, trigger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return IStructureElement.this.spawnHint(t, world, x, y, z, trigger);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> getDescription(T context) {
|
||||
return IStructureElement.this.getDescription(context);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -95,17 +268,24 @@ public interface IStructureElement<T> {
|
||||
|
||||
enum PlaceResult {
|
||||
/**
|
||||
* This element either exists already, or does not yet have an implementation for survivalPlaceBlock, or
|
||||
* some other unforeseen situations.
|
||||
* The element's check passed, the position is already valid. No placement is needed. In the walker: continue
|
||||
* iteration (no error, not counted as placed). In ofChain: short-circuits, remaining alternatives are skipped.
|
||||
*/
|
||||
SKIP,
|
||||
/**
|
||||
* This element's space is occupied by other stuff and require player attention, or player missing required resource,
|
||||
* or the block cannot be placed due to obscure mechanisms, or some other unforeseen situations.
|
||||
* The element cannot handle this position (occupied, missing resources, etc.). In the walker: stop iteration
|
||||
* with error particles. In ofChain: try next alternative.
|
||||
*/
|
||||
REJECT,
|
||||
/**
|
||||
* Autoplace cannot proceed within this tick. To proceed further would require stopping the placement and wait for next round.
|
||||
* This element has no survivalPlaceBlock implementation (default fallback), or otherwise cannot place but is
|
||||
* not an error condition. In the walker: continue iteration silently (like SKIP). In ofChain: try next
|
||||
* alternative (like REJECT), so a better-suited element can handle it.
|
||||
*/
|
||||
REJECT_CONTINUE,
|
||||
/**
|
||||
* Autoplace cannot proceed within this tick. To proceed further would require stopping the placement and wait
|
||||
* for next round.
|
||||
*/
|
||||
STOP,
|
||||
/**
|
||||
@@ -114,9 +294,88 @@ public interface IStructureElement<T> {
|
||||
ACCEPT,
|
||||
/**
|
||||
* Combination of ACCEPT and STOP.
|
||||
*
|
||||
* <p>
|
||||
* Element placed successfully. To proceed further would require stopping the placement and wait for next round.
|
||||
*/
|
||||
ACCEPT_STOP;
|
||||
}
|
||||
|
||||
final class BlocksToPlace {
|
||||
|
||||
public static final BlocksToPlace errored = createEmpty();
|
||||
private final Predicate<ItemStack> predicate;
|
||||
private final Iterable<ItemStack> stacks;
|
||||
|
||||
public static BlocksToPlace createEmpty() {
|
||||
return new BlocksToPlace(s -> false, Collections.emptyList());
|
||||
}
|
||||
|
||||
public static BlocksToPlace create(ItemStack... stacks) {
|
||||
return create(Arrays.asList(stacks));
|
||||
}
|
||||
|
||||
public static BlocksToPlace create(Iterable<ItemStack> stacks) {
|
||||
Predicate<ItemStack> predicate = null;
|
||||
|
||||
for (ItemStack stack : stacks) {
|
||||
ItemStackPredicate p = ItemStackPredicate.from(stack, NBTMode.EXACT);
|
||||
if (predicate == null) predicate = p;
|
||||
else predicate = predicate.or(p);
|
||||
}
|
||||
|
||||
return new BlocksToPlace(predicate, stacks);
|
||||
}
|
||||
|
||||
public static BlocksToPlace create(Block block, int meta) {
|
||||
Item itemBlock = Item.getItemFromBlock(block);
|
||||
if (itemBlock instanceof ISpecialItemBlock) {
|
||||
meta = ((ISpecialItemBlock) itemBlock).getItemMetaFromBlockMeta(block, meta);
|
||||
}
|
||||
return create(itemBlock, meta);
|
||||
}
|
||||
|
||||
public static BlocksToPlace create(Item item, int meta) {
|
||||
return new BlocksToPlace(
|
||||
ItemStackPredicate.from(item).setMeta(meta),
|
||||
Collections.singletonList(new ItemStack(item, 1, meta)));
|
||||
}
|
||||
|
||||
public static BlocksToPlace create(ItemStack itemStack) {
|
||||
return new BlocksToPlace(ItemStackPredicate.from(itemStack), Collections.singletonList(itemStack));
|
||||
}
|
||||
|
||||
public static BlocksToPlace create(Predicate<ItemStack> predicate) {
|
||||
return new BlocksToPlace(predicate, null);
|
||||
}
|
||||
|
||||
BlocksToPlace(Predicate<ItemStack> predicate, Iterable<ItemStack> stacks) {
|
||||
this.predicate = predicate;
|
||||
this.stacks = stacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the blocks to place filter. This is usually slower as it requires walking through inventories one by one.
|
||||
* <p>
|
||||
* Suitable for use with {@link IItemSource#takeOne(Predicate, boolean)}
|
||||
*
|
||||
* @return a predicate. never null.
|
||||
*/
|
||||
@Nonnull
|
||||
public Predicate<ItemStack> getPredicate() {
|
||||
return predicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ItemStacks that this one is known to accept. Can be null if this info is not available at the time
|
||||
* this instance is constructed.
|
||||
* <p>
|
||||
* Suitable for use with {@link IItemSource#takeOne(ItemStack, boolean)}.
|
||||
* <p>
|
||||
* This should be equivalent to the predicate returned from {@link #getPredicate()}
|
||||
*/
|
||||
@Nullable
|
||||
public Iterable<ItemStack> getStacks() {
|
||||
return stacks;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,26 @@
|
||||
package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.IChatComponent;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
|
||||
/**
|
||||
* Use StructureUtility to instantiate
|
||||
*/
|
||||
public interface IStructureElementChain<T> extends IStructureElement<T> {
|
||||
|
||||
IStructureElement<T>[] fallbacks();
|
||||
|
||||
@Override
|
||||
@@ -24,6 +33,16 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
for (IStructureElement<T> fallback : fallbacks()) {
|
||||
if (fallback.couldBeValid(t, world, x, y, z, trigger)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean spawnHint(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
for (IStructureElement<T> fallback : fallbacks()) {
|
||||
@@ -44,34 +63,85 @@ public interface IStructureElementChain<T> extends IStructureElement<T> {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
default PlaceResult survivalPlaceBlock(
|
||||
T t,
|
||||
World world,
|
||||
int x,
|
||||
int y,
|
||||
int z,
|
||||
ItemStack trigger,
|
||||
IItemSource s,
|
||||
EntityPlayerMP actor,
|
||||
Consumer<IChatComponent> chatter) {
|
||||
boolean haveSkip = false;
|
||||
default List<String> getDescription(T context) {
|
||||
Set<String> descriptions = new LinkedHashSet<>();
|
||||
for (IStructureElement<T> fallback : fallbacks()) {
|
||||
List<String> desc = fallback.getDescription(context);
|
||||
if (desc != null) {
|
||||
descriptions.addAll(desc);
|
||||
}
|
||||
}
|
||||
return descriptions.isEmpty() ? null : new ArrayList<>(descriptions);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
default BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
|
||||
AutoPlaceEnvironment env) {
|
||||
Predicate<ItemStack> predicate = null;
|
||||
List<Iterable<ItemStack>> is = new ArrayList<>();
|
||||
for (IStructureElement<T> fallback : fallbacks()) {
|
||||
BlocksToPlace e = fallback.getBlocksToPlace(t, world, x, y, z, trigger, env);
|
||||
if (e == null) continue;
|
||||
if (predicate == null) predicate = e.getPredicate();
|
||||
else predicate = predicate.or(e.getPredicate());
|
||||
Iterable<ItemStack> stacks = e.getStacks();
|
||||
if (stacks != null) is.add(stacks);
|
||||
}
|
||||
if (predicate == null) return null;
|
||||
return new BlocksToPlace(predicate, Iterables.concat(is));
|
||||
}
|
||||
|
||||
@Override
|
||||
default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s,
|
||||
EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
|
||||
boolean allContinue = true;
|
||||
List<IChatComponent> bufferedNoise = new ArrayList<>();
|
||||
for (IStructureElement<T> fallback : fallbacks()) {
|
||||
PlaceResult result = fallback.survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, bufferedNoise::add);
|
||||
switch (result) {
|
||||
case REJECT_CONTINUE:
|
||||
break;
|
||||
case REJECT:
|
||||
allContinue = false;
|
||||
break;
|
||||
case SKIP:
|
||||
haveSkip = true;
|
||||
break;
|
||||
bufferedNoise.forEach(chatter);
|
||||
return PlaceResult.SKIP;
|
||||
default:
|
||||
return result;
|
||||
}
|
||||
}
|
||||
// dump all that noise back into upstream
|
||||
bufferedNoise.forEach(chatter);
|
||||
// TODO need reconsider to ensure this is the right course of action
|
||||
return haveSkip ? PlaceResult.SKIP : PlaceResult.REJECT;
|
||||
return allContinue ? PlaceResult.REJECT_CONTINUE : PlaceResult.REJECT;
|
||||
}
|
||||
|
||||
@Override
|
||||
default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
|
||||
AutoPlaceEnvironment env) {
|
||||
boolean allContinue = true;
|
||||
List<IChatComponent> bufferedNoise = new ArrayList<>();
|
||||
for (IStructureElement<T> fallback : fallbacks()) {
|
||||
PlaceResult result = fallback
|
||||
.survivalPlaceBlock(t, world, x, y, z, trigger, env.withChatter(bufferedNoise::add));
|
||||
switch (result) {
|
||||
case REJECT_CONTINUE:
|
||||
break;
|
||||
case REJECT:
|
||||
allContinue = false;
|
||||
break;
|
||||
case SKIP:
|
||||
bufferedNoise.forEach(env.getChatter());
|
||||
return PlaceResult.SKIP;
|
||||
default:
|
||||
return result;
|
||||
}
|
||||
}
|
||||
// dump all that noise back into upstream
|
||||
bufferedNoise.forEach(env.getChatter());
|
||||
return allContinue ? PlaceResult.REJECT_CONTINUE : PlaceResult.REJECT;
|
||||
}
|
||||
}
|
||||
+16
-11
@@ -1,33 +1,38 @@
|
||||
package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.IChatComponent;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public interface IStructureElementCheckOnly<T> extends IStructureElement<T> {
|
||||
|
||||
@Override
|
||||
default boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
default PlaceResult survivalPlaceBlock(
|
||||
T t,
|
||||
World world,
|
||||
int x,
|
||||
int y,
|
||||
int z,
|
||||
ItemStack trigger,
|
||||
IItemSource s,
|
||||
EntityPlayerMP actor,
|
||||
Consumer<IChatComponent> chatter) {
|
||||
return PlaceResult.SKIP;
|
||||
default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s,
|
||||
EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
|
||||
return PlaceResult.REJECT_CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
|
||||
AutoPlaceEnvironment env) {
|
||||
return PlaceResult.REJECT_CONTINUE;
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -3,4 +3,5 @@ package com.gtnewhorizon.structurelib.structure;
|
||||
/**
|
||||
* Use StructureUtility to instantiate
|
||||
*/
|
||||
public interface IStructureElementDeferred<T> extends IStructureElement<T> {}
|
||||
public interface IStructureElementDeferred<T> extends IStructureElement<T> {
|
||||
}
|
||||
+10
-10
@@ -1,28 +1,28 @@
|
||||
package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.IChatComponent;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public interface IStructureElementNoPlacement<T> extends IStructureElement<T> {
|
||||
|
||||
@Override
|
||||
default boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
default PlaceResult survivalPlaceBlock(
|
||||
T t,
|
||||
World world,
|
||||
int x,
|
||||
int y,
|
||||
int z,
|
||||
ItemStack trigger,
|
||||
IItemSource s,
|
||||
EntityPlayerMP actor,
|
||||
Consumer<IChatComponent> chatter) {
|
||||
default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s,
|
||||
EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
|
||||
return PlaceResult.REJECT;
|
||||
}
|
||||
|
||||
@Override
|
||||
default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
|
||||
AutoPlaceEnvironment env) {
|
||||
return PlaceResult.REJECT;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.IChatComponent;
|
||||
@@ -10,11 +11,17 @@ import net.minecraft.world.World;
|
||||
* Use StructureUtility to instantiate
|
||||
*/
|
||||
interface IStructureNavigate<T> extends IStructureElement<T> {
|
||||
|
||||
@Override
|
||||
default boolean check(T t, World world, int x, int y, int z) {
|
||||
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;
|
||||
@@ -26,16 +33,14 @@ interface IStructureNavigate<T> extends IStructureElement<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
default PlaceResult survivalPlaceBlock(
|
||||
T t,
|
||||
World world,
|
||||
int x,
|
||||
int y,
|
||||
int z,
|
||||
ItemStack trigger,
|
||||
IItemSource s,
|
||||
EntityPlayerMP actor,
|
||||
Consumer<IChatComponent> chatter) {
|
||||
default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s,
|
||||
EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
|
||||
return PlaceResult.SKIP;
|
||||
}
|
||||
|
||||
@Override
|
||||
default PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
|
||||
AutoPlaceEnvironment env) {
|
||||
return PlaceResult.SKIP;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,36 +2,59 @@ package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
import net.minecraft.world.World;
|
||||
|
||||
/**
|
||||
* A functional interface that is used to iterate over an arbitrary structure.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface IStructureWalker<T> {
|
||||
boolean visit(IStructureElement<T> element, World world, int x, int y, int z);
|
||||
|
||||
default boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z) {
|
||||
/**
|
||||
* 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,
|
||||
int c) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static <T> IStructureWalker<T> ignoreBlockUnloaded(IStructureWalker<T> walker) {
|
||||
return new IStructureWalker<T>() {
|
||||
|
||||
@Override
|
||||
public boolean visit(IStructureElement<T> element, World world, int x, int y, int z) {
|
||||
return walker.visit(element, world, x, y, z);
|
||||
public boolean visit(IStructureElement<T> element, World world, int x, int y, int z, int a, int b, int c) {
|
||||
return walker.visit(element, world, x, y, z, a, b, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z) {
|
||||
return walker.visit(element, world, x, y, z);
|
||||
public boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z, int a, int b,
|
||||
int c) {
|
||||
return walker.visit(element, world, x, y, z, a, b, c);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static <T> IStructureWalker<T> skipBlockUnloaded(IStructureWalker<T> walker) {
|
||||
return new IStructureWalker<T>() {
|
||||
|
||||
@Override
|
||||
public boolean visit(IStructureElement<T> element, World world, int x, int y, int z) {
|
||||
return walker.visit(element, world, x, y, z);
|
||||
public boolean visit(IStructureElement<T> element, World world, int x, int y, int z, int a, int b, int c) {
|
||||
return walker.visit(element, world, x, y, z, a, b, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z) {
|
||||
public boolean blockNotLoaded(IStructureElement<T> element, World world, int x, int y, int z, int a, int b,
|
||||
int c) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayer;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import com.gtnewhorizon.structurelib.alignment.enumerable.ExtendedFacing;
|
||||
|
||||
/*
|
||||
* Architecture notes: this should never get any callback from survival build. All structure significant code belongs
|
||||
* within the IStructureElement. This will make the user easier to maintain the IStructureDefinition. Structure Element
|
||||
* knows the most about what this structure element should do, not the caller.
|
||||
*/
|
||||
public interface ISurvivalBuildEnvironment {
|
||||
|
||||
/**
|
||||
* Get the source of the items. This will be invoked exactly once per structure element build.
|
||||
* <p>
|
||||
* If the call chain ever got into the old API, this will be called at least once per
|
||||
* {@link IStructureDefinition#survivalBuild(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int, int, ISurvivalBuildEnvironment, boolean)}
|
||||
* call.
|
||||
*/
|
||||
IItemSource getSource();
|
||||
|
||||
/**
|
||||
* Get the origin of action. This will be invoked at least once per
|
||||
* {@link IStructureDefinition#survivalBuild(Object, ItemStack, String, World, ExtendedFacing, int, int, int, int, int, int, int, ISurvivalBuildEnvironment, boolean)}
|
||||
* call.
|
||||
*/
|
||||
EntityPlayer getActor();
|
||||
|
||||
/**
|
||||
* Create a default implementation with three parameters all being constants.
|
||||
*
|
||||
* @param source from where to drain resource
|
||||
* @param actor source of action
|
||||
*/
|
||||
static ISurvivalBuildEnvironment create(IItemSource source, EntityPlayer actor) {
|
||||
return new DefaultSurvivalBuildEnvironment(source, actor);
|
||||
}
|
||||
}
|
||||
@@ -9,5 +9,6 @@ import net.minecraft.block.Block;
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ITierConverter<TIER> {
|
||||
|
||||
TIER convert(Block block, int meta);
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package com.gtnewhorizon.structurelib.structure;
|
||||
/**
|
||||
* Implement this and use {@link StructureUtility#withContext(IStructureElement)} in case you need to record some
|
||||
* temporary states that don't need to live in your main context, which would usually live for the whole lifetime of the
|
||||
* structure than just during structure check. Usually just save some few bytes, but becomes non trivial if you need to
|
||||
* structure than just during structure check. Usually just save some few bytes, but becomes non-trivial if you need to
|
||||
* record too much stuff.
|
||||
* <p>
|
||||
* You will have to reset the context yourself though. The API can't guess when the old context becomes invalid.
|
||||
@@ -11,5 +11,6 @@ package com.gtnewhorizon.structurelib.structure;
|
||||
* @param <CTX> the temporary state context
|
||||
*/
|
||||
public interface IWithExtendedContext<CTX> {
|
||||
|
||||
CTX getCurrentContext();
|
||||
}
|
||||
@@ -1,13 +1,18 @@
|
||||
package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.IChatComponent;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
class LazyStructureElement<T> implements IStructureElementDeferred<T> {
|
||||
|
||||
private Function<T, IStructureElement<T>> to;
|
||||
private IStructureElement<T> elem;
|
||||
|
||||
@@ -28,6 +33,11 @@ class LazyStructureElement<T> implements IStructureElementDeferred<T> {
|
||||
return get(t).check(t, world, x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean couldBeValid(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return get(t).couldBeValid(t, world, x, y, z, trigger);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean placeBlock(T t, World world, int x, int y, int z, ItemStack trigger) {
|
||||
return get(t).placeBlock(t, world, x, y, z, trigger);
|
||||
@@ -38,17 +48,28 @@ class LazyStructureElement<T> implements IStructureElementDeferred<T> {
|
||||
return get(t).spawnHint(t, world, x, y, z, trigger);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PlaceResult survivalPlaceBlock(
|
||||
T t,
|
||||
World world,
|
||||
int x,
|
||||
int y,
|
||||
int z,
|
||||
ItemStack trigger,
|
||||
IItemSource s,
|
||||
EntityPlayerMP actor,
|
||||
Consumer<IChatComponent> chatter) {
|
||||
public BlocksToPlace getBlocksToPlace(T t, World world, int x, int y, int z, ItemStack trigger,
|
||||
AutoPlaceEnvironment env) {
|
||||
return get(t).getBlocksToPlace(t, world, x, y, z, trigger, env);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s,
|
||||
EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
|
||||
return get(t).survivalPlaceBlock(t, world, x, y, z, trigger, s, actor, chatter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
|
||||
AutoPlaceEnvironment env) {
|
||||
return get(t).survivalPlaceBlock(t, world, x, y, z, trigger, env);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<String> getDescription(T context) {
|
||||
return elem != null ? elem.getDescription(context) : null;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,40 @@
|
||||
package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
import static com.gtnewhorizon.structurelib.StructureLib.DEBUG_MODE;
|
||||
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 com.gtnewhorizon.structurelib.util.Vec3Impl;
|
||||
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;
|
||||
|
||||
public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
|
||||
private final Map<Character, IStructureElement<T>> elements;
|
||||
private final Map<String, String> shapes;
|
||||
private final Map<String, IStructureElement<T>[]> structures;
|
||||
private final Map<String, Set<Vec3Impl>> occupiedSpaces;
|
||||
|
||||
public static <B> Builder<B> builder() {
|
||||
return new Builder<>();
|
||||
}
|
||||
|
||||
private StructureDefinition(
|
||||
Map<Character, IStructureElement<T>> elements,
|
||||
Map<String, String> shapes,
|
||||
Map<String, IStructureElement<T>[]> structures) {
|
||||
private StructureDefinition(Map<Character, IStructureElement<T>> elements, Map<String, String> shapes,
|
||||
Map<String, IStructureElement<T>[]> structures, Map<String, Set<Vec3Impl>> occupiedSpaces) {
|
||||
this.elements = elements;
|
||||
this.shapes = shapes;
|
||||
this.structures = structures;
|
||||
this.occupiedSpaces = occupiedSpaces;
|
||||
}
|
||||
|
||||
public static class Builder<T> {
|
||||
|
||||
private static final char A = '\uA000';
|
||||
private static final char B = '\uB000';
|
||||
private static final char C = '\uC000';
|
||||
@@ -33,11 +42,13 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
private final Map<Vec3Impl, Character> navigates;
|
||||
private final Map<Character, IStructureElement<T>> elements;
|
||||
private final Map<String, String> shapes;
|
||||
private final Map<String, Set<Vec3Impl>> occupiedSpaces;
|
||||
|
||||
private Builder() {
|
||||
navigates = new HashMap<>();
|
||||
elements = new HashMap<>();
|
||||
shapes = new HashMap<>();
|
||||
occupiedSpaces = new HashMap<>();
|
||||
}
|
||||
|
||||
public Map<Character, IStructureElement<T>> getElements() {
|
||||
@@ -49,21 +60,25 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds shape
|
||||
* + is anything but air
|
||||
* - is air checks
|
||||
* space bar is skip
|
||||
* ~ is also skip (but marks controller position, optional and logically it is a space...)
|
||||
* rest needs to be defined
|
||||
* Adds shape. Shape is a two-dimensional string array. Each <b>character</b> inside any of these strings will
|
||||
* be later mapped to a particular type of {@link IStructureElement} as indicated by
|
||||
* {@link #addElement(Character, IStructureElement)}.
|
||||
* <p>
|
||||
* next char is next block(a)
|
||||
* next string is next line(b)
|
||||
* next string[] is next slice(c)
|
||||
* next char is next block(a) next string is next line(b) next string[] is next slice(c)
|
||||
* <p>
|
||||
* There are a few special reserved characters that are pre-mapped to some common structure elements
|
||||
* <ul>
|
||||
* <li>+ is anything but air</li>
|
||||
* <li>- is air checks</li>
|
||||
* <li>space bar is skip</li>
|
||||
* <li>~ is also skip (but marks controller position, not optional)</li>
|
||||
* <li>char {@code A000-FFFF} range is reserved for generated skips</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* char A000-FFFF range is reserved for generated skips
|
||||
*
|
||||
* @param name unlocalized/code name
|
||||
* @param structurePiece generated or written struct - DO NOT STORE IT ANYWHERE, or at least set them to null afterwards
|
||||
* @param structurePiece generated or written struct - DO NOT STORE IT ANYWHERE, or at least set them to null
|
||||
* afterwards
|
||||
* @return this builder
|
||||
*/
|
||||
public Builder<T> addShape(String name, String[][] structurePiece) {
|
||||
@@ -80,22 +95,37 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
}
|
||||
builder.setLength(builder.length() - 1);
|
||||
}
|
||||
Set<Vec3Impl> occupiedSpace = new HashSet<>();
|
||||
// these track the global current location
|
||||
int aa = 0, bb = 0, cc = 0;
|
||||
// these track the vec3 towards next meaningful element
|
||||
int a = 0, b = 0, c = 0;
|
||||
// I do know these are pretty bad variable naming, but I have no better idea
|
||||
for (int i = 0; i < builder.length(); i++) {
|
||||
char ch = builder.charAt(i);
|
||||
if (ch == ' ' || ch == '~') {
|
||||
if (ch == ' ') {
|
||||
builder.setCharAt(i, A);
|
||||
ch = A;
|
||||
} else if (ch == '~') {
|
||||
builder.setCharAt(i, A);
|
||||
ch = A;
|
||||
occupiedSpace.add(Vec3Impl.getFromPool(aa, bb, cc));
|
||||
}
|
||||
if (ch == A) {
|
||||
aa++;
|
||||
a++;
|
||||
} else if (ch == B) {
|
||||
aa = 0;
|
||||
a = 0;
|
||||
bb++;
|
||||
b++;
|
||||
} else if (ch == C) {
|
||||
aa = 0;
|
||||
bb = 0;
|
||||
a = 0;
|
||||
b = 0;
|
||||
c++;
|
||||
cc++;
|
||||
} else if (a != 0 || b != 0 || c != 0) {
|
||||
Vec3Impl vec3 = new Vec3Impl(a, b, c);
|
||||
Character navigate = navigates.get(vec3);
|
||||
@@ -105,51 +135,84 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
addElement(navigate, step(vec3));
|
||||
}
|
||||
builder.setCharAt(i - 1, navigate);
|
||||
occupiedSpace.add(Vec3Impl.getFromPool(aa, bb, cc));
|
||||
aa++;
|
||||
a = 0;
|
||||
b = 0;
|
||||
c = 0;
|
||||
} else {
|
||||
occupiedSpace.add(Vec3Impl.getFromPool(aa, bb, cc));
|
||||
aa++;
|
||||
}
|
||||
}
|
||||
|
||||
String built = builder.toString().replaceAll("[\\uA000\\uB000\\uC000]", "");
|
||||
occupiedSpaces.put(name, occupiedSpace);
|
||||
|
||||
if (built.contains("+")) {
|
||||
boolean hasPlus = false;
|
||||
boolean hasMinus = false;
|
||||
|
||||
int write = 0;
|
||||
for (int read = 0; read < builder.length(); read++) {
|
||||
char ch = builder.charAt(read);
|
||||
if (ch == A || ch == B || ch == C) {
|
||||
continue;
|
||||
}
|
||||
if (ch == '+') {
|
||||
hasPlus = true;
|
||||
} else if (ch == '-') {
|
||||
hasMinus = true;
|
||||
}
|
||||
if (read != write) builder.setCharAt(write, ch);
|
||||
write++;
|
||||
}
|
||||
builder.setLength(write);
|
||||
|
||||
if (hasPlus) {
|
||||
addElement('+', notAir());
|
||||
}
|
||||
if (built.contains("-")) {
|
||||
if (hasMinus) {
|
||||
addElement('-', isAir());
|
||||
}
|
||||
shapes.put(name, built);
|
||||
shapes.put(name, builder.toString());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use the unboxed version
|
||||
*/
|
||||
@Deprecated
|
||||
public Builder<T> addElement(Character name, IStructureElement<T> structurePiece) {
|
||||
elements.putIfAbsent(name, structurePiece);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder<T> addElement(char name, IStructureElement<T> structurePiece) {
|
||||
elements.putIfAbsent(name, structurePiece);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IStructureDefinition<T> build() {
|
||||
Map<String, IStructureElement<T>[]> structures = compileStructureMap();
|
||||
if (DEBUG_MODE) {
|
||||
return new StructureDefinition<>(new HashMap<>(elements), new HashMap<>(shapes), structures);
|
||||
} else {
|
||||
return structures::get;
|
||||
}
|
||||
return new StructureDefinition<>(
|
||||
new HashMap<>(elements),
|
||||
new HashMap<>(shapes),
|
||||
structures,
|
||||
occupiedSpaces);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, IStructureElement<T>[]> compileElementSetMap() {
|
||||
Set<Integer> missing = findMissing();
|
||||
if (missing.isEmpty()) {
|
||||
return shapes.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue()
|
||||
.chars()
|
||||
.mapToObj(c -> (char) c)
|
||||
.distinct()
|
||||
.map(elements::get)
|
||||
.toArray(IStructureElement[]::new)));
|
||||
return shapes.entrySet().stream().collect(
|
||||
Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
e -> e.getValue().chars().mapToObj(c -> (char) c).distinct().map(elements::get)
|
||||
.toArray(IStructureElement[]::new)));
|
||||
} else {
|
||||
throw new RuntimeException("Missing Structure Element bindings for (chars as integers): "
|
||||
+ Arrays.toString(missing.toArray()));
|
||||
throw new RuntimeException(
|
||||
"Missing Structure Element bindings for (chars as integers): "
|
||||
+ Arrays.toString(missing.toArray()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,22 +220,21 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
private Map<String, IStructureElement<T>[]> compileStructureMap() {
|
||||
Set<Integer> missing = findMissing();
|
||||
if (missing.isEmpty()) {
|
||||
return shapes.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue()
|
||||
.chars()
|
||||
.mapToObj(c -> elements.get((char) c))
|
||||
.toArray(IStructureElement[]::new)));
|
||||
return shapes.entrySet().stream().collect(
|
||||
Collectors.toMap(
|
||||
Map.Entry::getKey,
|
||||
e -> e.getValue().chars().mapToObj(c -> elements.get((char) c))
|
||||
.toArray(IStructureElement[]::new)));
|
||||
} else {
|
||||
throw new RuntimeException("Missing Structure Element bindings for (chars as integers): "
|
||||
+ Arrays.toString(missing.toArray()));
|
||||
throw new RuntimeException(
|
||||
"Missing Structure Element bindings for (chars as integers): "
|
||||
+ Arrays.toString(missing.toArray()));
|
||||
}
|
||||
}
|
||||
|
||||
private Set<Integer> findMissing() {
|
||||
return shapes.values().stream()
|
||||
.flatMapToInt(CharSequence::chars)
|
||||
.filter(i -> !elements.containsKey((char) i))
|
||||
.boxed()
|
||||
.collect(Collectors.toSet());
|
||||
return shapes.values().stream().flatMapToInt(CharSequence::chars)
|
||||
.filter(i -> !elements.containsKey((char) i)).boxed().collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,4 +256,11 @@ public class StructureDefinition<T> implements IStructureDefinition<T> {
|
||||
if (elements == null) throw new NoSuchElementException(name);
|
||||
return elements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isContainedInStructure(String name, int offsetA, int offsetB, int offsetC) {
|
||||
Set<Vec3Impl> occupiedSpace = occupiedSpaces.get(name);
|
||||
if (occupiedSpace == null) throw new NoSuchElementException(name);
|
||||
return occupiedSpace.contains(new Vec3Impl(offsetA, offsetB, offsetC));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.gtnewhorizon.structurelib.structure;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import net.minecraft.entity.player.EntityPlayerMP;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.util.IChatComponent;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
/**
|
||||
* Internal bridge class for easing implementation
|
||||
*/
|
||||
abstract class StructureElement_Bridge<T> implements IStructureElement<T> {
|
||||
|
||||
// seal the deprecated method to prevent accidental overrides
|
||||
@Deprecated
|
||||
@Override
|
||||
public final PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger, IItemSource s,
|
||||
EntityPlayerMP actor, Consumer<IChatComponent> chatter) {
|
||||
return survivalPlaceBlock(t, world, x, y, z, trigger, AutoPlaceEnvironment.fromLegacy(s, actor, chatter));
|
||||
}
|
||||
|
||||
// clear the default implementation. we don't need that.
|
||||
public abstract PlaceResult survivalPlaceBlock(T t, World world, int x, int y, int z, ItemStack trigger,
|
||||
AutoPlaceEnvironment env);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
Loaded 100 of 142 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user