From c34065d55ce9b787a3109d52b6cd3a3f2edb9ad5 Mon Sep 17 00:00:00 2001 From: CG Date: Sat, 7 Oct 2023 12:12:34 -0400 Subject: [PATCH] 20w14infinite port. --- .github/FUNDING.yml | 1 - .gitignore | 2 +- README.md | 77 +-- build.gradle | 121 ++-- gradle.properties | 20 +- gradle/wrapper/gradle-wrapper.jar | Bin 59203 -> 55616 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 35 +- gradlew.bat | 189 +++--- java/squeek/appleskin/AppleSkin.java | 42 +- java/squeek/appleskin/ModConfig.java | 129 ---- java/squeek/appleskin/ModInfo.java | 10 - .../appleskin/api/event/FoodValuesEvent.java | 26 - .../appleskin/api/event/HUDOverlayEvent.java | 92 --- .../api/event/TooltipOverlayEvent.java | 59 -- .../squeek/appleskin/api/food/FoodValues.java | 37 -- .../appleskin/client/DebugInfoHandler.java | 45 -- .../appleskin/client/HUDOverlayHandler.java | 574 +++--------------- .../client/TooltipOverlayHandler.java | 407 +++---------- .../squeek/appleskin/helpers/DynamicFood.java | 24 + java/squeek/appleskin/helpers/FoodHelper.java | 187 ++---- .../appleskin/helpers/HungerHelper.java | 39 +- java/squeek/appleskin/helpers/KeyHelper.java | 25 - java/squeek/appleskin/mixin/GuiMixin.java | 36 ++ .../appleskin/mixin/HungerManagerMixin.java | 25 + .../appleskin/mixin/InGameHudMixin.java | 25 + .../appleskin/mixin/MinecraftClientMixin.java | 18 + .../appleskin/mixin/PlayerManagerMixin.java | 20 + .../mixin/ServerPlayerEntityMixin.java | 27 + .../appleskin/mixin/util/BeforeInc.java | 76 +++ .../network/MessageExhaustionSync.java | 35 -- .../network/MessageSaturationSync.java | 34 -- .../appleskin/network/NetworkHelper.java | 14 - .../squeek/appleskin/network/SyncHandler.java | 90 +-- java/squeek/appleskin/util/IntPoint.java | 7 - resources/META-INF/mods.toml | 21 - resources/appleskin.mixins.json | 22 + resources/assets/appleskin/appleskin.png | Bin 0 -> 420 bytes resources/fabric.mod.json | 34 ++ resources/pack.mcmeta | 4 +- settings.gradle | 10 + 41 files changed, 757 insertions(+), 1884 deletions(-) delete mode 100644 .github/FUNDING.yml delete mode 100644 java/squeek/appleskin/ModConfig.java delete mode 100644 java/squeek/appleskin/ModInfo.java delete mode 100644 java/squeek/appleskin/api/event/FoodValuesEvent.java delete mode 100644 java/squeek/appleskin/api/event/HUDOverlayEvent.java delete mode 100644 java/squeek/appleskin/api/event/TooltipOverlayEvent.java delete mode 100644 java/squeek/appleskin/api/food/FoodValues.java delete mode 100644 java/squeek/appleskin/client/DebugInfoHandler.java create mode 100644 java/squeek/appleskin/helpers/DynamicFood.java delete mode 100644 java/squeek/appleskin/helpers/KeyHelper.java create mode 100644 java/squeek/appleskin/mixin/GuiMixin.java create mode 100644 java/squeek/appleskin/mixin/HungerManagerMixin.java create mode 100644 java/squeek/appleskin/mixin/InGameHudMixin.java create mode 100644 java/squeek/appleskin/mixin/MinecraftClientMixin.java create mode 100644 java/squeek/appleskin/mixin/PlayerManagerMixin.java create mode 100644 java/squeek/appleskin/mixin/ServerPlayerEntityMixin.java create mode 100644 java/squeek/appleskin/mixin/util/BeforeInc.java delete mode 100644 java/squeek/appleskin/network/MessageExhaustionSync.java delete mode 100644 java/squeek/appleskin/network/MessageSaturationSync.java delete mode 100644 java/squeek/appleskin/network/NetworkHelper.java delete mode 100644 java/squeek/appleskin/util/IntPoint.java delete mode 100644 resources/META-INF/mods.toml create mode 100644 resources/appleskin.mixins.json create mode 100644 resources/assets/appleskin/appleskin.png create mode 100644 resources/fabric.mod.json create mode 100644 settings.gradle diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 2d90a77..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -github: [squeek502] diff --git a/.gitignore b/.gitignore index f8db4ce..d0afed3 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ classes *.bat builds sources -logs # Created by https://www.gitignore.io/api/gradle,intellij,eclipse,windows,osx,linux @@ -178,3 +177,4 @@ Temporary Items # Linux trash folder which might appear on any partition or disk .Trash-* + diff --git a/README.md b/README.md index 1504c96..f1da6cf 100644 --- a/README.md +++ b/README.md @@ -3,23 +3,21 @@ Minecraft mod that adds various food-related HUD improvements formerly provided by [AppleCore](https://github.com/squeek502/AppleCore) (basically, AppleCore without the core). +This fork adds compatibility to Minecraft 20w14infinite. + ### Features * Adds food value information to tooltips: -![](https://i.imgur.com/YksBaUx.png) +![](https://i.imgur.com/furoAAi.png) * Adds a visualization of saturation and exhaustion to the HUD: -![](https://i.imgur.com/tmImVqo.gif) +![](https://zippy.gfycat.com/ShimmeringYearlyCicada.gif) * Adds a visualization of potential hunger/saturation restored while holding food: -![](https://i.imgur.com/aHf1QxQ.gif) - -* Adds a visualization of potential health restored while holding food: - -![](https://i.imgur.com/jUOKFUl.gif) +![](https://zippy.gfycat.com/PowerfulDeafeningHarvestmen.gif) * Adds hunger/saturation/exhaustion info to the debug overlay (F3) * Syncs the value of saturation and exhaustion to the client. @@ -31,68 +29,3 @@ Minecraft mod that adds various food-related HUD improvements formerly provided 2. Open a command line and execute ```gradlew build``` Note: To give the build a version number, use ```gradlew build -Pversion=``` instead (example: ```gradlew build -Pversion=1.0.0```). - ---- - -### For Mod Developers - -> Note: These instructions are Forge-specific. For Fabric, see the instructions in the relevant `-fabric` branch. - -If followed, the directions below will make it so that your mod's Maven dependencies won't include AppleSkin at all, and your mod will load fine with or without AppleSkin installed. - -To compile against the AppleSkin API, include the following in your `build.gradle`: - -```groovy -repositories { - maven { url "https://maven.ryanliptak.com/" } -} -``` - -and add this to your `dependencies` block: - -```groovy -compileOnly fg.deobf("squeek.appleskin:appleskin-forge::api") -``` - -where `` is replaced by the appropriate version found here: - -https://maven.ryanliptak.com/squeek/appleskin/appleskin-forge - -Once you're compiling against the AppleSkin API, you can create an event handler and only register it when `appleskin` is loaded. Here's an example implementation: - -In your `@Mod` annotated class: - -```java -private void clientInit(final FMLClientSetupEvent event) { - if (ModList.get().isLoaded("appleskin")) { - MinecraftForge.EVENT_BUS.register(new AppleSkinEventHandler()); - } -} -``` - -and the `AppleSkinEventHandler` class: - -```java -public class AppleSkinEventHandler -{ - @SubscribeEvent - public void onPreTooltipEvent(TooltipOverlayEvent.Pre event) { - // hide the tooltip for regular apples - if (event.itemStack.getItem() == Items.APPLE) { - event.setCanceled(true); - } - } -} -``` - -(see the `squeek.appleskin.api.event` package for all the possible events that can be registered) - ---- - -Note: if you want to test with the full AppleSkin mod in your development environment, you can also add the following to your `dependencies`: - -```groovy -runtimeOnly fg.deobf("squeek.appleskin:appleskin-forge:") -``` - -while replacing `` as mentioned above. \ No newline at end of file diff --git a/build.gradle b/build.gradle index f72572d..f9ecb62 100644 --- a/build.gradle +++ b/build.gradle @@ -1,112 +1,69 @@ -buildscript { - repositories { - maven { url = 'https://files.minecraftforge.net/maven' } - mavenCentral() - } - dependencies { - classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: true - } +plugins { + id 'fabric-loom' version '0.2.6-SNAPSHOT' + id 'maven-publish' } -apply plugin: 'net.minecraftforge.gradle' -//Only edit below this line, the above code adds and enables the necessary things for Forge to be setup. -apply plugin: 'eclipse' -apply plugin: 'maven-publish' -sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 -minecraft { - mappings channel: mappings_channel, version: mappings_version - - // default run configurations. - // these can be tweaked, removed, or duplicated as needed. - runs { - client { - workingDirectory project.file('run') - mods { - appleskin { - source sourceSets.main - } - } - } +archivesBaseName = project.archives_base_name +group = project.maven_group - server { - workingDirectory project.file('run') - mods { - appleskin { - source sourceSets.main - } - } - } - } +minecraft { } -group = project.maven_group -archivesBaseName = project.archives_base_name -version = "mc" + project.minecraft_version + "-" + project.mod_version -def semver_version = project.mod_version + "+mc" + project.minecraft_version - sourceSets.main.java.srcDirs += 'java' -sourceSets.main.java.srcDirs += 'apis' sourceSets.main.resources.srcDirs += 'resources' dependencies { - minecraft 'net.minecraftforge:forge:'+minecraft_version+'-'+forge_version + minecraft "com.mojang:minecraft:${project.minecraft_version}" + mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" + modCompile "net.fabricmc:fabric-loader:${project.loader_version}" + + modCompile "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" } -jar { - manifest { - attributes([ - "Specification-Title": "appleskin", - "Specification-Vendor": "squeek", - "Specification-Version": "1", - "Implementation-Title": project.name, - "Implementation-Version": semver_version, - "Implementation-Vendor" :"squeek", - "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") - ]) +processResources { + inputs.property "vars", project.version + from(sourceSets.main.resources.srcDirs) { + include '**/fabric.mod.json' + expand 'version':project.version + } + from(sourceSets.main.resources.srcDirs) { + exclude '**/fabric.mod.json' } - - finalizedBy 'reobfJar' } -task sourcesJar(type: Jar) { - archiveClassifier.set("sources") - from sourceSets.main.allJava +tasks.withType(JavaCompile) { + options.encoding = "UTF-8" } -task apiJar(type: Jar) { - archiveClassifier.set("api") - from sourceSets.main.output - include "squeek/appleskin/api/**" - - finalizedBy 'reobfJar' +task sourcesJar(type: Jar, dependsOn: classes) { + classifier = "sources" + from sourceSets.main.allSource } -artifacts { - archives sourcesJar - archives apiJar +jar { + from "LICENSE" } +// configure the maven publication publishing { publications { mavenJava(MavenPublication) { - groupId = group - artifactId = archivesBaseName - // add all the jars that should be included when publishing to maven - artifact(jar) - artifact(sourcesJar) - artifact(apiJar) + artifact(jar.archivePath) { + builtBy remapJar + } + artifact(sourcesJar) { + builtBy remapSourcesJar + } } } + // select the repositories you want to publish to repositories { - maven { - url = project.findProperty("maven.url") ?: System.getenv("MAVEN_URL") - credentials { - username=project.findProperty("maven.user") ?: System.getenv("MAVEN_USER") - password=project.findProperty("maven.password") ?: System.getenv("MAVEN_PASSWORD") - } - } + // uncomment to publish to the local maven + // mavenLocal() } -} +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 28e215f..71092d7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,14 +1,14 @@ -minecraft_version=1.16.4 -forge_version=35.1.4 -mappings_version=20201028-1.16.3 -mappings_channel=snapshot +# Done to increase the memory available to gradle. +org.gradle.jvmargs=-Xmx1G -# Sets default memory used for gradle commands. Can be overridden by user or command line properties. -# This is required to provide enough memory for the Minecraft decompilation process. -org.gradle.jvmargs=-Xmx3G -org.gradle.daemon=false +# Fabric Properties +# check these on https://fabricmc.net/use +minecraft_version=20w14infinite +yarn_mappings=20w14infinite+build.4 +loader_version=0.7.8+build.187 +fabric_version=0.5.7+build.2-20w14infinite # Mod Properties maven_group = squeek.appleskin -archives_base_name = appleskin-forge -mod_version = 2.5.1 \ No newline at end of file +archives_base_name = appleskin +version = 1.0.8 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c023ec8b20f512888fe07c5bd3ff77bb8f..5c2d1cf016b3885f6930543d57b744ea8c220a1a 100644 GIT binary patch delta 19998 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 delta 23334 zcmZ6yQ*_^7)b$%Swr#tyZQHhuU-WHk+qUgAc4J!&nxrusy#I5a=UlvJjD59l*Pe6C zy*_IVG(!&0LN+phBc)L-m3M)If#E@dfw80{QedYjfnx%cY|Q2krta=>YST_jBA9|p zot|vvp%0RvR1srYTl+z-NNCL@5oSg;&!BaMOR}sfJn192cT55<(x!dL7ut~~3^-Ur z4>ora_t}-M=h->qJpjxnx)1EWvn8?z{O>`3f+7iuKL<2+zHP~ldyrmD0P{Z4X%%`W zo_)z~Yy==^IcLFQUXFGeH8WebVkw~L>r{vkbd$z5MQq(ni#a^*>hw=_Z;C^Gfrdev z!mgg_pG zeMQUU+?X~Em$z2qQyLw%`*oeVS_0m|fcm)7q6xUbNU;Eku2#8)2t3}hj!-y+-89iQ z3fZ2srkJN7rV0vd0?Or&O+;oeJrGw6+{`LpB@d3*VpO>Un|q3BNDJspjozc(4hJDz zwgOl$df!`k*;k(~&;GPfVBAD3Hi3C}ZFV~#*$f>4hj%YsCq6tRQfp_Dt-)S_Uj!o= ze~fwe`&6h3{1?2yCfi zXybknxod^Z|~hQkrhOl74q z$G@Js5lv&IFx8Sm%&;&R^ZS012w;u(#-d_d7z}E<_L7JxsnmzL7!JXpt9>W$Br_-E zrt)8pGV-SsMKD!epNc6VMP@dY9SZ~}4KEJ0{AM}D(Ur&6>Xwy(7hK_??ybcBfV^H zx_aQ9cAG-(o3ZK6^5ob$c;XQ+WUNPojJo*4bQPb@#nF;E%h&FNJuVpSRK{}ljl}!b z#w$tS(t%=z)Q_2_4&C(JNz3Z&rgJG<@$5eR{6=#eNx!WXg2rrliM1=mC{vw4N32Vt z(hz+({@Wh2Y$x_R-d{$2XdqlCZW<@Yvix3|nho{g3fcY`x3r&v zC3T%<=pJrdP1&am@lIKma2=I=^4+>BZP8iAC+!5rKrxkP-K0t^lPkRKzej86htd0P z#d#*bI0LJ?=)BWl*(f{h=~UK26R;3?r6Z!LAuS$vtfd9{cVHb61Hh{>!#phiJ%Th9 zF?=-pJ;B(60kgq8M!6s_=E5q^V1BZqUk45QP(0*!5vKTDdWw8Z2W(yF7Cd4q6#8Au zDKAwS7y&OlW39}KP7u;mRY_qmKm6ZlbFdopRZRb2WvuPtfGOrS@2QJ&4I=v~NILZ5 zeRhAPI(ofewJkMGXux=19@_Z8{!gjzB73;zNpU}X|DXwxK^;Cvj0Ph3u|D+PK~V7Z z?T_+HtO$qw$Y7Eiis5+%de#S_2Eg{NT?gs+rEQ*+9;JM`;i65mGIf65%GmAWA1&vF zlc?PlDec;zALdLmib;DC&8{{TV>uUmnkgCuNg83d=~K)66oA^Xl2_g3joQ7h45dDe zhrM9pl;y7z>d~B9=jQH;Q=2Fr{5!6n4(@U2+i4B!LnEVpkskhl8Y&h?h2<}2MvUa(Z=c-L0$s#VLm_n6MN={uuQNF?aO%NJt-w^*Q^v38n zSik;)49a!p_y;?PBm+2+r&6d%&w5wFcSS3i(Q0})76N`VU$9#xpY*=PpEvRJL*_v? zq`fJn6uibh+U?Oh=7TngAZ+QgfVq{*FP4XT@%T4DJXQ3^Q%|A#S*bgV=uQOkLs3B> zPb@_|qGW^GJGUz;Rdk=&!X5<@+IA_92osMhzl2w&pZpOkH2wg6{QNKJ_SprLV)J7~ zswn~v{%5cFd4Dchvot~B4Q=>*(PzriPyl!KvQ;DQT4Jwc7b z@=RK6_wy*9Ls}eOd#i_ifu-1gyG1I4B$wrf0s~uz`Oi=PUk3$X;9w*ytxP=~JW?)j ziGecB9d!at%>E`;fCYBIE`?LXQ%q2#KyT1)F3gKTVQ(^OFF_%e>U9C|Jftsp-L z-uBgv--?x$jQ!7JVOO%A6s_NIULK3t`AUvLNRGy1+2c=*hNLTgEU{(f`aS3R&0c#8 zJ)H~+lk7p>Antxg8%KDw8HA(zRyL7IsRXPZq(&|IG=anACS|u!&ze?(596{Wa^56I z(Hh0)W(B=vPMB&$-+voJG+fh`2n6^ zE<#-hLF2)fS!S>(AgaU7)DA<}B0gb;cUhr}#B$zitS3?I zQ2dfsjc&|!;>ZmeP`tUDacf0iky2%{sdnvR10i;nHt{`{s%AE_Ck=O!`CgKV{TxZt zvGG&6h(`32V2E)jIe5jAb7h61MnLCplX!amDU*7b478F^m0qqf96LN3N^S2xtX@WV zqjdFPUpJ(hHl4?SW`Rxi^WJaHe&^dS6OY9@unu!n*p3<-W-CQ>pb^E?XzN3;LFQ%}E-2`SgWHo)7f-p+JMy`RG3E&3PwN54o9wVP*Nq{9PKSNP@R_eO zKB~SbZXrKS%qqUV1h!p7JvFb&fbotnqw2Q5-wA7wlEq4H?+^~Js$F8pms&<$wDQtJ zl0cD0WH*i-3Lza6dDXZ-#eh8JlXkv(BGQT%ufa%jHyi2P_PS;2Q-5b!JPW(HoNzYg z2(g^gwcm)p-Q2=kK{=bNP4d6yB|A(BM{w}7e~-*Rt}#Z0uO{Xa=nY%!B|uW5EG{vg zbLt&cVKr)8e;2Fjx3r;i#5>@hs!6e6@JKF5xyGp+&#)QM4t?M}2m%79NOpKi>$f_G zEbVBL#9J#iY7hDnU;}~%>)&#&&6NL$+Y}5cc(#RW7pC-r5LDH|vnfahGt*C$(Ng4D z@UDxQAtvS2YmtXYUy%%-_Rv?oQ+J+2A0XduD3tbTMwumZ;T%JDNb|+ing}FNbj9t~ zYGxl7j3TfT+7h#O8vy*@Fq~5xnOT1>jYI=xJWjqnga#r=N9ytv{fvN2b{8`alWjGR zxGp9OJ=YMcpx>2RD*S{iX1{ua$G_fF-G`KzuP(cV`XlqHAo&r7f6owqz}@^MOA{#l z4KRTMsx;y;x}?Yp$|XFTGd=EXS28c9e09?>)%mkh%af}^xQtw8f2@dr7LZh@?Sq?> zcW-rMFZvfi!!af2oBTEFEzu_^TzVv`3!l41E93Syt^yVFVj~8=LJ2f0!YqbD6YAk7 zKmYI0w$QC~$@pI|ANU3a#__+FLk|4sGU%$9UxpGmYm!ka>h~0!kQyrg7CF?}ro^aJ zmM$&Bh_;6e_0pGtO6v>oyxjAmau&Zc6ua{CZ7e(q>9`2LS;159*^j)IQzPWhz;`GU zSQbg2d79#U7UBnOiXWtF-y{&tWCj$`AfDkme-Ah^Uq^Pvn8HXAc8;&8f&=E{f6Wa- z5m0=p;lR})#1J*jtIM;G5V4H*&_e`EX|Te(Bdh7$yW%)UbrRPWEnKA^LUWChkgd#q}YO& z-pbQge_K3HLX{vY(v8Ndy#VD-l=A-7^=uxXfF$iZecnnss~ZngOBXAjT?%fNp=jA@ zJ$hVjBu#m=2~kpYLW_odtK3bm|tv16fZEfF7}7vKNtrxO>y&HXNY zk@aEbvcNc!%FRn9e-n0v=&ZM~tIvl%zUWONu6EzU5^P=>J9d(xjqA&t-4RL^kT$9l zs!&!tAx2x}F{d&--V5*q=Tp4jlGPnDEu6(X`YCrSOJRNsR_>@G$&QqRv*Wj?Cm3z1 z+B)G{0Tpehdc0unLyH^!<{~%!Q{=gk$$^+9v)6?MC%xlIu!lE;cR}zfui*qpu zU^U+QL4`B4A|#i(N|ymR?a!s_^Ah%HmhZ7vH#H{U^TAxnUVzYX*gi{ZONznMsp>8G zlXqmIR+hA;1|j(3Gmj_!Y9i{2*2{s$HMiU;=fA^~lna|G zxh0n{QMbc&j`l3G^&pebs;Ioym)!V;h)pUY*1FX27P^te?Y!%E9}ie*`yK((+Qt;c zOz*W3T1(fUGu(h0!oCiP`+vo+kYS(m;!bZAY%lHmZ{}&ABjSMEp6dA==9@c;=AyCB z8OwPO@f*ZPn$4$P<42s$=c;(mxgY#To)~al#PN04wIJIxvGI~PN*cW*v1o!=EzemPx0zMa zZ;bBC-;*cnZ5Fu(CV*q;^X=o^R6(neD;u2-MbsJ?Kjh~J;wxUx7rv7sMa6 zyXZ?tB}`;n(PPqEne_ZKK8veIPl?3xc=X=iHCs{s?(J;=^q2zSXfX0of1;|Y8-6~E z0M@h~)kmZj8PSo0-SNBm`LprhHawiDmwzvb2zgeBF8{!X^8suvETN+W_L=@4d4A7W zmL_iFGYhIs30Q{ZoSWb6&XY11zMGy$g_^c`Ov>t1n{1aP5GW8ogd;NGaULmfMu9$U zn5j>t{)SjQJ1+Pv?+z~;{rmxa-^X3hY#TYbVk%`~;i=8x^iVpcOtAVRkk1PCE5}rj zt5jc=%`1}Gj}eF_ZP1&r$h2X$*+^*FdG3x&Gi4V-CsNcM+rCV8VyVMXNF&onDL7xn zm~~o?EWwUaEl48ZzDytdEG(h2YrjkwL#z^Apg=RlSF1_HqQhlN_Tu<^R!wgZ19c{V z!-Z~!9%J9k7vj3rc<76Wpe8%K$#2J_8wXpU6c-!0ObhVtB9GoK`}`z}t!-4)Pw>RM zRrO<3PDYzdenBPA`qhZcPNhL=bAxoLm+tI^15f7^8m8KqSoBc7ah`}LWWEl$;5w|Z z!Fx2Q9nGe0=oHdN$Dh=U_D!5*+(Q=AF8$albswx3DM9U%mt9ui3x8Vjn427Oh z<0Ww@!X21VEnjhmXtAxo*TzB>OL5f~);4jMi>wlV*nG6$5a4F#!a{oYr-{P633WH8 zOo-HD6*7Z>P`;2g|F=5pqqDjg{zlHLhxp4*3W>jE;t$s)8wQzC{a5al8z=UxphGwIEah$cFjbEH#H{9_a9S-93G65cv3RM3dFTa!q6L_9(KzDb zR4D*OJ-W&f98>?9*_xEntwV~W_#QtXHeUp4%z+|N4rz{$f!Ho3>#x|1Fw8Q z%=fgQR!p;CNSfpCY2p~9K;&t9EhPUP851Bk zAxxcpgugdR!_lo^8@F4?eV}dX(t=nzMgzQJD$PJUti3p`atbkJvzpu7M2?jRl)Gpg z`Mt!Bv6()f;+<$nKsW1Fg*r-L#@jo%1>343`}n$_$F&I53rk7WCmIj+TT{{hk- zJnV~qI@rH+1`7AlIdqexY%9jF z)q(f5rmv4Yxp^EzJjov|oph-da{!Yt_AAPS$BncKzSe_>+zr%w02^c^eL7W%OPO$* zIxc*nR2bh<^zNxhC%<{96w8ukobU|E!i#DkA~ALjvWNxaJTti7(fDhL%#7~3WY{lJ zo;a49@!Zfk;~wUYVtU9PNGs~?_p6uq)d%SD1B2auw;*cYGSQmKfW@YZNZmR;4Jx`{h%yy)dYQr zt@w6Sex+QF4u@e!9ym`89{(vWzH`&Vt=BnGZA8?Vl!`Iho3K=WF)bNpvza!9Zl5FAhzk;2?O~IOhJz<5C8nJx!boh5 zeRIU;CDx{3AT@eh@*O#VXla?V2=LBc8ls1(3V;3iTf-7)j^(bo?j#`WGJQJ1*h%Zx zR1(z_#qZ}b` z_j*zU3xpSIr`jU`rv4;!#F#3Ic28Ex?YG?cdl~o~OsS0ed2`_93i95wyaqr-xTQ1F zi-iZmY3XQQn#J~Uf8ur_&~4m9I=g$(Z?Ju{9V(Y}|C=9y47Xv4p|vcfMt38s;=AcR zOdh;-S~GdvzW^pn#99R8FWMGoD6qQ*@I_ zHlQZ@RhZSv-X{dsxwIrHRCz`ui+7lbs@cD{C_VlgiT^e~*;|O}1<wPnjA&`|P)rr>99aZ=5x4*D#;(U-K6`Ir zSOW`9F0mTS&-_LSviyZE1#Z>CDqwmO<|7sYp-M#Q0ScV_-$-%W%L0=Ave6)o@9Bk( zWNA)C<>JD8UmEQTIK~eNt)lkg=D6hJ_$}O{^@(;WwLXKRS zqNbV>!OFaoo@j?WLF|YU}0P}K=ani9qJHOnzwAt=SpT=*PFXmu! z@>E_*KCrDO2tO=SZ>=3aRZ3}CS(!g`S6py=36!ikbO&j_rE=8Wb=h$b&2!E!UAvc^ zm#;Q&`ua*bYL41mc`3ifN8b^p^?xtOF3*YR$jA^-9>dbhD1R&{r(#+7c0I{S5g z=KQz3NcG#+4rF>_tB~gFEW2c7yy2-9U}?L#=%44Cv*dAs;L)gw247*jb%W{n{8wg4 zscFt|SL*$ z2!y5c!8O>CSr?+T66REewdMc8fhWNc!Rm*(%x{a!32+ltu{XP_DXFe%&Yu`?t-NCNZ+qV9}-dF%ibhW-Soz?`vjqUhmlsD=_h5QZ*5NSf23 z65X)`bqx_5`3}McHHQVJ3&nB5x9%y=Em$X-!kxXqnMmRyS%uPx^e1Fv$;y=HCaMyq*Sl87b+d6}O1Nl@% z=bYi3;Uwi1%k;})v8!lR&D#NCUJMV=Vf~f!G4KJhMJx;+YC1E_BD07qEEA*27bo3# zxDA-UAzyx(BtWMeD>RAeQ@|VMg10YYn!9}dfc}NZ1)?AVtyD(ONh1$zqX;A5+U1w; z3?tcY4%;}5Un9Ri9j?V2k7Hi-taB>QMXbc zn*=$+py&qwtsNaePb6_b7%vDY4^0tSDGkb~C$*jdex$S>WlelM8T4xcn1E{ogkS@eKF9RDdr z!(#S($E?h#bMf@hY`cybuYL(a5Ul|nsxKj)^yPymlw^SYsN@^q6Rx5}KV^#dL?F`Y zRg@ZEsPd+YYfc*nqk@f6%o_UhZ!k=Hka@OIP$(GuwdR9CA!Etf89q7BHxg?bl*7wc z{10^B53n3#Ddppdu-pa~nV*NqP?4`#Z<_100^2fF>?+3eOSsSvo~n=)R*8c3gm6%@ z{}uM3J7sdtlrk9T+8`K1+qjA=yt3_9vj36Gkn2DA+TQX_$DYIb?l*a}{jnLd`JZD@ z02+8N)RwW>uK;Kl5HE{5*Jx5h<%^)f>xch;04K(x@3T}75BytBOP18+~=(K$L_!W=YNW`AE!kT z;I%`-C#H~$PRZN7i3B-0nB4KP0Cp)AVG`O>dG{_jMuR0imc8f=X35&qK1hGz4%!snx>1ehns-T$;(Ra~dbQoHeA_HbaKh9FN9am&FQFo%Xe&CVI;tzU^C{ft;na zLBGpdTXX27IT6dZN^`nfB=_sHH((L+RP56EFQ`cD%2(R_px^7XVte}=#kt$+JE zo-0ELBc_m%r;S!tLHULc_jJ&yUQ3j>;n{Mw9DR1_DYZ7`;{RmP0m-W3@^+ri=)XyA z$hHfna0MQg$_)mTHoP0JrIZR@=#zAWuV#oiq9vp1a$DX`!uTu68@SVOE5xe~3I6?6 zwoMv2oM!mx_!MK{Lwa(8rEOT|imtU55ndAPun8V7@XCBw1WCxnRD+sf_5A5GT@Brl zUg|~s?Wou9#L{udfOoZQhU8EMWp45fm@dDiuiTJr(6sxk2SvC0O(VAD&b{wLXBD4q z&az{kY@#)or8I}*R`$7s-egp5eW;*YLRx!C_GzhsLw07YNXt$vzE*VMauu(*mcmd4 zmOvyM^pRo0qA?t$Xr7E<5?u9q7XkQ?( zYG2z&Vese$XbawJ{M;i~%CucV{AKDjL;~7wPDm=Gx#5TVseJ?Ut~!|Vk`gR@#3Eq; zkr`U4#o#zntvFq!l+$rBX(v}`H(sp70TWjY(v{4H1G2GcMBDREz4N!Kw3+%)c%{i!h*p(&{7sNpJvXEtDDke+v+ zY_FQ1k#1x_SHxv!Uww2^KME;}pMlhxMrpVd}5U^`LCYO%}FbsToEL*RYo;N8`n(dSDq1I3tUMO@~a z(@B@qY*%b}eL^?ID4oo|a&RVDKiaMKf@ZT3$eJock;T-Kt-l?BT=3xT|q@lFWbbHS_56z5n)Bch5eqJpxnbtzY zVs9D;HPw@Qb666^N#V;H8D6P&IeQ*Gx!~N5;BoG3CWRia%$h`fzR6$2Q+|uTLf3qO zcFSj~_2h&Xc{&g;G=a|G*w;V2tLS1#&tyhUB{(f1!_t#KlKm9D3>ESO2UHqM8A=Ef zLQo9!FLY2UKdH8sLME=x6_1}D7~TAQxfi&L69V~f{12Tf7Qm)RRRKf84_pbuVce-d z_~ZLE2>-_S8xUZ|P%9B&#!+htA|Aj1)${`^yO0r-+7YH@tp$8p5twc;?~&{?(LrU1 zO$xz&eKZq6%RAlBw+mtk-Ea4^Vt+}bySUZAXBv0?$VSADU+T%w3cxeqihg{=(}*w5 z!iHk;C5WMR0a*`2VJDDF7_L+;>4<$`;e|#8+7{5X-U-QkV%+@WTG|#4vNW6qq}c>& z;HE1SY;GeybXCnDw5?|O~ws%h9 zTcL)6*gKU>Fmpg2eTAo%l~g*VrQxZeAsz~I*|o(kE)Z=2G@txgX@nDn%ptz3(!!e# z6HcihI|AkX_H>b?GuWsHMvDU=jiIlKh2N1`C3Czznu$EDrUG^-D3?g+PFfH;6y-GB zqRO5ru7^^{!hWLhGL=_60Go+Vaol48mz3Q z^qA}=JXt?(gbyvd82FIn2rlJ`{g3m|^`N%+BEDwEx+jrOlK-1ptRp5<`a}FTr}rNU1pl7_E`S*pkacqRFm-Scx3M(0{~v^r zmTIVsA&MEkXWL=ey(7jHNLuVKuTQTJpN%?-D;rBK$-=65cH?xuV%zM3&wId7w?+_|O6p*gRmO4r*v=cWXsJ0ccK=*WD>+833#iZTs#T!E zs7%whGkVZp^I3n}vjaISpmwqQrrqH0zai`O86%C;DWnEFXzE%NVrQ-}>#)=?Bm9+x zcKm-D7PXhlqZeL|%0AAo`85Wd4u7>ePbUO=fy%X6g^R$gb~@AbiTrDq%s;m@N;|fK zmYLTfh&I(?R{9ahnuO)S2QOF$yfE?W){$23*SKo@Oim=u_g3qvgPJr5HKXL>WPX;N z7Lr2PJwKA691y|Jgz>ElIpH=5@jX7FsOC1+0zAK4F0R|Q3hGZZ??ASblTkYzrbnq7 z0PLpZmO~wXeE%*k;ou`ypa!WmR_;nfZyjj~##gusHhez1DR zqjpA3d=npHwp7I*uY8vYe8tr3cZojB0FbH0sRqi6n(!#s8KpLI#b%+tD;y#hTA|M_ zD{v7MkqEvv&bZ_M?$h{WXx*D{Q=TuT@gUng@@yKnr-#}r0T7dp+0%&!IW&=cv?gMb zuGVFZ=Z*w(ajmE#M%*)hl2WsOpg1)8fX6_NEYw6@dwcaVe8x{$9;TwRcyjetFG!SMDs#8nqkHnj& zm<~xPxe>|!{c)G*Q8;PcaU6aDNvWm|a$ek`Lvp$7i$i*qKE%7y`9`&C%h(n~uiyZG zskwEc-K*hZE7Un?x9rv_ZjY$}2kP8EP&tw7E)3rov-H?-(!5$}-WM5XFUjV#j}yr=5q6egj--@?H(CQu=6@ z)H6!6r_))WZ`Q92)G&69pcb1`3i^o}C~`E-(JvsAK5sNck_tzHZYfMy$~}T)xY#?W zZS#&6*I=fm&6 z>UNR;)sCb99fw1Zfv>4bv8%h{pr7P(YF7^D33q_g;f=eHinkx2@M%-rvecSs#X(&= zTdg#0laQ?`n7**%sHYichsq9l6_xM9VcN?6%ZtK6CxbXcvm2?W<{SB#Uda#$sNV`@ z>f*@c*tv9!DNjz4|Mi$usk^jlMV*op+gW5$<94J148fV48e>FBU$!Y+(}58BcJ)$H zVhp=OCiOFHxU;A^r4Fss=~wOawh$4cVbC3=JR(dbkNJ1b+j_`vwiVXWh>XSGOmZyo z+q;;PTeGyf>>8IqLq$YMv#FNAdXj{{XVuYzOtG8;dA-dvku|-brPh2U(X@WjYO23; zN3jA1(Ua>^{bqj~IAvHDTKojm6iR>)+$Fe^E*7t(4OiRi5#z-9|jZ9c!Aa|&I{qM>0Rr(JA>&WkKCN-QZ z3uKKmTZYre=imJnNP?XCmxDoUP?L-iqKgjlx@bKOb{O+;HuW(c*|G$^0z?oYLzmS^ zw|`UP(iAAD7gjf6t_j))Igl@j;4;hOlB%_2$>W{c-RdLP*%4nty-CmBXeiJk>K_eqEFle zEl#OaykO)Dq$pfOZcmGW2T$u@Y5}{$>?E@W!@Aq?h!us126P6xSwo}mT1_eR@e`|N z@k{$qCBKyLRH4&cCncur*fm9Bx&3;6acwzhQv_9p$X4QejjPuKe}qI4WN5C4Wvdq` zbV_*_@whKj!$xuPLf3HZ!DwZd>aU@n9N6};m!c(;Wuw4G_HCS0IFuWCn6|EeOgZe? z;a@3zSKPdcO3fRs(en)$ipFcNgY8wN6uvokk|dvFJHcikv+d%-isH*{j9SDqhqD+V zL_^MLQSITo060qkvUsXG4er={`R{|^YKG+4?1z!UL=tceM4tG@2q{v@{1mPZ=JPA+ zYTXESRLP3rV9o|Tc$`!_ddyGYMd=DvSI}yQ4D+kdo{Sg+LgpR%`8QyH@jvjHl}4YX z3U9OOUDGeX3-CJX`fD*#gV@^Ob!&~JDC-6xHweiFlTDie-U{RIC5_Rr&Cza|E92^H z>^Yl)a*WPBbpK-7xl`z4#_IoyBnuba(txkDOL!YAm7D459A*!0Te=s1YXMkG^d`xqC?6-o0^YiK5~QMaLQczA9`L$jQgZosC@1X9JVtyT<9 zUVC>Yk%JcAZd8;4bic}khi@$L+PU|GUmkHGjHhpw(ZadkL!*-RytKy~YJg5fApZP0 zem^oofz}FrO8we7eYai(gKfbW_t`t$Zo_@Wt5h5yOhE$U(I4f!`r6{pZa2{(^3Tll zi8s&rK)*<=K0NaI1c@_^*59K)PB@`(j_4PhnahuQe||vpl;tkNYKgGt`!g)UDy)YL%}G%NjT6nDJ@O8hz6dV7o?bAc$IY2}I1GXrt@ z?=@4Ypkm82@CV8A>lQ1W_f=vu&0@KmAI}1Cz{R<3I?#3H9(^==i~VCOjoRuVtS46f zmrIT9*l;`AMLId@HbzqqHum_+`9O5o74xu^c{onz>L)6WNO&0pymYe47W&2D@2l@r4mzkzc`!lDZ3e!+ox^e?CL~*ORHGP5Z0#zT2&dRU zr|Giw%E6(9t3Zm%u$tji;!@tDrGB?kt(FmZj!PW<(-`8}J5fK{<1g0!_VPn7N-L`i zRJiU46)Z&SJ^bnKZ2;CaivXqE+0^c?5<7_4h5w{4rxEnXPbBf6%LJdZGza zyCMe_@(BJCGkXjZ!PW3FzMkUX3s>CVAL2448Q@BfR@@@+{hVO2eQ%y^xTyj7zLJ5k z1L6vy<=3@$f;?dQr?~7NJ+$)&>(9Pf09E=k=_|GACbL=bbdB=yLw8%iy%mEiq4Ko+ zclp6KS<{#C2obPyPV%6f_cdk=0k53%-vRn+GCL7#Ik(zN2QwWJS0dujhbgW>L}MjnFelrnhW`3*o|5~4t-eY@qd z>0JN)R`@`<#&1+uYk1Sv)2`tZtG06$&eVp(M>z4iSsX>_`+jvEd6S+x<*D{L!B|x< zJiZl$G~6K)Muk+5dv_$TV(U%kFr972&kH|CTSXvW(8p8F)8yrJ49=gFBpyR~VZOtq zRQHM8Mp2ovglp9^t_Q4ZzB~Nt*RgwYHyGu6ywBst+d#PR-JfK`o_^b4y0piDBOo*J za26w5bs$J*BF?1zZB&vJT|(Q)g@2ZH70AF&NTnN)UOJarGNEjU^AiO32W`@oin%>C z2J!TBXi|x@Zc>87G6(&-r2Kd+X5+%*-PO&uZMQ3W3I=Mt5)F{8pI&ZntXM#n$n(7O z6K7<@8(PM@l^|@hT~4yHi<%CLiViQ;(Hr^YxqNe#xN0upuuQa$sNry8aaWuR#d(MA znf>o~Xs!3yjmlfPye}krTihRd`(L(Xpqa4D(h0?^t>N5kq@HX!M2y8K+IvAaeHUNt z={(JH6}5_Wb$DQTMpOSRbPdz(G5L&8SN^FeJDxYoS-$&+bv7U;Uq9>O=4G>?bIk1G z=l&#JnH#i1pTkM*o4ATJ31o4)*&3|PqXt=BpTuLBbc^nYQ4=9{8BK@Dx%F}0i8-ic zByFcQ&b(FPh3KOq935FTcx?9ef_$_+v=^^MVkzImGi8R;t`-8(4 zBYRTO@_AmO_gLFcd^eE3@@euY)=v11CiFdoqpXba80D3IiUFpwv7lT?M$$VzxdoFi zJ;)u}qOKIL6*ZYf&CSV0YkI0H-KkJnl$@ll_yc&bb%9&_-i`M3XySwy5bhLi#a?)7 zeePbEEzf?A-TQj3HS=V4;+Pq7)LDYE7uOFa^@O9qFIS`(!qHde|HFy{q~&u@v(y2x z(l6$`TgTDz{rI9Hi=j7cS3mqy5A6;FUvyj>BL1`bvSI^9w&7`7e&S0+QaDfdim23O z8VvYV^#sy-LHHoMZrZX{6+#N@4f`x3;gNH%X-iyHwgx$u+>-4bOMY-TTTjp!j`BC$ z+z%GfSaiL5i%rOSaOEL@&z0dnKG3#Y6^gYIsnlR#qKTZEb^4&>$*Ss!u;G4>2VvJ0 zQCjJ0B%FSeQ^k0kSNc{p*8?ax#`nh%8XHHM3OCfl$7hT2fHf-8uEy@Tjy5Q^HZbzVa` zvso)Xn7Xp1y3U1Sz+CKiF0_6rpaTS=mKeQZk9k_^;`NZ2oAt;Z^D3Ff#VZOc-JA5G zS%JX#c&uK@(lMo1G=&s6EwLb5OE>lD$hse>^$=T`w{#l~)Zx>)JA4+Jin~U&H?|>` zqlZ@dMfEn&?~vvn zt?eVYUdVVhwM}2ES}w>T3?nwIf6F!=>JXgwM$1%81aS%)XRweETO z{}w3VGg7Q!Wfi8O#@ONle+Y+1Ss}~|Zh-$bldVWN{4#&&Y;hd;5lHnWzRoo(D6%^o zqOq)IbQ2F=y)mK~qOo=Ov*3@O0QANFW3cZFVZHI5fXFE?$RF~K#|=;!2GvubB`BhbwiL_3(~Jt!=5NJG-b8}gp`#*Pp)v`M72u;IEg4pBH)7;IyWO^@&H56Z&< z7aT=NKayHO*nc|-dG`P=Ein|-PsNoVx=bc*7_8l}IvbGA22#QU?=*wws!(UEpLDgWk}V>hc&i3-`scPPeoect z59)7t{_aRN1w{oV&cXu!5Cv-nK2@+GQK}lHL=g}_#De-zD}4cGgePBksPIN7(j)Wt z6(9W5W zh4o(*#dXZ_J@Fmk)RIVQ<8KXJ7s1AsRJ>zr)O}EcOG`KjO|k2u`Vsm+!+N?do{3a1d&Q?oh&GX2#w=Sc@qzxkjYZo%Q}zH zBzP$gte#v;LuhjDZ>?vNMt(8AWumrP;;hh&I>(RxF&6H0p9=p zrVoMSx@hSbW8c-5-8smUlIfd?Rj#=}gsLGgZ$-68x;j{HZZkC)Kfk5oj}ZE$Q$2qH zlcSSafoIFz&AftXSDMBl44>j0w)MPcxL8q;2Rpt~YyHOqul$oIU-$1_8x_ar4RFn44%w%P;yIVb9ef-7}0iV__Wz7o;!E>}S zoaxaqaj|bsGnk?tcIg^)29X}^i-en1Xw%D%Chn#sDLmn(yMHKt*nH#;(v1O}gRE-l zNj!FY8likgX^GzhdF$_Pav7>zSEK4^Oq6IB=)>RiH zy!TV-XP=UVNTNWx2$mjn>zDzw@5aP%Z1iHpDd3blqoAL%<0{< zefvLMTy<1bU)P2Kq`QYf>23s(mhKK|X^`#^7)qq;BGO1pcSuNgGo*A#gP9Si-|y|DEN(ofamDx=H@h3gP&^`Dxi~>F zz;(*HaHsO^{ymGm>C`-PbmCl*U<$2KD(>SCDs?;V-Y?)(&IB9;1crx=Y0*(a=trGB zD8&r1h`A!zN7y)b9-ZG)EkoQwz99`kIXxw5o+qNC#>iwx=e&{CsizuKDMZ+b6G`+rLLIRzc1f_leG8 zvqD@L%3a!qfE>%I+V(3_)000>pqyFwrV8;@V?rc~o@6-VbM)a&or~$h_7Rs&p&{Nn zU5qF4=-FoP)rCp>is*&o#^naqYuT2GPG4q;ahjrWo}A={bB14z2)Qeqy)Zk9>PJ9po=#Q`NPHZ1QGo9&CYrSnF>Pou5!pH3>U zyb5J_Zd5ytZW9+%frh3;j-mlQNS$=|m}TD4a+4qYsMRpOrAwr_S>H}xHOFTr!egG& zn`F)6(XGYLuf@w(Ie)M-SjuCYX0a=7UuoMgtEqL=cKSN1zRPzheQ=Rgf0CPcRz&E! zLMN`Bb`4T{<4AP87Z?@@tq4Pe6zB5qL2{q~@V4b*Qq{)`>A z;ffhp7`u;5N%!hAMwso&U({Dk{c_gTt7j|tQdpn+b^#P7La#U~RA}W?P}6eHaQnt_ zczfTzMVMKf>e*kf92KYS8Ei38>S4ZDBqR>>Q1(*$%lA{}C6=4bf^D{?%|F6KKDSH~ zFbPV8neFNZlXl~;5*pP*HHR@%{UtiqjrbMMb5|xAPOw>!@WqIz@Q>-}N0kQ#?hxM^ zh9m5x;BbIrQ+0iSNT{k_%x`pZLT|Y~@(kirT5{W)*L{GuLLbYvrEnzM^3n1DPe8D) z#g_VKgOw4psYwNtnWR(A*(>q@l~?kEmnfACCyM0lW_#MLG;7n)zns2(m-XSR1DEUp zj2jm`+gz%oqUix@JLjJK(#EiK5Bu6$k?7JM@0082dXI3lc-^%m)_P1D9^-nC`H}*qm!av+;V-%t z5|+zZiR$P^*t6j}r8liJ)}O0u>m0!^noOGU5At6iCcu>e+;qumP`rM%ce}a@DPO3u z!M<}qX>QEaq1i4;i8G-)+7}CxitjM}hHGYONPB!>pQ9HH{^IH7yclB=Sqb#SS_=`t zMtqj5O|emTcT(Yz7%9~xUBBg3TIf7~=6%e<%FWf%HWI0o3I zYkbGNPMh@0+#>TzM4TFJ^7nn-YpTDQM7h#zlMCi_oaVjfR;^D{kEu!g}&Js96;>vsD4% z!cTn2>BKDIi%+0YZ8 z7o^FZhM3qgy%geo7jSp?i@1YIhweG;l$@lN z1SSoE8QGZ`+J!*a%VW&ZFUYanv8a$ug4UEIs&(pq+F0f%aaRiL$hlb1W%=a+Y1gof zQPu<{;~2WLa(2C825n`%l9qe2+FHmgL&HgmfuR>8 z;EJWyl_SuWYCepitN9d)E(uhWr`4DiHYjV)2@qhF|M~7ItpHRRpE11HnscS&wEH?x zV*5p(!62QB zo9M_Uv*ah(3|I6^0-p+pxA12r^)tcJV!x(HyWn{m`kK6u_bexrGeoz13@Mr7TKWYB zuk7Tpn8VhgCDr<7H6kiULt(Bwg>NG}Ye}(xd~+koOhazK|B;$8$n;*~&2t4kK`lws zvjxj$^O7qx?T=ropoAcnoeVRcvn0=GEnmsOln>U5(vaclMwQS%4H}g%Ke)0v2-cJQ zlu-7s)Tw(mcJYn|s*1$H-*oT6yF*su`OT8*{gbhg}e!%ab?AoKYMVjYC77z{yS}>qXrz!7P z*Eu^B@Qn*J<5i-sxJ+P;6$M$(ve@);>QK8f9yhLbk#$(66%9J@iqs0qyM}D1JED7` zgtiB%^l*VrzeQ5xoX$t$dz|t_nSMX&0*%Tyo}oU}DKAZeYp4A;LFmy@%7i!Yo6Q60 z2$X@kE^6W3#g=b1)l3N%%2QCSJt>m+i*U0`pSM*^G>)JkU3!w?3J}kHsV<0RgM9X(rx5W>+=Z-DdJ~cTk#jVgQ`zFmTp#~>xKR7|s7R#r_II{P020@S4?HU7r^wif zJYiJ>2>`XJo(##S?xx^U$g{{%jQ$d}76wUZpGPbO_0m=o{U*O?B6pxiY-=E#ha(95UCF@a&(zwOsyIlw3*|vCXbr?pV@5{YN>6ZjA@4d>@zHpxtyH z>QOY$^umFMsZm+8ajxWTTLthvmvg{dSCYu~wUFA8go-sA7E-dFyVfGJuqW2=)@7*a zgu%OSyA#v~2EdiHTx{!IHwgb6-D~u%~l=xIcY{e$O~ZzYU8F zV#0C&mAoZhHWgUKfDI?|OA(*ZDo$5Bi2Em_*7^T69%tD`|6F zRf_dABa#a^1fD@grvvt$?z`$<{_W1L`_mo>{d(X2MUk?f#cWy#E~C*)gRkCdODrWm z?aI}v++t9NJ5@%PC`KJGSLlg<6Z8kMRdQ3_rEhz(p9If}^n_zDY%ltZTLIdzUhyS4 zF?t;-!%6=Z6XO58^j*BdAkm`qs?3Hga#o($Ij=VYC;pHE?bOed^B%@;vhKL9%<_xQ z!Dk<>-;ps%t17f_Xfda7h{{@!hH(DDV=s`+*VT6taYG_dTc!Q_13iCWo2i02#`diOuVZ{rd%|YCfJ6~3 z705b0heS>{H??J{8tM4@y(#~Wpo%xk-`JP+9oB~Zkl!5d%<2O%kLSMbes2oBur-zr z|Mn)i3zJIacN5+97F*&p&N!N80-jWM>yt?oYZuhq?6D1V=0HxHJB`G9M3h?O_w68T zzeA0&33$CA13m(R2r%hS2b_I?Ku2Hic@e@@irV-`^I?dJ2`thsQoD)nLBT>gcG6{a z(&Z$q99V<#IQhIDR#U+g$1UNJa_Y{KE~LU5Woy1mxc6Z@moK~p_S<-Ydb9(5_@AF0k{nPi+zDx9Zh+c|KvNFv4NrY0Hmb9EM#ssaq(arJ_P@Z5!^ss2@ zdA2-|!DUk9n<@|kn+!NnJ?h;REO~9{OP@0`Esxnei#f&dX8K>trD#;L(@wOfW&?jP zmV!U{_(*l-`Q4J4h#3blRvC2xO4muD@K<5l&#xsbOjFw`98%=b$MG$WkkR}-(+VBE z@}KulQU)b+468KIIj|>8K@B#T^9s7bkm(VrPp11XY#Z_xqZp@5nDPG5qp=BM7pqFn z6Q4q=5F!|9xP#*5h9J6b9_ZtQ^_3EwNXThX2ZD&%+LW^zwhc8kcD4Lv_4!7$GgFoV z9Lpas!19`IFn(@h;UB&Q_nA{87K(4YC~6ICQ^FP*oIeMI8M7W2LpNemQ%|w|K{+_A zuVyoQnMC$FW19U-8@Q$8OE_373a+0ouKh$Hb4A5+)jkKqz})`j3_kb2HZX`7=*I_> z7aSR3Aa&FEp0vgNER{;t|D{Lx#hY6G!#0ikT#h1$eW4_5ji&DptByD$@_4 zq$mM@?{^Gc4lRw1lkJU$hIx$jee}kLF)F%kovA)t=-Ucam^eAVDgEu7_L7pwFydqD zAyG9ObHY=cY0?-@l5j$TWQTpOK<-~x=~9PLh5!`wBQGJI%wrhcXpLD_fkT*wy= z+=_G!_sVM{jdFvH>0)$6FD;m>w(eqXXblCWp_Q<5F3_eC?-GjM7HM&eD1I zs+wi3^G<3ngJdPjNr=ZlLs(2`mf8!w2C&%sT`TlT=J^nH6r)|ODpEV5)>uA*6}+bW zFO4nO{W*ree!qt*;plg^20PFCJaaj!9+Of>`FmOz+DOzI<3-dOwTywYCW7+QjqZCh zjCt-ec(}%M8h?4VX!M3kRPBV?;2vKzYs;hEkjSqK=bk8A{?bsKT}K!LXT7SUzc-Zdr}IX~(^WGTuqsS(XMhkBlB zMb2@nwg!Q#aY@5(U(>Ag%!Jlv^{9!{Q=NUJ4f}eW()U|^>dTfrV zH(u}SsY|W|dXpv!h^Mv3>AT=LY)HCC#tCDV`0wdq`c`4g0gk165Q#w)%soFOK_rJ4 z-rtcF<+7fK)yi^b)5igBT#^|)xtZ|IyI0Df$c~qJi=8?Eog_xhHP|rc9r5y zwE8J#TVg=B%c)QR0d!5*rR%qDl3z{KuZHvu!^q98uTO`x#>NSQa2KnP>|8YCQ84jh zGq)J$Mj6#P)|1=S-3TJR1lkF-Y#N`e8-15jVqTzR;{RPYcBD2EyDQUE7Iq998)xXA_> z4zqx?_#Z%-!_Od(h>(xQ6n*gkf^y&jH^X?4|0OEGYrg+;22p7mt_rZ-(zhOU`)e*z#^b9^9M6qhZ3k9WdSAIJh&&LQlJF8e@s+BV@v>a=nkA%(*tPZ5MXo+ z2c+ZysM)Z>T^7(s58(N@5U9rka2YoOsd~dtf$qy0^gPXK~)g&q8zq=_22ttppo$aO6XXeu@V2pBF<+1O(wndEa6lK)Zny4|&y7U=UH_L+E6R5Ata3_$aS833vsw z1)ZcnV8>z7pr2X5t2AanY+4+2mIDM$n}d)G9wN9iLLkH0$G1_KWJsQ>j};n6?p>kbBp_A`>G WDWbsF$p{Gi@ZUasP|4|kdH)CXgbPdn diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2e6e589..4b7e1f3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 4f906e0..8e25e6c 100755 --- a/gradlew +++ b/gradlew @@ -82,7 +82,6 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -126,11 +125,10 @@ if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath @@ -156,19 +154,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then else eval `echo args$i`="\"$arg\"" fi - i=`expr $i + 1` + i=$((i+1)) done case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -177,9 +175,14 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=`save "$@"` +APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index ac1b06f..9618d8d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,89 +1,100 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/java/squeek/appleskin/AppleSkin.java b/java/squeek/appleskin/AppleSkin.java index 523bd05..7b4fe75 100644 --- a/java/squeek/appleskin/AppleSkin.java +++ b/java/squeek/appleskin/AppleSkin.java @@ -1,47 +1,13 @@ package squeek.appleskin; -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.fml.ModLoadingContext; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; -import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; -import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; -import net.minecraftforge.fml.loading.FMLPaths; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import squeek.appleskin.client.DebugInfoHandler; -import squeek.appleskin.client.HUDOverlayHandler; -import squeek.appleskin.client.TooltipOverlayHandler; +import net.fabricmc.api.ClientModInitializer; import squeek.appleskin.network.SyncHandler; -@Mod(ModInfo.MODID) -public class AppleSkin +public class AppleSkin implements ClientModInitializer { - public static Logger Log = LogManager.getLogger(ModInfo.MODID); - - public AppleSkin() - { - FMLJavaModLoadingContext.get().getModEventBus().addListener(this::preInit); - FMLJavaModLoadingContext.get().getModEventBus().addListener(this::preInitClient); - ModLoadingContext.get().registerConfig( - net.minecraftforge.fml.config.ModConfig.Type.CLIENT, - ModConfig.SPEC - ); - ModConfig.init(FMLPaths.CONFIGDIR.get().resolve(ModInfo.MODID + "-client.toml")); - - // Register ourselves for server and other game events we are interested in - MinecraftForge.EVENT_BUS.register(this); - } - - private void preInit(final FMLCommonSetupEvent event) + @Override + public void onInitializeClient() { SyncHandler.init(); } - - private void preInitClient(final FMLClientSetupEvent event) - { - DebugInfoHandler.init(); - HUDOverlayHandler.init(); - TooltipOverlayHandler.init(); - } } diff --git a/java/squeek/appleskin/ModConfig.java b/java/squeek/appleskin/ModConfig.java deleted file mode 100644 index c9a2fc7..0000000 --- a/java/squeek/appleskin/ModConfig.java +++ /dev/null @@ -1,129 +0,0 @@ -package squeek.appleskin; - -import com.electronwill.nightconfig.core.file.CommentedFileConfig; -import com.electronwill.nightconfig.core.io.WritingMode; -import net.minecraftforge.common.ForgeConfigSpec; - -import java.nio.file.Path; - -public class ModConfig -{ - public static void init(Path file) - { - final CommentedFileConfig configData = CommentedFileConfig.builder(file) - .sync() - .autosave() - .writingMode(WritingMode.REPLACE) - .build(); - - configData.load(); - SPEC.setConfig(configData); - } - - private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder(); - - /* - * CLIENT - */ - public static final String CATEGORY_CLIENT = "client"; - private static final String CATEGORY_CLIENT_COMMENT = - "These config settings are client-side only"; - - public static final ForgeConfigSpec.BooleanValue SHOW_FOOD_VALUES_IN_TOOLTIP; - public static boolean SHOW_FOOD_VALUES_IN_TOOLTIP_DEFAULT = true; - private static final String SHOW_FOOD_VALUES_IN_TOOLTIP_NAME = "showFoodValuesInTooltip"; - private static final String SHOW_FOOD_VALUES_IN_TOOLTIP_COMMENT = - "If true, shows the hunger and saturation values of food in its tooltip while holding SHIFT"; - - public static final ForgeConfigSpec.BooleanValue ALWAYS_SHOW_FOOD_VALUES_TOOLTIP; - public static boolean ALWAYS_SHOW_FOOD_VALUES_TOOLTIP_DEFAULT = true; - private static final String ALWAYS_SHOW_FOOD_VALUES_TOOLTIP_NAME = "showFoodValuesInTooltipAlways"; - private static final String ALWAYS_SHOW_FOOD_VALUES_TOOLTIP_COMMENT = - "If true, shows the hunger and saturation values of food in its tooltip automatically (without needing to hold SHIFT)"; - - public static final ForgeConfigSpec.BooleanValue SHOW_SATURATION_OVERLAY; - public static boolean SHOW_SATURATION_OVERLAY_DEFAULT = true; - private static final String SHOW_SATURATION_OVERLAY_NAME = "showSaturationHudOverlay"; - private static final String SHOW_SATURATION_OVERLAY_COMMENT = - "If true, shows your current saturation level overlayed on the hunger bar"; - - public static final ForgeConfigSpec.BooleanValue SHOW_FOOD_VALUES_OVERLAY; - public static boolean SHOW_FOOD_VALUES_OVERLAY_DEFAULT = true; - private static final String SHOW_FOOD_VALUES_OVERLAY_NAME = "showFoodValuesHudOverlay"; - private static final String SHOW_FOOD_VALUES_OVERLAY_COMMENT = - "If true, shows the hunger (and saturation if " + SHOW_SATURATION_OVERLAY_NAME + " is true) that would be restored by food you are currently holding"; - - public static final ForgeConfigSpec.BooleanValue SHOW_FOOD_VALUES_OVERLAY_WHEN_OFFHAND; - public static boolean SHOW_FOOD_VALUES_OVERLAY_WHEN_OFFHAND_DEFAULT = true; - private static final String SHOW_FOOD_VALUES_OVERLAY_WHEN_OFFHAND_NAME = "showFoodValuesHudOverlayWhenOffhand"; - private static final String SHOW_FOOD_VALUES_OVERLAY_WHEN_OFFHAND_COMMENT = - "If true, enables the hunger/saturation/health overlays for food in your off-hand"; - - public static final ForgeConfigSpec.BooleanValue SHOW_FOOD_EXHAUSTION_UNDERLAY; - public static boolean SHOW_FOOD_EXHAUSTION_UNDERLAY_DEFAULT = true; - private static final String SHOW_FOOD_EXHAUSTION_UNDERLAY_NAME = "showFoodExhaustionHudUnderlay"; - private static final String SHOW_FOOD_EXHAUSTION_UNDERLAY_COMMENT = - "If true, shows your food exhaustion as a progress bar behind the hunger bars"; - - public static final ForgeConfigSpec.BooleanValue SHOW_FOOD_DEBUG_INFO; - public static boolean SHOW_FOOD_DEBUG_INFO_DEFAULT = true; - private static final String SHOW_FOOD_DEBUG_INFO_NAME = "showFoodStatsInDebugOverlay"; - private static final String SHOW_FOOD_DEBUG_INFO_COMMENT = - "If true, adds a line that shows your hunger, saturation, and exhaustion level in the F3 debug overlay"; - - public static final ForgeConfigSpec.BooleanValue SHOW_FOOD_HEALTH_HUD_OVERLAY; - public static boolean SHOW_FOOD_HEALTH_HUD_OVERLAY_DEFAULT = true; - private static final String SHOW_FOOD_HEALTH_HUD_OVERLAY_NAME = "showFoodHealthHudOverlay"; - private static final String SHOW_FOOD_HEALTH_HUD_OVERLAY_COMMENT = - "If true, shows estimated health restored by food on the health bar"; - - public static final ForgeConfigSpec.BooleanValue SHOW_VANILLA_ANIMATION_OVERLAY; - public static boolean SHOW_VANILLA_ANIMATION_OVERLAY_DEFAULT = true; - private static final String SHOW_VANILLA_ANIMATION_OVERLAY_NAME = "showVanillaAnimationsOverlay"; - private static final String SHOW_VANILLA_ANIMATION_OVERLAY_COMMENT = - "If true, health/hunger overlay will shake to match Minecraft's icon animations"; - - public static final ForgeConfigSpec.DoubleValue MAX_HUD_OVERLAY_FLASH_ALPHA; - public static double MAX_HUD_OVERLAY_FLASH_ALPHA_DEFAULT = 0.65D; - private static final String MAX_HUD_OVERLAY_FLASH_ALPHA_NAME = "maxHudOverlayFlashAlpha"; - private static final String MAX_HUD_OVERLAY_FLASH_ALPHA_COMMENT = - "Alpha value of the flashing icons at their most visible point (1.0 = fully opaque, 0.0 = fully transparent)"; - - static - { - BUILDER.push(CATEGORY_CLIENT); - SHOW_FOOD_VALUES_IN_TOOLTIP = BUILDER - .comment(SHOW_FOOD_VALUES_IN_TOOLTIP_COMMENT) - .define(SHOW_FOOD_VALUES_IN_TOOLTIP_NAME, SHOW_FOOD_VALUES_IN_TOOLTIP_DEFAULT); - ALWAYS_SHOW_FOOD_VALUES_TOOLTIP = BUILDER - .comment(ALWAYS_SHOW_FOOD_VALUES_TOOLTIP_COMMENT) - .define(ALWAYS_SHOW_FOOD_VALUES_TOOLTIP_NAME, ALWAYS_SHOW_FOOD_VALUES_TOOLTIP_DEFAULT); - SHOW_SATURATION_OVERLAY = BUILDER - .comment(SHOW_SATURATION_OVERLAY_COMMENT) - .define(SHOW_SATURATION_OVERLAY_NAME, SHOW_SATURATION_OVERLAY_DEFAULT); - SHOW_FOOD_VALUES_OVERLAY = BUILDER - .comment(SHOW_FOOD_VALUES_OVERLAY_COMMENT) - .define(SHOW_FOOD_VALUES_OVERLAY_NAME, SHOW_FOOD_VALUES_OVERLAY_DEFAULT); - SHOW_FOOD_VALUES_OVERLAY_WHEN_OFFHAND = BUILDER - .comment(SHOW_FOOD_VALUES_OVERLAY_WHEN_OFFHAND_COMMENT) - .define(SHOW_FOOD_VALUES_OVERLAY_WHEN_OFFHAND_NAME, SHOW_FOOD_VALUES_OVERLAY_WHEN_OFFHAND_DEFAULT); - SHOW_FOOD_EXHAUSTION_UNDERLAY = BUILDER - .comment(SHOW_FOOD_EXHAUSTION_UNDERLAY_COMMENT) - .define(SHOW_FOOD_EXHAUSTION_UNDERLAY_NAME, SHOW_FOOD_EXHAUSTION_UNDERLAY_DEFAULT); - SHOW_FOOD_DEBUG_INFO = BUILDER - .comment(SHOW_FOOD_DEBUG_INFO_COMMENT) - .define(SHOW_FOOD_DEBUG_INFO_NAME, SHOW_FOOD_DEBUG_INFO_DEFAULT); - SHOW_FOOD_HEALTH_HUD_OVERLAY = BUILDER - .comment(SHOW_FOOD_HEALTH_HUD_OVERLAY_COMMENT) - .define(SHOW_FOOD_HEALTH_HUD_OVERLAY_NAME, SHOW_FOOD_HEALTH_HUD_OVERLAY_DEFAULT); - SHOW_VANILLA_ANIMATION_OVERLAY = BUILDER - .comment(SHOW_VANILLA_ANIMATION_OVERLAY_COMMENT) - .define(SHOW_VANILLA_ANIMATION_OVERLAY_NAME, SHOW_VANILLA_ANIMATION_OVERLAY_DEFAULT); - MAX_HUD_OVERLAY_FLASH_ALPHA = BUILDER - .comment(MAX_HUD_OVERLAY_FLASH_ALPHA_COMMENT) - .defineInRange(MAX_HUD_OVERLAY_FLASH_ALPHA_NAME, MAX_HUD_OVERLAY_FLASH_ALPHA_DEFAULT, 0D, 1D); - BUILDER.pop(); - } - - public static final ForgeConfigSpec SPEC = BUILDER.build(); -} \ No newline at end of file diff --git a/java/squeek/appleskin/ModInfo.java b/java/squeek/appleskin/ModInfo.java deleted file mode 100644 index 5e05c40..0000000 --- a/java/squeek/appleskin/ModInfo.java +++ /dev/null @@ -1,10 +0,0 @@ -package squeek.appleskin; - -import java.util.Locale; - -public final class ModInfo -{ - public static final String MODID = "appleskin"; - public static final String MODID_LOWER = ModInfo.MODID.toLowerCase(Locale.ROOT); - public static final String GUI_FACTORY_CLASS = "squeek.appleskin.client.gui.GuiFactory"; -} \ No newline at end of file diff --git a/java/squeek/appleskin/api/event/FoodValuesEvent.java b/java/squeek/appleskin/api/event/FoodValuesEvent.java deleted file mode 100644 index 36f953d..0000000 --- a/java/squeek/appleskin/api/event/FoodValuesEvent.java +++ /dev/null @@ -1,26 +0,0 @@ -package squeek.appleskin.api.event; - -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.ItemStack; -import net.minecraftforge.eventbus.api.Event; -import squeek.appleskin.api.food.FoodValues; - -/** - * Can be used to customize the displayed hunger/saturation values of foods. - * Called whenever the food values of items are being determined. - */ -public class FoodValuesEvent extends Event -{ - public FoodValuesEvent(PlayerEntity player, ItemStack itemStack, FoodValues defaultFoodValues, FoodValues modifiedFoodValues) - { - this.player = player; - this.itemStack = itemStack; - this.defaultFoodValues = defaultFoodValues; - this.modifiedFoodValues = modifiedFoodValues; - } - - public FoodValues defaultFoodValues; - public FoodValues modifiedFoodValues; - public final ItemStack itemStack; - public final PlayerEntity player; -} diff --git a/java/squeek/appleskin/api/event/HUDOverlayEvent.java b/java/squeek/appleskin/api/event/HUDOverlayEvent.java deleted file mode 100644 index dd61728..0000000 --- a/java/squeek/appleskin/api/event/HUDOverlayEvent.java +++ /dev/null @@ -1,92 +0,0 @@ -package squeek.appleskin.api.event; - -import com.mojang.blaze3d.matrix.MatrixStack; -import net.minecraft.item.ItemStack; -import net.minecraftforge.eventbus.api.Cancelable; -import net.minecraftforge.eventbus.api.Event; -import squeek.appleskin.api.food.FoodValues; - -@Cancelable -public class HUDOverlayEvent extends Event -{ - /** - * If cancelled, will stop all rendering of the exhaustion meter. - */ - public static class Exhaustion extends HUDOverlayEvent - { - public Exhaustion(float exhaustion, int x, int y, MatrixStack matrixStack) - { - super(x, y, matrixStack); - this.exhaustion = exhaustion; - } - - public final float exhaustion; - } - - /** - * If cancelled, will stop all rendering of the saturation overlay. - */ - public static class Saturation extends HUDOverlayEvent - { - public Saturation(float saturationLevel, int x, int y, MatrixStack matrixStack) - { - super(x, y, matrixStack); - this.saturationLevel = saturationLevel; - } - - public final float saturationLevel; - } - - /** - * If cancelled, will stop all rendering of the hunger restored overlay. - */ - public static class HungerRestored extends HUDOverlayEvent - { - public HungerRestored(int foodLevel, ItemStack itemStack, FoodValues foodValues, int x, int y, MatrixStack matrixStack) - { - super(x, y, matrixStack); - this.currentFoodLevel = foodLevel; - this.itemStack = itemStack; - this.foodValues = foodValues; - } - - public final FoodValues foodValues; - public final ItemStack itemStack; - public final int currentFoodLevel; - } - - /** - * If cancelled, will stop all rendering of the estimated health overlay. - */ - public static class HealthRestored extends HUDOverlayEvent - { - public HealthRestored(float modifiedHealth, ItemStack itemStack, FoodValues foodValues, int x, int y, MatrixStack matrixStack) - { - super(x, y, matrixStack); - this.modifiedHealth = modifiedHealth; - this.itemStack = itemStack; - this.foodValues = foodValues; - } - - public final FoodValues foodValues; - public final ItemStack itemStack; - public final float modifiedHealth; - } - - private HUDOverlayEvent(int x, int y, MatrixStack matrixStack) - { - this.x = x; - this.y = y; - this.matrixStack = matrixStack; - } - - public int x; - public int y; - public MatrixStack matrixStack; - - @Override - public boolean isCancelable() - { - return true; - } -} diff --git a/java/squeek/appleskin/api/event/TooltipOverlayEvent.java b/java/squeek/appleskin/api/event/TooltipOverlayEvent.java deleted file mode 100644 index 537015b..0000000 --- a/java/squeek/appleskin/api/event/TooltipOverlayEvent.java +++ /dev/null @@ -1,59 +0,0 @@ -package squeek.appleskin.api.event; - -import com.mojang.blaze3d.matrix.MatrixStack; -import net.minecraft.item.ItemStack; -import net.minecraftforge.eventbus.api.Cancelable; -import net.minecraftforge.eventbus.api.Event; -import squeek.appleskin.api.food.FoodValues; - -@Cancelable -public class TooltipOverlayEvent extends Event -{ - /** - * If cancelled, will stop all rendering from happening. - */ - public static class Pre extends TooltipOverlayEvent - { - public Pre(ItemStack itemStack, FoodValues defaultFood, FoodValues modifiedFood) - { - super(itemStack, defaultFood, modifiedFood); - } - } - - /** - * If cancelled, will reserve space for the food values, but will not - * render them. - */ - public static class Render extends TooltipOverlayEvent - { - public Render(ItemStack itemStack, int x, int y, MatrixStack matrixStack, FoodValues defaultFood, FoodValues modifiedFood) - { - super(itemStack, defaultFood, modifiedFood); - this.matrixStack = matrixStack; - this.x = x; - this.y = y; - } - - public int x; - public int y; - public MatrixStack matrixStack; - } - - private TooltipOverlayEvent(ItemStack itemStack, FoodValues defaultFood, FoodValues modifiedFood) - { - this.itemStack = itemStack; - this.defaultFood = defaultFood; - this.modifiedFood = modifiedFood; - } - - public final FoodValues defaultFood; - public final FoodValues modifiedFood; - - public final ItemStack itemStack; - - @Override - public boolean isCancelable() - { - return true; - } -} diff --git a/java/squeek/appleskin/api/food/FoodValues.java b/java/squeek/appleskin/api/food/FoodValues.java deleted file mode 100644 index 32d408d..0000000 --- a/java/squeek/appleskin/api/food/FoodValues.java +++ /dev/null @@ -1,37 +0,0 @@ -package squeek.appleskin.api.food; - -public class FoodValues -{ - public final int hunger; - public final float saturationModifier; - - public FoodValues(int hunger, float saturationModifier) - { - this.hunger = hunger; - this.saturationModifier = saturationModifier; - } - - public float getSaturationIncrement() - { - return hunger * saturationModifier * 2f; - } - - @Override - public boolean equals(Object o) - { - if (this == o) return true; - if (!(o instanceof FoodValues)) return false; - - FoodValues that = (FoodValues) o; - - return hunger == that.hunger && Float.compare(that.saturationModifier, saturationModifier) == 0; - } - - @Override - public int hashCode() - { - int result = hunger; - result = 31 * result + (saturationModifier != +0.0f ? Float.floatToIntBits(saturationModifier) : 0); - return result; - } -} diff --git a/java/squeek/appleskin/client/DebugInfoHandler.java b/java/squeek/appleskin/client/DebugInfoHandler.java deleted file mode 100644 index b94d868..0000000 --- a/java/squeek/appleskin/client/DebugInfoHandler.java +++ /dev/null @@ -1,45 +0,0 @@ -package squeek.appleskin.client; - -import net.minecraft.client.Minecraft; -import net.minecraft.util.FoodStats; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; -import net.minecraftforge.client.event.RenderGameOverlayEvent; -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import squeek.appleskin.ModConfig; -import squeek.appleskin.helpers.HungerHelper; - -import java.text.DecimalFormat; - -@OnlyIn(Dist.CLIENT) -public class DebugInfoHandler -{ - private static final DecimalFormat saturationDF = new DecimalFormat("#.##"); - private static final DecimalFormat exhaustionValDF = new DecimalFormat("0.00"); - private static final DecimalFormat exhaustionMaxDF = new DecimalFormat("#.##"); - - public static void init() - { - MinecraftForge.EVENT_BUS.register(new DebugInfoHandler()); - } - - @SubscribeEvent - public void onTextRender(RenderGameOverlayEvent.Text textEvent) - { - if (textEvent.getType() != RenderGameOverlayEvent.ElementType.TEXT) - return; - - if (!ModConfig.SHOW_FOOD_DEBUG_INFO.get()) - return; - - Minecraft mc = Minecraft.getInstance(); - if (mc.gameSettings.showDebugInfo) - { - FoodStats stats = mc.player.getFoodStats(); - float curExhaustion = HungerHelper.getExhaustion(mc.player); - float maxExhaustion = HungerHelper.getMaxExhaustion(mc.player); - textEvent.getLeft().add("hunger: " + stats.getFoodLevel() + ", sat: " + saturationDF.format(stats.getSaturationLevel()) + ", exh: " + exhaustionValDF.format(curExhaustion) + "/" + exhaustionMaxDF.format(maxExhaustion)); - } - } -} diff --git a/java/squeek/appleskin/client/HUDOverlayHandler.java b/java/squeek/appleskin/client/HUDOverlayHandler.java index 070c015..7d3f51e 100644 --- a/java/squeek/appleskin/client/HUDOverlayHandler.java +++ b/java/squeek/appleskin/client/HUDOverlayHandler.java @@ -1,577 +1,185 @@ package squeek.appleskin.client; -import com.mojang.blaze3d.matrix.MatrixStack; +import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.systems.RenderSystem; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.AbstractGui; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.entity.effect.StatusEffects; +import net.minecraft.entity.player.HungerManager; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; -import net.minecraft.potion.Effects; -import net.minecraft.util.FoodStats; -import net.minecraft.util.ResourceLocation; -import net.minecraft.world.Difficulty; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; -import net.minecraftforge.client.event.RenderGameOverlayEvent; -import net.minecraftforge.client.gui.ForgeIngameGui; -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.event.TickEvent; -import net.minecraftforge.eventbus.api.EventPriority; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import org.lwjgl.opengl.GL11; -import squeek.appleskin.ModConfig; -import squeek.appleskin.ModInfo; -import squeek.appleskin.api.event.FoodValuesEvent; -import squeek.appleskin.api.event.HUDOverlayEvent; -import squeek.appleskin.api.food.FoodValues; +import net.minecraft.util.Identifier; import squeek.appleskin.helpers.FoodHelper; import squeek.appleskin.helpers.HungerHelper; -import squeek.appleskin.util.IntPoint; -import java.util.Random; -import java.util.Vector; - -@OnlyIn(Dist.CLIENT) public class HUDOverlayHandler { - private float unclampedFlashAlpha = 0f; - private float flashAlpha = 0f; - private byte alphaDir = 1; - protected int foodIconsOffset; - - public final Vector healthBarOffsets = new Vector<>(); - public final Vector foodBarOffsets = new Vector<>(); - - private final Random random = new Random(); + private static float flashAlpha = 0f; + private static byte alphaDir = 1; + private static int foodIconsOffset; + public static int FOOD_BAR_HEIGHT = 39; - private static final ResourceLocation modIcons = new ResourceLocation(ModInfo.MODID_LOWER, "textures/icons.png"); - - public static void init() - { - MinecraftForge.EVENT_BUS.register(new HUDOverlayHandler()); - } + private static final Identifier modIcons = new Identifier("appleskin", "textures/icons.png"); - @SubscribeEvent(priority = EventPriority.LOW) - public void onPreRender(RenderGameOverlayEvent.Pre event) + public static void onPreRender() { - if (event.getType() != RenderGameOverlayEvent.ElementType.FOOD) - return; - - foodIconsOffset = ForgeIngameGui.right_height; - - if (event.isCanceled()) - return; + foodIconsOffset = FOOD_BAR_HEIGHT; - if (!ModConfig.SHOW_FOOD_EXHAUSTION_UNDERLAY.get()) - return; - - Minecraft mc = Minecraft.getInstance(); + MinecraftClient mc = MinecraftClient.getInstance(); PlayerEntity player = mc.player; - assert player != null; - int right = mc.getMainWindow().getScaledWidth() / 2 + 91; - int top = mc.getMainWindow().getScaledHeight() - foodIconsOffset; - float exhaustion = HungerHelper.getExhaustion(player); + int left = mc.getWindow().getScaledWidth() / 2 + 91; + int top = mc.getWindow().getScaledHeight() - foodIconsOffset; - // Notify everyone that we should render exhaustion hud overlay - HUDOverlayEvent.Exhaustion renderEvent = new HUDOverlayEvent.Exhaustion(exhaustion, right, top, event.getMatrixStack()); - MinecraftForge.EVENT_BUS.post(renderEvent); - if (!renderEvent.isCanceled()) - drawExhaustionOverlay(renderEvent, mc, 1f); + drawExhaustionOverlay(HungerHelper.getExhaustion(player), mc, left, top, 1f); } - @SubscribeEvent(priority = EventPriority.LOW) - public void onRender(RenderGameOverlayEvent.Post event) + public static void onRender() { - if (event.getType() != RenderGameOverlayEvent.ElementType.FOOD && event.getType() != RenderGameOverlayEvent.ElementType.HEALTH) - return; - - if (event.isCanceled()) - return; - - if (!shouldRenderAnyOverlays()) - return; - - Minecraft mc = Minecraft.getInstance(); + MinecraftClient mc = MinecraftClient.getInstance(); PlayerEntity player = mc.player; - assert player != null; - FoodStats stats = player.getFoodStats(); - MatrixStack matrixStack = event.getMatrixStack(); - - int top = mc.getMainWindow().getScaledHeight() - foodIconsOffset; - int left = mc.getMainWindow().getScaledWidth() / 2 - 91; // left of health bar - int right = mc.getMainWindow().getScaledWidth() / 2 + 91; // right of food bar + ItemStack heldItem = player.getMainHandStack(); + HungerManager stats = player.getHungerManager(); - if (event.getType() == RenderGameOverlayEvent.ElementType.HEALTH) - generateHealthBarOffsets(top, left, right, mc.ingameGUI.getTicks(), player); - if (event.getType() == RenderGameOverlayEvent.ElementType.FOOD) - generateHungerBarOffsets(top, left, right, mc.ingameGUI.getTicks(), player); + int left = mc.getWindow().getScaledWidth() / 2 + 91; + int top = mc.getWindow().getScaledHeight() - foodIconsOffset; - HUDOverlayEvent.Saturation saturationRenderEvent = null; - if (event.getType() == RenderGameOverlayEvent.ElementType.FOOD) - { - saturationRenderEvent = new HUDOverlayEvent.Saturation(stats.getSaturationLevel(), right, top, matrixStack); - - // cancel render overlay event when configuration disabled. - if (!ModConfig.SHOW_SATURATION_OVERLAY.get()) - saturationRenderEvent.setCanceled(true); - - // notify everyone that we should render saturation hud overlay - if (!saturationRenderEvent.isCanceled()) - MinecraftForge.EVENT_BUS.post(saturationRenderEvent); - - // the render saturation event maybe cancelled by other mods - if (!saturationRenderEvent.isCanceled()) - drawSaturationOverlay(saturationRenderEvent, mc, 0, 1f); - } + // saturation overlay + drawSaturationOverlay(0, stats.getSaturationLevel(), mc, left, top, 1f); - // try to get the item stack in the player hand - ItemStack heldItem = player.getHeldItemMainhand(); - if (ModConfig.SHOW_FOOD_VALUES_OVERLAY_WHEN_OFFHAND.get() && !FoodHelper.canConsume(heldItem, player)) - heldItem = player.getHeldItemOffhand(); - - boolean shouldRenderHeldItemValues = !heldItem.isEmpty() && FoodHelper.canConsume(heldItem, player); - if (!shouldRenderHeldItemValues) + if (heldItem.isEmpty() || !FoodHelper.isFood(heldItem)) { - resetFlash(); + flashAlpha = 0; + alphaDir = 1; return; } - FoodValues modifiedFoodValues = FoodHelper.getModifiedFoodValues(heldItem, player); - FoodValuesEvent foodValuesEvent = new FoodValuesEvent(player, heldItem, FoodHelper.getDefaultFoodValues(heldItem), modifiedFoodValues); - MinecraftForge.EVENT_BUS.post(foodValuesEvent); - modifiedFoodValues = foodValuesEvent.modifiedFoodValues; - - if (event.getType() == RenderGameOverlayEvent.ElementType.HEALTH) - { - // Offsets size is set to zero intentionally to disable rendering when health is infinite. - if (healthBarOffsets.size() == 0) - return; - - if (!shouldShowEstimatedHealth(heldItem, modifiedFoodValues)) - return; - - float foodHealthIncrement = FoodHelper.getEstimatedHealthIncrement(heldItem, modifiedFoodValues, player); - float currentHealth = player.getHealth(); - float modifiedHealth = Math.min(currentHealth + foodHealthIncrement, player.getMaxHealth()); - - // only create object when the estimated health is successfully - HUDOverlayEvent.HealthRestored healthRenderEvent = null; - if (currentHealth < modifiedHealth) - healthRenderEvent = new HUDOverlayEvent.HealthRestored(modifiedHealth, heldItem, modifiedFoodValues, left, top, matrixStack); - - // notify everyone that we should render estimated health hud - if (healthRenderEvent != null) - MinecraftForge.EVENT_BUS.post(healthRenderEvent); - - if (healthRenderEvent != null && !healthRenderEvent.isCanceled()) - drawHealthOverlay(healthRenderEvent, mc, flashAlpha); - } - else if (event.getType() == RenderGameOverlayEvent.ElementType.FOOD) - { - if (!ModConfig.SHOW_FOOD_VALUES_OVERLAY.get()) - return; - - // notify everyone that we should render hunger hud overlay - HUDOverlayEvent.HungerRestored renderRenderEvent = new HUDOverlayEvent.HungerRestored(stats.getFoodLevel(), heldItem, modifiedFoodValues, right, top, matrixStack); - MinecraftForge.EVENT_BUS.post(renderRenderEvent); - if (renderRenderEvent.isCanceled()) - return; - - // calculate the final hunger and saturation - int foodHunger = modifiedFoodValues.hunger; - float foodSaturationIncrement = modifiedFoodValues.getSaturationIncrement(); - - // restored hunger/saturation overlay while holding food - drawHungerOverlay(renderRenderEvent, mc, foodHunger, flashAlpha, FoodHelper.isRotten(heldItem)); + // restored hunger/saturation overlay while holding food + FoodHelper.BasicFoodValues foodValues = FoodHelper.getModifiedFoodValues(heldItem, player); + drawHungerOverlay(foodValues.hunger, stats.getFoodLevel(), mc, left, top, flashAlpha); - // The render saturation overlay event maybe cancelled by other mods - assert saturationRenderEvent != null; - if (!saturationRenderEvent.isCanceled()) - { - int newFoodValue = stats.getFoodLevel() + foodHunger; - float newSaturationValue = stats.getSaturationLevel() + foodSaturationIncrement; - float saturationGained = newSaturationValue > newFoodValue ? newFoodValue - stats.getSaturationLevel() : foodSaturationIncrement; - // Redraw saturation overlay for gained - drawSaturationOverlay(saturationRenderEvent, mc, saturationGained, flashAlpha); - } - } + int newFoodValue = stats.getFoodLevel() + foodValues.hunger; + float newSaturationValue = stats.getSaturationLevel() + foodValues.getSaturationIncrement(); + drawSaturationOverlay(newSaturationValue > newFoodValue ? newFoodValue - stats.getSaturationLevel() : foodValues.getSaturationIncrement(), stats.getSaturationLevel(), mc, left, top, flashAlpha); } - public void drawSaturationOverlay(float saturationGained, float saturationLevel, Minecraft mc, MatrixStack matrixStack, int right, int top, float alpha) + public static void drawSaturationOverlay(float saturationGained, float saturationLevel, MinecraftClient mc, int left, int top, float alpha) { if (saturationLevel + saturationGained < 0) return; - enableAlpha(alpha); + int startBar = saturationGained != 0 ? Math.max(0, (int) saturationLevel / 2) : 0; + int endBar = (int) Math.ceil(Math.min(20, saturationLevel + saturationGained) / 2f); + int barsNeeded = endBar - startBar; mc.getTextureManager().bindTexture(modIcons); - float modifiedSaturation = Math.max(0, Math.min(saturationLevel + saturationGained, 20)); - - int startSaturationBar = 0; - int endSaturationBar = (int) Math.ceil(modifiedSaturation / 2.0F); - - // when require rendering the gained saturation, start should relocation to current saturation tail. - if (saturationGained != 0) - startSaturationBar = (int) Math.max(saturationLevel / 2.0F, 0); - - int iconSize = 9; - - for (int i = startSaturationBar; i < endSaturationBar; ++i) + enableAlpha(alpha); + for (int i = startBar; i < startBar + barsNeeded; ++i) { - // gets the offset that needs to be render of icon - IntPoint offset = foodBarOffsets.get(i); - if (offset == null) - continue; - - int x = right + offset.x; - int y = top + offset.y; - - int v = 0; - int u = 0; - - float effectiveSaturationOfBar = (modifiedSaturation / 2.0F) - i; + int x = left - i * 8 - 9; + int y = top; + float effectiveSaturationOfBar = (saturationLevel + saturationGained) / 2 - i; if (effectiveSaturationOfBar >= 1) - u = 3 * iconSize; + mc.inGameHud.drawTexture(x, y, 27, 0, 9, 9); else if (effectiveSaturationOfBar > .5) - u = 2 * iconSize; + mc.inGameHud.drawTexture(x, y, 18, 0, 9, 9); else if (effectiveSaturationOfBar > .25) - u = 1 * iconSize; - - mc.ingameGUI.blit(matrixStack, x, y, u, v, iconSize, iconSize); + mc.inGameHud.drawTexture(x, y, 9, 0, 9, 9); + else if (effectiveSaturationOfBar > 0) + mc.inGameHud.drawTexture(x, y, 0, 0, 9, 9); } + disableAlpha(alpha); // rebind default icons - mc.getTextureManager().bindTexture(AbstractGui.GUI_ICONS_LOCATION); - disableAlpha(alpha); + mc.getTextureManager().bindTexture(Screen.GUI_ICONS_TEXTURE); } - public void drawHungerOverlay(int hungerRestored, int foodLevel, Minecraft mc, MatrixStack matrixStack, int right, int top, float alpha, boolean useRottenTextures) + public static void drawHungerOverlay(int hungerRestored, int foodLevel, MinecraftClient mc, int left, int top, float alpha) { - if (hungerRestored <= 0) + if (hungerRestored == 0) return; - enableAlpha(alpha); - mc.getTextureManager().bindTexture(AbstractGui.GUI_ICONS_LOCATION); - - int modifiedFood = Math.max(0, Math.min(20, foodLevel + hungerRestored)); - - int startFoodBars = Math.max(0, foodLevel / 2); - int endFoodBars = (int) Math.ceil(modifiedFood / 2.0F); + int startBar = foodLevel / 2; + int endBar = (int) Math.ceil(Math.min(20, foodLevel + hungerRestored) / 2f); + int barsNeeded = endBar - startBar; + mc.getTextureManager().bindTexture(Screen.GUI_ICONS_TEXTURE); - int iconStartOffset = 16; - int iconSize = 9; - - for (int i = startFoodBars; i < endFoodBars; ++i) + enableAlpha(alpha); + for (int i = startBar; i < startBar + barsNeeded; ++i) { - // gets the offset that needs to be render of icon - IntPoint offset = foodBarOffsets.get(i); - if (offset == null) - continue; - - int x = right + offset.x; - int y = top + offset.y; - - // location to normal food by default - int v = 3 * iconSize; - int u = iconStartOffset + 4 * iconSize; - int ub = iconStartOffset + 1 * iconSize; + int idx = i * 2 + 1; + int x = left - i * 8 - 9; + int y = top; + int icon = 16; + int background = 13; - // relocation to rotten food - if (useRottenTextures) + if (mc.player.hasStatusEffect(StatusEffects.HUNGER)) { - u += 4 * iconSize; - ub += 12 * iconSize; + icon += 36; + background = 13; } - // relocation to half food - if (i * 2 + 1 == modifiedFood) - u += 1 * iconSize; - - // very faint background - RenderSystem.color4f(1.0F, 1.0F, 1.0F, alpha * 0.25F); - mc.ingameGUI.blit(matrixStack, x, y, ub, v, iconSize, iconSize); - RenderSystem.color4f(1.0F, 1.0F, 1.0F, alpha); - - mc.ingameGUI.blit(matrixStack, x, y, u, v, iconSize, iconSize); - } - - disableAlpha(alpha); - } - - public void drawHealthOverlay(float health, float modifiedHealth, Minecraft mc, MatrixStack matrixStack, int right, int top, float alpha) - { - if (modifiedHealth <= health) - return; - - enableAlpha(alpha); - mc.getTextureManager().bindTexture(AbstractGui.GUI_ICONS_LOCATION); - - int fixedModifiedHealth = (int) Math.ceil(modifiedHealth); - boolean isHardcore = mc.player.world != null && mc.player.world.getWorldInfo().isHardcore(); - - int startHealthBars = (int) Math.max(0, (Math.ceil(health) / 2.0F)); - int endHealthBars = (int) Math.max(0, Math.ceil(modifiedHealth / 2.0F)); + mc.inGameHud.drawTexture(x, y, 16 + background * 9, 27, 9, 9); - int iconStartOffset = 16; - int iconSize = 9; - - for (int i = startHealthBars; i < endHealthBars; ++i) - { - // gets the offset that needs to be render of icon - IntPoint offset = healthBarOffsets.get(i); - if (offset == null) - continue; - - int x = right + offset.x; - int y = top + offset.y; - - // location to full heart icon by default - int v = 0 * iconSize; - int u = iconStartOffset + 4 * iconSize; - int ub = iconStartOffset + 1 * iconSize; - - // relocation to half heart - if (i * 2 + 1 == fixedModifiedHealth) - u += 1 * iconSize; - - // relocation to special heart of hardcore - if (isHardcore) - v = 5 * iconSize; - - //// apply the status effects of the player - //if (player.hasStatusEffect(StatusEffects.POISON)) { - // u += 4 * iconSize; - //} else if (player.hasStatusEffect(StatusEffects.WITHER)) { - // u += 8 * iconSize; - //} - - // very faint background - RenderSystem.color4f(1.0F, 1.0F, 1.0F, alpha * 0.25F); - mc.ingameGUI.blit(matrixStack, x, y, ub, v, iconSize, iconSize); - RenderSystem.color4f(1.0F, 1.0F, 1.0F, alpha); - - mc.ingameGUI.blit(matrixStack, x, y, u, v, iconSize, iconSize); + if (idx < foodLevel + hungerRestored) + mc.inGameHud.drawTexture(x, y, icon + 36, 27, 9, 9); + else if (idx == foodLevel + hungerRestored) + mc.inGameHud.drawTexture(x, y, icon + 45, 27, 9, 9); } - disableAlpha(alpha); } - public void drawExhaustionOverlay(float exhaustion, Minecraft mc, MatrixStack matrixStack, int right, int top, float alpha) + public static void drawExhaustionOverlay(float exhaustion, MinecraftClient mc, int left, int top, float alpha) { mc.getTextureManager().bindTexture(modIcons); float maxExhaustion = HungerHelper.getMaxExhaustion(mc.player); - // clamp between 0 and 1 - float ratio = Math.min(1, Math.max(0, exhaustion / maxExhaustion)); + float ratio = exhaustion / maxExhaustion; int width = (int) (ratio * 81); int height = 9; enableAlpha(.75f); - mc.ingameGUI.blit(matrixStack, right - width, top, 81 - width, 18, width, height); + mc.inGameHud.drawTexture(left - width, top, 81 - width, 18, width, height); disableAlpha(.75f); // rebind default icons - mc.getTextureManager().bindTexture(AbstractGui.GUI_ICONS_LOCATION); + mc.getTextureManager().bindTexture(Screen.GUI_ICONS_TEXTURE); } - - public static void enableAlpha(float alpha) + private static void enableAlpha(float alpha) { RenderSystem.enableBlend(); - RenderSystem.color4f(1.0F, 1.0F, 1.0F, alpha); - RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); - } - public static void disableAlpha(float alpha) - { - RenderSystem.disableBlend(); - RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F); - } - - @SubscribeEvent - public void onClientTick(TickEvent.ClientTickEvent event) - { - if (event.phase != TickEvent.Phase.END) + if (alpha == 1f) return; - unclampedFlashAlpha += alphaDir * 0.125f; - if (unclampedFlashAlpha >= 1.5f) - { - alphaDir = -1; - } - else if (unclampedFlashAlpha <= -0.5f) - { - alphaDir = 1; - } - flashAlpha = Math.max(0F, Math.min(1F, unclampedFlashAlpha)) * Math.max(0F, Math.min(1F, ModConfig.MAX_HUD_OVERLAY_FLASH_ALPHA.get().floatValue())); - } - - public void resetFlash() - { - unclampedFlashAlpha = flashAlpha = 0f; - alphaDir = 1; - } - - private void drawSaturationOverlay(HUDOverlayEvent.Saturation event, Minecraft mc, float saturationGained, float alpha) - { - drawSaturationOverlay(saturationGained, event.saturationLevel, mc, event.matrixStack, event.x, event.y, alpha); - } - - private void drawHungerOverlay(HUDOverlayEvent.HungerRestored event, Minecraft mc, int hunger, float alpha, boolean useRottenTextures) - { - drawHungerOverlay(hunger, event.currentFoodLevel, mc, event.matrixStack, event.x, event.y, alpha, useRottenTextures); - } - - private void drawHealthOverlay(HUDOverlayEvent.HealthRestored event, Minecraft mc, float alpha) - { - drawHealthOverlay(mc.player.getHealth(), event.modifiedHealth, mc, event.matrixStack, event.x, event.y, alpha); - } - - private void drawExhaustionOverlay(HUDOverlayEvent.Exhaustion event, Minecraft mc, float alpha) - { - drawExhaustionOverlay(event.exhaustion, mc, event.matrixStack, event.x, event.y, alpha); - } - - private boolean shouldRenderAnyOverlays() - { - return ModConfig.SHOW_FOOD_VALUES_OVERLAY.get() || ModConfig.SHOW_SATURATION_OVERLAY.get() || ModConfig.SHOW_FOOD_HEALTH_HUD_OVERLAY.get(); + RenderSystem.color4f(1.0F, 1.0F, 1.0F, alpha); + RenderSystem.blendFunc(GlStateManager.SrcFactor.SRC_ALPHA, GlStateManager.DstFactor.ONE_MINUS_SRC_ALPHA); } - private boolean shouldShowEstimatedHealth(ItemStack hoveredStack, FoodValues modifiedFoodValues) + private static void disableAlpha(float alpha) { - // then configuration cancel the render event - if (!ModConfig.SHOW_FOOD_HEALTH_HUD_OVERLAY.get()) - return false; - - Minecraft mc = Minecraft.getInstance(); - PlayerEntity player = mc.player; - FoodStats stats = player.getFoodStats(); - - // in the `PEACEFUL` mode, health will restore faster - if (player.world.getDifficulty() == Difficulty.PEACEFUL) - return false; - - // when player has any changes health amount by any case can't show estimated health - // because player will confused how much of restored/damaged healths - if (stats.getFoodLevel() >= 18) - return false; - - if (player.isPotionActive(Effects.POISON)) - return false; - - if (player.isPotionActive(Effects.WITHER)) - return false; - - if (player.isPotionActive(Effects.REGENERATION)) - return false; - - return true; - } + RenderSystem.disableBlend(); - private void generateHealthBarOffsets(int top, int left, int right, int ticks, PlayerEntity player) - { - // hard code in `InGameHUD` - random.setSeed((long) (ticks * 312871L)); - - final int preferHealthBars = 10; - final float maxHealth = player.getMaxHealth(); - final float absorptionHealth = (float) Math.ceil(player.getAbsorptionAmount()); - - int healthBars = (int) Math.ceil((maxHealth + absorptionHealth) / 2.0F); - // When maxHealth + absorptionHealth is greater than Integer.INT_MAX, - // Minecraft will disable heart rendering due to a quirk of MathHelper.ceil. - // We have a much lower threshold since there's no reason to get the offsets - // for thousands of hearts. - // Note: Infinite and > INT_MAX absorption has been seen in the wild. - // This will effectively disable rendering whenever health is unexpectedly large. - if (healthBars < 0 || healthBars > 1000) { - healthBarOffsets.setSize(0); + if (alpha == 1f) return; - } - - int healthRows = (int) Math.ceil((float) healthBars / 10.0F); - - int healthRowHeight = Math.max(10 - (healthRows - 2), 3); - boolean shouldAnimatedHealth = false; - - // when some mods using custom render, we need to least provide an option to cancel animation - if (ModConfig.SHOW_VANILLA_ANIMATION_OVERLAY.get()) - { - // in vanilla health is too low (below 5) will show heartbeat animation - // when regeneration will also show heartbeat animation, but we don't need now - shouldAnimatedHealth = Math.ceil(player.getHealth()) <= 4; - } - - // adjust the size - if (healthBarOffsets.size() != healthBars) - healthBarOffsets.setSize(healthBars); - - // left alignment, multiple rows, reverse - for (int i = healthBars - 1; i >= 0; --i) - { - int row = (int) Math.ceil((float) (i + 1) / (float) preferHealthBars) - 1; - int x = left + i % preferHealthBars * 8; - int y = top - row * healthRowHeight; - // apply the animated offset - if (shouldAnimatedHealth) - y += random.nextInt(2); - - // reuse the point object to reduce memory usage - IntPoint point = healthBarOffsets.get(i); - if (point == null) - { - point = new IntPoint(); - healthBarOffsets.set(i, point); - } - - point.x = x - left; - point.y = y - top; - } + RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F); } - private void generateHungerBarOffsets(int top, int left, int right, int ticks, PlayerEntity player) + public static void onClientTick() { - final int preferFoodBars = 10; - - boolean shouldAnimatedFood = false; - - // when some mods using custom render, we need to least provide an option to cancel animation - if (ModConfig.SHOW_VANILLA_ANIMATION_OVERLAY.get()) + flashAlpha += alphaDir * 0.125f; + if (flashAlpha >= 1.5f) { - FoodStats stats = player.getFoodStats(); - - // in vanilla saturation level is zero will show hunger animation - float saturationLevel = stats.getSaturationLevel(); - int foodLevel = stats.getFoodLevel(); - shouldAnimatedFood = saturationLevel <= 0.0F && ticks % (foodLevel * 3 + 1) == 0; + flashAlpha = 1f; + alphaDir = -1; } - - if (foodBarOffsets.size() != preferFoodBars) - foodBarOffsets.setSize(preferFoodBars); - - // right alignment, single row - for (int i = 0; i < preferFoodBars; ++i) + else if (flashAlpha <= -0.5f) { - int x = right - i * 8 - 9; - int y = top; - - // apply the animated offset - if (shouldAnimatedFood) - y += random.nextInt(3) - 1; - - // reuse the point object to reduce memory usage - IntPoint point = foodBarOffsets.get(i); - if (point == null) - { - point = new IntPoint(); - foodBarOffsets.set(i, point); - } - - point.x = x - right; - point.y = y - top; + flashAlpha = 0f; + alphaDir = 1; } } } diff --git a/java/squeek/appleskin/client/TooltipOverlayHandler.java b/java/squeek/appleskin/client/TooltipOverlayHandler.java index d3f92fb..b26ae4a 100644 --- a/java/squeek/appleskin/client/TooltipOverlayHandler.java +++ b/java/squeek/appleskin/client/TooltipOverlayHandler.java @@ -1,410 +1,153 @@ package squeek.appleskin.client; -import com.mojang.blaze3d.matrix.MatrixStack; +import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.systems.RenderSystem; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.AbstractGui; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.renderer.RenderHelper; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.item.ItemStack; -import net.minecraft.util.ResourceLocation; -import net.minecraft.util.text.ITextComponent; -import net.minecraft.util.text.ITextProperties; -import net.minecraft.util.text.StringTextComponent; -import net.minecraft.util.text.Style; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; -import net.minecraftforge.client.event.RenderTooltipEvent; -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.event.entity.player.ItemTooltipEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import squeek.appleskin.ModConfig; -import squeek.appleskin.ModInfo; -import squeek.appleskin.api.event.FoodValuesEvent; -import squeek.appleskin.api.event.TooltipOverlayEvent; -import squeek.appleskin.api.food.FoodValues; +import net.minecraft.util.Identifier; import squeek.appleskin.helpers.FoodHelper; -import squeek.appleskin.helpers.KeyHelper; -import java.util.List; -import java.util.Optional; - -@OnlyIn(Dist.CLIENT) public class TooltipOverlayHandler { - private static ResourceLocation modIcons = new ResourceLocation(ModInfo.MODID_LOWER, "textures/icons.png"); + private static Identifier modIcons = new Identifier("appleskin", "textures/icons.png"); public static final int TOOLTIP_REAL_HEIGHT_OFFSET_BOTTOM = 3; public static final int TOOLTIP_REAL_HEIGHT_OFFSET_TOP = -3; public static final int TOOLTIP_REAL_WIDTH_OFFSET_RIGHT = 3; - public static void init() - { - MinecraftForge.EVENT_BUS.register(new TooltipOverlayHandler()); - } - - private static final TextureOffsets normalBarTextureOffsets = new TextureOffsets(); - - static - { - normalBarTextureOffsets.containerNegativeHunger = 43; - normalBarTextureOffsets.containerExtraHunger = 133; - normalBarTextureOffsets.containerNormalHunger = 16; - normalBarTextureOffsets.containerPartialHunger = 124; - normalBarTextureOffsets.containerMissingHunger = 34; - normalBarTextureOffsets.shankMissingFull = 70; - normalBarTextureOffsets.shankMissingPartial = normalBarTextureOffsets.shankMissingFull + 9; - normalBarTextureOffsets.shankFull = 52; - normalBarTextureOffsets.shankPartial = normalBarTextureOffsets.shankFull + 9; - } - - private static final TextureOffsets rottenBarTextureOffsets = new TextureOffsets(); - - static - { - rottenBarTextureOffsets.containerNegativeHunger = normalBarTextureOffsets.containerNegativeHunger; - rottenBarTextureOffsets.containerExtraHunger = normalBarTextureOffsets.containerExtraHunger; - rottenBarTextureOffsets.containerNormalHunger = normalBarTextureOffsets.containerNormalHunger; - rottenBarTextureOffsets.containerPartialHunger = normalBarTextureOffsets.containerPartialHunger; - rottenBarTextureOffsets.containerMissingHunger = normalBarTextureOffsets.containerMissingHunger; - rottenBarTextureOffsets.shankMissingFull = 106; - rottenBarTextureOffsets.shankMissingPartial = rottenBarTextureOffsets.shankMissingFull + 9; - rottenBarTextureOffsets.shankFull = 88; - rottenBarTextureOffsets.shankPartial = rottenBarTextureOffsets.shankFull + 9; - } - - static class TextureOffsets - { - int containerNegativeHunger; - int containerExtraHunger; - int containerNormalHunger; - int containerPartialHunger; - int containerMissingHunger; - int shankMissingFull; - int shankMissingPartial; - int shankFull; - int shankPartial; - } - - // We set a special font into placeholder text, so font will bind a food overlay, - // When the placeholder text is convert or truncation, user will restore the style. - static class FoodOverlayFont extends ResourceLocation - { - private FoodOverlay foodOverlay; - FoodOverlayFont(FoodOverlay foodOverlay) - { - super(Style.DEFAULT_FONT.getNamespace(), Style.DEFAULT_FONT.getPath()); - this.foodOverlay = foodOverlay; - } - - static Object getFontId(ITextProperties line) - { - // A fast path, however not all lines conform `ITextComponent`. - if (line instanceof ITextComponent) - return ((ITextComponent)line).getStyle().getFontId(); - - // A slow path, only to check frist string. - final Object[] fontId = { Style.DEFAULT_FONT }; - line.getComponentWithStyle(new ITextProperties.IStyledTextAcceptor() { - public Optional accept(Style n, String s) { - fontId[0] = n.getFontId(); - return Optional.empty(); - } - }, Style.EMPTY); - return fontId[0]; - } - - static FoodOverlay getFoodOverlay(ITextProperties line) - { - Object fontId = getFontId(line); - if (fontId instanceof FoodOverlayFont) - return ((FoodOverlayFont) fontId).foodOverlay; - return null; - } - } - - static class FoodOverlay - { - private FoodValues defaultFood; - private FoodValues modifiedFood; - - private int biggestHunger; - private float biggestSaturationIncrement; - - private int hungerBars; - private String hungerBarsText; - - private int saturationBars; - private String saturationBarsText; - - private String tooltip; - - private ItemStack itemStack; - - FoodOverlay(ItemStack itemStack, FoodValues defaultFood, FoodValues modifiedFood, PlayerEntity player) - { - this.itemStack = itemStack; - this.defaultFood = defaultFood; - this.modifiedFood = modifiedFood; - - biggestHunger = Math.max(defaultFood.hunger, modifiedFood.hunger); - biggestSaturationIncrement = Math.max(defaultFood.getSaturationIncrement(), modifiedFood.getSaturationIncrement()); - - hungerBars = (int) Math.ceil(Math.abs(biggestHunger) / 2f); - if (hungerBars > 10) - { - hungerBarsText = "x" + ((biggestHunger < 0 ? -1 : 1) * hungerBars); - hungerBars = 1; - } - - saturationBars = (int) Math.ceil(Math.abs(biggestSaturationIncrement) / 2f); - if (saturationBars > 10 || saturationBars == 0) - { - saturationBarsText = "x" + ((biggestSaturationIncrement < 0 ? -1 : 1) * saturationBars); - saturationBars = 1; - } - } - - String getTooltip() - { - if (tooltip != null) - { - return tooltip; - } - // 9x9 icon convert to scale of blank string. - float scale = 2.2f; - - float hungerBarsLength = (float) hungerBars * scale; - if (hungerBarsText != null) - { - hungerBarsLength += hungerBarsText.length(); - } - - float saturationBarsLength = (float) saturationBars * scale; - if (saturationBarsText != null) - { - saturationBarsLength += saturationBarsText.length(); - } - - int length = (int) Math.ceil(Math.max(hungerBarsLength, saturationBarsLength * 0.8f)); - StringBuilder s = new StringBuilder(" "); - for (int i = 0; i < length; i++) - { - s.append(" "); - } - - tooltip = s.toString(); - return tooltip; - } - - boolean shouldRenderHungerBars() - { - return hungerBars > 0; - } - } - - @SubscribeEvent - public void onItemTooltip(ItemTooltipEvent event) + public static void onRenderTooltip(ItemStack hoveredStack, int toolTipX, int toolTipY, int toolTipW, int toolTipH) { - if (event.isCanceled()) + if (hoveredStack == null || hoveredStack.isEmpty()) return; - ItemStack hoveredStack = event.getItemStack(); - if (!shouldShowTooltip(hoveredStack)) - return; + MinecraftClient mc = MinecraftClient.getInstance(); + Screen gui = mc.currentScreen; - List tooltip = event.getToolTip(); - if (tooltip == null) + if (gui == null) return; - Minecraft mc = Minecraft.getInstance(); - FoodValues defaultFood = FoodHelper.getDefaultFoodValues(hoveredStack); - FoodValues modifiedFood = FoodHelper.getModifiedFoodValues(hoveredStack, mc.player); - - FoodValuesEvent foodValuesEvent = new FoodValuesEvent(mc.player, hoveredStack, defaultFood, modifiedFood); - MinecraftForge.EVENT_BUS.post(foodValuesEvent); - defaultFood = foodValuesEvent.defaultFoodValues; - modifiedFood = foodValuesEvent.modifiedFoodValues; - - // Notify everyone that we should render tooltip overlay - TooltipOverlayEvent.Pre prerenderEvent = new TooltipOverlayEvent.Pre(hoveredStack, defaultFood, modifiedFood); - MinecraftForge.EVENT_BUS.post(prerenderEvent); - if (prerenderEvent.isCanceled()) + if (!FoodHelper.isFood(hoveredStack)) return; - FoodOverlay foodOverlay = new FoodOverlay(prerenderEvent.itemStack, defaultFood, modifiedFood, mc.player); - if (foodOverlay.shouldRenderHungerBars()) - { - Style style = Style.EMPTY.setFontId(new FoodOverlayFont(foodOverlay)); - StringTextComponent placeholder = new StringTextComponent(foodOverlay.getTooltip()); - tooltip.add(placeholder.setStyle(style)); - tooltip.add(placeholder.setStyle(style)); - } - } + PlayerEntity player = mc.player; - @SubscribeEvent - public void onRenderTooltip(RenderTooltipEvent.PostText event) - { - if (event.isCanceled()) - return; + FoodHelper.BasicFoodValues defaultFoodValues = FoodHelper.getDefaultFoodValues(hoveredStack); + FoodHelper.BasicFoodValues modifiedFoodValues = FoodHelper.getModifiedFoodValues(hoveredStack, player); - Minecraft mc = Minecraft.getInstance(); - Screen gui = mc.currentScreen; - if (gui == null) + if (defaultFoodValues.equals(modifiedFoodValues) && defaultFoodValues.hunger == 0) return; - int toolTipY = event.getY(); - int toolTipX = event.getX(); - int toolTipZ = 400; // tooltip text zLevel is 400, hardcode in GuiUtils. + int biggestHunger = Math.max(defaultFoodValues.hunger, modifiedFoodValues.hunger); + float biggestSaturationIncrement = Math.max(defaultFoodValues.getSaturationIncrement(), modifiedFoodValues.getSaturationIncrement()); - // Find food overlay of text lines. - FoodOverlay foodOverlay = null; - List lines = event.getLines(); - for (int i = 0; i < lines.size(); ++i) - { - foodOverlay = FoodOverlayFont.getFoodOverlay(lines.get(i)); - if (foodOverlay != null) - { - toolTipY += i * 10; - break; - } - } + int barsNeeded = (int) Math.ceil(Math.abs(biggestHunger) / 2f); + boolean hungerOverflow = barsNeeded > 10; + String hungerText = hungerOverflow ? ((biggestHunger < 0 ? -1 : 1) * barsNeeded) + "x " : null; + if (hungerOverflow) + barsNeeded = 1; - // Not found overlay text lines, maybe some mods removed it. - if (foodOverlay == null) - return; + int saturationBarsNeeded = (int) Math.max(1, Math.ceil(Math.abs(biggestSaturationIncrement) / 2f)); + boolean saturationOverflow = saturationBarsNeeded > 10; + String saturationText = saturationOverflow ? ((biggestSaturationIncrement < 0 ? -1 : 1) * saturationBarsNeeded) + "x " : null; + if (saturationOverflow) + saturationBarsNeeded = 1; - MatrixStack matrixStack = event.getMatrixStack(); - ItemStack itemStack = foodOverlay.itemStack; - FoodValues defaultFood = foodOverlay.defaultFood; - FoodValues modifiedFood = foodOverlay.modifiedFood; + int toolTipBottomY = toolTipY + toolTipH + 1 + TOOLTIP_REAL_HEIGHT_OFFSET_BOTTOM; + int toolTipRightX = toolTipX + toolTipW + 1 + TOOLTIP_REAL_WIDTH_OFFSET_RIGHT; - // Notify everyone that we should render tooltip overlay - TooltipOverlayEvent.Render renderEvent = new TooltipOverlayEvent.Render(itemStack, toolTipX, toolTipY, matrixStack, defaultFood, modifiedFood); - MinecraftForge.EVENT_BUS.post(renderEvent); - if (renderEvent.isCanceled()) - return; + boolean shouldDrawBelow = toolTipBottomY + 20 < mc.getWindow().getScaledHeight() - 3; - toolTipX = renderEvent.x; - toolTipY = renderEvent.y; - matrixStack = renderEvent.matrixStack; + int rightX = toolTipRightX - 3; + int leftX = rightX - (Math.max(barsNeeded * 9 + (int) (mc.textRenderer.getStringWidth(hungerText) * 0.75f), saturationBarsNeeded * 6 + (int) (mc.textRenderer.getStringWidth(saturationText) * 0.75f))) - 3; + int topY = (shouldDrawBelow ? toolTipBottomY : toolTipY - 20 + TOOLTIP_REAL_HEIGHT_OFFSET_TOP); + int bottomY = topY + 19; RenderSystem.disableLighting(); RenderSystem.disableDepthTest(); - RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - matrixStack.push(); - matrixStack.translate(0.0D, 0.0D, toolTipZ); + // bg + Screen.fill(leftX - 1, topY, rightX + 1, bottomY, 0xF0100010); + Screen.fill(leftX, (shouldDrawBelow ? bottomY : topY - 1), rightX, (shouldDrawBelow ? bottomY + 1 : topY), 0xF0100010); + Screen.fill(leftX, topY, rightX, bottomY, 0x66FFFFFF); - int x = toolTipX; - int y = toolTipY + 2; + // fill disables blending and modifies color, so reset them + RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F); + RenderSystem.enableBlend(); + RenderSystem.blendFunc(GlStateManager.SrcFactor.SRC_ALPHA, GlStateManager.DstFactor.ONE_MINUS_SRC_ALPHA); - int defaultHunger = defaultFood.hunger; - int modifiedHunger = modifiedFood.hunger; + int x = rightX - 2; + int startX = x; + int y = bottomY - 18; - // Render from right to left so that the icons 'face' the right way - x += (foodOverlay.hungerBars - 1) * 9; + mc.getTextureManager().bindTexture(Screen.GUI_ICONS_TEXTURE); - mc.getTextureManager().bindTexture(AbstractGui.GUI_ICONS_LOCATION); - TextureOffsets offsets = FoodHelper.isRotten(itemStack) ? rottenBarTextureOffsets : normalBarTextureOffsets; - for (int i = 0; i < foodOverlay.hungerBars * 2; i += 2) + for (int i = 0; i < barsNeeded * 2; i += 2) { + x -= 9; - if (modifiedHunger < 0) - gui.blit(matrixStack, x, y, offsets.containerNegativeHunger, 27, 9, 9); - else if (modifiedHunger > defaultHunger && defaultHunger <= i) - gui.blit(matrixStack, x, y, offsets.containerExtraHunger, 27, 9, 9); - else if (modifiedHunger > i + 1 || defaultHunger == modifiedHunger) - gui.blit(matrixStack, x, y, offsets.containerNormalHunger, 27, 9, 9); - else if (modifiedHunger == i + 1) - gui.blit(matrixStack, x, y, offsets.containerPartialHunger, 27, 9, 9); + if (modifiedFoodValues.hunger < 0) + gui.drawTexture(x, y, 34, 27, 9, 9); + else if (modifiedFoodValues.hunger > defaultFoodValues.hunger && defaultFoodValues.hunger <= i) + gui.drawTexture(x, y, 133, 27, 9, 9); + else if (modifiedFoodValues.hunger > i + 1 || defaultFoodValues.hunger == modifiedFoodValues.hunger) + gui.drawTexture(x, y, 16, 27, 9, 9); + else if (modifiedFoodValues.hunger == i + 1) + gui.drawTexture(x, y, 124, 27, 9, 9); else - { - RenderSystem.color4f(1.0F, 1.0F, 1.0F, .5F); - gui.blit(matrixStack, x, y, offsets.containerMissingHunger, 27, 9, 9); - RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F); - } + gui.drawTexture(x, y, 34, 27, 9, 9); RenderSystem.color4f(1.0F, 1.0F, 1.0F, .25F); - gui.blit(matrixStack, x, y, defaultHunger - 1 == i ? offsets.shankMissingPartial : offsets.shankMissingFull, 27, 9, 9); + gui.drawTexture(x, y, defaultFoodValues.hunger - 1 == i ? 115 : 106, 27, 9, 9); RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F); - if (modifiedHunger > i) - gui.blit(matrixStack, x, y, modifiedHunger - 1 == i ? offsets.shankPartial : offsets.shankFull, 27, 9, 9); - - x -= 9; + if (modifiedFoodValues.hunger > i) + gui.drawTexture(x, y, modifiedFoodValues.hunger - 1 == i ? 61 : 52, 27, 9, 9); } - if (foodOverlay.hungerBarsText != null) + if (hungerText != null) { - x += 18; - matrixStack.push(); - matrixStack.translate(x, y, 0); - matrixStack.scale(0.75f, 0.75f, 0.75f); - mc.fontRenderer.func_238406_a_(matrixStack, foodOverlay.hungerBarsText, 2, 2, 0xFFAAAAAA, false); - matrixStack.pop(); + RenderSystem.pushMatrix(); + RenderSystem.scalef(0.75F, 0.75F, 0.75F); + mc.textRenderer.drawWithShadow(hungerText, x * 4 / 3 - mc.textRenderer.getStringWidth(hungerText) + 2, y * 4 / 3 + 2, 0xFFDDDDDD); + RenderSystem.popMatrix(); } - x = toolTipX; y += 10; - - float modifiedSaturationIncrement = modifiedFood.getSaturationIncrement(); + x = startX; + float modifiedSaturationIncrement = modifiedFoodValues.getSaturationIncrement(); float absModifiedSaturationIncrement = Math.abs(modifiedSaturationIncrement); - // Render from right to left so that the icons 'face' the right way - x += (foodOverlay.saturationBars - 1) * 7; - RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F); mc.getTextureManager().bindTexture(modIcons); - for (int i = 0; i < foodOverlay.saturationBars * 2; i += 2) + for (int i = 0; i < saturationBarsNeeded * 2; i += 2) { float effectiveSaturationOfBar = (absModifiedSaturationIncrement - i) / 2f; + x -= 6; + boolean shouldBeFaded = absModifiedSaturationIncrement <= i; if (shouldBeFaded) RenderSystem.color4f(1.0F, 1.0F, 1.0F, .5F); - gui.blit(matrixStack, x, y, effectiveSaturationOfBar >= 1 ? 21 : effectiveSaturationOfBar > 0.5 ? 14 : effectiveSaturationOfBar > 0.25 ? 7 : effectiveSaturationOfBar > 0 ? 0 : 28, modifiedSaturationIncrement >= 0 ? 27 : 34, 7, 7); + gui.drawTexture(x, y, effectiveSaturationOfBar >= 1 ? 21 : effectiveSaturationOfBar > 0.5 ? 14 : effectiveSaturationOfBar > 0.25 ? 7 : effectiveSaturationOfBar > 0 ? 0 : 28, modifiedSaturationIncrement >= 0 ? 27 : 34, 7, 7); if (shouldBeFaded) RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F); - - x -= 7; } - if (foodOverlay.saturationBarsText != null) + if (saturationText != null) { - x += 14; - matrixStack.push(); - matrixStack.translate(x, y, 0); - matrixStack.scale(0.75f, 0.75f, 0.75f); - mc.fontRenderer.func_238406_a_(matrixStack, foodOverlay.saturationBarsText, 2, 1, 0xFFAAAAAA, false); - matrixStack.pop(); + RenderSystem.pushMatrix(); + RenderSystem.scalef(0.75F, 0.75F, 0.75F); + mc.textRenderer.drawWithShadow(saturationText, x * 4 / 3 - mc.textRenderer.getStringWidth(saturationText) + 2, y * 4 / 3 + 1, 0xFFDDDDDD); + RenderSystem.popMatrix(); } - matrixStack.pop(); - RenderSystem.disableBlend(); RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F); // reset to drawHoveringText state RenderSystem.disableRescaleNormal(); - RenderHelper.disableStandardItemLighting(); RenderSystem.disableLighting(); RenderSystem.disableDepthTest(); } - - private boolean shouldShowTooltip(ItemStack hoveredStack) - { - if (hoveredStack.isEmpty()) - return false; - - boolean shouldShowTooltip = (ModConfig.SHOW_FOOD_VALUES_IN_TOOLTIP.get() && KeyHelper.isShiftKeyDown()) || ModConfig.ALWAYS_SHOW_FOOD_VALUES_TOOLTIP.get(); - if (!shouldShowTooltip) - return false; - - if (!FoodHelper.isFood(hoveredStack)) - return false; - - return true; - } } diff --git a/java/squeek/appleskin/helpers/DynamicFood.java b/java/squeek/appleskin/helpers/DynamicFood.java new file mode 100644 index 0000000..cb46642 --- /dev/null +++ b/java/squeek/appleskin/helpers/DynamicFood.java @@ -0,0 +1,24 @@ +package squeek.appleskin.helpers; + +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; + +/** + * An interface for foods whose hunger/saturation restoration can vary by the ItemStack. + */ +public interface DynamicFood +{ + /** + * @param stack the stack to check + * @param player the player to check + * @return how much hunger an instance of this food will restore in total, including base restoration. + */ + int getDynamicHunger(ItemStack stack, PlayerEntity player); + + /** + * @param stack the stack to check + * @param player the player to check + * @return how much saturation an instance of this food will restore in total, including base restoration.. + */ + float getDynamicSaturation(ItemStack stack, PlayerEntity player); +} diff --git a/java/squeek/appleskin/helpers/FoodHelper.java b/java/squeek/appleskin/helpers/FoodHelper.java index 28a56c1..9c94187 100644 --- a/java/squeek/appleskin/helpers/FoodHelper.java +++ b/java/squeek/appleskin/helpers/FoodHelper.java @@ -1,173 +1,68 @@ package squeek.appleskin.helpers; -import com.mojang.datafixers.util.Pair; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.Food; +import net.minecraft.item.FoodComponent; import net.minecraft.item.ItemStack; -import net.minecraft.potion.EffectInstance; -import net.minecraft.potion.EffectType; -import net.minecraft.potion.Effects; -import net.minecraft.util.FoodStats; -import net.minecraft.world.GameRules; -import net.minecraft.world.World; -import squeek.appleskin.api.food.FoodValues; public class FoodHelper { - public static boolean isFood(ItemStack itemStack) - { - return itemStack.getItem().getFood() != null; - } - - public static boolean canConsume(ItemStack itemStack, PlayerEntity player) - { - // item is not a food that can be consume - if (!isFood(itemStack)) - return false; - - Food itemFood = itemStack.getItem().getFood(); - if (itemFood == null) - return false; - - return player.canEat(itemFood.canEatWhenFull()); - } - - public static FoodValues getDefaultFoodValues(ItemStack itemStack) + public static class BasicFoodValues { - Food itemFood = itemStack.getItem().getFood(); - int hunger = itemFood != null ? itemFood.getHealing() : 0; - float saturationModifier = itemFood != null ? itemFood.getSaturation() : 0; + public final int hunger; + public final float saturationModifier; - return new FoodValues(hunger, saturationModifier); - } - - public static FoodValues getModifiedFoodValues(ItemStack itemStack, PlayerEntity player) - { - // Previously, this would use AppleCore to get the modified values, but since AppleCore doesn't - // exist on this MC version and https://github.com/MinecraftForge/MinecraftForge/pull/7266 - // hasn't been merged, we just return the defaults here. - return getDefaultFoodValues(itemStack); - } - - public static boolean isRotten(ItemStack itemStack) - { - if (!isFood(itemStack)) - return false; - - for (Pair effect : itemStack.getItem().getFood().getEffects()) + public BasicFoodValues(int hunger, float saturationModifier) { - if (effect.getFirst() != null && effect.getFirst().getPotion() != null && effect.getFirst().getPotion().getEffectType() == EffectType.HARMFUL) - { - return true; - } + this.hunger = hunger; + this.saturationModifier = saturationModifier; } - return false; - } - public static float getEstimatedHealthIncrement(ItemStack itemStack, FoodValues modifiedFoodValues, PlayerEntity player) - { - if (!isFood(itemStack)) - return 0; - - if (!player.shouldHeal()) - return 0; - - FoodStats stats = player.getFoodStats(); - World world = player.getEntityWorld(); - - int foodLevel = Math.min(stats.getFoodLevel() + modifiedFoodValues.hunger, 20); - float healthIncrement = 0; - - // health for natural regen - if (foodLevel >= 18.0F && world != null && world.getGameRules().getBoolean(GameRules.NATURAL_REGENERATION)) + public float getSaturationIncrement() { - float saturationLevel = Math.min(stats.getSaturationLevel() + modifiedFoodValues.getSaturationIncrement(), (float) foodLevel); - float exhaustionLevel = HungerHelper.getExhaustion(player); - healthIncrement = getEstimatedHealthIncrement(foodLevel, saturationLevel, exhaustionLevel); + return hunger * saturationModifier * 2f; } - // health for regeneration effect - for (Pair effect : itemStack.getItem().getFood().getEffects()) + @Override + public boolean equals(Object o) { - EffectInstance effectInstance = effect.getFirst(); - if (effectInstance != null && effectInstance.getPotion() == Effects.REGENERATION) - { - int amplifier = effectInstance.getAmplifier(); - int duration = effectInstance.getDuration(); + if (this == o) return true; + if (!(o instanceof BasicFoodValues)) return false; - // Refer: https://minecraft.fandom.com/wiki/Regeneration - // Refer: net.minecraft.world.effect.MobEffect.isDurationEffectTick - healthIncrement += (float) Math.floor(duration / Math.max(50 >> amplifier, 1)); - break; - } + BasicFoodValues that = (BasicFoodValues) o; + + return hunger == that.hunger && Float.compare(that.saturationModifier, saturationModifier) == 0; } - return healthIncrement; + @Override + public int hashCode() + { + int result = hunger; + result = 31 * result + (saturationModifier != +0.0f ? Float.floatToIntBits(saturationModifier) : 0); + return result; + } } - public static float REGEN_EXHAUSTION_INCREMENT = 6.0F; - public static float MAX_EXHAUSTION = 4.0F; - - public static float getEstimatedHealthIncrement(int foodLevel, float saturationLevel, float exhaustionLevel) + public static boolean isFood(ItemStack itemStack) { - float health = 0; - - if (!Float.isFinite(exhaustionLevel) || !Float.isFinite(saturationLevel)) - return 0; + return itemStack.getItem().isFood(); + } - while (foodLevel >= 18) - { - while (exhaustionLevel > MAX_EXHAUSTION) - { - exhaustionLevel -= MAX_EXHAUSTION; - if (saturationLevel > 0) - saturationLevel = Math.max(saturationLevel - 1, 0); - else - foodLevel -= 1; - } - // Without this Float.compare, it's possible for this function to get stuck in an infinite loop - // if saturationLevel is small enough that exhaustionLevel does not actually change representation - // when it's incremented. This Float.compare makes it so we treat such close-to-zero values as zero. - if (foodLevel >= 20 && Float.compare(saturationLevel, Float.MIN_NORMAL) > 0) - { - // fast regen health - // - // Because only health and exhaustionLevel increase in this branch, - // we know that we will enter this branch again and again on each iteration - // if exhaustionLevel is not incremented above MAX_EXHAUSTION before the - // next iteration. - // - // So, instead of actually performing those iterations, we can calculate - // the number of iterations it would take to reach max exhaustion, and - // add all the health/exhaustion in one go. In practice, this takes the - // worst-case number of iterations performed in this function from the millions - // all the way down to around 18. - // - // Note: Due to how floating point works, the results of actually doing the - // iterations and 'simulating' them using multiplication will differ. That is, small increments - // in a loop can end up with a different (and higher) final result than multiplication - // due to floating point rounding. In degenerate cases, the difference can be fairly high - // (when testing, I found a case that had a difference of ~0.3), but this isn't a concern in - // this particular instance because the 'real' difference as seen by the player - // would likely take hundreds of thousands of ticks to materialize (since the - // `limitedSaturationLevel / REGEN_EXHAUSTION_INCREMENT` value must be very - // small for a difference to occur at all, and therefore numIterationsUntilAboveMax would - // be very large). - float limitedSaturationLevel = Math.min(saturationLevel, REGEN_EXHAUSTION_INCREMENT); - float exhaustionUntilAboveMax = Math.nextUp(MAX_EXHAUSTION) - exhaustionLevel; - int numIterationsUntilAboveMax = Math.max(1, (int) Math.ceil(exhaustionUntilAboveMax / limitedSaturationLevel)); + public static BasicFoodValues getDefaultFoodValues(ItemStack itemStack) + { + FoodComponent itemFood = itemStack.getItem().getFoodComponent(); + int hunger = itemFood.getHunger(); + float saturationModifier = itemFood.getSaturationModifier(); + return new BasicFoodValues(hunger, saturationModifier); + } - health += (limitedSaturationLevel / REGEN_EXHAUSTION_INCREMENT) * numIterationsUntilAboveMax; - exhaustionLevel += limitedSaturationLevel * numIterationsUntilAboveMax; - } - else if (foodLevel >= 18) - { - // slow regen health - health += 1; - exhaustionLevel += REGEN_EXHAUSTION_INCREMENT; - } + public static BasicFoodValues getModifiedFoodValues(ItemStack itemStack, PlayerEntity player) + { + if (itemStack.getItem() instanceof DynamicFood) { + DynamicFood food = (DynamicFood)itemStack.getItem(); + int hunger = food.getDynamicHunger(itemStack, player); + float saturationModifier = food.getDynamicSaturation(itemStack, player); + return new BasicFoodValues(hunger, saturationModifier); } - - return health; + return getDefaultFoodValues(itemStack); } } diff --git a/java/squeek/appleskin/helpers/HungerHelper.java b/java/squeek/appleskin/helpers/HungerHelper.java index 8823747..d54e8ba 100644 --- a/java/squeek/appleskin/helpers/HungerHelper.java +++ b/java/squeek/appleskin/helpers/HungerHelper.java @@ -1,27 +1,14 @@ package squeek.appleskin.helpers; -import cpw.mods.modlauncher.api.INameMappingService; import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.util.FoodStats; -import net.minecraftforge.fml.common.ObfuscationReflectionHelper; - -import java.lang.reflect.Field; public class HungerHelper { - protected static final Field foodExhaustion; - - static + public interface ExhaustionManipulator { - try - { - foodExhaustion = FoodStats.class.getDeclaredField(ObfuscationReflectionHelper.remapName(INameMappingService.Domain.FIELD, "field_75126_c")); - foodExhaustion.setAccessible(true); - } - catch (NoSuchFieldException e) - { - throw new RuntimeException(e); - } + float getExhaustion(); + + void setExhaustion(float exhaustion); } public static float getMaxExhaustion(PlayerEntity player) @@ -31,25 +18,11 @@ public static float getMaxExhaustion(PlayerEntity player) public static float getExhaustion(PlayerEntity player) { - try - { - return foodExhaustion.getFloat(player.getFoodStats()); - } - catch (IllegalAccessException e) - { - throw new RuntimeException(e); - } + return ((ExhaustionManipulator) player.getHungerManager()).getExhaustion(); } public static void setExhaustion(PlayerEntity player, float exhaustion) { - try - { - foodExhaustion.setFloat(player.getFoodStats(), exhaustion); - } - catch (IllegalAccessException e) - { - throw new RuntimeException(e); - } + ((ExhaustionManipulator) player.getHungerManager()).setExhaustion(exhaustion); } } diff --git a/java/squeek/appleskin/helpers/KeyHelper.java b/java/squeek/appleskin/helpers/KeyHelper.java deleted file mode 100644 index 47a092e..0000000 --- a/java/squeek/appleskin/helpers/KeyHelper.java +++ /dev/null @@ -1,25 +0,0 @@ -package squeek.appleskin.helpers; - -import net.minecraft.client.Minecraft; -import net.minecraft.client.util.InputMappings; -import org.lwjgl.glfw.GLFW; - -public class KeyHelper -{ - public static boolean isCtrlKeyDown() - { - long handle = Minecraft.getInstance().getMainWindow().getHandle(); - // prioritize CONTROL, but allow OPTION as well on Mac (note: GuiScreen's isCtrlKeyDown only checks for the OPTION key on Mac) - boolean isCtrlKeyDown = InputMappings.isKeyDown(handle, GLFW.GLFW_KEY_LEFT_CONTROL) || InputMappings.isKeyDown(handle, GLFW.GLFW_KEY_RIGHT_CONTROL); - if (!isCtrlKeyDown && Minecraft.IS_RUNNING_ON_MAC) - isCtrlKeyDown = InputMappings.isKeyDown(handle, GLFW.GLFW_KEY_LEFT_SUPER) || InputMappings.isKeyDown(handle, GLFW.GLFW_KEY_RIGHT_SUPER); - - return isCtrlKeyDown; - } - - public static boolean isShiftKeyDown() - { - long handle = Minecraft.getInstance().getMainWindow().getHandle(); - return InputMappings.isKeyDown(handle, GLFW.GLFW_KEY_LEFT_SHIFT) || InputMappings.isKeyDown(handle, GLFW.GLFW_KEY_RIGHT_SHIFT); - } -} \ No newline at end of file diff --git a/java/squeek/appleskin/mixin/GuiMixin.java b/java/squeek/appleskin/mixin/GuiMixin.java new file mode 100644 index 0000000..cabb0c1 --- /dev/null +++ b/java/squeek/appleskin/mixin/GuiMixin.java @@ -0,0 +1,36 @@ +package squeek.appleskin.mixin; + +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.item.ItemStack; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.LocalCapture; +import squeek.appleskin.client.TooltipOverlayHandler; + +import java.util.List; + +@Mixin(Screen.class) +public class GuiMixin +{ + private static ItemStack cachedItemStack; + + @Inject(at = @At("HEAD"), method = "renderTooltip(Lnet/minecraft/item/ItemStack;II)V") + private void renderTooltipStart(ItemStack itemStack, int x, int y, CallbackInfo info) + { + cachedItemStack = itemStack; + } + + @Inject(at = @At("TAIL"), method = "renderTooltip(Lnet/minecraft/item/ItemStack;II)V") + private void renderTooltipEnd(ItemStack itemStack, int x, int y, CallbackInfo info) + { + cachedItemStack = null; + } + + @Inject(at = @At(value = "FIELD", target = "Lnet/minecraft/client/render/item/ItemRenderer;zOffset:F", ordinal = 0), locals = LocalCapture.CAPTURE_FAILHARD, method = "renderTooltip(Ljava/util/List;II)V") + private void renderTooltipCB(List tooltip, int mouseX, int mouseY, CallbackInfo info, int w, int x, int y, int w2, int h) + { + TooltipOverlayHandler.onRenderTooltip(cachedItemStack, x, y, w, h); + } +} diff --git a/java/squeek/appleskin/mixin/HungerManagerMixin.java b/java/squeek/appleskin/mixin/HungerManagerMixin.java new file mode 100644 index 0000000..213f693 --- /dev/null +++ b/java/squeek/appleskin/mixin/HungerManagerMixin.java @@ -0,0 +1,25 @@ +package squeek.appleskin.mixin; + +import net.minecraft.entity.player.HungerManager; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import squeek.appleskin.helpers.HungerHelper; + +@Mixin(HungerManager.class) +public class HungerManagerMixin implements HungerHelper.ExhaustionManipulator +{ + @Shadow + private float exhaustion; + + @Override + public void setExhaustion(float value) + { + this.exhaustion = value; + } + + @Override + public float getExhaustion() + { + return this.exhaustion; + } +} diff --git a/java/squeek/appleskin/mixin/InGameHudMixin.java b/java/squeek/appleskin/mixin/InGameHudMixin.java new file mode 100644 index 0000000..080abf2 --- /dev/null +++ b/java/squeek/appleskin/mixin/InGameHudMixin.java @@ -0,0 +1,25 @@ +package squeek.appleskin.mixin; + +import net.minecraft.client.gui.hud.InGameHud; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Slice; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import squeek.appleskin.client.HUDOverlayHandler; + +@Mixin(InGameHud.class) +public class InGameHudMixin +{ + @Inject(at = @At(value = "CONSTANT", args = "stringValue=food", shift = At.Shift.BY, by = 2), method = "renderStatusBars()V") + private void renderFoodPre(CallbackInfo info) + { + HUDOverlayHandler.onPreRender(); + } + + @Inject(slice = @Slice(from = @At(value = "CONSTANT", args = "stringValue=food")), at = @At(value = "APPLESKIN_IINC", args = "intValue=-10", ordinal = 0), method = "renderStatusBars()V") + private void renderFoodPost(CallbackInfo info) + { + HUDOverlayHandler.onRender(); + } +} diff --git a/java/squeek/appleskin/mixin/MinecraftClientMixin.java b/java/squeek/appleskin/mixin/MinecraftClientMixin.java new file mode 100644 index 0000000..fad89b8 --- /dev/null +++ b/java/squeek/appleskin/mixin/MinecraftClientMixin.java @@ -0,0 +1,18 @@ +package squeek.appleskin.mixin; + +import net.minecraft.client.MinecraftClient; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import squeek.appleskin.client.HUDOverlayHandler; + +@Mixin(MinecraftClient.class) +public class MinecraftClientMixin +{ + @Inject(at = @At("HEAD"), method = "tick") + void onTick(CallbackInfo info) + { + HUDOverlayHandler.onClientTick(); + } +} diff --git a/java/squeek/appleskin/mixin/PlayerManagerMixin.java b/java/squeek/appleskin/mixin/PlayerManagerMixin.java new file mode 100644 index 0000000..c94f220 --- /dev/null +++ b/java/squeek/appleskin/mixin/PlayerManagerMixin.java @@ -0,0 +1,20 @@ +package squeek.appleskin.mixin; + +import net.minecraft.network.ClientConnection; +import net.minecraft.server.PlayerManager; +import net.minecraft.server.network.ServerPlayerEntity; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import squeek.appleskin.network.SyncHandler; + +@Mixin(PlayerManager.class) +public class PlayerManagerMixin +{ + @Inject(at = @At("TAIL"), method = "onPlayerConnect") + private void onPlayerConnect(ClientConnection conn, ServerPlayerEntity player, CallbackInfo info) + { + SyncHandler.onPlayerLoggedIn(player); + } +} diff --git a/java/squeek/appleskin/mixin/ServerPlayerEntityMixin.java b/java/squeek/appleskin/mixin/ServerPlayerEntityMixin.java new file mode 100644 index 0000000..4ff1bbc --- /dev/null +++ b/java/squeek/appleskin/mixin/ServerPlayerEntityMixin.java @@ -0,0 +1,27 @@ +package squeek.appleskin.mixin; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityType; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.world.World; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import squeek.appleskin.network.SyncHandler; + +@Mixin(ServerPlayerEntity.class) +public abstract class ServerPlayerEntityMixin extends Entity +{ + public ServerPlayerEntityMixin(EntityType entityType, World world) + { + super(entityType, world); + } + + @Inject(at = @At("HEAD"), method = "tick") + void onUpdate(CallbackInfo info) + { + ServerPlayerEntity player = (ServerPlayerEntity) (Object) this; + SyncHandler.onPlayerUpdate(player); + } +} diff --git a/java/squeek/appleskin/mixin/util/BeforeInc.java b/java/squeek/appleskin/mixin/util/BeforeInc.java new file mode 100644 index 0000000..cdf8824 --- /dev/null +++ b/java/squeek/appleskin/mixin/util/BeforeInc.java @@ -0,0 +1,76 @@ +package squeek.appleskin.mixin.util; + +import com.google.common.primitives.Ints; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.IincInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.spongepowered.asm.mixin.injection.InjectionPoint; +import org.spongepowered.asm.mixin.injection.struct.InjectionPointData; +import org.spongepowered.asm.mixin.refmap.IMixinContext; +import org.spongepowered.asm.util.Annotations; + +import java.util.Collection; +import java.util.ListIterator; + +@InjectionPoint.AtCode("APPLESKIN_IINC") +public class BeforeInc extends InjectionPoint +{ + private final int ordinal; + private final Integer intValue; + + public BeforeInc(IMixinContext context, AnnotationNode node, String returnType) + { + super(Annotations.getValue(node, "slice", ""), Selector.DEFAULT, null); + + this.ordinal = Annotations.getValue(node, "ordinal", -1); + this.intValue = Annotations.getValue(node, "intValue", (Integer) null); + } + + public BeforeInc(InjectionPointData data) + { + super(data); + + this.ordinal = data.getOrdinal(); + this.intValue = Ints.tryParse(data.get("intValue", "")); + } + + @Override + public boolean find(String desc, InsnList insns, Collection nodes) + { + boolean found = false; + + ListIterator iter = insns.iterator(); + for (int ordinal = 0; iter.hasNext(); ) + { + AbstractInsnNode insn = iter.next(); + + boolean matchesInsn = this.matchesIncInsn(insn); + if (matchesInsn) + { + if (this.ordinal == -1 || this.ordinal == ordinal) + { + nodes.add(insn); + found = true; + } + ordinal++; + } + } + + return found; + } + + private boolean matchesIncInsn(AbstractInsnNode insn) + { + if (insn.getOpcode() != Opcodes.IINC) + { + return false; + } + + int incr = ((IincInsnNode) insn).incr; + return this.intValue.equals(incr); + } + +} + diff --git a/java/squeek/appleskin/network/MessageExhaustionSync.java b/java/squeek/appleskin/network/MessageExhaustionSync.java deleted file mode 100644 index 8662aa5..0000000 --- a/java/squeek/appleskin/network/MessageExhaustionSync.java +++ /dev/null @@ -1,35 +0,0 @@ -package squeek.appleskin.network; - -import net.minecraft.network.PacketBuffer; -import net.minecraftforge.fml.network.NetworkEvent; -import squeek.appleskin.helpers.HungerHelper; - -import java.util.function.Supplier; - -public class MessageExhaustionSync -{ - float exhaustionLevel; - - public MessageExhaustionSync(float exhaustionLevel) - { - this.exhaustionLevel = exhaustionLevel; - } - - public static void encode(MessageExhaustionSync pkt, PacketBuffer buf) - { - buf.writeFloat(pkt.exhaustionLevel); - } - - public static MessageExhaustionSync decode(PacketBuffer buf) - { - return new MessageExhaustionSync(buf.readFloat()); - } - - public static void handle(final MessageExhaustionSync message, Supplier ctx) - { - ctx.get().enqueueWork(() -> { - HungerHelper.setExhaustion(NetworkHelper.getSidedPlayer(ctx.get()), message.exhaustionLevel); - }); - ctx.get().setPacketHandled(true); - } -} \ No newline at end of file diff --git a/java/squeek/appleskin/network/MessageSaturationSync.java b/java/squeek/appleskin/network/MessageSaturationSync.java deleted file mode 100644 index bcea62f..0000000 --- a/java/squeek/appleskin/network/MessageSaturationSync.java +++ /dev/null @@ -1,34 +0,0 @@ -package squeek.appleskin.network; - -import net.minecraft.network.PacketBuffer; -import net.minecraftforge.fml.network.NetworkEvent; - -import java.util.function.Supplier; - -public class MessageSaturationSync -{ - float saturationLevel; - - public MessageSaturationSync(float saturationLevel) - { - this.saturationLevel = saturationLevel; - } - - public static void encode(MessageSaturationSync pkt, PacketBuffer buf) - { - buf.writeFloat(pkt.saturationLevel); - } - - public static MessageSaturationSync decode(PacketBuffer buf) - { - return new MessageSaturationSync(buf.readFloat()); - } - - public static void handle(final MessageSaturationSync message, Supplier ctx) - { - ctx.get().enqueueWork(() -> { - NetworkHelper.getSidedPlayer(ctx.get()).getFoodStats().setFoodSaturationLevel(message.saturationLevel); - }); - ctx.get().setPacketHandled(true); - } -} \ No newline at end of file diff --git a/java/squeek/appleskin/network/NetworkHelper.java b/java/squeek/appleskin/network/NetworkHelper.java deleted file mode 100644 index a636045..0000000 --- a/java/squeek/appleskin/network/NetworkHelper.java +++ /dev/null @@ -1,14 +0,0 @@ -package squeek.appleskin.network; - -import net.minecraft.client.Minecraft; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraftforge.fml.network.NetworkDirection; -import net.minecraftforge.fml.network.NetworkEvent; - -public class NetworkHelper -{ - public static PlayerEntity getSidedPlayer(NetworkEvent.Context ctx) - { - return ctx.getDirection() == NetworkDirection.PLAY_TO_SERVER ? ctx.getSender() : Minecraft.getInstance().player; - } -} \ No newline at end of file diff --git a/java/squeek/appleskin/network/SyncHandler.java b/java/squeek/appleskin/network/SyncHandler.java index 9e0e48a..d3f1d7f 100644 --- a/java/squeek/appleskin/network/SyncHandler.java +++ b/java/squeek/appleskin/network/SyncHandler.java @@ -1,15 +1,14 @@ package squeek.appleskin.network; -import net.minecraft.entity.player.ServerPlayerEntity; -import net.minecraft.util.ResourceLocation; -import net.minecraftforge.common.MinecraftForge; -import net.minecraftforge.event.entity.living.LivingEvent.LivingUpdateEvent; -import net.minecraftforge.event.entity.player.PlayerEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.network.NetworkDirection; -import net.minecraftforge.fml.network.NetworkRegistry; -import net.minecraftforge.fml.network.simple.SimpleChannel; -import squeek.appleskin.ModInfo; +import io.netty.buffer.Unpooled; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.network.ClientSidePacketRegistry; +import net.minecraft.client.MinecraftClient; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket; +import net.minecraft.server.network.ServerPlayerEntity; +import net.minecraft.util.Identifier; import squeek.appleskin.helpers.HungerHelper; import java.util.HashMap; @@ -18,62 +17,65 @@ public class SyncHandler { - private static final String PROTOCOL_VERSION = Integer.toString(1); - public static final SimpleChannel CHANNEL = NetworkRegistry.ChannelBuilder - .named(new ResourceLocation(ModInfo.MODID, "sync")) - .clientAcceptedVersions(s -> true) - .serverAcceptedVersions(s -> true) - .networkProtocolVersion(() -> PROTOCOL_VERSION) - .simpleChannel(); + private static final Identifier EXHAUSTION_SYNC = new Identifier("appleskin", "exhaustion_sync"); + private static final Identifier SATURATION_SYNC = new Identifier("appleskin", "saturation_sync"); + @Environment(EnvType.CLIENT) public static void init() { - CHANNEL.registerMessage(1, MessageExhaustionSync.class, MessageExhaustionSync::encode, MessageExhaustionSync::decode, MessageExhaustionSync::handle); - CHANNEL.registerMessage(2, MessageSaturationSync.class, MessageSaturationSync::encode, MessageSaturationSync::decode, MessageSaturationSync::handle); + ClientSidePacketRegistry.INSTANCE.register(EXHAUSTION_SYNC, (packetContext, packetByteBuf) -> + { + float exhaustion = packetByteBuf.readFloat(); + MinecraftClient.getInstance().execute(() -> { + HungerHelper.setExhaustion(packetContext.getPlayer(), exhaustion); + }); + }); + ClientSidePacketRegistry.INSTANCE.register(SATURATION_SYNC, (packetContext, packetByteBuf) -> + { + float saturation = packetByteBuf.readFloat(); + MinecraftClient.getInstance().execute(() -> { + packetContext.getPlayer().getHungerManager().setSaturationLevelClient(saturation); + }); + }); + } - MinecraftForge.EVENT_BUS.register(new SyncHandler()); + private static CustomPayloadS2CPacket makeSyncPacket(Identifier identifier, float val) + { + PacketByteBuf buf = new PacketByteBuf(Unpooled.buffer()); + buf.writeFloat(val); + return new CustomPayloadS2CPacket(identifier, buf); } /* * Sync saturation (vanilla MC only syncs when it hits 0) * Sync exhaustion (vanilla MC does not sync it at all) */ - private static final Map lastSaturationLevels = new HashMap<>(); - private static final Map lastExhaustionLevels = new HashMap<>(); + private static final Map lastSaturationLevels = new HashMap(); + private static final Map lastExhaustionLevels = new HashMap(); - @SubscribeEvent - public void onLivingUpdateEvent(LivingUpdateEvent event) + public static void onPlayerUpdate(ServerPlayerEntity player) { - if (!(event.getEntity() instanceof ServerPlayerEntity)) - return; - - ServerPlayerEntity player = (ServerPlayerEntity) event.getEntity(); - Float lastSaturationLevel = lastSaturationLevels.get(player.getUniqueID()); - Float lastExhaustionLevel = lastExhaustionLevels.get(player.getUniqueID()); + Float lastSaturationLevel = lastSaturationLevels.get(player.getUuid()); + Float lastExhaustionLevel = lastExhaustionLevels.get(player.getUuid()); - if (lastSaturationLevel == null || lastSaturationLevel != player.getFoodStats().getSaturationLevel()) + float saturation = player.getHungerManager().getSaturationLevel(); + if (lastSaturationLevel == null || lastSaturationLevel != saturation) { - Object msg = new MessageSaturationSync(player.getFoodStats().getSaturationLevel()); - CHANNEL.sendTo(msg, player.connection.netManager, NetworkDirection.PLAY_TO_CLIENT); - lastSaturationLevels.put(player.getUniqueID(), player.getFoodStats().getSaturationLevel()); + player.networkHandler.sendPacket(makeSyncPacket(SATURATION_SYNC, saturation)); + lastSaturationLevels.put(player.getUuid(), saturation); } float exhaustionLevel = HungerHelper.getExhaustion(player); if (lastExhaustionLevel == null || Math.abs(lastExhaustionLevel - exhaustionLevel) >= 0.01f) { - Object msg = new MessageExhaustionSync(exhaustionLevel); - CHANNEL.sendTo(msg, player.connection.netManager, NetworkDirection.PLAY_TO_CLIENT); - lastExhaustionLevels.put(player.getUniqueID(), exhaustionLevel); + player.networkHandler.sendPacket(makeSyncPacket(EXHAUSTION_SYNC, exhaustionLevel)); + lastExhaustionLevels.put(player.getUuid(), exhaustionLevel); } } - @SubscribeEvent - public void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) + public static void onPlayerLoggedIn(ServerPlayerEntity player) { - if (!(event.getPlayer() instanceof ServerPlayerEntity)) - return; - - lastSaturationLevels.remove(event.getPlayer().getUniqueID()); - lastExhaustionLevels.remove(event.getPlayer().getUniqueID()); + lastSaturationLevels.remove(player.getUuid()); + lastExhaustionLevels.remove(player.getUuid()); } } diff --git a/java/squeek/appleskin/util/IntPoint.java b/java/squeek/appleskin/util/IntPoint.java deleted file mode 100644 index efc4691..0000000 --- a/java/squeek/appleskin/util/IntPoint.java +++ /dev/null @@ -1,7 +0,0 @@ -package squeek.appleskin.util; - -public class IntPoint -{ - public int x; - public int y; -} diff --git a/resources/META-INF/mods.toml b/resources/META-INF/mods.toml deleted file mode 100644 index 3f8c54e..0000000 --- a/resources/META-INF/mods.toml +++ /dev/null @@ -1,21 +0,0 @@ -modLoader="javafml" -loaderVersion="[33,)" -issueTrackerURL="https://github.com/squeek502/AppleSkin" -license="The Unlicense" - -[[mods]] -modId="appleskin" -version="${file.jarVersion}" -displayName="AppleSkin" -#updateJSONURL="" -displayURL="https://github.com/squeek502/AppleSkin" -#logoFile="appleskin.png" -authors="squeek" -description="Adds various food-related HUD improvements" - -[[dependencies.appleskin]] - modId="forge" - mandatory=true - versionRange="[33,)" - ordering="AFTER" - side="CLIENT" \ No newline at end of file diff --git a/resources/appleskin.mixins.json b/resources/appleskin.mixins.json new file mode 100644 index 0000000..c4d3886 --- /dev/null +++ b/resources/appleskin.mixins.json @@ -0,0 +1,22 @@ +{ + "required": true, + "package": "squeek.appleskin.mixin", + "compatibilityLevel": "JAVA_8", + "mixins": [ + "ServerPlayerEntityMixin", + "PlayerManagerMixin", + "HungerManagerMixin" + ], + "client": [ + "GuiMixin", + "InGameHudMixin", + "MinecraftClientMixin" + ], + "injectors": { + "defaultRequire": 1, + "maxShiftBy": 2, + "injectionPoints": [ + "squeek.appleskin.mixin.util.BeforeInc" + ] + } +} \ No newline at end of file diff --git a/resources/assets/appleskin/appleskin.png b/resources/assets/appleskin/appleskin.png new file mode 100644 index 0000000000000000000000000000000000000000..5ff47b120b7be3d56cf3c60782fe3f02751a332e GIT binary patch literal 420 zcmV;V0bBlwP)>pevVc?p z6#xJL00DGTPE!Ct=GbNc00A6HL_t(o!_}1Cu7e;D1`n<-Sj3*b|I2n^p&v2zs#$F3 zM*NwH0m9#}u&&nxpkEV!{x4h?ScR;A82I58xHjPZF5Cp7@c}P5w+T70Z6pi{Ko*EJ zp}I06P-Q_A3KDh!;GBNe6xD%!01!6~M%~%3Ww=0.7.2", + "fabric": "*" + } +} diff --git a/resources/pack.mcmeta b/resources/pack.mcmeta index ede4a9b..1044793 100644 --- a/resources/pack.mcmeta +++ b/resources/pack.mcmeta @@ -1,6 +1,6 @@ { "pack": { - "description": "AppleSkin resources", - "pack_format": 6 + "pack_format": 1, + "description": "Resources used for AppleSkin" } } \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..683b201 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,10 @@ +pluginManagement { + repositories { + jcenter() + maven { + name = 'Fabric' + url = 'http://maven.fabricmc.net/' + } + gradlePluginPortal() + } +}