mirror of
https://github.com/GTNewHorizons/CodeChickenCore.git
synced 2026-10-27 14:53:33 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63f36ca383 | ||
|
|
bb008a1265 | ||
|
|
bb98bdc6d4 | ||
|
|
4e5bcf829c | ||
|
|
678970e48b | ||
|
|
5ad7ebe1ac | ||
|
|
033009fed5 | ||
|
|
acd863e748 | ||
|
|
850e2b9368 | ||
|
|
4713f7aebd | ||
|
|
b999279656 | ||
|
|
1879ba2071 | ||
|
|
9d600169ac | ||
|
|
4e7eb37f3f | ||
|
|
22dfc1360e | ||
|
|
7bf5935f69 | ||
|
|
162de447d5 | ||
|
|
39ed6607a6 | ||
|
|
99b815c12e | ||
|
|
0f37937ada | ||
|
|
c9d29a5c8e | ||
|
|
ea4f68d163 | ||
|
|
549f769747 | ||
|
|
cba2e0f642 | ||
|
|
3740253590 | ||
|
|
183e658bb4 | ||
|
|
acf5b9a2e7 | ||
|
|
7f78ceb921 | ||
|
|
ce1830d7b5 | ||
|
|
4686fe8fa1 | ||
|
|
4e743ba64b | ||
|
|
c5b8594660 | ||
|
|
87168007d7 | ||
|
|
7b0ab21823 | ||
|
|
14983449e8 | ||
|
|
e8dab57101 | ||
|
|
d6e2b946b1 | ||
|
|
52d522f311 | ||
|
|
c841d484ee | ||
|
|
3145cccd70 | ||
|
|
fcb9063f2f | ||
|
|
16883088a6 | ||
|
|
f1034f699e | ||
|
|
777235b7fb | ||
|
|
d183e6d9a9 | ||
|
|
a4515fbef8 | ||
|
|
318c107957 | ||
|
|
fabf2af058 | ||
|
|
f6102d56ec | ||
|
|
9b42fa756f | ||
|
|
fa6fea6843 | ||
|
|
8d95baccb5 | ||
|
|
23327451a2 | ||
|
|
b8a70cb09b | ||
|
|
fcd42c8496 | ||
|
|
c84c6875a1 | ||
|
|
1908388ce7 | ||
|
|
5423d96eb7 | ||
|
|
cfd00f33c8 | ||
|
|
6ff72337b7 | ||
|
|
0d678abc10 | ||
|
|
2e1fc1ece0 | ||
|
|
8933d4cf78 | ||
|
|
35e8b9a956 | ||
|
|
2b7476f2e6 | ||
|
|
19dc507599 | ||
|
|
263864ab82 | ||
|
|
94630e0b2b | ||
|
|
53ac3d0ba7 | ||
|
|
8fe333ed86 | ||
|
|
0ac4906338 | ||
|
|
cea80e20a2 | ||
|
|
2987b36bad | ||
|
|
e9f9253875 | ||
|
|
05bd26eb87 | ||
|
|
42b022af2a | ||
|
|
16f0b50920 | ||
|
|
65a89cc92c | ||
|
|
6b5404f1c0 | ||
|
|
d92b0a6a31 | ||
|
|
ccc986f1d7 | ||
|
|
29e4eda53b | ||
|
|
feb8bfd028 | ||
|
|
4a40b215d5 | ||
|
|
e570e12e13 | ||
|
|
05d1e187f8 | ||
|
|
645919daa9 | ||
|
|
f9287f696b | ||
|
|
38744413af | ||
|
|
71f3c55d9d | ||
|
|
f50b326d92 | ||
|
|
3b081ed3cd | ||
|
|
76a6c9e7d5 | ||
|
|
0ba1132ff6 | ||
|
|
9411dd0808 | ||
|
|
4b16ef2776 | ||
|
|
23a58af62a | ||
|
|
f753dcdeb8 | ||
|
|
18461348e3 | ||
|
|
e7c670b1d3 | ||
|
|
ba15e4b116 | ||
|
|
93b1e5dbf0 | ||
|
|
bdb0c8f174 | ||
|
|
1680d74d6d | ||
|
|
09bf91715b | ||
|
|
97cb16586a | ||
|
|
29ef6904f0 | ||
|
|
0529baaf23 | ||
|
|
3bde4ab9ee | ||
|
|
5c54b184ea | ||
|
|
307ca81694 | ||
|
|
66015a13c5 | ||
|
|
6d19cc0079 | ||
|
|
8386a982a2 | ||
|
|
f1153412c1 | ||
|
|
215715f7c5 | ||
|
|
408e45ef25 | ||
|
|
f9fea33b5e | ||
|
|
47d7c667de | ||
|
|
66fd8ad103 | ||
|
|
1f03959a99 | ||
|
|
52f82017e7 | ||
|
|
0a14b64e12 | ||
|
|
32fa471c44 | ||
|
|
c3dcc42a65 | ||
|
|
12e3060669 | ||
|
|
2d9f409c7d | ||
|
|
41997e15e9 | ||
|
|
d910584217 | ||
|
|
4e87c9bfe7 | ||
|
|
91c65cf1dc | ||
|
|
4699a9e86e | ||
|
|
f56ba57d3a | ||
|
|
4babca7004 | ||
|
|
99865de255 | ||
|
|
0a5aee875b | ||
|
|
ca675b5f31 | ||
|
|
34154fc011 | ||
|
|
23b79b7b15 | ||
|
|
e274d1c512 | ||
|
|
eac0727165 | ||
|
|
ae652f51c2 | ||
|
|
7450370d99 | ||
|
|
477b77e7e1 | ||
|
|
5401f3afb0 | ||
|
|
eb6f5e5e7b | ||
|
|
ffc284349e | ||
|
|
3c81313a8c | ||
|
|
3d64337681 | ||
|
|
6a8a453496 | ||
|
|
16d43f287d | ||
|
|
9e6ac47eba | ||
|
|
8a777b265e | ||
|
|
699221214c | ||
|
|
3e65888097 | ||
|
|
6233c2f6ca | ||
|
|
dd297d6e42 | ||
|
|
a7637ca787 | ||
|
|
a315c0059e | ||
|
|
ddadd1f831 | ||
|
|
112c0abc08 | ||
|
|
3b762d300f | ||
|
|
ab0713c129 | ||
|
|
b1057544a7 | ||
|
|
1b71ee5e3d | ||
|
|
bd3234b033 | ||
|
|
cdb52bf72a | ||
|
|
d889cf9cbc | ||
|
|
f5bbe9d2da | ||
|
|
6a29a2428f | ||
|
|
ea515682c1 | ||
|
|
5c5384f79c | ||
|
|
c67c731647 | ||
|
|
c545683025 | ||
|
|
5ef9f6174d | ||
|
|
0941845227 | ||
|
|
9601c27e9d | ||
|
|
a580ca90a9 | ||
|
|
2636721bec | ||
|
|
3d4e112552 | ||
|
|
575ec57050 | ||
|
|
4bd5aab59a | ||
|
|
7f42cd94e0 | ||
|
|
56776c8149 | ||
|
|
5184abc23c | ||
|
|
b16ad2115b | ||
|
|
15d0edcafb | ||
|
|
a1433ba4d4 | ||
|
|
57bcf384d1 | ||
|
|
16704eb601 | ||
|
|
d6f14e9ab1 | ||
|
|
36e6c64b66 | ||
|
|
a003f2638d | ||
|
|
fed626df0b | ||
|
|
8518ed3bb0 | ||
|
|
a2d3051270 | ||
|
|
9341031e7f | ||
|
|
3c5a61e011 | ||
|
|
0f32dd8620 | ||
|
|
397135f687 |
No files matched your search
@@ -0,0 +1,62 @@
|
||||
name: Report Bug
|
||||
description: "Report a bug."
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "A bug/crash report with sufficient information and logs to reproduce and track down."
|
||||
- type: input
|
||||
id: discord
|
||||
attributes:
|
||||
label: Your GTNH Discord Username
|
||||
description: Leave empty if you don't have one, but this will make it harder to contact you if we need additional info.
|
||||
placeholder: "Example: Fake#1234"
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Mod Version
|
||||
description: "What version of the Mod are you using?"
|
||||
placeholder: "Example: 1.0.0"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: report
|
||||
attributes:
|
||||
label: Bug Report
|
||||
description: "Relevant information, as well as relevant logs attached such as `logs/fml-client-latest.log."
|
||||
placeholder: "Example: https://mclo.gs/ OR submit the file to github by dragging it to this textbox."
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: java
|
||||
attributes:
|
||||
label: Java Version
|
||||
description: What Java version are you using? It's worth mentioning that if you play on Java9+ you should try update to latest minor release (e.g. prefer Java 17.0.6 over 17.0.2) of that version.
|
||||
options:
|
||||
- Java 8
|
||||
- Java 9
|
||||
- Java 11
|
||||
- Java 17
|
||||
- Java 19
|
||||
- Java 20
|
||||
- Java 21
|
||||
- Other (Please Specify)
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: modlist
|
||||
attributes:
|
||||
label: Mod List or GTNH Pack Version
|
||||
description: "List of mods, ideally a minimal reproducible set (can be retrieved from latest.log). If using GTNH please indicate pack version and any changed mods, not the entire modlist."
|
||||
placeholder: "List of mods or GTNH Pack version goes here"
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: final
|
||||
attributes:
|
||||
label: Final Checklist
|
||||
description: Certify that you read things
|
||||
options:
|
||||
- label: "I have searched the issues and haven't found a similar issue."
|
||||
required: true
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
name: Feature Request
|
||||
description: "Request a feature."
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: "Please use this form to request features. We ask you check if it was already requested though"
|
||||
- type: input
|
||||
id: discord
|
||||
attributes:
|
||||
label: Your GTNH Discord Username
|
||||
description: Leave empty if you don't have one, but this will make it harder to contact you if we need additional info.
|
||||
placeholder: "Example: Wumpus#1234"
|
||||
- type: textarea
|
||||
id: request
|
||||
attributes:
|
||||
label: Feature Request
|
||||
description: "Relevant information, as well as relevant images"
|
||||
placeholder: "Example: https://mclo.gs/ OR submit the file to github by dragging it to this textbox."
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: final
|
||||
attributes:
|
||||
label: Final Checklist
|
||||
description: Certify that you read things
|
||||
options:
|
||||
- label: "I have searched the issues and haven't found a similar issue."
|
||||
required: true
|
||||
@@ -26,3 +26,4 @@ whitelist.json
|
||||
*.ipr
|
||||
*.iws
|
||||
src/main/resources/mixins.*.json
|
||||
*.bat
|
||||
+4
-1604
File diff suppressed because it is too large.
Load diff
@@ -1,5 +1,4 @@
|
||||
// Add your dependencies here
|
||||
|
||||
dependencies {
|
||||
api('com.github.GTNewHorizons:CodeChickenLib:1.2.0:dev')
|
||||
}
|
||||
+149
-36
@@ -1,85 +1,144 @@
|
||||
# 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 = CodeChicken Core
|
||||
|
||||
# This is a case-sensitive string to identify your mod. Convention is to use lower case.
|
||||
# Case-sensitive identifier string, available for mcmod.info population and used for automatic mixin JSON generation.
|
||||
# Conventionally lowercase.
|
||||
modId = CodeChickenCore
|
||||
|
||||
# Root package of the mod, used to find various classes in other properties,
|
||||
# mcmod.info substitution, enabling assertions in run tasks, etc.
|
||||
modGroup = codechicken
|
||||
|
||||
# WHY is there no version field?
|
||||
# The build script relies on git to provide a version via tags. It is super easy and will enable you to always know the
|
||||
# code base or your binary. Check out this tutorial: https://blog.mattclemente.com/2017/10/13/versioning-with-git-tags/
|
||||
# Whether to use modGroup as the maven publishing group.
|
||||
# When false, com.github.GTNewHorizons is used.
|
||||
useModGroupForPublishing = false
|
||||
|
||||
# Will update your build.gradle automatically whenever an update is available
|
||||
# Updates your build.gradle and settings.gradle automatically whenever an update is available.
|
||||
autoUpdateBuildScript = false
|
||||
|
||||
# Version of Minecraft to target
|
||||
minecraftVersion = 1.7.10
|
||||
|
||||
# Version of Minecraft Forge to target
|
||||
forgeVersion = 10.13.4.1614
|
||||
|
||||
# Select a username for testing your mod with breakpoints. You may leave this empty for a random username each time you
|
||||
# restart Minecraft in development. Choose this dependent on your mod:
|
||||
# Do you need consistent player progressing (for example Thaumcraft)? -> Select a name
|
||||
# Do you need to test how your custom blocks interacts with a player that is not the owner? -> leave name empty
|
||||
# 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
|
||||
|
||||
# Generate a class with String fields for the mod id, name, version and group name named with the fields below
|
||||
# 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 = codechicken.core.asm.Tags
|
||||
gradleTokenModId = MODID
|
||||
gradleTokenModName = MODNAME
|
||||
|
||||
# Name of the token containing the project's current version to generate/replace.
|
||||
gradleTokenVersion = VERSION
|
||||
gradleTokenGroupName = GROUPNAME
|
||||
|
||||
# [DEPRECATED] Mod Group replacement token.
|
||||
gradleTokenGroupName =
|
||||
|
||||
# [DEPRECATED]
|
||||
# Multiple source files can be defined here by providing a comma-seperated list: Class1.java,Class2.java,Class3.java
|
||||
# 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
|
||||
# version in @Mod([...], version = VERSION, [...]).
|
||||
# Leave these properties empty to skip individual token replacements.
|
||||
replaceGradleTokenInFile =
|
||||
|
||||
# In case your mod provides an API for other mods to implement you may declare its package here. Otherwise, you can
|
||||
# leave this property empty.
|
||||
# Example value: apiPackage = api + modGroup = com.myname.mymodid -> com.myname.mymodid.api
|
||||
# Example value: (apiPackage = api) + (modGroup = com.myname.mymodid) -> com.myname.mymodid.api
|
||||
apiPackage =
|
||||
|
||||
# Specify the configuration file for Forge's access transformers here. It must be placed into /src/main/resources/META-INF/
|
||||
# Example value: mymodid_at.cfg
|
||||
# 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
|
||||
# Specify the location of your implementation of IMixinPlugin. Leave it empty otherwise.
|
||||
|
||||
# Set to a non-empty string to configure mixins in a separate source set under src/VALUE, instead of src/main.
|
||||
# This can speed up compile times thanks to not running the mixin annotation processor on all input sources.
|
||||
# Mixin classes will have access to "main" classes, but not the other way around.
|
||||
separateMixinSourceSet =
|
||||
|
||||
# Adds some debug arguments like verbose output and class export.
|
||||
usesMixinDebug = false
|
||||
|
||||
# Specify the location of your implementation of IMixinConfigPlugin. Leave it empty otherwise.
|
||||
mixinPlugin =
|
||||
# Specify the package that contains all of your Mixins. You may only place Mixins in this package or the build will fail!
|
||||
|
||||
# Specify the package that contains all of your Mixins. The package must exist or
|
||||
# the build will fail. If you have a package property defined in your mixins.<modid>.json,
|
||||
# it must match with this or the build will fail.
|
||||
mixinsPackage =
|
||||
|
||||
# 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
|
||||
# Example value: (coreModClass = asm.FMLPlugin) + (modGroup = com.myname.mymodid) -> com.myname.mymodid.asm.FMLPlugin
|
||||
coreModClass = core.launch.CodeChickenCorePlugin
|
||||
|
||||
# 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 = true
|
||||
|
||||
# If enabled, you may use 'shadowImplementation' for dependencies. They will be integrated in your jar. It is your
|
||||
# responsibility check the licence and request permission for distribution, if required.
|
||||
# Enables Mixins even if this mod doesn't use them, useful if one of the dependencies uses mixins.
|
||||
forceEnableMixins = false
|
||||
|
||||
# If enabled, you may use 'shadowCompile' for dependencies. They will be integrated into your jar. It is your
|
||||
# responsibility to check the license and request permission for distribution if required.
|
||||
usesShadowedDependencies = false
|
||||
|
||||
# Publishing to CurseForge requires you to set the CURSEFORGE_TOKEN environment variable to one of your CurseForge API tokens.
|
||||
# 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
|
||||
|
||||
# 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 = 746279
|
||||
# If disabled, won't rename the shadowed classes.
|
||||
relocateShadowedDependencies = true
|
||||
|
||||
# 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 id of the other mod.
|
||||
# curseForgeRelations =
|
||||
# Adds CurseMaven, Modrinth, and some more well-known 1.7.10 repositories.
|
||||
includeWellKnownRepositories = true
|
||||
|
||||
# A list of repositories to exclude from the includeWellKnownRepositories setting. Should be a space separated
|
||||
# list of strings, with the acceptable keys being(case does not matter):
|
||||
# cursemaven
|
||||
# modrinth
|
||||
excludeWellKnownRepositories =
|
||||
|
||||
# Publishing to modrinth requires you to set the MODRINTH_TOKEN environment variable to your current modrinth API token.
|
||||
# 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 on Modrinth.
|
||||
|
||||
# Leave this empty if you don't want to publish to Modrinth.
|
||||
modrinthProjectId = codechickencore-unofficial
|
||||
|
||||
# The project's relations on Modrinth. You can use this to refer to other projects on Modrinth.
|
||||
@@ -87,6 +146,60 @@ modrinthProjectId = codechickencore-unofficial
|
||||
# Where scope can be one of [required, optional, incompatible, embedded],
|
||||
# type can be one of [project, version],
|
||||
# and the name is the Modrinth project or version slug/id of the other mod.
|
||||
# Example: required-project:fplib;optional-project:gasstation;incompatible-project:gregtech
|
||||
# Note: UniMixins is automatically set as a required dependency if usesMixins = true.
|
||||
modrinthRelations =
|
||||
|
||||
# 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 = 746279
|
||||
|
||||
# The project's relations on CurseForge. You can use this to refer to other projects on CurseForge.
|
||||
# Syntax: type1:name1;type2:name2;...
|
||||
# Where type can be one of [requiredDependency, embeddedLibrary, optionalDependency, tool, incompatible],
|
||||
# and the name is the CurseForge project slug of the other mod.
|
||||
# Example: requiredDependency:railcraft;embeddedLibrary:cofhlib;incompatible:buildcraft
|
||||
# Note: UniMixins is automatically set as a required dependency if usesMixins = true.
|
||||
curseForgeRelations =
|
||||
|
||||
# Optional parameter to customize the produced artifacts. Use this to preserve artifact naming when migrating older
|
||||
# projects. New projects should not use this parameter.
|
||||
# customArchiveBaseName =
|
||||
|
||||
# Optional parameter to customize the default working directory used by the runClient* tasks. Relative to the project directory.
|
||||
# runClientWorkingDirectory = run/client
|
||||
|
||||
# Optional parameter to customize the default working directory used by the runServer* tasks. Relative to the project directory.
|
||||
# runServerWorkingDirectory = run/server
|
||||
|
||||
# Optional parameter to have the build automatically fail if an illegal version is used.
|
||||
# This can be useful if you e.g. only want to allow versions in the form of '1.1.xxx'.
|
||||
# The check is ONLY performed if the version is a git tag.
|
||||
# Note: the specified string must be escaped, so e.g. 1\\.1\\.\\d+ instead of 1\.1\.\d+
|
||||
# versionPattern =
|
||||
|
||||
# Uncomment to prevent the source code from being published.
|
||||
# noPublishedSources = true
|
||||
|
||||
# Uncomment this to disable Spotless checks.
|
||||
# This should only be uncommented to keep it easier to sync with upstream/other forks.
|
||||
# That is, if there is no other active fork/upstream, NEVER change this.
|
||||
# disableSpotless = true
|
||||
|
||||
# Uncomment this to disable Checkstyle checks (currently wildcard import check).
|
||||
# disableCheckstyle = true
|
||||
|
||||
# Override the IDEA build type. Valid values are: "" (leave blank, do not override), "idea" (force use native IDEA build), "gradle"
|
||||
# (force use delegated build).
|
||||
# This is meant to be set in $HOME/.gradle/gradle.properties.
|
||||
# e.g. add "systemProp.org.gradle.project.ideaOverrideBuildType=idea" will override the build type to be native build.
|
||||
# WARNING: If you do use this option, it will overwrite whatever you have in your existing projects. This might not be what you want!
|
||||
# Usually there is no need to uncomment this here as other developers do not necessarily use the same build type as you.
|
||||
# ideaOverrideBuildType = idea
|
||||
|
||||
# Whether IDEA should run spotless checks when pressing the Build button.
|
||||
# This is meant to be set in $HOME/.gradle/gradle.properties.
|
||||
# ideaCheckSpotlessOnBuild = true
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -55,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -83,7 +85,8 @@ done
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -144,7 +147,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
@@ -152,7 +155,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@@ -201,11 +204,11 @@ fi
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
|
||||
Vendored
+12
-10
@@ -13,6 +13,8 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Add any additional repositiroes for your dependencies here
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
}
|
||||
+5
-10
@@ -4,10 +4,10 @@ pluginManagement {
|
||||
maven {
|
||||
// RetroFuturaGradle
|
||||
name "GTNH Maven"
|
||||
url "http://jenkins.usrv.eu:8081/nexus/content/groups/public/"
|
||||
allowInsecureProtocol = true
|
||||
url "https://nexus.gtnewhorizons.com/repository/public/"
|
||||
mavenContent {
|
||||
includeGroup("com.gtnewhorizons.retrofuturagradle")
|
||||
includeGroup("com.gtnewhorizons")
|
||||
includeGroupByRegex("com\\.gtnewhorizons\\..+")
|
||||
}
|
||||
}
|
||||
gradlePluginPortal()
|
||||
@@ -17,12 +17,7 @@ pluginManagement {
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'com.diffplug.blowdryerSetup' version '1.6.0'
|
||||
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.4.0' // Provides java toolchains
|
||||
id 'com.gtnewhorizons.gtnhsettingsconvention' version '1.0.48'
|
||||
}
|
||||
|
||||
blowdryerSetup {
|
||||
repoSubfolder 'gtnhShared'
|
||||
github('GTNewHorizons/ExampleMod1.7.10', 'tag', '0.2.0')
|
||||
//devLocal '.' // Use this when testing config updates locally
|
||||
}
|
||||
|
||||
@@ -1,113 +1,22 @@
|
||||
package codechicken.core;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.util.ChatComponentText;
|
||||
import net.minecraft.util.StatCollector;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
|
||||
import codechicken.core.launch.CodeChickenCorePlugin;
|
||||
import cpw.mods.fml.common.Loader;
|
||||
import cpw.mods.fml.common.versioning.ComparableVersion;
|
||||
import cpw.mods.fml.relauncher.FMLInjectionData;
|
||||
|
||||
public class CCUpdateChecker {
|
||||
|
||||
private static final ArrayList<String> updates = new ArrayList<String>();
|
||||
public static void tick() {}
|
||||
|
||||
private static class ThreadUpdateCheck extends Thread {
|
||||
|
||||
private final URL url;
|
||||
private final Function<String, Void> handler;
|
||||
|
||||
public ThreadUpdateCheck(URL url, Function<String, Void> handler) {
|
||||
this.url = url;
|
||||
this.handler = handler;
|
||||
|
||||
setName("CodeChicken Update Checker");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setConnectTimeout(5000);
|
||||
conn.setReadTimeout(5000);
|
||||
BufferedReader read = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
String ret = read.readLine();
|
||||
read.close();
|
||||
if (ret == null) ret = "";
|
||||
handler.apply(ret);
|
||||
} catch (SocketTimeoutException ignored) {} catch (UnknownHostException ignored) {} catch (IOException iox) {
|
||||
iox.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void tick() {
|
||||
Minecraft mc = Minecraft.getMinecraft();
|
||||
if (!mc.inGameHasFocus) return;
|
||||
|
||||
synchronized (updates) {
|
||||
for (String s : updates) mc.thePlayer.addChatMessage(new ChatComponentText(s));
|
||||
updates.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static void addUpdateMessage(String s) {
|
||||
synchronized (updates) {
|
||||
updates.add(s);
|
||||
}
|
||||
}
|
||||
public static void addUpdateMessage(String s) {}
|
||||
|
||||
public static String mcVersion() {
|
||||
return (String) FMLInjectionData.data()[4];
|
||||
}
|
||||
|
||||
public static void updateCheck(final String mod, final String version) {
|
||||
updateCheck(
|
||||
"http://www.chickenbones.net/Files/notification/version.php?" + "version="
|
||||
+ mcVersion()
|
||||
+ "&"
|
||||
+ "file="
|
||||
+ mod,
|
||||
new Function<String, Void>() {
|
||||
public static void updateCheck(final String mod, final String version) {}
|
||||
|
||||
@Override
|
||||
public Void apply(String ret) {
|
||||
if (!ret.startsWith("Ret: ")) {
|
||||
CodeChickenCorePlugin.logger
|
||||
.error("Failed to check update for " + mod + " returned: " + ret);
|
||||
return null;
|
||||
}
|
||||
ComparableVersion newversion = new ComparableVersion(ret.substring(5));
|
||||
if (newversion.compareTo(new ComparableVersion(version)) > 0) addUpdateMessage(
|
||||
StatCollector.translateToLocalFormatted("codechickencore.update", newversion, mod));
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
public static void updateCheck(String mod) {}
|
||||
|
||||
public static void updateCheck(String mod) {
|
||||
updateCheck(mod, Loader.instance().getIndexedModList().get(mod).getVersion());
|
||||
}
|
||||
|
||||
public static void updateCheck(String url, Function<String, Void> handler) {
|
||||
try {
|
||||
new ThreadUpdateCheck(new URL(url), handler).start();
|
||||
} catch (MalformedURLException e) {
|
||||
CodeChickenCorePlugin.logger.error("Malformed URL: " + url, e);
|
||||
}
|
||||
}
|
||||
public static void updateCheck(String url, Function<String, Void> handler) {}
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
package codechicken.core;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -22,6 +23,7 @@ import cpw.mods.fml.common.Loader;
|
||||
import cpw.mods.fml.common.ModClassLoader;
|
||||
import cpw.mods.fml.relauncher.CoreModManager;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ClassDiscoverer {
|
||||
|
||||
public IStringMatcher matcher;
|
||||
@@ -35,17 +37,12 @@ public class ClassDiscoverer {
|
||||
for (int i = 0; i < superclasses.length; i++)
|
||||
this.superclasses[i] = superclasses[i].getName().replace('.', '/');
|
||||
|
||||
classes = new ArrayList<Class<?>>();
|
||||
classes = new ArrayList<>();
|
||||
modClassLoader = (ModClassLoader) Loader.instance().getModClassLoader();
|
||||
}
|
||||
|
||||
public ClassDiscoverer(Class<?>... superclasses) {
|
||||
this(new IStringMatcher() {
|
||||
|
||||
public boolean matches(String test) {
|
||||
return true;
|
||||
}
|
||||
}, superclasses);
|
||||
this(test -> true, superclasses);
|
||||
}
|
||||
|
||||
public ArrayList<Class<?>> findClasses() {
|
||||
@@ -63,7 +60,7 @@ public class ClassDiscoverer {
|
||||
byte[] bytes = Launch.classLoader.getClassBytes(classname);
|
||||
if (bytes == null) return;
|
||||
|
||||
ClassNode cnode = ASMHelper.createClassNode(bytes);
|
||||
ClassNode cnode = ASMHelper.createClassNode(bytes, ClassReader.SKIP_CODE);
|
||||
for (String superclass : superclasses)
|
||||
if (!cnode.interfaces.contains(superclass) && !cnode.superName.equals(superclass)) return;
|
||||
|
||||
@@ -86,7 +83,7 @@ public class ClassDiscoverer {
|
||||
List<String> knownLibraries = ImmutableList.<String>builder().addAll(modClassLoader.getDefaultLibraries())
|
||||
.addAll(CoreModManager.getLoadedCoremods()).build();
|
||||
File[] minecraftSources = modClassLoader.getParentSources();
|
||||
HashSet<String> searchedSources = new HashSet<String>();
|
||||
HashSet<String> searchedSources = new HashSet<>();
|
||||
for (File minecraftSource : minecraftSources) {
|
||||
if (searchedSources.contains(minecraftSource.getAbsolutePath())) continue;
|
||||
searchedSources.add(minecraftSource.getAbsolutePath());
|
||||
@@ -114,21 +111,18 @@ public class ClassDiscoverer {
|
||||
}
|
||||
|
||||
private void readFromZipFile(File file) throws IOException {
|
||||
FileInputStream fileinputstream = new FileInputStream(file);
|
||||
ZipInputStream zipinputstream = new ZipInputStream(fileinputstream);
|
||||
do {
|
||||
ZipEntry zipentry = zipinputstream.getNextEntry();
|
||||
if (zipentry == null) {
|
||||
break;
|
||||
try (ZipFile zipFile = new ZipFile(file)) {
|
||||
Enumeration<? extends ZipEntry> zipEntries = zipFile.entries();
|
||||
while (zipEntries.hasMoreElements()) {
|
||||
ZipEntry zipentry = zipEntries.nextElement();
|
||||
String fullname = zipentry.getName().replace('\\', '/');
|
||||
int pos = fullname.lastIndexOf('/');
|
||||
String name = pos == -1 ? fullname : fullname.substring(pos + 1);
|
||||
if (!zipentry.isDirectory() && matcher.matches(name)) {
|
||||
checkAddClass(fullname);
|
||||
}
|
||||
}
|
||||
String fullname = zipentry.getName().replace('\\', '/');
|
||||
int pos = fullname.lastIndexOf('/');
|
||||
String name = pos == -1 ? fullname : fullname.substring(pos + 1);
|
||||
if (!zipentry.isDirectory() && matcher.matches(name)) {
|
||||
checkAddClass(fullname);
|
||||
}
|
||||
} while (true);
|
||||
fileinputstream.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void readFromDirectory(File directory, File basedirectory) {
|
||||
|
||||
@@ -5,32 +5,19 @@ import java.lang.reflect.Field;
|
||||
|
||||
import com.google.common.collect.ImmutableBiMap;
|
||||
|
||||
import codechicken.lib.asm.ObfMapping;
|
||||
import cpw.mods.fml.common.asm.transformers.AccessTransformer;
|
||||
import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper;
|
||||
|
||||
// This AccessTransformer will make all minecraft classes public at runtime in MCP just as they are in modloader.
|
||||
// You should ONLY use this when you are testing with a mod that relies on runtime publicity and doesn't include
|
||||
// access transformers. Such mods are doing the wrong thing and should be fixed.
|
||||
public class CodeChickenAccessTransformer extends AccessTransformer {
|
||||
|
||||
private static boolean makeAllPublic;
|
||||
private static Field f_classNameBiMap;
|
||||
private static final Field f_classNameBiMap;
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
private static Object emptyMap = ImmutableBiMap.of();
|
||||
|
||||
public CodeChickenAccessTransformer() throws IOException {
|
||||
super();
|
||||
loadPublicConfig();
|
||||
}
|
||||
|
||||
private void loadPublicConfig() {
|
||||
if (ObfMapping.obfuscated) return;
|
||||
|
||||
makeAllPublic = CodeChickenCoreModContainer.config.getTag("dev.runtimePublic").setComment(
|
||||
"Enabling this setting will make all minecraft classes public at runtime in MCP just as they are in modloader."
|
||||
+ "\nYou should ONLY use this when you are testing with a mod that relies on runtime publicity and doesn't include access transformers."
|
||||
+ "\nSuch mods are doing the wrong thing and should be fixed.")
|
||||
.getBooleanValue(false);
|
||||
|
||||
if (!makeAllPublic) return;
|
||||
|
||||
static {
|
||||
try {
|
||||
f_classNameBiMap = FMLDeobfuscatingRemapper.class.getDeclaredField("classNameBiMap");
|
||||
f_classNameBiMap.setAccessible(true);
|
||||
@@ -39,16 +26,18 @@ public class CodeChickenAccessTransformer extends AccessTransformer {
|
||||
}
|
||||
}
|
||||
|
||||
public CodeChickenAccessTransformer() throws IOException {}
|
||||
|
||||
@Override
|
||||
public byte[] transform(String name, String transformedName, byte[] bytes) {
|
||||
boolean setPublic = makeAllPublic && name.startsWith("net.minecraft.");
|
||||
boolean setPublic = name.startsWith("net.minecraft.");
|
||||
if (setPublic) setClassMap(name);
|
||||
bytes = super.transform(name, transformedName, bytes);
|
||||
if (setPublic) restoreClassMap();
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private void restoreClassMap() {
|
||||
private static void restoreClassMap() {
|
||||
try {
|
||||
f_classNameBiMap.set(FMLDeobfuscatingRemapper.INSTANCE, emptyMap);
|
||||
} catch (Exception e) {
|
||||
@@ -56,7 +45,7 @@ public class CodeChickenAccessTransformer extends AccessTransformer {
|
||||
}
|
||||
}
|
||||
|
||||
private void setClassMap(String name) {
|
||||
private static void setClassMap(String name) {
|
||||
try {
|
||||
f_classNameBiMap.set(FMLDeobfuscatingRemapper.INSTANCE, ImmutableBiMap.of(name.replace('.', '/'), ""));
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package codechicken.core.asm;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -9,12 +8,10 @@ import net.minecraftforge.common.MinecraftForge;
|
||||
import com.google.common.eventbus.EventBus;
|
||||
import com.google.common.eventbus.Subscribe;
|
||||
|
||||
import codechicken.core.CCUpdateChecker;
|
||||
import codechicken.core.ClientUtils;
|
||||
import codechicken.core.featurehack.LiquidTextures;
|
||||
import codechicken.core.internal.CCCEventHandler;
|
||||
import codechicken.core.launch.CodeChickenCorePlugin;
|
||||
import codechicken.lib.config.ConfigFile;
|
||||
import cpw.mods.fml.common.DummyModContainer;
|
||||
import cpw.mods.fml.common.FMLCommonHandler;
|
||||
import cpw.mods.fml.common.LoadController;
|
||||
@@ -27,14 +24,6 @@ import cpw.mods.fml.common.versioning.VersionRange;
|
||||
|
||||
public class CodeChickenCoreModContainer extends DummyModContainer {
|
||||
|
||||
public static ConfigFile config;
|
||||
|
||||
public static void loadConfig() {
|
||||
if (config == null)
|
||||
config = new ConfigFile(new File(CodeChickenCorePlugin.minecraftDir, "config/CodeChickenCore.cfg"))
|
||||
.setComment("CodeChickenCore configuration file.");
|
||||
}
|
||||
|
||||
public CodeChickenCoreModContainer() {
|
||||
super(getModMetadata());
|
||||
}
|
||||
@@ -75,8 +64,6 @@ public class CodeChickenCoreModContainer extends DummyModContainer {
|
||||
@Subscribe
|
||||
public void init(FMLInitializationEvent event) {
|
||||
if (event.getSide().isClient()) {
|
||||
if (config.getTag("checkUpdates").getBooleanValue(true)) CCUpdateChecker.updateCheck(getModId());
|
||||
|
||||
ClientUtils.enhanceSupportersList("CodeChickenCore");
|
||||
|
||||
FMLCommonHandler.instance().bus().register(new CCCEventHandler());
|
||||
|
||||
@@ -7,35 +7,50 @@ import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import net.minecraft.launchwrapper.IClassTransformer;
|
||||
import net.minecraft.launchwrapper.LaunchClassLoader;
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
|
||||
import org.objectweb.asm.tree.ClassNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import codechicken.lib.asm.ASMHelper;
|
||||
import codechicken.lib.asm.ClassHeirachyManager;
|
||||
import codechicken.lib.asm.ObfMapping;
|
||||
import cpw.mods.fml.relauncher.FMLRelaunchLog;
|
||||
|
||||
public class DefaultImplementationTransformer implements IClassTransformer {
|
||||
|
||||
private static LaunchClassLoader cl = (LaunchClassLoader) ClassHeirachyManager.class.getClassLoader();
|
||||
private static final HashMap<String, InterfaceImpl> impls = new HashMap<>();
|
||||
|
||||
private static ClassNode getClassNode(String name) {
|
||||
try {
|
||||
return ASMHelper.createClassNode(cl.getClassBytes(name.replace('/', '.')));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
public static void registerDefaultImpl(String iname, String cname) {
|
||||
if (impls.isEmpty()) {
|
||||
String name = DefaultImplementationTransformer.class.getName();
|
||||
FMLRelaunchLog.finer("Registering transformer %s", name);
|
||||
Launch.classLoader.registerTransformer(name);
|
||||
}
|
||||
impls.put(iname.replace('.', '/'), new InterfaceImpl(iname, cname));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] transform(String name, String transformedName, byte[] bytes) {
|
||||
if (transformedName.startsWith("net.minecraft") || impls.isEmpty()) return bytes;
|
||||
|
||||
ClassNode cnode = ASMHelper.createClassNode(bytes);
|
||||
boolean changed = false;
|
||||
for (String iname : cnode.interfaces) {
|
||||
InterfaceImpl impl = impls.get(iname);
|
||||
if (impl != null) changed |= impl.patch(cnode);
|
||||
}
|
||||
|
||||
return changed ? ASMHelper.createBytes(cnode, 0) : bytes;
|
||||
}
|
||||
|
||||
static class InterfaceImpl {
|
||||
|
||||
public final String iname;
|
||||
public ArrayList<MethodNode> impls = new ArrayList<MethodNode>();
|
||||
public ArrayList<MethodNode> impls = new ArrayList<>();
|
||||
|
||||
public InterfaceImpl(String iname, String cname) {
|
||||
this.iname = iname;
|
||||
HashSet<String> names = new HashSet<String>();
|
||||
HashSet<String> names = new HashSet<>();
|
||||
ClassNode inode = getClassNode(iname);
|
||||
for (MethodNode method : inode.methods) names.add(method.name + method.desc);
|
||||
|
||||
@@ -47,7 +62,7 @@ public class DefaultImplementationTransformer implements IClassTransformer {
|
||||
}
|
||||
|
||||
public boolean patch(ClassNode cnode) {
|
||||
LinkedList<String> names = new LinkedList<String>();
|
||||
LinkedList<String> names = new LinkedList<>();
|
||||
for (MethodNode method : cnode.methods) {
|
||||
ObfMapping m = new ObfMapping(cnode.name, method.name, method.desc).toRuntime();
|
||||
names.add(m.s_name + m.s_desc);
|
||||
@@ -69,25 +84,13 @@ public class DefaultImplementationTransformer implements IClassTransformer {
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
|
||||
private static HashMap<String, InterfaceImpl> impls = new HashMap<String, InterfaceImpl>();
|
||||
|
||||
public static void registerDefaultImpl(String iname, String cname) {
|
||||
impls.put(iname.replace('.', '/'), new InterfaceImpl(iname, cname));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] transform(String name, String transformedName, byte[] bytes) {
|
||||
if (transformedName.startsWith("net.minecraft") || impls.isEmpty()) return bytes;
|
||||
|
||||
ClassNode cnode = ASMHelper.createClassNode(bytes);
|
||||
boolean changed = false;
|
||||
for (String iname : cnode.interfaces) {
|
||||
InterfaceImpl impl = impls.get(iname);
|
||||
if (impl != null) changed |= impl.patch(cnode);
|
||||
private static ClassNode getClassNode(String name) {
|
||||
try {
|
||||
return ASMHelper.createClassNode(Launch.classLoader.getClassBytes(name.replace('/', '.')));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
return changed ? ASMHelper.createBytes(cnode, 0) : bytes;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package codechicken.core.asm;
|
||||
|
||||
import static codechicken.core.launch.CodeChickenCorePlugin.logger;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -11,7 +9,9 @@ import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
import net.minecraft.launchwrapper.IClassTransformer;
|
||||
@@ -21,14 +21,18 @@ import net.minecraft.launchwrapper.LaunchClassLoader;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import codechicken.core.launch.CodeChickenCorePlugin;
|
||||
import cpw.mods.fml.relauncher.FMLRelaunchLog;
|
||||
|
||||
public class DelegatedTransformer implements IClassTransformer {
|
||||
|
||||
private static ArrayList<IClassTransformer> delegatedTransformers;
|
||||
private static Method m_defineClass;
|
||||
private static Field f_cachedClasses;
|
||||
private static final ArrayList<IClassTransformer> delegatedTransformers;
|
||||
private static final Method m_defineClass;
|
||||
private static final Field f_cachedClasses;
|
||||
private static IClassTransformer[] transformers;
|
||||
|
||||
public DelegatedTransformer() {
|
||||
delegatedTransformers = new ArrayList<IClassTransformer>();
|
||||
static {
|
||||
delegatedTransformers = new ArrayList<>();
|
||||
try {
|
||||
m_defineClass = ClassLoader.class
|
||||
.getDeclaredMethod("defineClass", String.class, byte[].class, Integer.TYPE, Integer.TYPE);
|
||||
@@ -41,14 +45,75 @@ public class DelegatedTransformer implements IClassTransformer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] transform(String name, String tname, byte[] bytes) {
|
||||
if (bytes == null) return null;
|
||||
for (IClassTransformer trans : delegatedTransformers) bytes = trans.transform(name, tname, bytes);
|
||||
return bytes;
|
||||
public byte[] transform(String name, String transformedName, byte[] basicClass) {
|
||||
if (basicClass == null) return null;
|
||||
for (IClassTransformer t : transformers) {
|
||||
basicClass = t.transform(name, transformedName, basicClass);
|
||||
}
|
||||
return basicClass;
|
||||
}
|
||||
|
||||
public static void load() {
|
||||
scanModsForDelegatedTransformers();
|
||||
final int size = delegatedTransformers.size();
|
||||
if (size == 0) {
|
||||
CodeChickenCorePlugin.logger
|
||||
.debug("No delegated transformer found, skipping registration of main DelegatedTransformer.");
|
||||
return;
|
||||
}
|
||||
CodeChickenCorePlugin.logger
|
||||
.debug("Found " + size + " delegated transformers, registering main DelegatedTransformer.");
|
||||
transformers = delegatedTransformers.toArray(new IClassTransformer[0]);
|
||||
String name = DelegatedTransformer.class.getName();
|
||||
FMLRelaunchLog.finer("Registering transformer %s", name);
|
||||
Launch.classLoader.registerTransformer(name);
|
||||
}
|
||||
|
||||
private static void scanModsForDelegatedTransformers() {
|
||||
File modsDir = new File(CodeChickenCorePlugin.minecraftDir, "mods");
|
||||
if (modsDir.exists()) {
|
||||
final File[] files = modsDir.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
scanMod(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
File versionModsDir = new File(
|
||||
CodeChickenCorePlugin.minecraftDir,
|
||||
"mods/" + CodeChickenCorePlugin.currentMcVersion);
|
||||
if (versionModsDir.exists()) {
|
||||
final File[] files = versionModsDir.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
scanMod(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void scanMod(File file) {
|
||||
if (!file.getName().endsWith(".jar") && !file.getName().endsWith(".zip")) return;
|
||||
|
||||
try {
|
||||
try (JarFile jar = new JarFile(file)) {
|
||||
Manifest manifest = jar.getManifest();
|
||||
if (manifest == null) return;
|
||||
Attributes attr = manifest.getMainAttributes();
|
||||
if (attr == null) return;
|
||||
|
||||
String transformer = attr.getValue("CCTransformer");
|
||||
if (transformer != null) {
|
||||
addTransformer(transformer, jar, file);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
CodeChickenCorePlugin.logger.error("CodeChickenCore: Failed to read jar file: " + file.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addTransformer(String transformer, JarFile jar, File jarFile) {
|
||||
logger.debug("Adding CCTransformer: " + transformer);
|
||||
CodeChickenCorePlugin.logger.debug("Adding CCTransformer: " + transformer);
|
||||
try {
|
||||
byte[] bytes;
|
||||
bytes = Launch.classLoader.getClassBytes(transformer);
|
||||
@@ -107,7 +172,7 @@ public class DelegatedTransformer implements IClassTransformer {
|
||||
byte[] depbytes = readFully(jar.getInputStream(entry));
|
||||
defineDependancies(depbytes, jar, jarFile, depStack);
|
||||
|
||||
logger.debug("Defining dependancy: " + dependancy);
|
||||
CodeChickenCorePlugin.logger.debug("Defining dependancy: " + dependancy);
|
||||
|
||||
defineClass(dependancy.replace('/', '.'), depbytes);
|
||||
}
|
||||
@@ -128,7 +193,6 @@ public class DelegatedTransformer implements IClassTransformer {
|
||||
while ((r = stream.read()) != -1) {
|
||||
bos.write(r);
|
||||
}
|
||||
|
||||
return bos.toByteArray();
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,6 @@ import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
|
||||
import codechicken.lib.asm.ASMHelper;
|
||||
import codechicken.lib.asm.ASMInit;
|
||||
import codechicken.lib.asm.CC_ClassWriter;
|
||||
import codechicken.lib.asm.ObfMapping;
|
||||
import codechicken.obfuscator.IHeirachyEvaluator;
|
||||
@@ -35,15 +34,11 @@ import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
|
||||
|
||||
public class MCPDeobfuscationTransformer implements IClassTransformer, Opcodes, IHeirachyEvaluator {
|
||||
|
||||
static {
|
||||
ASMInit.init();
|
||||
}
|
||||
|
||||
public static class LoadPlugin implements IFMLLoadingPlugin {
|
||||
|
||||
@Override
|
||||
public String[] getASMTransformerClass() {
|
||||
return new String[0];
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -118,26 +113,20 @@ public class MCPDeobfuscationTransformer implements IClassTransformer, Opcodes,
|
||||
}
|
||||
|
||||
public static void load() {
|
||||
CodeChickenCoreModContainer.loadConfig();
|
||||
run = new ObfuscationRun(
|
||||
false,
|
||||
ObfMapping.MCPRemapper.getConfFiles(),
|
||||
ObfuscationRun.fillDefaults(new HashMap<String, String>()));
|
||||
run.obf.setHeirachyEvaluator(instance);
|
||||
run.setQuiet().parseMappings();
|
||||
Collections.addAll(excludedPackages, run.config.get("excludedPackages").split(";"));
|
||||
|
||||
if (CodeChickenCoreModContainer.config.getTag("dev.deobfuscate")
|
||||
.setComment("set to true to completely deobfuscate mcp names")
|
||||
.getBooleanValue(!ObfMapping.obfuscated)) {
|
||||
run = new ObfuscationRun(
|
||||
false,
|
||||
ObfMapping.MCPRemapper.getConfFiles(),
|
||||
ObfuscationRun.fillDefaults(new HashMap<String, String>()));
|
||||
run.obf.setHeirachyEvaluator(instance);
|
||||
run.setQuiet().parseMappings();
|
||||
Collections.addAll(excludedPackages, run.config.get("excludedPackages").split(";"));
|
||||
|
||||
if (ObfMapping.obfuscated) {
|
||||
ObfMapping.loadMCPRemapper();
|
||||
run.setSeargeConstants();
|
||||
getTransformers().add(instance);
|
||||
} else {
|
||||
getTransformers().add(0, instance); // insert transformer as first.
|
||||
}
|
||||
if (ObfMapping.obfuscated) {
|
||||
ObfMapping.loadMCPRemapper();
|
||||
run.setSeargeConstants();
|
||||
getTransformers().add(instance);
|
||||
} else {
|
||||
getTransformers().add(0, instance); // insert transformer as first.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,14 +5,15 @@ import static codechicken.lib.asm.InsnComparator.findOnce;
|
||||
import java.util.Map;
|
||||
|
||||
import net.minecraft.launchwrapper.IClassTransformer;
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.tree.JumpInsnNode;
|
||||
import org.objectweb.asm.tree.LabelNode;
|
||||
import org.objectweb.asm.tree.MethodNode;
|
||||
|
||||
import codechicken.core.launch.CodeChickenCorePlugin;
|
||||
import codechicken.lib.asm.ASMBlock;
|
||||
import codechicken.lib.asm.ASMInit;
|
||||
import codechicken.lib.asm.ASMReader;
|
||||
import codechicken.lib.asm.ModularASMTransformer;
|
||||
import codechicken.lib.asm.ModularASMTransformer.MethodReplacer;
|
||||
@@ -20,20 +21,29 @@ import codechicken.lib.asm.ModularASMTransformer.MethodTransformer;
|
||||
import codechicken.lib.asm.ModularASMTransformer.MethodWriter;
|
||||
import codechicken.lib.asm.ObfMapping;
|
||||
import codechicken.lib.config.ConfigTag;
|
||||
import cpw.mods.fml.relauncher.FMLRelaunchLog;
|
||||
|
||||
public class TweakTransformer implements IClassTransformer, Opcodes {
|
||||
|
||||
static {
|
||||
ASMInit.init();
|
||||
}
|
||||
|
||||
private static ModularASMTransformer transformer = new ModularASMTransformer();
|
||||
private static Map<String, ASMBlock> blocks = ASMReader.loadResource("/assets/codechickencore/asm/tweaks.asm");
|
||||
private static final ModularASMTransformer transformer = new ModularASMTransformer();
|
||||
private static final Map<String, ASMBlock> blocks = ASMReader
|
||||
.loadResource("/assets/codechickencore/asm/tweaks.asm");
|
||||
public static ConfigTag tweaks;
|
||||
|
||||
public static void load() {
|
||||
CodeChickenCoreModContainer.loadConfig();
|
||||
tweaks = CodeChickenCoreModContainer.config.getTag("tweaks")
|
||||
loadTransformersFromConfig();
|
||||
if (transformer.isEmpty()) {
|
||||
CodeChickenCorePlugin.logger
|
||||
.debug("No tweaks parsed from config, skipping registration of TweakTransformer.");
|
||||
return;
|
||||
}
|
||||
String name = TweakTransformer.class.getName();
|
||||
FMLRelaunchLog.finer("Registering transformer %s", name);
|
||||
Launch.classLoader.registerTransformer(name);
|
||||
}
|
||||
|
||||
private static void loadTransformersFromConfig() {
|
||||
tweaks = CodeChickenCorePlugin.config.getTag("tweaks")
|
||||
.setComment("Various tweaks that can be applied to game mechanics.").useBraces();
|
||||
tweaks.removeTag("persistantLava");
|
||||
|
||||
@@ -103,7 +113,7 @@ public class TweakTransformer implements IClassTransformer, Opcodes {
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] transform(String name, String tname, byte[] bytes) {
|
||||
public final byte[] transform(String name, String tname, byte[] bytes) {
|
||||
return transformer.transform(name, bytes);
|
||||
}
|
||||
}
|
||||
@@ -112,12 +112,12 @@ public abstract class CoreCommand implements ICommand {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<?> getCommandAliases() {
|
||||
public List<String> getCommandAliases() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<?> addTabCompletionOptions(ICommandSender var1, String[] var2) {
|
||||
public List<String> addTabCompletionOptions(ICommandSender var1, String[] var2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package codechicken.core.internal;
|
||||
|
||||
import net.minecraftforge.client.event.GuiScreenEvent;
|
||||
|
||||
import codechicken.core.CCUpdateChecker;
|
||||
import codechicken.core.GuiModListScroll;
|
||||
import cpw.mods.fml.client.GuiModList;
|
||||
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
|
||||
@@ -19,7 +18,6 @@ public class CCCEventHandler {
|
||||
@SubscribeEvent
|
||||
public void clientTick(TickEvent.ClientTickEvent event) {
|
||||
if (event.phase == Phase.END) {
|
||||
CCUpdateChecker.tick();
|
||||
renderTime++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,6 @@ import java.text.CharacterIterator;
|
||||
import java.text.StringCharacterIterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import javax.swing.JEditorPane;
|
||||
import javax.swing.JOptionPane;
|
||||
@@ -21,49 +18,87 @@ import javax.swing.event.HyperlinkListener;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import codechicken.core.asm.CodeChickenCoreModContainer;
|
||||
import codechicken.core.asm.DelegatedTransformer;
|
||||
import codechicken.core.asm.MCPDeobfuscationTransformer;
|
||||
import codechicken.core.asm.Tags;
|
||||
import codechicken.core.asm.TweakTransformer;
|
||||
import codechicken.lib.config.ConfigFile;
|
||||
import codechicken.lib.config.ConfigTag;
|
||||
import cpw.mods.fml.common.FMLCommonHandler;
|
||||
import cpw.mods.fml.common.versioning.DefaultArtifactVersion;
|
||||
import cpw.mods.fml.common.versioning.VersionParser;
|
||||
import cpw.mods.fml.relauncher.CoreModManager;
|
||||
import cpw.mods.fml.relauncher.FMLInjectionData;
|
||||
import cpw.mods.fml.relauncher.IFMLCallHook;
|
||||
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
|
||||
import cpw.mods.fml.relauncher.IFMLLoadingPlugin.MCVersion;
|
||||
import cpw.mods.fml.relauncher.IFMLLoadingPlugin.TransformerExclusions;
|
||||
|
||||
@TransformerExclusions(value = { "codechicken.core.asm", "codechicken.obfuscator" })
|
||||
@MCVersion("1.7.10")
|
||||
public class CodeChickenCorePlugin implements IFMLLoadingPlugin, IFMLCallHook {
|
||||
@IFMLLoadingPlugin.MCVersion("1.7.10")
|
||||
@IFMLLoadingPlugin.TransformerExclusions({ "codechicken.core.asm", "codechicken.obfuscator", "codechicken.lib.asm",
|
||||
"codechicken.lib.config" })
|
||||
public class CodeChickenCorePlugin implements IFMLLoadingPlugin {
|
||||
|
||||
public static final String mcVersion = "[1.7.10]";
|
||||
|
||||
@Deprecated
|
||||
public static final String version = Tags.VERSION;
|
||||
|
||||
public static ConfigFile config;
|
||||
public static File minecraftDir;
|
||||
public static String currentMcVersion;
|
||||
public static Logger logger = LogManager.getLogger("CodeChickenCore");
|
||||
|
||||
public CodeChickenCorePlugin() {
|
||||
if (minecraftDir != null) return; // get called twice, once for IFMLCallHook
|
||||
|
||||
if (minecraftDir != null) return;
|
||||
minecraftDir = (File) FMLInjectionData.data()[6];
|
||||
currentMcVersion = (String) FMLInjectionData.data()[4];
|
||||
config = new ConfigFile(new File(minecraftDir, "config/CodeChickenCore.cfg"))
|
||||
.setComment("CodeChickenCore configuration file.");
|
||||
if (Boolean.getBoolean("ccc.dev.deobfuscate")) {
|
||||
injectDeobfPlugin();
|
||||
}
|
||||
}
|
||||
|
||||
DepLoader.load();
|
||||
injectDeobfPlugin();
|
||||
@Override
|
||||
public String[] getASMTransformerClass() {
|
||||
versionCheck(mcVersion, "CodeChickenCore");
|
||||
return new String[] { "codechicken.lib.asm.ClassHeirachyManager", "codechicken.lib.asm.RedirectorTransformer" };
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccessTransformerClass() {
|
||||
if (Boolean.getBoolean("ccc.dev.runtimePublic")) {
|
||||
return "codechicken.core.asm.CodeChickenAccessTransformer";
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModContainerClass() {
|
||||
return "codechicken.core.asm.CodeChickenCoreModContainer";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSetupClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectData(Map<String, Object> data) {
|
||||
ConfigTag checkRAM = CodeChickenCorePlugin.config.getTag("checks")
|
||||
.setComment("Configuration options for checking various requirements for a modpack.").useBraces();
|
||||
if (checkRAM.getTag("checkRAM")
|
||||
.setComment("If set to true, check RAM available for Minecraft before continuing to load")
|
||||
.getBooleanValue(false)) {
|
||||
systemCheck(checkRAM);
|
||||
}
|
||||
TweakTransformer.load();
|
||||
DelegatedTransformer.load();
|
||||
}
|
||||
|
||||
private void injectDeobfPlugin() {
|
||||
try {
|
||||
Class<?> wrapperClass = Class.forName("cpw.mods.fml.relauncher.CoreModManager$FMLPluginWrapper");
|
||||
Constructor wrapperConstructor = wrapperClass
|
||||
Constructor<?> wrapperConstructor = wrapperClass
|
||||
.getConstructor(String.class, IFMLLoadingPlugin.class, File.class, Integer.TYPE, String[].class);
|
||||
Field f_loadPlugins = CoreModManager.class.getDeclaredField("loadPlugins");
|
||||
wrapperConstructor.setAccessible(true);
|
||||
@@ -77,7 +112,7 @@ public class CodeChickenCorePlugin implements IFMLLoadingPlugin, IFMLCallHook {
|
||||
0,
|
||||
new String[0]));
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to inject MCPDeobfuscation Transformer", e);
|
||||
logger.error("Failed to inject FMLPluginWrapper for MCPDeobfuscation Transformer", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,74 +222,4 @@ public class CodeChickenCorePlugin implements IFMLLoadingPlugin, IFMLCallHook {
|
||||
FMLCommonHandler.instance().exitJava(-98, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getASMTransformerClass() {
|
||||
versionCheck(mcVersion, "CodeChickenCore");
|
||||
return new String[] { "codechicken.lib.asm.ClassHeirachyManager", "codechicken.core.asm.TweakTransformer",
|
||||
"codechicken.core.asm.DelegatedTransformer", "codechicken.core.asm.DefaultImplementationTransformer",
|
||||
"codechicken.lib.asm.RedirectorTransformer" };
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccessTransformerClass() {
|
||||
return "codechicken.core.asm.CodeChickenAccessTransformer";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModContainerClass() {
|
||||
return "codechicken.core.asm.CodeChickenCoreModContainer";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSetupClass() {
|
||||
return getClass().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectData(Map<String, Object> data) {}
|
||||
|
||||
@Override
|
||||
public Void call() {
|
||||
CodeChickenCoreModContainer.loadConfig();
|
||||
ConfigTag checkRAM;
|
||||
checkRAM = CodeChickenCoreModContainer.config.getTag("checks")
|
||||
.setComment("Configuration options for checking various requirements for a modpack.").useBraces();
|
||||
if (checkRAM.getTag("checkRAM")
|
||||
.setComment("If set to true, check RAM available for Minecraft before continuing to load")
|
||||
.getBooleanValue(false))
|
||||
systemCheck(checkRAM);
|
||||
TweakTransformer.load();
|
||||
scanCodeChickenMods();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void scanCodeChickenMods() {
|
||||
File modsDir = new File(minecraftDir, "mods");
|
||||
for (File file : modsDir.listFiles()) scanMod(file);
|
||||
File versionModsDir = new File(minecraftDir, "mods/" + currentMcVersion);
|
||||
if (versionModsDir.exists()) for (File file : versionModsDir.listFiles()) scanMod(file);
|
||||
}
|
||||
|
||||
private void scanMod(File file) {
|
||||
if (!file.getName().endsWith(".jar") && !file.getName().endsWith(".zip")) return;
|
||||
|
||||
try {
|
||||
JarFile jar = new JarFile(file);
|
||||
try {
|
||||
Manifest manifest = jar.getManifest();
|
||||
if (manifest == null) return;
|
||||
Attributes attr = manifest.getMainAttributes();
|
||||
if (attr == null) return;
|
||||
|
||||
String transformer = attr.getValue("CCTransformer");
|
||||
if (transformer != null) DelegatedTransformer.addTransformer(transformer, jar, file);
|
||||
} finally {
|
||||
jar.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("CodeChickenCore: Failed to read jar file: " + file.getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,641 +0,0 @@
|
||||
package codechicken.core.launch;
|
||||
|
||||
import java.awt.Desktop;
|
||||
import java.awt.Dialog.ModalityType;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import javax.swing.Box;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JEditorPane;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JProgressBar;
|
||||
import javax.swing.WindowConstants;
|
||||
import javax.swing.event.HyperlinkEvent;
|
||||
import javax.swing.event.HyperlinkListener;
|
||||
|
||||
import net.minecraft.launchwrapper.LaunchClassLoader;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import cpw.mods.fml.common.versioning.ComparableVersion;
|
||||
import cpw.mods.fml.relauncher.FMLInjectionData;
|
||||
import cpw.mods.fml.relauncher.FMLLaunchHandler;
|
||||
import cpw.mods.fml.relauncher.IFMLCallHook;
|
||||
import cpw.mods.fml.relauncher.IFMLLoadingPlugin;
|
||||
import cpw.mods.fml.relauncher.IFMLLoadingPlugin.MCVersion;
|
||||
import sun.misc.URLClassPath;
|
||||
import sun.net.util.URLUtil;
|
||||
|
||||
/**
|
||||
* For autodownloading stuff. This is really unoriginal, mostly ripped off FML, credits to cpw.
|
||||
*/
|
||||
@MCVersion("1.7.10")
|
||||
public class DepLoader implements IFMLLoadingPlugin, IFMLCallHook {
|
||||
|
||||
private static ByteBuffer downloadBuffer = ByteBuffer.allocateDirect(1 << 23);
|
||||
private static final String owner = "CB's DepLoader";
|
||||
private static DepLoadInst inst;
|
||||
|
||||
public interface IDownloadDisplay {
|
||||
|
||||
void resetProgress(int sizeGuess);
|
||||
|
||||
void setPokeThread(Thread currentThread);
|
||||
|
||||
void updateProgress(int fullLength);
|
||||
|
||||
boolean shouldStopIt();
|
||||
|
||||
void updateProgressString(String string, Object... data);
|
||||
|
||||
Object makeDialog();
|
||||
|
||||
void showErrorDialog(String name, String url);
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public static class Downloader extends JOptionPane implements IDownloadDisplay {
|
||||
|
||||
private JDialog container;
|
||||
private JLabel currentActivity;
|
||||
private JProgressBar progress;
|
||||
boolean stopIt;
|
||||
Thread pokeThread;
|
||||
|
||||
private Box makeProgressPanel() {
|
||||
Box box = Box.createVerticalBox();
|
||||
box.add(Box.createRigidArea(new Dimension(0, 10)));
|
||||
JLabel welcomeLabel = new JLabel(
|
||||
"<html><b><font size='+1'>" + owner
|
||||
+ " is setting up your minecraft environment</font></b></html>");
|
||||
box.add(welcomeLabel);
|
||||
welcomeLabel.setAlignmentY(LEFT_ALIGNMENT);
|
||||
welcomeLabel = new JLabel(
|
||||
"<html>Please wait, " + owner + " has some tasks to do before you can play</html>");
|
||||
welcomeLabel.setAlignmentY(LEFT_ALIGNMENT);
|
||||
box.add(welcomeLabel);
|
||||
box.add(Box.createRigidArea(new Dimension(0, 10)));
|
||||
currentActivity = new JLabel("Currently doing ...");
|
||||
box.add(currentActivity);
|
||||
box.add(Box.createRigidArea(new Dimension(0, 10)));
|
||||
progress = new JProgressBar(0, 100);
|
||||
progress.setStringPainted(true);
|
||||
box.add(progress);
|
||||
box.add(Box.createRigidArea(new Dimension(0, 30)));
|
||||
return box;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JDialog makeDialog() {
|
||||
if (container != null) return container;
|
||||
|
||||
setMessageType(JOptionPane.INFORMATION_MESSAGE);
|
||||
setMessage(makeProgressPanel());
|
||||
setOptions(new Object[] { "Stop" });
|
||||
addPropertyChangeListener(new PropertyChangeListener() {
|
||||
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt) {
|
||||
if (evt.getSource() == Downloader.this && evt.getPropertyName() == VALUE_PROPERTY) {
|
||||
requestClose("This will stop minecraft from launching\nAre you sure you want to do this?");
|
||||
}
|
||||
}
|
||||
});
|
||||
container = new JDialog(null, "Hello", ModalityType.MODELESS);
|
||||
container.setResizable(false);
|
||||
container.setLocationRelativeTo(null);
|
||||
container.add(this);
|
||||
this.updateUI();
|
||||
container.pack();
|
||||
container.setMinimumSize(container.getPreferredSize());
|
||||
container.setVisible(true);
|
||||
container.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
|
||||
container.addWindowListener(new WindowAdapter() {
|
||||
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
requestClose(
|
||||
"Closing this window will stop minecraft from launching\nAre you sure you wish to do this?");
|
||||
}
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
protected void requestClose(String message) {
|
||||
int shouldClose = JOptionPane.showConfirmDialog(
|
||||
container,
|
||||
message,
|
||||
"Are you sure you want to stop?",
|
||||
JOptionPane.YES_NO_OPTION,
|
||||
JOptionPane.WARNING_MESSAGE);
|
||||
if (shouldClose == JOptionPane.YES_OPTION) container.dispose();
|
||||
|
||||
stopIt = true;
|
||||
if (pokeThread != null) pokeThread.interrupt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgressString(String progressUpdate, Object... data) {
|
||||
// FMLLog.finest(progressUpdate, data);
|
||||
if (currentActivity != null) currentActivity.setText(String.format(progressUpdate, data));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetProgress(int sizeGuess) {
|
||||
if (progress != null) progress.getModel().setRangeProperties(0, 0, 0, sizeGuess, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress(int fullLength) {
|
||||
if (progress != null) progress.getModel().setValue(fullLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPokeThread(Thread currentThread) {
|
||||
this.pokeThread = currentThread;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldStopIt() {
|
||||
return stopIt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showErrorDialog(String name, String url) {
|
||||
JEditorPane ep = new JEditorPane(
|
||||
"text/html",
|
||||
"<html>" + owner
|
||||
+ " was unable to download required library "
|
||||
+ name
|
||||
+ "<br>Check your internet connection and try restarting or download it manually from"
|
||||
+ "<br><a href=\""
|
||||
+ url
|
||||
+ "\">"
|
||||
+ url
|
||||
+ "</a> and put it in your mods folder"
|
||||
+ "</html>");
|
||||
|
||||
ep.setEditable(false);
|
||||
ep.setOpaque(false);
|
||||
ep.addHyperlinkListener(new HyperlinkListener() {
|
||||
|
||||
@Override
|
||||
public void hyperlinkUpdate(HyperlinkEvent event) {
|
||||
try {
|
||||
if (event.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED))
|
||||
Desktop.getDesktop().browse(event.getURL().toURI());
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
});
|
||||
|
||||
JOptionPane.showMessageDialog(null, ep, "A download error has occurred", JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DummyDownloader implements IDownloadDisplay {
|
||||
|
||||
@Override
|
||||
public void resetProgress(int sizeGuess) {}
|
||||
|
||||
@Override
|
||||
public void setPokeThread(Thread currentThread) {}
|
||||
|
||||
@Override
|
||||
public void updateProgress(int fullLength) {}
|
||||
|
||||
@Override
|
||||
public boolean shouldStopIt() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgressString(String string, Object... data) {}
|
||||
|
||||
@Override
|
||||
public Object makeDialog() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showErrorDialog(String name, String url) {}
|
||||
}
|
||||
|
||||
public static class VersionedFile {
|
||||
|
||||
public final Pattern pattern;
|
||||
public final String filename;
|
||||
public final ComparableVersion version;
|
||||
public final String name;
|
||||
|
||||
public VersionedFile(String filename, Pattern pattern) {
|
||||
this.pattern = pattern;
|
||||
this.filename = filename;
|
||||
Matcher m = pattern.matcher(filename);
|
||||
if (m.matches()) {
|
||||
name = m.group(1);
|
||||
version = new ComparableVersion(m.group(2));
|
||||
} else {
|
||||
name = null;
|
||||
version = null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean matches() {
|
||||
return name != null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Dependency {
|
||||
|
||||
public String url;
|
||||
public VersionedFile file;
|
||||
|
||||
public String existing;
|
||||
/**
|
||||
* Flag set to add this dep to the classpath immediately because it is required for a coremod.
|
||||
*/
|
||||
public boolean coreLib;
|
||||
|
||||
public Dependency(String url, VersionedFile file, boolean coreLib) {
|
||||
this.url = url;
|
||||
this.file = file;
|
||||
this.coreLib = coreLib;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DepLoadInst {
|
||||
|
||||
private File modsDir;
|
||||
private File v_modsDir;
|
||||
private IDownloadDisplay downloadMonitor;
|
||||
private JDialog popupWindow;
|
||||
|
||||
private Map<String, Dependency> depMap = new HashMap<String, Dependency>();
|
||||
private HashSet<String> depSet = new HashSet<String>();
|
||||
|
||||
public DepLoadInst() {
|
||||
String mcVer = (String) FMLInjectionData.data()[4];
|
||||
File mcDir = (File) FMLInjectionData.data()[6];
|
||||
|
||||
modsDir = new File(mcDir, "mods");
|
||||
v_modsDir = new File(mcDir, "mods/" + mcVer);
|
||||
if (!v_modsDir.exists()) v_modsDir.mkdirs();
|
||||
}
|
||||
|
||||
private void addClasspath(String name) {
|
||||
try {
|
||||
((LaunchClassLoader) DepLoader.class.getClassLoader())
|
||||
.addURL(new File(v_modsDir, name).toURI().toURL());
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteMod(File mod) {
|
||||
if (mod.delete()) return;
|
||||
|
||||
try {
|
||||
ClassLoader cl = DepLoader.class.getClassLoader();
|
||||
URL url = mod.toURI().toURL();
|
||||
Field f_ucp = URLClassLoader.class.getDeclaredField("ucp");
|
||||
Field f_loaders = URLClassPath.class.getDeclaredField("loaders");
|
||||
Field f_lmap = URLClassPath.class.getDeclaredField("lmap");
|
||||
f_ucp.setAccessible(true);
|
||||
f_loaders.setAccessible(true);
|
||||
f_lmap.setAccessible(true);
|
||||
|
||||
URLClassPath ucp = (URLClassPath) f_ucp.get(cl);
|
||||
Closeable loader = ((Map<String, Closeable>) f_lmap.get(ucp)).remove(URLUtil.urlNoFragString(url));
|
||||
if (loader != null) {
|
||||
loader.close();
|
||||
((List<?>) f_loaders.get(ucp)).remove(loader);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (!mod.delete()) {
|
||||
mod.deleteOnExit();
|
||||
String msg = owner + " was unable to delete file "
|
||||
+ mod.getPath()
|
||||
+ " the game will now try to delete it on exit. If this dialog appears again, delete it manually.";
|
||||
CodeChickenCorePlugin.logger.error(msg);
|
||||
if (!GraphicsEnvironment.isHeadless())
|
||||
JOptionPane.showMessageDialog(null, msg, "An update error has occurred", JOptionPane.ERROR_MESSAGE);
|
||||
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private void download(Dependency dep) {
|
||||
popupWindow = (JDialog) downloadMonitor.makeDialog();
|
||||
File libFile = new File(v_modsDir, dep.file.filename);
|
||||
try {
|
||||
URL libDownload = new URL(dep.url + '/' + dep.file.filename);
|
||||
downloadMonitor.updateProgressString("Downloading file %s", libDownload.toString());
|
||||
CodeChickenCorePlugin.logger.debug("Downloading file " + libDownload);
|
||||
URLConnection connection = libDownload.openConnection();
|
||||
connection.setConnectTimeout(5000);
|
||||
connection.setReadTimeout(5000);
|
||||
connection.setRequestProperty("User-Agent", "" + owner + " Downloader");
|
||||
int sizeGuess = connection.getContentLength();
|
||||
download(connection.getInputStream(), sizeGuess, libFile);
|
||||
downloadMonitor.updateProgressString("Download complete");
|
||||
CodeChickenCorePlugin.logger.trace("Download complete");
|
||||
|
||||
scanDepInfo(libFile);
|
||||
} catch (Exception e) {
|
||||
libFile.delete();
|
||||
if (downloadMonitor.shouldStopIt()) {
|
||||
CodeChickenCorePlugin.logger
|
||||
.error("You have stopped the downloading operation before it could complete");
|
||||
System.exit(1);
|
||||
return;
|
||||
}
|
||||
downloadMonitor.showErrorDialog(dep.file.filename, dep.url + '/' + dep.file.filename);
|
||||
throw new RuntimeException("A download error occured", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void download(InputStream is, int sizeGuess, File target) throws Exception {
|
||||
if (sizeGuess > downloadBuffer.capacity()) throw new Exception(
|
||||
String.format(
|
||||
"The file %s is too large to be downloaded by " + owner + " - the download is invalid",
|
||||
target.getName()));
|
||||
|
||||
downloadBuffer.clear();
|
||||
|
||||
int bytesRead, fullLength = 0;
|
||||
|
||||
downloadMonitor.resetProgress(sizeGuess);
|
||||
try {
|
||||
downloadMonitor.setPokeThread(Thread.currentThread());
|
||||
byte[] smallBuffer = new byte[1024];
|
||||
while ((bytesRead = is.read(smallBuffer)) >= 0) {
|
||||
downloadBuffer.put(smallBuffer, 0, bytesRead);
|
||||
fullLength += bytesRead;
|
||||
if (downloadMonitor.shouldStopIt()) {
|
||||
break;
|
||||
}
|
||||
downloadMonitor.updateProgress(fullLength);
|
||||
}
|
||||
is.close();
|
||||
downloadMonitor.setPokeThread(null);
|
||||
downloadBuffer.limit(fullLength);
|
||||
downloadBuffer.position(0);
|
||||
} catch (InterruptedIOException e) {
|
||||
// We were interrupted by the stop button. We're stopping now.. clear interruption flag.
|
||||
Thread.interrupted();
|
||||
throw new Exception("Stop");
|
||||
} catch (IOException e) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
try {
|
||||
/*
|
||||
* String cksum = generateChecksum(downloadBuffer); if (cksum.equals(validationHash)) {
|
||||
*/
|
||||
if (!target.exists()) target.createNewFile();
|
||||
|
||||
downloadBuffer.position(0);
|
||||
FileOutputStream fos = new FileOutputStream(target);
|
||||
fos.getChannel().write(downloadBuffer);
|
||||
fos.close();
|
||||
/*
|
||||
* } else { throw new RuntimeException(String.
|
||||
* format("The downloaded file %s has an invalid checksum %s (expecting %s). The download did not succeed correctly and the file has been deleted. Please try launching again."
|
||||
* , target.getName(), cksum, validationHash)); }
|
||||
*/
|
||||
} catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private String checkExisting(Dependency dep) {
|
||||
for (File f : modsDir.listFiles()) {
|
||||
VersionedFile vfile = new VersionedFile(f.getName(), dep.file.pattern);
|
||||
if (!vfile.matches() || !vfile.name.equals(dep.file.name)) continue;
|
||||
|
||||
if (f.renameTo(new File(v_modsDir, f.getName()))) continue;
|
||||
|
||||
deleteMod(f);
|
||||
}
|
||||
|
||||
for (File f : v_modsDir.listFiles()) {
|
||||
VersionedFile vfile = new VersionedFile(f.getName(), dep.file.pattern);
|
||||
if (!vfile.matches() || !vfile.name.equals(dep.file.name)) continue;
|
||||
|
||||
int cmp = vfile.version.compareTo(dep.file.version);
|
||||
if (cmp < 0) {
|
||||
CodeChickenCorePlugin.logger.warn("Deleted old version " + f.getName());
|
||||
deleteMod(f);
|
||||
return null;
|
||||
}
|
||||
if (cmp > 0) {
|
||||
CodeChickenCorePlugin.logger.warn(
|
||||
"Warning: version of " + dep.file.name
|
||||
+ ", "
|
||||
+ vfile.version
|
||||
+ " is newer than request "
|
||||
+ dep.file.version);
|
||||
return f.getName();
|
||||
}
|
||||
return f.getName(); // found dependency
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void load() {
|
||||
scanDepInfos();
|
||||
if (depMap.isEmpty()) return;
|
||||
|
||||
loadDeps();
|
||||
activateDeps();
|
||||
}
|
||||
|
||||
private void activateDeps() {
|
||||
for (Dependency dep : depMap.values()) if (dep.coreLib) addClasspath(dep.existing);
|
||||
}
|
||||
|
||||
private void loadDeps() {
|
||||
downloadMonitor = FMLLaunchHandler.side().isClient() ? new Downloader() : new DummyDownloader();
|
||||
try {
|
||||
while (!depSet.isEmpty()) {
|
||||
Iterator<String> it = depSet.iterator();
|
||||
Dependency dep = depMap.get(it.next());
|
||||
it.remove();
|
||||
load(dep);
|
||||
}
|
||||
} finally {
|
||||
if (popupWindow != null) {
|
||||
popupWindow.setVisible(false);
|
||||
popupWindow.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void load(Dependency dep) {
|
||||
dep.existing = checkExisting(dep);
|
||||
if (dep.existing == null) // download dep
|
||||
{
|
||||
download(dep);
|
||||
dep.existing = dep.file.filename;
|
||||
}
|
||||
}
|
||||
|
||||
private List<File> modFiles() {
|
||||
List<File> list = new LinkedList<File>();
|
||||
list.addAll(Arrays.asList(modsDir.listFiles()));
|
||||
list.addAll(Arrays.asList(v_modsDir.listFiles()));
|
||||
return list;
|
||||
}
|
||||
|
||||
private void scanDepInfos() {
|
||||
for (File file : modFiles()) {
|
||||
if (!file.getName().endsWith(".jar") && !file.getName().endsWith(".zip")) continue;
|
||||
|
||||
scanDepInfo(file);
|
||||
}
|
||||
}
|
||||
|
||||
private void scanDepInfo(File file) {
|
||||
try {
|
||||
ZipFile zip = new ZipFile(file);
|
||||
ZipEntry e = zip.getEntry("dependancies.info");
|
||||
if (e == null) e = zip.getEntry("dependencies.info");
|
||||
if (e != null) loadJSon(zip.getInputStream(e));
|
||||
zip.close();
|
||||
} catch (Exception e) {
|
||||
CodeChickenCorePlugin.logger
|
||||
.error("Failed to load dependencies.info from " + file.getName() + " as JSON");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void loadJSon(InputStream input) throws IOException {
|
||||
InputStreamReader reader = new InputStreamReader(input);
|
||||
JsonElement root = new JsonParser().parse(reader);
|
||||
if (root.isJsonArray()) loadJSonArr(root);
|
||||
else loadJson(root.getAsJsonObject());
|
||||
reader.close();
|
||||
}
|
||||
|
||||
private void loadJSonArr(JsonElement root) throws IOException {
|
||||
for (JsonElement node : root.getAsJsonArray()) loadJson(node.getAsJsonObject());
|
||||
}
|
||||
|
||||
private void loadJson(JsonObject node) throws IOException {
|
||||
boolean obfuscated = ((LaunchClassLoader) DepLoader.class.getClassLoader())
|
||||
.getClassBytes("net.minecraft.world.World") == null;
|
||||
|
||||
String testClass = node.get("class").getAsString();
|
||||
if (DepLoader.class.getResource("/" + testClass.replace('.', '/') + ".class") != null) return;
|
||||
|
||||
String repo = node.get("repo").getAsString();
|
||||
String filename = node.get("file").getAsString();
|
||||
if (!obfuscated && node.has("dev")) filename = node.get("dev").getAsString();
|
||||
|
||||
boolean coreLib = node.has("coreLib") && node.get("coreLib").getAsBoolean();
|
||||
|
||||
Pattern pattern = null;
|
||||
try {
|
||||
if (node.has("pattern")) pattern = Pattern.compile(node.get("pattern").getAsString());
|
||||
} catch (PatternSyntaxException e) {
|
||||
CodeChickenCorePlugin.logger.error("Invalid filename pattern: " + node.get("pattern"));
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (pattern == null) pattern = Pattern.compile("(\\w+).*?([\\d\\.]+)[-\\w]*\\.[^\\d]+");
|
||||
|
||||
VersionedFile file = new VersionedFile(filename, pattern);
|
||||
if (!file.matches()) throw new RuntimeException("Invalid filename format for dependency: " + filename);
|
||||
|
||||
addDep(new Dependency(repo, file, coreLib));
|
||||
}
|
||||
|
||||
private void addDep(Dependency newDep) {
|
||||
if (mergeNew(depMap.get(newDep.file.name), newDep)) {
|
||||
depMap.put(newDep.file.name, newDep);
|
||||
depSet.add(newDep.file.name);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean mergeNew(Dependency oldDep, Dependency newDep) {
|
||||
if (oldDep == null) return true;
|
||||
|
||||
Dependency newest = newDep.file.version.compareTo(oldDep.file.version) > 0 ? newDep : oldDep;
|
||||
newest.coreLib = newDep.coreLib || oldDep.coreLib;
|
||||
|
||||
return newest == newDep;
|
||||
}
|
||||
}
|
||||
|
||||
public static void load() {
|
||||
if (inst == null) {
|
||||
inst = new DepLoadInst();
|
||||
inst.load();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getASMTransformerClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModContainerClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSetupClass() {
|
||||
return getClass().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void injectData(Map<String, Object> data) {}
|
||||
|
||||
@Override
|
||||
public Void call() {
|
||||
load();
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAccessTransformerClass() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -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)]) {
|
||||
continue 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,173 @@
|
||||
package codechicken.lib.asm;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.minecraft.launchwrapper.IClassTransformer;
|
||||
import net.minecraft.launchwrapper.Launch;
|
||||
|
||||
import org.objectweb.asm.ClassReader;
|
||||
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 {
|
||||
|
||||
private static final Map<String, ClassInfo> superclasses = new ConcurrentHashMap<>(1000);
|
||||
|
||||
static {
|
||||
superclasses.put("java.lang.Object", ClassInfo.OBJECT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] transform(String name, String transformedName, byte[] bytes) {
|
||||
if (bytes == null) return null;
|
||||
if (!superclasses.containsKey(transformedName)) {
|
||||
declareASM(transformedName, bytes);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static ClassInfo declareASM(String name, byte[] bytes) {
|
||||
final ClassNode node = ASMHelper.createClassNode(bytes, ClassReader.SKIP_CODE);
|
||||
final ClassInfo classInfo = new ClassInfo(node);
|
||||
superclasses.put(name, classInfo);
|
||||
return classInfo;
|
||||
}
|
||||
|
||||
private static ClassInfo declareReflection(String name) throws ClassNotFoundException {
|
||||
final Class<?> aclass = Class.forName(name);
|
||||
final ClassInfo classInfo = new ClassInfo(aclass);
|
||||
superclasses.put(name, classInfo);
|
||||
return classInfo;
|
||||
}
|
||||
|
||||
private static ClassInfo declareClass(String name) {
|
||||
try {
|
||||
byte[] bytes = Launch.classLoader.getClassBytes(unKey(name));
|
||||
if (bytes != null) {
|
||||
return declareASM(name, bytes);
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
try {
|
||||
return declareReflection(name);
|
||||
} catch (ClassNotFoundException ignored) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String toKey(String name) {
|
||||
if (ObfMapping.obfuscated) {
|
||||
return FMLDeobfuscatingRemapper.INSTANCE.map(name.replace('.', '/')).replace('/', '.');
|
||||
}
|
||||
return name.replace('/', '.');
|
||||
}
|
||||
|
||||
private static String unKey(String name) {
|
||||
if (ObfMapping.obfuscated) {
|
||||
return FMLDeobfuscatingRemapper.INSTANCE.unmap(name.replace('.', '/')).replace('/', '.');
|
||||
}
|
||||
return name.replace('/', '.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
ClassInfo classInfo = superclasses.get(name);
|
||||
if (classInfo == null) classInfo = declareClass(name);
|
||||
if (classInfo == null) return false;
|
||||
|
||||
return classInfo.hasSuper(superclass);
|
||||
}
|
||||
|
||||
public static String getSuperClass(String name, boolean runtime) {
|
||||
name = toKey(name);
|
||||
|
||||
ClassInfo classInfo = superclasses.get(name);
|
||||
if (classInfo == null) classInfo = declareClass(name);
|
||||
if (classInfo == null || classInfo.superclass == null) return "java.lang.Object";
|
||||
|
||||
final String s = classInfo.superclass;
|
||||
if (!runtime) {
|
||||
return FMLDeobfuscatingRemapper.INSTANCE.unmap(s);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
private static class ClassInfo {
|
||||
|
||||
public static final ClassInfo OBJECT = new ClassInfo();
|
||||
|
||||
public final String superclass;
|
||||
public final String[] interfaces;
|
||||
public ClassInfo parent;
|
||||
|
||||
private ClassInfo() {
|
||||
this.superclass = null;
|
||||
this.interfaces = null;
|
||||
}
|
||||
|
||||
public ClassInfo(ClassNode node) {
|
||||
if ("java/lang/Object".equals(node.superName)) {
|
||||
superclass = "java.lang.Object";
|
||||
parent = OBJECT;
|
||||
} else {
|
||||
superclass = toKey(node.superName);
|
||||
}
|
||||
if (node.interfaces.isEmpty()) {
|
||||
interfaces = null;
|
||||
} else {
|
||||
interfaces = new String[node.interfaces.size()];
|
||||
for (int i = 0; i < node.interfaces.size(); i++) {
|
||||
interfaces[i] = toKey(node.interfaces.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ClassInfo(Class<?> aclass) {
|
||||
if (aclass.isInterface()) {
|
||||
this.superclass = "java.lang.Object";
|
||||
this.parent = OBJECT;
|
||||
} else {
|
||||
final Class<?> superclass = aclass.getSuperclass();
|
||||
if (superclass == null || "java.lang.Object".equals(superclass.getName())) {
|
||||
this.superclass = "java.lang.Object";
|
||||
this.parent = OBJECT;
|
||||
} else {
|
||||
this.superclass = toKey(superclass.getName());
|
||||
}
|
||||
}
|
||||
final Class<?>[] interfaces = aclass.getInterfaces();
|
||||
if (interfaces.length == 0) {
|
||||
this.interfaces = null;
|
||||
} else {
|
||||
this.interfaces = new String[interfaces.length];
|
||||
for (int i = 0; i < interfaces.length; i++) {
|
||||
this.interfaces[i] = toKey(interfaces[i].getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasSuper(String superclass) {
|
||||
if (this.superclass == null) return false;
|
||||
if (this.superclass.equals(superclass)) return true;
|
||||
ClassInfo parentInfo = this.parent;
|
||||
if (parentInfo == null) parentInfo = superclasses.get(this.superclass);
|
||||
if (parentInfo == null) parentInfo = declareClass(this.superclass);
|
||||
if (parentInfo == null) return false;
|
||||
this.parent = parentInfo;
|
||||
return parentInfo.hasSuper(superclass);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,297 @@
|
||||
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<>();
|
||||
|
||||
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 boolean isEmpty() {
|
||||
return transformers.isEmpty();
|
||||
}
|
||||
|
||||
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,205 @@
|
||||
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.Textifier;
|
||||
import org.objectweb.asm.util.TraceClassVisitor;
|
||||
|
||||
import codechicken.core.launch.CodeChickenCorePlugin;
|
||||
|
||||
public class RedirectorTransformer implements IClassTransformer, Opcodes {
|
||||
|
||||
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",
|
||||
"normalAttrib",
|
||||
"colourAttrib",
|
||||
"lightCoordAttrib",
|
||||
"sideAttrib",
|
||||
"lightingAttrib"
|
||||
|
||||
);
|
||||
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;
|
||||
|
||||
// spotless:off
|
||||
for (MethodNode mn : cn.methods) {
|
||||
for (AbstractInsnNode node : mn.instructions.toArray()) {
|
||||
if (node instanceof FieldInsnNode fNode) {
|
||||
if (node.getOpcode() == GETSTATIC && RenderStateClass.equals(fNode.owner) && redirectedFields.contains(fNode.name)) {
|
||||
mn.instructions.insertBefore(fNode,
|
||||
new MethodInsnNode(
|
||||
INVOKESTATIC,
|
||||
fNode.owner,
|
||||
"instance",
|
||||
"()Lcodechicken/lib/render/CCRenderState;",
|
||||
false));
|
||||
fNode.setOpcode(GETFIELD);
|
||||
changed = true;
|
||||
} else if (node.getOpcode() == PUTSTATIC && RenderStateClass.equals(fNode.owner) && redirectedFields.contains(fNode.name)) {
|
||||
InsnList list = new InsnList();
|
||||
list.add(new MethodInsnNode(
|
||||
INVOKESTATIC,
|
||||
fNode.owner,
|
||||
"instance",
|
||||
"()Lcodechicken/lib/render/CCRenderState;",
|
||||
false));
|
||||
list.add(new InsnNode(SWAP));
|
||||
mn.instructions.insertBefore(fNode, list);
|
||||
fNode.setOpcode(PUTFIELD);
|
||||
changed = true;
|
||||
}
|
||||
} else if (node instanceof MethodInsnNode mNode) {
|
||||
if (node.getOpcode() == INVOKESTATIC && RenderStateClass.equals(mNode.owner) && redirectedSimpleMethods.contains(mNode.name)) {
|
||||
mn.instructions.insertBefore(mNode,
|
||||
new MethodInsnNode(
|
||||
INVOKESTATIC,
|
||||
mNode.owner,
|
||||
"instance",
|
||||
"()Lcodechicken/lib/render/CCRenderState;",
|
||||
false));
|
||||
mNode.setOpcode(INVOKEVIRTUAL);
|
||||
mNode.name = mNode.name + "Instance";
|
||||
changed = true;
|
||||
} else if (node.getOpcode() == INVOKEVIRTUAL && RenderStateClass.equals(mNode.owner)
|
||||
&& (redirectedSimpleMethods.contains(mNode.name) || redirectedMethods.contains(mNode.name))) {
|
||||
// Handle mods that updated to previously new API
|
||||
mNode.name = mNode.name + "Instance";
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// spotless:on
|
||||
|
||||
if (changed) {
|
||||
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
cn.accept(cw);
|
||||
final byte[] bytes = cw.toByteArray();
|
||||
if (DUMP_CLASSES) {
|
||||
saveTransformedClass(basicClass, transformedName + "_PRE");
|
||||
saveTransformedClass(bytes, transformedName + "_POST");
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
return basicClass;
|
||||
}
|
||||
|
||||
private File outputDir = null;
|
||||
|
||||
private void saveTransformedClass(final byte[] data, final String classname) {
|
||||
if (outputDir == null) {
|
||||
outputDir = new File(Launch.minecraftHome, "ASM_CCL" + File.separatorChar + "REDIRECTOR");
|
||||
try {
|
||||
FileUtils.deleteDirectory(outputDir);
|
||||
} catch (IOException ignored) {}
|
||||
if (!outputDir.exists()) {
|
||||
// noinspection ResultOfMethodCallIgnored
|
||||
outputDir.mkdirs();
|
||||
}
|
||||
}
|
||||
final String fileName = classname.replace('.', File.separatorChar);
|
||||
final File classFile = new File(outputDir, fileName + ".class");
|
||||
final File bytecodeFile = new File(outputDir, fileName + "_BYTE.txt");
|
||||
final File outDir = classFile.getParentFile();
|
||||
if (!outDir.exists()) {
|
||||
// noinspection ResultOfMethodCallIgnored
|
||||
outDir.mkdirs();
|
||||
}
|
||||
if (classFile.exists()) {
|
||||
// noinspection ResultOfMethodCallIgnored
|
||||
classFile.delete();
|
||||
}
|
||||
try (final OutputStream output = Files.newOutputStream(classFile.toPath())) {
|
||||
output.write(data);
|
||||
CodeChickenCorePlugin.logger.info("Saved class (byte[]) to " + classFile.toPath());
|
||||
} catch (IOException e) {
|
||||
CodeChickenCorePlugin.logger.error("Could not save class (byte[]) " + classname);
|
||||
}
|
||||
if (bytecodeFile.exists()) {
|
||||
// noinspection ResultOfMethodCallIgnored
|
||||
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);
|
||||
CodeChickenCorePlugin.logger.info("Saved class (bytecode) to " + bytecodeFile.toPath());
|
||||
} catch (IOException e) {
|
||||
CodeChickenCorePlugin.logger.error("Could not save class (bytecode) " + classname);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,220 @@
|
||||
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.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
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 Integer.compare(o1.position, o2.position);
|
||||
if (o1.brace != o2.brace) return o1.brace ? 1 : -1; // braced one goes after
|
||||
|
||||
if (sortMode == 1) {
|
||||
if (Objects.equals(o1.value, o2.value)) return 0;
|
||||
if (o1.value == null) return 1;
|
||||
if (o2.value == null) return -1;
|
||||
return o1.value.compareTo(o2.value);
|
||||
} else {
|
||||
return o1.name.compareTo(o2.name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The immediate child tags of this tag
|
||||
*/
|
||||
private final TreeMap<String, ConfigTag> childtags = new TreeMap<>();
|
||||
/**
|
||||
* A map of all tags in the tree, meaning all tags in this tag and all tags in child tags (recursively). Allows for
|
||||
* quick access to any tag in the tree, without the need to traverse the tree part by part.
|
||||
*/
|
||||
private final Map<String, ConfigTag> allTagsInTree = new HashMap<>();
|
||||
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 allTagsInTree.get(tagname) != null;
|
||||
}
|
||||
|
||||
public ConfigTag getNewTag(String tagname) {
|
||||
return new ConfigTag(this, tagname);
|
||||
}
|
||||
|
||||
public ConfigTag getTag(String tagname, boolean create) {
|
||||
ConfigTag tag = allTagsInTree.get(tagname);
|
||||
if (tag == null && create) return getTagRecursive(tagname, create);
|
||||
return tag;
|
||||
}
|
||||
|
||||
public ConfigTag getTagRecursive(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) {
|
||||
removeChildFromFlatMap(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);
|
||||
|
||||
addChildToFlatMap(tag.name, tag);
|
||||
}
|
||||
|
||||
private void addChildToFlatMap(String path, ConfigTag tag) {
|
||||
allTagsInTree.put(path, tag);
|
||||
|
||||
// Stupid cast but these two garbage classes are interlinked anyway
|
||||
if ((this instanceof ConfigTag t) && t.parent != null) t.parent.addChildToFlatMap(t.name + "." + path, tag);
|
||||
}
|
||||
|
||||
private void removeChildFromFlatMap(String path) {
|
||||
allTagsInTree.remove(path);
|
||||
|
||||
// Stupid cast but these two garbage classes are interlinked anyway
|
||||
if ((this instanceof ConfigTag t) && t.parent != null) t.parent.removeChildFromFlatMap(t.name + "." + path);
|
||||
}
|
||||
|
||||
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.resetInstance();
|
||||
state.startDrawingInstance();
|
||||
|
||||
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.drawInstance();
|
||||
}
|
||||
|
||||
public void draw(int x, int y, int w, int h) {
|
||||
draw(CCRenderState.instance(), x, y, w, h);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
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) {
|
||||
drawMultilineTip(font, x, y, list, 0xf0100010, 0xf0100010, 0x505000ff, 0x5028007F);
|
||||
}
|
||||
|
||||
public static void drawMultilineTip(FontRenderer font, int x, int y, List<String> list, int bgStart, int bgEnd,
|
||||
int borderStart, int borderEnd) {
|
||||
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, bgStart, bgEnd, borderStart, borderEnd);
|
||||
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) {
|
||||
drawTooltipBox(x, y, w, h, 0xf0100010, 0xf0100010, 0x505000ff, 0x5028007F);
|
||||
}
|
||||
|
||||
public static void drawTooltipBox(int x, int y, int w, int h, int bgStart, int bgEnd, int borderStart,
|
||||
int borderEnd) {
|
||||
// spotless:off
|
||||
// draw background
|
||||
drawGradientRect(x + 1, y, w - 1, 1, bgStart, bgStart); // top
|
||||
drawGradientRect(x + 1, y + h, w - 1, 1, bgEnd, bgEnd); // bottom
|
||||
drawGradientRect(x + 1, y + 1, w - 1, h - 1, bgStart, bgEnd); // center
|
||||
drawGradientRect(x, y + 1, 1, h - 1, bgStart, bgEnd); // left
|
||||
drawGradientRect(x + w, y + 1, 1, h - 1, bgStart, bgEnd); // right
|
||||
// draw inner border
|
||||
drawGradientRect(x + 1, y + 2, 1, h - 3, borderStart, borderEnd); // left
|
||||
drawGradientRect(x + w - 1, y + 2, 1, h - 3, borderStart, borderEnd); // right
|
||||
drawGradientRect(x + 1, y + 1, w - 1, 1, borderStart, borderStart); // top
|
||||
drawGradientRect(x + 1, y + h - 1, w - 1, 1, borderEnd, borderEnd); // bottom
|
||||
// spotless:on
|
||||
}
|
||||
}
|
||||
@@ -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.setColourInstance(ColourRGBA.multiplyC(state.colour, f));
|
||||
state.setBrightnessInstance((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.setColourInstance(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.setBrightnessInstance(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.setColourInstance(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.setBrightnessInstance(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.setModelInstance(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.setVertexRangeInstance(s * 4, (s + 1) * 4);
|
||||
state.renderInstance();
|
||||
}
|
||||
}
|
||||
|
||||
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.setModelInstance(face);
|
||||
for (int s = 0; s < 6; s++) if ((sideMask & 1 << s) == 0) {
|
||||
face.loadCuboidFace(bounds, s);
|
||||
state.renderInstance();
|
||||
}
|
||||
}
|
||||
|
||||
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,92 @@
|
||||
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.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++;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static Matrix4 getRenderMatrix(Vector3 position, Rotation rotation, double scale) {
|
||||
return new Matrix4().translate(position).scale(scale).apply(rotation);
|
||||
}
|
||||
|
||||
public static Matrix4 getRenderMatrix(double posX, double posY, double posZ, Rotation rotation, double scale) {
|
||||
return new Matrix4().translate(posX, posY, posZ).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.renderInstance();
|
||||
}
|
||||
}
|
||||
|
||||
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,593 @@
|
||||
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() {
|
||||
return instances.get().normalAttrib;
|
||||
}
|
||||
|
||||
public static VertexAttribute<int[]> colourAttrib() {
|
||||
return instances.get().colourAttrib;
|
||||
}
|
||||
|
||||
public static VertexAttribute<LC[]> lightCoordAttrib() {
|
||||
return instances.get().lightCoordAttrib;
|
||||
}
|
||||
|
||||
public static VertexAttribute<int[]> sideAttrib() {
|
||||
return instances.get().sideAttrib;
|
||||
}
|
||||
|
||||
public static VertexAttribute<int[]> lightingAttrib() {
|
||||
return instances.get().lightingAttrib;
|
||||
}
|
||||
|
||||
public 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.setNormalInstance(normalRef[state.vertexIndex]);
|
||||
else state.setNormalInstance(Rotation.axes[state.side]);
|
||||
}
|
||||
};
|
||||
public 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.setColourInstance(ColourRGBA.multiply(state.baseColour, colourRef[state.vertexIndex]));
|
||||
else state.setColourInstance(state.baseColour);
|
||||
}
|
||||
};
|
||||
public 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.setColourInstance(ColourRGBA.multiply(state.colour, colourRef[state.vertexIndex]));
|
||||
}
|
||||
};
|
||||
public 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 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;
|
||||
|
||||
// attribute storage
|
||||
public int side;
|
||||
public LC lc = new LC();
|
||||
|
||||
public static void reset() {
|
||||
instance().resetInstance();
|
||||
}
|
||||
|
||||
public void resetInstance() {
|
||||
model = null;
|
||||
pipeline.reset();
|
||||
useNormals = hasNormal = hasBrightness = hasColour = false;
|
||||
useColour = computeLighting = true;
|
||||
baseColour = alphaOverride = -1;
|
||||
}
|
||||
|
||||
public void setPipelineInstance(IVertexOperation... ops) {
|
||||
pipeline.setPipeline(ops);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void setPipeline(IVertexOperation... ops) {
|
||||
instance().setPipelineInstance(ops);
|
||||
}
|
||||
|
||||
public void setPipelineInstance(IVertexSource model, int start, int end, IVertexOperation... ops) {
|
||||
pipeline.reset();
|
||||
setModelInstance(model, start, end);
|
||||
pipeline.setPipeline(ops);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void setPipeline(IVertexSource model, int start, int end, IVertexOperation... ops) {
|
||||
instance().setPipelineInstance(model, start, end, ops);
|
||||
}
|
||||
|
||||
public void bindModelInstance(IVertexSource model) {
|
||||
if (this.model != model) {
|
||||
this.model = model;
|
||||
pipeline.rebuild();
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void bindModel(IVertexSource model) {
|
||||
instance().bindModelInstance(model);
|
||||
}
|
||||
|
||||
public void setModelInstance(IVertexSource source) {
|
||||
setModelInstance(source, 0, source.getVertices().length);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void setModel(IVertexSource source) {
|
||||
instance().setModelInstance(source);
|
||||
}
|
||||
|
||||
public void setModelInstance(IVertexSource source, int start, int end) {
|
||||
bindModelInstance(source);
|
||||
setVertexRangeInstance(start, end);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void setModel(IVertexSource source, int start, int end) {
|
||||
instance().setModelInstance(source, start, end);
|
||||
}
|
||||
|
||||
public void setVertexRangeInstance(int start, int end) {
|
||||
firstVertexIndex = start;
|
||||
lastVertexIndex = end;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void setVertexRange(int start, int end) {
|
||||
instance().setVertexRangeInstance(start, end);
|
||||
}
|
||||
|
||||
public void renderInstance(IVertexOperation... ops) {
|
||||
setPipelineInstance(ops);
|
||||
renderInstance();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void render(IVertexOperation... ops) {
|
||||
instance().renderInstance(ops);
|
||||
}
|
||||
|
||||
public void renderInstance() {
|
||||
Vertex5[] verts = model.getVertices();
|
||||
for (vertexIndex = firstVertexIndex; vertexIndex < lastVertexIndex; vertexIndex++) {
|
||||
model.prepareVertex(this);
|
||||
vert.set(verts[vertexIndex]);
|
||||
runPipelineInstance();
|
||||
writeVertInstance();
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void render() {
|
||||
instance().renderInstance();
|
||||
}
|
||||
|
||||
public void runPipelineInstance() {
|
||||
pipeline.operate();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void runPipeline() {
|
||||
instance().runPipelineInstance();
|
||||
}
|
||||
|
||||
public void writeVertInstance() {
|
||||
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 writeVert() {
|
||||
instance().writeVertInstance();
|
||||
}
|
||||
|
||||
public void setNormalInstance(double x, double y, double z) {
|
||||
hasNormal = true;
|
||||
normal.set(x, y, z);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void setNormal(double x, double y, double z) {
|
||||
instance().setNormalInstance(x, y, z);
|
||||
}
|
||||
|
||||
public void setNormalInstance(Vector3 n) {
|
||||
hasNormal = true;
|
||||
normal.set(n);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void setNormal(Vector3 n) {
|
||||
instance().setNormalInstance(n);
|
||||
}
|
||||
|
||||
public void setColourInstance(int c) {
|
||||
hasColour = true;
|
||||
colour = c;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void setColour(int c) {
|
||||
instance().setColourInstance(c);
|
||||
}
|
||||
|
||||
public void setBrightnessInstance(int b) {
|
||||
hasBrightness = true;
|
||||
brightness = b;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void setBrightness(int b) {
|
||||
instance().setBrightnessInstance(b);
|
||||
}
|
||||
|
||||
public void setBrightnessInstance(IBlockAccess world, int x, int y, int z) {
|
||||
setBrightnessInstance(world.getBlock(x, y, z).getMixedBrightnessForBlock(world, x, y, z));
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void setBrightness(IBlockAccess world, int x, int y, int z) {
|
||||
instance().setBrightnessInstance(world, x, y, z);
|
||||
}
|
||||
|
||||
public static void pullLightmap() {
|
||||
instance().pullLightmapInstance();
|
||||
}
|
||||
|
||||
public void pullLightmapInstance() {
|
||||
setBrightnessInstance((int) OpenGlHelper.lastBrightnessY << 16 | (int) OpenGlHelper.lastBrightnessX);
|
||||
}
|
||||
|
||||
public static void pushLightmap() {
|
||||
instance().pushLightmapInstance();
|
||||
}
|
||||
|
||||
public void pushLightmapInstance() {
|
||||
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, brightness & 0xFFFF, brightness >>> 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compact helper for setting dynamic rendering context. Uses normals and doesn't compute lighting
|
||||
*/
|
||||
public static void setDynamic() {
|
||||
instance().setDynamicInstance();
|
||||
}
|
||||
|
||||
public void setDynamicInstance() {
|
||||
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 startDrawingInstance() {
|
||||
startDrawingInstance(7);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void startDrawing() {
|
||||
instance().startDrawingInstance();
|
||||
}
|
||||
|
||||
public void startDrawingInstance(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 startDrawing(int mode) {
|
||||
instance().startDrawingInstance(mode);
|
||||
}
|
||||
|
||||
public static void draw() {
|
||||
Tessellator.instance.draw();
|
||||
}
|
||||
|
||||
public void drawInstance() {
|
||||
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.setColourInstance(-1);
|
||||
return false;
|
||||
}
|
||||
|
||||
state.pipeline.addDependency(state.colourAttrib);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void operate(CCRenderState state) {
|
||||
state.setColourInstance(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,429 @@
|
||||
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.minecraft.util.MathHelper;
|
||||
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 RenderItem uniformRenderItem;
|
||||
static EntityItem entityItem;
|
||||
|
||||
static {
|
||||
uniformRenderItem = new RenderItem() {
|
||||
|
||||
public boolean shouldBob() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
uniformRenderItem.setRenderManager(RenderManager.instance);
|
||||
entityItem = new EntityItem(null);
|
||||
entityItem.hoverStart = 0;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static void renderFluidQuad(Vector3 point1, Vector3 point2, Vector3 point3, Vector3 point4, IIcon icon,
|
||||
double res) {
|
||||
// spotless:off
|
||||
renderFluidQuad(
|
||||
point2.x, point2.y, point2.z,
|
||||
point4.x - point1.x, point4.y - point1.y, point4.z - point1.z,
|
||||
point1.x - point2.x, point1.y - point2.y, point1.z - point2.z,
|
||||
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
|
||||
*/
|
||||
@Deprecated
|
||||
public static void renderFluidQuad(Vector3 base, Vector3 wide, Vector3 high, IIcon icon, double res) {
|
||||
renderFluidQuad(
|
||||
base.x, base.y, base.z,
|
||||
wide.x, wide.y, wide.z,
|
||||
wide.x, wide.y, wide.z,
|
||||
icon, res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a tessellated quadrilateral bottom to top, left to right
|
||||
* <p>
|
||||
* base : The bottom left corner of the quad
|
||||
* <p>
|
||||
* wide : The bottom of the quad
|
||||
* <p>
|
||||
* high : The left side of the quad
|
||||
* <p>
|
||||
* res : Units per icon
|
||||
*/
|
||||
public static void renderFluidQuad(
|
||||
double baseX, double baseY, double baseZ,
|
||||
double wideX, double wideY, double wideZ,
|
||||
double highX, double highY, double highZ,
|
||||
IIcon icon, double res) {
|
||||
|
||||
Tessellator tessellator = Tessellator.instance;
|
||||
|
||||
double u = icon.getMinU();
|
||||
double du = icon.getMaxU() - icon.getMinU();
|
||||
double v = icon.getMinV();
|
||||
double dv = icon.getMaxV() - icon.getMinV();
|
||||
|
||||
double wideLen = Math.sqrt(wideX * wideX + wideY * wideY + wideZ * wideZ);
|
||||
double highLen = Math.sqrt(highX * highX + highY * highY + highZ * highZ);
|
||||
|
||||
double x = 0;
|
||||
while (x < wideLen) {
|
||||
double rx = wideLen - x;
|
||||
if (rx > res) rx = res;
|
||||
|
||||
double y = 0;
|
||||
while (y < highLen) {
|
||||
double ry = highLen - y;
|
||||
if (ry > res) ry = res;
|
||||
|
||||
final double mult1 = x / wideLen;
|
||||
double dx1X = wideX * mult1;
|
||||
double dx1Y = wideY * mult1;
|
||||
double dx1Z = wideZ * mult1;
|
||||
|
||||
final double mult2 = (x + rx) / wideLen;
|
||||
double dx2X = wideX * mult2;
|
||||
double dx2Y = wideY * mult2;
|
||||
double dx2Z = wideZ * mult2;
|
||||
|
||||
final double mult3 = y / highLen;
|
||||
double dy1X = highX * mult3;
|
||||
double dy1Y = highY * mult3;
|
||||
double dy1Z = highZ * mult3;
|
||||
|
||||
final double mult4 = (y + ry) / highLen;
|
||||
double dy2X = highX * mult4;
|
||||
double dy2Y = highY * mult4;
|
||||
double dy2Z = highZ * mult4;
|
||||
|
||||
tessellator.addVertexWithUV(
|
||||
baseX + dx1X + dy2X,
|
||||
baseY + dx1Y + dy2Y,
|
||||
baseZ + dx1Z + dy2Z,
|
||||
u,
|
||||
v + ry / res * dv);
|
||||
tessellator.addVertexWithUV(
|
||||
baseX + dx1X + dy1X,
|
||||
baseY + dx1Y + dy1Y,
|
||||
baseZ + dx1Z + dy1Z,
|
||||
u,
|
||||
v);
|
||||
tessellator.addVertexWithUV(
|
||||
baseX + dx2X + dy1X,
|
||||
baseY + dx2Y + dy1Y,
|
||||
baseZ + dx2Z + dy1Z,
|
||||
u + rx / res * du,
|
||||
v);
|
||||
tessellator.addVertexWithUV(
|
||||
baseX + dx2X + dy2X,
|
||||
baseY + dx2Y + dy2Y,
|
||||
baseZ + dx2Z + dy2Z,
|
||||
u + rx / res * du,
|
||||
v + ry / res * dv);
|
||||
|
||||
y += ry;
|
||||
}
|
||||
|
||||
x += rx;
|
||||
}
|
||||
}
|
||||
|
||||
public static void translateToWorldCoords(Entity entity, float partialTicks) {
|
||||
double interpPosX = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * partialTicks;
|
||||
double interpPosY = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * partialTicks;
|
||||
double interpPosZ = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * partialTicks;
|
||||
GL11.glTranslated(-interpPosX, -interpPosY, -interpPosZ);
|
||||
}
|
||||
|
||||
public static void drawCuboidOutline(Cuboid6 c) {
|
||||
Tessellator tess = Tessellator.instance;
|
||||
tess.startDrawing(3);
|
||||
tess.addVertex(c.min.x, c.min.y, c.min.z);
|
||||
tess.addVertex(c.max.x, c.min.y, c.min.z);
|
||||
tess.addVertex(c.max.x, c.min.y, c.max.z);
|
||||
tess.addVertex(c.min.x, c.min.y, c.max.z);
|
||||
tess.addVertex(c.min.x, c.min.y, c.min.z);
|
||||
tess.draw();
|
||||
tess.startDrawing(3);
|
||||
tess.addVertex(c.min.x, c.max.y, c.min.z);
|
||||
tess.addVertex(c.max.x, c.max.y, c.min.z);
|
||||
tess.addVertex(c.max.x, c.max.y, c.max.z);
|
||||
tess.addVertex(c.min.x, c.max.y, c.max.z);
|
||||
tess.addVertex(c.min.x, c.max.y, c.min.z);
|
||||
tess.draw();
|
||||
tess.startDrawing(1);
|
||||
tess.addVertex(c.min.x, c.min.y, c.min.z);
|
||||
tess.addVertex(c.min.x, c.max.y, c.min.z);
|
||||
tess.addVertex(c.max.x, c.min.y, c.min.z);
|
||||
tess.addVertex(c.max.x, c.max.y, c.min.z);
|
||||
tess.addVertex(c.max.x, c.min.y, c.max.z);
|
||||
tess.addVertex(c.max.x, c.max.y, c.max.z);
|
||||
tess.addVertex(c.min.x, c.min.y, c.max.z);
|
||||
tess.addVertex(c.min.x, c.max.y, c.max.z);
|
||||
tess.draw();
|
||||
}
|
||||
|
||||
public static void renderFluidCuboid(CCRenderState state, Cuboid6 bound, IIcon tex, double res) {
|
||||
renderFluidCuboid(bound, tex, res);
|
||||
}
|
||||
|
||||
public static void renderFluidCuboid(Cuboid6 bound, IIcon tex, double res) {
|
||||
final double minX = bound.min.x;
|
||||
final double minY = bound.min.y;
|
||||
final double minZ = bound.min.z;
|
||||
final double maxX = bound.max.x;
|
||||
final double maxY = bound.max.y;
|
||||
final double maxZ = bound.max.z;
|
||||
renderFluidQuad( // bottom
|
||||
minX, minY, minZ,
|
||||
maxX - minX, 0, 0,
|
||||
0, 0, maxZ - minZ,
|
||||
tex, res);
|
||||
renderFluidQuad( // top
|
||||
minX, maxY, minZ,
|
||||
0, 0, maxZ - minZ,
|
||||
maxX - minX, 0, 0,
|
||||
tex, res);
|
||||
renderFluidQuad( // -x
|
||||
minX, maxY, minZ,
|
||||
0, minY - maxY, 0,
|
||||
0, 0, maxZ - minZ,
|
||||
tex, res);
|
||||
renderFluidQuad( // +x
|
||||
maxX, maxY, maxZ,
|
||||
0, minY - maxY, 0,
|
||||
0, 0, minZ - maxZ,
|
||||
tex, res);
|
||||
renderFluidQuad( // -z
|
||||
maxX, maxY, minZ,
|
||||
0, minY - maxY, 0,
|
||||
minX - maxX, 0, 0,
|
||||
tex, res);
|
||||
renderFluidQuad( // +z
|
||||
minX, maxY, maxZ,
|
||||
0, minY - maxY, 0,
|
||||
maxX - minX, 0, 0,
|
||||
tex, res);
|
||||
}
|
||||
// spotless:on
|
||||
|
||||
public static void renderBlockOverlaySide(int x, int y, int z, int side, double tx1, double tx2, double ty1,
|
||||
double ty2) {
|
||||
Tessellator tessellator = Tessellator.instance;
|
||||
final double minX = x - 0.009;
|
||||
final double maxX = x + 1.009;
|
||||
final double minY = y - 0.009;
|
||||
final double maxY = y + 1.009;
|
||||
final double minZ = z - 0.009;
|
||||
final double maxZ = z + 1.009;
|
||||
switch (side) {
|
||||
case 0:
|
||||
tessellator.addVertexWithUV(minX, minY, minZ, tx1, ty1);
|
||||
tessellator.addVertexWithUV(maxX, minY, minZ, tx2, ty1);
|
||||
tessellator.addVertexWithUV(maxX, minY, maxZ, tx2, ty2);
|
||||
tessellator.addVertexWithUV(minX, minY, maxZ, tx1, ty2);
|
||||
break;
|
||||
case 1:
|
||||
tessellator.addVertexWithUV(maxX, maxY, minZ, tx2, ty1);
|
||||
tessellator.addVertexWithUV(minX, maxY, minZ, tx1, ty1);
|
||||
tessellator.addVertexWithUV(minX, maxY, maxZ, tx1, ty2);
|
||||
tessellator.addVertexWithUV(maxX, maxY, maxZ, tx2, ty2);
|
||||
break;
|
||||
case 2:
|
||||
tessellator.addVertexWithUV(minX, maxY, minZ, tx2, ty1);
|
||||
tessellator.addVertexWithUV(maxX, maxY, minZ, tx1, ty1);
|
||||
tessellator.addVertexWithUV(maxX, minY, minZ, tx1, ty2);
|
||||
tessellator.addVertexWithUV(minX, minY, minZ, tx2, ty2);
|
||||
break;
|
||||
case 3:
|
||||
tessellator.addVertexWithUV(maxX, maxY, maxZ, tx2, ty1);
|
||||
tessellator.addVertexWithUV(minX, maxY, maxZ, tx1, ty1);
|
||||
tessellator.addVertexWithUV(minX, minY, maxZ, tx1, ty2);
|
||||
tessellator.addVertexWithUV(maxX, minY, maxZ, tx2, ty2);
|
||||
break;
|
||||
case 4:
|
||||
tessellator.addVertexWithUV(minX, maxY, maxZ, tx2, ty1);
|
||||
tessellator.addVertexWithUV(minX, maxY, minZ, tx1, ty1);
|
||||
tessellator.addVertexWithUV(minX, minY, minZ, tx1, ty2);
|
||||
tessellator.addVertexWithUV(minX, minY, maxZ, tx2, ty2);
|
||||
break;
|
||||
case 5:
|
||||
tessellator.addVertexWithUV(maxX, maxY, minZ, tx2, ty1);
|
||||
tessellator.addVertexWithUV(maxX, maxY, maxZ, tx1, ty1);
|
||||
tessellator.addVertexWithUV(maxX, minY, maxZ, tx1, ty2);
|
||||
tessellator.addVertexWithUV(maxX, minY, minZ, 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.setColourInstance(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 d) {
|
||||
return Math.pow(d, 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 fillRate/bound.height. If the fluid is a gas, it will render the full box with an alpha equal to fillRate.
|
||||
* Warning, bound will be mutated if the fluid is a liquid
|
||||
*
|
||||
* @param stack The fluid to render.
|
||||
* @param bound The bounding box within which the fluid is contained.
|
||||
* @param fillRate The volume of fluid / the capacity of the tank. This is a double between 0 and 1.
|
||||
* @param res The resolution to render at.
|
||||
*/
|
||||
public static void renderFluidCuboid(CCRenderState state, FluidStack stack, Cuboid6 bound, double fillRate,
|
||||
double res) {
|
||||
if (!shouldRenderFluid(stack)) return;
|
||||
|
||||
fillRate = MathHelper.clamp_double(fillRate, 0d, 1d);
|
||||
int alpha = 255;
|
||||
if (stack.getFluid().isGaseous()) {
|
||||
alpha = (int) (fluidDensityToAlpha(fillRate) * 255);
|
||||
} else {
|
||||
bound.max.y = bound.min.y + (bound.max.y - bound.min.y) * fillRate;
|
||||
}
|
||||
|
||||
IIcon tex = prepareFluidRender(state, stack, alpha);
|
||||
state.startDrawingInstance();
|
||||
renderFluidCuboid(bound, tex, res);
|
||||
state.drawInstance();
|
||||
postFluidRender();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a fluid within a bounding box. If the fluid is a liquid it will render as a normal tank with height equal
|
||||
* to fillRate/bound.height. If the fluid is a gas, it will render the full box with an alpha equal to fillRate.
|
||||
* Warning, bound will be mutated if the fluid is a liquid
|
||||
*
|
||||
* @param stack The fluid to render.
|
||||
* @param bound The bounding box within which the fluid is contained.
|
||||
* @param fillRate The volume of fluid / the capacity of the tank. This is a double between 0 and 1.
|
||||
* @param res The resolution to render at.
|
||||
*/
|
||||
public static void renderFluidCuboid(FluidStack stack, Cuboid6 bound, double fillRate, double res) {
|
||||
renderFluidCuboid(CCRenderState.instance(), stack, bound, fillRate, res);
|
||||
}
|
||||
|
||||
public static void renderFluidGauge(CCRenderState state, FluidStack stack, Rectangle4i rect, double fillRate,
|
||||
double res) {
|
||||
if (!shouldRenderFluid(stack)) return;
|
||||
|
||||
fillRate = MathHelper.clamp_double(fillRate, 0d, 1d);
|
||||
int alpha = 255;
|
||||
if (stack.getFluid().isGaseous()) {
|
||||
alpha = (int) (fluidDensityToAlpha(fillRate) * 255);
|
||||
} else {
|
||||
int height = (int) (rect.h * fillRate);
|
||||
rect.y += rect.h - height;
|
||||
rect.h = height;
|
||||
}
|
||||
|
||||
IIcon tex = prepareFluidRender(state, stack, alpha);
|
||||
state.startDrawingInstance();
|
||||
renderFluidQuad(rect.x, rect.y + rect.h, 0, rect.w, 0, 0, 0, -rect.h, 0, tex, res);
|
||||
state.drawInstance();
|
||||
postFluidRender();
|
||||
}
|
||||
|
||||
public static void renderFluidGauge(FluidStack stack, Rectangle4i rect, double fillRate, double res) {
|
||||
renderFluidGauge(CCRenderState.instance(), stack, rect, fillRate, 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);
|
||||
}
|
||||
}
|
||||
Loaded 100 of 142 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user