├── .gitignore ├── .gitmodules ├── .travis.yml ├── LICENSE ├── README.md ├── TODO ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── java └── squeek │ └── applecore │ ├── AppleCore.java │ ├── ModInfo.java │ ├── api │ ├── AppleCoreAPI.java │ ├── IAppleCoreAccessor.java │ ├── IAppleCoreMutator.java │ ├── IAppleCoreRegistry.java │ ├── food │ │ ├── FoodEvent.java │ │ ├── FoodValues.java │ │ ├── IEdible.java │ │ ├── IEdibleBlock.java │ │ └── ItemFoodProxy.java │ ├── hunger │ │ ├── ExhaustionEvent.java │ │ ├── HealthRegenEvent.java │ │ ├── HungerEvent.java │ │ ├── HungerRegenEvent.java │ │ └── StarvationEvent.java │ └── package-info.java │ ├── api_impl │ ├── AppleCoreAccessorMutatorImpl.java │ └── AppleCoreRegistryImpl.java │ ├── asm │ ├── ASMConstants.java │ ├── Hooks.java │ ├── IClassTransformerModule.java │ ├── TransformerModuleHandler.java │ ├── module │ │ ├── ModuleBlockFood.java │ │ ├── ModuleExhaustingActions.java │ │ ├── ModuleFoodEatingSpeed.java │ │ ├── ModuleFoodStats.java │ │ ├── ModuleHungerHUD.java │ │ └── ModulePeacefulRegen.java │ ├── reference │ │ ├── BlockCakeModifications.java │ │ ├── EntityPlayerModifications.java │ │ └── FoodStatsModifications.java │ └── util │ │ └── IAppleCoreFoodStats.java │ ├── commands │ ├── CommandHunger.java │ └── Commands.java │ ├── example │ ├── AppleCoreExample.java │ ├── BlockEdibleExample.java │ ├── EatingSpeedModifier.java │ ├── ExhaustionModifier.java │ ├── FoodEatenResult.java │ ├── FoodStatsAdditionCanceler.java │ ├── FoodValuesModifier.java │ ├── FoodValuesTooltipHandler.java │ ├── HealthRegenModifier.java │ ├── HungerRegenModifier.java │ ├── ItemMetadataFood.java │ ├── ItemNonStandardFood.java │ ├── MaxHungerModifier.java │ └── StarvationModifier.java │ └── network │ ├── MessageDifficultySync.java │ ├── NetworkHelper.java │ └── SyncHandler.java ├── resources ├── applecore.info ├── assets │ └── applecore │ │ └── lang │ │ ├── de_DE.lang │ │ ├── en_US.lang │ │ ├── fr_FR.lang │ │ ├── pl_PL.lang │ │ ├── ru_RU.lang │ │ ├── zh_CN.lang │ │ └── zh_TW.lang └── mcmod.info └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | eclipse 2 | run 3 | libs 4 | media 5 | classes 6 | *.bat 7 | builds 8 | sources 9 | 10 | # Created by https://www.gitignore.io/api/gradle,intellij,eclipse,windows,osx,linux 11 | 12 | ### Gradle ### 13 | .gradle 14 | build/ 15 | 16 | # Ignore Gradle GUI config 17 | gradle-app.setting 18 | 19 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 20 | !gradle-wrapper.jar 21 | 22 | # Cache of project 23 | .gradletasknamecache 24 | 25 | ### Intellij ### 26 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 27 | 28 | *.iml 29 | 30 | ## Directory-based project format: 31 | .idea/ 32 | # if you remove the above rule, at least ignore the following: 33 | 34 | # User-specific stuff: 35 | # .idea/workspace.xml 36 | # .idea/tasks.xml 37 | # .idea/dictionaries 38 | # .idea/shelf 39 | 40 | # Sensitive or high-churn files: 41 | # .idea/dataSources.ids 42 | # .idea/dataSources.xml 43 | # .idea/sqlDataSources.xml 44 | # .idea/dynamic.xml 45 | # .idea/uiDesigner.xml 46 | 47 | # Gradle: 48 | # .idea/gradle.xml 49 | # .idea/libraries 50 | 51 | # Mongo Explorer plugin: 52 | # .idea/mongoSettings.xml 53 | 54 | ## File-based project format: 55 | *.ipr 56 | *.iws 57 | 58 | ## Plugin-specific files: 59 | 60 | # IntelliJ 61 | /out/ 62 | 63 | # mpeltonen/sbt-idea plugin 64 | .idea_modules/ 65 | 66 | # JIRA plugin 67 | atlassian-ide-plugin.xml 68 | 69 | # Crashlytics plugin (for Android Studio and IntelliJ) 70 | com_crashlytics_export_strings.xml 71 | crashlytics.properties 72 | crashlytics-build.properties 73 | fabric.properties 74 | 75 | 76 | ### Eclipse ### 77 | *.pydevproject 78 | .metadata 79 | bin/ 80 | tmp/ 81 | *.tmp 82 | *.bak 83 | *.swp 84 | *~.nib 85 | local.properties 86 | .settings/ 87 | .loadpath 88 | 89 | # Eclipse Core 90 | .project 91 | 92 | # External tool builders 93 | .externalToolBuilders/ 94 | 95 | # Locally stored "Eclipse launch configurations" 96 | *.launch 97 | 98 | # CDT-specific 99 | .cproject 100 | 101 | # JDT-specific (Eclipse Java Development Tools) 102 | .classpath 103 | 104 | # Java annotation processor (APT) 105 | .factorypath 106 | 107 | # PDT-specific 108 | .buildpath 109 | 110 | # sbteclipse plugin 111 | .target 112 | 113 | # TeXlipse plugin 114 | .texlipse 115 | 116 | # STS (Spring Tool Suite) 117 | .springBeans 118 | 119 | 120 | ### Windows ### 121 | # Windows image file caches 122 | Thumbs.db 123 | ehthumbs.db 124 | 125 | # Folder config file 126 | Desktop.ini 127 | 128 | # Recycle Bin used on file shares 129 | $RECYCLE.BIN/ 130 | 131 | # Windows Installer files 132 | *.cab 133 | *.msi 134 | *.msm 135 | *.msp 136 | 137 | # Windows shortcuts 138 | *.lnk 139 | 140 | 141 | ### OSX ### 142 | .DS_Store 143 | .AppleDouble 144 | .LSOverride 145 | 146 | # Icon must end with two \r 147 | Icon 148 | 149 | 150 | # Thumbnails 151 | ._* 152 | 153 | # Files that might appear in the root of a volume 154 | .DocumentRevisions-V100 155 | .fseventsd 156 | .Spotlight-V100 157 | .TemporaryItems 158 | .Trashes 159 | .VolumeIcon.icns 160 | 161 | # Directories potentially created on remote AFP share 162 | .AppleDB 163 | .AppleDesktop 164 | Network Trash Folder 165 | Temporary Items 166 | .apdisk 167 | 168 | 169 | ### Linux ### 170 | *~ 171 | 172 | # temporary files which can be created if a process still has a handle open of a deleted file 173 | .fuse_hidden* 174 | 175 | # KDE directory preferences 176 | .directory 177 | 178 | # Linux trash folder which might appear on any partition or disk 179 | .Trash-* 180 | 181 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ASMHelper"] 2 | path = ASMHelper 3 | url = https://github.com/squeek502/ASMHelper.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: openjdk8 3 | script: ./gradlew build -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [AppleCore](https://minecraft.curseforge.com/projects/applecore) [![Build Status](https://travis-ci.org/squeek502/AppleCore.svg)](https://travis-ci.org/squeek502/AppleCore) 2 | =========== 3 | 4 | An API for modifying the food and hunger mechanics of Minecraft. 5 | 6 | ### Using AppleCore 7 | * [Building against AppleCore](https://github.com/squeek502/AppleCore/wiki/Building-against-AppleCore) 8 | * [AppleCore features](https://github.com/squeek502/AppleCore/wiki/AppleCore-features) 9 | * [Using the AppleCore API](https://github.com/squeek502/AppleCore/wiki/Using-the-AppleCore-API) 10 | * [Integrating food items with AppleCore](https://github.com/squeek502/AppleCore/wiki/Integrating-food-items-with-AppleCore) 11 | * [Integrating plants with AppleCore](https://github.com/squeek502/AppleCore/wiki/Integrating-plants-with-AppleCore) 12 | 13 | #### Mods that use AppleCore 14 | * [Hunger Overhaul](https://github.com/progwml6/HungerOverhaul) – *Extensive tweaks to hunger and related mechanics* 15 | * [The Spice of Life](https://github.com/squeek502/SpiceOfLife/tree/1.7.10) – *Encourages dietary variety through diminishing returns* 16 | * [Hunger In Peace](https://github.com/squeek502/HungerInPeace) – *Normal hunger and health regen in peaceful* 17 | 18 | --- 19 | 20 | ### Building AppleCore 21 | 1. Clone the repository 22 | 2. Open a command line and execute ```gradlew build``` 23 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | General: 2 | 3 | API: 4 | 5 | Core/ASM: 6 | Plant growth modification: 7 | [ ] mods.natura.blocks.trees.SaguaroBlock 8 | [ ] com.mark719.magicalcrops.crops.MagicalCrops 9 | [ ] com.mark719.magicalcrops.crops.BlockMagicalCropsIridium 10 | 11 | Test/Sample Implementations: 12 | 13 | Project Log/Notes: 14 | Modifying food eating duration using the Forge event PlayerUseItemEvent.Start does not produce clean visuals; it just adds a delay to the start of the animation. The solution should be player-specific, which isn't possible by only hooking into ItemFood. @date(2014-07-24 01:45) 15 | * Fixed by modifying ItemRenderer to get the modified max duration instead of getting it from the item each time @date(2014-07-27 13:30) 16 | DummyModContainers can listen for events, but they need to use @Subscribe instead of @SubscribeEvent @date(2014-07-27 18:47) 17 | An incorrect method signature in an INVOKE instruction can cause an ArrayIndexOutOfBoundsException in Frame.merge @date(2014-07-28 16:59) 18 | Using the label past the return as the end label of a newly added local variable in ASM can sometimes cause an ArrayIndexOutOfBoundException in obfuscated environments. Why? Who knows. @date(2014-08-03 02:05) 19 | Without a FMLModContainer class (@Mod annotated class), the mod will not automatically get added as a resource pack @date(14-08-24 20:46) 20 | Couldn't find a way to add the mod as a resource pack in a dev environment, because the source directory of a coremod is basically unknown (it'll either give you the dummy jar or null) @date(14-08-24 22:28) 21 | 22 | ___________________ 23 | Archive: 24 | [-] mods.natura.blocks.crops.HerbBlock @cancelled (2014-08-03 19:57) @project(Core/ASM) 25 | [x] mods.natura.blocks.crops.Glowshroom @done (2014-08-03 19:56) @project(Core/ASM) 26 | [x] mods.natura.blocks.crops.NetherBerryBush @done (2014-08-03 19:52) @project(Core/ASM) 27 | [x] mods.natura.blocks.crops.CropBlock @done (2014-08-03 19:50) @project(Core/ASM) 28 | [x] mods.natura.blocks.crops.BerryBush @done (2014-08-03 19:45) @project(Core/ASM) 29 | [x] BlockPamSapling @done (2014-08-03 19:36) @project(Core/ASM) 30 | [x] BlockPamFruit @done (2014-08-03 19:29) @project(Core/ASM) 31 | [-] BlockPamFlower @started(2014-08-03 19:05) @cancelled (2014-08-03 19:12) @wasted(0:07) @project(Core/ASM) 32 | [x] Add support for showing more-than-normal player-specific food values in the tooltip overlay @done (2014-08-03 18:55) @project(General) 33 | [x] Move HUD/tooltip stuff from TSoL to AppleCore @done (2014-08-03 17:37) @project(General) 34 | [x] Document PlantGrowthEvents @started(2014-07-29 22:19) @done (2014-07-29 22:22) @lasted(0:03) @project(API) 35 | [x] Implement event-driven growth ticks @done (2014-07-29 22:19) @project(API) 36 | [x] Crop growth modification @done (2014-07-29 22:19) @project(Test/Sample Implementations) 37 | [x] net.minecraft.block.BlockStem @started(2014-07-29 21:51) @done (2014-07-29 22:18) @lasted(0:27) @project(API) 38 | [x] net.minecraft.block.BlockSapling @started(2014-07-29 21:45) @done (2014-07-29 21:51) @lasted(0:06) @project(API) 39 | [x] net.minecraft.block.BlockNetherWart @started(2014-07-29 21:38) @done (2014-07-29 21:45) @lasted(0:07) @project(API) 40 | [x] net.minecraft.block.BlockMushroom @started(2014-07-29 21:34) @done (2014-07-29 21:38) @lasted(0:04) @project(API) 41 | [x] net.minecraft.block.BlockCocoa @started(2014-07-29 21:26) @done (2014-07-29 21:34) @lasted(0:08) @project(API) 42 | [x] net.minecraft.block.BlockCactus @started(2014-07-29 21:22) @done (2014-07-29 21:26) @lasted(0:04) @project(API) 43 | [x] net.minecraft.block.BlockReed @started(2014-07-29 21:03) @done (2014-07-29 21:22) @lasted(0:19) @project(API) 44 | [x] net.minecraft.block.BlockCrops @done (2014-07-29 20:50) @project(API) 45 | [x] Add support for block-based foods (cake) @done (2014-07-28 17:10) @project(General) 46 | [x] Document AppleCoreAccessor/IAppleCoreAccessor @done (2014-07-28 12:53) @project(API) 47 | [x] Add a way to get starvation damage period @done (2014-07-27 22:24) @project(API) 48 | [x] Add a way to get max exhaustion @done (2014-07-27 22:24) @project(API) 49 | [x] Add a way to get health regen period @done (2014-07-27 22:24) @project(API) 50 | [x] Move saturation/exhaustion values shown in debug info from The Spice of Life to AppleCore @done (2014-07-27 18:42) @project(General) 51 | [x] Move saturation/exhaustion syncing from The Spice of Life to AppleCore @done (2014-07-27 18:42) @project(General) 52 | [x] Implement better food eating speed modification event @done (2014-07-27 13:31) @project(API) 53 | [x] Modifying eating speed @done (2014-07-27 13:28) @project(Test/Sample Implementations) 54 | [x] Setup build.gradle to be maven-ready @done (2014-07-27 02:19) @project(General) 55 | [x] Event documentation @done (2014-07-27 02:19) @project(API) 56 | [x] Exclude example mod metadata file from gradle build @started(2014-07-24 19:54) @done (2014-07-24 20:04) @lasted(0:10) @project(General) 57 | [x] Implement event-driven FoodStats modification canceling @done (2014-07-24 19:47) @project(API) 58 | [x] Fix example mod metadata @done (2014-07-24 19:47) @project(General) 59 | [x] Canceling food stats changes @done (2014-07-24 19:47) @project(Test/Sample Implementations) 60 | [x] Exclude example/reference packages in build.gradle @done (2014-07-24 12:35) @project(General) 61 | [-] Modifying eating speed using PlayerUseItemEvent.Start @cancelled (2014-07-24 12:23) @project(Test/Sample Implementations) 62 | [x] Break FoodEvent up into multiple files @started(2014-07-24 12:03) @done (2014-07-24 12:14) @lasted(0:11) @project(API) 63 | [x] Modifying starvation @done (2014-07-24 01:38) @project(Test/Sample Implementations) 64 | [x] Modifying regen health @done (2014-07-24 01:38) @project(Test/Sample Implementations) 65 | [x] Modifying hunger/saturation @done (2014-07-24 01:38) @project(Test/Sample Implementations) 66 | [x] Modifying exhaustion @done (2014-07-24 01:38) @project(Test/Sample Implementations) 67 | [x] Implement event-driven starvation @done (2014-07-24 01:38) @project(API) 68 | [x] Implement event-driven configurable health regen @done (2014-07-24 01:38) @project(API) 69 | [x] Implement event-driven configurable exhaustion @done (2014-07-24 01:38) @project(API) 70 | [x] Add a way for mods to be able to integrate their plant growth with AppleCore @done (15-01-27 14:50) @project(API) 71 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | maven { url = "http://files.minecraftforge.net/maven" } 5 | } 6 | dependencies { 7 | classpath 'net.minecraftforge.gradle:ForgeGradle:2.3-SNAPSHOT' 8 | } 9 | } 10 | apply plugin: 'net.minecraftforge.gradle.forge' 11 | 12 | minecraft { 13 | version = minecraft_version + "-" + forge_version 14 | mappings = mappings_version 15 | runDir = "run" 16 | 17 | coreMod = "squeek.applecore.AppleCore" 18 | 19 | replace "\${version}", project.version 20 | replace "\${apiversion}", api_version 21 | replaceIn "ModInfo.java" 22 | replaceIn "package-info.java" 23 | } 24 | 25 | group = project.projectDir.name.toLowerCase() 26 | def modId = project.projectDir.name 27 | archivesBaseName = modId + "-mc" + project.minecraft.version 28 | 29 | if (version == "unspecified") 30 | version = "" 31 | if (System.getenv("TRAVIS_BUILD_NUMBER") != null) 32 | version += (version ? "+" : "") + "${System.getenv("TRAVIS_BUILD_NUMBER").toInteger() + 230}" 33 | if (System.getenv("TRAVIS_COMMIT") != null) 34 | version += (version ? "." : "") + "${System.getenv("TRAVIS_COMMIT").take(5)}" 35 | 36 | sourceSets.main{ 37 | java { 38 | srcDirs 'java', 'apis' 39 | } 40 | resources { 41 | srcDirs 'resources' 42 | } 43 | } 44 | 45 | jar { 46 | manifest { 47 | attributes 'FMLCorePlugin': 'squeek.applecore.AppleCore' 48 | attributes 'FMLCorePluginContainsFMLMod': 'true' 49 | } 50 | } 51 | 52 | task deobfArtifact(type: Jar) { 53 | from sourceSets.main.output 54 | manifest = jar.manifest 55 | classifier = 'deobf' 56 | } 57 | 58 | task apiArtifact(type: Jar, dependsOn: compileJava) { 59 | from sourceSets.main.output 60 | include "squeek/applecore/api/**" 61 | classifier = 'api' 62 | } 63 | 64 | task sourcesArtifact(type: Jar, dependsOn: compileJava) { 65 | from tasks.sourceMainJava.output 66 | classifier = 'sources' 67 | } 68 | 69 | artifacts { 70 | archives deobfArtifact 71 | archives apiArtifact 72 | archives sourcesArtifact 73 | } 74 | 75 | processResources { 76 | inputs.property "vars", project.version + project.minecraft.version + api_version 77 | from(sourceSets.main.resources.srcDirs) { 78 | include '**/*.info' 79 | expand 'version':project.version, 'mcversion':project.minecraft.version, 'apiversion':api_version 80 | } 81 | from(sourceSets.main.resources.srcDirs) { 82 | exclude '**/*.info' 83 | } 84 | } 85 | 86 | task removeExampleAndReferenceSources(type:Delete) { 87 | delete += tasks.sourceMainJava.output.getPath()+"/squeek/applecore/example" 88 | delete += tasks.sourceMainJava.output.getPath()+"/squeek/applecore/asm/reference" 89 | } 90 | 91 | task removeExampleAndReferenceResources(type:Delete) { 92 | delete += file(sourceSets.main.output.resourcesDir.getPath()+"/mcmod.info") 93 | } 94 | 95 | // insert custom tasks 96 | afterEvaluate { project -> 97 | if (!project.hasProperty('buildexample')) 98 | { 99 | sourceMainJava.finalizedBy(removeExampleAndReferenceSources) 100 | processResources.finalizedBy(removeExampleAndReferenceResources) 101 | } 102 | } 103 | 104 | ext.mavenUrl = project.hasProperty('mavenUrl') ? mavenUrl : System.getenv("MAVEN_URL") 105 | ext.mavenUser = project.hasProperty('mavenUser') ? mavenUser : System.getenv("MAVEN_USER") 106 | ext.mavenPass = project.hasProperty('mavenPass') ? mavenPass : System.getenv("MAVEN_PASS") 107 | def isPullRequest = System.getenv("TRAVIS_PULL_REQUEST") != null && System.getenv("TRAVIS_PULL_REQUEST") != false 108 | def isCI = System.getenv("CI") == true 109 | def isMainBranch = System.getenv("TRAVIS_BRANCH") == project.minecraft.version 110 | def okayToPublish = !isCI || (!isPullRequest && isMainBranch) 111 | 112 | if (mavenUrl && !project.hasProperty('nopublish') && okayToPublish) { 113 | apply plugin: 'maven' 114 | 115 | configurations { 116 | deployerJars 117 | } 118 | dependencies { 119 | deployerJars "org.apache.maven.wagon:wagon-webdav:1.0-beta-2" 120 | } 121 | uploadArchives { 122 | repositories.mavenDeployer { 123 | configuration = configurations.deployerJars 124 | pom.artifactId = modId 125 | pom.version = project.minecraft.version + "-" + project.version 126 | pom.project { 127 | url = "https://github.com/squeek502/" + modId 128 | } 129 | repository(url: mavenUrl) { 130 | authentication(userName: mavenUser, password: mavenPass) 131 | } 132 | } 133 | } 134 | afterEvaluate { project -> 135 | build.finalizedBy(uploadArchives) 136 | } 137 | } 138 | 139 | task testPom << { 140 | apply plugin: 'maven' 141 | pom { 142 | whenConfigured {pom -> 143 | //pom.dependencies.removeAll {dep -> dep.groupId == "squeek.asmhelper" } 144 | } 145 | }.writeTo(new PrintWriter(System.out)) 146 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | version=3.4.0 2 | api_version=3.4.0 3 | minecraft_version=1.12.2 4 | forge_version=14.23.0.2486 5 | mappings_version=snapshot_20170620 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squeek502/AppleCore/6445352660391cb69655b6a3e86ef2e70a66ed73/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Sep 14 12:28:28 PDT 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-bin.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /java/squeek/applecore/AppleCore.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore; 2 | 3 | import net.minecraftforge.fml.common.Loader; 4 | import net.minecraftforge.fml.common.MetadataCollection; 5 | import net.minecraftforge.fml.common.Mod; 6 | import net.minecraftforge.fml.common.Mod.EventHandler; 7 | import net.minecraftforge.fml.common.event.FMLInitializationEvent; 8 | import net.minecraftforge.fml.common.event.FMLInterModComms; 9 | import net.minecraftforge.fml.common.event.FMLPostInitializationEvent; 10 | import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; 11 | import net.minecraftforge.fml.common.event.FMLServerStartingEvent; 12 | import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin; 13 | import org.apache.logging.log4j.LogManager; 14 | import org.apache.logging.log4j.Logger; 15 | import squeek.applecore.api_impl.AppleCoreAccessorMutatorImpl; 16 | import squeek.applecore.api_impl.AppleCoreRegistryImpl; 17 | import squeek.applecore.asm.TransformerModuleHandler; 18 | import squeek.applecore.commands.Commands; 19 | import squeek.applecore.network.SyncHandler; 20 | import squeek.asmhelper.applecore.ObfHelper; 21 | 22 | import java.io.InputStream; 23 | import java.util.Map; 24 | 25 | @IFMLLoadingPlugin.SortingIndex(1100) 26 | @IFMLLoadingPlugin.MCVersion("1.12.2") 27 | @IFMLLoadingPlugin.TransformerExclusions({"squeek.applecore.asm", "squeek.asmhelper"}) 28 | @Mod(modid = ModInfo.MODID, name = ModInfo.MODNAME, version = ModInfo.VERSION, acceptedMinecraftVersions="[1.12.2]", acceptableRemoteVersions = "*", dependencies = "required-after:forge@[14.23,)") 29 | public class AppleCore implements IFMLLoadingPlugin 30 | { 31 | public static final Logger LOG = LogManager.getLogger(ModInfo.MODID); 32 | 33 | @EventHandler 34 | public void preInit(FMLPreInitializationEvent event) 35 | { 36 | // too lazy to figure out a real solution for this (@Mod enforces mcmod.info filename) 37 | // this will at least allow the metadata to populate the mod listing, though 38 | InputStream is = MetadataCollection.class.getResourceAsStream("/applecore.info"); 39 | MetadataCollection metadataCollection = MetadataCollection.from(is, ModInfo.MODID); 40 | Loader.instance().activeModContainer().bindMetadata(metadataCollection); 41 | 42 | // force initialization of the singletons 43 | AppleCoreAccessorMutatorImpl.values(); 44 | AppleCoreRegistryImpl.values(); 45 | 46 | FMLInterModComms.sendRuntimeMessage(ModInfo.MODID, "versionchecker", "addVersionCheck", "http://www.ryanliptak.com/minecraft/versionchecker/squeek502/AppleCore"); 47 | } 48 | 49 | @EventHandler 50 | public void postInit(FMLPostInitializationEvent event) 51 | { 52 | AppleCoreRegistryImpl.INSTANCE.init(); 53 | } 54 | 55 | @EventHandler 56 | public void init(FMLInitializationEvent event) 57 | { 58 | SyncHandler.init(); 59 | } 60 | 61 | @EventHandler 62 | public void onServerStarting(FMLServerStartingEvent event) 63 | { 64 | Commands.init(event.getServer()); 65 | } 66 | 67 | @Override 68 | public String[] getASMTransformerClass() 69 | { 70 | return new String[]{TransformerModuleHandler.class.getName()}; 71 | } 72 | 73 | @Override 74 | public String getModContainerClass() 75 | { 76 | return null; 77 | } 78 | 79 | @Override 80 | public String getSetupClass() 81 | { 82 | return null; 83 | } 84 | 85 | @Override 86 | public void injectData(Map data) 87 | { 88 | ObfHelper.setObfuscated((Boolean) data.get("runtimeDeobfuscationEnabled")); 89 | ObfHelper.setRunsAfterDeobfRemapper(true); 90 | } 91 | 92 | @Override 93 | public String getAccessTransformerClass() 94 | { 95 | return null; 96 | } 97 | } -------------------------------------------------------------------------------- /java/squeek/applecore/ModInfo.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore; 2 | 3 | public final class ModInfo 4 | { 5 | public static final String MODID = "applecore"; 6 | public static final String MODNAME = "AppleCore"; 7 | public static final String MODAPI = "AppleCoreAPI"; 8 | public static final String VERSION = "${version}"; 9 | public static final String APIVERSION = "${apiversion}"; 10 | } 11 | -------------------------------------------------------------------------------- /java/squeek/applecore/api/AppleCoreAPI.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.api; 2 | 3 | /** 4 | * Used to access/mutate various hidden values of the hunger system or fire standard AppleCore events. 5 | * 6 | * See {@link IAppleCoreAccessor} and {@link IAppleCoreMutator} for a list of the available functions. 7 | * {@link #accessor} and {@link #mutator} will be initialized by AppleCore on startup. 8 | */ 9 | public abstract class AppleCoreAPI 10 | { 11 | public static IAppleCoreAccessor accessor; 12 | public static IAppleCoreMutator mutator; 13 | public static IAppleCoreRegistry registry; 14 | } 15 | -------------------------------------------------------------------------------- /java/squeek/applecore/api/IAppleCoreAccessor.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.api; 2 | 3 | import net.minecraft.entity.player.EntityPlayer; 4 | import net.minecraft.item.ItemStack; 5 | import squeek.applecore.api.food.FoodValues; 6 | 7 | import javax.annotation.Nonnull; 8 | 9 | public interface IAppleCoreAccessor 10 | { 11 | /** 12 | * Check whether or not the given ItemStack is an edible food. 13 | * 14 | * Any ItemStack that gives a return of true in 15 | * this method will also return valid FoodValues from 16 | * the {@link #getFoodValues}/{@link #getFoodValuesForPlayer} methods.
17 | *
18 | * This method should be preferred when doing something like 19 | * determining whether or not to show food values in an 20 | * item's tooltip, as it is more inclusive than a simple 21 | * {@code instanceof ItemFood} check. 22 | */ 23 | boolean isFood(@Nonnull ItemStack food); 24 | 25 | /** 26 | * Check if the given ItemStack can currently be eaten by the player, taking into account their 27 | * max hunger, and if the food item is always edible.
28 | *
29 | * In particular, this method will always return {@code true} if 30 | * {@link net.minecraft.util.FoodStats#getFoodLevel} {@code <} {@link #getMaxHunger} 31 | * or if this ItemStack's Item is an instance of ItemFood and has its alwaysEdible field set.
32 | *
33 | * Note: {@link ItemStack#EMPTY} can be passed to this function in order to check whether 34 | * the player's hunger is currently below maximum. 35 | * 36 | * @return {@code true} if the player is currently able to eat the food item, {@code false} otherwise. 37 | */ 38 | boolean canPlayerEatFood(@Nonnull ItemStack food, @Nonnull EntityPlayer player); 39 | 40 | /** 41 | * Get player-agnostic food values. 42 | * 43 | * @return The food values, or {@link ItemStack#EMPTY} if none were found. 44 | */ 45 | FoodValues getFoodValues(@Nonnull ItemStack food); 46 | 47 | /** 48 | * Get player-specific food values. 49 | * 50 | * @return The food values, or {@link ItemStack#EMPTY} if none were found. 51 | */ 52 | FoodValues getFoodValuesForPlayer(@Nonnull ItemStack food, EntityPlayer player); 53 | 54 | /** 55 | * Get unmodified (vanilla) food values. 56 | * 57 | * @return The food values, or {@link ItemStack#EMPTY} if none were found. 58 | */ 59 | FoodValues getUnmodifiedFoodValues(@Nonnull ItemStack food); 60 | 61 | /** 62 | * @return The current exhaustion level of the {@code player}. 63 | */ 64 | float getExhaustion(EntityPlayer player); 65 | 66 | /** 67 | * @return The maximum exhaustion level of the {@code player}.
68 | *
69 | * Note: Maximum exhaustion refers to the amount of exhaustion that 70 | * will trigger {@link squeek.applecore.api.hunger.ExhaustionEvent.Exhausted} events; 71 | * exhaustion can exceed the maximum exhaustion value. 72 | */ 73 | float getMaxExhaustion(EntityPlayer player); 74 | 75 | /** 76 | * @return The number of ticks between health being regenerated by the {@code player}. 77 | */ 78 | int getHealthRegenTickPeriod(EntityPlayer player); 79 | 80 | /** 81 | * @return The number of ticks between starvation damage being dealt to the {@code player}. 82 | */ 83 | int getStarveDamageTickPeriod(EntityPlayer player); 84 | 85 | /** 86 | * @return The number of ticks between health being regenerated by the {@code player} when at full hunger and > 0 saturation. 87 | */ 88 | int getSaturatedHealthRegenTickPeriod(EntityPlayer player); 89 | 90 | /** 91 | * @return The maximum hunger level of the {@code player}. 92 | */ 93 | int getMaxHunger(EntityPlayer player); 94 | } -------------------------------------------------------------------------------- /java/squeek/applecore/api/IAppleCoreMutator.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.api; 2 | 3 | import net.minecraft.entity.player.EntityPlayer; 4 | 5 | public interface IAppleCoreMutator 6 | { 7 | /** 8 | * Sets the exhaustion level of the {@code player}. 9 | */ 10 | void setExhaustion(EntityPlayer player, float exhaustion); 11 | 12 | /** 13 | * Sets the hunger of the {@code player} in hunger units (1 hunger unit = 1/2 hunger bar). 14 | */ 15 | void setHunger(EntityPlayer player, int hunger); 16 | 17 | /** 18 | * Sets the saturation level of the {@code player}. 19 | */ 20 | void setSaturation(EntityPlayer player, float saturation); 21 | 22 | /** 23 | * Sets the health regen tick counter of the {@code player}. 24 | * 25 | * See {@link squeek.applecore.api.hunger.HealthRegenEvent.GetRegenTickPeriod} 26 | * and {@link squeek.applecore.api.hunger.HealthRegenEvent.Regen} 27 | */ 28 | void setHealthRegenTickCounter(EntityPlayer player, int tickCounter); 29 | 30 | /** 31 | * Sets the starvation tick counter of the {@code player}. 32 | * 33 | * See {@link squeek.applecore.api.hunger.StarvationEvent.GetStarveTickPeriod} 34 | * and {@link squeek.applecore.api.hunger.StarvationEvent.Starve} 35 | */ 36 | void setStarveDamageTickCounter(EntityPlayer player, int tickCounter); 37 | } -------------------------------------------------------------------------------- /java/squeek/applecore/api/IAppleCoreRegistry.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.api; 2 | 3 | import net.minecraft.block.Block; 4 | import net.minecraft.item.Item; 5 | 6 | public interface IAppleCoreRegistry 7 | { 8 | /** 9 | * Registers a Block <-> Item relationship for 10 | * block-based foods. This registration is only necessary 11 | * when {@link Block#getBlockFromItem} returns something other 12 | * than the 'canonical' block for the Item (or vice versa 13 | * with regards to {@link Item#getItemFromBlock}.
14 | *
15 | * For example, Blocks.cake <-> Items.cake requires their 16 | * relationship to be registered. 17 | */ 18 | void registerEdibleBlock(Block block, Item item); 19 | 20 | /** 21 | * Note: Falls back to {@link Item#getItemFromBlock} when no 22 | * registry data is found for the block 23 | * 24 | * @return The Item, or null if no item was found 25 | */ 26 | Item getItemFromEdibleBlock(Block block); 27 | 28 | /** 29 | * Note: Falls back to {@link Block#getBlockFromItem} when no 30 | * registry data is found for the item 31 | * 32 | * @return The Block, or null if no block was found 33 | */ 34 | Block getEdibleBlockFromItem(Item item); 35 | } 36 | -------------------------------------------------------------------------------- /java/squeek/applecore/api/food/FoodEvent.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.api.food; 2 | 3 | import net.minecraft.entity.player.EntityPlayer; 4 | import net.minecraft.item.ItemFood; 5 | import net.minecraft.item.ItemStack; 6 | import net.minecraft.util.FoodStats; 7 | import net.minecraftforge.common.MinecraftForge; 8 | import net.minecraftforge.fml.common.eventhandler.Cancelable; 9 | import net.minecraftforge.fml.common.eventhandler.Event; 10 | import squeek.applecore.api.AppleCoreAPI; 11 | 12 | import javax.annotation.Nonnull; 13 | 14 | /** 15 | * Base class for all FoodEvent events.
16 | *
17 | * All children of this event are fired on the {@link MinecraftForge#EVENT_BUS}. 18 | */ 19 | public abstract class FoodEvent extends Event 20 | { 21 | /** 22 | * Fired every time food values are retrieved to allow player-independent control over their values. 23 | * 24 | * This event is fired in {@link FoodStats#addStats(ItemFood, ItemStack)} and in {@link AppleCoreAPI}.
25 | *
26 | * {@link #foodValues} contains the values of the {@link #food}.
27 | * {@link #unmodifiedFoodValues} contains the food values of the {@link #food} before the GetFoodValues event was fired.
28 | * {@link #food} contains the food in question.
29 | *
30 | * This event is not {@link Cancelable}.
31 | *
32 | * This event does not have a result. {@link HasResult}
33 | */ 34 | public static class GetFoodValues extends FoodEvent 35 | { 36 | public FoodValues foodValues; 37 | public final FoodValues unmodifiedFoodValues; 38 | public final ItemStack food; 39 | 40 | public GetFoodValues(@Nonnull ItemStack itemStack, FoodValues foodValues) 41 | { 42 | this.food = itemStack; 43 | this.foodValues = foodValues; 44 | this.unmodifiedFoodValues = foodValues; 45 | } 46 | } 47 | 48 | /** 49 | * Fired every time food values are retrieved to allow player-dependent control over their values. 50 | * This event will always be preceded by {@link GetFoodValues} being fired. 51 | * 52 | * This event is fired in {@link FoodStats#addStats(ItemFood, ItemStack)} and in {@link AppleCoreAPI}.
53 | *
54 | * {@link #player} contains the player.
55 | * {@link #foodValues} contains the values of the {@link #food}.
56 | * {@link #unmodifiedFoodValues} contains the food values of the {@link #food} before the GetFoodValues event was fired.
57 | * {@link #food} contains the food in question.
58 | *
59 | * This event is not {@link Cancelable}.
60 | *
61 | * This event does not have a result. {@link HasResult}
62 | */ 63 | public static class GetPlayerFoodValues extends FoodEvent 64 | { 65 | public FoodValues foodValues; 66 | public final FoodValues unmodifiedFoodValues; 67 | public final ItemStack food; 68 | public final EntityPlayer player; 69 | 70 | public GetPlayerFoodValues(EntityPlayer player, @Nonnull ItemStack itemStack, FoodValues foodValues) 71 | { 72 | this.player = player; 73 | this.food = itemStack; 74 | this.foodValues = foodValues; 75 | this.unmodifiedFoodValues = foodValues; 76 | } 77 | } 78 | 79 | /** 80 | * Fired after {@link FoodStats#addStats}, containing the effects and context for the food that was eaten. 81 | * 82 | * This event is fired in {@link FoodStats#addStats(ItemFood, ItemStack)}.
83 | *
84 | * This event is not {@link Cancelable}.
85 | *
86 | * This event does not have a result. {@link HasResult}
87 | */ 88 | public static class FoodEaten extends FoodEvent 89 | { 90 | public final FoodValues foodValues; 91 | public final int hungerAdded; 92 | public final float saturationAdded; 93 | public final ItemStack food; 94 | public final EntityPlayer player; 95 | 96 | public FoodEaten(EntityPlayer player, @Nonnull ItemStack itemStack, FoodValues foodValues, int hungerAdded, float saturationAdded) 97 | { 98 | this.player = player; 99 | this.food = itemStack; 100 | this.foodValues = foodValues; 101 | this.hungerAdded = hungerAdded; 102 | this.saturationAdded = saturationAdded; 103 | } 104 | } 105 | 106 | /** 107 | * Fired when hunger/saturation is added to a player's FoodStats. 108 | * 109 | * This event is fired in {@link FoodStats#addStats(int, float)}.
110 | *
111 | * This event is {@link Cancelable}.
112 | * If this event is canceled, the hunger and saturation of the FoodStats will not change.
113 | *
114 | * This event does not have a result. {@link HasResult}
115 | */ 116 | @Cancelable 117 | public static class FoodStatsAddition extends FoodEvent 118 | { 119 | public final FoodValues foodValuesToBeAdded; 120 | public final EntityPlayer player; 121 | 122 | public FoodStatsAddition(EntityPlayer player, FoodValues foodValuesToBeAdded) 123 | { 124 | this.player = player; 125 | this.foodValuesToBeAdded = foodValuesToBeAdded; 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /java/squeek/applecore/api/food/FoodValues.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.api.food; 2 | 3 | import net.minecraft.entity.player.EntityPlayer; 4 | import net.minecraft.item.ItemStack; 5 | import squeek.applecore.api.AppleCoreAPI; 6 | import squeek.applecore.api.IAppleCoreAccessor; 7 | 8 | import javax.annotation.Nonnull; 9 | 10 | /** 11 | * FoodValues is a utility class used to retrieve and hold food values. 12 | * 13 | * To get food values for any given food, use any of the static {@link #get} methods. 14 | * 15 | *
 16 |  * {@code
 17 |  * FoodValues appleFoodValues = FoodValues.get(new ItemStack(Items.apple));
 18 |  * }
 19 |  * 
20 | */ 21 | public class FoodValues 22 | { 23 | public final int hunger; 24 | public final float saturationModifier; 25 | 26 | public FoodValues(int hunger, float saturationModifier) 27 | { 28 | this.hunger = hunger; 29 | this.saturationModifier = saturationModifier; 30 | } 31 | 32 | public FoodValues(FoodValues other) 33 | { 34 | this(other.hunger, other.saturationModifier); 35 | } 36 | 37 | /** 38 | * @return The amount of saturation that the food values would provide, ignoring any limits. 39 | */ 40 | public float getUnboundedSaturationIncrement() 41 | { 42 | return hunger * saturationModifier * 2f; 43 | } 44 | 45 | /** 46 | * @return The bounded amount of saturation that the food values would provide to this player, 47 | * taking their max hunger level into account. 48 | */ 49 | public float getSaturationIncrement(EntityPlayer player) 50 | { 51 | return Math.min(AppleCoreAPI.accessor.getMaxHunger(player), getUnboundedSaturationIncrement()); 52 | } 53 | 54 | /** 55 | * See {@link IAppleCoreAccessor#getUnmodifiedFoodValues} 56 | */ 57 | public static FoodValues getUnmodified(ItemStack itemStack) 58 | { 59 | return AppleCoreAPI.accessor.getUnmodifiedFoodValues(itemStack); 60 | } 61 | 62 | /** 63 | * See {@link IAppleCoreAccessor#getFoodValues} 64 | */ 65 | public static FoodValues get(@Nonnull ItemStack itemStack) 66 | { 67 | return AppleCoreAPI.accessor.getFoodValues(itemStack); 68 | } 69 | 70 | /** 71 | * See {@link IAppleCoreAccessor#getFoodValuesForPlayer} 72 | */ 73 | public static FoodValues get(@Nonnull ItemStack itemStack, EntityPlayer player) 74 | { 75 | return AppleCoreAPI.accessor.getFoodValuesForPlayer(itemStack, player); 76 | } 77 | 78 | @Override 79 | public int hashCode() 80 | { 81 | final int prime = 31; 82 | int result = 1; 83 | result = prime * result + hunger; 84 | result = prime * result + Float.floatToIntBits(saturationModifier); 85 | return result; 86 | } 87 | 88 | @Override 89 | public boolean equals(Object obj) 90 | { 91 | if (this == obj) 92 | return true; 93 | if (obj == null) 94 | return false; 95 | if (getClass() != obj.getClass()) 96 | return false; 97 | FoodValues other = (FoodValues) obj; 98 | if (hunger != other.hunger) 99 | return false; 100 | if (Float.floatToIntBits(saturationModifier) != Float.floatToIntBits(other.saturationModifier)) 101 | return false; 102 | return true; 103 | } 104 | } -------------------------------------------------------------------------------- /java/squeek/applecore/api/food/IEdible.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.api.food; 2 | 3 | import net.minecraft.item.ItemFood; 4 | import net.minecraft.item.ItemStack; 5 | 6 | import javax.annotation.Nonnull; 7 | 8 | /** 9 | * An interface for edible objects.
10 | *
11 | * Intended for use by classes that don't extend ItemFood.
12 | *
13 | * When extending ItemFood, 14 | * {@link ItemFood#getHealAmount} (for hunger values) and 15 | * {@link ItemFood#getSaturationModifier} (for saturation modifiers) 16 | * should be used instead.
17 | *
18 | * Note: {@link IEdible#getFoodValues} will take precedence over the 19 | * {@link ItemFood} methods when getting an item's food values 20 | */ 21 | public interface IEdible 22 | { 23 | FoodValues getFoodValues(@Nonnull ItemStack itemStack); 24 | } -------------------------------------------------------------------------------- /java/squeek/applecore/api/food/IEdibleBlock.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.api.food; 2 | 3 | import net.minecraft.item.ItemFood; 4 | import net.minecraft.item.ItemStack; 5 | 6 | /** 7 | * An interface for edible blocks (e.g. cakes).
8 | *
9 | * Note: AppleCore will implement this on BlockCake at runtime. 10 | */ 11 | public interface IEdibleBlock extends IEdible 12 | { 13 | /** 14 | * The IEdibleBlock equivalent of {@link ItemFood#setAlwaysEdible}. 15 | * Should set whether or not the food can be eaten when at max hunger. 16 | */ 17 | public void setEdibleAtMaxHunger(boolean value); 18 | } 19 | -------------------------------------------------------------------------------- /java/squeek/applecore/api/food/ItemFoodProxy.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.api.food; 2 | 3 | import net.minecraft.entity.player.EntityPlayer; 4 | import net.minecraft.item.ItemFood; 5 | import net.minecraft.item.ItemStack; 6 | 7 | import javax.annotation.Nonnull; 8 | 9 | /** 10 | * Useful for adding AppleCore compatibility to edible item 11 | * implementations that do not extend ItemFood for whatever reason.
12 | *
13 | * To use it, simply have your item implement {@link IEdible}, and then 14 | * instead of calling {@code player.getFoodStats().addStats(int, float)} 15 | * in your item's {@code onEaten} method, you'd instead call:
16 | *
17 | * {@code new ItemFoodProxy(this).onEaten(itemStack, player);} 18 | */ 19 | public class ItemFoodProxy extends ItemFood 20 | { 21 | public IEdible proxyEdible; 22 | 23 | public ItemFoodProxy(IEdible proxyEdible) 24 | { 25 | super(0, false); 26 | this.proxyEdible = proxyEdible; 27 | } 28 | 29 | /** 30 | * Applies the food values of the edible item to the player 31 | */ 32 | public void onEaten(@Nonnull ItemStack itemStack, EntityPlayer player) 33 | { 34 | player.getFoodStats().addStats(this, itemStack); 35 | } 36 | 37 | /** 38 | * @return The hunger value of the edible item 39 | */ 40 | @Override 41 | public int getHealAmount(@Nonnull ItemStack stack) 42 | { 43 | return proxyEdible.getFoodValues(stack).hunger; 44 | } 45 | 46 | /** 47 | * @return The saturation modifier of the edible item 48 | */ 49 | @Override 50 | public float getSaturationModifier(@Nonnull ItemStack stack) 51 | { 52 | return proxyEdible.getFoodValues(stack).saturationModifier; 53 | } 54 | } -------------------------------------------------------------------------------- /java/squeek/applecore/api/hunger/ExhaustionEvent.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.api.hunger; 2 | 3 | import net.minecraft.entity.player.EntityPlayer; 4 | import net.minecraft.util.FoodStats; 5 | import net.minecraft.world.EnumDifficulty; 6 | import net.minecraftforge.common.MinecraftForge; 7 | import net.minecraftforge.fml.common.eventhandler.Cancelable; 8 | import net.minecraftforge.fml.common.eventhandler.Event; 9 | import squeek.applecore.api.AppleCoreAPI; 10 | 11 | /** 12 | * Base class for all ExhaustionEvent events.
13 | *
14 | * All children of this event are fired on the {@link MinecraftForge#EVENT_BUS}. 15 | */ 16 | public abstract class ExhaustionEvent extends Event 17 | { 18 | public final EntityPlayer player; 19 | 20 | public ExhaustionEvent(EntityPlayer player) 21 | { 22 | this.player = player; 23 | } 24 | 25 | /** 26 | * Fired each FoodStats update to determine whether or not exhaustion is allowed for the {@link #player}. 27 | * 28 | * This event is fired in {@link FoodStats#onUpdate}.
29 | *
30 | * This event is not {@link Cancelable}.
31 | *
32 | * This event uses the {@link Result}. {@link HasResult}
33 | * {@link Result#DEFAULT} will use the vanilla conditionals.
34 | * {@link Result#ALLOW} will allow exhaustion without condition.
35 | * {@link Result#DENY} will deny exhaustion without condition.
36 | */ 37 | @HasResult 38 | public static class AllowExhaustion extends ExhaustionEvent 39 | { 40 | public AllowExhaustion(EntityPlayer player) 41 | { 42 | super(player); 43 | } 44 | } 45 | 46 | /** 47 | * Fired each time exhaustion is added to a {@link #player} to allow control over 48 | * its value. 49 | * 50 | * Note: This is a catch-all event for *any* time exhaustion is modified. For more fine-grained 51 | * control, see {@link ExhaustingAction} 52 | * 53 | * This event is fired in {@link FoodStats#addExhaustion}.
54 | *
55 | * This event is not {@link Cancelable}.
56 | *
57 | * This event does not have a result. {@link HasResult}
58 | */ 59 | public static class ExhaustionAddition extends ExhaustionEvent 60 | { 61 | public float deltaExhaustion; 62 | 63 | public ExhaustionAddition(EntityPlayer player, float deltaExhaustion) 64 | { 65 | super(player); 66 | this.deltaExhaustion = deltaExhaustion; 67 | } 68 | } 69 | 70 | /** 71 | * See {@link ExhaustingAction} 72 | */ 73 | public enum ExhaustingActions 74 | { 75 | HARVEST_BLOCK, 76 | NORMAL_JUMP, 77 | SPRINTING_JUMP, 78 | ATTACK_ENTITY, 79 | DAMAGE_TAKEN, 80 | HUNGER_POTION, 81 | MOVEMENT_DIVE, 82 | MOVEMENT_SWIM, 83 | MOVEMENT_SPRINT, 84 | MOVEMENT_CROUCH, 85 | MOVEMENT_WALK 86 | } 87 | 88 | /** 89 | * Fired each time a {@link #player} does something that changes exhaustion in vanilla Minecraft 90 | * (i.e. jumping, sprinting, etc; see {@link ExhaustingActions} for the full list of possible sources) 91 | * 92 | * This event is fired whenever {@link EntityPlayer#addExhaustion} is called from within Minecraft code.
93 | *
94 | * This event is not {@link Cancelable}.
95 | *
96 | * This event does not have a result. {@link HasResult}
97 | */ 98 | public static class ExhaustingAction extends ExhaustionEvent 99 | { 100 | public final ExhaustingActions source; 101 | public float deltaExhaustion; 102 | 103 | public ExhaustingAction(EntityPlayer player, ExhaustingActions source, float deltaExhaustion) 104 | { 105 | super(player); 106 | this.source = source; 107 | this.deltaExhaustion = deltaExhaustion; 108 | } 109 | } 110 | 111 | /** 112 | * Fired every time max exhaustion level is retrieved to allow control over its value. 113 | * 114 | * This event is fired in {@link FoodStats#onUpdate} and in {@link AppleCoreAPI}.
115 | *
116 | * {@link #maxExhaustionLevel} contains the exhaustion level that will trigger a hunger/saturation decrement.
117 | *
118 | * This event is not {@link Cancelable}.
119 | *
120 | * This event does not have a result. {@link HasResult}
121 | */ 122 | public static class GetMaxExhaustion extends ExhaustionEvent 123 | { 124 | public float maxExhaustionLevel = 4f; 125 | 126 | public GetMaxExhaustion(EntityPlayer player) 127 | { 128 | super(player); 129 | } 130 | } 131 | 132 | /** 133 | * Fired once exhaustionLevel exceeds maxExhaustionLevel (see {@link GetMaxExhaustion}), 134 | * in order to control how exhaustion affects hunger/saturation. 135 | * 136 | * This event is fired in {@link FoodStats#onUpdate}.
137 | *
138 | * {@link #currentExhaustionLevel} contains the exhaustion level of the {@link #player}.
139 | * {@link #deltaExhaustion} contains the delta to be applied to exhaustion level (default: {@link GetMaxExhaustion#maxExhaustionLevel}).
140 | * {@link #deltaHunger} contains the delta to be applied to hunger.
141 | * {@link #deltaSaturation} contains the delta to be applied to saturation.
142 | *
143 | * Note: {@link #deltaHunger} and {@link #deltaSaturation} will vary depending on their vanilla conditionals. 144 | * For example, deltaHunger will be 0 when this event is fired in Peaceful difficulty.
145 | *
146 | * This event is {@link Cancelable}.
147 | * If this event is canceled, it will skip applying the delta values to hunger and saturation.
148 | *
149 | * This event does not have a result. {@link HasResult}
150 | */ 151 | @Cancelable 152 | public static class Exhausted extends ExhaustionEvent 153 | { 154 | public final float currentExhaustionLevel; 155 | public float deltaExhaustion; 156 | public int deltaHunger = -1; 157 | public float deltaSaturation = -1f; 158 | 159 | public Exhausted(EntityPlayer player, float exhaustionToRemove, float currentExhaustionLevel) 160 | { 161 | super(player); 162 | this.deltaExhaustion = -exhaustionToRemove; 163 | this.currentExhaustionLevel = currentExhaustionLevel; 164 | 165 | boolean shouldDecreaseSaturationLevel = player.getFoodStats().getSaturationLevel() > 0f; 166 | 167 | if (!shouldDecreaseSaturationLevel) 168 | deltaSaturation = 0f; 169 | 170 | EnumDifficulty difficulty = player.world.getDifficulty(); 171 | boolean shouldDecreaseFoodLevel = !shouldDecreaseSaturationLevel && difficulty != EnumDifficulty.PEACEFUL; 172 | 173 | if (!shouldDecreaseFoodLevel) 174 | deltaHunger = 0; 175 | } 176 | } 177 | 178 | /** 179 | * Fired every time the exhaustion level is capped to allow control over the cap. 180 | * 181 | * This event is fired in {@link FoodStats#addExhaustion}.
182 | *
183 | * {@link #exhaustionLevelCap} contains the exhaustion level that will be used to cap the exhaustion level.
184 | *
185 | * This event is not {@link Cancelable}.
186 | *
187 | * This event does not have a result. {@link HasResult}
188 | */ 189 | public static class GetExhaustionCap extends ExhaustionEvent 190 | { 191 | public float exhaustionLevelCap = 40f; 192 | 193 | public GetExhaustionCap(EntityPlayer player) 194 | { 195 | super(player); 196 | } 197 | } 198 | } -------------------------------------------------------------------------------- /java/squeek/applecore/api/hunger/HealthRegenEvent.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.api.hunger; 2 | 3 | import net.minecraft.entity.player.EntityPlayer; 4 | import net.minecraft.util.FoodStats; 5 | import net.minecraftforge.common.MinecraftForge; 6 | import net.minecraftforge.fml.common.eventhandler.Cancelable; 7 | import net.minecraftforge.fml.common.eventhandler.Event; 8 | import squeek.applecore.api.AppleCoreAPI; 9 | 10 | /** 11 | * Base class for all HealthRegenEvent events.
12 | *
13 | * All children of this event are fired on the {@link MinecraftForge#EVENT_BUS}. 14 | */ 15 | public abstract class HealthRegenEvent extends Event 16 | { 17 | public final EntityPlayer player; 18 | 19 | public HealthRegenEvent(EntityPlayer player) 20 | { 21 | this.player = player; 22 | } 23 | 24 | /** 25 | * Fired each FoodStats update to determine whether or not health regen from food is allowed for the {@link #player}. 26 | * However, this event will not be fired if saturated regen occurs, as saturated health regen will take precedence 27 | * over normal health regen (see {@link AllowSaturatedRegen}). 28 | * 29 | * This event is fired in {@link FoodStats#onUpdate}.
30 | *
31 | * This event is not {@link Cancelable}.
32 | *
33 | * This event uses the {@link Result}. {@link HasResult}
34 | * {@link Result#DEFAULT} will use the vanilla conditionals.
35 | * {@link Result#ALLOW} will allow regen without condition.
36 | * {@link Result#DENY} will deny regen without condition.
37 | */ 38 | @HasResult 39 | public static class AllowRegen extends HealthRegenEvent 40 | { 41 | public AllowRegen(EntityPlayer player) 42 | { 43 | super(player); 44 | } 45 | } 46 | 47 | /** 48 | * Fired every time the regen tick period is retrieved to allow control over its value. 49 | * 50 | * This event is fired in {@link FoodStats#onUpdate} and in {@link AppleCoreAPI}.
51 | *
52 | * {@link #regenTickPeriod} contains the number of ticks between each regen.
53 | *
54 | * This event is not {@link Cancelable}.
55 | *
56 | * This event does not have a {@link Result}. {@link HasResult}
57 | */ 58 | public static class GetRegenTickPeriod extends HealthRegenEvent 59 | { 60 | public int regenTickPeriod = 80; 61 | 62 | public GetRegenTickPeriod(EntityPlayer player) 63 | { 64 | super(player); 65 | } 66 | } 67 | 68 | /** 69 | * Fired once the ticks since last regen reaches regenTickPeriod (see {@link GetRegenTickPeriod}), 70 | * in order to control how regen affects health/exhaustion. 71 | * 72 | * This event is fired in {@link FoodStats#onUpdate}.
73 | *
74 | * {@link #deltaHealth} contains the delta to be applied to health.
75 | * {@link #deltaExhaustion} contains the delta to be applied to exhaustion level.
76 | *
77 | * This event is {@link Cancelable}.
78 | * If this event is canceled, it will skip applying the delta values to health and exhaustion.
79 | *
80 | * This event does not have a {@link Result}. {@link HasResult}
81 | */ 82 | @Cancelable 83 | public static class Regen extends HealthRegenEvent 84 | { 85 | public float deltaHealth = 1f; 86 | public float deltaExhaustion = 6f; 87 | 88 | public Regen(EntityPlayer player) 89 | { 90 | super(player); 91 | } 92 | } 93 | 94 | /** 95 | * Fired every second for each player while in Peaceful difficulty, 96 | * in order to control how much health to passively regenerate. 97 | * 98 | * This event is fired in {@link EntityPlayer#onLivingUpdate}.
99 | *
100 | * This event is never fired if the game rule "naturalRegeneration" is false.
101 | *
102 | * {@link #deltaHealth} contains the delta to be applied to health.
103 | *
104 | * This event is {@link Cancelable}.
105 | * If this event is canceled, it will skip healing the player.
106 | *
107 | * This event does not have a {@link Result}. {@link HasResult}
108 | */ 109 | @Cancelable 110 | public static class PeacefulRegen extends HealthRegenEvent 111 | { 112 | public float deltaHealth = 1f; 113 | 114 | public PeacefulRegen(EntityPlayer player) 115 | { 116 | super(player); 117 | } 118 | } 119 | 120 | /** 121 | * Fired each FoodStats update to determine whether or health regen from full hunger + saturation is allowed for the {@link #player}. 122 | * 123 | * Saturated health regen will take precedence over normal health regen. 124 | * 125 | * This event is fired in {@link FoodStats#onUpdate}.
126 | *
127 | * This event is not {@link Cancelable}.
128 | *
129 | * This event uses the {@link Result}. {@link HasResult}
130 | * {@link Result#DEFAULT} will use the vanilla conditionals.
131 | * {@link Result#ALLOW} will allow regen without condition.
132 | * {@link Result#DENY} will deny regen without condition.
133 | */ 134 | @HasResult 135 | public static class AllowSaturatedRegen extends HealthRegenEvent 136 | { 137 | public AllowSaturatedRegen(EntityPlayer player) 138 | { 139 | super(player); 140 | } 141 | } 142 | 143 | /** 144 | * Fired every time the saturated regen tick period is retrieved to allow control over its value. 145 | * 146 | * This event is fired in {@link FoodStats#onUpdate} and in {@link AppleCoreAPI}.
147 | *
148 | * {@link #regenTickPeriod} contains the number of ticks between each saturated regen.
149 | *
150 | * This event is not {@link Cancelable}.
151 | *
152 | * This event does not have a {@link Result}. {@link HasResult}
153 | */ 154 | public static class GetSaturatedRegenTickPeriod extends HealthRegenEvent 155 | { 156 | public int regenTickPeriod = 10; 157 | 158 | public GetSaturatedRegenTickPeriod(EntityPlayer player) 159 | { 160 | super(player); 161 | } 162 | } 163 | 164 | /** 165 | * Fired once the ticks since last regen reaches regenTickPeriod (see {@link GetSaturatedRegenTickPeriod}), 166 | * in order to control how regen affects health/exhaustion. 167 | * 168 | * By default, the amount of health restored depends on the player's current saturation level. 169 | * 170 | * This event is fired in {@link FoodStats#onUpdate}.
171 | *
172 | * {@link #deltaHealth} contains the delta to be applied to health.
173 | * {@link #deltaExhaustion} contains the delta to be applied to exhaustion level.
174 | *
175 | * This event is {@link Cancelable}.
176 | * If this event is canceled, it will skip applying the delta values to health and exhaustion.
177 | *
178 | * This event does not have a {@link Result}. {@link HasResult}
179 | */ 180 | @Cancelable 181 | public static class SaturatedRegen extends HealthRegenEvent 182 | { 183 | public float deltaHealth; 184 | public float deltaExhaustion; 185 | 186 | public SaturatedRegen(EntityPlayer player) 187 | { 188 | super(player); 189 | this.deltaExhaustion = Math.min(player.getFoodStats().getSaturationLevel(), 6.0F); 190 | this.deltaHealth = deltaExhaustion / 6.0F; 191 | } 192 | } 193 | } -------------------------------------------------------------------------------- /java/squeek/applecore/api/hunger/HungerEvent.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.api.hunger; 2 | 3 | import net.minecraft.entity.player.EntityPlayer; 4 | import net.minecraft.util.FoodStats; 5 | import net.minecraftforge.common.MinecraftForge; 6 | import net.minecraftforge.fml.common.eventhandler.Cancelable; 7 | import net.minecraftforge.fml.common.eventhandler.Event; 8 | import squeek.applecore.api.AppleCoreAPI; 9 | 10 | /** 11 | * Base class for all HungerEvent events.
12 | *
13 | * All children of this event are fired on the {@link MinecraftForge#EVENT_BUS}. 14 | */ 15 | public abstract class HungerEvent extends Event 16 | { 17 | /** 18 | * Fired every time max hunger level is retrieved to allow control over its value. 19 | *

20 | * Note: This also affects max saturation, as saturation is bounded by the player's 21 | * current hunger level (that is, a max of 40 hunger would also mean a max of 40 22 | * saturation). 23 | *

24 | * This event is fired in {@link FoodStats#needFood()} and in {@link AppleCoreAPI}.
25 | *
26 | * {@link #maxHunger} contains the max hunger of the player.
27 | * {@link #player} contains the player.
28 | *
29 | * This event is not {@link Cancelable}.
30 | *
31 | * This event does not have a result. {@link HasResult}
32 | */ 33 | public static class GetMaxHunger extends HungerEvent 34 | { 35 | public int maxHunger = 20; 36 | public final EntityPlayer player; 37 | 38 | public GetMaxHunger(EntityPlayer player) 39 | { 40 | this.player = player; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /java/squeek/applecore/api/hunger/HungerRegenEvent.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.api.hunger; 2 | 3 | import net.minecraft.entity.player.EntityPlayer; 4 | import net.minecraftforge.common.MinecraftForge; 5 | import net.minecraftforge.fml.common.eventhandler.Cancelable; 6 | import net.minecraftforge.fml.common.eventhandler.Event; 7 | 8 | /** 9 | * Base class for all HealthRegenEvent events.
10 | *
11 | * All children of this event are fired on the {@link MinecraftForge#EVENT_BUS}. 12 | */ 13 | public class HungerRegenEvent extends Event 14 | { 15 | public final EntityPlayer player; 16 | 17 | public HungerRegenEvent(EntityPlayer player) 18 | { 19 | this.player = player; 20 | } 21 | 22 | /** 23 | * Fired twice every second for each player while in Peaceful difficulty, 24 | * in order to control how much hunger to passively regenerate. 25 | * 26 | * This event is fired in {@link EntityPlayer#onLivingUpdate}.
27 | *
28 | * This event is never fired if the game rule "naturalRegeneration" is false.
29 | *
30 | * {@link #deltaHunger} contains the delta to be applied to hunger.
31 | *
32 | * This event is {@link Cancelable}.
33 | * If this event is canceled, it will skip adding hunger to the player.
34 | *
35 | * This event does not have a {@link Result}. {@link HasResult}
36 | */ 37 | @Cancelable 38 | public static class PeacefulRegen extends HungerRegenEvent 39 | { 40 | public int deltaHunger = 1; 41 | 42 | public PeacefulRegen(EntityPlayer player) 43 | { 44 | super(player); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /java/squeek/applecore/api/hunger/StarvationEvent.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.api.hunger; 2 | 3 | import net.minecraft.entity.player.EntityPlayer; 4 | import net.minecraft.util.FoodStats; 5 | import net.minecraft.world.EnumDifficulty; 6 | import net.minecraftforge.common.MinecraftForge; 7 | import net.minecraftforge.fml.common.eventhandler.Cancelable; 8 | import net.minecraftforge.fml.common.eventhandler.Event; 9 | import squeek.applecore.api.AppleCoreAPI; 10 | 11 | /** 12 | * Base class for all StarvationEvent events.
13 | *
14 | * All children of this event are fired on the {@link MinecraftForge#EVENT_BUS}. 15 | */ 16 | public abstract class StarvationEvent extends Event 17 | { 18 | public final EntityPlayer player; 19 | 20 | public StarvationEvent(EntityPlayer player) 21 | { 22 | this.player = player; 23 | } 24 | 25 | /** 26 | * Fired each FoodStats update to determine whether or not starvation is allowed for the {@link #player}. 27 | * 28 | * This event is fired in {@link FoodStats#onUpdate}.
29 | *
30 | * This event is not {@link Cancelable}.
31 | *
32 | * This event uses the {@link Result}. {@link HasResult}
33 | * {@link Result#DEFAULT} will use the vanilla conditionals. 34 | * {@link Result#ALLOW} will allow starvation without condition. 35 | * {@link Result#DENY} will deny starvation without condition. 36 | */ 37 | @HasResult 38 | public static class AllowStarvation extends StarvationEvent 39 | { 40 | public AllowStarvation(EntityPlayer player) 41 | { 42 | super(player); 43 | } 44 | } 45 | 46 | /** 47 | * Fired every time the starve tick period is retrieved to allow control over its value. 48 | * 49 | * This event is fired in {@link FoodStats#onUpdate} and in {@link AppleCoreAPI}.
50 | *
51 | * {@link #starveTickPeriod} contains the number of ticks between starvation damage being done.
52 | *
53 | * This event is not {@link Cancelable}.
54 | *
55 | * This event does not have a {@link Result}. {@link HasResult}
56 | */ 57 | public static class GetStarveTickPeriod extends StarvationEvent 58 | { 59 | public int starveTickPeriod = 80; 60 | 61 | public GetStarveTickPeriod(EntityPlayer player) 62 | { 63 | super(player); 64 | } 65 | } 66 | 67 | /** 68 | * Fired once the time since last starvation damage reaches starveTickPeriod (see {@link GetStarveTickPeriod}), 69 | * in order to control how much starvation damage to do. 70 | * 71 | * This event is fired in {@link FoodStats#onUpdate}.
72 | *
73 | * {@link #starveDamage} contains the amount of damage to deal from starvation.
74 | *
75 | * This event is {@link Cancelable}.
76 | * If this event is canceled, it will skip dealing starvation damage.
77 | *
78 | * This event does not have a {@link Result}. {@link HasResult}
79 | */ 80 | @Cancelable 81 | public static class Starve extends StarvationEvent 82 | { 83 | public float starveDamage = 1f; 84 | 85 | public Starve(EntityPlayer player) 86 | { 87 | super(player); 88 | 89 | EnumDifficulty difficulty = player.world.getDifficulty(); 90 | boolean shouldDoDamage = player.getHealth() > 10.0F || difficulty == EnumDifficulty.HARD || player.getHealth() > 1.0F && difficulty == EnumDifficulty.NORMAL; 91 | 92 | if (!shouldDoDamage) 93 | starveDamage = 0f; 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /java/squeek/applecore/api/package-info.java: -------------------------------------------------------------------------------- 1 | @API(apiVersion = "${apiversion}", owner = "applecore", provides = "AppleCoreAPI") 2 | package squeek.applecore.api; 3 | 4 | import net.minecraftforge.fml.common.API; -------------------------------------------------------------------------------- /java/squeek/applecore/api_impl/AppleCoreAccessorMutatorImpl.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.api_impl; 2 | 3 | import net.minecraft.block.Block; 4 | import net.minecraft.entity.player.EntityPlayer; 5 | import net.minecraft.item.EnumAction; 6 | import net.minecraft.item.ItemFood; 7 | import net.minecraft.item.ItemStack; 8 | import net.minecraftforge.common.MinecraftForge; 9 | import net.minecraftforge.fml.relauncher.ReflectionHelper; 10 | import squeek.applecore.api.AppleCoreAPI; 11 | import squeek.applecore.api.IAppleCoreAccessor; 12 | import squeek.applecore.api.IAppleCoreMutator; 13 | import squeek.applecore.api.food.FoodEvent; 14 | import squeek.applecore.api.food.FoodValues; 15 | import squeek.applecore.api.food.IEdible; 16 | import squeek.applecore.api.hunger.ExhaustionEvent; 17 | import squeek.applecore.api.hunger.HealthRegenEvent; 18 | import squeek.applecore.api.hunger.HungerEvent; 19 | import squeek.applecore.api.hunger.StarvationEvent; 20 | import squeek.applecore.asm.util.IAppleCoreFoodStats; 21 | 22 | import javax.annotation.Nonnull; 23 | import java.lang.reflect.Field; 24 | 25 | public enum AppleCoreAccessorMutatorImpl implements IAppleCoreAccessor, IAppleCoreMutator 26 | { 27 | INSTANCE; 28 | 29 | AppleCoreAccessorMutatorImpl() 30 | { 31 | AppleCoreAPI.accessor = this; 32 | AppleCoreAPI.mutator = this; 33 | } 34 | 35 | /* 36 | * IAppleCoreAccessor implementation 37 | */ 38 | @Override 39 | public boolean isFood(@Nonnull ItemStack food) 40 | { 41 | return isEdible(food) && getUnmodifiedFoodValues(food) != null; 42 | } 43 | 44 | private boolean isEdible(@Nonnull ItemStack food) 45 | { 46 | if (food == ItemStack.EMPTY) 47 | return false; 48 | 49 | EnumAction useAction = food.getItem().getItemUseAction(food); 50 | if (useAction == EnumAction.EAT || useAction == EnumAction.DRINK) 51 | return true; 52 | 53 | // assume Block-based foods are edible 54 | return AppleCoreAPI.registry.getEdibleBlockFromItem(food.getItem()) != null; 55 | } 56 | 57 | @Override 58 | public boolean canPlayerEatFood(@Nonnull ItemStack food, @Nonnull EntityPlayer player) 59 | { 60 | return player.getFoodStats().getFoodLevel() < getMaxHunger(player) || isAlwaysEdible(food); 61 | } 62 | 63 | protected static final Field itemFoodAlwaysEdible = ReflectionHelper.findField(ItemFood.class, "alwaysEdible", "field_77852_bZ", "e"); 64 | 65 | private boolean isAlwaysEdible(@Nonnull ItemStack food) 66 | { 67 | if (food == ItemStack.EMPTY || !(food.getItem() instanceof ItemFood)) 68 | return false; 69 | 70 | try 71 | { 72 | return itemFoodAlwaysEdible.getBoolean(food.getItem()); 73 | } 74 | catch (IllegalAccessException e) 75 | { 76 | throw new RuntimeException(e); 77 | } 78 | } 79 | 80 | @Override 81 | public FoodValues getUnmodifiedFoodValues(@Nonnull ItemStack food) 82 | { 83 | if (food != ItemStack.EMPTY) 84 | { 85 | if (food.getItem() instanceof IEdible) 86 | return ((IEdible) food.getItem()).getFoodValues(food); 87 | else if (food.getItem() instanceof ItemFood) 88 | return getItemFoodValues((ItemFood) food.getItem(), food); 89 | 90 | Block block = AppleCoreAPI.registry.getEdibleBlockFromItem(food.getItem()); 91 | if (block != null && block instanceof IEdible) 92 | return ((IEdible) block).getFoodValues(food); 93 | } 94 | return null; 95 | } 96 | 97 | private FoodValues getItemFoodValues(@Nonnull ItemFood itemFood, @Nonnull ItemStack itemStack) 98 | { 99 | return new FoodValues(itemFood.getHealAmount(itemStack), itemFood.getSaturationModifier(itemStack)); 100 | } 101 | 102 | @Override 103 | public FoodValues getFoodValues(@Nonnull ItemStack food) 104 | { 105 | FoodValues foodValues = getUnmodifiedFoodValues(food); 106 | if (foodValues != null) 107 | { 108 | FoodEvent.GetFoodValues event = new FoodEvent.GetFoodValues(food, foodValues); 109 | MinecraftForge.EVENT_BUS.post(event); 110 | return event.foodValues; 111 | } 112 | return null; 113 | } 114 | 115 | @Override 116 | public FoodValues getFoodValuesForPlayer(@Nonnull ItemStack food, EntityPlayer player) 117 | { 118 | FoodValues foodValues = getFoodValues(food); 119 | if (foodValues != null) 120 | { 121 | FoodEvent.GetPlayerFoodValues event = new FoodEvent.GetPlayerFoodValues(player, food, foodValues); 122 | MinecraftForge.EVENT_BUS.post(event); 123 | return event.foodValues; 124 | } 125 | return null; 126 | } 127 | 128 | @Override 129 | public float getExhaustion(EntityPlayer player) 130 | { 131 | try 132 | { 133 | return getAppleCoreFoodStats(player).getExhaustion(); 134 | } 135 | catch (RuntimeException e) 136 | { 137 | throw e; 138 | } 139 | catch (Exception e) 140 | { 141 | return 0f; 142 | } 143 | } 144 | 145 | @Override 146 | public float getMaxExhaustion(EntityPlayer player) 147 | { 148 | ExhaustionEvent.GetMaxExhaustion event = new ExhaustionEvent.GetMaxExhaustion(player); 149 | MinecraftForge.EVENT_BUS.post(event); 150 | return event.maxExhaustionLevel; 151 | } 152 | 153 | @Override 154 | public int getHealthRegenTickPeriod(EntityPlayer player) 155 | { 156 | HealthRegenEvent.GetRegenTickPeriod event = new HealthRegenEvent.GetRegenTickPeriod(player); 157 | MinecraftForge.EVENT_BUS.post(event); 158 | return event.regenTickPeriod; 159 | } 160 | 161 | @Override 162 | public int getStarveDamageTickPeriod(EntityPlayer player) 163 | { 164 | StarvationEvent.GetStarveTickPeriod event = new StarvationEvent.GetStarveTickPeriod(player); 165 | MinecraftForge.EVENT_BUS.post(event); 166 | return event.starveTickPeriod; 167 | } 168 | 169 | @Override 170 | public int getSaturatedHealthRegenTickPeriod(EntityPlayer player) 171 | { 172 | HealthRegenEvent.GetSaturatedRegenTickPeriod event = new HealthRegenEvent.GetSaturatedRegenTickPeriod(player); 173 | MinecraftForge.EVENT_BUS.post(event); 174 | return event.regenTickPeriod; 175 | } 176 | 177 | @Override 178 | public int getMaxHunger(EntityPlayer player) 179 | { 180 | HungerEvent.GetMaxHunger event = new HungerEvent.GetMaxHunger(player); 181 | MinecraftForge.EVENT_BUS.post(event); 182 | return event.maxHunger; 183 | } 184 | 185 | /* 186 | * IAppleCoreMutator implementation 187 | */ 188 | @Override 189 | public void setExhaustion(EntityPlayer player, float exhaustion) 190 | { 191 | try 192 | { 193 | getAppleCoreFoodStats(player).setExhaustion(exhaustion); 194 | } 195 | catch (RuntimeException e) 196 | { 197 | throw e; 198 | } 199 | catch (Exception e) 200 | { 201 | throw new RuntimeException(e); 202 | } 203 | } 204 | 205 | @Override 206 | public void setHunger(EntityPlayer player, int hunger) 207 | { 208 | player.getFoodStats().setFoodLevel(hunger); 209 | } 210 | 211 | @Override 212 | public void setSaturation(EntityPlayer player, float saturation) 213 | { 214 | try 215 | { 216 | getAppleCoreFoodStats(player).setSaturation(saturation); 217 | } 218 | catch (RuntimeException e) 219 | { 220 | throw e; 221 | } 222 | catch (Exception e) 223 | { 224 | throw new RuntimeException(e); 225 | } 226 | } 227 | 228 | @Override 229 | public void setHealthRegenTickCounter(EntityPlayer player, int tickCounter) 230 | { 231 | try 232 | { 233 | getAppleCoreFoodStats(player).setFoodTimer(tickCounter); 234 | } 235 | catch (RuntimeException e) 236 | { 237 | throw e; 238 | } 239 | catch (Exception e) 240 | { 241 | throw new RuntimeException(e); 242 | } 243 | } 244 | 245 | @Override 246 | public void setStarveDamageTickCounter(EntityPlayer player, int tickCounter) 247 | { 248 | try 249 | { 250 | getAppleCoreFoodStats(player).setStarveTimer(tickCounter); 251 | } 252 | catch (RuntimeException e) 253 | { 254 | throw e; 255 | } 256 | catch (Exception e) 257 | { 258 | throw new RuntimeException(e); 259 | } 260 | } 261 | 262 | public IAppleCoreFoodStats getAppleCoreFoodStats(EntityPlayer player) throws ClassCastException 263 | { 264 | return (IAppleCoreFoodStats) player.getFoodStats(); 265 | } 266 | } -------------------------------------------------------------------------------- /java/squeek/applecore/api_impl/AppleCoreRegistryImpl.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.api_impl; 2 | 3 | import net.minecraft.block.Block; 4 | import net.minecraft.init.Blocks; 5 | import net.minecraft.item.Item; 6 | import net.minecraftforge.fml.common.registry.GameRegistry; 7 | import squeek.applecore.api.AppleCoreAPI; 8 | import squeek.applecore.api.IAppleCoreRegistry; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | public enum AppleCoreRegistryImpl implements IAppleCoreRegistry 14 | { 15 | INSTANCE; 16 | 17 | private Map edibleBlockToItem = new HashMap(); 18 | private Map edibleItemToBlock = new HashMap(); 19 | 20 | private AppleCoreRegistryImpl() 21 | { 22 | AppleCoreAPI.registry = this; 23 | } 24 | 25 | @GameRegistry.ObjectHolder("minecraft:cake") 26 | public static final Block CAKE_BLOCK = null; 27 | 28 | @GameRegistry.ObjectHolder("minecraft:cake") 29 | public static final Item CAKE_ITEM = null; 30 | 31 | public void init() 32 | { 33 | registerEdibleBlock(CAKE_BLOCK, CAKE_ITEM); 34 | } 35 | 36 | @Override 37 | public void registerEdibleBlock(Block block, Item item) 38 | { 39 | edibleBlockToItem.put(block, item); 40 | edibleItemToBlock.put(item, block); 41 | } 42 | 43 | @Override 44 | public Item getItemFromEdibleBlock(Block block) 45 | { 46 | Item item = edibleBlockToItem.get(block); 47 | if (item == null) item = Item.getItemFromBlock(block); 48 | return item; 49 | } 50 | 51 | @Override 52 | public Block getEdibleBlockFromItem(Item item) 53 | { 54 | Block block = edibleItemToBlock.get(item); 55 | if (block == null) block = Block.getBlockFromItem(item); 56 | return block != Blocks.AIR ? block : null; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /java/squeek/applecore/asm/ASMConstants.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.asm; 2 | 3 | import squeek.asmhelper.applecore.ASMHelper; 4 | 5 | public class ASMConstants 6 | { 7 | public static final String HOOKS = "squeek.applecore.asm.Hooks"; 8 | public static final String HOOKS_INTERNAL_CLASS = ASMHelper.toInternalClassName(HOOKS); 9 | public static final String FOOD_VALUES = "squeek.applecore.api.food.FoodValues"; 10 | public static final String IAPPLECOREFOODSTATS = "squeek.applecore.asm.util.IAppleCoreFoodStats"; 11 | public static final String IEDIBLEBLOCK = "squeek.applecore.api.food.IEdibleBlock"; 12 | public static final String IEDIBLE = "squeek.applecore.api.food.IEdible"; 13 | 14 | public static final class ExhaustionEvent 15 | { 16 | public static final String EXHAUSTED = "squeek.applecore.api.hunger.ExhaustionEvent$Exhausted"; 17 | public static final String EXHAUSTING_ACTIONS = "squeek.applecore.api.hunger.ExhaustionEvent$ExhaustingActions"; 18 | } 19 | public static final class HealthRegenEvent 20 | { 21 | public static final String REGEN = "squeek.applecore.api.hunger.HealthRegenEvent$Regen"; 22 | public static final String PEACEFUL_REGEN = "squeek.applecore.api.hunger.HealthRegenEvent$PeacefulRegen"; 23 | } 24 | public static final class HungerRegenEvent 25 | { 26 | public static final String PEACEFUL_REGEN = "squeek.applecore.api.hunger.HungerRegenEvent$PeacefulRegen"; 27 | } 28 | public static final class StarvationEvent 29 | { 30 | public static final String STARVE = "squeek.applecore.api.hunger.StarvationEvent$Starve"; 31 | } 32 | 33 | //Java 34 | public static final String RANDOM = "java.util.Random"; 35 | 36 | //Minecraft 37 | public static final String BLOCK = "net.minecraft.block.Block"; 38 | public static final String BLOCK_CONTAINER = "net.minecraft.block.BlockContainer"; 39 | public static final String BLOCK_ICE = "net.minecraft.block.BlockIce"; 40 | public static final String BLOCK_POS = "net.minecraft.util.math.BlockPos"; 41 | public static final String DAMAGE_SOURCE = "net.minecraft.util.DamageSource"; 42 | public static final String ENTITY = "net.minecraft.entity.Entity"; 43 | public static final String ENTITY_LIVING = "net.minecraft.entity.EntityLivingBase"; 44 | public static final String FOOD_STATS = "net.minecraft.util.FoodStats"; 45 | public static final String HAND = "net.minecraft.util.EnumHand"; 46 | public static final String HAND_SIDE = "net.minecraft.util.EnumHandSide"; 47 | public static final String IBLOCKSTATE = "net.minecraft.block.state.IBlockState"; 48 | public static final String IGROWABLE = "net.minecraft.block.IGrowable"; 49 | public static final String ITEM_FOOD = "net.minecraft.item.ItemFood"; 50 | public static final String ITEM_RENDERER = "net.minecraft.client.renderer.ItemRenderer"; 51 | public static final String MINECRAFT = "net.minecraft.client.Minecraft"; 52 | public static final String PLAYER = "net.minecraft.entity.player.EntityPlayer"; 53 | public static final String PLAYER_SP = "net.minecraft.client.entity.EntityPlayerSP"; 54 | public static final String POTION = "net.minecraft.potion.Potion"; 55 | public static final String ITEM_STACK = "net.minecraft.item.ItemStack"; 56 | public static final String TILE_ENTITY = "net.minecraft.tileentity.TileEntity"; 57 | public static final String STAT_BASE = "net.minecraft.stats.StatBase"; 58 | public static final String STAT_LIST = "net.minecraft.stats.StatList"; 59 | public static final String WORLD = "net.minecraft.world.World"; 60 | 61 | //FML & Forge 62 | public static final String GUI_INGAME_FORGE = "net.minecraftforge.client.GuiIngameForge"; 63 | 64 | //Blocks 65 | public static final String CAKE = "net.minecraft.block.BlockCake"; 66 | } -------------------------------------------------------------------------------- /java/squeek/applecore/asm/Hooks.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.asm; 2 | 3 | import net.minecraft.block.Block; 4 | import net.minecraft.block.state.IBlockState; 5 | import net.minecraft.entity.EntityLivingBase; 6 | import net.minecraft.entity.player.EntityPlayer; 7 | import net.minecraft.item.EnumAction; 8 | import net.minecraft.item.ItemFood; 9 | import net.minecraft.item.ItemStack; 10 | import net.minecraft.util.DamageSource; 11 | import net.minecraft.util.FoodStats; 12 | import net.minecraft.util.math.BlockPos; 13 | import net.minecraft.util.math.MathHelper; 14 | import net.minecraft.world.World; 15 | import net.minecraftforge.common.MinecraftForge; 16 | import net.minecraftforge.fml.common.eventhandler.Event; 17 | import net.minecraftforge.fml.common.eventhandler.Event.Result; 18 | import net.minecraftforge.fml.relauncher.Side; 19 | import net.minecraftforge.fml.relauncher.SideOnly; 20 | import squeek.applecore.api.AppleCoreAPI; 21 | import squeek.applecore.api.food.FoodEvent; 22 | import squeek.applecore.api.food.FoodValues; 23 | import squeek.applecore.api.food.ItemFoodProxy; 24 | import squeek.applecore.api.hunger.ExhaustionEvent; 25 | import squeek.applecore.api.hunger.HealthRegenEvent; 26 | import squeek.applecore.api.hunger.HungerRegenEvent; 27 | import squeek.applecore.api.hunger.StarvationEvent; 28 | import squeek.applecore.asm.util.IAppleCoreFoodStats; 29 | 30 | import javax.annotation.Nonnull; 31 | import java.util.Random; 32 | 33 | public class Hooks 34 | { 35 | private static void verifyFoodStats(FoodStats foodStats, EntityPlayer player) 36 | { 37 | if (!(foodStats instanceof IAppleCoreFoodStats)) 38 | { 39 | String playerName = player != null ? player.getName() : ""; 40 | String className = foodStats.getClass().getName(); 41 | throw new RuntimeException("FoodStats does not implement IAppleCoreFoodStats on player '"+playerName+"' (class = "+className+"). This likely means that AppleCore has failed catastrophically in some way."); 42 | } 43 | if (((IAppleCoreFoodStats)foodStats).getPlayer() == null) 44 | { 45 | // attempt to salvage the situation by setting the field here 46 | if (player != null) 47 | { 48 | ((IAppleCoreFoodStats)foodStats).setPlayer(player); 49 | return; 50 | } 51 | String playerName = ""; 52 | String className = foodStats.getClass().getName(); 53 | throw new RuntimeException("FoodStats has a null player field (this field is added by AppleCore at runtime) on player '"+playerName+"' (class = "+className+"). This likely means that some mod has overloaded FoodStats, which is incompatible with AppleCore."); 54 | } 55 | } 56 | 57 | public static boolean onAppleCoreFoodStatsUpdate(FoodStats foodStats, EntityPlayer player) 58 | { 59 | verifyFoodStats(foodStats, player); 60 | 61 | IAppleCoreFoodStats appleCoreFoodStats = (IAppleCoreFoodStats) foodStats; 62 | 63 | appleCoreFoodStats.setPrevFoodLevel(foodStats.getFoodLevel()); 64 | 65 | Result allowExhaustionResult = Hooks.fireAllowExhaustionEvent(player); 66 | float maxExhaustion = Hooks.fireExhaustionTickEvent(player, appleCoreFoodStats.getExhaustion()); 67 | if (allowExhaustionResult == Result.ALLOW || (allowExhaustionResult == Result.DEFAULT && appleCoreFoodStats.getExhaustion() >= maxExhaustion)) 68 | { 69 | ExhaustionEvent.Exhausted exhaustedEvent = Hooks.fireExhaustionMaxEvent(player, maxExhaustion, appleCoreFoodStats.getExhaustion()); 70 | 71 | appleCoreFoodStats.setExhaustion(appleCoreFoodStats.getExhaustion() + exhaustedEvent.deltaExhaustion); 72 | if (!exhaustedEvent.isCanceled()) 73 | { 74 | appleCoreFoodStats.setSaturation(Math.max(foodStats.getSaturationLevel() + exhaustedEvent.deltaSaturation, 0.0F)); 75 | foodStats.setFoodLevel(Math.max(foodStats.getFoodLevel() + exhaustedEvent.deltaHunger, 0)); 76 | } 77 | } 78 | 79 | boolean hasNaturalRegen = player.world.getGameRules().getBoolean("naturalRegeneration"); 80 | 81 | Result allowSaturatedRegenResult = Hooks.fireAllowSaturatedRegenEvent(player); 82 | boolean shouldDoSaturatedRegen = allowSaturatedRegenResult == Result.ALLOW || (allowSaturatedRegenResult == Result.DEFAULT && hasNaturalRegen && foodStats.getSaturationLevel() > 0.0F && player.shouldHeal() && foodStats.getFoodLevel() >= 20); 83 | Result allowRegenResult = shouldDoSaturatedRegen ? Result.DENY : Hooks.fireAllowRegenEvent(player); 84 | boolean shouldDoRegen = allowRegenResult == Result.ALLOW || (allowRegenResult == Result.DEFAULT && hasNaturalRegen && foodStats.getFoodLevel() >= 18 && player.shouldHeal()); 85 | if (shouldDoSaturatedRegen) 86 | { 87 | appleCoreFoodStats.setFoodTimer(appleCoreFoodStats.getFoodTimer() + 1); 88 | 89 | if (appleCoreFoodStats.getFoodTimer() >= Hooks.fireSaturatedRegenTickEvent(player)) 90 | { 91 | HealthRegenEvent.SaturatedRegen saturatedRegenEvent = Hooks.fireSaturatedRegenEvent(player); 92 | if (!saturatedRegenEvent.isCanceled()) 93 | { 94 | player.heal(saturatedRegenEvent.deltaHealth); 95 | foodStats.addExhaustion(saturatedRegenEvent.deltaExhaustion); 96 | } 97 | appleCoreFoodStats.setFoodTimer(0); 98 | } 99 | } 100 | else if (shouldDoRegen) 101 | { 102 | appleCoreFoodStats.setFoodTimer(appleCoreFoodStats.getFoodTimer() + 1); 103 | 104 | if (appleCoreFoodStats.getFoodTimer() >= Hooks.fireRegenTickEvent(player)) 105 | { 106 | HealthRegenEvent.Regen regenEvent = Hooks.fireRegenEvent(player); 107 | if (!regenEvent.isCanceled()) 108 | { 109 | player.heal(regenEvent.deltaHealth); 110 | foodStats.addExhaustion(regenEvent.deltaExhaustion); 111 | } 112 | appleCoreFoodStats.setFoodTimer(0); 113 | } 114 | } 115 | else 116 | { 117 | appleCoreFoodStats.setFoodTimer(0); 118 | } 119 | 120 | Result allowStarvationResult = Hooks.fireAllowStarvation(player); 121 | if (allowStarvationResult == Result.ALLOW || (allowStarvationResult == Result.DEFAULT && foodStats.getFoodLevel() <= 0)) 122 | { 123 | appleCoreFoodStats.setStarveTimer(appleCoreFoodStats.getStarveTimer() + 1); 124 | 125 | if (appleCoreFoodStats.getStarveTimer() >= Hooks.fireStarvationTickEvent(player)) 126 | { 127 | StarvationEvent.Starve starveEvent = Hooks.fireStarveEvent(player); 128 | if (!starveEvent.isCanceled()) 129 | { 130 | player.attackEntityFrom(DamageSource.STARVE, starveEvent.starveDamage); 131 | } 132 | appleCoreFoodStats.setStarveTimer(0); 133 | } 134 | } 135 | else 136 | { 137 | appleCoreFoodStats.setStarveTimer(0); 138 | } 139 | 140 | return true; 141 | } 142 | 143 | public static FoodValues onFoodStatsAdded(FoodStats foodStats, @Nonnull ItemFood itemFood, @Nonnull ItemStack itemStack, EntityPlayer player) 144 | { 145 | verifyFoodStats(foodStats, player); 146 | return AppleCoreAPI.accessor.getFoodValuesForPlayer(itemStack, player); 147 | } 148 | 149 | public static void onPostFoodStatsAdded(FoodStats foodStats, @Nonnull ItemFood itemFood, @Nonnull ItemStack itemStack, FoodValues foodValues, int hungerAdded, float saturationAdded, EntityPlayer player) 150 | { 151 | verifyFoodStats(foodStats, player); 152 | MinecraftForge.EVENT_BUS.post(new FoodEvent.FoodEaten(player, itemStack, foodValues, hungerAdded, saturationAdded)); 153 | } 154 | 155 | public static int getItemInUseMaxCount(EntityLivingBase entityLiving, int savedMaxDuration) 156 | { 157 | EnumAction useAction = entityLiving.getActiveItemStack().getItemUseAction(); 158 | if (useAction == EnumAction.EAT || useAction == EnumAction.DRINK) 159 | return savedMaxDuration; 160 | else 161 | return entityLiving.getActiveItemStack().getMaxItemUseDuration(); 162 | } 163 | 164 | public static void onBlockFoodEaten(Block block, World world, EntityPlayer player) 165 | { 166 | squeek.applecore.api.food.IEdible edibleBlock = (squeek.applecore.api.food.IEdible)block; 167 | ItemStack itemStack = new ItemStack(AppleCoreAPI.registry.getItemFromEdibleBlock(block)); 168 | new ItemFoodProxy(edibleBlock).onEaten(itemStack, player); 169 | } 170 | 171 | public static Result fireAllowExhaustionEvent(EntityPlayer player) 172 | { 173 | ExhaustionEvent.AllowExhaustion event = new ExhaustionEvent.AllowExhaustion(player); 174 | MinecraftForge.EVENT_BUS.post(event); 175 | return event.getResult(); 176 | } 177 | 178 | public static float fireExhaustionTickEvent(EntityPlayer player, float foodExhaustionLevel) 179 | { 180 | return AppleCoreAPI.accessor.getMaxExhaustion(player); 181 | } 182 | 183 | public static ExhaustionEvent.Exhausted fireExhaustionMaxEvent(EntityPlayer player, float maxExhaustionLevel, float foodExhaustionLevel) 184 | { 185 | ExhaustionEvent.Exhausted event = new ExhaustionEvent.Exhausted(player, maxExhaustionLevel, foodExhaustionLevel); 186 | MinecraftForge.EVENT_BUS.post(event); 187 | return event; 188 | } 189 | 190 | public static Result fireAllowRegenEvent(EntityPlayer player) 191 | { 192 | HealthRegenEvent.AllowRegen event = new HealthRegenEvent.AllowRegen(player); 193 | MinecraftForge.EVENT_BUS.post(event); 194 | return event.getResult(); 195 | } 196 | 197 | public static Result fireAllowSaturatedRegenEvent(EntityPlayer player) 198 | { 199 | HealthRegenEvent.AllowSaturatedRegen event = new HealthRegenEvent.AllowSaturatedRegen(player); 200 | MinecraftForge.EVENT_BUS.post(event); 201 | return event.getResult(); 202 | } 203 | 204 | public static int fireRegenTickEvent(EntityPlayer player) 205 | { 206 | return AppleCoreAPI.accessor.getHealthRegenTickPeriod(player); 207 | } 208 | 209 | public static int fireSaturatedRegenTickEvent(EntityPlayer player) 210 | { 211 | return AppleCoreAPI.accessor.getSaturatedHealthRegenTickPeriod(player); 212 | } 213 | 214 | public static HealthRegenEvent.Regen fireRegenEvent(EntityPlayer player) 215 | { 216 | HealthRegenEvent.Regen event = new HealthRegenEvent.Regen(player); 217 | MinecraftForge.EVENT_BUS.post(event); 218 | return event; 219 | } 220 | 221 | public static HealthRegenEvent.SaturatedRegen fireSaturatedRegenEvent(EntityPlayer player) 222 | { 223 | HealthRegenEvent.SaturatedRegen event = new HealthRegenEvent.SaturatedRegen(player); 224 | MinecraftForge.EVENT_BUS.post(event); 225 | return event; 226 | } 227 | 228 | public static HealthRegenEvent.PeacefulRegen firePeacefulRegenEvent(EntityPlayer player) 229 | { 230 | HealthRegenEvent.PeacefulRegen event = new HealthRegenEvent.PeacefulRegen(player); 231 | MinecraftForge.EVENT_BUS.post(event); 232 | return event; 233 | } 234 | 235 | public static HungerRegenEvent.PeacefulRegen firePeacefulHungerRegenEvent(EntityPlayer player) 236 | { 237 | HungerRegenEvent.PeacefulRegen event = new HungerRegenEvent.PeacefulRegen(player); 238 | MinecraftForge.EVENT_BUS.post(event); 239 | return event; 240 | } 241 | 242 | public static Result fireAllowStarvation(EntityPlayer player) 243 | { 244 | StarvationEvent.AllowStarvation event = new StarvationEvent.AllowStarvation(player); 245 | MinecraftForge.EVENT_BUS.post(event); 246 | return event.getResult(); 247 | } 248 | 249 | public static int fireStarvationTickEvent(EntityPlayer player) 250 | { 251 | return AppleCoreAPI.accessor.getStarveDamageTickPeriod(player); 252 | } 253 | 254 | public static StarvationEvent.Starve fireStarveEvent(EntityPlayer player) 255 | { 256 | StarvationEvent.Starve event = new StarvationEvent.Starve(player); 257 | MinecraftForge.EVENT_BUS.post(event); 258 | return event; 259 | } 260 | 261 | public static boolean fireFoodStatsAdditionEvent(EntityPlayer player, FoodValues foodValuesToBeAdded) 262 | { 263 | FoodEvent.FoodStatsAddition event = new FoodEvent.FoodStatsAddition(player, foodValuesToBeAdded); 264 | MinecraftForge.EVENT_BUS.post(event); 265 | return event.isCancelable() && event.isCanceled(); 266 | } 267 | 268 | public static boolean needFood(FoodStats foodStats) 269 | { 270 | verifyFoodStats(foodStats, null); 271 | return foodStats.getFoodLevel() < getMaxHunger(foodStats); 272 | } 273 | 274 | public static int getMaxHunger(FoodStats foodStats) 275 | { 276 | verifyFoodStats(foodStats, null); 277 | 278 | EntityPlayer player = ((IAppleCoreFoodStats) foodStats).getPlayer(); 279 | return AppleCoreAPI.accessor.getMaxHunger(player); 280 | } 281 | 282 | @SideOnly(Side.CLIENT) 283 | public static int getHungerForDisplay(FoodStats foodStats) 284 | { 285 | if (!(foodStats instanceof IAppleCoreFoodStats)) 286 | return foodStats.getFoodLevel(); 287 | 288 | // return a scaled value so that the HUD can still use the same logic 289 | // as if the max was 20 290 | EntityPlayer player = ((IAppleCoreFoodStats) foodStats).getPlayer(); 291 | float scale = 20f / AppleCoreAPI.accessor.getMaxHunger(player); 292 | int realHunger = foodStats.getFoodLevel(); 293 | 294 | // only return 0 if the real hunger value is 0 295 | if (realHunger == 0) 296 | return 0; 297 | 298 | // floor here so that full hunger is only drawn when its actually maxed 299 | int scaledHunger = MathHelper.floor(realHunger * scale); 300 | 301 | // hunger is always some non-zero value here, so return at least one 302 | // to make sure we don't draw 0 hunger when we're not actually 303 | // starving 304 | return Math.max(scaledHunger, 1); 305 | } 306 | 307 | public static float onExhaustionAdded(FoodStats foodStats, float deltaExhaustion) 308 | { 309 | verifyFoodStats(foodStats, null); 310 | 311 | EntityPlayer player = ((IAppleCoreFoodStats) foodStats).getPlayer(); 312 | ExhaustionEvent.ExhaustionAddition event = new ExhaustionEvent.ExhaustionAddition(player, deltaExhaustion); 313 | MinecraftForge.EVENT_BUS.post(event); 314 | return event.deltaExhaustion; 315 | } 316 | 317 | public static float getExhaustionCap(FoodStats foodStats) 318 | { 319 | verifyFoodStats(foodStats, null); 320 | 321 | EntityPlayer player = ((IAppleCoreFoodStats) foodStats).getPlayer(); 322 | ExhaustionEvent.GetExhaustionCap event = new ExhaustionEvent.GetExhaustionCap(player); 323 | MinecraftForge.EVENT_BUS.post(event); 324 | return event.exhaustionLevelCap; 325 | } 326 | 327 | public static float fireExhaustingActionEvent(EntityPlayer player, ExhaustionEvent.ExhaustingActions source, float deltaExhaustion) 328 | { 329 | ExhaustionEvent.ExhaustingAction event = new ExhaustionEvent.ExhaustingAction(player, source, deltaExhaustion); 330 | MinecraftForge.EVENT_BUS.post(event); 331 | return event.deltaExhaustion; 332 | } 333 | } -------------------------------------------------------------------------------- /java/squeek/applecore/asm/IClassTransformerModule.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.asm; 2 | 3 | import net.minecraft.launchwrapper.IClassTransformer; 4 | 5 | public interface IClassTransformerModule extends IClassTransformer 6 | { 7 | String[] getClassesToTransform(); 8 | } -------------------------------------------------------------------------------- /java/squeek/applecore/asm/TransformerModuleHandler.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.asm; 2 | 3 | import net.minecraft.launchwrapper.IClassTransformer; 4 | import squeek.applecore.asm.module.*; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class TransformerModuleHandler implements IClassTransformer 10 | { 11 | public static final String WILDCARD = "*"; 12 | public static final String[] ALL_CLASSES = new String[] { WILDCARD }; 13 | 14 | private static final List TRANSFORMER_MODULES = new ArrayList<>(); 15 | static 16 | { 17 | registerTransformerModule(new ModuleFoodStats()); 18 | registerTransformerModule(new ModuleFoodEatingSpeed()); 19 | registerTransformerModule(new ModuleBlockFood()); 20 | registerTransformerModule(new ModulePeacefulRegen()); 21 | registerTransformerModule(new ModuleHungerHUD()); 22 | registerTransformerModule(new ModuleExhaustingActions()); 23 | } 24 | 25 | public static void registerTransformerModule(IClassTransformerModule transformerModule) 26 | { 27 | TRANSFORMER_MODULES.add(transformerModule); 28 | } 29 | 30 | @Override 31 | public byte[] transform(String name, String transformedName, byte[] basicClass) 32 | { 33 | if (basicClass == null) 34 | return null; 35 | 36 | for (IClassTransformerModule transformerModule : TRANSFORMER_MODULES) 37 | { 38 | for (String classToTransform : transformerModule.getClassesToTransform()) 39 | { 40 | if (classToTransform.equals(WILDCARD) || classToTransform.equals(transformedName)) 41 | { 42 | basicClass = transformerModule.transform(name, transformedName, basicClass); 43 | } 44 | } 45 | } 46 | return basicClass; 47 | } 48 | } -------------------------------------------------------------------------------- /java/squeek/applecore/asm/module/ModuleBlockFood.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.asm.module; 2 | 3 | import org.objectweb.asm.ClassWriter; 4 | import org.objectweb.asm.tree.*; 5 | import org.objectweb.asm.Label; 6 | import org.objectweb.asm.MethodVisitor; 7 | import squeek.applecore.asm.ASMConstants; 8 | import squeek.applecore.asm.IClassTransformerModule; 9 | import squeek.asmhelper.applecore.ASMHelper; 10 | import squeek.asmhelper.applecore.InsnComparator; 11 | import squeek.asmhelper.applecore.ObfHelper; 12 | 13 | import static org.objectweb.asm.Opcodes.*; 14 | 15 | public class ModuleBlockFood implements IClassTransformerModule 16 | { 17 | private static String isEdibleAtMaxHungerField = "AppleCore_isEdibleAtMaxHunger"; 18 | 19 | @Override 20 | public String[] getClassesToTransform() 21 | { 22 | return new String[]{ASMConstants.CAKE}; 23 | } 24 | 25 | @Override 26 | public byte[] transform(String name, String transformedName, byte[] basicClass) 27 | { 28 | ClassNode classNode = ASMHelper.readClassFromBytes(basicClass); 29 | 30 | implementIEdibleBlock(classNode); 31 | 32 | MethodNode methodNode = ASMHelper.findMethodNodeOfClass(classNode, "func_180682_b", "eatCake", ASMHelper.toMethodDescriptor("Z", ASMConstants.WORLD, ASMConstants.BLOCK_POS, ASMConstants.IBLOCKSTATE, ASMConstants.PLAYER)); 33 | 34 | if (methodNode != null) 35 | { 36 | addOnBlockFoodEatenHook(classNode, methodNode); 37 | addAlwaysEdibleCheck(classNode, methodNode); 38 | return ASMHelper.writeClassToBytes(classNode, ClassWriter.COMPUTE_FRAMES); 39 | } 40 | else 41 | throw new RuntimeException("BlockCake: eatCake (func_180682_b) method not found"); 42 | } 43 | 44 | private void implementIEdibleBlock(ClassNode classNode) 45 | { 46 | classNode.interfaces.add(ASMHelper.toInternalClassName(ASMConstants.IEDIBLE)); 47 | classNode.interfaces.add(ASMHelper.toInternalClassName(ASMConstants.IEDIBLEBLOCK)); 48 | 49 | MethodVisitor mv = classNode.visitMethod(ACC_PUBLIC, "getFoodValues", "(Lnet/minecraft/item/ItemStack;)Lsqueek/applecore/api/food/FoodValues;", null, null); 50 | mv.visitCode(); 51 | Label l0 = new Label(); 52 | mv.visitLabel(l0); 53 | mv.visitTypeInsn(NEW, "squeek/applecore/api/food/FoodValues"); 54 | mv.visitInsn(DUP); 55 | mv.visitInsn(ICONST_2); 56 | mv.visitLdcInsn(new Float("0.1")); 57 | mv.visitMethodInsn(INVOKESPECIAL, "squeek/applecore/api/food/FoodValues", "", "(IF)V", false); 58 | mv.visitInsn(ARETURN); 59 | Label l1 = new Label(); 60 | mv.visitLabel(l1); 61 | mv.visitLocalVariable("this", ASMHelper.toDescriptor(classNode.name), null, l0, l1, 0); 62 | mv.visitLocalVariable("itemStack", "Lnet/minecraft/item/ItemStack;", null, l0, l1, 1); 63 | mv.visitMaxs(4, 2); 64 | mv.visitEnd(); 65 | 66 | classNode.fields.add(new FieldNode(ACC_PUBLIC, isEdibleAtMaxHungerField, "Z", null, null)); 67 | 68 | mv = classNode.visitMethod(ACC_PUBLIC, "setEdibleAtMaxHunger", ASMHelper.toMethodDescriptor("V", "Z"), null, null); 69 | mv.visitVarInsn(ALOAD, 0); 70 | mv.visitVarInsn(ILOAD, 1); 71 | mv.visitFieldInsn(PUTFIELD, ASMHelper.toInternalClassName(classNode.name), isEdibleAtMaxHungerField, "Z"); 72 | mv.visitInsn(RETURN); 73 | mv.visitMaxs(0, 0); 74 | } 75 | 76 | private void addOnBlockFoodEatenHook(ClassNode classNode, MethodNode method) 77 | { 78 | // default player.getFoodStats().addStats call replaced with: 79 | /* 80 | Hooks.onBlockFoodEaten(this, world, player); 81 | */ 82 | InsnList needle = new InsnList(); 83 | needle.add(new VarInsnNode(ALOAD, 4)); 84 | needle.add(new MethodInsnNode(INVOKEVIRTUAL, ObfHelper.getInternalClassName("net.minecraft.entity.player.EntityPlayer"), InsnComparator.WILDCARD, InsnComparator.WILDCARD, false)); 85 | needle.add(new InsnNode(ICONST_2)); 86 | needle.add(new LdcInsnNode(0.1f)); 87 | needle.add(new MethodInsnNode(INVOKEVIRTUAL, ObfHelper.getInternalClassName("net.minecraft.util.FoodStats"), InsnComparator.WILDCARD, "(IF)V", false)); 88 | 89 | InsnList replacement = new InsnList(); 90 | replacement.add(new VarInsnNode(ALOAD, 0)); 91 | replacement.add(new VarInsnNode(ALOAD, 1)); 92 | replacement.add(new VarInsnNode(ALOAD, 4)); 93 | replacement.add(new MethodInsnNode(INVOKESTATIC, ASMConstants.HOOKS_INTERNAL_CLASS, "onBlockFoodEaten", "(Lnet/minecraft/block/Block;Lnet/minecraft/world/World;Lnet/minecraft/entity/player/EntityPlayer;)V", false)); 94 | 95 | if (ASMHelper.findAndReplace(method.instructions, needle, replacement) == null) 96 | throw new RuntimeException("Could not replace FoodStats.addStats call in " + classNode.name + "." + method.name + "\n" + ASMHelper.getMethodAsString(method)); 97 | } 98 | 99 | private void addAlwaysEdibleCheck(ClassNode classNode, MethodNode method) 100 | { 101 | AbstractInsnNode pushFalse = ASMHelper.findFirstInstructionWithOpcode(method, ICONST_0); 102 | 103 | if (pushFalse == null) 104 | throw new RuntimeException("ICONST_0 instruction not found in " + classNode.name + "." + method.name); 105 | 106 | InsnList pushField = new InsnList(); 107 | pushField.add(new VarInsnNode(ALOAD, 0)); 108 | pushField.add(new FieldInsnNode(GETFIELD, ASMHelper.toInternalClassName(classNode.name), isEdibleAtMaxHungerField, "Z")); 109 | method.instructions.insert(pushFalse, pushField); 110 | method.instructions.remove(pushFalse); 111 | } 112 | } -------------------------------------------------------------------------------- /java/squeek/applecore/asm/module/ModuleExhaustingActions.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.asm.module; 2 | 3 | import org.objectweb.asm.tree.*; 4 | import squeek.applecore.asm.ASMConstants; 5 | import squeek.applecore.asm.IClassTransformerModule; 6 | import squeek.asmhelper.applecore.ASMHelper; 7 | import squeek.asmhelper.applecore.InsnComparator; 8 | import squeek.asmhelper.applecore.ObfHelper; 9 | 10 | import static org.objectweb.asm.Opcodes.*; 11 | 12 | public class ModuleExhaustingActions implements IClassTransformerModule 13 | { 14 | @Override 15 | public String[] getClassesToTransform() 16 | { 17 | return new String[]{ASMConstants.PLAYER, ASMConstants.BLOCK, ASMConstants.BLOCK_CONTAINER, ASMConstants.BLOCK_ICE, ASMConstants.POTION}; 18 | } 19 | 20 | @Override 21 | public byte[] transform(String name, String transformedName, byte[] basicClass) 22 | { 23 | if (transformedName.equals(ASMConstants.PLAYER)) 24 | { 25 | ClassNode classNode = ASMHelper.readClassFromBytes(basicClass); 26 | 27 | MethodNode jumpMethod = ASMHelper.findMethodNodeOfClass(classNode, ObfHelper.isObfuscated() ? "func_70664_aZ" : "jump", "()V"); 28 | if (jumpMethod == null) 29 | throw new RuntimeException("EntityPlayer.jump method not found"); 30 | patchSimpleAddExhaustionCall(classNode, jumpMethod, 1, "NORMAL_JUMP"); 31 | patchSimpleAddExhaustionCall(classNode, jumpMethod, 0, "SPRINTING_JUMP"); 32 | 33 | MethodNode damageEntityMethod = ASMHelper.findMethodNodeOfClass(classNode, ObfHelper.isObfuscated() ? "func_70665_d" : "damageEntity", ASMHelper.toMethodDescriptor("V", ASMConstants.DAMAGE_SOURCE, "F")); 34 | if (damageEntityMethod == null) 35 | throw new RuntimeException("EntityPlayer.damageEntity method not found"); 36 | patchDamageEntity(damageEntityMethod); 37 | 38 | MethodNode attackEntityMethod = ASMHelper.findMethodNodeOfClass(classNode, ObfHelper.isObfuscated() ? "func_71059_n" : "attackTargetEntityWithCurrentItem", ASMHelper.toMethodDescriptor("V", ASMConstants.ENTITY)); 39 | if (attackEntityMethod == null) 40 | throw new RuntimeException("EntityPlayer.attackTargetEntityWithCurrentItem method not found"); 41 | patchSimpleAddExhaustionCall(classNode, attackEntityMethod, 0, "ATTACK_ENTITY"); 42 | 43 | MethodNode addMovementStatMethod = ASMHelper.findMethodNodeOfClass(classNode, ObfHelper.isObfuscated() ? "func_71000_j" : "addMovementStat", ASMHelper.toMethodDescriptor("V", "D", "D", "D")); 44 | if (addMovementStatMethod == null) 45 | throw new RuntimeException("EntityPlayer.addMovementStat method not found"); 46 | patchMovementStat(addMovementStatMethod, 0, "MOVEMENT_DIVE"); 47 | patchMovementStat(addMovementStatMethod, 1, "MOVEMENT_SWIM"); 48 | patchMovementStat(addMovementStatMethod, 2, "MOVEMENT_SPRINT"); 49 | patchMovementStat(addMovementStatMethod, 3, "MOVEMENT_CROUCH"); 50 | patchMovementStat(addMovementStatMethod, 4, "MOVEMENT_WALK"); 51 | 52 | return ASMHelper.writeClassToBytes(classNode); 53 | } 54 | else if (transformedName.equals(ASMConstants.BLOCK) || transformedName.equals(ASMConstants.BLOCK_CONTAINER) || transformedName.equals(ASMConstants.BLOCK_ICE)) 55 | { 56 | ClassNode classNode = ASMHelper.readClassFromBytes(basicClass); 57 | String methodDesc = ASMHelper.toMethodDescriptor( 58 | "V", 59 | ASMConstants.WORLD, 60 | ASMConstants.PLAYER, 61 | ASMConstants.BLOCK_POS, 62 | ASMConstants.IBLOCKSTATE, 63 | ASMConstants.TILE_ENTITY, 64 | ASMConstants.ITEM_STACK 65 | ); 66 | MethodNode harvestBlock = ASMHelper.findMethodNodeOfClass(classNode, ObfHelper.isObfuscated() ? "func_180657_a" : "harvestBlock", methodDesc); 67 | if (harvestBlock == null) 68 | throw new RuntimeException("Block.harvestBlock method not found in class " + classNode.name + " with desc " + methodDesc); 69 | patchSimpleAddExhaustionCall(classNode, harvestBlock, 0, "HARVEST_BLOCK"); 70 | return ASMHelper.writeClassToBytes(classNode); 71 | } 72 | else if (transformedName.equals(ASMConstants.POTION)) 73 | { 74 | ClassNode classNode = ASMHelper.readClassFromBytes(basicClass); 75 | MethodNode performEffect = ASMHelper.findMethodNodeOfClass(classNode, ObfHelper.isObfuscated() ? "func_76394_a" : "performEffect", ASMHelper.toMethodDescriptor("V", ASMConstants.ENTITY_LIVING, "I")); 76 | if (performEffect == null) 77 | throw new RuntimeException("Potion.performEffect method not found"); 78 | 79 | AbstractInsnNode call = getAddExhaustionCall(performEffect); 80 | AbstractInsnNode load = ASMHelper.findPreviousInstructionWithOpcode(call, CHECKCAST); 81 | if (load == null) 82 | throw new RuntimeException("Unexpected instruction pattern found in Potion.performEffect:\n" + ASMHelper.getInsnListAsString(performEffect.instructions)); 83 | 84 | patchAddExhaustionCall(performEffect.instructions, load, call, 1, "HUNGER_POTION"); 85 | // inserted ALOAD needs a CHECKCAST 86 | performEffect.instructions.insert(load.getNext(), new TypeInsnNode(CHECKCAST, ObfHelper.getInternalClassName(ASMConstants.PLAYER))); 87 | return ASMHelper.writeClassToBytes(classNode); 88 | } 89 | return basicClass; 90 | } 91 | 92 | // transform 93 | // search for pattern: 94 | /* 95 | ALOAD 0 96 | LDC 0.2 97 | INVOKEVIRTUAL net/minecraft/entity/player/EntityPlayer.addExhaustion (F)V 98 | */ 99 | // resulting in: 100 | /* 101 | ALOAD 0 102 | ALOAD 0 103 | GETSTATIC squeek/applecore/api/hunger/ExhaustionEvent$ExhaustingActions.SPRINTING_JUMP : Lsqueek/applecore/api/hunger/ExhaustionEvent$ExhaustingActions; 104 | LDC 0.2 105 | INVOKESTATIC squeek/applecore/asm/Hooks.fireExhaustingActionEvent (Lnet/minecraft/entity/player/EntityPlayer;Lsqueek/applecore/api/hunger/ExhaustionEvent$ExhaustingActions;F)F 106 | INVOKEVIRTUAL net/minecraft/entity/player/EntityPlayer.addExhaustion (F)V 107 | */ 108 | 109 | /** 110 | * Usage note: This will modify the instructions so that the search pattern will not be found in the next call 111 | * so doing something like patchAddExhaustionCall(..., 0, ...); patchAddExhaustionCall(..., 1, ...); will fail if there 112 | * are only two instances of that pattern in the function. Should either reverse it (so the second gets modified first) or call 113 | * it twice with index 0 both times. 114 | */ 115 | private void patchSimpleAddExhaustionCall(ClassNode classNode, MethodNode method, int patternIndex, String exhaustingActionEnum) 116 | { 117 | AbstractInsnNode start = null; 118 | AbstractInsnNode haystackStart = method.instructions.getFirst(); 119 | 120 | for (int i = 0; i <= patternIndex; i++) 121 | { 122 | InsnList needle = new InsnList(); 123 | needle.add(new VarInsnNode(ALOAD, InsnComparator.INT_WILDCARD)); 124 | needle.add(new LdcInsnNode(InsnComparator.WILDCARD)); 125 | needle.add(new MethodInsnNode(INVOKEVIRTUAL, ObfHelper.getInternalClassName(ASMConstants.PLAYER), ObfHelper.isObfuscated() ? "func_71020_j" : "addExhaustion", ASMHelper.toMethodDescriptor("V", "F"), false)); 126 | 127 | start = ASMHelper.find(haystackStart, needle); 128 | if (start == null || start.getNext() == null) 129 | throw new RuntimeException("EntityPlayer.addExhaustion call pattern (index=" + patternIndex + ") not found in " + classNode.name + "." + method.name); 130 | 131 | haystackStart = start.getNext(); 132 | } 133 | 134 | patchAddExhaustionCall(method.instructions, start, start.getNext().getNext(), ((VarInsnNode) start).var, exhaustingActionEnum); 135 | } 136 | 137 | private void patchAddExhaustionCall(InsnList instructions, AbstractInsnNode loadPoint, AbstractInsnNode callPoint, int playerLoadIndex, String exhaustingAction) 138 | { 139 | // load the player 140 | AbstractInsnNode loadPlayer = new VarInsnNode(ALOAD, playerLoadIndex); 141 | instructions.insert(loadPoint, loadPlayer); 142 | // add a GETSTATIC for the enum 143 | AbstractInsnNode getEnum = new FieldInsnNode(GETSTATIC, ObfHelper.getInternalClassName(ASMConstants.ExhaustionEvent.EXHAUSTING_ACTIONS), exhaustingAction, ASMHelper.toDescriptor(ASMConstants.ExhaustionEvent.EXHAUSTING_ACTIONS)); 144 | instructions.insert(loadPlayer, getEnum); 145 | // add an INVOKE for the fire event hook before the call 146 | AbstractInsnNode fireEvent = new MethodInsnNode(INVOKESTATIC, ASMConstants.HOOKS_INTERNAL_CLASS, "fireExhaustingActionEvent", ASMHelper.toMethodDescriptor("F", ASMConstants.PLAYER, ASMConstants.ExhaustionEvent.EXHAUSTING_ACTIONS, "F"), false); 147 | instructions.insertBefore(callPoint, fireEvent); 148 | } 149 | 150 | // special case for the call in EntityPlayer.damageEntity 151 | private void patchDamageEntity(MethodNode method) 152 | { 153 | AbstractInsnNode addExhaustionCall = getAddExhaustionCall(method); 154 | AbstractInsnNode loadPoint = addExhaustionCall.getPrevious().getPrevious().getPrevious(); 155 | patchAddExhaustionCall(method.instructions, loadPoint, addExhaustionCall, 0, "DAMAGE_TAKEN"); 156 | } 157 | 158 | // special case for the calls in EntityPlayer.addMovementStat 159 | private void patchMovementStat(MethodNode method, int callIndex, String exhaustingAction) 160 | { 161 | AbstractInsnNode addExhaustionCall = getAddExhaustionCall(method, callIndex); 162 | 163 | AbstractInsnNode loadPoint = ASMHelper.findPreviousInstructionWithOpcode(addExhaustionCall, ALOAD); 164 | if (loadPoint == null) 165 | throw new RuntimeException("No ALOAD found before addExhaustion call (index=" + callIndex + ") in EntityPlayer.addMovementStat"); 166 | 167 | patchAddExhaustionCall(method.instructions, loadPoint, addExhaustionCall, 0, exhaustingAction); 168 | } 169 | 170 | private AbstractInsnNode getAddExhaustionCall(MethodNode method) 171 | { 172 | return getAddExhaustionCall(method, 0); 173 | } 174 | 175 | private AbstractInsnNode getAddExhaustionCall(MethodNode method, int callIndex) 176 | { 177 | AbstractInsnNode addExhaustionCall = null; 178 | AbstractInsnNode haystackStart = method.instructions.getFirst(); 179 | AbstractInsnNode needle = new MethodInsnNode(INVOKEVIRTUAL, ObfHelper.getInternalClassName(ASMConstants.PLAYER), ObfHelper.isObfuscated() ? "func_71020_j" : "addExhaustion", ASMHelper.toMethodDescriptor("V", "F"), false); 180 | 181 | for (int i = 0; i <= callIndex && haystackStart != null; i++) 182 | { 183 | addExhaustionCall = ASMHelper.find(haystackStart, needle); 184 | if (addExhaustionCall == null) 185 | throw new RuntimeException("No addExhaustion call (index=" + callIndex + ") found in " + method.name + ":\n" + ASMHelper.getInsnListAsString(method.instructions)); 186 | haystackStart = addExhaustionCall.getNext(); 187 | } 188 | 189 | return addExhaustionCall; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /java/squeek/applecore/asm/module/ModuleFoodEatingSpeed.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.asm.module; 2 | 3 | import org.objectweb.asm.tree.*; 4 | import squeek.applecore.asm.ASMConstants; 5 | import squeek.applecore.asm.IClassTransformerModule; 6 | import squeek.asmhelper.applecore.ASMHelper; 7 | import squeek.asmhelper.applecore.ObfHelper; 8 | 9 | import static org.objectweb.asm.Opcodes.*; 10 | 11 | public class ModuleFoodEatingSpeed implements IClassTransformerModule 12 | { 13 | @Override 14 | public String[] getClassesToTransform() 15 | { 16 | return new String[]{ 17 | ASMConstants.ENTITY_LIVING, 18 | ASMConstants.ITEM_RENDERER 19 | }; 20 | } 21 | 22 | @Override 23 | public byte[] transform(String name, String transformedName, byte[] basicClass) 24 | { 25 | ClassNode classNode = ASMHelper.readClassFromBytes(basicClass); 26 | 27 | if (transformedName.equals(ASMConstants.ENTITY_LIVING)) 28 | { 29 | addItemInUseMaxDurationField(classNode); 30 | 31 | MethodNode methodNode = ASMHelper.findMethodNodeOfClass(classNode, "func_184598_c", "setActiveHand", ASMHelper.toMethodDescriptor("V", ASMConstants.HAND)); 32 | if (methodNode != null) 33 | { 34 | patchSetActiveHand(classNode, methodNode); 35 | } 36 | else 37 | throw new RuntimeException(classNode.name + ": setActiveHand method not found"); 38 | 39 | methodNode = ASMHelper.findMethodNodeOfClass(classNode, "func_184612_cw", "getItemInUseMaxCount", ASMHelper.toMethodDescriptor("I")); 40 | if (methodNode != null) 41 | { 42 | patchGetItemInUseMaxCount(classNode, methodNode); 43 | } 44 | else 45 | throw new RuntimeException(classNode.name + ": getItemInUseMaxCount method not found"); 46 | } 47 | else if (transformedName.equals(ASMConstants.ITEM_RENDERER)) 48 | { 49 | MethodNode methodNode = ASMHelper.findMethodNodeOfClass(classNode, "func_187454_a", "transformEatFirstPerson", ASMHelper.toMethodDescriptor("V", "F", ASMConstants.HAND_SIDE, ASMConstants.ITEM_STACK)); 50 | if (methodNode != null) 51 | { 52 | patchRenderItemInFirstPerson(methodNode); 53 | } 54 | else 55 | throw new RuntimeException(classNode.name + ": setActiveHand method not found"); 56 | } 57 | 58 | return ASMHelper.writeClassToBytes(classNode); 59 | } 60 | 61 | private void patchRenderItemInFirstPerson(MethodNode method) 62 | { 63 | InsnList needle = new InsnList(); 64 | needle.add(new VarInsnNode(ALOAD, 3)); 65 | needle.add(new MethodInsnNode(INVOKEVIRTUAL, ASMHelper.toInternalClassName(ASMConstants.ITEM_STACK), ObfHelper.isObfuscated() ? "func_77988_m" : "getMaxItemUseDuration", ASMHelper.toMethodDescriptor("I"), false)); 66 | 67 | InsnList replacement = new InsnList(); 68 | replacement.add(new VarInsnNode(ALOAD, 0)); 69 | replacement.add(new FieldInsnNode(GETFIELD, ObfHelper.getInternalClassName(ASMConstants.ITEM_RENDERER), ObfHelper.isObfuscated() ? "field_78455_a" : "mc", ASMHelper.toDescriptor(ASMConstants.MINECRAFT))); 70 | replacement.add(new FieldInsnNode(GETFIELD, ObfHelper.getInternalClassName(ASMConstants.MINECRAFT), ObfHelper.isObfuscated() ? "field_71439_g" : "player", ASMHelper.toDescriptor(ASMConstants.PLAYER_SP))); 71 | replacement.add(new FieldInsnNode(GETFIELD, ObfHelper.getInternalClassName(ASMConstants.PLAYER), "itemInUseMaxDuration", "I")); 72 | 73 | boolean replaced = ASMHelper.findAndReplace(method.instructions, needle, replacement) != null; 74 | if (!replaced) 75 | throw new RuntimeException("ItemRenderer.transformEatFirstPerson: no replacements made"); 76 | } 77 | 78 | private void patchGetItemInUseMaxCount(ClassNode classNode, MethodNode method) 79 | { 80 | InsnList needle = new InsnList(); 81 | needle.add(new VarInsnNode(ALOAD, 0)); 82 | needle.add(new FieldInsnNode(GETFIELD, ObfHelper.getInternalClassName(ASMConstants.ENTITY_LIVING), ObfHelper.isObfuscated() ? "field_184627_bm" : "activeItemStack", ASMHelper.toDescriptor(ASMConstants.ITEM_STACK))); 83 | needle.add(new MethodInsnNode(INVOKEVIRTUAL, ObfHelper.getInternalClassName(ASMConstants.ITEM_STACK), ObfHelper.isObfuscated() ? "func_77988_m" : "getMaxItemUseDuration", ASMHelper.toMethodDescriptor("I"), false)); 84 | 85 | InsnList replacement = new InsnList(); 86 | replacement.add(new VarInsnNode(ALOAD, 0)); 87 | replacement.add(new VarInsnNode(ALOAD, 0)); 88 | replacement.add(new FieldInsnNode(GETFIELD, ASMHelper.toInternalClassName(classNode.name), "itemInUseMaxDuration", "I")); 89 | replacement.add(new MethodInsnNode(INVOKESTATIC, ASMHelper.toInternalClassName(ASMConstants.HOOKS), "getItemInUseMaxCount", ASMHelper.toMethodDescriptor("I", ASMConstants.ENTITY_LIVING, "I"), false)); 90 | 91 | int numReplacementsMade = ASMHelper.findAndReplaceAll(method.instructions, needle, replacement); 92 | if (numReplacementsMade == 0) 93 | throw new RuntimeException("EntityLivingBase.getItemInUseMaxCount: no replacements made"); 94 | } 95 | 96 | private void patchSetActiveHand(ClassNode classNode, MethodNode method) 97 | { 98 | AbstractInsnNode targetNode = ASMHelper.findFirstInstructionWithOpcode(method, PUTFIELD); 99 | while (targetNode != null && !((FieldInsnNode) targetNode).name.equals(ObfHelper.isObfuscated() ? "field_184628_bn" : "activeItemStackUseCount")) 100 | { 101 | targetNode = ASMHelper.findNextInstructionWithOpcode(targetNode, PUTFIELD); 102 | } 103 | 104 | if (targetNode == null) 105 | throw new RuntimeException("EntityLivingBase.setActiveHand: PUTFIELD activeItemStackUseCount instruction not found"); 106 | 107 | InsnList toInject = new InsnList(); 108 | 109 | toInject.add(new VarInsnNode(ALOAD, 0)); 110 | toInject.add(new VarInsnNode(ILOAD, 3)); 111 | toInject.add(new FieldInsnNode(PUTFIELD, ASMHelper.toInternalClassName(classNode.name), "itemInUseMaxDuration", "I")); 112 | 113 | method.instructions.insert(targetNode, toInject); 114 | } 115 | 116 | private void addItemInUseMaxDurationField(ClassNode classNode) 117 | { 118 | classNode.fields.add(new FieldNode(ACC_PUBLIC, "itemInUseMaxDuration", "I", null, null)); 119 | } 120 | } -------------------------------------------------------------------------------- /java/squeek/applecore/asm/module/ModuleFoodStats.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.asm.module; 2 | 3 | import org.objectweb.asm.ClassWriter; 4 | import org.objectweb.asm.MethodVisitor; 5 | import org.objectweb.asm.Type; 6 | import org.objectweb.asm.tree.*; 7 | import squeek.applecore.asm.ASMConstants; 8 | import squeek.applecore.asm.IClassTransformerModule; 9 | import squeek.asmhelper.applecore.ASMHelper; 10 | import squeek.asmhelper.applecore.ObfHelper; 11 | 12 | import static org.objectweb.asm.Opcodes.*; 13 | 14 | public class ModuleFoodStats implements IClassTransformerModule 15 | { 16 | public static String foodStatsPlayerField = "entityplayer"; 17 | public static String foodStatsStarveTimerField = "starveTimer"; 18 | 19 | @Override 20 | public String[] getClassesToTransform() 21 | { 22 | return new String[]{ASMConstants.PLAYER, ASMConstants.FOOD_STATS}; 23 | } 24 | 25 | @Override 26 | public byte[] transform(String name, String transformedName, byte[] basicClass) 27 | { 28 | if (transformedName.equals(ASMConstants.PLAYER)) 29 | { 30 | ClassNode classNode = ASMHelper.readClassFromBytes(basicClass); 31 | 32 | MethodNode methodNode = ASMHelper.findMethodNodeOfClass(classNode, "", null); 33 | if (methodNode != null) 34 | { 35 | patchEntityPlayerInit(methodNode); 36 | return ASMHelper.writeClassToBytes(classNode); 37 | } 38 | else 39 | throw new RuntimeException("EntityPlayer: method not found"); 40 | } 41 | if (transformedName.equals(ASMConstants.FOOD_STATS)) 42 | { 43 | ClassNode classNode = ASMHelper.readClassFromBytes(basicClass); 44 | 45 | injectFoodStatsPlayerField(classNode); 46 | injectFoodStatsConstructor(classNode); 47 | 48 | // add starveTimer field 49 | classNode.fields.add(new FieldNode(ACC_PUBLIC, foodStatsStarveTimerField, "I", null, null)); 50 | 51 | // IAppleCoreFoodStats implementation 52 | classNode.interfaces.add(ASMHelper.toInternalClassName(ASMConstants.IAPPLECOREFOODSTATS)); 53 | tryAddFieldGetter(classNode, "getFoodTimer", ObfHelper.isObfuscated() ? "field_75123_d" : "foodTimer", "I"); 54 | tryAddFieldSetter(classNode, "setFoodTimer", ObfHelper.isObfuscated() ? "field_75123_d" : "foodTimer", "I"); 55 | tryAddFieldGetter(classNode, "getStarveTimer", foodStatsStarveTimerField, "I"); 56 | tryAddFieldSetter(classNode, "setStarveTimer", foodStatsStarveTimerField, "I"); 57 | tryAddFieldGetter(classNode, "getPlayer", foodStatsPlayerField, ASMHelper.toDescriptor(ASMConstants.PLAYER)); 58 | tryAddFieldSetter(classNode, "setPlayer", foodStatsPlayerField, ASMHelper.toDescriptor(ASMConstants.PLAYER)); 59 | tryAddFieldSetter(classNode, "setPrevFoodLevel", ObfHelper.isObfuscated() ? "field_75124_e" : "prevFoodLevel", "I"); 60 | tryAddFieldGetter(classNode, "getExhaustion", ObfHelper.isObfuscated() ? "field_75126_c" : "foodExhaustionLevel", "F"); 61 | tryAddFieldSetter(classNode, "setExhaustion", ObfHelper.isObfuscated() ? "field_75126_c" : "foodExhaustionLevel", "F"); 62 | tryAddFieldSetter(classNode, "setSaturation", ObfHelper.isObfuscated() ? "field_75125_b" : "foodSaturationLevel", "F"); 63 | 64 | MethodNode addStatsMethodNode = ASMHelper.findMethodNodeOfClass(classNode, "func_75122_a", "addStats", ASMHelper.toMethodDescriptor("V", "I", "F")); 65 | if (addStatsMethodNode != null) 66 | { 67 | hookFoodStatsAddition(classNode, addStatsMethodNode); 68 | } 69 | else 70 | throw new RuntimeException("FoodStats: addStats(IF)V method not found"); 71 | 72 | MethodNode methodNode = ASMHelper.findMethodNodeOfClass(classNode, "func_151686_a", "addStats", ASMHelper.toMethodDescriptor("V", ASMConstants.ITEM_FOOD, ASMConstants.ITEM_STACK)); 73 | if (methodNode != null) 74 | { 75 | addItemStackAwareFoodStatsHook(classNode, methodNode, ObfHelper.isObfuscated()); 76 | } 77 | else 78 | throw new RuntimeException("FoodStats: ItemStack-aware addStats method not found"); 79 | 80 | MethodNode updateMethodNode = ASMHelper.findMethodNodeOfClass(classNode, "func_75118_a", "onUpdate", ASMHelper.toMethodDescriptor("V", ASMConstants.PLAYER)); 81 | if (updateMethodNode != null) 82 | { 83 | hookUpdate(classNode, updateMethodNode); 84 | } 85 | else 86 | throw new RuntimeException("FoodStats: onUpdate method not found"); 87 | 88 | MethodNode needFoodMethodNode = ASMHelper.findMethodNodeOfClass(classNode, "func_75121_c", "needFood", ASMHelper.toMethodDescriptor("Z")); 89 | if (needFoodMethodNode != null) 90 | { 91 | hookNeedFood(classNode, needFoodMethodNode); 92 | } 93 | else 94 | throw new RuntimeException("FoodStats: needFood method not found"); 95 | 96 | MethodNode addExhaustionMethod = ASMHelper.findMethodNodeOfClass(classNode, "func_75113_a", "addExhaustion", ASMHelper.toMethodDescriptor("V", "F")); 97 | if (addExhaustionMethod != null) 98 | { 99 | hookAddExhaustion(classNode, addExhaustionMethod); 100 | } 101 | else 102 | throw new RuntimeException("FoodStats: addExhaustion method not found"); 103 | 104 | return ASMHelper.writeClassToBytes(classNode, ClassWriter.COMPUTE_FRAMES); 105 | } 106 | return basicClass; 107 | } 108 | 109 | public void patchEntityPlayerInit(MethodNode method) 110 | { 111 | // find NEW net/minecraft/util/FoodStats 112 | AbstractInsnNode targetNode = ASMHelper.find(method.instructions, new TypeInsnNode(NEW, ASMHelper.toInternalClassName(ASMConstants.FOOD_STATS))); 113 | 114 | if (targetNode == null) 115 | { 116 | throw new RuntimeException("patchEntityPlayerInit: NEW instruction not found"); 117 | } 118 | 119 | do 120 | { 121 | targetNode = targetNode.getNext(); 122 | } 123 | while (targetNode != null && targetNode.getOpcode() != INVOKESPECIAL); 124 | 125 | if (targetNode == null) 126 | { 127 | throw new RuntimeException("patchEntityPlayerInit: INVOKESPECIAL instruction not found"); 128 | } 129 | 130 | method.instructions.insertBefore(targetNode, new VarInsnNode(ALOAD, 0)); 131 | ((MethodInsnNode) targetNode).desc = ASMHelper.toMethodDescriptor("V", ASMConstants.PLAYER); 132 | } 133 | 134 | public void injectFoodStatsPlayerField(ClassNode classNode) 135 | { 136 | classNode.fields.add(new FieldNode(ACC_PUBLIC, foodStatsPlayerField, ASMHelper.toDescriptor(ASMConstants.PLAYER), null, null)); 137 | } 138 | 139 | public void injectFoodStatsConstructor(ClassNode classNode) 140 | { 141 | // get the default constructor, apply max hunger patches, and copy it 142 | MethodNode defaultConstructor = ASMHelper.findMethodNodeOfClass(classNode, "", ASMHelper.toMethodDescriptor("V")); 143 | 144 | if (defaultConstructor == null) 145 | throw new RuntimeException("FoodStats.() not found"); 146 | 147 | MethodNode constructor = new MethodNode(ACC_PUBLIC, "", ASMHelper.toMethodDescriptor("V", ASMConstants.PLAYER), null, null); 148 | constructor.instructions = ASMHelper.cloneInsnList(defaultConstructor.instructions); 149 | 150 | InsnList foodLevelNeedle = new InsnList(); 151 | foodLevelNeedle.add(new IntInsnNode(BIPUSH, 20)); 152 | 153 | InsnList foodLevelReplacement = new InsnList(); 154 | foodLevelReplacement.add(new VarInsnNode(ALOAD, 0)); 155 | foodLevelReplacement.add(new MethodInsnNode(INVOKESTATIC, ASMHelper.toInternalClassName(ASMConstants.HOOKS), "getMaxHunger", ASMHelper.toMethodDescriptor("I", ASMConstants.FOOD_STATS), false)); 156 | 157 | int numReplacements = ASMHelper.findAndReplaceAll(constructor.instructions, foodLevelNeedle, foodLevelReplacement); 158 | 159 | if (numReplacements < 2) 160 | throw new RuntimeException("FoodStats.() replaced " + numReplacements + " (BIPUSH 20) instructions, expected >= 2"); 161 | 162 | AbstractInsnNode targetNode = ASMHelper.findFirstInstructionWithOpcode(constructor, INVOKESPECIAL); 163 | 164 | InsnList toInject = new InsnList(); 165 | toInject.add(new VarInsnNode(ALOAD, 0)); // this 166 | toInject.add(new VarInsnNode(ALOAD, 1)); // player param 167 | toInject.add(new FieldInsnNode(PUTFIELD, classNode.name, foodStatsPlayerField, ASMHelper.toDescriptor(ASMConstants.PLAYER))); 168 | 169 | constructor.instructions.insert(targetNode, toInject); 170 | 171 | classNode.methods.add(constructor); 172 | } 173 | 174 | public void addItemStackAwareFoodStatsHook(ClassNode classNode, MethodNode method, boolean isObfuscated) 175 | { 176 | String internalFoodStatsName = ASMHelper.toInternalClassName(classNode.name); 177 | 178 | /* 179 | * Modify food values 180 | */ 181 | InsnList toInject = new InsnList(); 182 | AbstractInsnNode targetNode = ASMHelper.findFirstInstruction(method); 183 | 184 | // create modifiedFoodValues variable 185 | LabelNode modifiedFoodValuesStart = new LabelNode(); 186 | LabelNode end = ASMHelper.findEndLabel(method); 187 | LocalVariableNode modifiedFoodValues = new LocalVariableNode("modifiedFoodValues", ASMHelper.toDescriptor(ASMConstants.FOOD_STATS), null, modifiedFoodValuesStart, end, method.maxLocals); 188 | method.maxLocals += 1; 189 | method.localVariables.add(modifiedFoodValues); 190 | 191 | // create prevFoodLevel variable 192 | LabelNode prevFoodLevelStart = new LabelNode(); 193 | LocalVariableNode prevFoodLevel = new LocalVariableNode("prevFoodLevel", "I", null, prevFoodLevelStart, end, method.maxLocals); 194 | method.maxLocals += 1; 195 | method.localVariables.add(prevFoodLevel); 196 | 197 | // create prevSaturationLevel variable 198 | LabelNode prevSaturationLevelStart = new LabelNode(); 199 | LocalVariableNode prevSaturationLevel = new LocalVariableNode("prevSaturationLevel", "F", null, prevSaturationLevelStart, end, method.maxLocals); 200 | method.maxLocals += 1; 201 | method.localVariables.add(prevSaturationLevel); 202 | 203 | // get modifiedFoodValues 204 | toInject.add(new VarInsnNode(ALOAD, 0)); // this 205 | toInject.add(new VarInsnNode(ALOAD, 1)); // param 1: ItemFood 206 | toInject.add(new VarInsnNode(ALOAD, 2)); // param 2: ItemStack 207 | toInject.add(new VarInsnNode(ALOAD, 0)); // this.player (together with below line) 208 | toInject.add(new FieldInsnNode(GETFIELD, internalFoodStatsName, foodStatsPlayerField, ASMHelper.toDescriptor(ASMConstants.PLAYER))); 209 | toInject.add(new MethodInsnNode(INVOKESTATIC, ASMConstants.HOOKS_INTERNAL_CLASS, "onFoodStatsAdded", ASMHelper.toMethodDescriptor(ASMConstants.FOOD_VALUES, ASMConstants.FOOD_STATS, ASMConstants.ITEM_FOOD, ASMConstants.ITEM_STACK, ASMConstants.PLAYER), false)); 210 | toInject.add(new VarInsnNode(ASTORE, modifiedFoodValues.index)); // modifiedFoodValues = hookClass.hookMethod(...) 211 | toInject.add(modifiedFoodValuesStart); // variable scope start 212 | 213 | // save current hunger/saturation levels 214 | toInject.add(new VarInsnNode(ALOAD, 0)); 215 | toInject.add(new FieldInsnNode(GETFIELD, internalFoodStatsName, ObfHelper.isObfuscated() ? "field_75127_a" : "foodLevel", "I")); 216 | toInject.add(new VarInsnNode(ISTORE, prevFoodLevel.index)); 217 | toInject.add(prevFoodLevelStart); 218 | toInject.add(new VarInsnNode(ALOAD, 0)); 219 | toInject.add(new FieldInsnNode(GETFIELD, internalFoodStatsName, ObfHelper.isObfuscated() ? "field_75125_b" : "foodSaturationLevel", "F")); 220 | toInject.add(new VarInsnNode(FSTORE, prevSaturationLevel.index)); 221 | toInject.add(prevSaturationLevelStart); 222 | 223 | method.instructions.insertBefore(targetNode, toInject); 224 | 225 | /* 226 | * Make all calls to getHealAmount/getSaturationModifier use the modified values instead 227 | */ 228 | InsnList hungerNeedle = new InsnList(); 229 | hungerNeedle.add(new VarInsnNode(ALOAD, 1)); 230 | hungerNeedle.add(new VarInsnNode(ALOAD, 2)); 231 | hungerNeedle.add(new MethodInsnNode(INVOKEVIRTUAL, ObfHelper.getInternalClassName(ASMConstants.ITEM_FOOD), ObfHelper.isObfuscated() ? "func_150905_g" : "getHealAmount" , ASMHelper.toMethodDescriptor("I", ASMHelper.toDescriptor(ASMConstants.ITEM_STACK)), false)); 232 | 233 | InsnList hungerReplacement = new InsnList(); 234 | hungerReplacement.add(new VarInsnNode(ALOAD, modifiedFoodValues.index)); 235 | hungerReplacement.add(new FieldInsnNode(GETFIELD, ASMHelper.toInternalClassName(ASMConstants.FOOD_VALUES), "hunger", "I")); 236 | 237 | InsnList saturationNeedle = new InsnList(); 238 | saturationNeedle.add(new VarInsnNode(ALOAD, 1)); 239 | saturationNeedle.add(new VarInsnNode(ALOAD, 2)); 240 | saturationNeedle.add(new MethodInsnNode(INVOKEVIRTUAL, ObfHelper.getInternalClassName(ASMConstants.ITEM_FOOD), ObfHelper.isObfuscated() ? "func_150906_h" : "getSaturationModifier", ASMHelper.toMethodDescriptor("F", ASMHelper.toDescriptor(ASMConstants.ITEM_STACK)), false)); 241 | 242 | InsnList saturationReplacement = new InsnList(); 243 | saturationReplacement.add(new VarInsnNode(ALOAD, modifiedFoodValues.index)); 244 | saturationReplacement.add(new FieldInsnNode(GETFIELD, ASMHelper.toInternalClassName(ASMConstants.FOOD_VALUES), "saturationModifier", "F")); 245 | 246 | ASMHelper.findAndReplaceAll(method.instructions, hungerNeedle, hungerReplacement); 247 | ASMHelper.findAndReplaceAll(method.instructions, saturationNeedle, saturationReplacement); 248 | 249 | /* 250 | * onPostFoodStatsAdded 251 | */ 252 | targetNode = ASMHelper.findLastInstructionWithOpcode(method, RETURN); 253 | toInject.clear(); 254 | 255 | // this 256 | toInject.add(new VarInsnNode(ALOAD, 0)); 257 | 258 | // par1 (ItemFood) 259 | toInject.add(new VarInsnNode(ALOAD, 1)); 260 | 261 | // par2 (ItemStack) 262 | toInject.add(new VarInsnNode(ALOAD, 2)); 263 | 264 | // modifiedFoodValues 265 | toInject.add(new VarInsnNode(ALOAD, modifiedFoodValues.index)); 266 | 267 | // prevFoodLevel - this.foodLevel 268 | toInject.add(new VarInsnNode(ALOAD, 0)); 269 | toInject.add(new FieldInsnNode(GETFIELD, internalFoodStatsName, ObfHelper.isObfuscated() ? "field_75127_a" : "foodLevel", "I")); 270 | toInject.add(new VarInsnNode(ILOAD, prevFoodLevel.index)); 271 | toInject.add(new InsnNode(ISUB)); 272 | 273 | // prevSaturationLevel - this.foodSaturationLevel 274 | toInject.add(new VarInsnNode(ALOAD, 0)); 275 | toInject.add(new FieldInsnNode(GETFIELD, internalFoodStatsName, ObfHelper.isObfuscated() ? "field_75125_b" : "foodSaturationLevel", "F")); 276 | toInject.add(new VarInsnNode(FLOAD, prevSaturationLevel.index)); 277 | toInject.add(new InsnNode(FSUB)); 278 | 279 | // player 280 | toInject.add(new VarInsnNode(ALOAD, 0)); 281 | toInject.add(new FieldInsnNode(GETFIELD, internalFoodStatsName, foodStatsPlayerField, ASMHelper.toDescriptor(ASMConstants.PLAYER))); 282 | toInject.add(new MethodInsnNode(INVOKESTATIC, ASMConstants.HOOKS_INTERNAL_CLASS, "onPostFoodStatsAdded", ASMHelper.toMethodDescriptor("V", ASMConstants.FOOD_STATS, ASMConstants.ITEM_FOOD, ASMConstants.ITEM_STACK, ASMConstants.FOOD_VALUES, "I", "F", ASMConstants.PLAYER), false)); 283 | 284 | method.instructions.insertBefore(targetNode, toInject); 285 | } 286 | 287 | private void hookFoodStatsAddition(ClassNode classNode, MethodNode method) 288 | { 289 | // injected code: 290 | /* 291 | if (!Hooks.fireFoodStatsAdditionEvent(player, new FoodValues(p_75122_1_, p_75122_2_))) 292 | { 293 | // default code 294 | } 295 | */ 296 | 297 | AbstractInsnNode targetNode = ASMHelper.findFirstInstruction(method); 298 | 299 | LabelNode ifCanceled = new LabelNode(); 300 | 301 | InsnList toInject = new InsnList(); 302 | toInject.add(new VarInsnNode(ALOAD, 0)); 303 | toInject.add(new FieldInsnNode(GETFIELD, ASMHelper.toInternalClassName(classNode.name), foodStatsPlayerField, ASMHelper.toDescriptor(ASMConstants.PLAYER))); 304 | toInject.add(new TypeInsnNode(NEW, ASMHelper.toInternalClassName(ASMConstants.FOOD_VALUES))); 305 | toInject.add(new InsnNode(DUP)); 306 | toInject.add(new VarInsnNode(ILOAD, 1)); 307 | toInject.add(new VarInsnNode(FLOAD, 2)); 308 | toInject.add(new MethodInsnNode(INVOKESPECIAL, ASMHelper.toInternalClassName(ASMConstants.FOOD_VALUES), "", ASMHelper.toMethodDescriptor("V", "I", "F"), false)); 309 | toInject.add(new MethodInsnNode(INVOKESTATIC, ASMConstants.HOOKS_INTERNAL_CLASS, "fireFoodStatsAdditionEvent", ASMHelper.toMethodDescriptor("Z", ASMConstants.PLAYER, ASMConstants.FOOD_VALUES), false)); 310 | toInject.add(new JumpInsnNode(IFNE, ifCanceled)); 311 | 312 | method.instructions.insertBefore(targetNode, toInject); 313 | 314 | targetNode = ASMHelper.findLastInstructionWithOpcode(method, RETURN); 315 | 316 | method.instructions.insertBefore(targetNode, ifCanceled); 317 | 318 | // BIPUSH 20 replaced with GetMaxHunger lookup 319 | InsnList needle = new InsnList(); 320 | needle.add(new IntInsnNode(BIPUSH, 20)); 321 | InsnList replacement = new InsnList(); 322 | replacement.add(new VarInsnNode(ALOAD, 0)); 323 | replacement.add(new MethodInsnNode(INVOKESTATIC, ASMHelper.toInternalClassName(ASMConstants.HOOKS), "getMaxHunger", ASMHelper.toMethodDescriptor("I", ASMConstants.FOOD_STATS), false)); 324 | 325 | ASMHelper.findAndReplaceAll(method.instructions, needle, replacement); 326 | } 327 | 328 | private void hookUpdate(ClassNode classNode, MethodNode updateMethodNode) 329 | { 330 | LabelNode ifSkipReturn = new LabelNode(); 331 | 332 | InsnList toInject = new InsnList(); 333 | toInject.add(new VarInsnNode(ALOAD, 0)); 334 | toInject.add(new VarInsnNode(ALOAD, 1)); 335 | toInject.add(new MethodInsnNode(INVOKESTATIC, ASMHelper.toInternalClassName(ASMConstants.HOOKS), "onAppleCoreFoodStatsUpdate", ASMHelper.toMethodDescriptor("Z", ASMConstants.FOOD_STATS, ASMConstants.PLAYER), false)); 336 | toInject.add(new JumpInsnNode(IFEQ, ifSkipReturn)); 337 | toInject.add(new LabelNode()); 338 | toInject.add(new InsnNode(RETURN)); 339 | toInject.add(ifSkipReturn); 340 | 341 | updateMethodNode.instructions.insertBefore(ASMHelper.findFirstInstruction(updateMethodNode), toInject); 342 | } 343 | 344 | private void hookNeedFood(ClassNode classNode, MethodNode needFoodMethodNode) 345 | { 346 | InsnList toInject = new InsnList(); 347 | toInject.add(new VarInsnNode(ALOAD, 0)); 348 | toInject.add(new MethodInsnNode(INVOKESTATIC, ASMHelper.toInternalClassName(ASMConstants.HOOKS), "needFood", ASMHelper.toMethodDescriptor("Z", ASMConstants.FOOD_STATS), false)); 349 | toInject.add(new InsnNode(IRETURN)); 350 | 351 | needFoodMethodNode.instructions.clear(); 352 | needFoodMethodNode.instructions.insert(toInject); 353 | } 354 | 355 | private void hookAddExhaustion(ClassNode classNode, MethodNode addExhaustionMethodNode) 356 | { 357 | InsnList toInject = new InsnList(); 358 | toInject.add(new VarInsnNode(ALOAD, 0)); 359 | toInject.add(new VarInsnNode(FLOAD, 1)); 360 | toInject.add(new MethodInsnNode(INVOKESTATIC, ASMHelper.toInternalClassName(ASMConstants.HOOKS), "onExhaustionAdded", ASMHelper.toMethodDescriptor("F", ASMConstants.FOOD_STATS, "F"), false)); 361 | toInject.add(new VarInsnNode(FSTORE, 1)); 362 | 363 | addExhaustionMethodNode.instructions.insertBefore(ASMHelper.findFirstInstruction(addExhaustionMethodNode), toInject); 364 | 365 | // Replace the 40.0f constant with GetExhaustionCap call 366 | InsnList replacement = new InsnList(); 367 | replacement.add(new VarInsnNode(ALOAD, 0)); 368 | replacement.add(new MethodInsnNode(INVOKESTATIC, ASMHelper.toInternalClassName(ASMConstants.HOOKS), "getExhaustionCap", ASMHelper.toMethodDescriptor("F", ASMConstants.FOOD_STATS), false)); 369 | 370 | InsnList needle = new InsnList(); 371 | needle.add(new LdcInsnNode(new Float("40.0"))); 372 | 373 | if (ASMHelper.findAndReplaceAll(addExhaustionMethodNode.instructions, needle, replacement) == 0) 374 | { 375 | throw new RuntimeException("Failed to inject GetExhaustionCap"); 376 | } 377 | } 378 | 379 | private boolean tryAddFieldGetter(ClassNode classNode, String methodName, String fieldName, String fieldDescriptor) 380 | { 381 | String methodDescriptor = ASMHelper.toMethodDescriptor(fieldDescriptor); 382 | if (ASMHelper.findMethodNodeOfClass(classNode, methodName, methodDescriptor) != null) 383 | return false; 384 | 385 | MethodVisitor mv = classNode.visitMethod(ACC_PUBLIC, methodName, methodDescriptor, null, null); 386 | mv.visitVarInsn(ALOAD, 0); 387 | mv.visitFieldInsn(GETFIELD, ASMHelper.toInternalClassName(classNode.name), fieldName, fieldDescriptor); 388 | mv.visitInsn(Type.getType(fieldDescriptor).getOpcode(IRETURN)); 389 | mv.visitMaxs(0, 0); 390 | return true; 391 | } 392 | 393 | private boolean tryAddFieldSetter(ClassNode classNode, String methodName, String fieldName, String fieldDescriptor) 394 | { 395 | String methodDescriptor = ASMHelper.toMethodDescriptor("V", fieldDescriptor); 396 | if (ASMHelper.findMethodNodeOfClass(classNode, methodName, methodDescriptor) != null) 397 | return false; 398 | 399 | MethodVisitor mv = classNode.visitMethod(ACC_PUBLIC, methodName, methodDescriptor, null, null); 400 | mv.visitVarInsn(ALOAD, 0); 401 | mv.visitVarInsn(Type.getType(fieldDescriptor).getOpcode(ILOAD), 1); 402 | mv.visitFieldInsn(PUTFIELD, ASMHelper.toInternalClassName(classNode.name), fieldName, fieldDescriptor); 403 | mv.visitInsn(RETURN); 404 | mv.visitMaxs(0, 0); 405 | return true; 406 | } 407 | } -------------------------------------------------------------------------------- /java/squeek/applecore/asm/module/ModuleHungerHUD.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.asm.module; 2 | 3 | import org.objectweb.asm.tree.*; 4 | import squeek.applecore.asm.ASMConstants; 5 | import squeek.applecore.asm.IClassTransformerModule; 6 | import squeek.asmhelper.applecore.ASMHelper; 7 | import squeek.asmhelper.applecore.ObfHelper; 8 | 9 | import static org.objectweb.asm.Opcodes.INVOKESTATIC; 10 | import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; 11 | 12 | /** 13 | * Modify the hunger HUD rendering to draw hunger relative to AppleCore's variable max hunger 14 | */ 15 | public class ModuleHungerHUD implements IClassTransformerModule 16 | { 17 | @Override 18 | public String[] getClassesToTransform() 19 | { 20 | return new String[]{ASMConstants.GUI_INGAME_FORGE}; 21 | } 22 | 23 | @Override 24 | public byte[] transform(String name, String transformedName, byte[] basicClass) 25 | { 26 | ClassNode classNode = ASMHelper.readClassFromBytes(basicClass); 27 | 28 | MethodNode methodNode = ASMHelper.findMethodNodeOfClass(classNode, "renderFood", "(II)V"); 29 | if (methodNode != null) 30 | { 31 | InsnList needle = new InsnList(); 32 | needle.add(new MethodInsnNode(INVOKEVIRTUAL, ObfHelper.getInternalClassName(ASMConstants.FOOD_STATS), ObfHelper.isObfuscated() ? "func_75116_a" : "getFoodLevel", ASMHelper.toMethodDescriptor("I"), false)); 33 | 34 | InsnList replace = new InsnList(); 35 | replace.add(new MethodInsnNode(INVOKESTATIC, ASMConstants.HOOKS_INTERNAL_CLASS, "getHungerForDisplay", ASMHelper.toMethodDescriptor("I", ASMConstants.FOOD_STATS), false)); 36 | 37 | AbstractInsnNode found = ASMHelper.findAndReplace(methodNode.instructions, needle, replace); 38 | 39 | if (found == null) 40 | throw new RuntimeException("GuiIngameForge: expected instruction pattern not found"); 41 | 42 | return ASMHelper.writeClassToBytes(classNode); 43 | } 44 | else 45 | throw new RuntimeException("GuiIngameForge: renderFood method not found"); 46 | } 47 | } -------------------------------------------------------------------------------- /java/squeek/applecore/asm/module/ModulePeacefulRegen.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.asm.module; 2 | 3 | import org.objectweb.asm.tree.*; 4 | import squeek.applecore.asm.ASMConstants; 5 | import squeek.applecore.asm.IClassTransformerModule; 6 | import squeek.asmhelper.applecore.ASMHelper; 7 | import squeek.asmhelper.applecore.ObfHelper; 8 | 9 | import static org.objectweb.asm.Opcodes.*; 10 | 11 | public class ModulePeacefulRegen implements IClassTransformerModule 12 | { 13 | @Override 14 | public String[] getClassesToTransform() 15 | { 16 | return new String[]{ASMConstants.PLAYER}; 17 | } 18 | 19 | @Override 20 | public byte[] transform(String name, String transformedName, byte[] basicClass) 21 | { 22 | if (transformedName.equals(ASMConstants.PLAYER)) 23 | { 24 | ClassNode classNode = ASMHelper.readClassFromBytes(basicClass); 25 | 26 | MethodNode methodNode = ASMHelper.findMethodNodeOfClass(classNode, "func_70636_d", "onLivingUpdate", ASMHelper.toMethodDescriptor("V")); 27 | if (methodNode != null) 28 | { 29 | addPeacefulRegenHook(classNode, methodNode); 30 | addPeacefulHungerRegenHook(classNode, methodNode); 31 | return ASMHelper.writeClassToBytes(classNode); 32 | } 33 | else 34 | throw new RuntimeException("EntityPlayer: onLivingUpdate method not found"); 35 | } 36 | return basicClass; 37 | } 38 | 39 | public void addPeacefulRegenHook(ClassNode classNode, MethodNode method) 40 | { 41 | AbstractInsnNode relevantParentConditional = ASMHelper.find(method.instructions, new LdcInsnNode("naturalRegeneration")); 42 | AbstractInsnNode relevantConditional = ASMHelper.find(relevantParentConditional, new MethodInsnNode(INVOKEVIRTUAL, ASMHelper.toInternalClassName(ASMConstants.PLAYER), ObfHelper.isObfuscated() ? "func_110143_aJ" : "getHealth", ASMHelper.toMethodDescriptor("F"), false)); 43 | JumpInsnNode ifNode = (JumpInsnNode) ASMHelper.find(relevantConditional, new JumpInsnNode(IFNE, new LabelNode())); 44 | LabelNode ifBlockEndLabel = ifNode.label; 45 | AbstractInsnNode targetNode = ASMHelper.find(ifNode, new InsnNode(FCONST_1)).getPrevious(); 46 | 47 | int peacefulRegenEventIndex = firePeacefulRegenEventAndStoreEventBefore(method, targetNode, ifBlockEndLabel); 48 | 49 | InsnList healAmountNeedle = new InsnList(); 50 | healAmountNeedle.add(new InsnNode(FCONST_1)); 51 | 52 | InsnList healAmountReplacement = new InsnList(); 53 | healAmountReplacement.add(new VarInsnNode(ALOAD, peacefulRegenEventIndex)); 54 | healAmountReplacement.add(new FieldInsnNode(GETFIELD, ASMHelper.toInternalClassName(ASMConstants.HealthRegenEvent.PEACEFUL_REGEN), "deltaHealth", "F")); 55 | 56 | ASMHelper.findAndReplace(method.instructions, healAmountNeedle, healAmountReplacement, targetNode); 57 | 58 | InsnList ifNotCanceledBlock = new InsnList(); 59 | LabelNode ifNotCanceled = new LabelNode(); 60 | 61 | ifNotCanceledBlock.add(new VarInsnNode(ALOAD, peacefulRegenEventIndex)); 62 | ifNotCanceledBlock.add(new MethodInsnNode(INVOKEVIRTUAL, ASMHelper.toInternalClassName(ASMConstants.HealthRegenEvent.PEACEFUL_REGEN), "isCanceled", ASMHelper.toMethodDescriptor("Z"), false)); 63 | ifNotCanceledBlock.add(new JumpInsnNode(IFNE, ifNotCanceled)); 64 | method.instructions.insertBefore(targetNode, ifNotCanceledBlock); 65 | 66 | method.instructions.insertBefore(ifBlockEndLabel, ifNotCanceled); 67 | } 68 | 69 | private int firePeacefulRegenEventAndStoreEventBefore(MethodNode method, AbstractInsnNode injectPoint, LabelNode endLabel) 70 | { 71 | // create variable 72 | LabelNode peacefulRegenEventStart = new LabelNode(); 73 | LocalVariableNode peacefulRegenEvent = new LocalVariableNode("peacefulRegenEvent", ASMHelper.toDescriptor(ASMConstants.HealthRegenEvent.PEACEFUL_REGEN), null, peacefulRegenEventStart, endLabel, method.maxLocals); 74 | method.maxLocals += 1; 75 | method.localVariables.add(peacefulRegenEvent); 76 | 77 | InsnList toInject = new InsnList(); 78 | 79 | // HealthRegenEvent.PeacefulRegen peacefulRegenEvent = Hooks.firePeacefulRegenEvent(this); 80 | toInject.add(new VarInsnNode(ALOAD, 0)); 81 | toInject.add(new MethodInsnNode(INVOKESTATIC, ASMHelper.toInternalClassName(ASMConstants.HOOKS), "firePeacefulRegenEvent", ASMHelper.toMethodDescriptor(ASMConstants.HealthRegenEvent.PEACEFUL_REGEN, ASMConstants.PLAYER), false)); 82 | toInject.add(new VarInsnNode(ASTORE, peacefulRegenEvent.index)); 83 | toInject.add(peacefulRegenEventStart); 84 | 85 | method.instructions.insertBefore(injectPoint, toInject); 86 | 87 | return peacefulRegenEvent.index; 88 | } 89 | 90 | public void addPeacefulHungerRegenHook(ClassNode classNode, MethodNode method) 91 | { 92 | AbstractInsnNode relevantParentConditional = ASMHelper.find(method.instructions, new LdcInsnNode("naturalRegeneration")); 93 | AbstractInsnNode relevantConditional = ASMHelper.find(relevantParentConditional, new MethodInsnNode(INVOKEVIRTUAL, ASMHelper.toInternalClassName(ASMConstants.FOOD_STATS), ObfHelper.isObfuscated() ? "func_75121_c" : "needFood", "()Z", false)); 94 | JumpInsnNode ifNode = (JumpInsnNode) ASMHelper.find(relevantConditional, new JumpInsnNode(IFNE, new LabelNode())); 95 | LabelNode ifBlockEndLabel = ifNode.label; 96 | AbstractInsnNode targetNode = ASMHelper.findNextInstruction(ifNode); 97 | 98 | int peacefulRegenEventIndex = firePeacefulHungerRegenEventAndStoreEventBefore(method, targetNode, ifBlockEndLabel); 99 | 100 | InsnList healAmountNeedle = new InsnList(); 101 | healAmountNeedle.add(new InsnNode(ICONST_1)); 102 | 103 | InsnList healAmountReplacement = new InsnList(); 104 | healAmountReplacement.add(new VarInsnNode(ALOAD, peacefulRegenEventIndex)); 105 | healAmountReplacement.add(new FieldInsnNode(GETFIELD, ASMHelper.toInternalClassName(ASMConstants.HungerRegenEvent.PEACEFUL_REGEN), "deltaHunger", "I")); 106 | 107 | ASMHelper.findAndReplace(method.instructions, healAmountNeedle, healAmountReplacement, targetNode); 108 | 109 | InsnList ifNotCanceledBlock = new InsnList(); 110 | LabelNode ifNotCanceled = new LabelNode(); 111 | 112 | ifNotCanceledBlock.add(new VarInsnNode(ALOAD, peacefulRegenEventIndex)); 113 | ifNotCanceledBlock.add(new MethodInsnNode(INVOKEVIRTUAL, ASMHelper.toInternalClassName(ASMConstants.HungerRegenEvent.PEACEFUL_REGEN), "isCanceled", ASMHelper.toMethodDescriptor("Z"), false)); 114 | ifNotCanceledBlock.add(new JumpInsnNode(IFNE, ifNotCanceled)); 115 | method.instructions.insertBefore(targetNode, ifNotCanceledBlock); 116 | 117 | method.instructions.insertBefore(ifBlockEndLabel, ifNotCanceled); 118 | } 119 | 120 | private int firePeacefulHungerRegenEventAndStoreEventBefore(MethodNode method, AbstractInsnNode injectPoint, LabelNode endLabel) 121 | { 122 | // create variable 123 | LabelNode peacefulRegenEventStart = new LabelNode(); 124 | LocalVariableNode peacefulRegenEvent = new LocalVariableNode("peacefulHungerRegenEvent", ASMHelper.toDescriptor(ASMConstants.HungerRegenEvent.PEACEFUL_REGEN), null, peacefulRegenEventStart, endLabel, method.maxLocals); 125 | method.maxLocals += 1; 126 | method.localVariables.add(peacefulRegenEvent); 127 | 128 | InsnList toInject = new InsnList(); 129 | 130 | // HealthRegenEvent.PeacefulRegen peacefulRegenEvent = Hooks.firePeacefulRegenEvent(this); 131 | toInject.add(new VarInsnNode(ALOAD, 0)); 132 | toInject.add(new MethodInsnNode(INVOKESTATIC, ASMHelper.toInternalClassName(ASMConstants.HOOKS), "firePeacefulHungerRegenEvent", ASMHelper.toMethodDescriptor(ASMConstants.HungerRegenEvent.PEACEFUL_REGEN, ASMConstants.PLAYER), false)); 133 | toInject.add(new VarInsnNode(ASTORE, peacefulRegenEvent.index)); 134 | toInject.add(peacefulRegenEventStart); 135 | 136 | method.instructions.insertBefore(injectPoint, toInject); 137 | 138 | return peacefulRegenEvent.index; 139 | } 140 | } -------------------------------------------------------------------------------- /java/squeek/applecore/asm/reference/BlockCakeModifications.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.asm.reference; 2 | 3 | import net.minecraft.block.BlockCake; 4 | import net.minecraft.block.state.IBlockState; 5 | import net.minecraft.entity.player.EntityPlayer; 6 | import net.minecraft.item.ItemStack; 7 | import net.minecraft.stats.StatList; 8 | import net.minecraft.util.math.BlockPos; 9 | import net.minecraft.world.World; 10 | import squeek.applecore.api.food.FoodValues; 11 | import squeek.applecore.api.food.IEdibleBlock; 12 | import squeek.applecore.asm.Hooks; 13 | 14 | public class BlockCakeModifications extends BlockCake /* implemented by AppleCore */ implements IEdibleBlock 15 | { 16 | @SuppressWarnings("unused") 17 | private boolean eatCake(World world, BlockPos pos, IBlockState state, EntityPlayer player) 18 | { 19 | if (!player.canEat(AppleCore_isEdibleAtMaxHunger)) // modified (changed false to AppleCore_isEdibleAtMaxHunger 20 | { 21 | return false; 22 | } 23 | else 24 | { 25 | // begin modifications 26 | Hooks.onBlockFoodEaten(this, world, player); 27 | // end modifications 28 | 29 | player.addStat(StatList.CAKE_SLICES_EATEN); 30 | 31 | int i = state.getValue(BITES); 32 | 33 | if (i < 6) 34 | { 35 | world.setBlockState(pos, state.withProperty(BITES, i + 1), 3); 36 | } 37 | else 38 | { 39 | world.setBlockToAir(pos); 40 | } 41 | 42 | return true; 43 | } 44 | } 45 | 46 | // All of the following added by AppleCore 47 | private boolean AppleCore_isEdibleAtMaxHunger; 48 | 49 | @Override 50 | public void setEdibleAtMaxHunger(boolean value) 51 | { 52 | AppleCore_isEdibleAtMaxHunger = value; 53 | } 54 | 55 | @Override 56 | public FoodValues getFoodValues(ItemStack itemStack) 57 | { 58 | return new FoodValues(2, 0.1f); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /java/squeek/applecore/asm/reference/EntityPlayerModifications.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.asm.reference; 2 | 3 | import com.mojang.authlib.GameProfile; 4 | import net.minecraft.entity.player.EntityPlayer; 5 | import net.minecraft.item.ItemStack; 6 | import net.minecraft.stats.StatList; 7 | import net.minecraft.util.EnumHand; 8 | import net.minecraft.util.FoodStats; 9 | import net.minecraft.world.EnumDifficulty; 10 | import net.minecraft.world.World; 11 | import net.minecraftforge.event.ForgeEventFactory; 12 | import squeek.applecore.api.hunger.ExhaustionEvent; 13 | import squeek.applecore.api.hunger.HealthRegenEvent; 14 | import squeek.applecore.api.hunger.HungerRegenEvent; 15 | import squeek.applecore.asm.Hooks; 16 | 17 | import javax.annotation.Nonnull; 18 | 19 | public abstract class EntityPlayerModifications extends EntityPlayer 20 | { 21 | // modified initialization of foodStats field to use the added constructor 22 | protected FoodStats foodStats = new FoodStatsModifications(this); 23 | 24 | // added field 25 | public int itemInUseMaxDuration; 26 | 27 | // a single line added 28 | @Override 29 | public void setActiveHand(@Nonnull EnumHand hand) 30 | { 31 | ItemStack stack = this.getHeldItem(hand); 32 | 33 | if (!stack.isEmpty() && !this.isHandActive()) 34 | { 35 | int duration = ForgeEventFactory.onItemUseStart(this, stack, stack.getMaxItemUseDuration()); 36 | if (duration <= 0) return; 37 | this.activeItemStack = stack; 38 | this.activeItemStackUseCount = duration; 39 | 40 | // added: 41 | this.itemInUseMaxDuration = duration; 42 | 43 | if (!this.world.isRemote) 44 | { 45 | int i = 1; 46 | 47 | if (hand == EnumHand.OFF_HAND) 48 | { 49 | i |= 2; 50 | } 51 | 52 | this.dataManager.set(HAND_STATES, (byte) i); 53 | } 54 | } 55 | } 56 | 57 | // changed this.activeItemStack.getMaxItemUseDuration() to Hooks.getItemInUseMaxDuration() 58 | @Override 59 | public int getItemInUseMaxCount() 60 | { 61 | return this.isHandActive() ? Hooks.getItemInUseMaxCount(this, itemInUseMaxDuration) - this.itemInUseCount : 0; 62 | } 63 | 64 | // add hook for peaceful health regen 65 | @Override 66 | public void onLivingUpdate() 67 | { 68 | if (this.flyToggleTimer > 0) 69 | { 70 | --this.flyToggleTimer; 71 | } 72 | 73 | // modified 74 | if (this.world.getDifficulty() == EnumDifficulty.PEACEFUL && this.world.getGameRules().getBoolean("naturalRegeneration")) 75 | { 76 | if (this.getHealth() < this.getMaxHealth() && this.ticksExisted % 20 == 0) 77 | { 78 | // added event and if statement 79 | HealthRegenEvent.PeacefulRegen peacefulRegenEvent = Hooks.firePeacefulRegenEvent(this); 80 | if (!peacefulRegenEvent.isCanceled()) 81 | { 82 | // modified from this.heal(1.0F); 83 | this.heal(peacefulRegenEvent.deltaHealth); 84 | } 85 | } 86 | 87 | if (this.foodStats.needFood() && this.ticksExisted % 10 == 0) 88 | { 89 | HungerRegenEvent.PeacefulRegen peacefulHungerRegenEvent = Hooks.firePeacefulHungerRegenEvent(this); 90 | if (!peacefulHungerRegenEvent.isCanceled()) { 91 | this.foodStats.setFoodLevel(this.foodStats.getFoodLevel() + peacefulHungerRegenEvent.deltaHunger); 92 | } 93 | } 94 | 95 | // ... 96 | } 97 | } 98 | 99 | // example modification of addExhaustion callers throughout the code 100 | @Override 101 | public void jump() 102 | { 103 | super.jump(); 104 | this.addStat(StatList.JUMP); 105 | 106 | if (this.isSprinting()) 107 | { 108 | this.addExhaustion(Hooks.fireExhaustingActionEvent(this, ExhaustionEvent.ExhaustingActions.SPRINTING_JUMP, 0.2F)); 109 | } 110 | else 111 | { 112 | this.addExhaustion(Hooks.fireExhaustingActionEvent(this, ExhaustionEvent.ExhaustingActions.NORMAL_JUMP, 0.05F)); 113 | } 114 | } 115 | 116 | /* 117 | * everything below is unmodified 118 | * it is only required to avoid compilation errors 119 | */ 120 | public ItemStack itemInUse; 121 | public int itemInUseCount; 122 | 123 | public EntityPlayerModifications(World world, GameProfile gameProfile) 124 | { 125 | super(world, gameProfile); 126 | } 127 | } -------------------------------------------------------------------------------- /java/squeek/applecore/asm/reference/FoodStatsModifications.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.asm.reference; 2 | 3 | import net.minecraft.entity.player.EntityPlayer; 4 | import net.minecraft.item.ItemFood; 5 | import net.minecraft.item.ItemStack; 6 | import net.minecraft.util.FoodStats; 7 | import squeek.applecore.api.food.FoodValues; 8 | import squeek.applecore.asm.Hooks; 9 | 10 | import javax.annotation.Nonnull; 11 | 12 | /* 13 | * The end result of the changes made by ModuleFoodStats 14 | */ 15 | public class FoodStatsModifications extends FoodStats 16 | { 17 | // added fields 18 | EntityPlayer player; 19 | int starveTimer; 20 | 21 | // added constructor 22 | public FoodStatsModifications(EntityPlayer player) 23 | { 24 | this.player = player; 25 | } 26 | 27 | // default code wrapped in a conditional 28 | @Override 29 | public void addStats(int foodLevel, float foodSaturationModifier) 30 | { 31 | if (!Hooks.fireFoodStatsAdditionEvent(player, new FoodValues(foodLevel, foodSaturationModifier))) 32 | { 33 | // 20 replaced with getMaxHunger 34 | this.foodLevel = Math.min(foodLevel + this.foodLevel, Hooks.getMaxHunger(this)); 35 | this.foodSaturationLevel = Math.min(this.foodSaturationLevel + foodLevel * foodSaturationModifier * 2.0F, this.foodLevel); 36 | } 37 | } 38 | 39 | // hooks injected into method 40 | @Override 41 | public void addStats(@Nonnull ItemFood food, @Nonnull ItemStack stack) 42 | { 43 | // added lines 44 | FoodValues modifiedFoodValues = Hooks.onFoodStatsAdded(this, food, stack, this.player); 45 | int prevFoodLevel = this.foodLevel; 46 | float prevSaturationLevel = this.foodSaturationLevel; 47 | 48 | // this is a default line that has been altered to use the modified food values 49 | this.addStats(modifiedFoodValues.hunger, modifiedFoodValues.saturationModifier); 50 | 51 | // added lines 52 | Hooks.onPostFoodStatsAdded(this, food, stack, modifiedFoodValues, this.foodLevel - prevFoodLevel, this.foodSaturationLevel - prevSaturationLevel, this.player); 53 | } 54 | 55 | @Override 56 | public void onUpdate(EntityPlayer player) 57 | { 58 | // added lines 59 | if (Hooks.onAppleCoreFoodStatsUpdate(this, player)) 60 | return; 61 | 62 | // the body of the base function 63 | } 64 | 65 | @Override 66 | public void addExhaustion(float exhaustion) 67 | { 68 | exhaustion = Hooks.onExhaustionAdded(this, exhaustion); 69 | 70 | this.foodExhaustionLevel = Math.min(this.foodExhaustionLevel + exhaustion, Hooks.getExhaustionCap(this)); 71 | } 72 | 73 | // start unmodified 74 | int foodLevel = 20; 75 | float foodSaturationLevel = 5f; 76 | int prevFoodLevel = 20; 77 | int foodTimer; 78 | float foodExhaustionLevel; 79 | // end unmodified 80 | } -------------------------------------------------------------------------------- /java/squeek/applecore/asm/util/IAppleCoreFoodStats.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.asm.util; 2 | 3 | import net.minecraft.entity.player.EntityPlayer; 4 | 5 | public interface IAppleCoreFoodStats 6 | { 7 | int getFoodTimer(); 8 | void setFoodTimer(int value); 9 | int getStarveTimer(); 10 | void setStarveTimer(int value); 11 | EntityPlayer getPlayer(); 12 | void setPlayer(EntityPlayer player); 13 | void setPrevFoodLevel(int value); 14 | float getExhaustion(); 15 | void setExhaustion(float value); 16 | void setSaturation(float value); 17 | } -------------------------------------------------------------------------------- /java/squeek/applecore/commands/CommandHunger.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.commands; 2 | 3 | import net.minecraft.command.CommandBase; 4 | import net.minecraft.command.CommandException; 5 | import net.minecraft.command.ICommandSender; 6 | import net.minecraft.command.WrongUsageException; 7 | import net.minecraft.entity.player.EntityPlayerMP; 8 | import net.minecraft.server.MinecraftServer; 9 | import net.minecraft.util.math.BlockPos; 10 | import squeek.applecore.api.AppleCoreAPI; 11 | 12 | import javax.annotation.Nonnull; 13 | import java.util.Collections; 14 | import java.util.List; 15 | 16 | public class CommandHunger extends CommandBase 17 | { 18 | @Override 19 | @Nonnull 20 | public String getName() 21 | { 22 | return "hunger"; 23 | } 24 | 25 | @Override 26 | @Nonnull 27 | public String getUsage(@Nonnull ICommandSender sender) 28 | { 29 | return "applecore.commands.hunger.usage"; 30 | } 31 | 32 | @Override 33 | public int getRequiredPermissionLevel() 34 | { 35 | return 2; 36 | } 37 | 38 | @Override 39 | public void execute(@Nonnull MinecraftServer server, @Nonnull ICommandSender commandSender, @Nonnull String[] args) throws CommandException 40 | { 41 | if (args.length > 0) 42 | { 43 | EntityPlayerMP playerToActOn = args.length >= 2 ? getPlayer(server, commandSender, args[0]) : getCommandSenderAsPlayer(commandSender); 44 | int maxHunger = AppleCoreAPI.accessor.getMaxHunger(playerToActOn); 45 | int newHunger = args.length >= 2 ? parseInt(args[1], 0, maxHunger) : parseInt(args[0], 0, maxHunger); 46 | 47 | AppleCoreAPI.mutator.setHunger(playerToActOn, newHunger); 48 | if (playerToActOn.getFoodStats().getSaturationLevel() > newHunger) 49 | AppleCoreAPI.mutator.setSaturation(playerToActOn, newHunger); 50 | 51 | notifyCommandListener(commandSender, this, 1, "applecore.commands.hunger.set.hunger.to", playerToActOn.getDisplayName(), newHunger); 52 | } 53 | else 54 | { 55 | throw new WrongUsageException(getUsage(commandSender)); 56 | } 57 | } 58 | 59 | @Override 60 | @Nonnull 61 | public List getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args, BlockPos pos) 62 | { 63 | if (args.length == 1) 64 | return getListOfStringsMatchingLastWord(args, server.getOnlinePlayerNames()); 65 | else 66 | return Collections.emptyList(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /java/squeek/applecore/commands/Commands.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.commands; 2 | 3 | import net.minecraft.command.CommandHandler; 4 | import net.minecraft.server.MinecraftServer; 5 | 6 | public class Commands 7 | { 8 | public static void init(MinecraftServer server) 9 | { 10 | CommandHandler commandHandler = (CommandHandler) server.getCommandManager(); 11 | 12 | commandHandler.registerCommand(new CommandHunger()); 13 | } 14 | } -------------------------------------------------------------------------------- /java/squeek/applecore/example/AppleCoreExample.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.example; 2 | 3 | import net.minecraft.client.renderer.block.model.ModelResourceLocation; 4 | import net.minecraft.item.Item; 5 | import net.minecraft.util.ResourceLocation; 6 | import net.minecraftforge.client.event.ModelRegistryEvent; 7 | import net.minecraftforge.client.model.ModelLoader; 8 | import net.minecraftforge.common.MinecraftForge; 9 | import net.minecraftforge.event.RegistryEvent; 10 | import net.minecraftforge.fml.common.Loader; 11 | import net.minecraftforge.fml.common.Mod; 12 | import net.minecraftforge.fml.common.Mod.EventHandler; 13 | import net.minecraftforge.fml.common.Mod.Instance; 14 | import net.minecraftforge.fml.common.ModContainer; 15 | import net.minecraftforge.fml.common.event.FMLInitializationEvent; 16 | import net.minecraftforge.fml.common.event.FMLPreInitializationEvent; 17 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; 18 | import net.minecraftforge.fml.common.registry.GameRegistry; 19 | import net.minecraftforge.fml.common.versioning.ArtifactVersion; 20 | import net.minecraftforge.fml.relauncher.Side; 21 | import net.minecraftforge.fml.relauncher.SideOnly; 22 | import org.apache.logging.log4j.LogManager; 23 | import org.apache.logging.log4j.Logger; 24 | import squeek.applecore.ModInfo; 25 | 26 | @Mod.EventBusSubscriber 27 | @Mod(modid = ModInfo.MODID + "example", version = ModInfo.VERSION, dependencies = "required-after:applecore") 28 | public class AppleCoreExample 29 | { 30 | public static final Logger LOG = LogManager.getLogger(ModInfo.MODID + "example"); 31 | 32 | @Instance(ModInfo.MODID + "example") 33 | public static AppleCoreExample instance; 34 | 35 | public static Item testFood; 36 | public static Item testMetadataFood; 37 | 38 | @SubscribeEvent 39 | public static void registerItems(RegistryEvent.Register event) 40 | { 41 | testFood = new ItemNonStandardFood().setUnlocalizedName("testNonStandardFood"); 42 | testFood.setRegistryName(new ResourceLocation(ModInfo.MODID + "example", "testNonStandardFood")); 43 | event.getRegistry().register(testFood); 44 | 45 | testMetadataFood = new ItemMetadataFood(new int[]{1, 10}, new float[]{2f, 0.1f}).setUnlocalizedName("testMetadataFood"); 46 | testMetadataFood.setRegistryName(new ResourceLocation(ModInfo.MODID + "example", "testMetadataFood")); 47 | event.getRegistry().register(testMetadataFood); 48 | } 49 | 50 | @SideOnly(Side.CLIENT) 51 | @SubscribeEvent 52 | public static void registerModels(ModelRegistryEvent event) 53 | { 54 | ModelLoader.setCustomModelResourceLocation(testFood, 0, new ModelResourceLocation("potato")); 55 | ModelLoader.setCustomModelResourceLocation(testMetadataFood, 0, new ModelResourceLocation("potato")); 56 | ModelLoader.setCustomModelResourceLocation(testMetadataFood, 1, new ModelResourceLocation("potato")); 57 | } 58 | 59 | @EventHandler 60 | public void init(FMLInitializationEvent event) 61 | { 62 | // only add in the test modifications if we are 'alone' in the dev environment 63 | boolean otherDependantModsExist = false; 64 | for (ModContainer mod : Loader.instance().getActiveModList()) 65 | { 66 | if (mod.getMod() == this) 67 | continue; 68 | 69 | for (ArtifactVersion dependency : mod.getRequirements()) 70 | { 71 | if (dependency.getLabel().equals(ModInfo.MODID)) 72 | { 73 | otherDependantModsExist = true; 74 | break; 75 | } 76 | } 77 | if (otherDependantModsExist) 78 | break; 79 | } 80 | if (!otherDependantModsExist) 81 | { 82 | MinecraftForge.EVENT_BUS.register(new EatingSpeedModifier()); 83 | MinecraftForge.EVENT_BUS.register(new ExhaustionModifier()); 84 | MinecraftForge.EVENT_BUS.register(new FoodEatenResult()); 85 | MinecraftForge.EVENT_BUS.register(new FoodStatsAdditionCanceler()); 86 | MinecraftForge.EVENT_BUS.register(new FoodValuesModifier()); 87 | MinecraftForge.EVENT_BUS.register(new HealthRegenModifier()); 88 | MinecraftForge.EVENT_BUS.register(new HungerRegenModifier()); 89 | MinecraftForge.EVENT_BUS.register(new StarvationModifier()); 90 | MinecraftForge.EVENT_BUS.register(new MaxHungerModifier()); 91 | } 92 | if (event.getSide() == Side.CLIENT) 93 | MinecraftForge.EVENT_BUS.register(new FoodValuesTooltipHandler()); 94 | } 95 | } -------------------------------------------------------------------------------- /java/squeek/applecore/example/BlockEdibleExample.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.example; 2 | 3 | import net.minecraft.block.BlockCake; 4 | import net.minecraft.block.state.IBlockState; 5 | import net.minecraft.entity.player.EntityPlayer; 6 | import net.minecraft.item.ItemStack; 7 | import net.minecraft.stats.StatList; 8 | import net.minecraft.util.EnumFacing; 9 | import net.minecraft.util.EnumHand; 10 | import net.minecraft.util.math.BlockPos; 11 | import net.minecraft.world.World; 12 | import net.minecraftforge.fml.common.Loader; 13 | import net.minecraftforge.fml.common.Optional; 14 | import squeek.applecore.api.food.FoodValues; 15 | import squeek.applecore.api.food.IEdibleBlock; 16 | import squeek.applecore.api.food.ItemFoodProxy; 17 | 18 | import javax.annotation.Nonnull; 19 | import javax.annotation.Nullable; 20 | 21 | @Optional.Interface(iface = "squeek.applecore.api.food.IEdibleBlock", modid = "applecore") 22 | public class BlockEdibleExample extends BlockCake implements IEdibleBlock 23 | { 24 | private boolean isEdibleAtMaxHunger = false; 25 | 26 | @Optional.Method(modid = "applecore") 27 | @Override 28 | public void setEdibleAtMaxHunger(boolean value) 29 | { 30 | isEdibleAtMaxHunger = value; 31 | } 32 | 33 | @Optional.Method(modid = "applecore") 34 | @Override 35 | public FoodValues getFoodValues(@Nonnull ItemStack itemStack) 36 | { 37 | return new FoodValues(2, 0.1f); 38 | } 39 | 40 | // This needs to be abstracted into an Optional method, 41 | // otherwise the ItemFoodProxy reference will cause problems 42 | @Optional.Method(modid = "applecore") 43 | public void onEatenCompatibility(ItemStack itemStack, EntityPlayer player) 44 | { 45 | // one possible compatible method 46 | player.getFoodStats().addStats(new ItemFoodProxy(this), itemStack); 47 | 48 | // another possible compatible method: 49 | // new ItemFoodProxy(this).onEaten(itemStack, player); 50 | } 51 | 52 | @Override 53 | public boolean onBlockActivated(@Nullable World world, @Nullable BlockPos pos, @Nullable IBlockState state, EntityPlayer player, @Nullable EnumHand hand, EnumFacing side, float hitX, float hitY, float hitZ) 54 | { 55 | if (!world.isRemote) 56 | { 57 | return this.eat(world, pos, state, player); 58 | } 59 | else 60 | { 61 | ItemStack itemstack = player.getHeldItem(hand); 62 | return this.eat(world, pos, state, player) || itemstack.isEmpty(); 63 | } 64 | } 65 | 66 | private boolean eat(World world, BlockPos pos, IBlockState state, EntityPlayer player) 67 | { 68 | if (!player.canEat(isEdibleAtMaxHunger)) 69 | { 70 | return false; 71 | } 72 | else 73 | { 74 | if (Loader.isModLoaded("applecore")) 75 | { 76 | onEatenCompatibility(new ItemStack(this), player); 77 | } 78 | else 79 | { 80 | // this method is not compatible with AppleCore 81 | player.getFoodStats().addStats(2, 0.1f); 82 | } 83 | 84 | int bites = state.getValue(BITES); 85 | if (bites < 6) 86 | { 87 | world.setBlockState(pos, state.withProperty(BITES, bites + 1), 3); 88 | } 89 | else 90 | { 91 | world.setBlockToAir(pos); 92 | } 93 | 94 | return true; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /java/squeek/applecore/example/EatingSpeedModifier.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.example; 2 | 3 | import net.minecraft.entity.player.EntityPlayer; 4 | import net.minecraft.item.ItemFood; 5 | import net.minecraftforge.event.entity.living.LivingEntityUseItemEvent; 6 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; 7 | 8 | public class EatingSpeedModifier 9 | { 10 | // this is a default Forge event 11 | // normally, doing this will cause the eating animation to not work properly, 12 | // but AppleCore fixes that 13 | @SubscribeEvent 14 | public void onItemUse(LivingEntityUseItemEvent.Start event) 15 | { 16 | if (event.getEntity() instanceof EntityPlayer) 17 | { 18 | if (event.getItem().getItem() instanceof ItemFood) 19 | { 20 | event.setDuration(100); 21 | } 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /java/squeek/applecore/example/ExhaustionModifier.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.example; 2 | 3 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; 4 | import squeek.applecore.api.AppleCoreAPI; 5 | import squeek.applecore.api.hunger.ExhaustionEvent; 6 | 7 | public class ExhaustionModifier 8 | { 9 | @SubscribeEvent 10 | public void onExhaustionTick(ExhaustionEvent.GetMaxExhaustion event) 11 | { 12 | event.maxExhaustionLevel = 30f; 13 | } 14 | 15 | @SubscribeEvent 16 | public void onExhausted(ExhaustionEvent.Exhausted event) 17 | { 18 | // this will enable hunger loss in peaceful difficulty 19 | if (event.player.getFoodStats().getSaturationLevel() <= 0) 20 | event.deltaHunger = -1; 21 | 22 | AppleCoreExample.LOG.info("onExhausted exhaustion=" + AppleCoreAPI.accessor.getExhaustion(event.player)); 23 | } 24 | 25 | @SubscribeEvent 26 | public void onExhaustionAddition(ExhaustionEvent.ExhaustionAddition event) 27 | { 28 | // scale all exhaustion additions by 1.5x 29 | event.deltaExhaustion = event.deltaExhaustion * 1.5f; 30 | } 31 | 32 | @SubscribeEvent 33 | public void onExhaustingAction(ExhaustionEvent.ExhaustingAction event) 34 | { 35 | if (event.source == ExhaustionEvent.ExhaustingActions.NORMAL_JUMP) 36 | { 37 | // random exhaustion each jump 38 | event.deltaExhaustion *= Math.random(); 39 | } 40 | else if (event.source == ExhaustionEvent.ExhaustingActions.SPRINTING_JUMP) 41 | { 42 | // note: this is over the default cap of 40, but also over the modified cap 43 | event.deltaExhaustion = 100.0f; 44 | } 45 | } 46 | 47 | @SubscribeEvent 48 | public void getExhaustionCap(ExhaustionEvent.GetExhaustionCap event) 49 | { 50 | event.exhaustionLevelCap = 90.0f; 51 | } 52 | } -------------------------------------------------------------------------------- /java/squeek/applecore/example/FoodEatenResult.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.example; 2 | 3 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; 4 | import squeek.applecore.api.food.FoodEvent; 5 | 6 | public class FoodEatenResult 7 | { 8 | @SubscribeEvent 9 | public void onFoodEaten(FoodEvent.FoodEaten event) 10 | { 11 | AppleCoreExample.LOG.info(event.player.getDisplayNameString() + " ate " + event.food.toString()); 12 | 13 | if (event.hungerAdded >= 1) 14 | event.player.heal(1); 15 | } 16 | } -------------------------------------------------------------------------------- /java/squeek/applecore/example/FoodStatsAdditionCanceler.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.example; 2 | 3 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; 4 | import squeek.applecore.api.AppleCoreAPI; 5 | import squeek.applecore.api.food.FoodEvent; 6 | 7 | public class FoodStatsAdditionCanceler 8 | { 9 | @SubscribeEvent 10 | public void onFoodStatsAddition(FoodEvent.FoodStatsAddition event) 11 | { 12 | if (event.player.getFoodStats().getFoodLevel() > AppleCoreAPI.accessor.getMaxHunger(event.player) / 2) 13 | event.setCanceled(true); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /java/squeek/applecore/example/FoodValuesModifier.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.example; 2 | 3 | import net.minecraft.init.Items; 4 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; 5 | import squeek.applecore.api.food.FoodEvent; 6 | import squeek.applecore.api.food.FoodValues; 7 | 8 | public class FoodValuesModifier 9 | { 10 | @SubscribeEvent 11 | public void getFoodValues(FoodEvent.GetFoodValues event) 12 | { 13 | if (event.food.getItem() == Items.APPLE) 14 | event.foodValues = new FoodValues(5, 1f); 15 | } 16 | 17 | @SubscribeEvent 18 | public void getPlayerSpecificFoodValues(FoodEvent.GetPlayerFoodValues event) 19 | { 20 | // Player can be null when, for example, Minecraft caches tooltips at startup, 21 | // and a tooltip handler forwards that null player to AppleCore 22 | if (event.player == null) 23 | return; 24 | 25 | if (event.food.getItem() == Items.APPLE) 26 | event.foodValues = new FoodValues(19, 1f); 27 | else 28 | event.foodValues = new FoodValues((20 - event.player.getFoodStats().getFoodLevel()) / 8, 1); 29 | } 30 | } -------------------------------------------------------------------------------- /java/squeek/applecore/example/FoodValuesTooltipHandler.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.example; 2 | 3 | import net.minecraftforge.event.entity.player.ItemTooltipEvent; 4 | import net.minecraftforge.fml.common.eventhandler.EventPriority; 5 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; 6 | import squeek.applecore.api.AppleCoreAPI; 7 | import squeek.applecore.api.food.FoodValues; 8 | 9 | import java.text.DecimalFormat; 10 | 11 | public class FoodValuesTooltipHandler 12 | { 13 | public static final DecimalFormat DF = new DecimalFormat("##.##"); 14 | 15 | @SubscribeEvent(priority = EventPriority.LOWEST) 16 | public void onItemTooltip(ItemTooltipEvent event) 17 | { 18 | if (AppleCoreAPI.accessor.isFood(event.getItemStack())) 19 | { 20 | FoodValues unmodifiedValues = AppleCoreAPI.accessor.getUnmodifiedFoodValues(event.getItemStack()); 21 | FoodValues modifiedValues = AppleCoreAPI.accessor.getFoodValues(event.getItemStack()); 22 | FoodValues playerValues = AppleCoreAPI.accessor.getFoodValuesForPlayer(event.getItemStack(), event.getEntityPlayer()); 23 | 24 | event.getToolTip().add("Food Values [hunger : satMod (+sat)]"); 25 | event.getToolTip().add("- Player-specific: " + playerValues.hunger + " : " + playerValues.saturationModifier + " (+" + DF.format(playerValues.getSaturationIncrement(event.getEntityPlayer())) + ")"); 26 | event.getToolTip().add("- Player-agnostic: " + modifiedValues.hunger + " : " + modifiedValues.saturationModifier + " (+" + DF.format(modifiedValues.getSaturationIncrement(event.getEntityPlayer())) + ")"); 27 | event.getToolTip().add("- Unmodified: " + unmodifiedValues.hunger + " : " + unmodifiedValues.saturationModifier + " (+" + DF.format(unmodifiedValues.getSaturationIncrement(event.getEntityPlayer())) + ")"); 28 | 29 | if (event.getEntityPlayer() != null) 30 | { 31 | boolean isCurrentlyEdible = AppleCoreAPI.accessor.canPlayerEatFood(event.getItemStack(), event.getEntityPlayer()); 32 | event.getToolTip().add(isCurrentlyEdible ? "Can currently be eaten" : "Can not currently be eaten"); 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /java/squeek/applecore/example/HealthRegenModifier.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.example; 2 | 3 | import net.minecraftforge.fml.common.eventhandler.Event.Result; 4 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; 5 | import squeek.applecore.api.hunger.HealthRegenEvent; 6 | 7 | public class HealthRegenModifier 8 | { 9 | @SubscribeEvent 10 | public void allowHealthRegen(HealthRegenEvent.AllowRegen event) 11 | { 12 | event.setResult(Result.ALLOW); 13 | } 14 | 15 | @SubscribeEvent 16 | public void onRegenTick(HealthRegenEvent.GetRegenTickPeriod event) 17 | { 18 | event.regenTickPeriod = 6; 19 | } 20 | 21 | @SubscribeEvent 22 | public void onRegen(HealthRegenEvent.Regen event) 23 | { 24 | event.deltaHealth = 2; 25 | event.deltaExhaustion = 5f; 26 | } 27 | 28 | @SubscribeEvent 29 | public void onPeacefulRegen(HealthRegenEvent.PeacefulRegen event) 30 | { 31 | event.deltaHealth = 0f; 32 | } 33 | 34 | @SubscribeEvent 35 | public void allowSaturatedHealthRegen(HealthRegenEvent.AllowSaturatedRegen event) 36 | { 37 | event.setResult(Result.DEFAULT); 38 | } 39 | 40 | @SubscribeEvent 41 | public void onSaturatedRegenTick(HealthRegenEvent.GetSaturatedRegenTickPeriod event) 42 | { 43 | event.regenTickPeriod = 20; 44 | } 45 | 46 | @SubscribeEvent 47 | public void onSaturatedRegen(HealthRegenEvent.SaturatedRegen event) 48 | { 49 | event.deltaHealth = 1; 50 | event.deltaExhaustion = 1f; 51 | } 52 | } -------------------------------------------------------------------------------- /java/squeek/applecore/example/HungerRegenModifier.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.example; 2 | 3 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; 4 | import squeek.applecore.api.hunger.HungerRegenEvent; 5 | 6 | public class HungerRegenModifier 7 | { 8 | @SubscribeEvent 9 | public void onRegen(HungerRegenEvent.PeacefulRegen event) 10 | { 11 | if (event.player.getFoodStats().getFoodLevel() > 10) 12 | event.deltaHunger = 0; 13 | else 14 | event.deltaHunger = 1; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /java/squeek/applecore/example/ItemMetadataFood.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.example; 2 | 3 | import net.minecraft.creativetab.CreativeTabs; 4 | import net.minecraft.item.ItemFood; 5 | import net.minecraft.item.ItemStack; 6 | import net.minecraft.util.NonNullList; 7 | 8 | import javax.annotation.Nonnull; 9 | 10 | /** 11 | * An example implementation of a metadata-based food item 12 | * that is AppleCore compatible 13 | */ 14 | public class ItemMetadataFood extends ItemFood 15 | { 16 | public int[] hungerValues; 17 | public float[] saturationModifiers; 18 | 19 | public ItemMetadataFood(int[] hungerValues, float[] saturationModifiers) 20 | { 21 | super(0, 0f, false); 22 | this.hungerValues = hungerValues; 23 | this.saturationModifiers = saturationModifiers; 24 | 25 | setHasSubtypes(true); 26 | } 27 | 28 | /** 29 | * @return The hunger value of the ItemStack 30 | */ 31 | @Override 32 | public int getHealAmount(@Nonnull ItemStack stack) 33 | { 34 | return hungerValues[stack.getItemDamage()]; 35 | } 36 | 37 | /** 38 | * @return The saturation modifier of the ItemStack 39 | */ 40 | @Override 41 | public float getSaturationModifier(@Nonnull ItemStack stack) 42 | { 43 | return saturationModifiers[stack.getItemDamage()]; 44 | } 45 | 46 | @Override 47 | public void getSubItems(@Nonnull CreativeTabs tab, @Nonnull NonNullList subItems) 48 | { 49 | if (this.isInCreativeTab(tab)) 50 | { 51 | for (int meta = 0; meta < Math.min(hungerValues.length, saturationModifiers.length); meta++) 52 | { 53 | subItems.add(new ItemStack(this, 1, meta)); 54 | } 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /java/squeek/applecore/example/ItemNonStandardFood.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.example; 2 | 3 | import net.minecraft.creativetab.CreativeTabs; 4 | import net.minecraft.entity.EntityLivingBase; 5 | import net.minecraft.entity.player.EntityPlayer; 6 | import net.minecraft.init.SoundEvents; 7 | import net.minecraft.item.EnumAction; 8 | import net.minecraft.item.Item; 9 | import net.minecraft.item.ItemStack; 10 | import net.minecraft.stats.StatList; 11 | import net.minecraft.util.ActionResult; 12 | import net.minecraft.util.EnumActionResult; 13 | import net.minecraft.util.EnumHand; 14 | import net.minecraft.util.SoundCategory; 15 | import net.minecraft.world.World; 16 | import net.minecraftforge.fml.common.Loader; 17 | import net.minecraftforge.fml.common.Optional; 18 | import squeek.applecore.api.food.FoodValues; 19 | import squeek.applecore.api.food.IEdible; 20 | import squeek.applecore.api.food.ItemFoodProxy; 21 | 22 | import javax.annotation.Nonnull; 23 | 24 | @Optional.Interface(iface = "squeek.applecore.api.food.IEdible", modid = "applecore") 25 | public class ItemNonStandardFood extends Item implements IEdible 26 | { 27 | public ItemNonStandardFood() 28 | { 29 | this.setCreativeTab(CreativeTabs.FOOD); 30 | } 31 | 32 | @Optional.Method(modid = "applecore") 33 | @Override 34 | public FoodValues getFoodValues(@Nonnull ItemStack itemStack) 35 | { 36 | return new FoodValues(1, 1f); 37 | } 38 | 39 | // This needs to be abstracted into an Optional method, 40 | // otherwise the ItemFoodProxy reference will cause problems 41 | @Optional.Method(modid = "applecore") 42 | public void onEatenCompatibility(@Nonnull ItemStack itemStack, EntityPlayer player) 43 | { 44 | // one possible compatible method 45 | player.getFoodStats().addStats(new ItemFoodProxy(this), itemStack); 46 | 47 | // another possible compatible method: 48 | // new ItemFoodProxy(this).onEaten(itemStack, player); 49 | } 50 | 51 | @Override 52 | @Nonnull 53 | public ItemStack onItemUseFinish(@Nonnull ItemStack stack, World world, EntityLivingBase entityLiving) { 54 | stack.shrink(1); 55 | 56 | if (entityLiving instanceof EntityPlayer) 57 | { 58 | EntityPlayer player = (EntityPlayer)entityLiving; 59 | 60 | if (Loader.isModLoaded("applecore")) 61 | { 62 | onEatenCompatibility(stack, player); 63 | } 64 | else 65 | { 66 | // this method is not compatible with AppleCore 67 | player.getFoodStats().addStats(1, 1F); 68 | } 69 | 70 | player.addStat(StatList.getObjectUseStats(this)); 71 | world.playSound(null, player.posX, player.posY, player.posZ, SoundEvents.ENTITY_PLAYER_BURP, SoundCategory.PLAYERS, 0.5F, world.rand.nextFloat() * 0.1F + 0.9F); 72 | } 73 | 74 | return stack; 75 | } 76 | 77 | @Override 78 | @Nonnull 79 | public EnumAction getItemUseAction(@Nonnull ItemStack itemStack) 80 | { 81 | return EnumAction.EAT; 82 | } 83 | 84 | @Override 85 | public int getMaxItemUseDuration(@Nonnull ItemStack itemStack) 86 | { 87 | return 32; 88 | } 89 | 90 | @Override 91 | @Nonnull 92 | public ActionResult onItemRightClick(World world, EntityPlayer player, @Nonnull EnumHand hand) 93 | { 94 | ItemStack stack = player.getHeldItem(hand); 95 | if (player.canEat(true)) 96 | { 97 | player.setActiveHand(hand); 98 | return new ActionResult<>(EnumActionResult.SUCCESS, stack); 99 | } 100 | return new ActionResult<>(EnumActionResult.FAIL, stack); 101 | } 102 | } -------------------------------------------------------------------------------- /java/squeek/applecore/example/MaxHungerModifier.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.example; 2 | 3 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; 4 | import squeek.applecore.api.hunger.HungerEvent; 5 | 6 | public class MaxHungerModifier 7 | { 8 | @SubscribeEvent 9 | public void onGetMaxHunger(HungerEvent.GetMaxHunger event) 10 | { 11 | event.maxHunger = 60; 12 | } 13 | } -------------------------------------------------------------------------------- /java/squeek/applecore/example/StarvationModifier.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.example; 2 | 3 | import net.minecraftforge.fml.common.eventhandler.Event; 4 | import net.minecraftforge.fml.common.eventhandler.EventPriority; 5 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; 6 | import squeek.applecore.api.hunger.StarvationEvent; 7 | 8 | public class StarvationModifier 9 | { 10 | @SubscribeEvent 11 | public void allowStarvation(StarvationEvent.AllowStarvation event) 12 | { 13 | event.setResult(Event.Result.ALLOW); 14 | } 15 | 16 | @SubscribeEvent 17 | public void onStarveTick(StarvationEvent.GetStarveTickPeriod event) 18 | { 19 | event.starveTickPeriod = 60; 20 | } 21 | 22 | @SubscribeEvent(priority= EventPriority.LOWEST) 23 | public void onStarve(StarvationEvent.Starve event) 24 | { 25 | event.starveDamage = 1; 26 | } 27 | } -------------------------------------------------------------------------------- /java/squeek/applecore/network/MessageDifficultySync.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.network; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import net.minecraft.client.Minecraft; 5 | import net.minecraft.world.EnumDifficulty; 6 | import net.minecraftforge.fml.common.network.simpleimpl.IMessage; 7 | import net.minecraftforge.fml.common.network.simpleimpl.IMessageHandler; 8 | import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; 9 | 10 | public class MessageDifficultySync implements IMessage, IMessageHandler 11 | { 12 | EnumDifficulty difficulty; 13 | 14 | public MessageDifficultySync() 15 | { 16 | } 17 | 18 | public MessageDifficultySync(EnumDifficulty difficulty) 19 | { 20 | this.difficulty = difficulty; 21 | } 22 | 23 | @Override 24 | public void toBytes(ByteBuf buf) 25 | { 26 | buf.writeByte(difficulty.getDifficultyId()); 27 | } 28 | 29 | @Override 30 | public void fromBytes(ByteBuf buf) 31 | { 32 | difficulty = EnumDifficulty.getDifficultyEnum(buf.readByte()); 33 | } 34 | 35 | @Override 36 | public IMessage onMessage(final MessageDifficultySync message, final MessageContext ctx) 37 | { 38 | // defer to the next game loop; we can't guarantee that Minecraft.thePlayer is initialized yet 39 | Minecraft.getMinecraft().addScheduledTask(() -> NetworkHelper.getSidedPlayer(ctx).world.getWorldInfo().setDifficulty(message.difficulty)); 40 | return null; 41 | } 42 | } -------------------------------------------------------------------------------- /java/squeek/applecore/network/NetworkHelper.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.network; 2 | 3 | import net.minecraft.entity.player.EntityPlayer; 4 | import net.minecraftforge.fml.client.FMLClientHandler; 5 | import net.minecraftforge.fml.common.network.simpleimpl.MessageContext; 6 | import net.minecraftforge.fml.relauncher.Side; 7 | 8 | public class NetworkHelper 9 | { 10 | public static EntityPlayer getSidedPlayer(MessageContext ctx) 11 | { 12 | return ctx.side == Side.SERVER ? ctx.getServerHandler().player : FMLClientHandler.instance().getClientPlayerEntity(); 13 | } 14 | } -------------------------------------------------------------------------------- /java/squeek/applecore/network/SyncHandler.java: -------------------------------------------------------------------------------- 1 | package squeek.applecore.network; 2 | 3 | import net.minecraft.entity.player.EntityPlayerMP; 4 | import net.minecraft.world.EnumDifficulty; 5 | import net.minecraft.world.WorldServer; 6 | import net.minecraftforge.common.MinecraftForge; 7 | import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; 8 | import net.minecraftforge.fml.common.gameevent.PlayerEvent; 9 | import net.minecraftforge.fml.common.gameevent.TickEvent; 10 | import net.minecraftforge.fml.common.gameevent.TickEvent.WorldTickEvent; 11 | import net.minecraftforge.fml.common.network.NetworkRegistry; 12 | import net.minecraftforge.fml.common.network.simpleimpl.SimpleNetworkWrapper; 13 | import net.minecraftforge.fml.relauncher.Side; 14 | import squeek.applecore.ModInfo; 15 | 16 | public class SyncHandler 17 | { 18 | public static final SimpleNetworkWrapper CHANNEL = NetworkRegistry.INSTANCE.newSimpleChannel(ModInfo.MODID); 19 | 20 | public static void init() 21 | { 22 | CHANNEL.registerMessage(MessageDifficultySync.class, MessageDifficultySync.class, 0, Side.CLIENT); 23 | 24 | MinecraftForge.EVENT_BUS.register(new SyncHandler()); 25 | } 26 | 27 | /* 28 | * Sync difficulty (vanilla MC does not sync it on servers) 29 | */ 30 | private EnumDifficulty lastDifficultySetting = null; 31 | 32 | @SubscribeEvent 33 | public void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) 34 | { 35 | if (!(event.player instanceof EntityPlayerMP)) 36 | return; 37 | 38 | CHANNEL.sendTo(new MessageDifficultySync(event.player.world.getDifficulty()), (EntityPlayerMP) event.player); 39 | } 40 | 41 | @SubscribeEvent 42 | public void onWorldTick(WorldTickEvent event) 43 | { 44 | if (event.phase != TickEvent.Phase.END) 45 | return; 46 | 47 | if (event.world instanceof WorldServer) 48 | { 49 | if (this.lastDifficultySetting != event.world.getDifficulty()) 50 | { 51 | CHANNEL.sendToAll(new MessageDifficultySync(event.world.getDifficulty())); 52 | this.lastDifficultySetting = event.world.getDifficulty(); 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /resources/applecore.info: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "modid": "applecore", 4 | "name": "AppleCore", 5 | "description": "An API for modifying the food and hunger mechanics of Minecraft", 6 | "version": "${version}", 7 | "mcversion": "${mcversion}", 8 | "url": "http://www.minecraftforum.net/forums/mapping-and-modding/minecraft-mods/2222837-applecore-an-api-for-modifying-the-food-and-hunger", 9 | "updateUrl": "", 10 | "authorList": ["squeek"], 11 | "credits": "", 12 | "logoFile": "", 13 | "screenshots": [], 14 | "dependencies": [] 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /resources/assets/applecore/lang/de_DE.lang: -------------------------------------------------------------------------------- 1 | applecore.commands.hunger.usage=/hunger [player] 2 | applecore.commands.hunger.set.hunger.to=Setze Hunger von %s auf %d -------------------------------------------------------------------------------- /resources/assets/applecore/lang/en_US.lang: -------------------------------------------------------------------------------- 1 | applecore.commands.hunger.usage=/hunger [player] 2 | applecore.commands.hunger.set.hunger.to=Set %s's hunger to %d -------------------------------------------------------------------------------- /resources/assets/applecore/lang/fr_FR.lang: -------------------------------------------------------------------------------- 1 | applecore.commands.hunger.usage=/hunger [joueur] 2 | applecore.commands.hunger.set.hunger.to=La faim de %s a été défini à %d 3 | -------------------------------------------------------------------------------- /resources/assets/applecore/lang/pl_PL.lang: -------------------------------------------------------------------------------- 1 | applecore.commands.hunger.usage=/hunger [gracz] 2 | applecore.commands.hunger.set.hunger.to=Ustawiono głód gracza %s na %d 3 | -------------------------------------------------------------------------------- /resources/assets/applecore/lang/ru_RU.lang: -------------------------------------------------------------------------------- 1 | applecore.commands.hunger.usage=/hunger [игрок] <значение> 2 | applecore.commands.hunger.set.hunger.to=Голод игрока %s изменён на %d 3 | -------------------------------------------------------------------------------- /resources/assets/applecore/lang/zh_CN.lang: -------------------------------------------------------------------------------- 1 | #Traslated by gxy17886 From InfinityStudio 2 | applecore.commands.hunger.usage=/hunger [玩家ID] <数值> 3 | applecore.commands.hunger.set.hunger.to=设置%s的饥饿值为%d 4 | -------------------------------------------------------------------------------- /resources/assets/applecore/lang/zh_TW.lang: -------------------------------------------------------------------------------- 1 | #Translate by hugoalh 2 | applecore.commands.hunger.usage=/hunger [玩家ID] <數值> 3 | applecore.commands.hunger.set.hunger.to=設置%s的飢餓值為%d 4 | -------------------------------------------------------------------------------- /resources/mcmod.info: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "modid": "applecoreexample", 4 | "name": "AppleCore Example", 5 | "description": "An example mod that uses the AppleCore API to make various test modifications", 6 | "version": "${version}", 7 | "mcversion": "${mcversion}", 8 | "url": "", 9 | "updateUrl": "", 10 | "authorList": ["squeek"], 11 | "credits": "", 12 | "logoFile": "", 13 | "screenshots": [], 14 | "dependencies": [], 15 | "parent": "applecore" 16 | } 17 | ] -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ":ASMHelper" --------------------------------------------------------------------------------