Merge in CCL

git-subtree-dir: ccl
git-subtree-mainline: 7f78ceb921
git-subtree-split: ce1830d7b5
This commit is contained in:
Jason Mitchell committed 2024-07-03 08:22:26 -07:00
commit acf5b9a2e7
135 files changed
+15937

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
# Ignore spotlessApply reformat
a4515fbef8382f0f961ade90ca82c85d924fc06d
+44
View File
@@ -0,0 +1,44 @@
* text eol=lf
*.[jJ][aA][rR] binary
*.[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
+13
View File
@@ -0,0 +1,13 @@
name: Build and test
on:
pull_request:
branches: [ master, main ]
push:
branches: [ master, main ]
jobs:
build-and-test:
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/build-and-test.yml@master
secrets: inherit
+14
View File
@@ -0,0 +1,14 @@
name: Release tagged build
on:
push:
tags: [ '*' ]
permissions:
contents: write
jobs:
release-tags:
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/release-tags.yml@master
secrets: inherit
+38
View File
@@ -0,0 +1,38 @@
.gradle
.settings
/.idea/
/.vscode/
/run/
/build/
/eclipse/
.classpath
.project
/bin/
/config/
/crash-reports/
/logs/
options.txt
/saves/
usernamecache.json
banned-ips.json
banned-players.json
eula.txt
ops.json
server.properties
servers.dat
usercache.json
whitelist.json
/out/
*.iml
*.ipr
*.iws
src/main/resources/mixins.*([!.]).json
*.bat
*.DS_Store
!gradlew.bat
.factorypath
addon.local.gradle
addon.local.gradle.kts
addon.late.local.gradle
addon.late.local.gradle.kts
layout.json
+3
View File
@@ -0,0 +1,3 @@
# Any Github changes require admin approval
/.github/** @GTNewHorizons/admin
+502
View File
@@ -0,0 +1,502 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
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 this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. 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 not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
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
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the 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
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "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
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY 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
LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
Copyright (C) 2013 Chicken-Bones
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
{signature of Ty Coon}, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!
+4
View File
@@ -0,0 +1,4 @@
CodeChickenLib
==============
Formerly known as CodeChickenCore-Public, this is a library of systems to help make various aspects of minecraft modding easier.
It contains libraries for 3D math and transformations, model rendering, packets, config, colours, asm and a few other things.
+5
View File
@@ -0,0 +1,5 @@
//version: 1707058017
plugins {
id 'com.gtnewhorizons.gtnhconvention'
}
+5
View File
@@ -0,0 +1,5 @@
// Add your dependencies here
dependencies {
}
+191
View File
@@ -0,0 +1,191 @@
# 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 = CodeChickenLib
# Case-sensitive identifier string, available for mcmod.info population and used for automatic mixin JSON generation.
# Conventionally lowercase.
modId = CodeChickenLib
# Root package of the mod, used to find various classes in other properties,
# mcmod.info substitution, enabling assertions in run tasks, etc.
modGroup = codechicken
# Whether to use modGroup as the maven publishing group.
# Due to a history of using JitPack, the default is com.github.GTNewHorizons for all mods.
useModGroupForPublishing = false
# 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
# Specify an MCP channel for dependency deobfuscation and the deobfParams task.
channel = stable
# Specify an MCP mappings version for dependency deobfuscation and the deobfParams task.
mappingsVersion = 12
# Defines other MCP mappings for dependency deobfuscation.
remoteMappings = https://raw.githubusercontent.com/MinecraftForge/FML/1.7.10/conf/
# Select a default username for testing your mod. You can always override this per-run by running
# `./gradlew runClient --username=AnotherPlayer`, or configuring this command in your IDE.
developmentEnvironmentUserName = Developer
# Enables using modern Java syntax (up to version 17) via Jabel, while still targeting JVM 8.
# See https://github.com/bsideup/jabel for details on how this works.
enableModernJavaSyntax = true
# Enables injecting missing generics into the decompiled source code for a better coding experience.
# Turns most publicly visible List, Map, etc. into proper List<E>, Map<K, V> types.
enableGenericInjection = true
# Generate a class with a String field for the mod version named as defined below.
# If generateGradleTokenClass is empty or not missing, no such class will be generated.
# If gradleTokenVersion is empty or missing, the field will not be present in the class.
generateGradleTokenClass =
# Name of the token containing the project's current version to generate/replace.
gradleTokenVersion =
# [DEPRECATED] Mod ID replacement token.
gradleTokenModId =
# [DEPRECATED] Mod name replacement token.
gradleTokenModName =
# [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
apiPackage =
# 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
# 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 core mod entry class if you use a core mod. This class must implement IFMLLoadingPlugin!
# This parameter is for legacy compatibility only
# Example value: (coreModClass = asm.FMLPlugin) + (modGroup = com.myname.mymodid) -> com.myname.mymodid.asm.FMLPlugin
coreModClass =
# 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 =
# 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 the GTNH maven, CurseMaven, IC2/Player maven, and some more well-known 1.7.10 repositories.
includeWellKnownRepositories = true
# 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 = codechickenlib-unofficial
# 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: GTNH Mixins 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 = 746280
# The project's relations on CurseForge. You can use this to refer to other projects on CurseForge.
# Syntax: type1:name1;type2:name2;...
# Where type can be one of [requiredDependency, embeddedLibrary, optionalDependency, tool, incompatible],
# and the name is the CurseForge project slug of the other mod.
# Example: requiredDependency:railcraft;embeddedLibrary:cofhlib;incompatible:buildcraft
# Note: UniMixins is automatically set as a required dependency if usesMixins = true.
curseForgeRelations =
# Optional parameter to customize the produced artifacts. Use this to preserve artifact naming when migrating older
# projects. New projects should not use this parameter.
# customArchiveBaseName =
# Optional parameter to have the build automatically fail if an illegal version is used.
# This can be useful if you e.g. only want to allow versions in the form of '1.1.xxx'.
# The check is ONLY performed if the version is a git tag.
# Note: the specified string must be escaped, so e.g. 1\\.1\\.\\d+ instead of 1\.1\.\d+
# versionPattern =
# Uncomment to prevent the source code from being published.
# noPublishedSources = true
# Uncomment this to disable Spotless checks.
# This should only be uncommented to keep it easier to sync with upstream/other forks.
# That is, if there is no other active fork/upstream, NEVER change this.
# disableSpotless = true
# Uncomment this to disable Checkstyle checks (currently wildcard import check).
# disableCheckstyle = true
# Override the IDEA build type. Valid values are: "" (leave blank, do not override), "idea" (force use native IDEA build), "gradle"
# (force use delegated build).
# This is meant to be set in $HOME/.gradle/gradle.properties.
# e.g. add "systemProp.org.gradle.project.ideaOverrideBuildType=idea" will override the build type to be native build.
# WARNING: If you do use this option, it will overwrite whatever you have in your existing projects. This might not be what you want!
# Usually there is no need to uncomment this here as other developers do not necessarily use the same build type as you.
# ideaOverrideBuildType = idea
# Whether IDEA should run spotless checks when pressing the Build button.
# This is meant to be set in $HOME/.gradle/gradle.properties.
# ideaCheckSpotlessOnBuild = true
# Non-GTNH properties
gradleTokenGroupName =
Binary file not shown.
+7
View File
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+249
View File
@@ -0,0 +1,249 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# 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/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
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
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
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, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# 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" "$@"
+92
View File
@@ -0,0 +1,92 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
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 %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+2
View File
@@ -0,0 +1,2 @@
before_install:
- ./gradlew setupCIWorkspace
+4
View File
@@ -0,0 +1,4 @@
// Add any additional repositiroes for your dependencies here
repositories {
}
+23
View File
@@ -0,0 +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.gtnewhorizons.gtnhsettingsconvention' version '1.0.8'
}
@@ -0,0 +1,177 @@
package codechicken.lib.asm;
import static org.objectweb.asm.tree.AbstractInsnNode.FRAME;
import static org.objectweb.asm.tree.AbstractInsnNode.JUMP_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.LABEL;
import static org.objectweb.asm.tree.AbstractInsnNode.LOOKUPSWITCH_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.TABLESWITCH_INSN;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableMap;
public class ASMBlock {
public InsnListSection list;
private BiMap<String, LabelNode> labels;
public ASMBlock(InsnListSection list, BiMap<String, LabelNode> labels) {
this.list = list;
this.labels = labels;
}
public ASMBlock(InsnListSection list) {
this(list, HashBiMap.<String, LabelNode>create());
}
public ASMBlock(InsnList list) {
this(new InsnListSection(list));
}
public ASMBlock() {
this(new InsnListSection());
}
public LabelNode getOrAdd(String s) {
LabelNode l = get(s);
if (l == null) labels.put(s, l = new LabelNode());
return l;
}
public LabelNode get(String s) {
return labels.get(s);
}
public void replaceLabels(Map<LabelNode, LabelNode> labelMap, Set<LabelNode> usedLabels) {
for (AbstractInsnNode insn : list) switch (insn.getType()) {
case LABEL:
AbstractInsnNode insn2 = insn.clone(labelMap);
if (insn2 == insn) // identity mapping
continue;
if (usedLabels.contains(insn2))
throw new IllegalStateException("LabelNode cannot be a part of two InsnLists");
list.replace(insn, insn2);
break;
case JUMP_INSN:
case FRAME:
case LOOKUPSWITCH_INSN:
case TABLESWITCH_INSN:
list.replace(insn, insn.clone(labelMap));
}
for (Entry<LabelNode, LabelNode> entry : labelMap.entrySet()) {
String key = labels.inverse().get(entry.getKey());
if (key != null) labels.put(key, entry.getValue());
}
}
public void replaceLabels(Map<LabelNode, LabelNode> labelMap) {
replaceLabels(labelMap, Collections.EMPTY_SET);
}
public void replaceLabel(String s, LabelNode l) {
LabelNode old = get(s);
if (old != null) replaceLabels(ImmutableMap.of(old, l));
}
/**
* Pulls all common labels from other into this
*
* @return this
*/
public ASMBlock mergeLabels(ASMBlock other) {
if (labels.isEmpty() || other.labels.isEmpty()) return this;
// common labels, give them our nodes
HashMap<LabelNode, LabelNode> labelMap = list.identityLabelMap();
for (Entry<String, LabelNode> entry : other.labels.entrySet()) {
LabelNode old = labels.get(entry.getKey());
if (old != null) labelMap.put(old, entry.getValue());
}
HashSet<LabelNode> usedLabels = new HashSet<LabelNode>();
for (AbstractInsnNode insn = other.list.list.getFirst(); insn != null; insn = insn.getNext())
if (insn.getType() == LABEL) usedLabels.add((LabelNode) insn);
replaceLabels(labelMap, usedLabels);
return this;
}
/**
* Like mergeLabels but pulls insns from other list into this so LabelNodes can be transferred
*
* @return this
*/
public ASMBlock pullLabels(ASMBlock other) {
other.list.remove();
return mergeLabels(other);
}
public ASMBlock copy() {
BiMap<String, LabelNode> labels = HashBiMap.create();
Map<LabelNode, LabelNode> labelMap = list.cloneLabels();
for (Entry<String, LabelNode> entry : this.labels.entrySet())
labels.put(entry.getKey(), labelMap.get(entry.getValue()));
return new ASMBlock(list.copy(labelMap), labels);
}
public ASMBlock applyLabels(InsnListSection list2) {
if (labels.isEmpty()) return new ASMBlock(list2);
Set<LabelNode> cFlowLabels1 = labels.values();
Set<LabelNode> cFlowLabels2 = InsnComparator.getControlFlowLabels(list2);
ASMBlock block = new ASMBlock(list2);
HashMap<LabelNode, LabelNode> labelMap = new HashMap<LabelNode, LabelNode>();
for (int i = 0, k = 0; i < list.size() && k < list2.size();) {
AbstractInsnNode insn1 = list.get(i);
if (!InsnComparator.insnImportant(insn1, cFlowLabels1)) {
i++;
continue;
}
AbstractInsnNode insn2 = list2.get(k);
if (!InsnComparator.insnImportant(insn2, cFlowLabels2)) {
k++;
continue;
}
if (insn1.getOpcode() != insn2.getOpcode())
throw new IllegalArgumentException("Lists do not match:\n" + list + "\n\n" + list2);
switch (insn1.getType()) {
case LABEL:
labelMap.put((LabelNode) insn1, (LabelNode) insn2);
break;
case JUMP_INSN:
labelMap.put(((JumpInsnNode) insn1).label, ((JumpInsnNode) insn2).label);
break;
}
i++;
k++;
}
for (Entry<String, LabelNode> entry : labels.entrySet())
block.labels.put(entry.getKey(), labelMap.get(entry.getValue()));
return block;
}
public InsnList rawListCopy() {
return list.copy().list;
}
}
@@ -0,0 +1,203 @@
package codechicken.lib.asm;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
import org.objectweb.asm.util.ASMifier;
import org.objectweb.asm.util.Textifier;
import org.objectweb.asm.util.TraceClassVisitor;
import codechicken.lib.config.ConfigFile;
import codechicken.lib.config.DefaultingConfigFile;
public class ASMHelper {
public static ConfigFile config = loadConfig();
public static Logger logger = LogManager.getLogger("CCL ASM");
private static ConfigFile loadConfig() {
try { // weak reference for environments without FML
File mcDir = (File) ((Object[]) Class.forName("cpw.mods.fml.relauncher.FMLInjectionData").getMethod("data")
.invoke(null))[6];
File file = new File(mcDir, "config/CodeChickenLib.cfg");
if (ObfMapping.obfuscated) return new DefaultingConfigFile(file);
else return new ConfigFile(file).setComment("CodeChickenLib development configuration file.");
} catch (Exception ignored) {
return null; // no config for these systems
}
}
public static interface Acceptor {
public void accept(ClassVisitor cv) throws IOException;
}
public static MethodNode findMethod(ObfMapping methodmap, ClassNode cnode) {
for (MethodNode mnode : cnode.methods) if (methodmap.matches(mnode)) return mnode;
return null;
}
public static FieldNode findField(ObfMapping fieldmap, ClassNode cnode) {
for (FieldNode fnode : cnode.fields) if (fieldmap.matches(fnode)) return fnode;
return null;
}
public static ClassNode createClassNode(byte[] bytes) {
return createClassNode(bytes, 0);
}
public static ClassNode createClassNode(byte[] bytes, int flags) {
ClassNode cnode = new ClassNode();
ClassReader reader = new ClassReader(bytes);
reader.accept(cnode, flags);
return cnode;
}
public static byte[] createBytes(ClassNode cnode, int flags) {
ClassWriter cw = new CC_ClassWriter(flags);
cnode.accept(cw);
return cw.toByteArray();
}
public static Map<LabelNode, LabelNode> cloneLabels(InsnList list) {
return new InsnListSection(list).cloneLabels();
}
public static InsnList cloneInsnList(InsnList list) {
return new InsnListSection(list).copy().list;
}
public static InsnList cloneInsnList(Map<LabelNode, LabelNode> labelMap, InsnList list) {
return new InsnListSection(list).copy(labelMap).list;
}
public static List<TryCatchBlockNode> cloneTryCatchBlocks(Map<LabelNode, LabelNode> labelMap,
List<TryCatchBlockNode> tcblocks) {
ArrayList<TryCatchBlockNode> clone = new ArrayList<TryCatchBlockNode>();
for (TryCatchBlockNode node : tcblocks) clone.add(
new TryCatchBlockNode(
labelMap.get(node.start),
labelMap.get(node.end),
labelMap.get(node.handler),
node.type));
return clone;
}
public static List<LocalVariableNode> cloneLocals(Map<LabelNode, LabelNode> labelMap,
List<LocalVariableNode> locals) {
ArrayList<LocalVariableNode> clone = new ArrayList<LocalVariableNode>(locals.size());
for (LocalVariableNode node : locals) clone.add(
new LocalVariableNode(
node.name,
node.desc,
node.signature,
labelMap.get(node.start),
labelMap.get(node.end),
node.index));
return clone;
}
public static void copy(MethodNode src, MethodNode dst) {
Map<LabelNode, LabelNode> labelMap = cloneLabels(src.instructions);
dst.instructions = cloneInsnList(labelMap, src.instructions);
dst.tryCatchBlocks = cloneTryCatchBlocks(labelMap, src.tryCatchBlocks);
if (src.localVariables != null) dst.localVariables = cloneLocals(labelMap, src.localVariables);
dst.visibleAnnotations = src.visibleAnnotations;
dst.invisibleAnnotations = src.invisibleAnnotations;
dst.visitMaxs(src.maxStack, src.maxLocals);
}
public static String toString(InsnList list) {
return new InsnListSection(list).toString();
}
public static int getLocal(List<LocalVariableNode> list, String name) {
int found = -1;
for (LocalVariableNode node : list) {
if (node.name.equals(name)) {
if (found >= 0) throw new RuntimeException(
"Duplicate local variable: " + name + " not coded to handle this scenario.");
found = node.index;
}
}
return found;
}
public static void replaceMethod(MethodNode original, MethodNode replacement) {
original.instructions.clear();
if (original.localVariables != null) original.localVariables.clear();
if (original.tryCatchBlocks != null) original.tryCatchBlocks.clear();
replacement.accept(original);
}
public static void dump(Acceptor acceptor, File file, boolean filterImportant, boolean sortLocals,
boolean textify) {
try {
if (!file.getParentFile().exists()) file.getParentFile().mkdirs();
if (!file.exists()) file.createNewFile();
PrintWriter pout = new PrintWriter(file);
ClassVisitor cv = new TraceClassVisitor(null, textify ? new Textifier() : new ASMifier(), pout);
if (filterImportant) cv = new ImportantInsnVisitor(cv);
if (sortLocals) cv = new LocalVariablesSorterVisitor(cv);
acceptor.accept(cv);
pout.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void dump(Acceptor acceptor, File file, boolean filterImportant, boolean sortLocals) {
dump(acceptor, file, filterImportant, sortLocals, config.getTag("textify").getBooleanValue(true));
}
public static void dump(final byte[] bytes, File file, boolean filterImportant, boolean sortLocals) {
dump(new Acceptor() {
@Override
public void accept(ClassVisitor cv) {
new ClassReader(bytes).accept(cv, ClassReader.EXPAND_FRAMES);
}
}, file, filterImportant, sortLocals);
}
public static void dump(final InputStream is, File file, boolean filterImportant, boolean sortLocals) {
dump(new Acceptor() {
@Override
public void accept(ClassVisitor cv) throws IOException {
new ClassReader(is).accept(cv, ClassReader.EXPAND_FRAMES);
}
}, file, filterImportant, sortLocals);
}
public static void dump(final ClassNode cnode, File file, boolean filterImportant, boolean sortLocals) {
dump(new Acceptor() {
@Override
public void accept(ClassVisitor cv) {
cnode.accept(cv);
}
}, file, filterImportant, sortLocals);
}
}
@@ -0,0 +1,19 @@
package codechicken.lib.asm;
import net.minecraft.launchwrapper.Launch;
/**
* Initialisation class for using this package. Call this on coremod load
*/
public class ASMInit {
private static boolean initialised = false;
public static void init() {
if (!initialised) {
Launch.classLoader.addTransformerExclusion("codechicken.lib.asm");
Launch.classLoader.addTransformerExclusion("codechicken.lib.config");
initialised = true;
}
}
}
@@ -0,0 +1,469 @@
package codechicken.lib.asm;
import static org.objectweb.asm.Opcodes.AALOAD;
import static org.objectweb.asm.Opcodes.AASTORE;
import static org.objectweb.asm.Opcodes.ACONST_NULL;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ANEWARRAY;
import static org.objectweb.asm.Opcodes.ARETURN;
import static org.objectweb.asm.Opcodes.ARRAYLENGTH;
import static org.objectweb.asm.Opcodes.ASTORE;
import static org.objectweb.asm.Opcodes.ATHROW;
import static org.objectweb.asm.Opcodes.BALOAD;
import static org.objectweb.asm.Opcodes.BASTORE;
import static org.objectweb.asm.Opcodes.BIPUSH;
import static org.objectweb.asm.Opcodes.CALOAD;
import static org.objectweb.asm.Opcodes.CASTORE;
import static org.objectweb.asm.Opcodes.CHECKCAST;
import static org.objectweb.asm.Opcodes.D2F;
import static org.objectweb.asm.Opcodes.D2I;
import static org.objectweb.asm.Opcodes.D2L;
import static org.objectweb.asm.Opcodes.DADD;
import static org.objectweb.asm.Opcodes.DALOAD;
import static org.objectweb.asm.Opcodes.DASTORE;
import static org.objectweb.asm.Opcodes.DCMPG;
import static org.objectweb.asm.Opcodes.DCMPL;
import static org.objectweb.asm.Opcodes.DCONST_0;
import static org.objectweb.asm.Opcodes.DCONST_1;
import static org.objectweb.asm.Opcodes.DDIV;
import static org.objectweb.asm.Opcodes.DLOAD;
import static org.objectweb.asm.Opcodes.DMUL;
import static org.objectweb.asm.Opcodes.DNEG;
import static org.objectweb.asm.Opcodes.DREM;
import static org.objectweb.asm.Opcodes.DRETURN;
import static org.objectweb.asm.Opcodes.DSTORE;
import static org.objectweb.asm.Opcodes.DSUB;
import static org.objectweb.asm.Opcodes.DUP;
import static org.objectweb.asm.Opcodes.DUP2;
import static org.objectweb.asm.Opcodes.DUP2_X1;
import static org.objectweb.asm.Opcodes.DUP2_X2;
import static org.objectweb.asm.Opcodes.DUP_X1;
import static org.objectweb.asm.Opcodes.DUP_X2;
import static org.objectweb.asm.Opcodes.F2D;
import static org.objectweb.asm.Opcodes.F2I;
import static org.objectweb.asm.Opcodes.F2L;
import static org.objectweb.asm.Opcodes.FADD;
import static org.objectweb.asm.Opcodes.FALOAD;
import static org.objectweb.asm.Opcodes.FASTORE;
import static org.objectweb.asm.Opcodes.FCMPG;
import static org.objectweb.asm.Opcodes.FCMPL;
import static org.objectweb.asm.Opcodes.FCONST_0;
import static org.objectweb.asm.Opcodes.FCONST_1;
import static org.objectweb.asm.Opcodes.FCONST_2;
import static org.objectweb.asm.Opcodes.FDIV;
import static org.objectweb.asm.Opcodes.FLOAD;
import static org.objectweb.asm.Opcodes.FMUL;
import static org.objectweb.asm.Opcodes.FNEG;
import static org.objectweb.asm.Opcodes.FREM;
import static org.objectweb.asm.Opcodes.FRETURN;
import static org.objectweb.asm.Opcodes.FSTORE;
import static org.objectweb.asm.Opcodes.FSUB;
import static org.objectweb.asm.Opcodes.GETFIELD;
import static org.objectweb.asm.Opcodes.GETSTATIC;
import static org.objectweb.asm.Opcodes.GOTO;
import static org.objectweb.asm.Opcodes.I2B;
import static org.objectweb.asm.Opcodes.I2C;
import static org.objectweb.asm.Opcodes.I2D;
import static org.objectweb.asm.Opcodes.I2F;
import static org.objectweb.asm.Opcodes.I2L;
import static org.objectweb.asm.Opcodes.I2S;
import static org.objectweb.asm.Opcodes.IADD;
import static org.objectweb.asm.Opcodes.IALOAD;
import static org.objectweb.asm.Opcodes.IAND;
import static org.objectweb.asm.Opcodes.IASTORE;
import static org.objectweb.asm.Opcodes.ICONST_0;
import static org.objectweb.asm.Opcodes.ICONST_1;
import static org.objectweb.asm.Opcodes.ICONST_2;
import static org.objectweb.asm.Opcodes.ICONST_3;
import static org.objectweb.asm.Opcodes.ICONST_4;
import static org.objectweb.asm.Opcodes.ICONST_5;
import static org.objectweb.asm.Opcodes.ICONST_M1;
import static org.objectweb.asm.Opcodes.IDIV;
import static org.objectweb.asm.Opcodes.IFEQ;
import static org.objectweb.asm.Opcodes.IFGE;
import static org.objectweb.asm.Opcodes.IFGT;
import static org.objectweb.asm.Opcodes.IFLE;
import static org.objectweb.asm.Opcodes.IFLT;
import static org.objectweb.asm.Opcodes.IFNE;
import static org.objectweb.asm.Opcodes.IFNONNULL;
import static org.objectweb.asm.Opcodes.IFNULL;
import static org.objectweb.asm.Opcodes.IF_ACMPEQ;
import static org.objectweb.asm.Opcodes.IF_ACMPNE;
import static org.objectweb.asm.Opcodes.IF_ICMPEQ;
import static org.objectweb.asm.Opcodes.IF_ICMPGE;
import static org.objectweb.asm.Opcodes.IF_ICMPGT;
import static org.objectweb.asm.Opcodes.IF_ICMPLE;
import static org.objectweb.asm.Opcodes.IF_ICMPLT;
import static org.objectweb.asm.Opcodes.IF_ICMPNE;
import static org.objectweb.asm.Opcodes.IINC;
import static org.objectweb.asm.Opcodes.ILOAD;
import static org.objectweb.asm.Opcodes.IMUL;
import static org.objectweb.asm.Opcodes.INEG;
import static org.objectweb.asm.Opcodes.INSTANCEOF;
import static org.objectweb.asm.Opcodes.INVOKEDYNAMIC;
import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.IOR;
import static org.objectweb.asm.Opcodes.IREM;
import static org.objectweb.asm.Opcodes.IRETURN;
import static org.objectweb.asm.Opcodes.ISHL;
import static org.objectweb.asm.Opcodes.ISHR;
import static org.objectweb.asm.Opcodes.ISTORE;
import static org.objectweb.asm.Opcodes.ISUB;
import static org.objectweb.asm.Opcodes.IUSHR;
import static org.objectweb.asm.Opcodes.IXOR;
import static org.objectweb.asm.Opcodes.JSR;
import static org.objectweb.asm.Opcodes.L2D;
import static org.objectweb.asm.Opcodes.L2F;
import static org.objectweb.asm.Opcodes.L2I;
import static org.objectweb.asm.Opcodes.LADD;
import static org.objectweb.asm.Opcodes.LALOAD;
import static org.objectweb.asm.Opcodes.LAND;
import static org.objectweb.asm.Opcodes.LASTORE;
import static org.objectweb.asm.Opcodes.LCMP;
import static org.objectweb.asm.Opcodes.LCONST_0;
import static org.objectweb.asm.Opcodes.LCONST_1;
import static org.objectweb.asm.Opcodes.LDC;
import static org.objectweb.asm.Opcodes.LDIV;
import static org.objectweb.asm.Opcodes.LLOAD;
import static org.objectweb.asm.Opcodes.LMUL;
import static org.objectweb.asm.Opcodes.LNEG;
import static org.objectweb.asm.Opcodes.LOOKUPSWITCH;
import static org.objectweb.asm.Opcodes.LOR;
import static org.objectweb.asm.Opcodes.LREM;
import static org.objectweb.asm.Opcodes.LRETURN;
import static org.objectweb.asm.Opcodes.LSHL;
import static org.objectweb.asm.Opcodes.LSHR;
import static org.objectweb.asm.Opcodes.LSTORE;
import static org.objectweb.asm.Opcodes.LSUB;
import static org.objectweb.asm.Opcodes.LUSHR;
import static org.objectweb.asm.Opcodes.LXOR;
import static org.objectweb.asm.Opcodes.MONITORENTER;
import static org.objectweb.asm.Opcodes.MONITOREXIT;
import static org.objectweb.asm.Opcodes.MULTIANEWARRAY;
import static org.objectweb.asm.Opcodes.NEW;
import static org.objectweb.asm.Opcodes.NEWARRAY;
import static org.objectweb.asm.Opcodes.NOP;
import static org.objectweb.asm.Opcodes.POP;
import static org.objectweb.asm.Opcodes.POP2;
import static org.objectweb.asm.Opcodes.PUTFIELD;
import static org.objectweb.asm.Opcodes.PUTSTATIC;
import static org.objectweb.asm.Opcodes.RET;
import static org.objectweb.asm.Opcodes.RETURN;
import static org.objectweb.asm.Opcodes.SALOAD;
import static org.objectweb.asm.Opcodes.SASTORE;
import static org.objectweb.asm.Opcodes.SIPUSH;
import static org.objectweb.asm.Opcodes.SWAP;
import static org.objectweb.asm.Opcodes.TABLESWITCH;
import static org.objectweb.asm.tree.AbstractInsnNode.FIELD_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.FRAME;
import static org.objectweb.asm.tree.AbstractInsnNode.IINC_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.INT_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.INVOKE_DYNAMIC_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.JUMP_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.LABEL;
import static org.objectweb.asm.tree.AbstractInsnNode.LDC_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.LOOKUPSWITCH_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.METHOD_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.MULTIANEWARRAY_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.TABLESWITCH_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.TYPE_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.VAR_INSN;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.IincInsnNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.LineNumberNode;
import org.objectweb.asm.tree.MultiANewArrayInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
public class ASMReader {
public static Map<String, Integer> opCodes = new HashMap<String, Integer>();
public static byte[] TYPE;
static {
opCodes.put("NOP", NOP);
opCodes.put("ACONST_NULL", ACONST_NULL);
opCodes.put("ICONST_M1", ICONST_M1);
opCodes.put("ICONST_0", ICONST_0);
opCodes.put("ICONST_1", ICONST_1);
opCodes.put("ICONST_2", ICONST_2);
opCodes.put("ICONST_3", ICONST_3);
opCodes.put("ICONST_4", ICONST_4);
opCodes.put("ICONST_5", ICONST_5);
opCodes.put("LCONST_0", LCONST_0);
opCodes.put("LCONST_1", LCONST_1);
opCodes.put("FCONST_0", FCONST_0);
opCodes.put("FCONST_1", FCONST_1);
opCodes.put("FCONST_2", FCONST_2);
opCodes.put("DCONST_0", DCONST_0);
opCodes.put("DCONST_1", DCONST_1);
opCodes.put("BIPUSH", BIPUSH);
opCodes.put("SIPUSH", SIPUSH);
opCodes.put("LDC", LDC);
opCodes.put("ILOAD", ILOAD);
opCodes.put("LLOAD", LLOAD);
opCodes.put("FLOAD", FLOAD);
opCodes.put("DLOAD", DLOAD);
opCodes.put("ALOAD", ALOAD);
opCodes.put("IALOAD", IALOAD);
opCodes.put("LALOAD", LALOAD);
opCodes.put("FALOAD", FALOAD);
opCodes.put("DALOAD", DALOAD);
opCodes.put("AALOAD", AALOAD);
opCodes.put("BALOAD", BALOAD);
opCodes.put("CALOAD", CALOAD);
opCodes.put("SALOAD", SALOAD);
opCodes.put("ISTORE", ISTORE);
opCodes.put("LSTORE", LSTORE);
opCodes.put("FSTORE", FSTORE);
opCodes.put("DSTORE", DSTORE);
opCodes.put("ASTORE", ASTORE);
opCodes.put("IASTORE", IASTORE);
opCodes.put("LASTORE", LASTORE);
opCodes.put("FASTORE", FASTORE);
opCodes.put("DASTORE", DASTORE);
opCodes.put("AASTORE", AASTORE);
opCodes.put("BASTORE", BASTORE);
opCodes.put("CASTORE", CASTORE);
opCodes.put("SASTORE", SASTORE);
opCodes.put("POP", POP);
opCodes.put("POP2", POP2);
opCodes.put("DUP", DUP);
opCodes.put("DUP_X1", DUP_X1);
opCodes.put("DUP_X2", DUP_X2);
opCodes.put("DUP2", DUP2);
opCodes.put("DUP2_X1", DUP2_X1);
opCodes.put("DUP2_X2", DUP2_X2);
opCodes.put("SWAP", SWAP);
opCodes.put("IADD", IADD);
opCodes.put("LADD", LADD);
opCodes.put("FADD", FADD);
opCodes.put("DADD", DADD);
opCodes.put("ISUB", ISUB);
opCodes.put("LSUB", LSUB);
opCodes.put("FSUB", FSUB);
opCodes.put("DSUB", DSUB);
opCodes.put("IMUL", IMUL);
opCodes.put("LMUL", LMUL);
opCodes.put("FMUL", FMUL);
opCodes.put("DMUL", DMUL);
opCodes.put("IDIV", IDIV);
opCodes.put("LDIV", LDIV);
opCodes.put("FDIV", FDIV);
opCodes.put("DDIV", DDIV);
opCodes.put("IREM", IREM);
opCodes.put("LREM", LREM);
opCodes.put("FREM", FREM);
opCodes.put("DREM", DREM);
opCodes.put("INEG", INEG);
opCodes.put("LNEG", LNEG);
opCodes.put("FNEG", FNEG);
opCodes.put("DNEG", DNEG);
opCodes.put("ISHL", ISHL);
opCodes.put("LSHL", LSHL);
opCodes.put("ISHR", ISHR);
opCodes.put("LSHR", LSHR);
opCodes.put("IUSHR", IUSHR);
opCodes.put("LUSHR", LUSHR);
opCodes.put("IAND", IAND);
opCodes.put("LAND", LAND);
opCodes.put("IOR", IOR);
opCodes.put("LOR", LOR);
opCodes.put("IXOR", IXOR);
opCodes.put("LXOR", LXOR);
opCodes.put("IINC", IINC);
opCodes.put("I2L", I2L);
opCodes.put("I2F", I2F);
opCodes.put("I2D", I2D);
opCodes.put("L2I", L2I);
opCodes.put("L2F", L2F);
opCodes.put("L2D", L2D);
opCodes.put("F2I", F2I);
opCodes.put("F2L", F2L);
opCodes.put("F2D", F2D);
opCodes.put("D2I", D2I);
opCodes.put("D2L", D2L);
opCodes.put("D2F", D2F);
opCodes.put("I2B", I2B);
opCodes.put("I2C", I2C);
opCodes.put("I2S", I2S);
opCodes.put("LCMP", LCMP);
opCodes.put("FCMPL", FCMPL);
opCodes.put("FCMPG", FCMPG);
opCodes.put("DCMPL", DCMPL);
opCodes.put("DCMPG", DCMPG);
opCodes.put("IFEQ", IFEQ);
opCodes.put("IFNE", IFNE);
opCodes.put("IFLT", IFLT);
opCodes.put("IFGE", IFGE);
opCodes.put("IFGT", IFGT);
opCodes.put("IFLE", IFLE);
opCodes.put("IF_ICMPEQ", IF_ICMPEQ);
opCodes.put("IF_ICMPNE", IF_ICMPNE);
opCodes.put("IF_ICMPLT", IF_ICMPLT);
opCodes.put("IF_ICMPGE", IF_ICMPGE);
opCodes.put("IF_ICMPGT", IF_ICMPGT);
opCodes.put("IF_ICMPLE", IF_ICMPLE);
opCodes.put("IF_ACMPEQ", IF_ACMPEQ);
opCodes.put("IF_ACMPNE", IF_ACMPNE);
opCodes.put("GOTO", GOTO);
opCodes.put("JSR", JSR);
opCodes.put("RET", RET);
opCodes.put("TABLESWITCH", TABLESWITCH);
opCodes.put("LOOKUPSWITCH", LOOKUPSWITCH);
opCodes.put("IRETURN", IRETURN);
opCodes.put("LRETURN", LRETURN);
opCodes.put("FRETURN", FRETURN);
opCodes.put("DRETURN", DRETURN);
opCodes.put("ARETURN", ARETURN);
opCodes.put("RETURN", RETURN);
opCodes.put("GETSTATIC", GETSTATIC);
opCodes.put("PUTSTATIC", PUTSTATIC);
opCodes.put("GETFIELD", GETFIELD);
opCodes.put("PUTFIELD", PUTFIELD);
opCodes.put("INVOKEVIRTUAL", INVOKEVIRTUAL);
opCodes.put("INVOKESPECIAL", INVOKESPECIAL);
opCodes.put("INVOKESTATIC", INVOKESTATIC);
opCodes.put("INVOKEINTERFACE", INVOKEINTERFACE);
opCodes.put("INVOKEDYNAMIC", INVOKEDYNAMIC);
opCodes.put("NEW", NEW);
opCodes.put("NEWARRAY", NEWARRAY);
opCodes.put("ANEWARRAY", ANEWARRAY);
opCodes.put("ARRAYLENGTH", ARRAYLENGTH);
opCodes.put("ATHROW", ATHROW);
opCodes.put("CHECKCAST", CHECKCAST);
opCodes.put("INSTANCEOF", INSTANCEOF);
opCodes.put("MONITORENTER", MONITORENTER);
opCodes.put("MONITOREXIT", MONITOREXIT);
opCodes.put("MULTIANEWARRAY", MULTIANEWARRAY);
opCodes.put("IFNULL", IFNULL);
opCodes.put("IFNONNULL", IFNONNULL);
// derived from classWriter, mapped to AbstractInsnNode
TYPE = new byte[200];
String s = "AAAAAAAAAAAAAAAABBJ__CCCCC____________________AAAAAAAACC"
+ "CCC____________________AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAHHHHHHHHHHHHHHHHCLMAA"
+ "AAAAEEEEFFFFGDBDAADDAA_NHH";
for (int i = 0; i < s.length(); i++) TYPE[i] = (byte) (s.charAt(i) - 'A');
}
public static Map<String, ASMBlock> loadResource(String res) {
return loadResource(ASMHelper.class.getResourceAsStream(res), res);
}
public static Map<String, ASMBlock> loadResource(InputStream in, String res) {
HashMap<String, ASMBlock> blocks = new HashMap<String, ASMBlock>();
String current = "unnamed";
ASMBlock block = new ASMBlock();
try {
BufferedReader r = new BufferedReader(new InputStreamReader(in));
String line;
while ((line = r.readLine()) != null) {
{
int hpos = line.indexOf('#');
if (hpos >= 0) line = line.substring(0, hpos);
}
line = line.trim();
if (line.length() == 0) continue;
if (line.startsWith("list ")) {
if (block.list.size() > 0) blocks.put(current, block);
current = line.substring(5);
block = new ASMBlock();
continue;
}
try {
AbstractInsnNode insn = null;
String[] split = line.replace(" : ", ":").split(" ");
Integer i_opcode = opCodes.get(split[0]);
if (i_opcode == null) {
if (split[0].equals("LINENUMBER"))
insn = new LineNumberNode(Integer.parseInt(split[1]), block.getOrAdd(split[2]));
else if (split[0].startsWith("L")) insn = block.getOrAdd(split[0]);
else throw new Exception("Unknown opcode " + split[0]);
} else {
int opcode = i_opcode;
switch (TYPE[opcode]) {
case INSN:
insn = new InsnNode(opcode);
break;
case INT_INSN:
insn = new IntInsnNode(opcode, Integer.parseInt(split[1]));
break;
case VAR_INSN:
insn = new VarInsnNode(opcode, Integer.parseInt(split[1]));
break;
case TYPE_INSN:
insn = new ObfMapping(split[1]).toClassloading().toInsn(opcode);
break;
case FIELD_INSN:
case METHOD_INSN:
StringBuilder sb = new StringBuilder();
for (int i = 1; i < split.length; i++) sb.append(split[i]);
insn = ObfMapping.fromDesc(sb.toString()).toClassloading().toInsn(opcode);
break;
case INVOKE_DYNAMIC_INSN:
throw new Exception("Found INVOKEDYNAMIC while reading");
case JUMP_INSN:
insn = new JumpInsnNode(opcode, block.getOrAdd(split[1]));
break;
case LDC_INSN:
String cst = split[1];
if (cst.equals("*")) insn = new LdcInsnNode(null);
else if (cst.endsWith("\"")) insn = new LdcInsnNode(cst.substring(1, cst.length() - 1));
else if (cst.endsWith("L"))
insn = new LdcInsnNode(Long.valueOf(cst.substring(0, cst.length() - 1)));
else if (cst.endsWith("F"))
insn = new LdcInsnNode(Float.valueOf(cst.substring(0, cst.length() - 1)));
else if (cst.endsWith("D"))
insn = new LdcInsnNode(Double.valueOf(cst.substring(0, cst.length() - 1)));
else if (cst.contains(".")) insn = new LdcInsnNode(Double.valueOf(cst));
else insn = new LdcInsnNode(Integer.valueOf(cst));
break;
case IINC_INSN:
insn = new IincInsnNode(opcode, Integer.parseInt(split[1]));
break;
case LABEL:
throw new Exception("Use L# for labels");
case TABLESWITCH_INSN:
case LOOKUPSWITCH_INSN:
throw new Exception("I don't know how to deal with this insn type");
case MULTIANEWARRAY_INSN:
insn = new MultiANewArrayInsnNode(split[1], Integer.parseInt(split[2]));
break;
case FRAME:
throw new Exception("Use ClassWriter.COMPUTE_FRAMES");
}
}
if (insn != null) block.list.add(insn);
} catch (Exception e) {
System.err.println("Error while reading ASM Block " + current + " from " + res + ", line: " + line);
e.printStackTrace();
}
}
r.close();
if (block.list.size() > 0) blocks.put(current, block);
} catch (IOException e) {
throw new RuntimeException("Failed to read ASM resource: " + res, e);
}
return blocks;
}
}
@@ -0,0 +1,29 @@
package codechicken.lib.asm;
import org.objectweb.asm.ClassWriter;
public class CC_ClassWriter extends ClassWriter {
private final boolean runtime;
public CC_ClassWriter(int flags) {
this(flags, false);
}
public CC_ClassWriter(int flags, boolean runtime) {
super(flags);
this.runtime = runtime;
}
@Override
protected String getCommonSuperClass(String type1, String type2) {
String c = type1.replace('/', '.');
String d = type2.replace('/', '.');
if (ClassHeirachyManager.classExtends(d, c)) return type1;
if (ClassHeirachyManager.classExtends(c, d)) return type2;
do {
c = ClassHeirachyManager.getSuperClass(c, runtime);
} while (!ClassHeirachyManager.classExtends(d, c));
return c.replace('.', '/');
}
}
@@ -0,0 +1,109 @@
package codechicken.lib.asm;
/***
* This Class is derived from the ASM ClassReader
* <p>
* ASM: a very small and fast Java bytecode manipulation framework Copyright (c) 2000-2011 INRIA, France Telecom All
* rights reserved.
* <p>
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of
* conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation and/or other materials provided with the
* distribution. 3. Neither the name of the copyright holders nor the names of its contributors may be used to endorse
* or promote products derived from this software without specific prior written permission.
* <p>
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import java.nio.charset.StandardCharsets;
import org.objectweb.asm.Opcodes;
/**
* Using this class to search for a (single) String reference is > 40 times faster than parsing a class with a
* ClassReader + ClassNode while using way less RAM
*/
public class ClassConstantPoolParser {
private static final int UTF8 = 1;
private static final int INT = 3;
private static final int FLOAT = 4;
private static final int LONG = 5;
private static final int DOUBLE = 6;
private static final int FIELD = 9;
private static final int METH = 10;
private static final int IMETH = 11;
private static final int NAME_TYPE = 12;
private static final int HANDLE = 15;
private static final int INDY = 18;
private final byte[][] BYTES_TO_SEARCH;
public ClassConstantPoolParser(String... strings) {
BYTES_TO_SEARCH = new byte[strings.length][];
for (int i = 0; i < BYTES_TO_SEARCH.length; i++) {
BYTES_TO_SEARCH[i] = strings[i].getBytes(StandardCharsets.UTF_8);
}
}
/**
* Returns true if the constant pool of the class represented by this byte array contains one of the Strings we are
* looking for
*/
public boolean find(byte[] basicClass) {
if (basicClass == null || basicClass.length == 0) {
return false;
}
// checks the class version
if (readShort(6, basicClass) > Opcodes.V1_8) {
return false;
}
// parses the constant pool
int n = readUnsignedShort(8, basicClass);
int index = 10;
for (int i = 1; i < n; ++i) {
int size;
switch (basicClass[index]) {
case FIELD, METH, IMETH, INT, FLOAT, NAME_TYPE, INDY -> size = 5;
case LONG, DOUBLE -> {
size = 9;
++i;
}
case UTF8 -> {
final int strLen = readUnsignedShort(index + 1, basicClass);
size = 3 + strLen;
label: for (byte[] bytes : BYTES_TO_SEARCH) {
if (strLen == bytes.length) {
for (int j = index + 3; j < index + 3 + strLen; j++) {
if (basicClass[j] != bytes[j - (index + 3)]) {
break label;
}
}
return true;
}
}
}
case HANDLE -> size = 4;
default -> size = 3;
}
index += size;
}
return false;
}
private static short readShort(final int index, byte[] basicClass) {
return (short) (((basicClass[index] & 0xFF) << 8) | (basicClass[index + 1] & 0xFF));
}
private static int readUnsignedShort(final int index, byte[] basicClass) {
return ((basicClass[index] & 0xFF) << 8) | (basicClass[index + 1] & 0xFF);
}
}
@@ -0,0 +1,154 @@
package codechicken.lib.asm;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraft.launchwrapper.Launch;
import net.minecraft.launchwrapper.LaunchClassLoader;
import org.objectweb.asm.tree.ClassNode;
import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper;
/**
* This is added as a class transformer if CodeChickenCore is installed. Adding it as a class transformer will speed
* evaluation up slightly by automatically caching superclasses when they are first loaded.
*/
public class ClassHeirachyManager implements IClassTransformer {
static {
ASMInit.init();
}
public static class SuperCache {
String superclass;
public HashSet<String> parents = new HashSet<String>();
private boolean flattened;
public void add(String parent) {
parents.add(parent);
}
public void flatten() {
if (flattened) return;
for (String s : new ArrayList<String>(parents)) {
SuperCache c = declareClass(s);
if (c != null) {
c.flatten();
parents.addAll(c.parents);
}
}
flattened = true;
}
}
public static HashMap<String, SuperCache> superclasses = new HashMap<String, SuperCache>();
private static LaunchClassLoader cl = Launch.classLoader;
public static String toKey(String name) {
if (ObfMapping.obfuscated)
name = FMLDeobfuscatingRemapper.INSTANCE.map(name.replace('.', '/')).replace('/', '.');
return name;
}
public static String unKey(String name) {
if (ObfMapping.obfuscated)
name = FMLDeobfuscatingRemapper.INSTANCE.unmap(name.replace('.', '/')).replace('/', '.');
return name;
}
/**
* @param name The class in question
* @param superclass The class being extended
* @return true if clazz extends, either directly or indirectly, superclass.
*/
public static boolean classExtends(String name, String superclass) {
name = toKey(name);
superclass = toKey(superclass);
if (name.equals(superclass)) return true;
SuperCache cache = declareClass(name);
if (cache == null) // just can't handle this
return false;
cache.flatten();
return cache.parents.contains(superclass);
}
private static SuperCache declareClass(String name) {
name = toKey(name);
SuperCache cache = superclasses.get(name);
if (cache != null) return cache;
try {
byte[] bytes = cl.getClassBytes(unKey(name));
if (bytes != null) cache = declareASM(bytes);
} catch (Exception e) {}
if (cache != null) return cache;
try {
cache = declareReflection(name);
} catch (ClassNotFoundException e) {}
return cache;
}
private static SuperCache declareReflection(String name) throws ClassNotFoundException {
Class<?> aclass = Class.forName(name);
SuperCache cache = getOrCreateCache(name);
if (aclass.isInterface()) cache.superclass = "java.lang.Object";
else if (name.equals("java.lang.Object")) return cache;
else cache.superclass = toKey(aclass.getSuperclass().getName());
cache.add(cache.superclass);
for (Class<?> iclass : aclass.getInterfaces()) cache.add(toKey(iclass.getName()));
return cache;
}
private static SuperCache declareASM(byte[] bytes) {
ClassNode node = ASMHelper.createClassNode(bytes);
String name = toKey(node.name);
SuperCache cache = getOrCreateCache(name);
cache.superclass = toKey(node.superName.replace('/', '.'));
cache.add(cache.superclass);
for (String iclass : node.interfaces) cache.add(toKey(iclass.replace('/', '.')));
return cache;
}
@Override
public byte[] transform(String name, String tname, byte[] bytes) {
if (bytes == null) return null;
if (!superclasses.containsKey(tname)) declareASM(bytes);
return bytes;
}
public static SuperCache getOrCreateCache(String name) {
SuperCache cache = superclasses.get(name);
if (cache == null) superclasses.put(name, cache = new SuperCache());
return cache;
}
public static String getSuperClass(String name, boolean runtime) {
name = toKey(name);
SuperCache cache = declareClass(name);
if (cache == null) return "java.lang.Object";
cache.flatten();
String s = cache.superclass;
if (!runtime) s = FMLDeobfuscatingRemapper.INSTANCE.unmap(s);
return s;
}
}
@@ -0,0 +1,36 @@
package codechicken.lib.asm;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.MethodNode;
public class ImportantInsnVisitor extends ClassVisitor {
public class ImportantInsnMethodVisitor extends MethodVisitor {
MethodVisitor delegate;
public ImportantInsnMethodVisitor(int access, String name, String desc, String signature, String[] exceptions) {
super(Opcodes.ASM4, new MethodNode(access, name, desc, signature, exceptions));
delegate = cv.visitMethod(access, name, desc, signature, exceptions);
}
@Override
public void visitEnd() {
super.visitEnd();
MethodNode mnode = (MethodNode) mv;
mnode.instructions = InsnComparator.getImportantList(mnode.instructions);
mnode.accept(delegate);
}
}
public ImportantInsnVisitor(ClassVisitor cv) {
super(Opcodes.ASM4, cv);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
return new ImportantInsnMethodVisitor(access, name, desc, signature, exceptions);
}
}
@@ -0,0 +1,210 @@
package codechicken.lib.asm;
import static org.objectweb.asm.tree.AbstractInsnNode.FIELD_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.FRAME;
import static org.objectweb.asm.tree.AbstractInsnNode.IINC_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.INT_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.JUMP_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.LABEL;
import static org.objectweb.asm.tree.AbstractInsnNode.LDC_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.LINE;
import static org.objectweb.asm.tree.AbstractInsnNode.LOOKUPSWITCH_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.METHOD_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.TABLESWITCH_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.TYPE_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.VAR_INSN;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.IincInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.LookupSwitchInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.TableSwitchInsnNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
import com.google.common.base.Function;
import com.google.common.collect.Maps;
public class InsnComparator {
public static boolean varInsnEqual(VarInsnNode insn1, VarInsnNode insn2) {
return insn1.var == -1 || insn2.var == -1 || insn1.var == insn2.var;
}
public static boolean methodInsnEqual(MethodInsnNode insn1, MethodInsnNode insn2) {
return insn1.owner.equals(insn2.owner) && insn1.name.equals(insn2.name) && insn1.desc.equals(insn2.desc);
}
public static boolean fieldInsnEqual(FieldInsnNode insn1, FieldInsnNode insn2) {
return insn1.owner.equals(insn2.owner) && insn1.name.equals(insn2.name) && insn1.desc.equals(insn2.desc);
}
public static boolean ldcInsnEqual(LdcInsnNode insn1, LdcInsnNode insn2) {
return insn1.cst == null || insn2.cst == null || insn1.cst.equals(insn2.cst);
}
public static boolean typeInsnEqual(TypeInsnNode insn1, TypeInsnNode insn2) {
return insn1.desc.equals("*") || insn2.desc.equals("*") || insn1.desc.equals(insn2.desc);
}
public static boolean iincInsnEqual(IincInsnNode node1, IincInsnNode node2) {
return node1.var == node2.var && node1.incr == node2.incr;
}
public static boolean intInsnEqual(IntInsnNode node1, IntInsnNode node2) {
return node1.operand == -1 || node2.operand == -1 || node1.operand == node2.operand;
}
public static boolean insnEqual(AbstractInsnNode node1, AbstractInsnNode node2) {
if (node1.getOpcode() != node2.getOpcode()) return false;
switch (node2.getType()) {
case VAR_INSN:
return varInsnEqual((VarInsnNode) node1, (VarInsnNode) node2);
case TYPE_INSN:
return typeInsnEqual((TypeInsnNode) node1, (TypeInsnNode) node2);
case FIELD_INSN:
return fieldInsnEqual((FieldInsnNode) node1, (FieldInsnNode) node2);
case METHOD_INSN:
return methodInsnEqual((MethodInsnNode) node1, (MethodInsnNode) node2);
case LDC_INSN:
return ldcInsnEqual((LdcInsnNode) node1, (LdcInsnNode) node2);
case IINC_INSN:
return iincInsnEqual((IincInsnNode) node1, (IincInsnNode) node2);
case INT_INSN:
return intInsnEqual((IntInsnNode) node1, (IntInsnNode) node2);
default:
return true;
}
}
public static boolean insnImportant(AbstractInsnNode insn, Set<LabelNode> controlFlowLabels) {
switch (insn.getType()) {
case LINE:
case FRAME:
return false;
case LABEL:
return controlFlowLabels.contains(insn);
default:
return true;
}
}
public static Set<LabelNode> getControlFlowLabels(InsnListSection list) {
return getControlFlowLabels(list.list);
}
public static Set<LabelNode> getControlFlowLabels(InsnList list) {
HashSet<LabelNode> controlFlowLabels = new HashSet<LabelNode>();
for (AbstractInsnNode insn = list.getFirst(); insn != null; insn = insn.getNext()) {
switch (insn.getType()) {
case JUMP_INSN:
JumpInsnNode jinsn = (JumpInsnNode) insn;
controlFlowLabels.add(jinsn.label);
break;
case TABLESWITCH_INSN:
TableSwitchInsnNode tsinsn = (TableSwitchInsnNode) insn;
controlFlowLabels.add(tsinsn.dflt);
for (LabelNode label : tsinsn.labels) controlFlowLabels.add(label);
break;
case LOOKUPSWITCH_INSN:
LookupSwitchInsnNode lsinsn = (LookupSwitchInsnNode) insn;
controlFlowLabels.add(lsinsn.dflt);
for (LabelNode label : lsinsn.labels) controlFlowLabels.add(label);
break;
}
}
return controlFlowLabels;
}
public static InsnList getImportantList(InsnList list) {
return getImportantList(new InsnListSection(list)).list;
}
public static InsnListSection getImportantList(InsnListSection list) {
if (list.size() == 0) return list;
Set<LabelNode> controlFlowLabels = getControlFlowLabels(list);
Map<LabelNode, LabelNode> labelMap = Maps.asMap(controlFlowLabels, new Function<LabelNode, LabelNode>() {
@Override
public LabelNode apply(LabelNode input) {
return input;
}
});
InsnListSection importantNodeList = new InsnListSection();
for (AbstractInsnNode insn : list)
if (insnImportant(insn, controlFlowLabels)) importantNodeList.add(insn.clone(labelMap));
return importantNodeList;
}
public static List<InsnListSection> find(InsnListSection haystack, InsnListSection needle) {
Set<LabelNode> controlFlowLabels = getControlFlowLabels(haystack);
LinkedList<InsnListSection> list = new LinkedList<InsnListSection>();
for (int start = 0; start <= haystack.size() - needle.size(); start++) {
InsnListSection section = matches(haystack.drop(start), needle, controlFlowLabels);
if (section != null) {
list.add(section);
start = section.end - 1;
}
}
return list;
}
public static List<InsnListSection> find(InsnList haystack, InsnListSection needle) {
return find(new InsnListSection(haystack), needle);
}
public static InsnListSection matches(InsnListSection haystack, InsnListSection needle,
Set<LabelNode> controlFlowLabels) {
int h = 0, n = 0;
for (; h < haystack.size() && n < needle.size(); h++) {
AbstractInsnNode insn = haystack.get(h);
if (!insnImportant(insn, controlFlowLabels)) continue;
if (!insnEqual(haystack.get(h), needle.get(n))) return null;
n++;
}
if (n != needle.size()) return null;
return haystack.take(h);
}
public static InsnListSection findOnce(InsnListSection haystack, InsnListSection needle) {
List<InsnListSection> list = find(haystack, needle);
if (list.size() != 1) throw new RuntimeException(
"Needle found " + list.size() + " times in Haystack:\n" + haystack + "\n\n" + needle);
return list.get(0);
}
public static InsnListSection findOnce(InsnList haystack, InsnListSection needle) {
return findOnce(new InsnListSection(haystack), needle);
}
public static List<InsnListSection> findN(InsnListSection haystack, InsnListSection needle) {
List<InsnListSection> list = find(haystack, needle);
if (list.isEmpty()) throw new RuntimeException("Needle not found in Haystack:\n" + haystack + "\n\n" + needle);
return list;
}
public static List<InsnListSection> findN(InsnList haystack, InsnListSection needle) {
return findN(new InsnListSection(haystack), needle);
}
}
@@ -0,0 +1,235 @@
package codechicken.lib.asm;
import static org.objectweb.asm.tree.AbstractInsnNode.FRAME;
import static org.objectweb.asm.tree.AbstractInsnNode.JUMP_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.LABEL;
import static org.objectweb.asm.tree.AbstractInsnNode.LOOKUPSWITCH_INSN;
import static org.objectweb.asm.tree.AbstractInsnNode.TABLESWITCH_INSN;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FrameNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LookupSwitchInsnNode;
import org.objectweb.asm.tree.TableSwitchInsnNode;
import org.objectweb.asm.util.Textifier;
import org.objectweb.asm.util.TraceMethodVisitor;
/**
* A section of an InsnList, may become invalid if the insn list is modified
*/
public class InsnListSection implements Iterable<AbstractInsnNode> {
private class InsnListSectionIterator implements Iterator<AbstractInsnNode> {
int i = 0;
@Override
public boolean hasNext() {
return i < size();
}
@Override
public AbstractInsnNode next() {
return get(i++);
}
@Override
public void remove() {
InsnListSection.this.remove(--i);
}
}
public InsnList list;
public int start;
public int end;
public InsnListSection(InsnList list, int start, int end) {
this.list = list;
this.start = start;
this.end = end;
}
public InsnListSection(InsnList list, AbstractInsnNode first, AbstractInsnNode last) {
this(list, list.indexOf(first), list.indexOf(last) + 1);
}
public InsnListSection(InsnList list) {
this(list, 0, list.size());
}
public InsnListSection() {
this(new InsnList());
}
public void accept(MethodVisitor mv) {
for (AbstractInsnNode insn : this) insn.accept(mv);
}
public AbstractInsnNode getFirst() {
return size() == 0 ? null : list.get(start);
}
public AbstractInsnNode getLast() {
return size() == 0 ? null : list.get(end - 1);
}
public int size() {
return end - start;
}
public AbstractInsnNode get(int i) {
return list.get(start + i);
}
public void set(int i, AbstractInsnNode insn) {
list.set(get(i), insn);
}
public void remove(int i) {
list.remove(get(i));
end--;
}
public void replace(AbstractInsnNode location, AbstractInsnNode insn) {
list.set(location, insn);
}
public void add(AbstractInsnNode insn) {
list.add(insn);
end++;
}
public void insertBefore(InsnList insns) {
int s = insns.size();
if (this.list.size() == 0) list.insert(insns);
else list.insertBefore(list.get(start), insns);
start += s;
end += s;
}
public void insert(InsnList insns) {
if (end == 0) list.insert(insns);
else list.insert(list.get(end - 1), insns);
}
public void replace(InsnList insns) {
int s = insns.size();
remove();
insert(insns);
end = start + s;
}
public void remove() {
while (end != start) remove(0);
}
public void setLast(AbstractInsnNode last) {
end = list.indexOf(last) + 1;
}
public void setFirst(AbstractInsnNode first) {
start = list.indexOf(first);
}
public InsnListSection drop(int n) {
return slice(n, size());
}
public InsnListSection take(int n) {
return slice(0, n);
}
public InsnListSection slice(int start, int end) {
return new InsnListSection(list, this.start + start, this.start + end);
}
/**
* Removes leading and trailing labels and line number nodes that don't affect control flow
*
* @return this
*/
public InsnListSection trim(Set<LabelNode> controlFlowLabels) {
while (start < end && !InsnComparator.insnImportant(getFirst(), controlFlowLabels)) start++;
while (start < end && !InsnComparator.insnImportant(getLast(), controlFlowLabels)) end--;
return this;
}
public String toString() {
Textifier t = new Textifier();
accept(new TraceMethodVisitor(t));
StringWriter sw = new StringWriter();
t.print(new PrintWriter(sw));
return sw.toString();
}
public void println() {
System.out.println(toString());
}
public HashMap<LabelNode, LabelNode> identityLabelMap() {
HashMap<LabelNode, LabelNode> labelMap = new HashMap<LabelNode, LabelNode>();
for (AbstractInsnNode insn : this) switch (insn.getType()) {
case LABEL:
labelMap.put((LabelNode) insn, (LabelNode) insn);
break;
case JUMP_INSN:
labelMap.put(((JumpInsnNode) insn).label, ((JumpInsnNode) insn).label);
break;
case LOOKUPSWITCH_INSN:
LookupSwitchInsnNode linsn = (LookupSwitchInsnNode) insn;
labelMap.put(linsn.dflt, linsn.dflt);
for (LabelNode label : linsn.labels) labelMap.put(label, label);
break;
case TABLESWITCH_INSN:
TableSwitchInsnNode tinsn = (TableSwitchInsnNode) insn;
labelMap.put(tinsn.dflt, tinsn.dflt);
for (LabelNode label : tinsn.labels) labelMap.put(label, label);
break;
case FRAME:
FrameNode fnode = (FrameNode) insn;
if (fnode.local != null)
for (Object o : fnode.local) if (o instanceof LabelNode) labelMap.put((LabelNode) o, (LabelNode) o);
if (fnode.stack != null)
for (Object o : fnode.stack) if (o instanceof LabelNode) labelMap.put((LabelNode) o, (LabelNode) o);
break;
}
return labelMap;
}
public Map<LabelNode, LabelNode> cloneLabels() {
Map<LabelNode, LabelNode> labelMap = identityLabelMap();
for (Entry<LabelNode, LabelNode> entry : labelMap.entrySet()) entry.setValue(new LabelNode());
return labelMap;
}
public InsnListSection copy() {
return copy(cloneLabels());
}
public InsnListSection copy(Map<LabelNode, LabelNode> labelMap) {
InsnListSection copy = new InsnListSection();
for (AbstractInsnNode insn : this) copy.add(insn.clone(labelMap));
return copy;
}
@Override
public Iterator<AbstractInsnNode> iterator() {
return new InsnListSectionIterator();
}
}
@@ -0,0 +1,37 @@
package codechicken.lib.asm;
import java.util.Set;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.LocalVariablesSorter;
public class LocalVariablesSorterVisitor extends ClassVisitor {
public Set<ObfMapping> methods;
public String owner;
public LocalVariablesSorterVisitor(Set<ObfMapping> methods, ClassVisitor cv) {
super(Opcodes.ASM4, cv);
this.methods = methods;
}
public LocalVariablesSorterVisitor(ClassVisitor cv) {
this(null, cv);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
owner = name;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
return methods == null || methods.contains(new ObfMapping(owner, name, desc))
? new LocalVariablesSorter(access, desc, mv)
: mv;
}
}
@@ -0,0 +1,292 @@
package codechicken.lib.asm;
import static codechicken.lib.asm.ASMHelper.config;
import static codechicken.lib.asm.ASMHelper.createBytes;
import static codechicken.lib.asm.ASMHelper.dump;
import static codechicken.lib.asm.ASMHelper.findMethod;
import static codechicken.lib.asm.ASMHelper.logger;
import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.MethodNode;
public class ModularASMTransformer {
public static class ClassNodeTransformerList {
List<ClassNodeTransformer> transformers = new LinkedList<ClassNodeTransformer>();
HashSet<ObfMapping> methodsToSort = new HashSet<ObfMapping>();
public void add(ClassNodeTransformer t) {
transformers.add(t);
t.addMethodsToSort(methodsToSort);
}
public byte[] transform(byte[] bytes) {
ClassNode cnode = new ClassNode();
ClassReader reader = new ClassReader(bytes);
ClassVisitor cv = cnode;
if (!methodsToSort.isEmpty()) cv = new LocalVariablesSorterVisitor(methodsToSort, cv);
reader.accept(cv, ClassReader.EXPAND_FRAMES);
try {
int writeFlags = 0;
for (ClassNodeTransformer t : transformers) {
t.transform(cnode);
writeFlags |= t.writeFlags;
}
bytes = createBytes(cnode, writeFlags);
if (config.getTag("dump_asm").getBooleanValue(false))
dump(bytes, new File("asm/ccl_modular/" + cnode.name.replace('/', '#') + ".txt"), false, false);
return bytes;
} catch (RuntimeException e) {
dump(bytes, new File("asm/ccl_modular/" + cnode.name.replace('/', '#') + ".txt"), false, false);
throw e;
}
}
}
public abstract static class ClassNodeTransformer {
public int writeFlags;
public ClassNodeTransformer(int writeFlags) {
this.writeFlags = writeFlags;
}
public ClassNodeTransformer() {
this(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
}
public abstract String className();
public abstract void transform(ClassNode cnode);
public void addMethodsToSort(Set<ObfMapping> set) {}
}
public abstract static class MethodTransformer extends ClassNodeTransformer {
public final ObfMapping method;
public MethodTransformer(ObfMapping method) {
this.method = method.toClassloading();
}
@Override
public String className() {
return method.javaClass();
}
@Override
public void transform(ClassNode cnode) {
MethodNode mv = findMethod(method, cnode);
if (mv == null) throw new RuntimeException("Method not found: " + method);
try {
transform(mv);
} catch (Exception e) {
throw new RuntimeException("Error transforming method: " + method, e);
}
}
public abstract void transform(MethodNode mv);
}
public static class MethodWriter extends ClassNodeTransformer {
public final int access;
public final ObfMapping method;
public final String[] exceptions;
public InsnList list;
public MethodWriter(int access, ObfMapping method) {
this(access, method, null, (InsnList) null);
}
public MethodWriter(int access, ObfMapping method, InsnList list) {
this(access, method, null, list);
}
public MethodWriter(int access, ObfMapping method, ASMBlock block) {
this(access, method, null, block);
}
public MethodWriter(int access, ObfMapping method, String[] exceptions) {
this(access, method, exceptions, (InsnList) null);
}
public MethodWriter(int access, ObfMapping method, String[] exceptions, InsnList list) {
this.access = access;
this.method = method.toClassloading();
this.exceptions = exceptions;
this.list = list;
}
public MethodWriter(int access, ObfMapping method, String[] exceptions, ASMBlock block) {
this(access, method, exceptions, block.rawListCopy());
}
@Override
public String className() {
return method.javaClass();
}
@Override
public void transform(ClassNode cnode) {
MethodNode mv = findMethod(method, cnode);
if (mv == null) mv = (MethodNode) method.visitMethod(cnode, access, exceptions);
else {
mv.access = access;
mv.instructions.clear();
if (mv.localVariables != null) mv.localVariables.clear();
if (mv.tryCatchBlocks != null) mv.tryCatchBlocks.clear();
}
write(mv);
}
public void write(MethodNode mv) {
logger.debug("Writing method " + method);
list.accept(mv);
}
}
public static class MethodInjector extends MethodTransformer {
public ASMBlock needle;
public ASMBlock injection;
public boolean before;
public MethodInjector(ObfMapping method, ASMBlock needle, ASMBlock injection, boolean before) {
super(method);
this.needle = needle;
this.injection = injection;
this.before = before;
}
public MethodInjector(ObfMapping method, ASMBlock injection, boolean before) {
this(method, null, injection, before);
}
public MethodInjector(ObfMapping method, InsnList needle, InsnList injection, boolean before) {
this(method, new ASMBlock(needle), new ASMBlock(injection), before);
}
public MethodInjector(ObfMapping method, InsnList injection, boolean before) {
this(method, null, new ASMBlock(injection), before);
}
@Override
public void addMethodsToSort(Set<ObfMapping> set) {
set.add(method);
}
@Override
public void transform(MethodNode mv) {
if (needle == null) {
logger.debug("Injecting " + (before ? "before" : "after") + " method " + method);
if (before) mv.instructions.insert(injection.rawListCopy());
else mv.instructions.add(injection.rawListCopy());
} else {
for (InsnListSection key : InsnComparator.findN(mv.instructions, needle.list)) {
logger.debug(
"Injecting " + (before ? "before" : "after")
+ " method "
+ method
+ " @ "
+ key.start
+ " - "
+ key.end);
ASMBlock injectBlock = injection.copy().mergeLabels(needle.applyLabels(key));
if (before) key.insertBefore(injectBlock.list.list);
else key.insert(injectBlock.list.list);
}
}
}
}
public static class MethodReplacer extends MethodTransformer {
public ASMBlock needle;
public ASMBlock replacement;
public MethodReplacer(ObfMapping method, ASMBlock needle, ASMBlock replacement) {
super(method);
this.needle = needle;
this.replacement = replacement;
}
public MethodReplacer(ObfMapping method, InsnList needle, InsnList replacement) {
this(method, new ASMBlock(needle), new ASMBlock(replacement));
}
@Override
public void addMethodsToSort(Set<ObfMapping> set) {
set.add(method);
}
@Override
public void transform(MethodNode mv) {
for (InsnListSection key : InsnComparator.findN(mv.instructions, needle.list)) {
logger.debug("Replacing method " + method + " @ " + key.start + " - " + key.end);
ASMBlock replaceBlock = replacement.copy().pullLabels(needle.applyLabels(key));
key.insert(replaceBlock.list.list);
}
}
}
public static class FieldWriter extends ClassNodeTransformer {
public final ObfMapping field;
public final int access;
public final Object value;
public FieldWriter(int access, ObfMapping field, Object value) {
this.field = field.toClassloading();
this.access = access;
this.value = value;
}
public FieldWriter(int access, ObfMapping field) {
this(access, field, null);
}
@Override
public String className() {
return field.javaClass();
}
@Override
public void transform(ClassNode cnode) {
field.visitField(cnode, access, value);
}
}
public HashMap<String, ClassNodeTransformerList> transformers = new HashMap<String, ClassNodeTransformerList>();
public void add(ClassNodeTransformer t) {
ClassNodeTransformerList list = transformers.get(t.className());
if (list == null) transformers.put(t.className(), list = new ClassNodeTransformerList());
list.add(t);
}
public byte[] transform(String name, byte[] bytes) {
if (bytes == null) return null;
ClassNodeTransformerList list = transformers.get(name);
return list == null ? bytes : list.transform(bytes);
}
}
@@ -0,0 +1,401 @@
package codechicken.lib.asm;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import javax.swing.JFileChooser;
import net.minecraft.launchwrapper.Launch;
import net.minecraftforge.common.ForgeVersion;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.commons.Remapper;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
import com.google.common.base.Charsets;
import com.google.common.base.Objects;
import com.google.common.io.LineProcessor;
import com.google.common.io.Resources;
import codechicken.lib.config.ConfigTag;
import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper;
import cpw.mods.fml.relauncher.FMLInjectionData;
public class ObfMapping {
public static class ObfRemapper extends Remapper {
private final HashMap<String, String> fields = new HashMap<>();
private final HashMap<String, String> funcs = new HashMap<>();
public ObfRemapper() {
try {
Field rawFieldMapsField = FMLDeobfuscatingRemapper.class.getDeclaredField("rawFieldMaps");
Field rawMethodMapsField = FMLDeobfuscatingRemapper.class.getDeclaredField("rawMethodMaps");
rawFieldMapsField.setAccessible(true);
rawMethodMapsField.setAccessible(true);
Map<String, Map<String, String>> rawFieldMaps = (Map<String, Map<String, String>>) rawFieldMapsField
.get(FMLDeobfuscatingRemapper.INSTANCE);
Map<String, Map<String, String>> rawMethodMaps = (Map<String, Map<String, String>>) rawMethodMapsField
.get(FMLDeobfuscatingRemapper.INSTANCE);
if (rawFieldMaps == null) throw new IllegalStateException(
"codechicken.lib.asm.ObfMapping loaded too early. Make sure all references are in or after the asm transformer load stage");
for (Map<String, String> map : rawFieldMaps.values())
for (Entry<String, String> entry : map.entrySet()) if (entry.getValue().startsWith("field"))
fields.put(entry.getValue(), entry.getKey().substring(0, entry.getKey().indexOf(':')));
for (Map<String, String> map : rawMethodMaps.values())
for (Entry<String, String> entry : map.entrySet()) if (entry.getValue().startsWith("func"))
funcs.put(entry.getValue(), entry.getKey().substring(0, entry.getKey().indexOf('(')));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public String mapMethodName(String owner, String name, String desc) {
String s = funcs.get(name);
return s == null ? name : s;
}
@Override
public String mapFieldName(String owner, String name, String desc) {
String s = fields.get(name);
return s == null ? name : s;
}
@Override
public String map(String typeName) {
return FMLDeobfuscatingRemapper.INSTANCE.unmap(typeName);
}
public String unmap(String typeName) {
return FMLDeobfuscatingRemapper.INSTANCE.map(typeName);
}
public boolean isObf(String typeName) {
return !map(typeName).equals(typeName) || !unmap(typeName).equals(typeName);
}
}
public static class MCPRemapper extends Remapper implements LineProcessor<Void> {
public static File[] getConfFiles() {
ConfigTag tag = ASMHelper.config.getTag("mappingDir")
.setComment("Path to directory holding packaged.srg, fields.csv and methods.csv for mcp remapping");
for (int i = 0; i < DIR_GUESSES + DIR_ASKS; i++) {
File dir = confDirectoryGuess(i, tag);
if (dir == null || dir.isFile()) continue;
File[] mappings;
try {
mappings = parseConfDir(dir);
} catch (Exception e) {
if (i >= DIR_GUESSES) e.printStackTrace();
continue;
}
tag.setValue(dir.getPath());
return mappings;
}
throw new RuntimeException("Failed to select mappings directory, set it manually in the config");
}
private static final int DIR_GUESSES = 6;
private static final int DIR_ASKS = 3;
public static File confDirectoryGuess(int i, ConfigTag tag) {
File mcDir = (File) FMLInjectionData.data()[6];
switch (i) {
case 0:
return tag.value != null ? new File(tag.getValue()) : null;
case 1:
return new File(mcDir, "../conf");
case 2:
return new File(mcDir, "../build/unpacked/conf");
case 3:
return new File(
System.getProperty("user.home"),
".gradle/caches/minecraft/net/minecraftforge/forge/" + FMLInjectionData.data()[4]
+ "-"
+ ForgeVersion.getVersion()
+ "/unpacked/conf");
case 4:
return new File(
System.getProperty("user.home"),
".gradle/caches/minecraft/net/minecraftforge/forge/" + FMLInjectionData.data()[4]
+ "-"
+ ForgeVersion.getVersion()
+ "-"
+ FMLInjectionData.data()[4]
+ "/unpacked/conf");
case 5:
final String gradleCsvDir = System.getProperty("net.minecraftforge.gradle.GradleStart.csvDir");
return gradleCsvDir != null ? new File(gradleCsvDir) : null;
default:
JFileChooser fc = new JFileChooser(mcDir);
fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
fc.setDialogTitle("Select an mcp conf dir for the deobfuscator.");
int ret = fc.showDialog(null, "Select");
return ret == JFileChooser.APPROVE_OPTION ? fc.getSelectedFile() : null;
}
}
public static File[] parseConfDir(File confDir) {
File srgDir = new File(confDir, "conf");
if (!srgDir.exists()) srgDir = confDir;
File srgs = new File(srgDir, "packaged.srg");
if (!srgs.exists()) srgs = new File(srgDir, "joined.srg");
if (!srgs.exists()) throw new RuntimeException("Could not find packaged.srg or joined.srg");
File mapDir = new File(confDir, "mappings");
if (!mapDir.exists()) mapDir = confDir;
File methods = new File(mapDir, "methods.csv");
if (!methods.exists()) throw new RuntimeException("Could not find methods.csv");
File fields = new File(mapDir, "fields.csv");
if (!fields.exists()) throw new RuntimeException("Could not find fields.csv");
return new File[] { srgs, methods, fields };
}
private final HashMap<String, String> fields = new HashMap<>();
private final HashMap<String, String> funcs = new HashMap<>();
public MCPRemapper() {
File[] mappings = getConfFiles();
try {
Resources.readLines(mappings[1].toURI().toURL(), Charsets.UTF_8, this);
Resources.readLines(mappings[2].toURI().toURL(), Charsets.UTF_8, this);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public String mapMethodName(String owner, String name, String desc) {
String s = funcs.get(name);
return s == null ? name : s;
}
@Override
public String mapFieldName(String owner, String name, String desc) {
String s = fields.get(name);
return s == null ? name : s;
}
@Override
public boolean processLine(String line) throws IOException {
int i = line.indexOf(',');
String srg = line.substring(0, i);
int i2 = i + 1;
i = line.indexOf(',', i2);
String mcp = line.substring(i2, i);
(srg.startsWith("func") ? funcs : fields).put(srg, mcp);
return true;
}
@Override
public Void getResult() {
return null;
}
}
public static ObfRemapper obfMapper = new ObfRemapper();
public static Remapper mcpMapper = null;
public static void loadMCPRemapper() {
if (mcpMapper == null) mcpMapper = new MCPRemapper();
}
public static final boolean obfuscated;
static {
boolean obf = true;
try {
obf = Launch.classLoader.getClassBytes("net.minecraft.world.World") == null;
} catch (IOException ignored) {}
obfuscated = obf;
if (!obf) loadMCPRemapper();
}
public String s_owner;
public String s_name;
public String s_desc;
public ObfMapping(String owner) {
this(owner, "", "");
}
public ObfMapping(String owner, String name, String desc) {
this.s_owner = owner;
this.s_name = name;
this.s_desc = desc;
if (s_owner.contains(".")) throw new IllegalArgumentException(s_owner);
}
public ObfMapping(ObfMapping descmap, String subclass) {
this(subclass, descmap.s_name, descmap.s_desc);
}
public static ObfMapping fromDesc(String s) {
int lastDot = s.lastIndexOf('.');
if (lastDot < 0) return new ObfMapping(s, "", "");
int sep = s.indexOf('('); // methods
int sep_end = sep;
if (sep < 0) {
sep = s.indexOf(' '); // some stuffs
sep_end = sep + 1;
}
if (sep < 0) {
sep = s.indexOf(':'); // fields
sep_end = sep + 1;
}
if (sep < 0) return new ObfMapping(s.substring(0, lastDot), s.substring(lastDot + 1), "");
return new ObfMapping(s.substring(0, lastDot), s.substring(lastDot + 1, sep), s.substring(sep_end));
}
public ObfMapping subclass(String subclass) {
return new ObfMapping(this, subclass);
}
public boolean matches(MethodNode node) {
return s_name.equals(node.name) && s_desc.equals(node.desc);
}
public boolean matches(MethodInsnNode node) {
return s_owner.equals(node.owner) && s_name.equals(node.name) && s_desc.equals(node.desc);
}
public AbstractInsnNode toInsn(int opcode) {
if (isClass()) return new TypeInsnNode(opcode, s_owner);
else if (isMethod()) return new MethodInsnNode(opcode, s_owner, s_name, s_desc);
else return new FieldInsnNode(opcode, s_owner, s_name, s_desc);
}
public void visitTypeInsn(MethodVisitor mv, int opcode) {
mv.visitTypeInsn(opcode, s_owner);
}
public void visitMethodInsn(MethodVisitor mv, int opcode) {
mv.visitMethodInsn(opcode, s_owner, s_name, s_desc);
}
public void visitFieldInsn(MethodVisitor mv, int opcode) {
mv.visitFieldInsn(opcode, s_owner, s_name, s_desc);
}
public MethodVisitor visitMethod(ClassVisitor visitor, int access, String[] exceptions) {
return visitor.visitMethod(access, s_name, s_desc, null, exceptions);
}
public FieldVisitor visitField(ClassVisitor visitor, int access, Object value) {
return visitor.visitField(access, s_name, s_desc, null, value);
}
public boolean isClass(String name) {
return name.replace('.', '/').equals(s_owner);
}
public boolean matches(String name, String desc) {
return s_name.equals(name) && s_desc.equals(desc);
}
public boolean matches(FieldNode node) {
return s_name.equals(node.name) && s_desc.equals(node.desc);
}
public boolean matches(FieldInsnNode node) {
return s_owner.equals(node.owner) && s_name.equals(node.name) && s_desc.equals(node.desc);
}
public String javaClass() {
return s_owner.replace('/', '.');
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ObfMapping)) return false;
ObfMapping desc = (ObfMapping) obj;
return s_owner.equals(desc.s_owner) && s_name.equals(desc.s_name) && s_desc.equals(desc.s_desc);
}
@Override
public int hashCode() {
return Objects.hashCode(s_desc, s_name, s_owner);
}
@Override
public String toString() {
if (s_name.length() == 0) return "[" + s_owner + "]";
if (s_desc.length() == 0) return "[" + s_owner + "." + s_name + "]";
return "[" + (isMethod() ? methodDesc() : fieldDesc()) + "]";
}
public String methodDesc() {
return s_owner + "." + s_name + s_desc;
}
public String fieldDesc() {
return s_owner + "." + s_name + ":" + s_desc;
}
public boolean isClass() {
return s_name.length() == 0;
}
public boolean isMethod() {
return s_desc.contains("(");
}
public boolean isField() {
return !isClass() && !isMethod();
}
public ObfMapping map(Remapper mapper) {
if (mapper == null) return this;
if (isMethod()) s_name = mapper.mapMethodName(s_owner, s_name, s_desc);
else if (isField()) s_name = mapper.mapFieldName(s_owner, s_name, s_desc);
s_owner = mapper.mapType(s_owner);
if (isMethod()) s_desc = mapper.mapMethodDesc(s_desc);
else if (s_desc.length() > 0) s_desc = mapper.mapDesc(s_desc);
return this;
}
public ObfMapping toRuntime() {
map(mcpMapper);
return this;
}
public ObfMapping toClassloading() {
if (!obfuscated) map(mcpMapper);
else if (obfMapper.isObf(s_owner)) map(obfMapper);
return this;
}
public ObfMapping copy() {
return new ObfMapping(s_owner, s_name, s_desc);
}
}
@@ -0,0 +1,207 @@
package codechicken.lib.asm;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.file.Files;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraft.launchwrapper.Launch;
import org.apache.commons.io.FileUtils;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.util.ASMifier;
import org.objectweb.asm.util.Textifier;
import org.objectweb.asm.util.TraceClassVisitor;
public class RedirectorTransformer implements IClassTransformer {
private static final boolean DUMP_CLASSES = Boolean.parseBoolean(System.getProperty("ccl.dumpClass", "false"));
private static final String RenderStateClass = "codechicken/lib/render/CCRenderState";
private static final Set<String> redirectedFields = new HashSet<>();
private static final Set<String> redirectedSimpleMethods = new HashSet<>();
private static final Set<String> redirectedMethods = new HashSet<>();
private static final ClassConstantPoolParser cstPoolParser;
static {
Collections.addAll(
redirectedFields,
"pipeline",
"model",
"firstVertexIndex",
"lastVertexIndex",
"vertexIndex",
"baseColour",
"alphaOverride",
"useNormals",
"computeLighting",
"useColour",
"lightMatrix",
"vert",
"hasNormal",
"normal",
"hasColour",
"colour",
"hasBrightness",
"brightness",
"side",
"lc"
);
Collections.addAll(redirectedSimpleMethods, "reset", "pullLightmap", "pushLightmap", "setDynamic", "draw");
Collections.addAll(
redirectedMethods,
"setPipeline",
"bindModel",
"setModel",
"setVertexRange",
"render",
"runPipeline",
"writeVert",
"setNormal",
"setColour",
"setBrightness",
"startDrawing");
cstPoolParser = new ClassConstantPoolParser(RenderStateClass);
}
@Override
public byte[] transform(String name, String transformedName, byte[] basicClass) {
if (!cstPoolParser.find(basicClass)) {
return basicClass;
}
final ClassReader cr = new ClassReader(basicClass);
final ClassNode cn = new ClassNode();
cr.accept(cn, 0);
boolean changed = false;
for (MethodNode mn : cn.methods) {
for (AbstractInsnNode node : mn.instructions.toArray()) {
if (node instanceof FieldInsnNode fNode) {
if (node.getOpcode() == Opcodes.GETSTATIC && redirectedFields.contains(fNode.name)
&& fNode.owner.equals(RenderStateClass)) {
mn.instructions.insertBefore(
fNode,
new MethodInsnNode(
Opcodes.INVOKESTATIC,
fNode.owner,
"instance",
"()Lcodechicken/lib/render/CCRenderState;"));
fNode.setOpcode(Opcodes.GETFIELD);
changed = true;
} else if (node.getOpcode() == Opcodes.PUTSTATIC
&& (redirectedFields.contains(fNode.name) && fNode.owner.equals(RenderStateClass))) {
InsnList beforePut = new InsnList();
beforePut.add(
new MethodInsnNode(
Opcodes.INVOKESTATIC,
fNode.owner,
"instance",
"()Lcodechicken/lib/render/CCRenderState;"));
beforePut.add(new InsnNode(Opcodes.SWAP));
mn.instructions.insertBefore(fNode, beforePut);
fNode.setOpcode(Opcodes.PUTFIELD);
changed = true;
}
} else if (node.getOpcode() == Opcodes.INVOKESTATIC && node instanceof MethodInsnNode mNode) {
if (redirectedMethods.contains(mNode.name) && mNode.owner.equals(RenderStateClass)) {
mNode.name = mNode.name + "Static";
changed = true;
} else if (redirectedSimpleMethods.contains(mNode.name) && mNode.owner.equals(RenderStateClass)) {
mn.instructions.insertBefore(
mNode,
new MethodInsnNode(
Opcodes.INVOKESTATIC,
mNode.owner,
"instance",
"()Lcodechicken/lib/render/CCRenderState;"));
mNode.setOpcode(Opcodes.INVOKEVIRTUAL);
changed = true;
}
}
}
}
if (changed) {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cn.accept(cw);
final byte[] bytes = cw.toByteArray();
if (DUMP_CLASSES) {
saveTransformedClass(bytes, transformedName);
saveTransformedClass(basicClass, transformedName + "_original");
}
return bytes;
}
return basicClass;
}
private File outputDir = null;
private void saveTransformedClass(final byte[] data, final String transformedName) {
if (!DUMP_CLASSES) {
return;
}
if (outputDir == null) {
outputDir = new File(Launch.minecraftHome, "ASM_REDIRECTOR");
try {
FileUtils.deleteDirectory(outputDir);
} catch (IOException ignored) {}
if (!outputDir.exists()) {
// noinspection ResultOfMethodCallIgnored
outputDir.mkdirs();
}
}
final String fileName = transformedName.replace('.', File.separatorChar);
final File classFile = new File(outputDir, fileName + ".class");
final File bytecodeFile = new File(outputDir, fileName + "_BYTE.txt");
final File asmifiedFile = new File(outputDir, fileName + "_ASM.txt");
final File outDir = classFile.getParentFile();
if (!outDir.exists()) {
outDir.mkdirs();
}
if (classFile.exists()) {
classFile.delete();
}
try (final OutputStream output = Files.newOutputStream(classFile.toPath())) {
output.write(data);
} catch (IOException e) {
e.printStackTrace();
}
if (bytecodeFile.exists()) {
bytecodeFile.delete();
}
try (final OutputStream output = Files.newOutputStream(bytecodeFile.toPath())) {
final ClassReader classReader = new ClassReader(data);
classReader.accept(new TraceClassVisitor(null, new Textifier(), new PrintWriter(output)), 0);
} catch (IOException e) {
e.printStackTrace();
}
if (asmifiedFile.exists()) {
asmifiedFile.delete();
}
try (final OutputStream output = Files.newOutputStream(asmifiedFile.toPath())) {
final ClassReader classReader = new ClassReader(data);
classReader.accept(new TraceClassVisitor(null, new ASMifier(), new PrintWriter(output)), 0);
} catch (IOException e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,158 @@
package codechicken.lib.colour;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.lwjgl.opengl.GL11;
import codechicken.lib.config.ConfigTag.IConfigType;
import codechicken.lib.math.MathHelper;
import codechicken.lib.util.Copyable;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
public abstract class Colour implements Copyable<Colour> {
public static IConfigType<Colour> configRGB = new IConfigType<Colour>() {
@Override
public String configValue(Colour entry) {
String s = Long.toString(((long) entry.rgb()) << 32 >>> 32, 16);
while (s.length() < 6) s = "0" + s;
return "0x" + s.toUpperCase();
}
private final Pattern patternRGB = Pattern.compile("(\\d+),(\\d+),(\\d+)");
@Override
public Colour valueOf(String text) throws Exception {
Matcher matcherRGB = patternRGB.matcher(text.replaceAll("\\s", ""));
if (matcherRGB.matches()) return new ColourRGBA(
Integer.parseInt(matcherRGB.group(1)),
Integer.parseInt(matcherRGB.group(2)),
Integer.parseInt(matcherRGB.group(3)),
0xFF);
int hex = (int) Long.parseLong(text.replace("0x", ""), 16);
return new ColourRGBA(hex << 8 | 0xFF);
}
};
public byte r;
public byte g;
public byte b;
public byte a;
public Colour(int r, int g, int b, int a) {
this.r = (byte) r;
this.g = (byte) g;
this.b = (byte) b;
this.a = (byte) a;
}
public Colour(Colour colour) {
r = colour.r;
g = colour.g;
b = colour.b;
a = colour.a;
}
@SideOnly(Side.CLIENT)
public void glColour() {
GL11.glColor4ub(r, g, b, a);
}
@SideOnly(Side.CLIENT)
public void glColour(int a) {
GL11.glColor4ub(r, g, b, (byte) a);
}
public abstract int pack();
@Override
public String toString() {
return getClass().getSimpleName() + "[0x" + Integer.toHexString(pack()).toUpperCase() + "]";
}
public Colour add(Colour colour2) {
a += colour2.a;
r += colour2.r;
g += colour2.g;
b += colour2.b;
return this;
}
public Colour sub(Colour colour2) {
int ia = (a & 0xFF) - (colour2.a & 0xFF);
int ir = (r & 0xFF) - (colour2.r & 0xFF);
int ig = (g & 0xFF) - (colour2.g & 0xFF);
int ib = (b & 0xFF) - (colour2.b & 0xFF);
a = (byte) (ia < 0 ? 0 : ia);
r = (byte) (ir < 0 ? 0 : ir);
g = (byte) (ig < 0 ? 0 : ig);
b = (byte) (ib < 0 ? 0 : ib);
return this;
}
public Colour invert() {
a = (byte) (0xFF - (a & 0xFF));
r = (byte) (0xFF - (r & 0xFF));
g = (byte) (0xFF - (g & 0xFF));
b = (byte) (0xFF - (b & 0xFF));
return this;
}
public Colour multiply(Colour colour2) {
a = (byte) ((a & 0xFF) * ((colour2.a & 0xFF) / 255D));
r = (byte) ((r & 0xFF) * ((colour2.r & 0xFF) / 255D));
g = (byte) ((g & 0xFF) * ((colour2.g & 0xFF) / 255D));
b = (byte) ((b & 0xFF) * ((colour2.b & 0xFF) / 255D));
return this;
}
public Colour scale(double d) {
a = (byte) ((a & 0xFF) * d);
r = (byte) ((r & 0xFF) * d);
g = (byte) ((g & 0xFF) * d);
b = (byte) ((b & 0xFF) * d);
return this;
}
public Colour interpolate(Colour colour2, double d) {
return this.add(colour2.copy().sub(this).scale(d));
}
public Colour multiplyC(double d) {
r = (byte) MathHelper.clip((r & 0xFF) * d, 0, 255);
g = (byte) MathHelper.clip((g & 0xFF) * d, 0, 255);
b = (byte) MathHelper.clip((b & 0xFF) * d, 0, 255);
return this;
}
public abstract Colour copy();
public int rgb() {
return (r & 0xFF) << 16 | (g & 0xFF) << 8 | (b & 0xFF);
}
public int argb() {
return (a & 0xFF) << 24 | (r & 0xFF) << 16 | (g & 0xFF) << 8 | (b & 0xFF);
}
public int rgba() {
return (r & 0xFF) << 24 | (g & 0xFF) << 16 | (b & 0xFF) << 8 | (a & 0xFF);
}
public Colour set(Colour colour) {
r = colour.r;
g = colour.g;
b = colour.b;
a = colour.a;
return this;
}
public boolean equals(Colour colour) {
return colour != null && rgba() == colour.rgba();
}
}
@@ -0,0 +1,28 @@
package codechicken.lib.colour;
public class ColourARGB extends Colour {
public ColourARGB(int colour) {
super((colour >> 16) & 0xFF, (colour >> 8) & 0xFF, colour & 0xFF, (colour >> 24) & 0xFF);
}
public ColourARGB(int a, int r, int g, int b) {
super(r, g, b, a);
}
public ColourARGB(ColourARGB colour) {
super(colour);
}
public ColourARGB copy() {
return new ColourARGB(this);
}
public int pack() {
return pack(this);
}
public static int pack(Colour colour) {
return (colour.a & 0xFF) << 24 | (colour.r & 0xFF) << 16 | (colour.g & 0xFF) << 8 | (colour.b & 0xFF);
}
}
@@ -0,0 +1,50 @@
package codechicken.lib.colour;
public class ColourRGBA extends Colour {
public ColourRGBA(int colour) {
super((colour >> 24) & 0xFF, (colour >> 16) & 0xFF, (colour >> 8) & 0xFF, colour & 0xFF);
}
public ColourRGBA(double r, double g, double b, double a) {
super((int) (255 * r), (int) (255 * g), (int) (255 * b), (int) (255 * a));
}
public ColourRGBA(int r, int g, int b, int a) {
super(r, g, b, a);
}
public ColourRGBA(ColourRGBA colour) {
super(colour);
}
public int pack() {
return pack(this);
}
@Override
public Colour copy() {
return new ColourRGBA(this);
}
public static int pack(Colour colour) {
return (colour.r & 0xFF) << 24 | (colour.g & 0xFF) << 16 | (colour.b & 0xFF) << 8 | (colour.a & 0xFF);
}
public static int multiply(int c1, int c2) {
if (c1 == -1) return c2;
if (c2 == -1) return c1;
int r = (((c1 >>> 24) * (c2 >>> 24)) & 0xFF00) << 16;
int g = (((c1 >> 16 & 0xFF) * (c2 >> 16 & 0xFF)) & 0xFF00) << 8;
int b = ((c1 >> 8 & 0xFF) * (c2 >> 8 & 0xFF)) & 0xFF00;
int a = ((c1 & 0xFF) * (c2 & 0xFF)) >> 8;
return r | g | b | a;
}
public static int multiplyC(int c, float f) {
int r = (int) ((c >>> 24) * f);
int g = (int) ((c >> 16 & 0xFF) * f);
int b = (int) ((c >> 8 & 0xFF) * f);
return r << 24 | g << 16 | b << 8 | c & 0xFF;
}
}
@@ -0,0 +1,32 @@
package codechicken.lib.colour;
import java.awt.image.BufferedImage;
import net.minecraft.util.ResourceLocation;
import codechicken.lib.math.MathHelper;
import codechicken.lib.render.TextureUtils;
public class CustomGradient {
public int[] gradient;
public CustomGradient(ResourceLocation textureFile) {
BufferedImage img = TextureUtils.loadBufferedImage(textureFile);
if (img == null) throw new RuntimeException("File not found: " + textureFile.toString());
int[] data = new int[img.getWidth()];
img.getRGB(0, 0, img.getWidth(), 1, data, 0, img.getWidth());
gradient = new int[img.getWidth()];
for (int i = 0; i < data.length; i++) gradient[i] = (data[i] << 8) | (((data[i]) >> 24) & 0xFF);
}
public ColourRGBA getColour(double position) {
return new ColourRGBA(getColourI(position));
}
public int getColourI(double position) {
int off = (int) MathHelper.clip(gradient.length * position, 0, gradient.length - 1);
return gradient[off];
}
}
@@ -0,0 +1,110 @@
package codechicken.lib.config;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
public class ConfigFile extends ConfigTagParent {
public static final byte[] crlf = new byte[] { 0xD, 0xA };
public File file;
private boolean loading;
public ConfigFile(File file) {
newlinemode = 2;
load(file);
}
protected ConfigFile() {}
protected void load(File file) {
try {
if (!file.getParentFile().exists()) file.getParentFile().mkdirs();
if (!file.exists()) file.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
this.file = file;
loadConfig();
}
protected void loadConfig() {
loading = true;
BufferedReader reader;
try {
reader = new BufferedReader(new FileReader(file));
while (true) {
reader.mark(2000);
String line = reader.readLine();
if (line != null && line.startsWith("#")) {
if (comment == null || comment.equals("")) comment = line.substring(1);
else comment = comment + "\n" + line.substring(1);
} else {
reader.reset();
break;
}
}
loadChildren(reader);
reader.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
loading = false;
}
@Override
public ConfigFile setComment(String header) {
super.setComment(header);
return this;
}
@Override
public ConfigFile setSortMode(int mode) {
super.setSortMode(mode);
return this;
}
@Override
public String getNameQualifier() {
return "";
}
public static String readLine(BufferedReader reader) throws IOException {
String line = reader.readLine();
return line == null ? null : line.replace("\t", "");
}
public static void writeLine(PrintWriter writer, String line, int tabs) {
for (int i = 0; i < tabs; i++) writer.print('\t');
writer.println(line);
}
public void saveConfig() {
if (loading) return;
PrintWriter writer;
try {
writer = new PrintWriter(file);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
writeComment(writer, 0);
ConfigFile.writeLine(writer, "", 0);
saveTagTree(writer, 0, "");
writer.flush();
writer.close();
}
public boolean isLoading() {
return loading;
}
}
@@ -0,0 +1,225 @@
package codechicken.lib.config;
import java.io.PrintWriter;
public class ConfigTag extends ConfigTagParent {
public interface IConfigType<T> {
public String configValue(T entry);
public T valueOf(String text) throws Exception;
}
public ConfigTag(ConfigTagParent parent, String name) {
this.parent = parent;
this.name = name;
qualifiedname = parent.getNameQualifier() + name;
newline = parent.newlinemode == 2;
parent.addChild(this);
}
@Override
public String getNameQualifier() {
return qualifiedname + ".";
}
@Override
public void saveConfig() {
parent.saveConfig();
}
/**
* Called when the tag is loaded from a config file as opposed to constructed by a mod
*
* @return this
*/
public ConfigTag onLoaded() {
return this;
}
public void setValue(String value) {
this.value = value;
saveConfig();
}
public void setDefaultValue(String defaultValue) {
if (value == null) {
value = defaultValue;
saveConfig();
}
}
public void setIntValue(int i) {
setValue(Integer.toString(i));
}
public void setBooleanValue(boolean b) {
setValue(Boolean.toString(b));
}
public void setHexValue(int i) {
setValue("0x" + Long.toString(((long) i) << 32 >>> 32, 16));
}
public <T> void set(IConfigType<T> type, T entry) {
setValue(type.configValue(entry));
}
public String getValue() {
return value;
}
public String getValue(String defaultValue) {
setDefaultValue(defaultValue);
return value;
}
public int getIntValue() {
return Integer.parseInt(getValue());
}
public int getIntValue(int defaultValue) {
try {
if (value != null) return getIntValue();
} catch (NumberFormatException ignored) {}
setIntValue(defaultValue);
return defaultValue;
}
public boolean getBooleanValue() {
String value = getValue();
if (value != null && (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("yes"))) return true;
else if (value != null && (value.equalsIgnoreCase("false") || value.equalsIgnoreCase("no"))) return false;
throw new NumberFormatException(qualifiedname + ".value=" + value);
}
public boolean getBooleanValue(boolean defaultValue) {
try {
if (value != null) return getBooleanValue();
} catch (NumberFormatException ignored) {}
setBooleanValue(defaultValue);
return defaultValue;
}
public int getHexValue() {
return (int) Long.parseLong(getValue().replace("0x", ""), 16);
}
public int getHexValue(int defaultValue) {
try {
if (value != null) return getHexValue();
} catch (NumberFormatException ignored) {}
setHexValue(defaultValue);
return defaultValue;
}
public <T> T get(IConfigType<T> type) {
try {
return type.valueOf(getValue());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public <T> T get(IConfigType<T> type, T defaultValue) {
try {
if (value != null) return get(type);
} catch (Exception ignored) {}
set(type, defaultValue);
return defaultValue;
}
public void save(PrintWriter writer, int tabs, String bracequalifier, boolean first) {
String vname;
if (qualifiedname.contains(".") && bracequalifier.length() > 0)
vname = qualifiedname.substring(bracequalifier.length() + 1);
else vname = qualifiedname;
if (newline && !first) ConfigFile.writeLine(writer, "", tabs);
writeComment(writer, tabs);
if (value != null) ConfigFile.writeLine(writer, vname + "=" + value, tabs);
if (!hasChildTags()) return;
if (brace) {
if (value == null) ConfigFile.writeLine(writer, vname, tabs);
ConfigFile.writeLine(writer, "{", tabs);
saveTagTree(writer, tabs + 1, qualifiedname);
ConfigFile.writeLine(writer, "}", tabs);
} else {
saveTagTree(writer, tabs, bracequalifier);
}
}
@Override
public ConfigTag setComment(String comment) {
super.setComment(comment);
return this;
}
@Override
public ConfigTag setSortMode(int mode) {
super.setSortMode(mode);
return this;
}
public ConfigTag setNewLine(boolean b) {
newline = b;
saveConfig();
return this;
}
public ConfigTag useBraces() {
brace = true;
if (parent.newlinemode == 1) newline = true;
saveConfig();
return this;
}
public ConfigTag setPosition(int pos) {
position = pos;
saveConfig();
return this;
}
public boolean containsTag(String tagname) {
return getTag(tagname, false) != null;
}
public int getId(String name, int defaultValue) {
return getTag(name).getIntValue(defaultValue);
}
public int getId(String name) {
int ret = getId(name, IDBase);
IDBase = ret + 1;
return ret;
}
public int getAcheivementId(String name, int defaultValue) {
return getTag(name).getIntValue(defaultValue);
}
public ConfigTag setBaseID(int i) {
IDBase = i;
return this;
}
public ConfigTagParent parent;
public String name;
public String qualifiedname;
public String value;
public boolean brace;
public boolean newline;
public int position = Integer.MAX_VALUE;
private int IDBase;
}
@@ -0,0 +1,189 @@
package codechicken.lib.config;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
public abstract class ConfigTagParent {
public static class TagOrderComparator implements Comparator<ConfigTag> {
int sortMode;
public TagOrderComparator(int sortMode) {
this.sortMode = sortMode;
}
public int compare(ConfigTag o1, ConfigTag o2) {
if (o1.position != o2.position) return compareInt(o1.position, o2.position);
if (o1.brace != o2.brace) return o1.brace ? 1 : -1; // braced one goes after
switch (sortMode) {
case 1:
if (o1.value == o2.value) return 0;
if (o1.value == null) return 1;
if (o2.value == null) return -1;
return o1.value.compareTo(o2.value);
default:
return o1.name.compareTo(o2.name);
}
}
private int compareInt(int a, int b) {
return a == b ? 0 : a < b ? -1 : 1;
}
}
private TreeMap<String, ConfigTag> childtags = new TreeMap<String, ConfigTag>();
public String comment;
/**
* 0 = name, 1 = value
*/
public int sortMode = 0;
/**
* The mode for determining when child tags should leave a blank line between them and the one above 0 = never, 1 =
* when braced, 2 = always
*/
public int newlinemode = 1;
public abstract void saveConfig();
public abstract String getNameQualifier();
public ConfigTagParent setComment(String comment) {
this.comment = comment;
saveConfig();
return this;
}
public ConfigTagParent setSortMode(int mode) {
sortMode = mode;
saveConfig();
return this;
}
public ConfigTagParent setNewLineMode(int mode) {
newlinemode = mode;
for (Entry<String, ConfigTag> entry : childtags.entrySet()) {
ConfigTag tag = entry.getValue();
if (newlinemode == 0) tag.newline = false;
else if (newlinemode == 1) tag.newline = tag.brace;
else if (newlinemode == 2) tag.newline = true;
}
saveConfig();
return this;
}
public Map<String, ConfigTag> childTagMap() {
return childtags;
}
public boolean hasChildTags() {
return !childtags.isEmpty();
}
public boolean containsTag(String tagname) {
return getTag(tagname, false) != null;
}
public ConfigTag getNewTag(String tagname) {
return new ConfigTag(this, tagname);
}
public ConfigTag getTag(String tagname, boolean create) {
int dotpos = tagname.indexOf(".");
String basetagname = dotpos == -1 ? tagname : tagname.substring(0, dotpos);
ConfigTag basetag = childtags.get(basetagname);
if (basetag == null) {
if (!create) return null;
basetag = getNewTag(basetagname);
saveConfig();
}
if (dotpos == -1) return basetag;
return basetag.getTag(tagname.substring(dotpos + 1), create);
}
public ConfigTag getTag(String tagname) {
return getTag(tagname, true);
}
public boolean removeTag(String tagname) {
ConfigTag tag = getTag(tagname, false);
if (tag == null) return false;
int dotpos = tagname.lastIndexOf(".");
String lastpart = dotpos == -1 ? tagname : tagname.substring(dotpos + 1, tagname.length());
if (tag.parent != null) {
boolean ret = tag.parent.childtags.remove(lastpart) != null;
if (ret) saveConfig();
return ret;
}
return false;
}
public void addChild(ConfigTag tag) {
childtags.put(tag.name, tag);
}
public <T extends ConfigTag> ArrayList<T> getSortedTagList() {
ArrayList<T> taglist = new ArrayList<T>(childtags.size());
for (Entry<String, ConfigTag> tag : childtags.entrySet()) taglist.add((T) tag.getValue());
Collections.sort(taglist, new TagOrderComparator(sortMode));
return taglist;
}
public void loadChildren(BufferedReader reader) {
String comment = "";
String bracequalifier = "";
try {
while (true) {
String line = ConfigFile.readLine(reader);
if (line == null) break;
if (line.startsWith("#")) {
if (comment.equals("")) comment = line.substring(1);
else comment = comment + "\n" + line.substring(1);
} else if (line.contains("=")) {
String qualifiedname = line.substring(0, line.indexOf("="));
getTag(qualifiedname).onLoaded().setComment(comment)
.setValue(line.substring(line.indexOf("=") + 1));
comment = "";
bracequalifier = qualifiedname;
} else if (line.equals("{")) {
getTag(bracequalifier).setComment(comment).useBraces().loadChildren(reader);
comment = "";
bracequalifier = "";
} else if (line.equals("}")) {
break;
} else {
bracequalifier = line;
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void saveTagTree(PrintWriter writer, int tabs, String bracequalifier) {
boolean first = true;
for (ConfigTag tag : getSortedTagList()) {
tag.save(writer, tabs, bracequalifier, first);
first = false;
}
}
public void writeComment(PrintWriter writer, int tabs) {
if (comment != null && !comment.equals("")) {
String[] comments = comment.split("\n");
for (int i = 0; i < comments.length; i++) ConfigFile.writeLine(writer, "#" + comments[i], tabs);
}
}
}
@@ -0,0 +1,16 @@
package codechicken.lib.config;
import java.io.File;
public class DefaultingConfigFile extends ConfigFile {
public DefaultingConfigFile(File file) {
super();
if (file.exists()) load(file);
}
@Override
public void saveConfig() {
if (file != null) super.saveConfig();
}
}
@@ -0,0 +1,125 @@
package codechicken.lib.config;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map.Entry;
public class SimpleProperties {
public HashMap<String, String> propertyMap = new HashMap<String, String>();
public File propertyFile;
public boolean saveOnChange = false;
public String encoding;
private boolean loading = false;
public SimpleProperties(File file, boolean saveOnChange, String encoding) {
propertyFile = file;
this.saveOnChange = saveOnChange;
this.encoding = encoding;
}
public SimpleProperties(File file, boolean saveOnChange) {
this(file, saveOnChange, Charset.defaultCharset().name());
}
public SimpleProperties(File file) {
this(file, true);
}
public void load() {
clear();
loading = true;
try {
BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream(propertyFile), encoding));
while (true) {
String read = reader.readLine();
if (read == null) break;
int equalIndex = read.indexOf('=');
if (equalIndex == -1) continue;
setProperty(read.substring(0, equalIndex), read.substring(equalIndex + 1));
}
reader.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
loading = false;
}
public void save() {
try {
PrintStream writer = new PrintStream(propertyFile);
for (Entry<String, String> entry : propertyMap.entrySet()) {
writer.println(entry.getKey() + "=" + entry.getValue());
}
writer.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void clear() {
propertyMap.clear();
}
public boolean hasProperty(String key) {
return propertyMap.containsKey(key);
}
public void removeProperty(String key) {
if (propertyMap.remove(key) != null && saveOnChange && !loading) save();
}
public void setProperty(String key, int value) {
setProperty(key, Integer.toString(value));
}
public void setProperty(String key, boolean value) {
setProperty(key, Boolean.toString(value));
}
public void setProperty(String key, String value) {
propertyMap.put(key, value);
if (saveOnChange && !loading) save();
}
public int getProperty(String property, int defaultvalue) {
try {
return Integer.parseInt(getProperty(property, Integer.toString(defaultvalue)));
} catch (NumberFormatException nfe) {
return defaultvalue;
}
}
public boolean getProperty(String property, boolean defaultvalue) {
try {
return Boolean.parseBoolean(getProperty(property, Boolean.toString(defaultvalue)));
} catch (NumberFormatException nfe) {
return defaultvalue;
}
}
public String getProperty(String property, String defaultvalue) {
String value = propertyMap.get(property);
if (value == null) {
setProperty(property, defaultvalue);
return defaultvalue;
}
return value;
}
public String getProperty(String property) {
return propertyMap.get(property);
}
}
@@ -0,0 +1,46 @@
package codechicken.lib.data;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraftforge.fluids.FluidStack;
import codechicken.lib.vec.BlockCoord;
public interface MCDataInput {
public long readLong();
public int readInt();
public short readShort();
public int readUShort();
public byte readByte();
public short readUByte();
public double readDouble();
public float readFloat();
public boolean readBoolean();
public char readChar();
public int readVarShort();
public int readVarInt();
public byte[] readByteArray(int length);
public String readString();
public BlockCoord readCoord();
public NBTTagCompound readNBTTagCompound();
public ItemStack readItemStack();
public FluidStack readFluidStack();
}
@@ -0,0 +1,17 @@
package codechicken.lib.data;
import java.io.InputStream;
public class MCDataInputStream extends InputStream {
private MCDataInput in;
public MCDataInputStream(MCDataInput in) {
this.in = in;
}
@Override
public int read() {
return in.readByte() & 0xFF;
}
}
@@ -0,0 +1,44 @@
package codechicken.lib.data;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraftforge.fluids.FluidStack;
import codechicken.lib.vec.BlockCoord;
public interface MCDataOutput {
public MCDataOutput writeLong(long l);
public MCDataOutput writeInt(int i);
public MCDataOutput writeShort(int s);
public MCDataOutput writeByte(int b);
public MCDataOutput writeDouble(double d);
public MCDataOutput writeFloat(float f);
public MCDataOutput writeBoolean(boolean b);
public MCDataOutput writeChar(char c);
public MCDataOutput writeVarInt(int i);
public MCDataOutput writeVarShort(int s);
public MCDataOutput writeByteArray(byte[] array);
public MCDataOutput writeString(String s);
public MCDataOutput writeCoord(int x, int y, int z);
public MCDataOutput writeCoord(BlockCoord coord);
public MCDataOutput writeNBTTagCompound(NBTTagCompound tag);
public MCDataOutput writeItemStack(ItemStack stack);
public MCDataOutput writeFluidStack(FluidStack liquid);
}
@@ -0,0 +1,17 @@
package codechicken.lib.data;
import java.io.OutputStream;
public class MCDataOutputStream extends OutputStream {
private MCDataOutput out;
public MCDataOutputStream(MCDataOutput out) {
this.out = out;
}
@Override
public void write(int b) {
out.writeByte(b);
}
}
@@ -0,0 +1,240 @@
package codechicken.lib.data;
import java.io.DataOutput;
import java.io.IOException;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompressedStreamTools;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraftforge.fluids.FluidStack;
import org.apache.commons.lang3.Validate;
import com.google.common.base.Charsets;
import codechicken.lib.vec.BlockCoord;
import cpw.mods.fml.common.network.ByteBufUtils;
public class MCDataOutputWrapper implements MCDataOutput {
/**
* Mimics ByteBufUtils Write an integer using variable length encoding.
*
* @param to The buffer to write to
* @param i The integer to write
*/
public static void writeVarInt(DataOutput to, int i) throws IOException {
while ((i & 0x80) != 0) {
to.writeByte(i & 0x7F | 0x80);
i >>>= 7;
}
to.writeByte(i);
}
/**
* Mimics ByteBufUtils Write an extended short using a short and a byte if necessary
*
* @param to The buffer to write to
* @param s The short to write, less than 0x7FFFFF
*/
public static void writeVarShort(DataOutput to, int s) throws IOException {
int low = s & 0x7FFF;
int high = (s & 0x7F8000) >> 15;
if (high != 0) low |= 0x8000;
to.writeShort(low);
if (high != 0) to.writeByte(high);
}
/**
* Mimics ByteBufUtils Write a String with UTF8 byte encoding to the buffer. It is encoded as <varint length>[<UTF8
* char bytes>]
*
* @param to the data output to write to
* @param string The string to write
*/
public static void writeUTF8String(DataOutput to, String string) throws IOException {
byte[] utf8Bytes = string.getBytes(Charsets.UTF_8);
Validate.isTrue(
ByteBufUtils.varIntByteCount(utf8Bytes.length) < 3,
"The string is too long for this encoding.");
writeVarInt(to, utf8Bytes.length);
to.write(utf8Bytes);
}
public DataOutput dataout;
public MCDataOutputWrapper(DataOutput out) {
dataout = out;
}
public MCDataOutputWrapper writeBoolean(boolean b) {
try {
dataout.writeBoolean(b);
} catch (IOException e) {
throw new RuntimeException(e);
}
return this;
}
public MCDataOutputWrapper writeByte(int b) {
try {
dataout.writeByte(b);
} catch (IOException e) {
throw new RuntimeException(e);
}
return this;
}
public MCDataOutputWrapper writeShort(int s) {
try {
dataout.writeShort(s);
} catch (IOException e) {
throw new RuntimeException(e);
}
return this;
}
public MCDataOutputWrapper writeInt(int i) {
try {
dataout.writeInt(i);
} catch (IOException e) {
throw new RuntimeException(e);
}
return this;
}
public MCDataOutputWrapper writeFloat(float f) {
try {
dataout.writeFloat(f);
} catch (IOException e) {
throw new RuntimeException(e);
}
return this;
}
public MCDataOutputWrapper writeDouble(double d) {
try {
dataout.writeDouble(d);
} catch (IOException e) {
throw new RuntimeException(e);
}
return this;
}
public MCDataOutputWrapper writeLong(long l) {
try {
dataout.writeLong(l);
} catch (IOException e) {
throw new RuntimeException(e);
}
return this;
}
@Override
public MCDataOutputWrapper writeChar(char c) {
try {
dataout.writeChar(c);
} catch (IOException e) {
throw new RuntimeException(e);
}
return this;
}
@Override
public MCDataOutput writeVarInt(int i) {
try {
writeVarInt(dataout, i);
} catch (IOException e) {
throw new RuntimeException(e);
}
return this;
}
@Override
public MCDataOutput writeVarShort(int s) {
try {
writeVarShort(dataout, s);
} catch (IOException e) {
throw new RuntimeException(e);
}
return this;
}
public MCDataOutputWrapper writeByteArray(byte[] barray) {
try {
dataout.write(barray);
} catch (IOException e) {
throw new RuntimeException(e);
}
return this;
}
public MCDataOutputWrapper writeCoord(int x, int y, int z) {
writeInt(x);
writeInt(y);
writeInt(z);
return this;
}
public MCDataOutputWrapper writeCoord(BlockCoord coord) {
writeInt(coord.x);
writeInt(coord.y);
writeInt(coord.z);
return this;
}
public MCDataOutputWrapper writeString(String s) {
try {
writeUTF8String(dataout, s);
} catch (IOException e) {
throw new RuntimeException(e);
}
return this;
}
public MCDataOutputWrapper writeItemStack(ItemStack stack) {
writeItemStack(stack, false);
return this;
}
public MCDataOutputWrapper writeItemStack(ItemStack stack, boolean large) {
if (stack == null) {
writeInt(-1);
} else {
writeInt(Item.getIdFromItem(stack.getItem()));
if (large) writeInt(stack.stackSize);
else writeByte(stack.stackSize);
writeShort(stack.getItemDamage());
writeNBTTagCompound(stack.stackTagCompound);
}
return this;
}
public MCDataOutputWrapper writeNBTTagCompound(NBTTagCompound compound) {
try {
if (compound == null) {
writeShort(-1);
} else {
byte[] bytes = CompressedStreamTools.compress(compound);
writeShort((short) bytes.length);
writeByteArray(bytes);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return this;
}
public MCDataOutputWrapper writeFluidStack(FluidStack fluid) {
if (fluid == null) {
writeShort(-1);
} else {
writeShort(fluid.getFluidID());
writeVarInt(fluid.amount);
writeNBTTagCompound(fluid.tag);
}
return this;
}
}
@@ -0,0 +1,81 @@
package codechicken.lib.gui;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.util.ResourceLocation;
import codechicken.lib.render.CCRenderState;
import codechicken.lib.render.TextureDataHolder;
import codechicken.lib.render.TextureUtils;
public class Canvas9Seg {
public final ResourceLocation tex;
public float[] seg_u = new float[4];
public float[] seg_v = new float[4];
public int[] seg_w = new int[3];
public int[] seg_h = new int[3];
public Canvas9Seg(ResourceLocation tex) {
this.tex = tex;
load();
}
private int[] readMarkers(TextureDataHolder data, int stride, int size) {
int[] markers = new int[4];
int marker = 1;
int prev_col = data.data[0];
for (int i = 1; i < size; i++) {
if (data.data[i * stride] != prev_col) {
markers[marker] = i;
prev_col = data.data[i * stride];
if (++marker == 4) break;
}
}
markers[0] += 1;
markers[3] -= 1;
return markers;
}
private void parseMarkers(TextureDataHolder data, int stride, int size, int[] sizes, float[] texcoords) {
int[] markers = readMarkers(data, stride, size);
for (int i = 0; i < 4; i++) {
texcoords[i] = markers[i] / (float) size;
if (i > 0) sizes[i - 1] = markers[i] - markers[i - 1];
}
}
private void load() {
TextureDataHolder data = TextureUtils.loadTexture(tex);
parseMarkers(data, 1, data.width, seg_w, seg_u);
parseMarkers(data, data.width, data.height, seg_h, seg_v);
}
private void drawSeg(int[] sw, int[] sh, int seg) {
Tessellator t = Tessellator.instance;
int u = seg % 3;
int v = seg / 3;
t.addVertexWithUV(sw[u], sh[v], 0, seg_u[u], seg_v[v]);
t.addVertexWithUV(sw[u], sh[v + 1], 0, seg_u[u], seg_v[v + 1]);
t.addVertexWithUV(sw[u + 1], sh[v + 1], 0, seg_u[u + 1], seg_v[v + 1]);
t.addVertexWithUV(sw[u + 1], sh[v], 0, seg_u[u + 1], seg_v[v]);
}
public void draw(CCRenderState state, int x, int y, int w, int h) {
CCRenderState.changeTexture(tex);
state.reset();
state.startDrawing();
int[] sw = new int[] { x, x + seg_w[0], x + w - seg_w[2], x + w };
int[] sh = new int[] { y, y + seg_h[0], y + h - seg_h[2], y + h };
for (int seg = 0; seg < 9; seg++) drawSeg(sw, sh, seg);
state.draw();
}
public void draw(int x, int y, int w, int h) {
draw(CCRenderState.instance(), x, y, w, h);
}
}
@@ -0,0 +1,227 @@
package codechicken.lib.gui;
import java.awt.Dimension;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.util.ResourceLocation;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import codechicken.lib.math.MathHelper;
import codechicken.lib.render.CCRenderState;
public class GuiDraw {
public static class GuiHook extends Gui {
public void setZLevel(float f) {
zLevel = f;
}
public float getZLevel() {
return zLevel;
}
public void incZLevel(float f) {
zLevel += f;
}
@Override
public void drawGradientRect(int par1, int par2, int par3, int par4, int par5, int par6) {
super.drawGradientRect(par1, par2, par3, par4, par5, par6);
}
}
public static final GuiHook gui = new GuiHook();
public static FontRenderer fontRenderer = Minecraft.getMinecraft().fontRenderer;
public static TextureManager renderEngine = Minecraft.getMinecraft().renderEngine;
public static void drawRect(int x, int y, int w, int h, int colour) {
drawGradientRect(x, y, w, h, colour, colour);
}
public static void drawGradientRect(int x, int y, int w, int h, int colour1, int colour2) {
gui.drawGradientRect(x, y, x + w, y + h, colour1, colour2);
}
public static void drawTexturedModalRect(int x, int y, int tx, int ty, int w, int h) {
gui.drawTexturedModalRect(x, y, tx, ty, w, h);
}
public static void drawString(String text, int x, int y, int colour, boolean shadow) {
if (shadow) fontRenderer.drawStringWithShadow(text, x, y, colour);
else fontRenderer.drawString(text, x, y, colour);
}
public static void drawString(String text, int x, int y, int colour) {
drawString(text, x, y, colour, true);
}
public static void drawStringC(String text, int x, int y, int w, int h, int colour, boolean shadow) {
drawString(text, x + (w - getStringWidth(text)) / 2, y + (h - 8) / 2, colour, shadow);
}
public static void drawStringC(String text, int x, int y, int w, int h, int colour) {
drawStringC(text, x, y, w, h, colour, true);
}
public static void drawStringC(String text, int x, int y, int colour, boolean shadow) {
drawString(text, x - getStringWidth(text) / 2, y, colour, shadow);
}
public static void drawStringC(String text, int x, int y, int colour) {
drawStringC(text, x, y, colour, true);
}
public static void drawStringR(String text, int x, int y, int colour, boolean shadow) {
drawString(text, x - getStringWidth(text), y, colour, shadow);
}
public static void drawStringR(String text, int x, int y, int colour) {
drawStringR(text, x, y, colour, true);
}
public static int getStringWidth(String s) {
return fontRenderer.getStringWidth(s);
}
public static Dimension displaySize() {
Minecraft mc = Minecraft.getMinecraft();
ScaledResolution res = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight);
return new Dimension(res.getScaledWidth(), res.getScaledHeight());
}
public static Dimension displayRes() {
Minecraft mc = Minecraft.getMinecraft();
return new Dimension(mc.displayWidth, mc.displayHeight);
}
public static Point getMousePosition(int eventX, int eventY) {
Dimension size = displaySize();
Dimension res = displayRes();
return new Point(eventX * size.width / res.width, size.height - eventY * size.height / res.height - 1);
}
public static Point getMousePosition() {
return getMousePosition(Mouse.getX(), Mouse.getY());
}
public static void changeTexture(String s) {
CCRenderState.changeTexture(s);
}
public static void changeTexture(ResourceLocation r) {
CCRenderState.changeTexture(r);
}
public static void drawTip(int x, int y, String text) {
drawMultilineTip(x, y, Arrays.asList(text));
}
/**
* Append a string in the tooltip list with TOOLTIP_LINESPACE to have a small gap between it and the next line
*/
public static final String TOOLTIP_LINESPACE = "\u00A7h";
/**
* Have a string in the tooltip list with TOOLTIP_HANDLER + getTipLineId(handler) for a custom handler
*/
public static final String TOOLTIP_HANDLER = "\u00A7x";
private static List<ITooltipLineHandler> tipLineHandlers = new ArrayList<ITooltipLineHandler>();
public static interface ITooltipLineHandler {
public Dimension getSize();
public void draw(int x, int y);
}
public static int getTipLineId(ITooltipLineHandler handler) {
tipLineHandlers.add(handler);
return tipLineHandlers.size() - 1;
}
public static ITooltipLineHandler getTipLine(String line) {
if (!line.startsWith(TOOLTIP_HANDLER)) return null;
return tipLineHandlers.get(Integer.parseInt(line.substring(2)));
}
public static void drawMultilineTip(int x, int y, List<String> list) {
drawMultilineTip(fontRenderer, x, y, list);
}
public static void drawMultilineTip(FontRenderer font, int x, int y, List<String> list) {
if (list.isEmpty()) return;
GL11.glDisable(GL12.GL_RESCALE_NORMAL);
GL11.glDisable(GL11.GL_DEPTH_TEST);
RenderHelper.disableStandardItemLighting();
int w = 0;
int h = -2;
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
ITooltipLineHandler line = getTipLine(s);
Dimension d = line != null ? line.getSize()
: new Dimension(
font.getStringWidth(s),
list.get(i).endsWith(TOOLTIP_LINESPACE) && i + 1 < list.size() ? 12 : 10);
w = Math.max(w, d.width);
h += d.height;
}
if (x < 8) x = 8;
else if (x > displaySize().width - w - 8) {
x -= 24 + w; // flip side of cursor
if (x < 8) x = 8;
}
y = (int) MathHelper.clip(y, 8, displaySize().height - 8 - h);
gui.incZLevel(300);
drawTooltipBox(x - 4, y - 4, w + 7, h + 7);
for (String s : list) {
ITooltipLineHandler line = getTipLine(s);
if (line != null) {
line.draw(x, y);
y += line.getSize().height;
} else {
font.drawStringWithShadow(s, x, y, -1);
y += s.endsWith(TOOLTIP_LINESPACE) ? 12 : 10;
}
}
tipLineHandlers.clear();
gui.incZLevel(-300);
GL11.glEnable(GL11.GL_DEPTH_TEST);
GL11.glEnable(GL12.GL_RESCALE_NORMAL);
RenderHelper.enableGUIStandardItemLighting();
}
public static void drawTooltipBox(int x, int y, int w, int h) {
int bg = 0xf0100010;
drawGradientRect(x + 1, y, w - 1, 1, bg, bg);
drawGradientRect(x + 1, y + h, w - 1, 1, bg, bg);
drawGradientRect(x + 1, y + 1, w - 1, h - 1, bg, bg); // center
drawGradientRect(x, y + 1, 1, h - 1, bg, bg);
drawGradientRect(x + w, y + 1, 1, h - 1, bg, bg);
int grad1 = 0x505000ff;
int grad2 = 0x5028007F;
drawGradientRect(x + 1, y + 2, 1, h - 3, grad1, grad2);
drawGradientRect(x + w - 1, y + 2, 1, h - 3, grad1, grad2);
drawGradientRect(x + 1, y + 1, w - 1, 1, grad1, grad1);
drawGradientRect(x + 1, y + h - 1, w - 1, 1, grad2, grad2);
}
}
@@ -0,0 +1,274 @@
package codechicken.lib.inventory;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.inventory.Container;
import net.minecraft.inventory.ICrafting;
import net.minecraft.inventory.Slot;
import net.minecraft.item.ItemStack;
import net.minecraft.network.play.INetHandlerPlayClient;
import net.minecraft.network.play.INetHandlerPlayServer;
import org.apache.logging.log4j.LogManager;
import codechicken.lib.packet.PacketCustom;
import codechicken.lib.packet.PacketCustom.IClientPacketHandler;
import codechicken.lib.packet.PacketCustom.IServerPacketHandler;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.relauncher.Side;
/**
* Clean container implementation with a few extra features. Easy shift-click handling. Hooks for slot clicks, large
* stack sizes and some networking.
*/
public abstract class ContainerExtended extends Container implements ICrafting {
private static final String netChannel = "CCL:Container";
private static int nextNetworkID = 0;
static {
PacketCustom.assignHandler(netChannel, new IClientPacketHandler() {
@Override
public void handlePacket(PacketCustom packet, Minecraft mc, INetHandlerPlayClient handler) {
Container cont = mc.thePlayer.openContainer;
if (!(cont instanceof ContainerExtended)) return;
ContainerExtended c = (ContainerExtended) cont;
if (packet.getType() == 1) c.netID = packet.readInt();
else if (c.netID == packet.readInt()) c.handleClientPacket(packet);
}
});
PacketCustom.assignHandler(netChannel, new IServerPacketHandler() {
@Override
public void handlePacket(PacketCustom packet, EntityPlayerMP sender, INetHandlerPlayServer handler) {
Container cont = sender.openContainer;
if (!(cont instanceof ContainerExtended)) return;
ContainerExtended c = (ContainerExtended) cont;
if (c.netID == packet.readInt()) c.handleServerPacket(packet);
}
});
}
public LinkedList<EntityPlayerMP> playerCrafters = new LinkedList<EntityPlayerMP>();
private int netID;
public ContainerExtended() {
crafters.add(this);
if (FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER) netID = ++nextNetworkID;
}
@Override
public void addCraftingToCrafters(ICrafting icrafting) {
if (icrafting instanceof EntityPlayerMP) {
playerCrafters.add((EntityPlayerMP) icrafting);
sendNetID((EntityPlayerMP) icrafting);
sendContainerAndContentsToPlayer(this, getInventory(), Arrays.asList((EntityPlayerMP) icrafting));
detectAndSendChanges();
} else super.addCraftingToCrafters(icrafting);
}
private void sendNetID(EntityPlayerMP player) {
if (netID == 0) LogManager.getLogger("CodeChickenLib").error("Player added to container with 0 network ID");
else new PacketCustom(netChannel, 1).writeInt(netID).sendToPlayer(player);
}
@Override
public void removeCraftingFromCrafters(ICrafting icrafting) {
if (icrafting instanceof EntityPlayerMP) playerCrafters.remove(icrafting);
else super.removeCraftingFromCrafters(icrafting);
}
@Override
public void sendContainerAndContentsToPlayer(Container container, List list) {
sendContainerAndContentsToPlayer(container, list, playerCrafters);
}
public void sendContainerAndContentsToPlayer(Container container, List<ItemStack> list,
List<EntityPlayerMP> playerCrafters) {
LinkedList<ItemStack> largeStacks = new LinkedList<ItemStack>();
for (int i = 0; i < list.size(); i++) {
ItemStack stack = list.get(i);
if (stack != null && stack.stackSize > Byte.MAX_VALUE) {
list.set(i, null);
largeStacks.add(stack);
} else largeStacks.add(null);
}
for (EntityPlayerMP player : playerCrafters) player.sendContainerAndContentsToPlayer(container, list);
for (int i = 0; i < largeStacks.size(); i++) {
ItemStack stack = largeStacks.get(i);
if (stack != null) sendLargeStack(stack, i, playerCrafters);
}
}
public void sendLargeStack(ItemStack stack, int slot, List<EntityPlayerMP> players) {}
@Override
public void sendProgressBarUpdate(Container container, int i, int j) {
for (EntityPlayerMP player : playerCrafters) player.sendProgressBarUpdate(container, i, j);
}
@Override
public void sendSlotContents(Container container, int slot, ItemStack stack) {
if (stack != null && stack.stackSize > Byte.MAX_VALUE) sendLargeStack(stack, slot, playerCrafters);
else for (EntityPlayerMP player : playerCrafters) player.sendSlotContents(container, slot, stack);
}
@Override
public ItemStack slotClick(int par1, int par2, int par3, EntityPlayer player) {
if (par1 >= 0 && par1 < inventorySlots.size()) {
Slot slot = getSlot(par1);
if (slot instanceof SlotHandleClicks) return ((SlotHandleClicks) slot).slotClick(this, player, par2, par3);
}
return super.slotClick(par1, par2, par3, player);
}
@Override
public ItemStack transferStackInSlot(EntityPlayer par1EntityPlayer, int slotIndex) {
ItemStack transferredStack = null;
Slot slot = (Slot) inventorySlots.get(slotIndex);
if (slot != null && slot.getHasStack()) {
ItemStack stack = slot.getStack();
transferredStack = stack.copy();
if (!doMergeStackAreas(slotIndex, stack)) return null;
if (stack.stackSize == 0) slot.putStack(null);
else slot.onSlotChanged();
}
return transferredStack;
}
@Override
public boolean mergeItemStack(ItemStack stack, int startIndex, int endIndex, boolean reverse) {
boolean merged = false;
int slotIndex = reverse ? endIndex - 1 : startIndex;
if (stack == null) return false;
if (stack.isStackable()) // search for stacks to increase
{
while (stack.stackSize > 0 && (reverse ? slotIndex >= startIndex : slotIndex < endIndex)) {
Slot slot = (Slot) inventorySlots.get(slotIndex);
ItemStack slotStack = slot.getStack();
if (slotStack != null && slotStack.getItem() == stack.getItem()
&& (!stack.getHasSubtypes() || stack.getItemDamage() == slotStack.getItemDamage())
&& ItemStack.areItemStackTagsEqual(stack, slotStack)) {
int totalStackSize = slotStack.stackSize + stack.stackSize;
int maxStackSize = Math.min(stack.getMaxStackSize(), slot.getSlotStackLimit());
if (totalStackSize <= maxStackSize) {
stack.stackSize = 0;
slotStack.stackSize = totalStackSize;
slot.onSlotChanged();
merged = true;
} else if (slotStack.stackSize < maxStackSize) {
stack.stackSize -= maxStackSize - slotStack.stackSize;
slotStack.stackSize = maxStackSize;
slot.onSlotChanged();
merged = true;
}
}
slotIndex += reverse ? -1 : 1;
}
}
if (stack.stackSize > 0) // normal transfer :)
{
slotIndex = reverse ? endIndex - 1 : startIndex;
while (stack.stackSize > 0 && (reverse ? slotIndex >= startIndex : slotIndex < endIndex)) {
Slot slot = (Slot) this.inventorySlots.get(slotIndex);
if (!slot.getHasStack() && slot.isItemValid(stack)) {
int maxStackSize = Math.min(stack.getMaxStackSize(), slot.getSlotStackLimit());
if (stack.stackSize <= maxStackSize) {
slot.putStack(stack.copy());
slot.onSlotChanged();
stack.stackSize = 0;
merged = true;
} else {
slot.putStack(stack.splitStack(maxStackSize));
slot.onSlotChanged();
merged = true;
}
}
slotIndex += reverse ? -1 : 1;
}
}
return merged;
}
/**
* Called when slotIndex is shift clicked on. Recommended implementation is to call mergeItemStack based on
* slotIndex
*
* @param stack The stack in the clicked slot
* @return True if one or more items were moved from this slots into other slots
*/
public boolean doMergeStackAreas(int slotIndex, ItemStack stack) {
return false;
}
protected void bindPlayerInventory(InventoryPlayer inventoryPlayer) {
bindPlayerInventory(inventoryPlayer, 8, 84);
}
protected void bindPlayerInventory(InventoryPlayer inventoryPlayer, int x, int y) {
for (int row = 0; row < 3; row++) for (int col = 0; col < 9; col++)
addSlotToContainer(new Slot(inventoryPlayer, col + row * 9 + 9, x + col * 18, y + row * 18));
for (int slot = 0; slot < 9; slot++) addSlotToContainer(new Slot(inventoryPlayer, slot, x + slot * 18, y + 58));
}
@Override
public boolean canInteractWith(EntityPlayer var1) {
return true;
}
public void sendContainerPacket(PacketCustom packet) {
for (EntityPlayerMP player : playerCrafters) packet.sendToPlayer(player);
}
/**
* @param type An identifying number for the packet type between 2 and 0x79 inclusive. 1 is reserved for
* synchronising networkIDs
* @return A packet on the CCL inventory channel that will be recieved by this container on the other network side
*/
public PacketCustom getPacket(int type) {
if (netID == 0)
LogManager.getLogger("CodeChickenLib").error("Tried to get packet for container with 0 network ID");
if (type == 1) throw new IllegalArgumentException(
"Packet type 1 is reserved for network synchronisation in ContainerExtended");
return new PacketCustom(netChannel, type).writeInt(netID);
}
/**
* Handle a packet from the server obtained by getPacket.
*/
public void handleClientPacket(PacketCustom packet) {}
/**
* Handle a packet from the client obtained by getPacket.
*/
public void handleServerPacket(PacketCustom packet) {}
public void sendProgressBarUpdate(int barID, int value) {
for (ICrafting crafting : (List<ICrafting>) crafters) crafting.sendProgressBarUpdate(this, barID, value);
}
}
@@ -0,0 +1,67 @@
package codechicken.lib.inventory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.inventory.Container;
import net.minecraft.item.ItemStack;
import codechicken.lib.packet.PacketCustom;
public abstract class ContainerSynchronised extends ContainerExtended {
private ArrayList<IContainerSyncVar> syncVars = new ArrayList<IContainerSyncVar>();
/**
* Create a packet to be used to send a synced variable update. Calls getPacket. Can be overriden to add extra
* identifying data or change the type (default 2)
*/
public PacketCustom createSyncPacket() {
return getPacket(2);
}
@Override
public final void detectAndSendChanges() {
super.detectAndSendChanges();
for (int i = 0; i < syncVars.size(); i++) {
IContainerSyncVar var = syncVars.get(i);
if (var.changed()) {
PacketCustom packet = createSyncPacket();
packet.writeByte(i);
var.writeChange(packet);
sendContainerPacket(packet);
var.reset();
}
}
}
@Override
public void sendContainerAndContentsToPlayer(Container container, List<ItemStack> list,
List<EntityPlayerMP> playerCrafters) {
super.sendContainerAndContentsToPlayer(container, list, playerCrafters);
for (int i = 0; i < syncVars.size(); i++) {
IContainerSyncVar var = syncVars.get(i);
PacketCustom packet = createSyncPacket();
packet.writeByte(i);
var.writeChange(packet);
var.reset();
for (EntityPlayerMP player : playerCrafters) packet.sendToPlayer(player);
}
}
public void addSyncVar(IContainerSyncVar var) {
syncVars.add(var);
}
@Override
public final void handleClientPacket(PacketCustom packet) {
syncVars.get(packet.readUByte()).readChange(packet);
}
public List<IContainerSyncVar> getSyncedVars() {
return Collections.unmodifiableList(syncVars);
}
}
@@ -0,0 +1,14 @@
package codechicken.lib.inventory;
import codechicken.lib.packet.PacketCustom;
public interface IContainerSyncVar {
public boolean changed();
public void reset();
public void writeChange(PacketCustom packet);
public void readChange(PacketCustom packet);
}
@@ -0,0 +1,30 @@
package codechicken.lib.inventory;
import codechicken.lib.packet.PacketCustom;
public abstract class IntegerSync implements IContainerSyncVar {
public int c_value;
@Override
public boolean changed() {
return getValue() != c_value;
}
@Override
public void reset() {
c_value = getValue();
}
@Override
public void writeChange(PacketCustom packet) {
packet.writeInt(getValue());
}
@Override
public void readChange(PacketCustom packet) {
c_value = packet.readInt();
}
public abstract int getValue();
}
@@ -0,0 +1,103 @@
package codechicken.lib.inventory;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
/**
* Creates a copy of an IInventory for extended simulation
*/
public class InventoryCopy implements IInventory {
public boolean[] accessible;
public ItemStack[] items;
public IInventory inv;
public InventoryCopy(IInventory inv) {
items = new ItemStack[inv.getSizeInventory()];
accessible = new boolean[inv.getSizeInventory()];
this.inv = inv;
update();
}
public void update() {
for (int i = 0; i < items.length; i++) {
ItemStack stack = inv.getStackInSlot(i);
if (stack != null) items[i] = stack.copy();
}
}
public InventoryCopy open(InventoryRange access) {
int lslot = access.lastSlot();
if (lslot > accessible.length) {
boolean[] l_accessible = new boolean[lslot];
ItemStack[] l_items = new ItemStack[lslot];
System.arraycopy(accessible, 0, l_accessible, 0, accessible.length);
System.arraycopy(items, 0, l_items, 0, items.length);
accessible = l_accessible;
items = l_items;
}
for (int slot : access.slots) accessible[slot] = true;
return this;
}
@Override
public int getSizeInventory() {
return items.length;
}
@Override
public ItemStack getStackInSlot(int slot) {
return items[slot];
}
public ItemStack decrStackSize(int slot, int amount) {
return InventoryUtils.decrStackSize(this, slot, amount);
}
@Override
public ItemStack getStackInSlotOnClosing(int slot) {
return InventoryUtils.getStackInSlotOnClosing(this, slot);
}
@Override
public void setInventorySlotContents(int slot, ItemStack stack) {
items[slot] = stack;
markDirty();
}
@Override
public String getInventoryName() {
return "copy";
}
@Override
public boolean isUseableByPlayer(EntityPlayer player) {
return true;
}
@Override
public void openInventory() {}
@Override
public void closeInventory() {}
@Override
public int getInventoryStackLimit() {
return 64;
}
@Override
public void markDirty() {}
@Override
public boolean isItemValidForSlot(int i, ItemStack itemstack) {
return inv.isItemValidForSlot(i, itemstack);
}
@Override
public boolean hasCustomInventoryName() {
return true;
}
}
@@ -0,0 +1,91 @@
package codechicken.lib.inventory;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
/**
* IInventory implementation which saves and loads from an NBT tag
*/
public class InventoryNBT implements IInventory {
protected ItemStack[] items;
protected NBTTagCompound tag;
public InventoryNBT(int size, NBTTagCompound tag) {
this.tag = tag;
items = new ItemStack[size];
readNBT();
}
private void writeNBT() {
tag.setTag("items", InventoryUtils.writeItemStacksToTag(items, getInventoryStackLimit()));
}
private void readNBT() {
if (tag.hasKey("items")) InventoryUtils.readItemStacksFromTag(items, tag.getTagList("items", 10));
}
@Override
public int getSizeInventory() {
return items.length;
}
@Override
public ItemStack getStackInSlot(int slot) {
return items[slot];
}
@Override
public ItemStack decrStackSize(int slot, int amount) {
return InventoryUtils.decrStackSize(this, slot, amount);
}
@Override
public ItemStack getStackInSlotOnClosing(int slot) {
return InventoryUtils.getStackInSlotOnClosing(this, slot);
}
@Override
public void setInventorySlotContents(int slot, ItemStack stack) {
items[slot] = stack;
markDirty();
}
@Override
public String getInventoryName() {
return "NBT";
}
@Override
public int getInventoryStackLimit() {
return 64;
}
@Override
public void markDirty() {
writeNBT();
}
@Override
public boolean isUseableByPlayer(EntityPlayer var1) {
return true;
}
@Override
public void openInventory() {}
@Override
public void closeInventory() {}
@Override
public boolean isItemValidForSlot(int i, ItemStack itemstack) {
return true;
}
@Override
public boolean hasCustomInventoryName() {
return true;
}
}
@@ -0,0 +1,59 @@
package codechicken.lib.inventory;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.ISidedInventory;
import net.minecraft.item.ItemStack;
/**
* Inventory wrapper for unified ISided/IInventory access
*/
public class InventoryRange {
public IInventory inv;
public int side;
public ISidedInventory sidedInv;
public int[] slots;
public InventoryRange(IInventory inv, int side) {
this.inv = inv;
this.side = side;
if (inv instanceof ISidedInventory) {
sidedInv = (ISidedInventory) inv;
slots = sidedInv.getAccessibleSlotsFromSide(side);
} else {
slots = new int[inv.getSizeInventory()];
for (int i = 0; i < slots.length; i++) slots[i] = i;
}
}
public InventoryRange(IInventory inv) {
this(inv, 0);
}
public InventoryRange(IInventory inv, int fslot, int size) {
this.inv = inv;
slots = new int[size];
for (int i = 0; i < slots.length; i++) slots[i] = fslot + i;
}
public InventoryRange(IInventory inv, InventoryRange access) {
this.inv = inv;
this.slots = access.slots;
this.side = access.side;
if (inv instanceof ISidedInventory) sidedInv = (ISidedInventory) inv;
}
public boolean canInsertItem(int slot, ItemStack item) {
return sidedInv == null ? inv.isItemValidForSlot(slot, item) : sidedInv.canInsertItem(slot, item, side);
}
public boolean canExtractItem(int slot, ItemStack item) {
return sidedInv == null ? inv.isItemValidForSlot(slot, item) : sidedInv.canExtractItem(slot, item, side);
}
public int lastSlot() {
int last = 0;
for (int slot : slots) if (slot > last) last = slot;
return last;
}
}
@@ -0,0 +1,109 @@
package codechicken.lib.inventory;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
/**
* Simple IInventory implementation with an array of items, name and maximum stack size
*/
public class InventorySimple implements IInventory {
public ItemStack[] items;
public int limit;
public String name;
public InventorySimple(ItemStack[] items, int limit, String name) {
this.items = items;
this.limit = limit;
this.name = name;
}
public InventorySimple(ItemStack[] items, String name) {
this(items, 64, name);
}
public InventorySimple(ItemStack[] items, int limit) {
this(items, limit, "inv");
}
public InventorySimple(ItemStack[] items) {
this(items, 64, "inv");
}
public InventorySimple(int size, int limit, String name) {
this(new ItemStack[size], limit, name);
}
public InventorySimple(int size, int limit) {
this(size, limit, "inv");
}
public InventorySimple(int size, String name) {
this(size, 64, name);
}
public InventorySimple(int size) {
this(size, 64, "inv");
}
@Override
public int getSizeInventory() {
return items.length;
}
@Override
public ItemStack getStackInSlot(int slot) {
return items[slot];
}
@Override
public ItemStack decrStackSize(int slot, int amount) {
return InventoryUtils.decrStackSize(this, slot, amount);
}
@Override
public ItemStack getStackInSlotOnClosing(int slot) {
return InventoryUtils.getStackInSlotOnClosing(this, slot);
}
@Override
public void setInventorySlotContents(int slot, ItemStack stack) {
items[slot] = stack;
markDirty();
}
@Override
public String getInventoryName() {
return name;
}
@Override
public int getInventoryStackLimit() {
return limit;
}
@Override
public boolean isUseableByPlayer(EntityPlayer var1) {
return true;
}
@Override
public void openInventory() {}
@Override
public void closeInventory() {}
@Override
public boolean isItemValidForSlot(int i, ItemStack itemstack) {
return true;
}
@Override
public boolean hasCustomInventoryName() {
return true;
}
@Override
public void markDirty() {}
}
@@ -0,0 +1,323 @@
package codechicken.lib.inventory;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Items;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.InventoryLargeChest;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTBase;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.tileentity.TileEntityChest;
import net.minecraft.world.World;
import net.minecraftforge.common.util.ForgeDirection;
import com.google.common.base.Objects;
import codechicken.lib.vec.Vector3;
public class InventoryUtils {
/**
* Constructor for ItemStack with tag
*/
public static ItemStack newItemStack(Item item, int size, int damage, NBTTagCompound tag) {
ItemStack stack = new ItemStack(item, size, damage);
stack.stackTagCompound = tag;
return stack;
}
/**
* Gets the actual damage of an item without asking the Item
*/
public static int actualDamage(ItemStack stack) {
return Items.diamond.getDamage(stack);
}
/**
* Static default implementation for IInventory method
*/
public static ItemStack decrStackSize(IInventory inv, int slot, int size) {
ItemStack item = inv.getStackInSlot(slot);
if (item != null) {
if (item.stackSize <= size) {
inv.setInventorySlotContents(slot, null);
inv.markDirty();
return item;
}
ItemStack itemstack1 = item.splitStack(size);
if (item.stackSize == 0) inv.setInventorySlotContents(slot, null);
else inv.setInventorySlotContents(slot, item);
inv.markDirty();
return itemstack1;
}
return null;
}
/**
* Static default implementation for IInventory method
*/
public static ItemStack getStackInSlotOnClosing(IInventory inv, int slot) {
ItemStack stack = inv.getStackInSlot(slot);
inv.setInventorySlotContents(slot, null);
return stack;
}
/**
* @return The quantity of items from addition that can be added to base
*/
public static int incrStackSize(ItemStack base, ItemStack addition) {
if (canStack(base, addition)) return incrStackSize(base, addition.stackSize);
return 0;
}
/**
* @return The quantity of items from addition that can be added to base
*/
public static int incrStackSize(ItemStack base, int addition) {
int totalSize = base.stackSize + addition;
if (totalSize <= base.getMaxStackSize()) return addition;
else if (base.stackSize < base.getMaxStackSize()) return base.getMaxStackSize() - base.stackSize;
return 0;
}
/**
* NBT item saving function
*/
public static NBTTagList writeItemStacksToTag(ItemStack[] items) {
return writeItemStacksToTag(items, 64);
}
/**
* NBT item saving function with support for stack sizes > 32K
*/
public static NBTTagList writeItemStacksToTag(ItemStack[] items, int maxQuantity) {
NBTTagList tagList = new NBTTagList();
for (int i = 0; i < items.length; i++) {
if (items[i] != null) {
NBTTagCompound tag = new NBTTagCompound();
tag.setShort("Slot", (short) i);
items[i].writeToNBT(tag);
if (maxQuantity > Short.MAX_VALUE) tag.setInteger("Quantity", items[i].stackSize);
else if (maxQuantity > Byte.MAX_VALUE) tag.setShort("Quantity", (short) items[i].stackSize);
tagList.appendTag(tag);
}
}
return tagList;
}
/**
* NBT item loading function with support for stack sizes > 32K
*/
public static void readItemStacksFromTag(ItemStack[] items, NBTTagList tagList) {
for (int i = 0; i < tagList.tagCount(); i++) {
NBTTagCompound tag = tagList.getCompoundTagAt(i);
int b = tag.getShort("Slot");
items[b] = ItemStack.loadItemStackFromNBT(tag);
if (tag.hasKey("Quantity"))
items[b].stackSize = ((NBTBase.NBTPrimitive) tag.getTag("Quantity")).func_150287_d();
}
}
/**
* Spawns an itemstack in the world at a location
*/
public static void dropItem(ItemStack stack, World world, Vector3 dropLocation) {
EntityItem item = new EntityItem(world, dropLocation.x, dropLocation.y, dropLocation.z, stack);
item.motionX = world.rand.nextGaussian() * 0.05;
item.motionY = world.rand.nextGaussian() * 0.05 + 0.2F;
item.motionZ = world.rand.nextGaussian() * 0.05;
world.spawnEntityInWorld(item);
}
/**
* Copies an itemstack with a new quantity
*/
public static ItemStack copyStack(ItemStack stack, int quantity) {
if (stack == null) return null;
stack = stack.copy();
stack.stackSize = quantity;
return stack;
}
/**
* Gets the maximum quantity of an item that can be inserted into inv
*/
public static int getInsertibleQuantity(InventoryRange inv, ItemStack stack) {
int quantity = 0;
stack = copyStack(stack, Integer.MAX_VALUE);
for (int slot : inv.slots) quantity += fitStackInSlot(inv, slot, stack);
return quantity;
}
public static int getInsertibleQuantity(IInventory inv, ItemStack stack) {
return getInsertibleQuantity(new InventoryRange(inv), stack);
}
public static int fitStackInSlot(InventoryRange inv, int slot, ItemStack stack) {
ItemStack base = inv.inv.getStackInSlot(slot);
if (!canStack(base, stack) || !inv.canInsertItem(slot, stack)) return 0;
int fit = base != null ? incrStackSize(base, inv.inv.getInventoryStackLimit() - base.stackSize)
: inv.inv.getInventoryStackLimit();
return Math.min(fit, stack.stackSize);
}
public static int fitStackInSlot(IInventory inv, int slot, ItemStack stack) {
return fitStackInSlot(new InventoryRange(inv), slot, stack);
}
/**
* @param simulate If set to true, no items will actually be inserted
* @return The number of items unable to be inserted
*/
public static int insertItem(InventoryRange inv, ItemStack stack, boolean simulate) {
stack = stack.copy();
for (int pass = 0; pass < 2; pass++) {
for (int slot : inv.slots) {
ItemStack base = inv.inv.getStackInSlot(slot);
if ((pass == 0) == (base == null)) continue;
int fit = fitStackInSlot(inv, slot, stack);
if (fit == 0) continue;
if (base != null) {
stack.stackSize -= fit;
if (!simulate) {
base.stackSize += fit;
inv.inv.setInventorySlotContents(slot, base);
}
} else {
if (!simulate) inv.inv.setInventorySlotContents(slot, copyStack(stack, fit));
stack.stackSize -= fit;
}
if (stack.stackSize == 0) return 0;
}
}
return stack.stackSize;
}
public static int insertItem(IInventory inv, ItemStack stack, boolean simulate) {
return insertItem(new InventoryRange(inv), stack, simulate);
}
/**
* Gets the stack in slot if it can be extracted
*/
public static ItemStack getExtractableStack(InventoryRange inv, int slot) {
ItemStack stack = inv.inv.getStackInSlot(slot);
if (stack == null || !inv.canExtractItem(slot, stack)) return null;
return stack;
}
public static ItemStack getExtractableStack(IInventory inv, int slot) {
return getExtractableStack(new InventoryRange(inv), slot);
}
public static boolean areStacksIdentical(ItemStack stack1, ItemStack stack2) {
if (stack1 == null || stack2 == null) return stack1 == stack2;
return stack1.getItem() == stack2.getItem() && stack1.getItemDamage() == stack2.getItemDamage()
&& stack1.stackSize == stack2.stackSize
&& Objects.equal(stack1.getTagCompound(), stack2.getTagCompound());
}
/**
* Gets an IInventory from a coordinate with support for double chests
*/
public static IInventory getInventory(World world, int x, int y, int z) {
TileEntity tile = world.getTileEntity(x, y, z);
if (!(tile instanceof IInventory)) return null;
if (tile instanceof TileEntityChest) return getChest((TileEntityChest) tile);
return (IInventory) tile;
}
public static final ForgeDirection[] chestSides = new ForgeDirection[] { ForgeDirection.WEST, ForgeDirection.EAST,
ForgeDirection.NORTH, ForgeDirection.SOUTH };
public static IInventory getChest(TileEntityChest chest) {
for (ForgeDirection fside : chestSides) {
if (chest.getWorldObj()
.getBlock(chest.xCoord + fside.offsetX, chest.yCoord + fside.offsetY, chest.zCoord + fside.offsetZ)
== chest.getBlockType())
return new InventoryLargeChest(
"container.chestDouble",
(TileEntityChest) chest.getWorldObj().getTileEntity(
chest.xCoord + fside.offsetX,
chest.yCoord + fside.offsetY,
chest.zCoord + fside.offsetZ),
chest);
}
return chest;
}
public static boolean canStack(ItemStack stack1, ItemStack stack2) {
return stack1 == null || stack2 == null
|| (stack1.getItem() == stack2.getItem()
&& (!stack2.getHasSubtypes() || stack2.getItemDamage() == stack1.getItemDamage())
&& ItemStack.areItemStackTagsEqual(stack2, stack1)) && stack1.isStackable();
}
/**
* Consumes one item from slot in inv with support for containers.
*/
public static void consumeItem(IInventory inv, int slot) {
ItemStack stack = inv.getStackInSlot(slot);
Item item = stack.getItem();
if (item.hasContainerItem(stack)) {
ItemStack container = item.getContainerItem(stack);
inv.setInventorySlotContents(slot, container);
} else {
inv.decrStackSize(slot, 1);
}
}
/**
* Gets the size of the stack in a slot. Returns 0 on null stacks
*/
public static int stackSize(IInventory inv, int slot) {
ItemStack stack = inv.getStackInSlot(slot);
return stack == null ? 0 : stack.stackSize;
}
/**
* Drops all items from inv using getStackInSlotOnClosing
*/
public static void dropOnClose(EntityPlayer player, IInventory inv) {
for (int i = 0; i < inv.getSizeInventory(); i++) {
ItemStack stack = inv.getStackInSlotOnClosing(i);
if (stack != null) player.dropPlayerItemWithRandomChoice(stack, false);
}
}
public static NBTTagCompound savePersistant(ItemStack stack, NBTTagCompound tag) {
stack.writeToNBT(tag);
tag.removeTag("id");
tag.setString("name", Item.itemRegistry.getNameForObject(stack.getItem()));
return tag;
}
public static ItemStack loadPersistant(NBTTagCompound tag) {
String name = tag.getString("name");
Item item = (Item) Item.itemRegistry.getObject(name);
if (item == null) return null;
int count = tag.hasKey("Count") ? tag.getByte("Count") : 1;
int damage = tag.hasKey("Damage") ? tag.getShort("Damage") : 0;
ItemStack stack = new ItemStack(item, count, damage);
if (tag.hasKey("tag", 10)) stack.stackTagCompound = tag.getCompoundTag("tag");
return stack;
}
}
@@ -0,0 +1,63 @@
package codechicken.lib.inventory;
import static codechicken.lib.inventory.InventoryUtils.actualDamage;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraftforge.oredict.OreDictionary;
import com.google.common.base.Objects;
/**
* Comparable ItemStack with a hashCode implementation.
*/
public class ItemKey implements Comparable<ItemKey> {
public ItemStack stack;
private int hashcode = 0;
public ItemKey(ItemStack k) {
stack = k;
}
public ItemKey(Item item, int damage) {
this(new ItemStack(item, 1, damage));
}
public ItemKey(Item item, NBTTagCompound tag) {
this(item, OreDictionary.WILDCARD_VALUE, tag);
}
public ItemKey(Item item, int damage, NBTTagCompound tag) {
this(item, damage);
stack.setTagCompound(tag);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ItemKey)) return false;
ItemKey k = (ItemKey) obj;
return stack.getItem() == k.stack.getItem() && actualDamage(stack) == actualDamage(k.stack)
&& Objects.equal(stack.stackTagCompound, k.stack.stackTagCompound);
}
@Override
public int hashCode() {
return hashcode != 0 ? hashcode
: (hashcode = Objects.hashCode(stack.getItem(), actualDamage(stack), stack.stackTagCompound));
}
public int compareInt(int a, int b) {
return a == b ? 0 : a < b ? -1 : 1;
}
@Override
public int compareTo(ItemKey o) {
if (stack.getItem() != o.stack.getItem())
return compareInt(Item.getIdFromItem(stack.getItem()), Item.getIdFromItem(o.stack.getItem()));
if (actualDamage(stack) != actualDamage(o.stack)) return compareInt(actualDamage(stack), actualDamage(o.stack));
return 0;
}
}
@@ -0,0 +1,55 @@
package codechicken.lib.inventory;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
public class SlotDummy extends SlotHandleClicks {
public final int stackLimit;
public SlotDummy(IInventory inv, int slot, int x, int y) {
this(inv, slot, x, y, 64);
}
public SlotDummy(IInventory inv, int slot, int x, int y, int limit) {
super(inv, slot, x, y);
stackLimit = limit;
}
@Override
public ItemStack slotClick(ContainerExtended container, EntityPlayer player, int button, int modifier) {
ItemStack held = player.inventory.getItemStack();
boolean shift = modifier == 1;
slotClick(held, button, shift);
return null;
}
public void slotClick(ItemStack held, int button, boolean shift) {
ItemStack tstack = getStack();
if (held != null && (tstack == null || !InventoryUtils.canStack(held, tstack))) {
int quantity = Math.min(held.stackSize, stackLimit);
if (shift) quantity = Math.min(stackLimit, held.getMaxStackSize() * 16);
if (button == 1) quantity = 1;
putStack(InventoryUtils.copyStack(held, quantity));
} else if (tstack != null) {
int inc;
if (held != null) {
inc = button == 1 ? -held.stackSize : held.stackSize;
if (shift) inc *= 16;
} else {
inc = button == 1 ? -1 : 1;
if (shift) inc *= 16;
}
int quantity = tstack.stackSize + inc;
if (quantity <= 0) putStack(null);
else putStack(InventoryUtils.copyStack(tstack, quantity));
}
}
@Override
public void putStack(ItemStack stack) {
if (stack != null && stack.stackSize > stackLimit) stack = InventoryUtils.copyStack(stack, stackLimit);
super.putStack(stack);
}
}
@@ -0,0 +1,17 @@
package codechicken.lib.inventory;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.item.ItemStack;
public class SlotDummyOutput extends SlotHandleClicks {
public SlotDummyOutput(IInventory inv, int slot, int x, int y) {
super(inv, slot, x, y);
}
@Override
public ItemStack slotClick(ContainerExtended container, EntityPlayer player, int button, int modifier) {
return null;
}
}
@@ -0,0 +1,15 @@
package codechicken.lib.inventory;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.Slot;
import net.minecraft.item.ItemStack;
public abstract class SlotHandleClicks extends Slot {
public SlotHandleClicks(IInventory inv, int slot, int x, int y) {
super(inv, slot, x, y);
}
public abstract ItemStack slotClick(ContainerExtended container, EntityPlayer player, int button, int modifier);
}
@@ -0,0 +1,87 @@
package codechicken.lib.lighting;
import codechicken.lib.render.CCModel;
import codechicken.lib.util.Copyable;
import codechicken.lib.vec.Rotation;
import codechicken.lib.vec.Vector3;
public class LC implements Copyable<LC> {
public int side;
public float fa;
public float fb;
public float fc;
public float fd;
public LC() {
this(0, 0, 0, 0, 0);
}
public LC(int s, float a, float b, float c, float d) {
side = s;
fa = a;
fb = b;
fc = c;
fd = d;
}
public LC set(int s, float a, float b, float c, float d) {
side = s;
fa = a;
fb = b;
fc = c;
fd = d;
return this;
}
public LC set(LC lc) {
return set(lc.side, lc.fa, lc.fb, lc.fc, lc.fd);
}
public LC compute(Vector3 vec, Vector3 normal) {
int side = CCModel.findSide(normal);
if (side < 0) return set(12, 1, 0, 0, 0);
return compute(vec, side);
}
public LC compute(Vector3 vec, int side) {
boolean offset = false;
switch (side) {
case 0:
offset = vec.y <= 0;
break;
case 1:
offset = vec.y >= 1;
break;
case 2:
offset = vec.z <= 0;
break;
case 3:
offset = vec.z >= 1;
break;
case 4:
offset = vec.x <= 0;
break;
case 5:
offset = vec.x >= 1;
break;
}
if (!offset) side += 6;
return computeO(vec, side);
}
public LC computeO(Vector3 vec, int side) {
Vector3 v1 = Rotation.axes[((side & 0xE) + 3) % 6];
Vector3 v2 = Rotation.axes[((side & 0xE) + 5) % 6];
float d1 = (float) vec.scalarProject(v1);
float d2 = 1 - d1;
float d3 = (float) vec.scalarProject(v2);
float d4 = 1 - d3;
return set(side, d2 * d4, d2 * d3, d1 * d4, d1 * d3);
}
@Override
public LC copy() {
return new LC(side, fa, fb, fc, fd);
}
}
@@ -0,0 +1,139 @@
package codechicken.lib.lighting;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.world.IBlockAccess;
import codechicken.lib.colour.ColourRGBA;
import codechicken.lib.render.CCRenderState;
import codechicken.lib.vec.BlockCoord;
/**
* Note that when using the class as a vertex transformer, the vertices are assumed to be within the BB (x, y, z) ->
* (x+1, y+1, z+1)
*/
public class LightMatrix implements CCRenderState.IVertexOperation {
public static final int operationIndex = CCRenderState.registerOperation();
public int computed = 0;
public float[][] ao = new float[13][4];
public int[][] brightness = new int[13][4];
public IBlockAccess access;
public BlockCoord pos = new BlockCoord();
private int sampled = 0;
private float[] aSamples = new float[27];
private int[] bSamples = new int[27];
/**
* The 9 positions in the sample array for each side, sides >= 6 are centered on sample 13 (the block itself)
*/
public static final int[][] ssamplem = new int[][] { { 0, 1, 2, 3, 4, 5, 6, 7, 8 },
{ 18, 19, 20, 21, 22, 23, 24, 25, 26 }, { 0, 9, 18, 1, 10, 19, 2, 11, 20 },
{ 6, 15, 24, 7, 16, 25, 8, 17, 26 }, { 0, 3, 6, 9, 12, 15, 18, 21, 24 },
{ 2, 5, 8, 11, 14, 17, 20, 23, 26 }, { 9, 10, 11, 12, 13, 14, 15, 16, 17 },
{ 9, 10, 11, 12, 13, 14, 15, 16, 17 }, { 3, 12, 21, 4, 13, 22, 5, 14, 23 },
{ 3, 12, 21, 4, 13, 22, 5, 14, 23 }, { 1, 4, 7, 10, 13, 16, 19, 22, 25 },
{ 1, 4, 7, 10, 13, 16, 19, 22, 25 }, { 13, 13, 13, 13, 13, 13, 13, 13, 13 } };
public static final int[][] qsamplem = new int[][] { // the positions in the side sample array for each corner
{ 0, 1, 3, 4 }, { 5, 1, 2, 4 }, { 6, 7, 3, 4 }, { 5, 7, 8, 4 } };
public static final float[] sideao = new float[] { 0.5F, 1F, 0.8F, 0.8F, 0.6F, 0.6F, 0.5F, 1F, 0.8F, 0.8F, 0.6F,
0.6F, 1F };
/*
* static { int[][] os = new int[][]{ {0,-1,0}, {0, 1,0}, {0,0,-1}, {0,0, 1}, {-1,0,0}, { 1,0,0}}; for(int s = 0; s
* < 12; s++) { int[] d0 = s < 6 ? new int[]{os[s][0]+1, os[s][1]+1, os[s][2]+1} : new int[]{1, 1, 1}; int[] d1 =
* os[((s&0xE)+3)%6]; int[] d2 = os[((s&0xE)+5)%6]; for(int a = -1; a <= 1; a++) for(int b = -1; b <= 1; b++)
* ssamplem[s][(a+1)*3+b+1] = (d0[1]+d1[1]*a+d2[1]*b)*9+(d0[2]+d1[2]*a+d2[2]*b)*3+(d0[0]+d1[0]*a+d2[0]*b); }
* System.out.println(Arrays.deepToString(ssamplem)); }
*/
public void locate(IBlockAccess a, int x, int y, int z) {
access = a;
pos.set(x, y, z);
computed = 0;
sampled = 0;
}
public void sample(int i) {
if ((sampled & 1 << i) == 0) {
int x = pos.x + (i % 3) - 1;
int y = pos.y + (i / 9) - 1;
int z = pos.z + (i / 3 % 3) - 1;
Block b = access.getBlock(x, y, z);
bSamples[i] = access.getLightBrightnessForSkyBlocks(x, y, z, b.getLightValue(access, x, y, z));
aSamples[i] = b.getAmbientOcclusionLightValue();
sampled |= 1 << i;
}
}
public int[] brightness(int side) {
sideSample(side);
return brightness[side];
}
public float[] ao(int side) {
sideSample(side);
return ao[side];
}
public void sideSample(int side) {
if ((computed & 1 << side) == 0) {
int[] ssample = ssamplem[side];
for (int q = 0; q < 4; q++) {
int[] qsample = qsamplem[q];
if (Minecraft.isAmbientOcclusionEnabled())
interp(side, q, ssample[qsample[0]], ssample[qsample[1]], ssample[qsample[2]], ssample[qsample[3]]);
else interp(side, q, ssample[4], ssample[4], ssample[4], ssample[4]);
}
computed |= 1 << side;
}
}
private void interp(int s, int q, int a, int b, int c, int d) {
sample(a);
sample(b);
sample(c);
sample(d);
ao[s][q] = interpAO(aSamples[a], aSamples[b], aSamples[c], aSamples[d]) * sideao[s];
brightness[s][q] = interpBrightness(bSamples[a], bSamples[b], bSamples[c], bSamples[d]);
}
public static float interpAO(float a, float b, float c, float d) {
return (a + b + c + d) / 4F;
}
public static int interpBrightness(int a, int b, int c, int d) {
if (a == 0) a = d;
if (b == 0) b = d;
if (c == 0) c = d;
return (a + b + c + d) >> 2 & 0xFF00FF;
}
@Override
public boolean load(CCRenderState state) {
if (!state.computeLighting) return false;
state.pipeline.addDependency(state.colourAttrib);
state.pipeline.addDependency(state.lightCoordAttrib);
return true;
}
@Override
public void operate(CCRenderState state) {
LC lc = state.lc;
float[] a = ao(lc.side);
float f = (a[0] * lc.fa + a[1] * lc.fb + a[2] * lc.fc + a[3] * lc.fd);
int[] b = brightness(lc.side);
state.setColour(ColourRGBA.multiplyC(state.colour, f));
state.setBrightness((int) (b[0] * lc.fa + b[1] * lc.fb + b[2] * lc.fc + b[3] * lc.fd) & 0xFF00FF);
}
@Override
public int operationID() {
return operationIndex;
}
}
@@ -0,0 +1,104 @@
package codechicken.lib.lighting;
import codechicken.lib.render.CCRenderState;
import codechicken.lib.vec.Rotation;
import codechicken.lib.vec.Vector3;
public class LightModel implements CCRenderState.IVertexOperation {
public static final int operationIndex = CCRenderState.registerOperation();
public static class Light {
public Vector3 ambient = new Vector3();
public Vector3 diffuse = new Vector3();
public Vector3 position;
public Light(Vector3 pos) {
position = pos.copy().normalize();
}
public Light setDiffuse(Vector3 vec) {
diffuse.set(vec);
return this;
}
public Light setAmbient(Vector3 vec) {
ambient.set(vec);
return this;
}
}
public static LightModel standardLightModel;
static {
standardLightModel = new LightModel().setAmbient(new Vector3(0.4, 0.4, 0.4))
.addLight(new Light(new Vector3(0.2, 1, -0.7)).setDiffuse(new Vector3(0.6, 0.6, 0.6)))
.addLight(new Light(new Vector3(-0.2, 1, 0.7)).setDiffuse(new Vector3(0.6, 0.6, 0.6)));
}
private Vector3 ambient = new Vector3();
private Light[] lights = new Light[8];
private int lightCount;
public LightModel addLight(Light light) {
lights[lightCount++] = light;
return this;
}
public LightModel setAmbient(Vector3 vec) {
ambient.set(vec);
return this;
}
/**
* @param colour The pre-lighting vertex colour. RGBA format
* @param normal The normal at the vertex
* @return The lighting applied colour
*/
public int apply(int colour, Vector3 normal) {
Vector3 n_colour = ambient.copy();
for (int l = 0; l < lightCount; l++) {
Light light = lights[l];
double n_l = light.position.dotProduct(normal);
double f = n_l > 0 ? 1 : 0;
n_colour.x += light.ambient.x + f * light.diffuse.x * n_l;
n_colour.y += light.ambient.y + f * light.diffuse.y * n_l;
n_colour.z += light.ambient.z + f * light.diffuse.z * n_l;
}
if (n_colour.x > 1) n_colour.x = 1;
if (n_colour.y > 1) n_colour.y = 1;
if (n_colour.z > 1) n_colour.z = 1;
n_colour.multiply((colour >>> 24) / 255D, (colour >> 16 & 0xFF) / 255D, (colour >> 8 & 0xFF) / 255D);
return (int) (n_colour.x * 255) << 24 | (int) (n_colour.y * 255) << 16
| (int) (n_colour.z * 255) << 8
| colour & 0xFF;
}
@Override
public boolean load(CCRenderState state) {
if (!state.computeLighting) return false;
state.pipeline.addDependency(CCRenderState.normalAttrib);
state.pipeline.addDependency(CCRenderState.colourAttrib);
return true;
}
@Override
public void operate(CCRenderState state) {
state.setColour(apply(state.colour, state.normal));
}
@Override
public int operationID() {
return operationIndex;
}
public PlanarLightModel reducePlanar() {
int[] colours = new int[6];
for (int i = 0; i < 6; i++) colours[i] = apply(-1, Rotation.axes[i]);
return new PlanarLightModel(colours);
}
}
@@ -0,0 +1,57 @@
package codechicken.lib.lighting;
import net.minecraft.block.Block;
import net.minecraft.world.IBlockAccess;
import codechicken.lib.render.CCRenderState;
import codechicken.lib.vec.BlockCoord;
public class PlanarLightMatrix extends PlanarLightModel {
public static final int operationIndex = CCRenderState.registerOperation();
public static PlanarLightMatrix instance = new PlanarLightMatrix();
public IBlockAccess access;
public BlockCoord pos = new BlockCoord();
private int sampled = 0;
public int[] brightness = new int[6];
public PlanarLightMatrix() {
super(PlanarLightModel.standardLightModel.colours);
}
public PlanarLightMatrix locate(IBlockAccess a, int x, int y, int z) {
access = a;
pos.set(x, y, z);
sampled = 0;
return this;
}
public int brightness(int side) {
if ((sampled & 1 << side) == 0) {
Block b = access.getBlock(pos.x, pos.y, pos.z);
brightness[side] = access
.getLightBrightnessForSkyBlocks(pos.x, pos.y, pos.z, b.getLightValue(access, pos.x, pos.y, pos.z));
sampled |= 1 << side;
}
return brightness[side];
}
@Override
public boolean load(CCRenderState state) {
state.pipeline.addDependency(CCRenderState.sideAttrib);
return true;
}
@Override
public void operate(CCRenderState state) {
super.operate(state);
state.setBrightness(brightness(state.side));
}
@Override
public int operationID() {
return operationIndex;
}
}
@@ -0,0 +1,37 @@
package codechicken.lib.lighting;
import codechicken.lib.colour.ColourRGBA;
import codechicken.lib.render.CCRenderState;
/**
* Faster precomputed version of LightModel that only works for axis planar sides
*/
public class PlanarLightModel implements CCRenderState.IVertexOperation {
public static PlanarLightModel standardLightModel = LightModel.standardLightModel.reducePlanar();
public int[] colours;
public PlanarLightModel(int[] colours) {
this.colours = colours;
}
@Override
public boolean load(CCRenderState state) {
if (!state.computeLighting) return false;
state.pipeline.addDependency(CCRenderState.sideAttrib);
state.pipeline.addDependency(CCRenderState.colourAttrib);
return true;
}
@Override
public void operate(CCRenderState state) {
state.setColour(ColourRGBA.multiply(state.colour, colours[state.side]));
}
@Override
public int operationID() {
return LightModel.operationIndex;
}
}
@@ -0,0 +1,56 @@
package codechicken.lib.lighting;
import net.minecraft.block.Block;
import net.minecraft.world.IBlockAccess;
import codechicken.lib.render.CCRenderState;
import codechicken.lib.vec.BlockCoord;
/**
* Faster precomputed version of LightModel that only works for axis planar sides
*/
public class SimpleBrightnessModel implements CCRenderState.IVertexOperation {
public static final int operationIndex = CCRenderState.registerOperation();
public static SimpleBrightnessModel instance = new SimpleBrightnessModel();
public IBlockAccess access;
public BlockCoord pos = new BlockCoord();
private int sampled = 0;
private final int[] samples = new int[6];
private final BlockCoord c = new BlockCoord();
public void locate(IBlockAccess a, int x, int y, int z) {
access = a;
pos.set(x, y, z);
sampled = 0;
}
public int sample(int side) {
if ((sampled & 1 << side) == 0) {
c.set(pos).offset(side);
Block block = access.getBlock(c.x, c.y, c.z);
samples[side] = access
.getLightBrightnessForSkyBlocks(c.x, c.y, c.z, block.getLightValue(access, c.x, c.y, c.z));
sampled |= 1 << side;
}
return samples[side];
}
@Override
public boolean load(CCRenderState state) {
state.pipeline.addDependency(CCRenderState.sideAttrib);
return true;
}
@Override
public void operate(CCRenderState state) {
state.setBrightness(sample(state.side));
}
@Override
public int operationID() {
return operationIndex;
}
}
@@ -0,0 +1,152 @@
package codechicken.lib.math;
public class MathHelper {
public static final double phi = 1.618033988749894;
public static final double pi = Math.PI;
public static final double todeg = 57.29577951308232;
public static final double torad = 0.017453292519943;
public static final double sqrt2 = 1.414213562373095;
public static double[] SIN_TABLE = new double[65536];
static {
for (int i = 0; i < 65536; ++i) SIN_TABLE[i] = Math.sin(i / 65536D * 2 * Math.PI);
SIN_TABLE[0] = 0;
SIN_TABLE[16384] = 1;
SIN_TABLE[32768] = 0;
SIN_TABLE[49152] = 1;
}
public static double sin(double d) {
return SIN_TABLE[(int) ((float) d * 10430.378F) & 65535];
}
public static double cos(double d) {
return SIN_TABLE[(int) ((float) d * 10430.378F + 16384.0F) & 65535];
}
/**
* @param a The value
* @param b The value to approach
* @param max The maximum step
* @return the closed value to b no less than max from a
*/
public static float approachLinear(float a, float b, float max) {
return (a > b) ? (a - b < max ? b : a - max) : (b - a < max ? b : a + max);
}
/**
* @param a The value
* @param b The value to approach
* @param max The maximum step
* @return the closed value to b no less than max from a
*/
public static double approachLinear(double a, double b, double max) {
return (a > b) ? (a - b < max ? b : a - max) : (b - a < max ? b : a + max);
}
/**
* @param a The first value
* @param b The second value
* @param d The interpolation factor, between 0 and 1
* @return a+(b-a)*d
*/
public static float interpolate(float a, float b, float d) {
return a + (b - a) * d;
}
/**
* @param a The first value
* @param b The second value
* @param d The interpolation factor, between 0 and 1
* @return a+(b-a)*d
*/
public static double interpolate(double a, double b, double d) {
return a + (b - a) * d;
}
/**
* @param a The value
* @param b The value to approach
* @param ratio The ratio to reduce the difference by
* @return a+(b-a)*ratio
*/
public static double approachExp(double a, double b, double ratio) {
return a + (b - a) * ratio;
}
/**
* @param a The value
* @param b The value to approach
* @param ratio The ratio to reduce the difference by
* @param cap The maximum amount to advance by
* @return a+(b-a)*ratio
*/
public static double approachExp(double a, double b, double ratio, double cap) {
double d = (b - a) * ratio;
if (Math.abs(d) > cap) d = Math.signum(d) * cap;
return a + d;
}
/**
* @param a The value
* @param b The value to approach
* @param ratio The ratio to reduce the difference by
* @param c The value to retreat from
* @param kick The difference when a == c
* @return
*/
public static double retreatExp(double a, double b, double c, double ratio, double kick) {
double d = (Math.abs(c - a) + kick) * ratio;
if (d > Math.abs(b - a)) return b;
return a + Math.signum(b - a) * d;
}
/**
*
* @param value The value
* @param min The min value
* @param max The max value
* @return The clipped value between min and max
*/
public static double clip(double value, double min, double max) {
if (value > max) value = max;
if (value < min) value = min;
return value;
}
/**
* @return a <= x <= b
*/
public static boolean between(double a, double x, double b) {
return a <= x && x <= b;
}
public static int approachExpI(int a, int b, double ratio) {
int r = (int) Math.round(approachExp(a, b, ratio));
return r == a ? b : r;
}
public static int retreatExpI(int a, int b, int c, double ratio, int kick) {
int r = (int) Math.round(retreatExp(a, b, c, ratio, kick));
return r == a ? b : r;
}
public static int floor_double(double d) {
return net.minecraft.util.MathHelper.floor_double(d);
}
public static int roundAway(double d) {
return (int) (d < 0 ? Math.floor(d) : Math.ceil(d));
}
public static int compare(int a, int b) {
return a == b ? 0 : a < b ? -1 : 1;
}
public static int compare(double a, double b) {
return a == b ? 0 : a < b ? -1 : 1;
}
}
@@ -0,0 +1,6 @@
package codechicken.lib.packet;
public interface ICustomPacketTile {
public void handleDescriptionPacket(PacketCustom packet);
}
@@ -0,0 +1,553 @@
package codechicken.lib.packet;
import java.util.EnumMap;
import java.util.List;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import net.minecraft.client.Minecraft;
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.network.INetHandler;
import net.minecraft.network.NetHandlerPlayServer;
import net.minecraft.network.Packet;
import net.minecraft.network.play.INetHandlerPlayClient;
import net.minecraft.network.play.INetHandlerPlayServer;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.management.PlayerManager;
import net.minecraft.world.World;
import net.minecraft.world.WorldServer;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidRegistry;
import net.minecraftforge.fluids.FluidStack;
import com.google.common.collect.Maps;
import codechicken.lib.data.MCDataInput;
import codechicken.lib.data.MCDataOutput;
import codechicken.lib.vec.BlockCoord;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.ModContainer;
import cpw.mods.fml.common.network.ByteBufUtils;
import cpw.mods.fml.common.network.FMLEmbeddedChannel;
import cpw.mods.fml.common.network.FMLOutboundHandler;
import cpw.mods.fml.common.network.NetworkHandshakeEstablished;
import cpw.mods.fml.common.network.NetworkRegistry;
import cpw.mods.fml.common.network.handshake.NetworkDispatcher;
import cpw.mods.fml.common.network.internal.FMLProxyPacket;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.AttributeKey;
public final class PacketCustom implements MCDataInput, MCDataOutput {
public static interface ICustomPacketHandler {
}
public interface IClientPacketHandler extends ICustomPacketHandler {
public void handlePacket(PacketCustom packetCustom, Minecraft mc, INetHandlerPlayClient handler);
}
public interface IServerPacketHandler extends ICustomPacketHandler {
public void handlePacket(PacketCustom packetCustom, EntityPlayerMP sender, INetHandlerPlayServer handler);
}
public static AttributeKey<CustomInboundHandler> cclHandler = new AttributeKey<CustomInboundHandler>("ccl:handler");
@ChannelHandler.Sharable
public static class CustomInboundHandler extends SimpleChannelInboundHandler<FMLProxyPacket> {
public EnumMap<Side, CustomHandler> handlers = Maps.newEnumMap(Side.class);
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
super.handlerAdded(ctx);
ctx.channel().attr(cclHandler).set(this);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, FMLProxyPacket msg) throws Exception {
handlers.get(ctx.channel().attr(NetworkRegistry.CHANNEL_SOURCE).get()).handle(
ctx.channel().attr(NetworkRegistry.NET_HANDLER).get(),
ctx.channel().attr(NetworkRegistry.FML_CHANNEL).get(),
new PacketCustom(msg.payload()));
}
}
private static interface CustomHandler {
public void handle(INetHandler handler, String channel, PacketCustom packet) throws Exception;
}
public static class ClientInboundHandler implements CustomHandler {
private IClientPacketHandler handler;
public ClientInboundHandler(ICustomPacketHandler handler) {
this.handler = (IClientPacketHandler) handler;
}
@Override
public void handle(INetHandler netHandler, String channel, PacketCustom packet) throws Exception {
if (netHandler instanceof INetHandlerPlayClient)
handler.handlePacket(packet, Minecraft.getMinecraft(), (INetHandlerPlayClient) netHandler);
else System.err.println("Invalid INetHandler for PacketCustom on channel: " + channel);
}
}
public static class ServerInboundHandler implements CustomHandler {
private IServerPacketHandler handler;
public ServerInboundHandler(ICustomPacketHandler handler) {
this.handler = (IServerPacketHandler) handler;
}
@Override
public void handle(INetHandler netHandler, String channel, PacketCustom packet) throws Exception {
if (netHandler instanceof NetHandlerPlayServer) handler.handlePacket(
packet,
((NetHandlerPlayServer) netHandler).playerEntity,
(INetHandlerPlayServer) netHandler);
else System.err.println("Invalid INetHandler for PacketCustom on channel: " + channel);
}
}
public static interface IHandshakeHandler {
public void handshakeRecieved(NetHandlerPlayServer netHandler);
}
public static class HandshakeInboundHandler extends ChannelInboundHandlerAdapter {
public IHandshakeHandler handler;
public HandshakeInboundHandler(IHandshakeHandler handler) {
this.handler = handler;
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof NetworkHandshakeEstablished) {
INetHandler netHandler = ((NetworkDispatcher) ctx.channel()
.attr(FMLOutboundHandler.FML_MESSAGETARGETARGS).get()).getNetHandler();
if (netHandler instanceof NetHandlerPlayServer)
handler.handshakeRecieved((NetHandlerPlayServer) netHandler);
} else ctx.fireUserEventTriggered(evt);
}
}
public static String channelName(Object channelKey) {
if (channelKey instanceof String) return (String) channelKey;
ModContainer mc = channelKey instanceof ModContainer ? (ModContainer) channelKey
: FMLCommonHandler.instance().findContainerFor(channelKey);
if (mc != null) {
String s = mc.getModId();
if (s.length() > 20) throw new IllegalArgumentException(
"Mod ID (" + s + ") too long for use as channel (20 chars). Use a string identifier");
return s;
}
throw new IllegalArgumentException("Invalid channel: " + channelKey);
}
public static FMLEmbeddedChannel getOrCreateChannel(String channelName, Side side) {
if (!NetworkRegistry.INSTANCE.hasChannel(channelName, side))
NetworkRegistry.INSTANCE.newChannel(channelName, new CustomInboundHandler());
return NetworkRegistry.INSTANCE.getChannel(channelName, side);
}
public static void assignHandler(Object channelKey, ICustomPacketHandler handler) {
String channelName = channelName(channelKey);
Side side = handler instanceof IServerPacketHandler ? Side.SERVER : Side.CLIENT;
FMLEmbeddedChannel channel = getOrCreateChannel(channelName, side);
channel.attr(cclHandler).get().handlers
.put(side, side == Side.SERVER ? new ServerInboundHandler(handler) : new ClientInboundHandler(handler));
}
public static void assignHandshakeHandler(Object channelKey, IHandshakeHandler handler) {
FMLEmbeddedChannel channel = getOrCreateChannel(channelName(channelKey), Side.SERVER);
channel.pipeline().addLast(new HandshakeInboundHandler(handler));
}
private ByteBuf byteBuf;
private String channel;
private int type;
public PacketCustom(ByteBuf payload) {
byteBuf = payload;
type = byteBuf.readUnsignedByte();
if (type > 0x80) decompress();
type &= 0x7F;
}
public PacketCustom(Object channelKey, int type) {
if (type <= 0 || type >= 0x80)
throw new IllegalArgumentException("Packet type: " + type + " is not within required 0 < t < 0x80");
this.channel = channelName(channelKey);
this.type = type;
byteBuf = Unpooled.buffer();
byteBuf.writeByte(type);
}
/**
* Decompresses the remaining ByteBuf (after type has been read) using Snappy
*/
private void decompress() {
Inflater inflater = new Inflater();
try {
int len = byteBuf.readInt();
ByteBuf out = Unpooled.buffer(len);
inflater.setInput(byteBuf.array(), byteBuf.readerIndex(), byteBuf.readableBytes());
inflater.inflate(out.array());
out.writerIndex(len);
byteBuf = out;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
inflater.end();
}
}
/**
* Compresses the payload ByteBuf after the type byte
*/
private void do_compress() {
Deflater deflater = new Deflater();
try {
byteBuf.readerIndex(1);
int len = byteBuf.readableBytes();
deflater.setInput(byteBuf.array(), byteBuf.readerIndex(), len);
deflater.finish();
ByteBuf out = Unpooled.buffer(len + 5);
int clen = deflater.deflate(out.array(), 5, len);
if (clen >= len - 5 || !deflater.finished()) // not worth compressing, gets larger
return;
out.setByte(0, type | 0x80);
out.setInt(1, len);
out.writerIndex(clen + 5);
byteBuf = out;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
byteBuf.readerIndex(0);
deflater.end();
}
}
public boolean incoming() {
return channel == null;
}
public int getType() {
return type & 0x7F;
}
public ByteBuf getByteBuf() {
return byteBuf;
}
public PacketCustom compress() {
if (incoming()) throw new IllegalStateException("Tried to compress an incoming packet");
if ((type & 0x80) != 0) throw new IllegalStateException("Packet already compressed");
type |= 0x80;
return this;
}
public PacketCustom writeBoolean(boolean b) {
byteBuf.writeBoolean(b);
return this;
}
public PacketCustom writeByte(int b) {
byteBuf.writeByte(b);
return this;
}
public PacketCustom writeShort(int s) {
byteBuf.writeShort(s);
return this;
}
public PacketCustom writeInt(int i) {
byteBuf.writeInt(i);
return this;
}
public PacketCustom writeFloat(float f) {
byteBuf.writeFloat(f);
return this;
}
public PacketCustom writeDouble(double d) {
byteBuf.writeDouble(d);
return this;
}
public PacketCustom writeLong(long l) {
byteBuf.writeLong(l);
return this;
}
@Override
public PacketCustom writeChar(char c) {
byteBuf.writeChar(c);
return this;
}
public PacketCustom writeVarInt(int i) {
ByteBufUtils.writeVarInt(byteBuf, i, 5);
return this;
}
public PacketCustom writeVarShort(int s) {
ByteBufUtils.writeVarShort(byteBuf, s);
return this;
}
public PacketCustom writeByteArray(byte[] barray) {
byteBuf.writeBytes(barray);
return this;
}
public PacketCustom writeString(String s) {
ByteBufUtils.writeUTF8String(byteBuf, s);
return this;
}
public PacketCustom writeCoord(int x, int y, int z) {
writeInt(x);
writeInt(y);
writeInt(z);
return this;
}
public PacketCustom writeCoord(BlockCoord coord) {
writeInt(coord.x);
writeInt(coord.y);
writeInt(coord.z);
return this;
}
public PacketCustom writeItemStack(ItemStack stack) {
writeItemStack(stack, false);
return this;
}
public PacketCustom writeItemStack(ItemStack stack, boolean large) {
if (stack == null) {
writeInt(-1);
} else {
writeInt(Item.getIdFromItem(stack.getItem()));
if (large) writeInt(stack.stackSize);
else writeByte(stack.stackSize);
writeShort(stack.getItemDamage());
writeNBTTagCompound(stack.stackTagCompound);
}
return this;
}
public PacketCustom writeNBTTagCompound(NBTTagCompound compound) {
ByteBufUtils.writeTag(byteBuf, compound);
return this;
}
public PacketCustom writeFluidStack(FluidStack fluid) {
if (fluid == null) {
writeShort(-1);
} else {
writeShort(fluid.getFluidID());
writeVarInt(fluid.amount);
writeNBTTagCompound(fluid.tag);
}
return this;
}
public boolean readBoolean() {
return byteBuf.readBoolean();
}
public short readUByte() {
return byteBuf.readUnsignedByte();
}
public int readUShort() {
return byteBuf.readUnsignedShort();
}
public byte readByte() {
return byteBuf.readByte();
}
public short readShort() {
return byteBuf.readShort();
}
public int readInt() {
return byteBuf.readInt();
}
public float readFloat() {
return byteBuf.readFloat();
}
public double readDouble() {
return byteBuf.readDouble();
}
public long readLong() {
return byteBuf.readLong();
}
public char readChar() {
return byteBuf.readChar();
}
@Override
public int readVarShort() {
return ByteBufUtils.readVarShort(byteBuf);
}
@Override
public int readVarInt() {
return ByteBufUtils.readVarInt(byteBuf, 5);
}
public BlockCoord readCoord() {
return new BlockCoord(readInt(), readInt(), readInt());
}
public byte[] readByteArray(int length) {
byte[] barray = new byte[length];
byteBuf.readBytes(barray, 0, length);
return barray;
}
public String readString() {
return ByteBufUtils.readUTF8String(byteBuf);
}
public ItemStack readItemStack() {
return readItemStack(false);
}
public ItemStack readItemStack(boolean large) {
ItemStack item = null;
int itemID = readInt();
if (itemID >= 0) {
int stackSize = large ? readInt() : readByte();
short damage = readShort();
item = new ItemStack(Item.getItemById(itemID), stackSize, damage);
item.stackTagCompound = readNBTTagCompound();
}
return item;
}
public NBTTagCompound readNBTTagCompound() {
return ByteBufUtils.readTag(byteBuf);
}
public FluidStack readFluidStack() {
Fluid fluid = FluidRegistry.getFluid(readShort());
if (fluid == null) fluid = FluidRegistry.WATER;
return new FluidStack(fluid, readVarInt(), readNBTTagCompound());
}
public FMLProxyPacket toPacket() {
if (incoming()) throw new IllegalStateException("Tried to write an incoming packet");
if (byteBuf.readableBytes() > 32000 || (type & 0x80) != 0) do_compress();
// FML packet impl returns the whole of the backing array, copy used portion of array to another ByteBuf
return new FMLProxyPacket(byteBuf.copy(), channel);
}
public void sendToPlayer(EntityPlayer player) {
sendToPlayer(toPacket(), player);
}
public static void sendToPlayer(Packet packet, EntityPlayer player) {
if (player == null) sendToClients(packet);
else((EntityPlayerMP) player).playerNetServerHandler.sendPacket(packet);
}
public void sendToClients() {
sendToClients(toPacket());
}
public static void sendToClients(Packet packet) {
MinecraftServer.getServer().getConfigurationManager().sendPacketToAllPlayers(packet);
}
public void sendPacketToAllAround(double x, double y, double z, double range, int dim) {
sendToAllAround(toPacket(), x, y, z, range, dim);
}
public static void sendToAllAround(Packet packet, double x, double y, double z, double range, int dim) {
MinecraftServer.getServer().getConfigurationManager().sendToAllNear(x, y, z, range, dim, packet);
}
public void sendToDimension(int dim) {
sendToDimension(toPacket(), dim);
}
public static void sendToDimension(Packet packet, int dim) {
MinecraftServer.getServer().getConfigurationManager().sendPacketToAllPlayersInDimension(packet, dim);
}
public void sendToChunk(World world, int chunkX, int chunkZ) {
sendToChunk(toPacket(), world, chunkX, chunkZ);
}
public static void sendToChunk(Packet packet, World world, int chunkX, int chunkZ) {
PlayerManager playerManager = ((WorldServer) world).getPlayerManager();
for (EntityPlayerMP player : (List<EntityPlayerMP>) MinecraftServer.getServer()
.getConfigurationManager().playerEntityList)
if (playerManager.isPlayerWatchingChunk(player, chunkX, chunkZ)) sendToPlayer(packet, player);
/*
* Commented until forge accepts access tranformer request PlayerInstance p = ((WorldServer)
* world).getPlayerManager().getOrCreateChunkWatcher(chunkX, chunkZ, false); if (p != null)
* p.sendToAllPlayersWatchingChunk(packet);
*/
}
public void sendToOps() {
sendToOps(toPacket());
}
public static void sendToOps(Packet packet) {
for (EntityPlayerMP player : (List<EntityPlayerMP>) MinecraftServer.getServer()
.getConfigurationManager().playerEntityList)
if (MinecraftServer.getServer().getConfigurationManager().func_152596_g(player.getGameProfile()))
sendToPlayer(packet, player);
}
@SideOnly(Side.CLIENT)
public void sendToServer() {
sendToServer(toPacket());
}
@SideOnly(Side.CLIENT)
public static void sendToServer(Packet packet) {
Minecraft.getMinecraft().getNetHandler().addToSendQueue(packet);
}
}
@@ -0,0 +1,53 @@
package codechicken.lib.raytracer;
import net.minecraft.entity.Entity;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.util.Vec3;
public class ExtendedMOP extends MovingObjectPosition implements Comparable<ExtendedMOP> {
public Object data;
/**
* The square distance from the start of the raytrace.
*/
public double dist;
public ExtendedMOP(Entity entity, Object data) {
super(entity);
setData(data);
}
public ExtendedMOP(int x, int y, int z, int side, Vec3 hit, Object data) {
super(x, y, z, side, hit);
setData(data);
}
public ExtendedMOP(MovingObjectPosition mop, Object data, double dist) {
super(0, 0, 0, 0, mop.hitVec);
typeOfHit = mop.typeOfHit;
blockX = mop.blockX;
blockY = mop.blockY;
blockZ = mop.blockZ;
sideHit = mop.sideHit;
subHit = mop.subHit;
setData(data);
this.dist = dist;
}
public void setData(Object data) {
if (data instanceof Integer) subHit = ((Integer) data).intValue();
this.data = data;
}
@SuppressWarnings("unchecked")
public static <T> T getData(MovingObjectPosition mop) {
if (mop instanceof ExtendedMOP) return (T) ((ExtendedMOP) mop).data;
return (T) Integer.valueOf(mop.subHit);
}
@Override
public int compareTo(ExtendedMOP o) {
return dist == o.dist ? 0 : dist < o.dist ? -1 : 1;
}
}
@@ -0,0 +1,13 @@
package codechicken.lib.raytracer;
import codechicken.lib.vec.Cuboid6;
public class IndexedCuboid6 extends Cuboid6 {
public Object data;
public IndexedCuboid6(Object data, Cuboid6 cuboid) {
super(cuboid);
this.data = data;
}
}
@@ -0,0 +1,193 @@
package codechicken.lib.raytracer;
import java.util.List;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.util.MovingObjectPosition.MovingObjectType;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;
import codechicken.lib.math.MathHelper;
import codechicken.lib.vec.BlockCoord;
import codechicken.lib.vec.Cuboid6;
import codechicken.lib.vec.Vector3;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
public class RayTracer {
private Vector3 vec = new Vector3();
private Vector3 vec2 = new Vector3();
private Vector3 s_vec = new Vector3();
private double s_dist;
private int s_side;
private IndexedCuboid6 c_cuboid;
private static ThreadLocal<RayTracer> t_inst = new ThreadLocal<RayTracer>();
public static RayTracer instance() {
RayTracer inst = t_inst.get();
if (inst == null) t_inst.set(inst = new RayTracer());
return inst;
}
private void traceSide(int side, Vector3 start, Vector3 end, Cuboid6 cuboid) {
vec.set(start);
Vector3 hit = null;
switch (side) {
case 0:
hit = vec.XZintercept(end, cuboid.min.y);
break;
case 1:
hit = vec.XZintercept(end, cuboid.max.y);
break;
case 2:
hit = vec.XYintercept(end, cuboid.min.z);
break;
case 3:
hit = vec.XYintercept(end, cuboid.max.z);
break;
case 4:
hit = vec.YZintercept(end, cuboid.min.x);
break;
case 5:
hit = vec.YZintercept(end, cuboid.max.x);
break;
}
if (hit == null) return;
switch (side) {
case 0:
case 1:
if (!MathHelper.between(cuboid.min.x, hit.x, cuboid.max.x)
|| !MathHelper.between(cuboid.min.z, hit.z, cuboid.max.z))
return;
break;
case 2:
case 3:
if (!MathHelper.between(cuboid.min.x, hit.x, cuboid.max.x)
|| !MathHelper.between(cuboid.min.y, hit.y, cuboid.max.y))
return;
break;
case 4:
case 5:
if (!MathHelper.between(cuboid.min.y, hit.y, cuboid.max.y)
|| !MathHelper.between(cuboid.min.z, hit.z, cuboid.max.z))
return;
break;
}
double dist = vec2.set(hit).subtract(start).magSquared();
if (dist < s_dist) {
s_side = side;
s_dist = dist;
s_vec.set(vec);
}
}
public MovingObjectPosition rayTraceCuboid(Vector3 start, Vector3 end, Cuboid6 cuboid) {
s_dist = Double.MAX_VALUE;
s_side = -1;
for (int i = 0; i < 6; i++) traceSide(i, start, end, cuboid);
if (s_side < 0) return null;
MovingObjectPosition mop = new MovingObjectPosition(0, 0, 0, s_side, s_vec.toVec3D());
mop.typeOfHit = null;
return mop;
}
public MovingObjectPosition rayTraceCuboids(Vector3 start, Vector3 end, List<IndexedCuboid6> cuboids) {
double c_dist = Double.MAX_VALUE;
MovingObjectPosition c_hit = null;
for (IndexedCuboid6 cuboid : cuboids) {
MovingObjectPosition mop = rayTraceCuboid(start, end, cuboid);
if (mop != null && s_dist < c_dist) {
mop = new ExtendedMOP(mop, cuboid.data, s_dist);
c_dist = s_dist;
c_hit = mop;
c_cuboid = cuboid;
}
}
return c_hit;
}
public MovingObjectPosition rayTraceCuboids(Vector3 start, Vector3 end, List<IndexedCuboid6> cuboids,
BlockCoord pos, Block block) {
MovingObjectPosition mop = rayTraceCuboids(start, end, cuboids);
if (mop != null) {
mop.typeOfHit = MovingObjectType.BLOCK;
mop.blockX = pos.x;
mop.blockY = pos.y;
mop.blockZ = pos.z;
if (block != null) c_cuboid.add(new Vector3(-pos.x, -pos.y, -pos.z)).setBlockBounds(block);
}
return mop;
}
public static MovingObjectPosition retraceBlock(World world, EntityPlayer player, int x, int y, int z) {
Block block = world.getBlock(x, y, z);
Vec3 headVec = getCorrectedHeadVec(player);
Vec3 lookVec = player.getLook(1.0F);
double reach = getBlockReachDistance(player);
Vec3 endVec = headVec.addVector(lookVec.xCoord * reach, lookVec.yCoord * reach, lookVec.zCoord * reach);
return block.collisionRayTrace(world, x, y, z, headVec, endVec);
}
private static double getBlockReachDistance_server(EntityPlayerMP player) {
return player.theItemInWorldManager.getBlockReachDistance();
}
@SideOnly(Side.CLIENT)
private static double getBlockReachDistance_client() {
return Minecraft.getMinecraft().playerController.getBlockReachDistance();
}
public static MovingObjectPosition reTrace(World world, EntityPlayer player) {
return reTrace(world, player, getBlockReachDistance(player));
}
public static MovingObjectPosition reTrace(World world, EntityPlayer player, double reach) {
Vec3 headVec = getCorrectedHeadVec(player);
Vec3 lookVec = player.getLook(1);
Vec3 endVec = headVec.addVector(lookVec.xCoord * reach, lookVec.yCoord * reach, lookVec.zCoord * reach);
return world.func_147447_a(headVec, endVec, true, false, true);
}
public static Vec3 getCorrectedHeadVec(EntityPlayer player) {
Vec3 v = Vec3.createVectorHelper(player.posX, player.posY, player.posZ);
if (player.worldObj.isRemote) {
v.yCoord += player.getEyeHeight() - player.getDefaultEyeHeight(); // compatibility with eye height changing
// mods
} else {
v.yCoord += player.getEyeHeight();
if (player instanceof EntityPlayerMP && player.isSneaking()) v.yCoord -= 0.08;
}
return v;
}
public static Vec3 getStartVec(EntityPlayer player) {
return getCorrectedHeadVec(player);
}
public static double getBlockReachDistance(EntityPlayer player) {
return player.worldObj.isRemote ? getBlockReachDistance_client()
: player instanceof EntityPlayerMP ? getBlockReachDistance_server((EntityPlayerMP) player) : 5D;
}
public static Vec3 getEndVec(EntityPlayer player) {
Vec3 headVec = getCorrectedHeadVec(player);
Vec3 lookVec = player.getLook(1.0F);
double reach = getBlockReachDistance(player);
return headVec.addVector(lookVec.xCoord * reach, lookVec.yCoord * reach, lookVec.zCoord * reach);
}
}
@@ -0,0 +1,204 @@
package codechicken.lib.render;
import codechicken.lib.lighting.LC;
import codechicken.lib.render.CCRenderState.VertexAttribute;
import codechicken.lib.vec.Cuboid6;
public class BlockRenderer {
public static class BlockFace implements CCRenderState.IVertexSource {
public Vertex5[] verts = new Vertex5[] { new Vertex5(), new Vertex5(), new Vertex5(), new Vertex5() };
public LC[] lightCoords = new LC[] { new LC(), new LC(), new LC(), new LC() };
public boolean lcComputed = false;
public int side;
@Override
public Vertex5[] getVertices() {
return verts;
}
@Override
public <T> T getAttributes(CCRenderState.VertexAttribute<T> attr) {
return attr == CCRenderState.lightCoordAttrib && lcComputed ? (T) lightCoords : null;
}
@Override
public boolean hasAttribute(CCRenderState.VertexAttribute<?> attr) {
return attr == CCRenderState.sideAttrib || attr == CCRenderState.lightCoordAttrib && lcComputed;
}
@Override
public void prepareVertex(CCRenderState state) {
state.side = side;
}
public BlockFace computeLightCoords() {
if (!lcComputed) {
for (int i = 0; i < 4; i++) lightCoords[i].compute(verts[i].vec, side);
lcComputed = true;
}
return this;
}
public BlockFace loadCuboidFace(Cuboid6 c, int side) {
double x1 = c.min.x;
double x2 = c.max.x;
double y1 = c.min.y;
double y2 = c.max.y;
double z1 = c.min.z;
double z2 = c.max.z;
double u1;
double u2;
double v1;
double v2;
this.side = side;
lcComputed = false;
switch (side) {
case 0:
u1 = x1;
v1 = z1;
u2 = x2;
v2 = z2;
verts[0].set(x1, y1, z2, u1, v2, 0);
verts[1].set(x1, y1, z1, u1, v1, 0);
verts[2].set(x2, y1, z1, u2, v1, 0);
verts[3].set(x2, y1, z2, u2, v2, 0);
break;
case 1:
u1 = x1;
v1 = z1;
u2 = x2;
v2 = z2;
verts[0].set(x2, y2, z2, u2, v2, 1);
verts[1].set(x2, y2, z1, u2, v1, 1);
verts[2].set(x1, y2, z1, u1, v1, 1);
verts[3].set(x1, y2, z2, u1, v2, 1);
break;
case 2:
u1 = 1 - x1;
v1 = 1 - y2;
u2 = 1 - x2;
v2 = 1 - y1;
verts[0].set(x1, y1, z1, u1, v2, 2);
verts[1].set(x1, y2, z1, u1, v1, 2);
verts[2].set(x2, y2, z1, u2, v1, 2);
verts[3].set(x2, y1, z1, u2, v2, 2);
break;
case 3:
u1 = x1;
v1 = 1 - y2;
u2 = x2;
v2 = 1 - y1;
verts[0].set(x2, y1, z2, u2, v2, 3);
verts[1].set(x2, y2, z2, u2, v1, 3);
verts[2].set(x1, y2, z2, u1, v1, 3);
verts[3].set(x1, y1, z2, u1, v2, 3);
break;
case 4:
u1 = z1;
v1 = 1 - y2;
u2 = z2;
v2 = 1 - y1;
verts[0].set(x1, y1, z2, u2, v2, 4);
verts[1].set(x1, y2, z2, u2, v1, 4);
verts[2].set(x1, y2, z1, u1, v1, 4);
verts[3].set(x1, y1, z1, u1, v2, 4);
break;
case 5:
u1 = 1 - z1;
v1 = 1 - y2;
u2 = 1 - z2;
v2 = 1 - y1;
verts[0].set(x2, y1, z1, u1, v2, 5);
verts[1].set(x2, y2, z1, u1, v1, 5);
verts[2].set(x2, y2, z2, u2, v1, 5);
verts[3].set(x2, y1, z2, u2, v2, 5);
}
return this;
}
}
public static class FullBlock implements CCRenderState.IVertexSource {
public Vertex5[] verts = CCModel.quadModel(24).generateBlock(0, Cuboid6.full).verts;
public LC[] lightCoords = new LC[24];
public FullBlock() {
for (int i = 0; i < 24; i++) lightCoords[i] = new LC().compute(verts[i].vec, i / 4);
}
@Override
public Vertex5[] getVertices() {
return verts;
}
@Override
public <T> T getAttributes(VertexAttribute<T> attr) {
return attr == CCRenderState.lightCoordAttrib ? (T) lightCoords : null;
}
@Override
public boolean hasAttribute(VertexAttribute<?> attr) {
return attr == CCRenderState.sideAttrib || attr == CCRenderState.lightCoordAttrib;
}
@Override
public void prepareVertex(CCRenderState state) {
state.side = state.vertexIndex >> 2;
}
}
public static FullBlock fullBlock = new FullBlock();
// public static void renderFullBlock(int sideMask) {
public static void renderFullBlock(CCRenderState state, int sideMask) {
state.setModel(fullBlock);
renderFaces(state, sideMask);
}
public static void renderFullBlock(int sideMask) {
renderFullBlock(CCRenderState.instance(), sideMask);
}
/**
* Renders faces of a block-like model based on a sideMask. Eg for side 2, verts 8-11 will be rendered
*
* @param sideMask A mask of faces not to render
*/
// public static void renderFaces(int sideMask) {
public static void renderFaces(CCRenderState state, int sideMask) {
if (sideMask == 0x3F) return;
for (int s = 0; s < 6; s++) if ((sideMask & 1 << s) == 0) {
state.setVertexRange(s * 4, (s + 1) * 4);
state.render();
}
}
public static void renderFaces(int sideMask) {
renderFaces(CCRenderState.instance(), sideMask);
}
private static final BlockFace face = new BlockFace();
/**
* Renders faces of a cuboid with texture coordinates mapped to match a standard minecraft block
*
* @param bounds The bounding cuboid to render
* @param sideMask A mask of faces not to render
*/
public static void renderCuboid(CCRenderState state, Cuboid6 bounds, int sideMask) {
if (sideMask == 0x3F) return;
state.setModel(face);
for (int s = 0; s < 6; s++) if ((sideMask & 1 << s) == 0) {
face.loadCuboidFace(bounds, s);
state.render();
}
}
public static void renderCuboid(Cuboid6 bounds, int sideMask) {
renderCuboid(CCRenderState.instance(), bounds, sideMask);
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,88 @@
package codechicken.lib.render;
import static codechicken.lib.math.MathHelper.phi;
import codechicken.lib.vec.Matrix4;
import codechicken.lib.vec.Quat;
import codechicken.lib.vec.Rotation;
import codechicken.lib.vec.Scale;
import codechicken.lib.vec.Vector3;
public class CCModelLibrary {
public static CCModel icosahedron4;
public static CCModel icosahedron7;
private static int i;
static {
generateIcosahedron();
}
private static void generateIcosahedron() {
Vector3[] verts = new Vector3[12];
verts[0] = new Vector3(-1, phi, 0);
verts[1] = new Vector3(1, phi, 0);
verts[2] = new Vector3(1, -phi, 0);
verts[3] = new Vector3(-1, -phi, 0);
verts[4] = new Vector3(0, -1, phi);
verts[5] = new Vector3(0, 1, phi);
verts[6] = new Vector3(0, 1, -phi);
verts[7] = new Vector3(0, -1, -phi);
verts[8] = new Vector3(phi, 0, -1);
verts[9] = new Vector3(phi, 0, 1);
verts[10] = new Vector3(-phi, 0, 1);
verts[11] = new Vector3(-phi, 0, -1);
Quat quat = Quat.aroundAxis(0, 0, 1, Math.atan(1 / phi));
for (Vector3 vec : verts) quat.rotate(vec);
icosahedron4 = CCModel.newModel(4, 60);
icosahedron7 = CCModel.newModel(7, 80);
i = 0;
// top
addIcosahedronTriangle(verts[1], 0.5, 0, verts[0], 0, 0.25, verts[5], 1, 0.25);
addIcosahedronTriangle(verts[1], 0.5, 0, verts[5], 0, 0.25, verts[9], 1, 0.25);
addIcosahedronTriangle(verts[1], 0.5, 0, verts[9], 0, 0.25, verts[8], 1, 0.25);
addIcosahedronTriangle(verts[1], 0.5, 0, verts[8], 0, 0.25, verts[6], 1, 0.25);
addIcosahedronTriangle(verts[1], 0.5, 0, verts[6], 0, 0.25, verts[0], 1, 0.25);
// centre 1vert top
addIcosahedronTriangle(verts[0], 0.5, 0.25, verts[11], 0, 0.75, verts[10], 1, 0.75);
addIcosahedronTriangle(verts[5], 0.5, 0.25, verts[10], 0, 0.75, verts[4], 1, 0.75);
addIcosahedronTriangle(verts[9], 0.5, 0.25, verts[4], 0, 0.75, verts[2], 1, 0.75);
addIcosahedronTriangle(verts[8], 0.5, 0.25, verts[2], 0, 0.75, verts[7], 1, 0.75);
addIcosahedronTriangle(verts[6], 0.5, 0.25, verts[7], 0, 0.75, verts[11], 1, 0.75);
// centre 1vert bottom
addIcosahedronTriangle(verts[2], 0.5, 0.75, verts[8], 0, 0.25, verts[9], 1, 0.25);
addIcosahedronTriangle(verts[7], 0.5, 0.75, verts[6], 0, 0.25, verts[8], 1, 0.25);
addIcosahedronTriangle(verts[11], 0.5, 0.75, verts[0], 0, 0.25, verts[6], 1, 0.25);
addIcosahedronTriangle(verts[10], 0.5, 0.75, verts[5], 0, 0.25, verts[0], 1, 0.25);
addIcosahedronTriangle(verts[4], 0.5, 0.75, verts[9], 0, 0.25, verts[5], 1, 0.25);
// bottom
addIcosahedronTriangle(verts[3], 0.5, 1, verts[2], 0, 0.75, verts[4], 1, 0.75);
addIcosahedronTriangle(verts[3], 0.5, 1, verts[7], 0, 0.75, verts[2], 1, 0.75);
addIcosahedronTriangle(verts[3], 0.5, 1, verts[11], 0, 0.75, verts[7], 1, 0.75);
addIcosahedronTriangle(verts[3], 0.5, 1, verts[10], 0, 0.75, verts[11], 1, 0.75);
addIcosahedronTriangle(verts[3], 0.5, 1, verts[4], 0, 0.75, verts[10], 1, 0.75);
icosahedron4.computeNormals().smoothNormals();
icosahedron7.computeNormals().smoothNormals();
}
private static void addIcosahedronTriangle(Vector3 vec1, double u1, double v1, Vector3 vec2, double u2, double v2,
Vector3 vec3, double u3, double v3) {
icosahedron4.verts[i * 3] = icosahedron7.verts[i * 4] = new Vertex5(vec1, u1, v1);
icosahedron4.verts[i * 3 + 1] = icosahedron7.verts[i * 4 + 1] = new Vertex5(vec2, u2, v2);
icosahedron4.verts[i * 3
+ 2] = icosahedron7.verts[i * 4 + 2] = icosahedron7.verts[i * 4 + 3] = new Vertex5(vec3, u3, v3);
i++;
}
public static Matrix4 getRenderMatrix(Vector3 position, Rotation rotation, double scale) {
return new Matrix4().translate(position).apply(new Scale(scale)).apply(rotation);
}
}
@@ -0,0 +1,142 @@
package codechicken.lib.render;
import java.util.ArrayList;
import java.util.Collections;
import codechicken.lib.render.CCRenderState.IVertexOperation;
import codechicken.lib.render.CCRenderState.VertexAttribute;
@SuppressWarnings("ForLoopReplaceableByForEach")
public class CCRenderPipeline {
private final CCRenderState renderState;
private final PipelineBuilder builder;
public CCRenderPipeline(CCRenderState renderState) {
this.renderState = renderState;
builder = new PipelineBuilder(renderState);
}
public CCRenderPipeline() {
this(CCRenderState.instance());
}
public class PipelineBuilder {
private final CCRenderState renderState;
public PipelineBuilder(CCRenderState renderState) {
this.renderState = renderState;
}
public PipelineBuilder() {
this(CCRenderState.instance());
}
public PipelineBuilder add(IVertexOperation op) {
ops.add(op);
return this;
}
public PipelineBuilder add(IVertexOperation... ops) {
Collections.addAll(CCRenderPipeline.this.ops, ops);
return this;
}
public void build() {
rebuild();
}
public void render() {
rebuild();
renderState.render();
}
}
private class PipelineNode {
public ArrayList<PipelineNode> deps = new ArrayList<>();
public IVertexOperation op;
public void add() {
if (op == null) return;
for (int i = 0; i < deps.size(); i++) deps.get(i).add();
deps.clear();
sorted.add(op);
op = null;
}
}
private final ArrayList<VertexAttribute> attribs = new ArrayList<>();
private final ArrayList<IVertexOperation> ops = new ArrayList<>();
private final ArrayList<PipelineNode> nodes = new ArrayList<>();
private final ArrayList<IVertexOperation> sorted = new ArrayList<>();
private PipelineNode loading;
public void setPipeline(IVertexOperation... ops) {
this.ops.clear();
for (int i = 0; i < ops.length; i++) this.ops.add(ops[i]);
rebuild();
}
public void reset() {
ops.clear();
unbuild();
}
private void unbuild() {
for (int i = 0; i < attribs.size(); i++) attribs.get(i).active = false;
attribs.clear();
sorted.clear();
}
public void rebuild() {
if (ops.isEmpty() || this.renderState.model == null) return;
// ensure enough nodes for all ops
while (nodes.size() < this.renderState.operationCount()) nodes.add(new PipelineNode());
unbuild();
if (this.renderState.useNormals) addAttribute(this.renderState.normalAttrib);
if (this.renderState.useColour) addAttribute(this.renderState.colourAttrib);
if (this.renderState.computeLighting) addAttribute(this.renderState.lightingAttrib);
for (int i = 0; i < ops.size(); i++) {
IVertexOperation op = ops.get(i);
loading = nodes.get(op.operationID());
boolean loaded = op.load(renderState);
if (loaded) loading.op = op;
if (op instanceof VertexAttribute) if (loaded) attribs.add((VertexAttribute) op);
else((VertexAttribute) op).active = false;
}
for (int i = 0; i < nodes.size(); i++) nodes.get(i).add();
}
public void addRequirement(int opRef) {
loading.deps.add(nodes.get(opRef));
}
public void addDependency(VertexAttribute attrib) {
loading.deps.add(nodes.get(attrib.operationID()));
addAttribute(attrib);
}
public void addAttribute(VertexAttribute attrib) {
if (!attrib.active) {
ops.add(attrib);
attrib.active = true;
}
}
public void operate() {
for (int i = 0; i < sorted.size(); i++) sorted.get(i).operate(renderState);
}
public PipelineBuilder builder() {
ops.clear();
return builder;
}
}
@@ -0,0 +1,552 @@
package codechicken.lib.render;
import java.util.ArrayList;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.IBlockAccess;
import codechicken.lib.colour.ColourRGBA;
import codechicken.lib.lighting.LC;
import codechicken.lib.lighting.LightMatrix;
import codechicken.lib.util.Copyable;
import codechicken.lib.vec.Rotation;
import codechicken.lib.vec.Transformation;
import codechicken.lib.vec.Vector3;
/**
* The core of the CodeChickenLib render system. Where possible assign a local var of CCRenderState to avoid millions of
* calls to instance(); Uses a ThreadLocal system to assign each thread their own CCRenderState so we can use it in
* Multithreaded chunk batching.
* <p>
* Backported from CCL - 1.16.x
*/
public class CCRenderState {
public final CCRenderPipeline pipeline;
private static final ThreadLocal<CCRenderState> instances = ThreadLocal.withInitial(CCRenderState::new);
private CCRenderState() {
pipeline = new CCRenderPipeline(this);
}
public static CCRenderState instance() {
return instances.get();
}
private static int nextOperationIndex;
public static int registerOperation() {
return nextOperationIndex++;
}
public static int operationCount() {
return nextOperationIndex;
}
/**
* Represents an operation to be run for each vertex that operates on and modifies the current state
*/
public interface IVertexOperation {
/**
* Load any required references and add dependencies to the pipeline based on the current model (may be null)
* Return false if this operation is redundant in the pipeline with the given model
*/
default boolean load() {
// Existing code will will override this method so it shouldn't infinite loop
// Default it only for new state aware code
return load(CCRenderState.instance());
}
default boolean load(CCRenderState state) {
// New code will override this and not inplement load() so we shouldn't infinite loop
return load();
}
/**
* Perform the operation on the current render state
*/
default void operate() {
operate(CCRenderState.instance());
}
default void operate(CCRenderState state) {
operate();
}
/**
* Get the unique id representing this type of operation. Duplicate operation IDs within the pipeline may have
* unexpected results. ID shoulld be obtained from CCRenderState.registerOperation() and stored in a static
* variable
*/
int operationID();
}
private static ArrayList<VertexAttribute<?>> vertexAttributes = new ArrayList<>();
private static int registerVertexAttribute(VertexAttribute<?> attr) {
vertexAttributes.add(attr);
return vertexAttributes.size() - 1;
}
public static VertexAttribute<?> getAttribute(int index) {
return vertexAttributes.get(index);
}
/**
* Management class for a vertex attrute such as colour, normal etc This class should handle the loading of the
* attrute from an array provided by IVertexSource.getAttributes or the computation of this attrute from others
*
* @param <T> The array type for this attrute eg. int[], Vector3[]
*/
public abstract static class VertexAttribute<T> implements IVertexOperation {
public final int attributeIndex = registerVertexAttribute(this);
private final int operationIndex = registerOperation();
/**
* Set to true when the attrute is part of the pipeline. Should only be managed by CCRenderState when
* constructing the pipeline
*/
public boolean active = false;
/**
* Construct a new array for storage of vertex attrutes in a model
*/
public abstract T newArray(int length);
@Override
public int operationID() {
return operationIndex;
}
}
public static void arrayCopy(Object src, int srcPos, Object dst, int destPos, int length) {
System.arraycopy(src, srcPos, dst, destPos, length);
if (dst instanceof Copyable[]) {
Object[] oa = (Object[]) dst;
Copyable<Object>[] c = (Copyable[]) dst;
for (int i = destPos; i < destPos + length; i++) if (c[i] != null) oa[i] = c[i].copy();
}
}
public static <T> T copyOf(VertexAttribute<T> attr, T src, int length) {
T dst = attr.newArray(length);
arrayCopy(src, 0, dst, 0, ((Object[]) src).length);
return dst;
}
public interface IVertexSource {
Vertex5[] getVertices();
/**
* Gets an array of vertex attrutes
*
* @param attr The vertex attrute to get
* @param <T> The attrute array type
* @return An array, or null if not computed
*/
<T> T getAttributes(VertexAttribute<T> attr);
/**
* @return True if the specified attrute is provided by this model, either by returning an array from
* getAttributes or by setting the state in prepareVertex
*/
boolean hasAttribute(VertexAttribute<?> attr);
/**
* Callback to set CCRenderState for a vertex before the pipeline runs
*/
void prepareVertex(CCRenderState state);
default void prepareVertex() {
prepareVertex(CCRenderState.instance());
}
}
public static VertexAttribute<Vector3[]> normalAttrib = new VertexAttribute<>() {
private Vector3[] normalRef;
@Override
public Vector3[] newArray(int length) {
return new Vector3[length];
}
@Override
public boolean load(CCRenderState state) {
normalRef = state.model.getAttributes(this);
if (state.model.hasAttribute(this)) return normalRef != null;
if (state.model.hasAttribute(sideAttrib)) {
state.pipeline.addDependency(sideAttrib);
return true;
}
throw new IllegalStateException(
"Normals requested but neither normal or side attrutes are provided by the model");
}
@Override
public void operate(CCRenderState state) {
if (normalRef != null) state.setNormal(normalRef[state.vertexIndex]);
else state.setNormal(Rotation.axes[state.side]);
}
};
public static VertexAttribute<int[]> colourAttrib = new VertexAttribute<>() {
private int[] colourRef;
@Override
public int[] newArray(int length) {
return new int[length];
}
@Override
public boolean load(CCRenderState state) {
colourRef = state.model.getAttributes(this);
return colourRef != null || !state.model.hasAttribute(this);
}
@Override
public void operate(CCRenderState state) {
if (colourRef != null) state.setColour(ColourRGBA.multiply(state.baseColour, colourRef[state.vertexIndex]));
else state.setColour(state.baseColour);
}
};
public static VertexAttribute<int[]> lightingAttrib = new VertexAttribute<>() {
private int[] colourRef;
@Override
public int[] newArray(int length) {
return new int[length];
}
@Override
public boolean load(CCRenderState state) {
if (!state.computeLighting || !state.useColour || !state.model.hasAttribute(this)) return false;
colourRef = state.model.getAttributes(this);
if (colourRef != null) {
state.pipeline.addDependency(colourAttrib);
return true;
}
return false;
}
@Override
public void operate(CCRenderState state) {
state.setColour(ColourRGBA.multiply(state.colour, colourRef[state.vertexIndex]));
}
};
public static VertexAttribute<int[]> sideAttrib = new VertexAttribute<>() {
private int[] sideRef;
@Override
public int[] newArray(int length) {
return new int[length];
}
@Override
public boolean load(CCRenderState state) {
sideRef = state.model.getAttributes(this);
if (state.model.hasAttribute(this)) return sideRef != null;
state.pipeline.addDependency(normalAttrib);
return true;
}
@Override
public void operate(CCRenderState state) {
if (sideRef != null) state.side = sideRef[state.vertexIndex];
else state.side = CCModel.findSide(state.normal);
}
};
/**
* Uses the position of the lightmatrix to compute LC if not provided
*/
public static VertexAttribute<LC[]> lightCoordAttrib = new VertexAttribute<>() {
private LC[] lcRef;
private final Vector3 vec = new Vector3(); // for computation
private final Vector3 pos = new Vector3();
@Override
public LC[] newArray(int length) {
return new LC[length];
}
@Override
public boolean load(CCRenderState state) {
lcRef = state.model.getAttributes(this);
if (state.model.hasAttribute(this)) return lcRef != null;
pos.set(state.lightMatrix.pos.x, state.lightMatrix.pos.y, state.lightMatrix.pos.z);
state.pipeline.addDependency(sideAttrib);
state.pipeline.addRequirement(Transformation.operationIndex);
return true;
}
@Override
public void operate(CCRenderState state) {
if (lcRef != null) state.lc.set(lcRef[state.vertexIndex]);
else state.lc.compute(vec.set(state.vert.vec).sub(pos), state.side);
}
};
// pipeline state
public IVertexSource model;
public int firstVertexIndex;
public int lastVertexIndex;
public int vertexIndex;
// context
public int baseColour;
public int alphaOverride;
public boolean useNormals;
public boolean computeLighting;
public boolean useColour;
public LightMatrix lightMatrix = new LightMatrix();
// vertex outputs
public Vertex5 vert = new Vertex5();
public boolean hasNormal;
public Vector3 normal = new Vector3();
public boolean hasColour;
public int colour;
public boolean hasBrightness;
public int brightness;
// attrute storage
public int side;
public LC lc = new LC();
public void reset() {
model = null;
pipeline.reset();
useNormals = hasNormal = hasBrightness = hasColour = false;
useColour = computeLighting = true;
baseColour = alphaOverride = -1;
}
public void setPipeline(IVertexOperation... ops) {
pipeline.setPipeline(ops);
}
@Deprecated
public static void setPipelineStatic(IVertexOperation... ops) {
instance().setPipeline(ops);
}
public void setPipeline(IVertexSource model, int start, int end, IVertexOperation... ops) {
pipeline.reset();
setModel(model, start, end);
pipeline.setPipeline(ops);
}
@Deprecated
public static void setPipelineStatic(IVertexSource model, int start, int end, IVertexOperation... ops) {
instance().setPipeline(model, start, end, ops);
}
public void bindModel(IVertexSource model) {
if (this.model != model) {
this.model = model;
pipeline.rebuild();
}
}
@Deprecated
public static void bindModelStatic(IVertexSource model) {
instance().bindModel(model);
}
public void setModel(IVertexSource source) {
setModel(source, 0, source.getVertices().length);
}
@Deprecated
public static void setModelStatic(IVertexSource source) {
instance().setModel(source);
}
public void setModel(IVertexSource source, int start, int end) {
bindModel(source);
setVertexRange(start, end);
}
@Deprecated
public static void setModelStatic(IVertexSource source, int start, int end) {
instance().setModel(source, start, end);
}
public void setVertexRange(int start, int end) {
firstVertexIndex = start;
lastVertexIndex = end;
}
@Deprecated
public static void setVertexRangeStatic(int start, int end) {
instance().setVertexRange(start, end);
}
public void render(IVertexOperation... ops) {
setPipeline(ops);
render();
}
@Deprecated
public static void renderStatic(IVertexOperation... ops) {
instance().render(ops);
}
public void render() {
Vertex5[] verts = model.getVertices();
for (vertexIndex = firstVertexIndex; vertexIndex < lastVertexIndex; vertexIndex++) {
model.prepareVertex(this);
vert.set(verts[vertexIndex]);
runPipeline();
writeVert();
}
}
@Deprecated
public static void renderStatic() {
instance().render();
}
public void runPipeline() {
pipeline.operate();
}
@Deprecated
public static void runPipelineStatic() {
instance().runPipeline();
}
public void writeVert() {
if (hasNormal) Tessellator.instance.setNormal((float) normal.x, (float) normal.y, (float) normal.z);
if (hasColour) Tessellator.instance.setColorRGBA(
colour >>> 24,
colour >> 16 & 0xFF,
colour >> 8 & 0xFF,
alphaOverride >= 0 ? alphaOverride : colour & 0xFF);
if (hasBrightness) Tessellator.instance.setBrightness(brightness);
Tessellator.instance.addVertexWithUV(vert.vec.x, vert.vec.y, vert.vec.z, vert.uv.u, vert.uv.v);
}
@Deprecated
public static void writeVertStatic() {
instance().writeVert();
}
public void setNormal(double x, double y, double z) {
hasNormal = true;
normal.set(x, y, z);
}
@Deprecated
public static void setNormalStatic(double x, double y, double z) {
instance().setNormal(x, y, z);
}
public void setNormal(Vector3 n) {
hasNormal = true;
normal.set(n);
}
@Deprecated
public static void setNormalStatic(Vector3 n) {
instance().setNormal(n);
}
public void setColour(int c) {
hasColour = true;
colour = c;
}
@Deprecated
public static void setColourStatic(int c) {
instance().setColour(c);
}
public void setBrightness(int b) {
hasBrightness = true;
brightness = b;
}
@Deprecated
public static void setBrightnessStatic(int b) {
instance().setBrightness(b);
}
public void setBrightness(IBlockAccess world, int x, int y, int z) {
setBrightness(world.getBlock(x, y, z).getMixedBrightnessForBlock(world, x, y, z));
}
@Deprecated
public static void setBrightnessStatic(IBlockAccess world, int x, int y, int z) {
instance().setBrightness(world, x, y, z);
}
public void pullLightmap() {
setBrightness((int) OpenGlHelper.lastBrightnessY << 16 | (int) OpenGlHelper.lastBrightnessX);
}
public void pushLightmap() {
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, brightness & 0xFFFF, brightness >>> 16);
}
/**
* Compact helper for setting dynamic rendering context. Uses normals and doesn't compute lighting
*/
public void setDynamic() {
useNormals = true;
computeLighting = false;
}
public static void changeTexture(String texture) {
changeTexture(new ResourceLocation(texture));
}
public static void changeTexture(ResourceLocation texture) {
Minecraft.getMinecraft().renderEngine.bindTexture(texture);
}
public void startDrawing() {
startDrawing(7);
}
@Deprecated
public static void startDrawingStatic() {
instance().startDrawing();
}
public void startDrawing(int mode) {
Tessellator.instance.startDrawing(mode);
if (hasColour) Tessellator.instance.setColorRGBA(
colour >>> 24,
colour >> 16 & 0xFF,
colour >> 8 & 0xFF,
alphaOverride >= 0 ? alphaOverride : colour & 0xFF);
if (hasBrightness) Tessellator.instance.setBrightness(brightness);
}
@Deprecated
public static void startDrawingStatic(int mode) {
instance().startDrawing(mode);
}
public void draw() {
Tessellator.instance.draw();
}
}
@@ -0,0 +1,43 @@
package codechicken.lib.render;
import codechicken.lib.colour.ColourRGBA;
public class ColourMultiplier implements CCRenderState.IVertexOperation {
private static final ThreadLocal<ColourMultiplier> instances = ThreadLocal
.withInitial(() -> new ColourMultiplier(-1));
public static ColourMultiplier instance(int colour) {
ColourMultiplier instance = instances.get();
instance.colour = colour;
return instance;
}
public static final int operationIndex = CCRenderState.registerOperation();
public int colour;
public ColourMultiplier(int colour) {
this.colour = colour;
}
@Override
public boolean load(CCRenderState state) {
if (colour == -1) {
state.setColour(-1);
return false;
}
state.pipeline.addDependency(state.colourAttrib);
return true;
}
@Override
public void operate(CCRenderState state) {
state.setColour(ColourRGBA.multiply(state.colour, colour));
}
@Override
public int operationID() {
return operationIndex;
}
}
@@ -0,0 +1,139 @@
package codechicken.lib.render;
import net.minecraft.client.particle.EffectRenderer;
import net.minecraft.client.particle.EntityFX;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.util.IIcon;
import net.minecraft.world.World;
import codechicken.lib.vec.Cuboid6;
import codechicken.lib.vec.Vector3;
public class EntityDigIconFX extends EntityFX {
public EntityDigIconFX(World world, double x, double y, double z, double dx, double dy, double dz, IIcon icon) {
super(world, x, y, z, dx, dy, dz);
particleIcon = icon;
particleGravity = 1;
particleRed = particleGreen = particleBlue = 0.6F;
particleScale /= 2.0F;
}
@Override
public int getFXLayer() {
return 1;
}
public void setScale(float scale) {
particleScale = scale;
}
public float getScale() {
return particleScale;
}
public void setMaxAge(int age) {
particleMaxAge = age;
}
public int getMaxAge() {
return particleMaxAge;
}
/**
* copy pasted from EntityDiggingFX
*/
@Override
public void renderParticle(Tessellator par1Tessellator, float par2, float par3, float par4, float par5, float par6,
float par7) {
float f6 = (particleTextureIndexX + particleTextureJitterX / 4.0F) / 16.0F;
float f7 = f6 + 0.015609375F;
float f8 = (particleTextureIndexY + particleTextureJitterY / 4.0F) / 16.0F;
float f9 = f8 + 0.015609375F;
float f10 = 0.1F * particleScale;
if (particleIcon != null) {
f6 = particleIcon.getInterpolatedU(particleTextureJitterX / 4.0F * 16.0F);
f7 = particleIcon.getInterpolatedU((particleTextureJitterX + 1.0F) / 4.0F * 16.0F);
f8 = particleIcon.getInterpolatedV(particleTextureJitterY / 4.0F * 16.0F);
f9 = particleIcon.getInterpolatedV((particleTextureJitterY + 1.0F) / 4.0F * 16.0F);
}
float f11 = (float) (prevPosX + (posX - prevPosX) * par2 - interpPosX);
float f12 = (float) (prevPosY + (posY - prevPosY) * par2 - interpPosY);
float f13 = (float) (prevPosZ + (posZ - prevPosZ) * par2 - interpPosZ);
float f14 = 1.0F;
par1Tessellator.setColorOpaque_F(f14 * particleRed, f14 * particleGreen, f14 * particleBlue);
par1Tessellator.addVertexWithUV(
f11 - par3 * f10 - par6 * f10,
f12 - par4 * f10,
f13 - par5 * f10 - par7 * f10,
f6,
f9);
par1Tessellator.addVertexWithUV(
f11 - par3 * f10 + par6 * f10,
f12 + par4 * f10,
f13 - par5 * f10 + par7 * f10,
f6,
f8);
par1Tessellator.addVertexWithUV(
f11 + par3 * f10 + par6 * f10,
f12 + par4 * f10,
f13 + par5 * f10 + par7 * f10,
f7,
f8);
par1Tessellator.addVertexWithUV(
f11 + par3 * f10 - par6 * f10,
f12 - par4 * f10,
f13 + par5 * f10 - par7 * f10,
f7,
f9);
}
public static void addBlockHitEffects(World world, Cuboid6 bounds, int side, IIcon icon,
EffectRenderer effectRenderer) {
float border = 0.1F;
Vector3 diff = bounds.max.copy().subtract(bounds.min).add(-2 * border);
diff.x *= world.rand.nextDouble();
diff.y *= world.rand.nextDouble();
diff.z *= world.rand.nextDouble();
Vector3 pos = diff.add(bounds.min).add(border);
if (side == 0) diff.y = bounds.min.y - border;
if (side == 1) diff.y = bounds.max.y + border;
if (side == 2) diff.z = bounds.min.z - border;
if (side == 3) diff.z = bounds.max.z + border;
if (side == 4) diff.x = bounds.min.x - border;
if (side == 5) diff.x = bounds.max.x + border;
effectRenderer.addEffect(
new EntityDigIconFX(world, pos.x, pos.y, pos.z, 0, 0, 0, icon).multiplyVelocity(0.2F)
.multipleParticleScaleBy(0.6F));
}
public static void addBlockDestroyEffects(World world, Cuboid6 bounds, IIcon[] icons,
EffectRenderer effectRenderer) {
Vector3 diff = bounds.max.copy().subtract(bounds.min);
Vector3 center = bounds.min.copy().add(bounds.max).multiply(0.5);
Vector3 density = diff.copy().multiply(4);
density.x = Math.ceil(density.x);
density.y = Math.ceil(density.y);
density.z = Math.ceil(density.z);
for (int i = 0; i < density.x; ++i) for (int j = 0; j < density.y; ++j) for (int k = 0; k < density.z; ++k) {
double x = bounds.min.x + (i + 0.5) * diff.x / density.x;
double y = bounds.min.y + (j + 0.5) * diff.y / density.y;
double z = bounds.min.z + (k + 0.5) * diff.z / density.z;
effectRenderer.addEffect(
new EntityDigIconFX(
world,
x,
y,
z,
x - center.x,
y - center.y,
z - center.z,
icons[world.rand.nextInt(icons.length)]));
}
}
}
@@ -0,0 +1,61 @@
package codechicken.lib.render;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
import net.minecraft.item.ItemStack;
import org.lwjgl.opengl.GL11;
public class FontUtils {
public static FontRenderer fontRenderer = Minecraft.getMinecraft().fontRenderer;
public static void drawCenteredString(String s, int xCenter, int y, int colour) {
fontRenderer.drawString(s, xCenter - fontRenderer.getStringWidth(s) / 2, y, colour);
}
public static void drawRightString(String s, int xRight, int y, int colour) {
fontRenderer.drawString(s, xRight - fontRenderer.getStringWidth(s), y, colour);
}
public static final String[] prefixes = new String[] { "K", "M", "G" };
public static void drawItemQuantity(int x, int y, ItemStack item, String quantity, int mode) {
if (item == null || (quantity == null && item.stackSize <= 1)) return;
if (quantity == null) {
switch (mode) {
case 2:
int q = item.stackSize;
String postfix = "";
for (int p = 0; p < 3 && q > 1000; p++) {
q /= 1000;
postfix = prefixes[p];
}
quantity = Integer.toString(q) + postfix;
case 1:
quantity = "";
if (item.stackSize / 64 > 0) quantity += item.stackSize / 64 + "s";
if (item.stackSize % 64 > 0) quantity += item.stackSize % 64;
break;
default:
quantity = Integer.toString(item.stackSize);
break;
}
}
double scale = quantity.length() > 2 ? 0.5 : 1;
double sheight = 8 * scale;
double swidth = fontRenderer.getStringWidth(quantity) * scale;
GL11.glDisable(GL11.GL_LIGHTING);
GL11.glDisable(GL11.GL_DEPTH_TEST);
GL11.glPushMatrix();
GL11.glTranslated(x + 16 - swidth, y + 16 - sheight, 0);
GL11.glScaled(scale, scale, 1);
fontRenderer.drawStringWithShadow(quantity, 0, 0, 0xFFFFFF);
GL11.glPopMatrix();
GL11.glEnable(GL11.GL_LIGHTING);
GL11.glEnable(GL11.GL_DEPTH_TEST);
}
}
@@ -0,0 +1,6 @@
package codechicken.lib.render;
public interface IFaceRenderer {
public void renderFace(Vertex5[] face, int side);
}
@@ -0,0 +1,26 @@
package codechicken.lib.render;
public class ManagedTextureFX extends TextureFX {
public boolean changed;
public ManagedTextureFX(int size, String name) {
super(size, name);
imageData = new int[size * size];
}
@Override
public void setup() {}
public void setData(int[] data) {
System.arraycopy(data, 0, imageData, 0, imageData.length);
changed = true;
}
@Override
public boolean changed() {
boolean r = changed;
changed = false;
return r;
}
}
@@ -0,0 +1,22 @@
package codechicken.lib.render;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.util.ResourceLocation;
public class PlaceholderTexture extends TextureAtlasSprite {
protected PlaceholderTexture(String par1) {
super(par1);
}
@Override
public boolean hasCustomLoader(IResourceManager manager, ResourceLocation location) {
return true;
}
@Override
public boolean load(IResourceManager manager, ResourceLocation location) {
return true;
}
}
@@ -0,0 +1,710 @@
package codechicken.lib.render;
import java.awt.image.BufferedImage;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.IIconRegister;
import net.minecraft.util.IIcon;
import net.minecraft.util.ResourceLocation;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import codechicken.lib.render.uv.UV;
import codechicken.lib.render.uv.UVScale;
import codechicken.lib.vec.BlockCoord;
import codechicken.lib.vec.Cuboid6;
import codechicken.lib.vec.CuboidCoord;
import codechicken.lib.vec.Rectangle4i;
import codechicken.lib.vec.Rotation;
import codechicken.lib.vec.Scale;
import codechicken.lib.vec.Transformation;
import codechicken.lib.vec.Translation;
import codechicken.lib.vec.Vector3;
public class QBImporter {
private static class ImagePackNode {
Rectangle4i rect;
ImagePackNode child1;
ImagePackNode child2;
QBImage packed;
public ImagePackNode(int x, int y, int w, int h) {
rect = new Rectangle4i(x, y, w, h);
}
public boolean pack(QBImage img) {
if (child1 != null) return child1.pack(img) || child2.pack(img);
if (packed != null) return false;
int fit = getFit(img.width(), img.height());
if (fit == 0) return false;
if ((fit & 2) != 0) { // exact fit
packed = img;
img.packSlot = rect;
img.packT = new ImageTransform((fit & 1) << 2);
return true;
}
int w = (fit & 1) == 0 ? img.width() : img.height();
int h = (fit & 1) == 0 ? img.height() : img.width();
if (rect.w - w > rect.h - h) { // create split with biggest leftover space
child1 = new ImagePackNode(rect.x, rect.y, w, rect.h);
child2 = new ImagePackNode(rect.x + w, rect.y, rect.w - w, rect.h);
} else {
child1 = new ImagePackNode(rect.x, rect.y, rect.w, h);
child2 = new ImagePackNode(rect.x, rect.y + h, rect.w, rect.h - h);
}
return child1.pack(img);
}
private int getFit(int w, int h) {
if (w == rect.w && h == rect.h) return 2;
if (w == rect.h && h == rect.w) return 3;
if (rect.w >= w && rect.h >= h) return 4;
if (rect.w >= h && rect.h >= w) return 5;
return 0;
}
private static void nextSize(Rectangle4i rect, boolean square) {
if (square) {
rect.w <<= 1;
rect.h <<= 1;
} else {
if (rect.w == rect.h) rect.w *= 2;
else rect.h *= 2;
}
}
public static ImagePackNode pack(List<QBImage> images, boolean square) {
Collections.sort(images);
int area = 0;
for (QBImage img : images) area += img.area();
ImagePackNode node = new ImagePackNode(0, 0, 2, 2);
while (node.rect.area() < area) nextSize(node.rect, square);
while (true) {
boolean packed = true;
for (QBImage img : images) if (!node.pack(img)) {
packed = false;
break;
}
if (packed) return node;
node.child1 = node.child2 = null;
nextSize(node.rect, square);
}
}
public BufferedImage toImage() {
BufferedImage img = new BufferedImage(rect.w, rect.h, BufferedImage.TYPE_INT_ARGB);
write(img);
return img;
}
private void write(BufferedImage img) {
if (child1 != null) {
child1.write(img);
child2.write(img);
} else if (packed != null) {
ImageTransform t = packed.packT;
for (int u = 0; u < rect.w; u++) for (int v = 0; v < rect.h; v++) {
int rgba = t.access(packed, u, v);
img.setRGB(u + rect.x, v + rect.y, rgba >>> 8 | rgba << 24);
}
}
}
}
private static class ImageTransform {
int transform;
public ImageTransform(int i) {
transform = i;
}
public ImageTransform() {
this(0);
}
public boolean transpose() {
return (transform & 4) != 0;
}
public boolean flipU() {
return (transform & 1) != 0;
}
public boolean flipV() {
return (transform & 2) != 0;
}
public int access(QBImage img, int u, int v) {
if (transpose()) {
int tmp = u;
u = v;
v = tmp;
}
if (flipU()) u = img.width() - 1 - u;
if (flipV()) v = img.height() - 1 - v;
return img.data[u][v];
}
public UV transform(UV uv) {
if (transpose()) {
double tmp = uv.u;
uv.u = uv.v;
uv.v = tmp;
}
if (flipU()) uv.u = 1 - uv.u;
if (flipV()) uv.v = 1 - uv.v;
return uv;
}
}
public static class QBImage implements Comparable<QBImage> {
int[][] data;
ImageTransform packT;
Rectangle4i packSlot;
public int width() {
return data.length;
}
public int height() {
return data[0].length;
}
public int area() {
return width() * height();
}
@Override
public int compareTo(QBImage o) {
int a = area();
int b = o.area();
return a > b ? -1 : a == b ? 0 : 1;
}
public ImageTransform transformTo(QBImage img) {
if (width() == img.width() && height() == img.height()) for (int i = 0; i < 4; i++) {
ImageTransform t = new ImageTransform(i);
if (equals(img, t)) return t;
}
if (width() == img.height() && height() == img.width()) for (int i = 4; i < 8; i++) {
ImageTransform t = new ImageTransform(i);
if (equals(img, t)) return t;
}
return null;
}
public boolean equals(QBImage img, ImageTransform t) {
for (int u = 0; u < img.width(); u++)
for (int v = 0; v < img.height(); v++) if (t.access(this, u, v) != img.data[u][v]) return false;
return true;
}
public void transform(UV uv) {
packT.transform(uv);
uv.u *= packSlot.w;
uv.v *= packSlot.h;
uv.u += packSlot.x;
uv.v += packSlot.y;
}
}
private static final int[][] vertOrder = new int[][] { // clockwise because MC is left handed
{ 3, 0 }, { 1, 0 }, { 1, 2 }, { 3, 2 } };
public static class QBQuad {
public Vertex5[] verts = new Vertex5[4];
public QBImage image = new QBImage();
public ImageTransform t = new ImageTransform();
public int side;
public QBQuad(int side) {
this.side = side;
}
public void applyImageT() {
for (Vertex5 vert : verts) {
t.transform(vert.uv);
image.transform(vert.uv);
}
}
public static QBQuad restore(Rectangle4i flat, int side, double d, QBImage img) {
QBQuad quad = new QBQuad(side);
quad.image = img;
Transformation t = new Scale(-1, 1, -1).with(Rotation.sideOrientation(side, 0))
.with(new Translation(new Vector3().setSide(side, d)));
quad.verts[0] = new Vertex5(flat.x, 0, flat.y, 0, 0);
quad.verts[1] = new Vertex5(flat.x + flat.w, 0, flat.y, 1, 0);
quad.verts[2] = new Vertex5(flat.x + flat.w, 0, flat.y + flat.h, 1, 1);
quad.verts[3] = new Vertex5(flat.x, 0, flat.y + flat.h, 0, 1);
for (Vertex5 vert : quad.verts) vert.apply(t);
return quad;
}
public Rectangle4i flatten() {
Transformation t = Rotation.sideOrientation(side, 0).inverse().with(new Scale(-1, 0, -1));
Vector3 vmin = verts[0].vec.copy().apply(t);
Vector3 vmax = verts[2].vec.copy().apply(t);
return new Rectangle4i((int) vmin.x, (int) vmin.z, (int) (vmax.x - vmin.x), (int) (vmax.z - vmin.z));
}
}
public static class QBCuboid {
public QBMatrix mat;
public CuboidCoord c;
public int sides;
public QBCuboid(QBMatrix mat, CuboidCoord c) {
this.mat = mat;
this.c = c;
sides = 0;
}
public static boolean intersects(QBCuboid a, QBCuboid b) {
CuboidCoord c = a.c;
CuboidCoord d = b.c;
return c.min.x <= d.max.x && d.min.x <= c.max.x
&& c.min.y <= d.max.y
&& d.min.y <= c.max.y
&& c.min.z <= d.max.z
&& d.min.z <= c.max.z;
}
public static void clip(QBCuboid a, QBCuboid b) {
if (intersects(a, b)) {
a.clip(b);
b.clip(a);
}
}
public void clip(QBCuboid o) {
CuboidCoord d = o.c;
for (int a = 0; a < 6; a += 2) {
int a1 = (a + 2) % 6;
int a2 = (a + 4) % 6;
if (c.getSide(a1 + 1) <= d.getSide(a1 + 1) && c.getSide(a1) >= d.getSide(a1)
&& c.getSide(a2 + 1) <= d.getSide(a2 + 1)
&& c.getSide(a2) >= d.getSide(a2)) {
if (c.getSide(a) <= d.getSide(a + 1) && c.getSide(a) >= d.getSide(a)) {
c.setSide(a, d.getSide(a + 1) + 1);
sides |= 1 << a;
}
if (c.getSide(a + 1) >= d.getSide(a) && c.getSide(a + 1) <= d.getSide(a + 1)) {
c.setSide(a + 1, d.getSide(a) - 1);
sides |= 2 << a;
}
}
}
}
public void extractQuads(List<QBQuad> quads) {
Cuboid6 box = c.bounds();
for (int s = 0; s < 6; s++) if ((sides & 1 << s) == 0) quads.add(extractQuad(s, box));
}
private QBQuad extractQuad(int side, Cuboid6 box) {
double[] da = new double[3];
da[side >> 1] = box.getSide(side);
QBQuad quad = new QBQuad(side);
for (int i = 0; i < 4; i++) {
int rU = vertOrder[i][0];
int rV = vertOrder[i][1];
int sideU = Rotation.rotateSide(side, rU);
int sideV = Rotation.rotateSide(side, rV);
da[sideU >> 1] = box.getSide(sideU);
da[sideV >> 1] = box.getSide(sideV);
quad.verts[i] = new Vertex5(Vector3.fromAxes(da), (3 - rU) / 2, rV / 2);
}
int sideU = Rotation.rotateSide(side, 1);
int sideV = Rotation.rotateSide(side, 2);
quad.image.data = new int[c.size(sideU)][c.size(sideV)];
QBImage image = quad.image;
int[] ia = new int[3];
ia[side >> 1] = c.getSide(side);
ia[sideU >> 1] = c.getSide(sideU ^ 1);
ia[sideV >> 1] = c.getSide(sideV ^ 1);
BlockCoord b = BlockCoord.fromAxes(ia);
BlockCoord bU = BlockCoord.sideOffsets[sideU];
BlockCoord bV = BlockCoord.sideOffsets[sideV];
for (int u = 0; u < image.width(); u++) for (int v = 0; v < image.height(); v++)
image.data[u][v] = mat.matrix[b.x + bU.x * u + bV.x * v][b.y + bU.y * u + bV.y * v][b.z + bU.z * u
+ bV.z * v];
return quad;
}
}
public static class QBMatrix {
public String name;
public BlockCoord pos;
public BlockCoord size;
public int[][][] matrix;
public void readMatrix(DataInputStream din, boolean compressed) throws IOException {
if (compressed) {
int z = 0;
while (z < size.z) {
int index = 0;
while (true) {
int data = din.readInt();
if (data == NEXTSLICEFLAG) break;
if (data == CODEFLAG) {
int count = readTni(din);
data = din.readInt();
for (int j = 0; j < count; j++, index++) matrix[index % size.x][index / size.x][z] = data;
} else {
matrix[index % size.x][index / size.x][z] = data;
index++;
}
}
z++;
}
} else {
for (int z = 0; z < size.z; z++)
for (int y = 0; y < size.y; y++) for (int x = 0; x < size.x; x++) matrix[x][y][z] = din.readInt();
}
}
public void convertBGRAtoRGBA() {
for (int z = 0; z < size.z; z++) for (int y = 0; y < size.y; y++) for (int x = 0; x < size.x; x++) {
int i = matrix[x][y][z];
matrix[x][y][z] = Integer.reverseBytes(i >>> 8) | i & 0xFF;
}
}
private boolean voxelFull(boolean[][][] solid, CuboidCoord c) {
for (BlockCoord b : c) if (matrix[b.x][b.y][b.z] == 0) return false;
for (BlockCoord b : c) solid[b.x][b.y][b.z] = false;
return true;
}
private QBCuboid expand(boolean[][][] solid, BlockCoord b) {
CuboidCoord c = new CuboidCoord(b);
solid[b.x][b.y][b.z] = false;
for (int s = 0; s < 6; s++) {
CuboidCoord slice = c.copy();
slice.expand(s ^ 1, -(slice.size(s) - 1));
slice.expand(s, 1);
while (slice.getSide(s) >= 0 && slice.getSide(s) < size.getSide(s)) {
if (!voxelFull(solid, slice)) break;
slice.expand(s ^ 1, -1);
slice.expand(s, 1);
c.expand(s, 1);
}
}
return new QBCuboid(this, c);
}
public List<QBCuboid> rectangulate() {
List<QBCuboid> list = new ArrayList<QBCuboid>();
boolean[][][] solid = new boolean[size.x][size.y][size.z];
for (int z = 0; z < size.z; z++)
for (int y = 0; y < size.y; y++) for (int x = 0; x < size.x; x++) solid[x][y][z] = matrix[x][y][z] != 0;
for (int x = 0; x < size.x; x++) for (int z = 0; z < size.z; z++)
for (int y = 0; y < size.y; y++) if (solid[x][y][z]) list.add(expand(solid, new BlockCoord(x, y, z)));
for (int i = 0; i < list.size(); i++)
for (int j = i + 1; j < list.size(); j++) QBCuboid.clip(list.get(i), list.get(j));
return list;
}
public List<QBQuad> extractQuads(boolean texturePlanes) {
List<QBQuad> quads = new LinkedList<QBQuad>();
for (QBCuboid c : rectangulate()) c.extractQuads(quads);
if (texturePlanes) optimisePlanes(quads);
return quads;
}
private void optimisePlanes(List<QBQuad> quads) {
Multimap<Integer, QBQuad> map = HashMultimap.create();
for (QBQuad quad : quads) map.put(quad.side | ((int) quad.verts[0].vec.getSide(quad.side)) << 3, quad);
quads.clear();
for (Integer key : map.keySet()) {
Collection<QBQuad> plane = map.get(key);
if (plane.size() == 1) {
quads.add(plane.iterator().next());
continue;
}
int side = key & 7;
Rectangle4i rect = null;
for (QBQuad q : plane) if (rect == null) rect = q.flatten();
else rect.include(q.flatten());
QBImage img = new QBImage();
img.data = new int[rect.w][rect.h];
for (QBQuad q : plane) {
QBImage from = q.image;
Rectangle4i r = q.flatten();
int du = r.x - rect.x;
int dv = r.y - rect.y;
for (int u = 0; u < from.width(); u++)
for (int v = 0; v < from.height(); v++) img.data[du + u][dv + v] = from.data[u][v];
}
quads.add(QBQuad.restore(rect, side, key >> 3, img));
}
}
public CCModel buildModel(List<QBQuad> quads, BufferedImage img, boolean scaleMC) {
CCModel m = CCModel.quadModel(quads.size() * 4);
int i = 0;
for (QBQuad quad : quads) {
quad.applyImageT();
m.verts[i++] = quad.verts[0];
m.verts[i++] = quad.verts[1];
m.verts[i++] = quad.verts[2];
m.verts[i++] = quad.verts[3];
}
m.apply(new UVScale(1D / img.getWidth(), 1D / img.getHeight()));
m.apply(new Translation(pos.x, pos.y, pos.z));
if (scaleMC) m.apply(new Scale(1 / 16D));
m.computeNormals();
return m;
}
private static void addImages(List<QBQuad> quads, List<QBImage> images) {
for (QBQuad q : quads) {
QBImage img = q.image;
boolean matched = false;
for (QBImage img2 : images) {
ImageTransform t = img.transformTo(img2);
if (t != null) {
q.t = t;
q.image = img2;
matched = true;
break;
}
}
if (!matched) images.add(img);
}
}
}
public static final int TEXTUREPLANES = 1;
public static final int SQUARETEXTURE = 2;
public static final int MERGETEXTURES = 4;
public static final int SCALEMC = 8;
public static class QBModel {
public QBMatrix[] matrices;
public boolean rightHanded;
public RasterisedModel toRasterisedModel(int flags) {
List<QBImage> qbImages = new ArrayList<QBImage>();
List<List<QBQuad>> modelQuads = new ArrayList<List<QBQuad>>();
List<BufferedImage> images = new ArrayList<BufferedImage>();
boolean texturePlanes = (flags & TEXTUREPLANES) != 0;
boolean squareTextures = (flags & SQUARETEXTURE) != 0;
boolean mergeTextures = (flags & MERGETEXTURES) != 0;
boolean scaleMC = (flags & SCALEMC) != 0;
for (QBMatrix mat : matrices) {
List<QBQuad> quads = mat.extractQuads(texturePlanes);
modelQuads.add(quads);
QBMatrix.addImages(quads, qbImages);
if (!mergeTextures) {
images.add(ImagePackNode.pack(qbImages, squareTextures).toImage());
qbImages.clear();
}
}
if (mergeTextures) images.add(ImagePackNode.pack(qbImages, squareTextures).toImage());
RasterisedModel m = new RasterisedModel(images);
for (int i = 0; i < matrices.length; i++) {
QBMatrix mat = matrices[i];
BufferedImage img = images.get(mergeTextures ? 0 : i);
m.add(mat.name, mat.buildModel(modelQuads.get(i), img, scaleMC));
}
return m;
}
}
public static class RasterisedModel {
private class Holder {
CCModel m;
int img;
public Holder(CCModel m, int img) {
this.m = m;
this.img = img;
}
}
private Map<String, Holder> map = new HashMap<String, Holder>();
private List<BufferedImage> images;
private String[] icons;
public RasterisedModel(List<BufferedImage> images) {
this.images = images;
icons = new String[images.size()];
}
public void add(String name, CCModel m) {
map.put(name, new Holder(m, Math.min(map.size(), images.size() - 1)));
}
public CCModel getModel(String key) {
return map.get(key).m;
}
public IIcon getIcon(String key, IIconRegister r, String iconName) {
int img = map.get(key).img;
if (icons[img] != null && !iconName.equals(icons[img])) throw new IllegalArgumentException(
"Attempted to get a previously registered icon by a different name: " + icons[img]
+ ", "
+ iconName);
if (icons[img] != null) return r.registerIcon(iconName);
icons[img] = iconName;
return TextureUtils.getTextureSpecial(r, iconName).addTexture(new TextureDataHolder(images.get(img)));
}
private void exportImg(BufferedImage img, File imgFile) throws IOException {
if (!imgFile.exists()) imgFile.createNewFile();
ImageIO.write(img, "PNG", imgFile);
}
public void export(File objFile, File imgDir) {
try {
if (!objFile.exists()) objFile.createNewFile();
if (!imgDir.exists()) imgDir.mkdirs();
Map<String, CCModel> modelMap = new HashMap<String, CCModel>();
for (Map.Entry<String, Holder> e : map.entrySet()) modelMap.put(e.getKey(), e.getValue().m);
PrintWriter p = new PrintWriter(objFile);
CCModel.exportObj(modelMap, p);
p.close();
if (images.size() < map.size())
exportImg(images.get(0), new File(imgDir, objFile.getName().replaceAll("(.+)\\..+", "$1.png")));
else for (Map.Entry<String, Holder> e : map.entrySet())
exportImg(images.get(e.getValue().img), new File(imgDir, e.getKey() + ".png"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
private static String readAsciiString(DataInputStream din) throws IOException {
byte[] bytes = new byte[din.readByte() & 0xFF];
din.readFully(bytes);
return new String(bytes, "US-ASCII");
}
private static int readTni(DataInputStream din) throws IOException {
return Integer.reverseBytes(din.readInt());
}
private static final int CODEFLAG = Integer.reverseBytes(2);
private static final int NEXTSLICEFLAG = Integer.reverseBytes(6);
public static QBModel loadQB(InputStream input) throws IOException {
DataInputStream din = new DataInputStream(input);
QBModel m = new QBModel();
int version = din.readInt();
int colorFormat = din.readInt();
m.rightHanded = din.readInt() != 0;
boolean compressed = din.readInt() != 0;
boolean visEncoded = din.readInt() != 0;
if (visEncoded) throw new IllegalArgumentException("Encoded Visiblity States not supported");
m.matrices = new QBMatrix[readTni(din)];
for (int i = 0; i < m.matrices.length; i++) {
QBMatrix mat = new QBMatrix();
m.matrices[i] = mat;
mat.name = readAsciiString(din);
mat.size = new BlockCoord(readTni(din), readTni(din), readTni(din));
mat.pos = new BlockCoord(readTni(din), readTni(din), readTni(din));
mat.matrix = new int[mat.size.x][mat.size.y][mat.size.z];
mat.readMatrix(din, compressed);
if (colorFormat == 1) mat.convertBGRAtoRGBA();
}
return m;
}
public static QBModel loadQB(ResourceLocation res) {
try {
return loadQB(Minecraft.getMinecraft().getResourceManager().getResource(res).getInputStream());
} catch (Exception e) {
throw new RuntimeException("failed to load model: " + res, e);
}
}
public static QBModel loadQB(File file) {
try {
FileInputStream fin = new FileInputStream(file);
try {
return loadQB(fin);
} finally {
fin.close();
}
} catch (Exception e) {
throw new RuntimeException("failed to load model: " + file.getPath(), e);
}
}
}
@@ -0,0 +1,377 @@
package codechicken.lib.render;
import static net.minecraftforge.client.IItemRenderer.ItemRenderType.ENTITY;
import static net.minecraftforge.client.IItemRenderer.ItemRendererHelper.BLOCK_3D;
import net.minecraft.block.Block;
import net.minecraft.client.renderer.RenderBlocks;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.entity.RenderItem;
import net.minecraft.client.renderer.entity.RenderManager;
import net.minecraft.entity.Entity;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemStack;
import net.minecraft.util.IIcon;
import net.minecraftforge.client.IItemRenderer;
import net.minecraftforge.client.MinecraftForgeClient;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidStack;
import org.lwjgl.opengl.GL11;
import codechicken.lib.vec.Cuboid6;
import codechicken.lib.vec.Rectangle4i;
import codechicken.lib.vec.Vector3;
public class RenderUtils {
static Vector3[] vectors = new Vector3[8];
static RenderItem uniformRenderItem = new RenderItem() {
public boolean shouldBob() {
return false;
}
};
static EntityItem entityItem;
static {
for (int i = 0; i < vectors.length; i++) vectors[i] = new Vector3();
uniformRenderItem.setRenderManager(RenderManager.instance);
entityItem = new EntityItem(null);
entityItem.hoverStart = 0;
}
public static void renderFluidQuad(Vector3 point1, Vector3 point2, Vector3 point3, Vector3 point4, IIcon icon,
double res) {
renderFluidQuad(
point2,
vectors[0].set(point4).subtract(point1),
vectors[1].set(point1).subtract(point2),
icon,
res);
}
/**
* Draws a tessellated quadrilateral bottom to top, left to right
*
* @param base The bottom left corner of the quad
* @param wide The bottom of the quad
* @param high The left side of the quad
* @param res Units per icon
*/
public static void renderFluidQuad(Vector3 base, Vector3 wide, Vector3 high, IIcon icon, double res) {
Tessellator t = Tessellator.instance;
double u1 = icon.getMinU();
double du = icon.getMaxU() - icon.getMinU();
double v2 = icon.getMaxV();
double dv = icon.getMaxV() - icon.getMinV();
double wlen = wide.mag();
double hlen = high.mag();
double x = 0;
while (x < wlen) {
double rx = wlen - x;
if (rx > res) rx = res;
double y = 0;
while (y < hlen) {
double ry = hlen - y;
if (ry > res) ry = res;
Vector3 dx1 = vectors[2].set(wide).multiply(x / wlen);
Vector3 dx2 = vectors[3].set(wide).multiply((x + rx) / wlen);
Vector3 dy1 = vectors[4].set(high).multiply(y / hlen);
Vector3 dy2 = vectors[5].set(high).multiply((y + ry) / hlen);
t.addVertexWithUV(
base.x + dx1.x + dy2.x,
base.y + dx1.y + dy2.y,
base.z + dx1.z + dy2.z,
u1,
v2 - ry / res * dv);
t.addVertexWithUV(base.x + dx1.x + dy1.x, base.y + dx1.y + dy1.y, base.z + dx1.z + dy1.z, u1, v2);
t.addVertexWithUV(
base.x + dx2.x + dy1.x,
base.y + dx2.y + dy1.y,
base.z + dx2.z + dy1.z,
u1 + rx / res * du,
v2);
t.addVertexWithUV(
base.x + dx2.x + dy2.x,
base.y + dx2.y + dy2.y,
base.z + dx2.z + dy2.z,
u1 + rx / res * du,
v2 - ry / res * dv);
y += ry;
}
x += rx;
}
}
public static void translateToWorldCoords(Entity entity, float frame) {
double interpPosX = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * frame;
double interpPosY = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * frame;
double interpPosZ = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * frame;
GL11.glTranslated(-interpPosX, -interpPosY, -interpPosZ);
}
public static void drawCuboidOutline(Cuboid6 c) {
Tessellator var2 = Tessellator.instance;
var2.startDrawing(3);
var2.addVertex(c.min.x, c.min.y, c.min.z);
var2.addVertex(c.max.x, c.min.y, c.min.z);
var2.addVertex(c.max.x, c.min.y, c.max.z);
var2.addVertex(c.min.x, c.min.y, c.max.z);
var2.addVertex(c.min.x, c.min.y, c.min.z);
var2.draw();
var2.startDrawing(3);
var2.addVertex(c.min.x, c.max.y, c.min.z);
var2.addVertex(c.max.x, c.max.y, c.min.z);
var2.addVertex(c.max.x, c.max.y, c.max.z);
var2.addVertex(c.min.x, c.max.y, c.max.z);
var2.addVertex(c.min.x, c.max.y, c.min.z);
var2.draw();
var2.startDrawing(1);
var2.addVertex(c.min.x, c.min.y, c.min.z);
var2.addVertex(c.min.x, c.max.y, c.min.z);
var2.addVertex(c.max.x, c.min.y, c.min.z);
var2.addVertex(c.max.x, c.max.y, c.min.z);
var2.addVertex(c.max.x, c.min.y, c.max.z);
var2.addVertex(c.max.x, c.max.y, c.max.z);
var2.addVertex(c.min.x, c.min.y, c.max.z);
var2.addVertex(c.min.x, c.max.y, c.max.z);
var2.draw();
}
public static void renderFluidCuboid(CCRenderState state, Cuboid6 bound, IIcon tex, double res) {
renderFluidQuad( // bottom
new Vector3(bound.min.x, bound.min.y, bound.min.z),
new Vector3(bound.max.x, bound.min.y, bound.min.z),
new Vector3(bound.max.x, bound.min.y, bound.max.z),
new Vector3(bound.min.x, bound.min.y, bound.max.z),
tex,
res);
renderFluidQuad( // top
new Vector3(bound.min.x, bound.max.y, bound.min.z),
new Vector3(bound.min.x, bound.max.y, bound.max.z),
new Vector3(bound.max.x, bound.max.y, bound.max.z),
new Vector3(bound.max.x, bound.max.y, bound.min.z),
tex,
res);
renderFluidQuad( // -x
new Vector3(bound.min.x, bound.max.y, bound.min.z),
new Vector3(bound.min.x, bound.min.y, bound.min.z),
new Vector3(bound.min.x, bound.min.y, bound.max.z),
new Vector3(bound.min.x, bound.max.y, bound.max.z),
tex,
res);
renderFluidQuad( // +x
new Vector3(bound.max.x, bound.max.y, bound.max.z),
new Vector3(bound.max.x, bound.min.y, bound.max.z),
new Vector3(bound.max.x, bound.min.y, bound.min.z),
new Vector3(bound.max.x, bound.max.y, bound.min.z),
tex,
res);
renderFluidQuad( // -z
new Vector3(bound.max.x, bound.max.y, bound.min.z),
new Vector3(bound.max.x, bound.min.y, bound.min.z),
new Vector3(bound.min.x, bound.min.y, bound.min.z),
new Vector3(bound.min.x, bound.max.y, bound.min.z),
tex,
res);
renderFluidQuad( // +z
new Vector3(bound.min.x, bound.max.y, bound.max.z),
new Vector3(bound.min.x, bound.min.y, bound.max.z),
new Vector3(bound.max.x, bound.min.y, bound.max.z),
new Vector3(bound.max.x, bound.max.y, bound.max.z),
tex,
res);
}
public static void renderFluidCuboid(Cuboid6 bound, IIcon tex, double res) {
renderFluidCuboid(CCRenderState.instance(), bound, tex, res);
}
public static void renderBlockOverlaySide(int x, int y, int z, int side, double tx1, double tx2, double ty1,
double ty2) {
double[] points = new double[] { x - 0.009, x + 1.009, y - 0.009, y + 1.009, z - 0.009, z + 1.009 };
Tessellator tessellator = Tessellator.instance;
switch (side) {
case 0:
tessellator.addVertexWithUV(points[0], points[2], points[4], tx1, ty1);
tessellator.addVertexWithUV(points[1], points[2], points[4], tx2, ty1);
tessellator.addVertexWithUV(points[1], points[2], points[5], tx2, ty2);
tessellator.addVertexWithUV(points[0], points[2], points[5], tx1, ty2);
break;
case 1:
tessellator.addVertexWithUV(points[1], points[3], points[4], tx2, ty1);
tessellator.addVertexWithUV(points[0], points[3], points[4], tx1, ty1);
tessellator.addVertexWithUV(points[0], points[3], points[5], tx1, ty2);
tessellator.addVertexWithUV(points[1], points[3], points[5], tx2, ty2);
break;
case 2:
tessellator.addVertexWithUV(points[0], points[3], points[4], tx2, ty1);
tessellator.addVertexWithUV(points[1], points[3], points[4], tx1, ty1);
tessellator.addVertexWithUV(points[1], points[2], points[4], tx1, ty2);
tessellator.addVertexWithUV(points[0], points[2], points[4], tx2, ty2);
break;
case 3:
tessellator.addVertexWithUV(points[1], points[3], points[5], tx2, ty1);
tessellator.addVertexWithUV(points[0], points[3], points[5], tx1, ty1);
tessellator.addVertexWithUV(points[0], points[2], points[5], tx1, ty2);
tessellator.addVertexWithUV(points[1], points[2], points[5], tx2, ty2);
break;
case 4:
tessellator.addVertexWithUV(points[0], points[3], points[5], tx2, ty1);
tessellator.addVertexWithUV(points[0], points[3], points[4], tx1, ty1);
tessellator.addVertexWithUV(points[0], points[2], points[4], tx1, ty2);
tessellator.addVertexWithUV(points[0], points[2], points[5], tx2, ty2);
break;
case 5:
tessellator.addVertexWithUV(points[1], points[3], points[4], tx2, ty1);
tessellator.addVertexWithUV(points[1], points[3], points[5], tx1, ty1);
tessellator.addVertexWithUV(points[1], points[2], points[5], tx1, ty2);
tessellator.addVertexWithUV(points[1], points[2], points[4], tx2, ty2);
break;
}
}
public static boolean shouldRenderFluid(FluidStack stack) {
return stack.amount > 0 && stack.getFluid() != null;
}
/**
* @param stack The fluid stack to render
* @return The icon of the fluid
*/
public static IIcon prepareFluidRender(CCRenderState state, FluidStack stack, int alpha) {
GL11.glDisable(GL11.GL_LIGHTING);
GL11.glEnable(GL11.GL_BLEND);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
Fluid fluid = stack.getFluid();
state.setColour(fluid.getColor(stack) << 8 | alpha);
TextureUtils.bindAtlas(fluid.getSpriteNumber());
return TextureUtils.safeIcon(fluid.getIcon(stack));
}
public static IIcon prepareFluidRender(FluidStack stack, int alpha) {
return prepareFluidRender(CCRenderState.instance(), stack, alpha);
}
/**
* Re-enables lighting and disables blending.
*/
public static void postFluidRender() {
GL11.glEnable(GL11.GL_LIGHTING);
GL11.glDisable(GL11.GL_BLEND);
}
public static double fluidDensityToAlpha(double density) {
return Math.pow(density, 0.4);
}
/**
* Renders a fluid within a bounding box. If the fluid is a liquid it will render as a normal tank with height equal
* to density/bound.height. If the fluid is a gas, it will render the full box with an alpha equal to density.
* Warning, bound will be mutated if the fluid is a liquid
*
* @param stack The fluid to render.
* @param bound The box within which the fluid is contained.
* @param density The volume of fluid / the capacity of the tank. For gases this determines the alpha, for liquids
* this determines the height.
* @param res The resolution to render at.
*/
public static void renderFluidCuboid(CCRenderState state, FluidStack stack, Cuboid6 bound, double density,
double res) {
if (!shouldRenderFluid(stack)) return;
int alpha = 255;
if (stack.getFluid().isGaseous()) alpha = (int) (fluidDensityToAlpha(density) * 255);
else bound.max.y = bound.min.y + (bound.max.y - bound.min.y) * density;
IIcon tex = prepareFluidRender(state, stack, alpha);
state.startDrawing();
renderFluidCuboid(state, bound, tex, res);
state.draw();
postFluidRender();
}
public static void renderFluidCuboid(FluidStack stack, Cuboid6 bound, double density, double res) {
renderFluidCuboid(CCRenderState.instance(), stack, bound, density, res);
}
public static void renderFluidGauge(CCRenderState state, FluidStack stack, Rectangle4i rect, double density,
double res) {
if (!shouldRenderFluid(stack)) return;
int alpha = 255;
if (stack.getFluid().isGaseous()) alpha = (int) (fluidDensityToAlpha(density) * 255);
else {
int height = (int) (rect.h * density);
rect.y += rect.h - height;
rect.h = height;
}
IIcon tex = prepareFluidRender(state, stack, alpha);
state.startDrawing();
renderFluidQuad(
new Vector3(rect.x, rect.y + rect.h, 0),
new Vector3(rect.w, 0, 0),
new Vector3(0, -rect.h, 0),
tex,
res);
state.draw();
postFluidRender();
}
public static void renderFluidGauge(FluidStack stack, Rectangle4i rect, double density, double res) {
renderFluidGauge(CCRenderState.instance(), stack, rect, density, res);
}
/**
* Renders items and blocks in the world at 0,0,0 with transformations that size them appropriately
*/
public static void renderItemUniform(ItemStack item) {
renderItemUniform(item, 0);
}
/**
* Renders items and blocks in the world at 0,0,0 with transformations that size them appropriately
*
* @param spin The spin angle of the item around the y axis in degrees
*/
public static void renderItemUniform(ItemStack item, double spin) {
IItemRenderer customRenderer = MinecraftForgeClient.getItemRenderer(item, ENTITY);
boolean is3D = customRenderer != null && customRenderer.shouldUseRenderHelper(ENTITY, item, BLOCK_3D);
boolean larger = false;
if (item.getItem() instanceof ItemBlock
&& RenderBlocks.renderItemIn3d(Block.getBlockFromItem(item.getItem()).getRenderType())) {
int renderType = Block.getBlockFromItem(item.getItem()).getRenderType();
larger = !(renderType == 1 || renderType == 19 || renderType == 12 || renderType == 2);
} else if (is3D) {
larger = true;
}
double d = 2;
double d1 = 1 / d;
if (larger) GL11.glScaled(d, d, d);
GL11.glColor4f(1, 1, 1, 1);
entityItem.setEntityItemStack(item);
uniformRenderItem.doRender(entityItem, 0, larger ? 0.09 : 0.06, 0, 0, (float) (spin * 9 / Math.PI));
if (larger) GL11.glScaled(d1, d1, d1);
}
}
@@ -0,0 +1,125 @@
package codechicken.lib.render;
import static org.lwjgl.opengl.ARBShaderObjects.GL_OBJECT_COMPILE_STATUS_ARB;
import static org.lwjgl.opengl.ARBShaderObjects.GL_OBJECT_INFO_LOG_LENGTH_ARB;
import static org.lwjgl.opengl.ARBShaderObjects.GL_OBJECT_LINK_STATUS_ARB;
import static org.lwjgl.opengl.ARBShaderObjects.GL_OBJECT_VALIDATE_STATUS_ARB;
import static org.lwjgl.opengl.ARBShaderObjects.glAttachObjectARB;
import static org.lwjgl.opengl.ARBShaderObjects.glCompileShaderARB;
import static org.lwjgl.opengl.ARBShaderObjects.glCreateProgramObjectARB;
import static org.lwjgl.opengl.ARBShaderObjects.glCreateShaderObjectARB;
import static org.lwjgl.opengl.ARBShaderObjects.glDeleteObjectARB;
import static org.lwjgl.opengl.ARBShaderObjects.glGetInfoLogARB;
import static org.lwjgl.opengl.ARBShaderObjects.glGetObjectParameteriARB;
import static org.lwjgl.opengl.ARBShaderObjects.glLinkProgramARB;
import static org.lwjgl.opengl.ARBShaderObjects.glShaderSourceARB;
import static org.lwjgl.opengl.ARBShaderObjects.glUseProgramObjectARB;
import static org.lwjgl.opengl.ARBShaderObjects.glValidateProgramARB;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.lwjgl.opengl.ARBShaderObjects;
import org.lwjgl.opengl.ARBVertexShader;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.vector.Matrix4f;
public class ShaderProgram {
int programID;
public ShaderProgram() {
programID = glCreateProgramObjectARB();
if (programID == 0) throw new RuntimeException("Unable to allocate shader program object.");
}
public void attach(int shaderType, String resource) {
InputStream stream = ShaderProgram.class.getResourceAsStream(resource);
if (stream == null) throw new RuntimeException("Unable to locate resource: " + resource);
attach(shaderType, stream);
}
public void use() {
glUseProgramObjectARB(programID);
}
public static void restore() {
glUseProgramObjectARB(0);
}
public void link() {
glLinkProgramARB(programID);
if (glGetObjectParameteriARB(programID, GL_OBJECT_LINK_STATUS_ARB) == GL11.GL_FALSE)
throw new RuntimeException("Error linking program: " + getInfoLog(programID));
glValidateProgramARB(programID);
if (glGetObjectParameteriARB(programID, GL_OBJECT_VALIDATE_STATUS_ARB) == GL11.GL_FALSE)
throw new RuntimeException("Error validating program: " + getInfoLog(programID));
use();
onLink();
restore();
}
public void attach(int shaderType, InputStream stream) {
if (stream == null) throw new RuntimeException("Invalid shader inputstream");
int shaderID = 0;
try {
shaderID = glCreateShaderObjectARB(shaderType);
if (shaderID == 0) throw new RuntimeException("Unable to allocate shader object.");
try {
glShaderSourceARB(shaderID, asString(stream));
} catch (IOException e) {
throw new RuntimeException("Error reading inputstream.", e);
}
glCompileShaderARB(shaderID);
if (glGetObjectParameteriARB(shaderID, GL_OBJECT_COMPILE_STATUS_ARB) == GL11.GL_FALSE)
throw new RuntimeException("Error compiling shader: " + getInfoLog(shaderID));
glAttachObjectARB(programID, shaderID);
} catch (RuntimeException e) {
glDeleteObjectARB(shaderID);
throw e;
}
}
public static String asString(InputStream stream) throws IOException {
StringBuilder sb = new StringBuilder();
BufferedReader bin = new BufferedReader(new InputStreamReader(stream));
String line;
while ((line = bin.readLine()) != null) sb.append(line).append('\n');
stream.close();
return sb.toString();
}
private static String getInfoLog(int shaderID) {
return glGetInfoLogARB(shaderID, glGetObjectParameteriARB(shaderID, GL_OBJECT_INFO_LOG_LENGTH_ARB));
}
public int getUniformLoc(String name) {
return ARBShaderObjects.glGetUniformLocationARB(programID, name);
}
public int getAttribLoc(String name) {
return ARBVertexShader.glGetAttribLocationARB(programID, name);
}
public void uniformTexture(String name, int textureIndex) {
ARBShaderObjects.glUniform1iARB(getUniformLoc(name), textureIndex);
}
public void onLink() {}
public void glVertexAttributeMat4(int loc, Matrix4f matrix) {
ARBVertexShader.glVertexAttrib4fARB(loc, matrix.m00, matrix.m01, matrix.m02, matrix.m03);
ARBVertexShader.glVertexAttrib4fARB(loc + 1, matrix.m10, matrix.m11, matrix.m12, matrix.m13);
ARBVertexShader.glVertexAttrib4fARB(loc + 2, matrix.m20, matrix.m21, matrix.m22, matrix.m23);
ARBVertexShader.glVertexAttrib4fARB(loc + 3, matrix.m30, matrix.m31, matrix.m32, matrix.m33);
}
}
@@ -0,0 +1,129 @@
package codechicken.lib.render;
import java.util.ArrayList;
import java.util.HashMap;
import net.minecraft.client.renderer.texture.IIconRegister;
import net.minecraft.client.renderer.texture.TextureMap;
import net.minecraft.util.IIcon;
import net.minecraft.util.ResourceLocation;
import codechicken.lib.render.TextureUtils.IIconSelfRegister;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
public class SpriteSheetManager {
@SideOnly(Side.CLIENT)
public static class SpriteSheet implements IIconSelfRegister {
private int tilesX;
private int tilesY;
private ArrayList<Integer> newSprites = new ArrayList<Integer>();
private TextureSpecial[] sprites;
private ResourceLocation resource;
private TextureDataHolder texture;
private int spriteWidth;
private int spriteHeight;
public int atlasIndex;
private SpriteSheet(int tilesX, int tilesY, ResourceLocation textureFile) {
this.tilesX = tilesX;
this.tilesY = tilesY;
this.resource = textureFile;
sprites = new TextureSpecial[tilesX * tilesY];
}
public void requestIndicies(int... indicies) {
for (int i : indicies) setupSprite(i);
}
public void registerIcons(IIconRegister register) {
TextureMap textureMap = (TextureMap) register;
if (TextureUtils.refreshTexture(textureMap, resource.getResourcePath())) {
reloadTexture();
for (TextureSpecial sprite : sprites)
if (sprite != null) textureMap.setTextureEntry(sprite.getIconName(), sprite);
} else {
for (int i : newSprites) textureMap.setTextureEntry(sprites[i].getIconName(), sprites[i]);
}
newSprites.clear();
}
public TextureSpecial setupSprite(int i) {
if (sprites[i] == null) {
String name = resource + "_" + i;
sprites[i] = new TextureSpecial(name).baseFromSheet(this, i);
newSprites.add(i);
}
return sprites[i];
}
private void reloadTexture() {
texture = TextureUtils.loadTexture(resource);
spriteWidth = texture.width / tilesX;
spriteHeight = texture.height / tilesY;
}
public IIcon getSprite(int index) {
IIcon i = sprites[index];
if (i == null) throw new IllegalArgumentException(
"Sprite at index: " + index + " from texture file " + resource + " was not preloaded.");
return i;
}
public TextureDataHolder createSprite(int spriteIndex) {
int sx = spriteIndex % tilesX;
int sy = spriteIndex / tilesX;
TextureDataHolder sprite = new TextureDataHolder(spriteWidth, spriteHeight);
TextureUtils.copySubImg(
texture.data,
texture.width,
sx * spriteWidth,
sy * spriteHeight,
spriteWidth,
spriteHeight,
sprite.data,
spriteWidth,
0,
0);
return sprite;
}
public int spriteWidth() {
return spriteWidth;
}
public int spriteHeight() {
return spriteHeight;
}
public TextureSpecial bindTextureFX(int i, TextureFX textureFX) {
return setupSprite(i).addTextureFX(textureFX);
}
public SpriteSheet selfRegister(int atlas) {
TextureUtils.addIconRegistrar(this);
return this;
}
@Override
public int atlasIndex() {
return atlasIndex;
}
}
private static HashMap<String, SpriteSheet> spriteSheets = new HashMap<String, SpriteSheet>();
public static SpriteSheet getSheet(ResourceLocation resource) {
return getSheet(16, 16, resource);
}
public static SpriteSheet getSheet(int tilesX, int tilesY, ResourceLocation resource) {
SpriteSheet sheet = spriteSheets.get(resource.toString());
if (sheet == null) spriteSheets.put(resource.toString(), sheet = new SpriteSheet(tilesX, tilesY, resource));
return sheet;
}
}
@@ -0,0 +1,34 @@
package codechicken.lib.render;
import java.awt.image.BufferedImage;
public class TextureDataHolder {
public int width;
public int height;
public int[] data;
public TextureDataHolder(int width, int height) {
this.width = width;
this.height = height;
data = new int[width * height];
}
public TextureDataHolder(int[] data, int width) {
this.data = data;
this.width = width;
height = data.length / width;
}
public TextureDataHolder(BufferedImage img) {
this(img.getWidth(), img.getHeight());
img.getRGB(0, 0, width, height, data, 0, width);
}
public TextureDataHolder copyData() {
int[] copy = new int[data.length];
System.arraycopy(data, 0, copy, 0, data.length);
data = copy;
return this;
}
}
@@ -0,0 +1,59 @@
package codechicken.lib.render;
import net.minecraft.client.Minecraft;
import codechicken.lib.render.SpriteSheetManager.SpriteSheet;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
public class TextureFX {
public int[] imageData;
public int tileSizeBase = 16;
public int tileSizeSquare = 256;
public int tileSizeMask = 15;
public int tileSizeSquareMask = 255;
public boolean anaglyphEnabled;
public TextureSpecial texture;
public TextureFX(int spriteIndex, SpriteSheet sheet) {
texture = sheet.bindTextureFX(spriteIndex, this);
}
public TextureFX(int size, String name) {
texture = new TextureSpecial(name).blank(size).selfRegister().addTextureFX(this);
}
public TextureFX setAtlas(int index) {
texture.atlasIndex = index;
return this;
}
public void setup() {
imageData = new int[tileSizeSquare];
}
public void onTextureDimensionsUpdate(int width, int height) {
if (width != height)
throw new IllegalArgumentException("Non-Square textureFX not supported (" + width + ":" + height + ")");
tileSizeBase = width;
tileSizeSquare = tileSizeBase * tileSizeBase;
tileSizeMask = tileSizeBase - 1;
tileSizeSquareMask = tileSizeSquare - 1;
setup();
}
public void update() {
anaglyphEnabled = Minecraft.getMinecraft().gameSettings.anaglyph;
onTick();
}
public void onTick() {}
public boolean changed() {
return true;
}
}
@@ -0,0 +1,180 @@
package codechicken.lib.render;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.IIconRegister;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.texture.TextureMap;
import net.minecraft.client.renderer.texture.TextureUtil;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.client.resources.data.AnimationMetadataSection;
import net.minecraft.client.settings.GameSettings;
import net.minecraft.util.ResourceLocation;
import codechicken.lib.render.SpriteSheetManager.SpriteSheet;
import codechicken.lib.render.TextureUtils.IIconSelfRegister;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
@SideOnly(Side.CLIENT)
public class TextureSpecial extends TextureAtlasSprite implements IIconSelfRegister {
// sprite sheet fields
private int spriteIndex;
private SpriteSheet spriteSheet;
// textureFX fields
private TextureFX textureFX;
private int mipmapLevels;
private int rawWidth;
private int rawHeight;
private int blankSize = -1;
private ArrayList<TextureDataHolder> baseTextures;
private boolean selfRegister;
public int atlasIndex;
protected TextureSpecial(String par1) {
super(par1);
}
public TextureSpecial addTexture(TextureDataHolder t) {
if (baseTextures == null) baseTextures = new ArrayList<TextureDataHolder>();
baseTextures.add(t);
return this;
}
public TextureSpecial baseFromSheet(SpriteSheet spriteSheet, int spriteIndex) {
this.spriteSheet = spriteSheet;
this.spriteIndex = spriteIndex;
return this;
}
public TextureSpecial addTextureFX(TextureFX fx) {
textureFX = fx;
return this;
}
@Override
public void initSprite(int sheetWidth, int sheetHeight, int originX, int originY, boolean rotated) {
super.initSprite(sheetWidth, sheetHeight, originX, originY, rotated);
if (textureFX != null) textureFX.onTextureDimensionsUpdate(rawWidth, rawHeight);
}
@Override
public void updateAnimation() {
if (textureFX != null) {
textureFX.update();
if (textureFX.changed()) {
int[][] mipmaps = new int[mipmapLevels + 1][];
mipmaps[0] = textureFX.imageData;
mipmaps = prepareAnisotropicFiltering(mipmaps);
mipmaps = TextureUtil.generateMipmapData(mipmapLevels, width, mipmaps);
TextureUtil.uploadTextureMipmap(mipmaps, width, height, originX, originY, false, false);
}
}
}
/**
* Copy paste mojang code because it's private, and CCL can't have access transformers or reflection
*/
public int[][] prepareAnisotropicFiltering(int[][] mipmaps) {
if (Minecraft.getMinecraft().gameSettings.anisotropicFiltering <= 1) {
return mipmaps;
} else {
int[][] aint1 = new int[mipmaps.length][];
for (int k = 0; k < mipmaps.length; ++k) {
int[] aint2 = mipmaps[k];
if (aint2 != null) {
int[] aint3 = new int[(rawWidth + 16 >> k) * (rawHeight + 16 >> k)];
System.arraycopy(aint2, 0, aint3, 0, aint2.length);
aint1[k] = TextureUtil.prepareAnisotropicData(aint3, rawWidth >> k, rawHeight >> k, 8 >> k);
}
}
return aint1;
}
}
@Override
public void loadSprite(BufferedImage[] images, AnimationMetadataSection animationMeta,
boolean anisotropicFiltering) {
rawWidth = images[0].getWidth();
rawHeight = images[0].getHeight();
super.loadSprite(images, animationMeta, anisotropicFiltering);
}
@Override
public void generateMipmaps(int p_147963_1_) {
super.generateMipmaps(p_147963_1_);
mipmapLevels = p_147963_1_;
}
@Override
public boolean hasCustomLoader(IResourceManager manager, ResourceLocation location) {
return true;
}
public void addFrame(int[] data, int width, int height) {
GameSettings settings = Minecraft.getMinecraft().gameSettings;
BufferedImage[] images = new BufferedImage[settings.mipmapLevels + 1];
images[0] = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
images[0].setRGB(0, 0, width, height, data, 0, width);
loadSprite(images, null, settings.anisotropicFiltering > 1);
}
@Override
public boolean load(IResourceManager manager, ResourceLocation location) {
if (baseTextures != null) {
for (TextureDataHolder tex : baseTextures) addFrame(tex.data, tex.width, tex.height);
} else if (spriteSheet != null) {
TextureDataHolder tex = spriteSheet.createSprite(spriteIndex);
addFrame(tex.data, tex.width, tex.height);
} else if (blankSize > 0) {
addFrame(new int[blankSize * blankSize], blankSize, blankSize);
}
if (framesTextureData.isEmpty()) throw new RuntimeException("No base frame for texture: " + getIconName());
return false;
}
@Override
public boolean hasAnimationMetadata() {
return textureFX != null || super.hasAnimationMetadata();
}
@Override
public int getFrameCount() {
if (textureFX != null) return 1;
return super.getFrameCount();
}
public TextureSpecial blank(int size) {
blankSize = size;
return this;
}
public TextureSpecial selfRegister() {
selfRegister = true;
TextureUtils.addIconRegistrar(this);
return this;
}
@Override
public void registerIcons(IIconRegister register) {
if (selfRegister) ((TextureMap) register).setTextureEntry(getIconName(), this);
}
@Override
public int atlasIndex() {
return atlasIndex;
}
}
@@ -0,0 +1,168 @@
package codechicken.lib.render;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.IIconRegister;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.client.renderer.texture.TextureMap;
import net.minecraft.util.IIcon;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.client.event.TextureStitchEvent;
import net.minecraftforge.common.MinecraftForge;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import codechicken.lib.colour.Colour;
import codechicken.lib.colour.ColourARGB;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
public class TextureUtils {
public static interface IIconSelfRegister {
public void registerIcons(IIconRegister register);
public int atlasIndex();
}
static {
MinecraftForge.EVENT_BUS.register(new TextureUtils());
}
private static ArrayList<IIconSelfRegister> iconRegistrars = new ArrayList<TextureUtils.IIconSelfRegister>();
public static void addIconRegistrar(IIconSelfRegister registrar) {
iconRegistrars.add(registrar);
}
@SubscribeEvent
public void textureLoad(TextureStitchEvent.Pre event) {
for (IIconSelfRegister reg : iconRegistrars)
if (reg.atlasIndex() == event.map.getTextureType()) reg.registerIcons(event.map);
}
/**
* @return an array of ARGB pixel data
*/
public static int[] loadTextureData(ResourceLocation resource) {
return loadTexture(resource).data;
}
public static Colour[] loadTextureColours(ResourceLocation resource) {
int[] idata = loadTextureData(resource);
Colour[] data = new Colour[idata.length];
for (int i = 0; i < data.length; i++) data[i] = new ColourARGB(idata[i]);
return data;
}
public static InputStream getTextureResource(ResourceLocation textureFile) throws IOException {
return Minecraft.getMinecraft().getResourceManager().getResource(textureFile).getInputStream();
}
public static BufferedImage loadBufferedImage(ResourceLocation textureFile) {
try {
return loadBufferedImage(getTextureResource(textureFile));
} catch (Exception e) {
System.err.println("Failed to load texture file: " + textureFile);
e.printStackTrace();
}
return null;
}
public static BufferedImage loadBufferedImage(InputStream in) throws IOException {
BufferedImage img = ImageIO.read(in);
in.close();
return img;
}
public static TextureManager engine() {
return Minecraft.getMinecraft().renderEngine;
}
public static void copySubImg(int[] fromTex, int fromWidth, int fromX, int fromY, int width, int height,
int[] toTex, int toWidth, int toX, int toY) {
for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) {
int fp = (y + fromY) * fromWidth + x + fromX;
int tp = (y + toX) * toWidth + x + toX;
toTex[tp] = fromTex[fp];
}
}
public static void bindAtlas(int atlasIndex) {
engine().bindTexture(atlasIndex == 0 ? TextureMap.locationBlocksTexture : TextureMap.locationItemsTexture);
}
public static IIcon getBlankIcon(int size, IIconRegister iconRegister) {
TextureMap textureMap = (TextureMap) iconRegister;
String s = "blank_" + size;
if (textureMap.getTextureExtry(s) == null) {
TextureSpecial icon = new TextureSpecial(s).blank(size);
textureMap.setTextureEntry(s, icon);
}
return iconRegister.registerIcon(s);
}
public static TextureSpecial getTextureSpecial(IIconRegister iconRegister, String name) {
TextureMap textureMap = (TextureMap) iconRegister;
IIcon entry = textureMap.getTextureExtry(name);
if (entry != null) throw new IllegalStateException("Texture: " + name + " is already registered");
TextureSpecial icon = new TextureSpecial(name);
textureMap.setTextureEntry(name, icon);
return icon;
}
public static void prepareTexture(int target, int texture, int min_mag_filter, int wrap) {
GL11.glBindTexture(target, texture);
GL11.glTexParameteri(target, GL11.GL_TEXTURE_MIN_FILTER, min_mag_filter);
GL11.glTexParameteri(target, GL11.GL_TEXTURE_MAG_FILTER, min_mag_filter);
switch (target) {
case GL12.GL_TEXTURE_3D:
GL11.glTexParameteri(target, GL12.GL_TEXTURE_WRAP_R, wrap);
case GL11.GL_TEXTURE_2D:
GL11.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_T, wrap);
case GL11.GL_TEXTURE_1D:
GL11.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_S, wrap);
}
}
public static TextureDataHolder loadTexture(ResourceLocation resource) {
BufferedImage img = loadBufferedImage(resource);
if (img == null) throw new RuntimeException("Texture not found: " + resource);
return new TextureDataHolder(img);
}
/**
* Uses an empty placeholder texture to tell if the map has been reloaded since the last call to refresh texture and
* the texture with name needs to be reacquired to be valid
*/
public static boolean refreshTexture(TextureMap map, String name) {
if (map.getTextureExtry(name) == null) {
map.setTextureEntry(name, new PlaceholderTexture(name));
return true;
}
return false;
}
public static IIcon safeIcon(IIcon icon) {
if (icon == null)
icon = ((TextureMap) engine().getTexture(TextureMap.locationBlocksTexture)).getAtlasSprite("missingno");
return icon;
}
public static boolean isMissing(IIcon icon, ResourceLocation atlas) {
if (icon == null) return true;
IIcon missing = ((TextureMap) engine().getTexture(atlas)).getAtlasSprite("missingno");
return icon.getMinU() == missing.getMinU() && icon.getMinV() == missing.getMinV();
}
}
@@ -0,0 +1,91 @@
package codechicken.lib.render;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import codechicken.lib.render.uv.UV;
import codechicken.lib.render.uv.UVTransformation;
import codechicken.lib.util.Copyable;
import codechicken.lib.vec.Transformation;
import codechicken.lib.vec.Vector3;
public class Vertex5 implements Copyable<Vertex5> {
public Vector3 vec;
public UV uv;
public Vertex5() {
this(new Vector3(), new UV());
}
public Vertex5(Vector3 vert, UV uv) {
this.vec = vert;
this.uv = uv;
}
public Vertex5(Vector3 vert, double u, double v) {
this(vert, new UV(u, v));
}
public Vertex5(double x, double y, double z, double u, double v) {
this(x, y, z, u, v, 0);
}
public Vertex5(double x, double y, double z, double u, double v, int tex) {
this(new Vector3(x, y, z), new UV(u, v, tex));
}
public Vertex5 set(double x, double y, double z, double u, double v) {
vec.set(x, y, z);
uv.set(u, v);
return this;
}
public Vertex5 set(double x, double y, double z, double u, double v, int tex) {
vec.set(x, y, z);
uv.set(u, v, tex);
return this;
}
public Vertex5 set(Vertex5 vert) {
vec.set(vert.vec);
uv.set(vert.uv);
return this;
}
public Vertex5(Vertex5 vertex5) {
this(vertex5.vec.copy(), vertex5.uv.copy());
}
public Vertex5 copy() {
return new Vertex5(this);
}
public String toString() {
MathContext cont = new MathContext(4, RoundingMode.HALF_UP);
return "Vertex: (" + new BigDecimal(vec.x, cont)
+ ", "
+ new BigDecimal(vec.y, cont)
+ ", "
+ new BigDecimal(vec.z, cont)
+ ") "
+ "("
+ new BigDecimal(uv.u, cont)
+ ", "
+ new BigDecimal(uv.v, cont)
+ ") ("
+ uv.tex
+ ")";
}
public Vertex5 apply(Transformation t) {
vec.apply(t);
return this;
}
public Vertex5 apply(UVTransformation t) {
uv.apply(t);
return this;
}
}
@@ -0,0 +1,25 @@
package codechicken.lib.render.uv;
import net.minecraft.util.IIcon;
import codechicken.lib.vec.IrreversibleTransformationException;
public class IconTransformation extends UVTransformation {
public IIcon icon;
public IconTransformation(IIcon icon) {
this.icon = icon;
}
@Override
public void apply(UV uv) {
uv.u = icon.getInterpolatedU(uv.u * 16);
uv.v = icon.getInterpolatedV(uv.v * 16);
}
@Override
public UVTransformation inverse() {
throw new IrreversibleTransformationException(this);
}
}
@@ -0,0 +1,26 @@
package codechicken.lib.render.uv;
import net.minecraft.util.IIcon;
import codechicken.lib.vec.IrreversibleTransformationException;
public class MultiIconTransformation extends UVTransformation {
public IIcon[] icons;
public MultiIconTransformation(IIcon... icons) {
this.icons = icons;
}
@Override
public void apply(UV uv) {
IIcon icon = icons[uv.tex % icons.length];
uv.u = icon.getInterpolatedU(uv.u * 16);
uv.v = icon.getInterpolatedV(uv.v * 16);
}
@Override
public UVTransformation inverse() {
throw new IrreversibleTransformationException(this);
}
}
@@ -0,0 +1,78 @@
package codechicken.lib.render.uv;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import codechicken.lib.util.Copyable;
public class UV implements Copyable<UV> {
public double u;
public double v;
public int tex;
public UV() {}
public UV(double u, double v) {
this(u, v, 0);
}
public UV(double u, double v, int tex) {
this.u = u;
this.v = v;
this.tex = tex;
}
public UV(UV uv) {
this(uv.u, uv.v, uv.tex);
}
public UV set(double u, double v, int tex) {
this.u = u;
this.v = v;
this.tex = tex;
return this;
}
public UV set(double u, double v) {
return set(u, v, tex);
}
public UV set(UV uv) {
return set(uv.u, uv.v, uv.tex);
}
public UV copy() {
return new UV(this);
}
public UV add(UV uv) {
u += uv.u;
v += uv.v;
return this;
}
public UV multiply(double d) {
u *= d;
v *= d;
return this;
}
public String toString() {
MathContext cont = new MathContext(4, RoundingMode.HALF_UP);
return "UV(" + new BigDecimal(u, cont) + ", " + new BigDecimal(v, cont) + ")";
}
public UV apply(UVTransformation t) {
t.apply(this);
return this;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof UV)) return false;
UV uv = (UV) o;
return u == uv.u && v == uv.v;
}
}
@@ -0,0 +1,51 @@
package codechicken.lib.render.uv;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import codechicken.lib.math.MathHelper;
public class UVRotation extends UVTransformation {
public double angle;
/**
* @param angle The angle to rotate counterclockwise in radians
*/
public UVRotation(double angle) {
this.angle = angle;
}
@Override
public void apply(UV uv) {
double c = MathHelper.cos(angle);
double s = MathHelper.sin(angle);
double u2 = c * uv.u + s * uv.v;
uv.v = -s * uv.u + c * uv.v;
uv.u = u2;
}
@Override
public UVTransformation inverse() {
return new UVRotation(-angle);
}
@Override
public UVTransformation merge(UVTransformation next) {
if (next instanceof UVRotation) return new UVRotation(angle + ((UVRotation) next).angle);
return null;
}
@Override
public boolean isRedundant() {
return MathHelper.between(-1E-5, angle, 1E-5);
}
@Override
public String toString() {
MathContext cont = new MathContext(4, RoundingMode.HALF_UP);
return "UVRotation(" + new BigDecimal(angle, cont) + ")";
}
}
@@ -0,0 +1,37 @@
package codechicken.lib.render.uv;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
public class UVScale extends UVTransformation {
double su;
double sv;
public UVScale(double scaleu, double scalev) {
su = scaleu;
sv = scalev;
}
public UVScale(double d) {
this(d, d);
}
@Override
public void apply(UV uv) {
uv.u *= su;
uv.v *= sv;
}
@Override
public UVTransformation inverse() {
return new UVScale(1 / su, 1 / sv);
}
@Override
public String toString() {
MathContext cont = new MathContext(4, RoundingMode.HALF_UP);
return "UVScale(" + new BigDecimal(su, cont) + ", " + new BigDecimal(sv, cont) + ")";
}
}
@@ -0,0 +1,39 @@
package codechicken.lib.render.uv;
import codechicken.lib.render.CCRenderState;
import codechicken.lib.vec.ITransformation;
/**
* Abstract supertype for any UV transformation
*/
public abstract class UVTransformation extends ITransformation<UV, UVTransformation>
implements CCRenderState.IVertexOperation {
public static final int operationIndex = CCRenderState.registerOperation();
public UVTransformation at(UV point) {
return new UVTransformationList(
new UVTranslation(-point.u, -point.v),
this,
new UVTranslation(point.u, point.v));
}
public UVTransformationList with(UVTransformation t) {
return new UVTransformationList(this, t);
}
@Override
public boolean load(CCRenderState state) {
return !isRedundant();
}
@Override
public void operate(CCRenderState state) {
apply(state.vert.uv);
}
@Override
public int operationID() {
return operationIndex;
}
}
@@ -0,0 +1,83 @@
package codechicken.lib.render.uv;
import java.util.ArrayList;
import java.util.Iterator;
public class UVTransformationList extends UVTransformation {
private ArrayList<UVTransformation> transformations = new ArrayList<UVTransformation>();
public UVTransformationList(UVTransformation... transforms) {
for (UVTransformation t : transforms)
if (t instanceof UVTransformationList) transformations.addAll(((UVTransformationList) t).transformations);
else transformations.add(t);
compact();
}
@Override
public void apply(UV uv) {
for (int i = 0; i < transformations.size(); i++) transformations.get(i).apply(uv);
}
@Override
public UVTransformationList with(UVTransformation t) {
if (t.isRedundant()) return this;
if (t instanceof UVTransformationList) transformations.addAll(((UVTransformationList) t).transformations);
else transformations.add(t);
compact();
return this;
}
public UVTransformationList prepend(UVTransformation t) {
if (t.isRedundant()) return this;
if (t instanceof UVTransformationList) transformations.addAll(0, ((UVTransformationList) t).transformations);
else transformations.add(0, t);
compact();
return this;
}
private void compact() {
ArrayList<UVTransformation> newList = new ArrayList<UVTransformation>(transformations.size());
Iterator<UVTransformation> iterator = transformations.iterator();
UVTransformation prev = null;
while (iterator.hasNext()) {
UVTransformation t = iterator.next();
if (t.isRedundant()) continue;
if (prev != null) {
UVTransformation m = prev.merge(t);
if (m == null) newList.add(prev);
else if (m.isRedundant()) t = null;
else t = m;
}
prev = t;
}
if (prev != null) newList.add(prev);
if (newList.size() < transformations.size()) transformations = newList;
}
@Override
public boolean isRedundant() {
return transformations.size() == 0;
}
@Override
public UVTransformation inverse() {
UVTransformationList rev = new UVTransformationList();
for (int i = transformations.size() - 1; i >= 0; i--) rev.with(transformations.get(i).inverse());
return rev;
}
@Override
public String toString() {
String s = "";
for (UVTransformation t : transformations) s += "\n" + t.toString();
return s.trim();
}
}
Loaded 100 of 135 files, more files were not shown because too many files have changed in this diff. Show more