From aa1b254c5997d1d75d0d8cddef558575ba18cc7b Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Wed, 19 Dec 2018 19:39:10 -0500 Subject: [PATCH] Initial commit --- .gitignore | 16 ++ LICENSE | 121 +++++++++++++ README.md | 3 + build.gradle | 22 +++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 52818 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 169 ++++++++++++++++++ gradlew.bat | 84 +++++++++ settings.gradle | 10 ++ .../simplemultipart/SimpleMultipart.java | 67 +++++++ .../client/MissingMultipartBakedModel.java | 22 +++ .../client/MultipartBakedModel.java | 18 ++ .../client/MultipartContainerBakedModel.java | 70 ++++++++ .../client/MultipartModels.java | 36 ++++ .../client/RenderStateProvider.java | 14 ++ .../client/SimpleMultipartClient.java | 26 +++ .../container/MultipartContainerBlock.java | 71 ++++++++ .../MultipartContainerBlockEntity.java | 134 ++++++++++++++ .../MultipartContainerBlockState.java | 24 +++ .../MultipartContainerEventHandler.java | 42 +++++ .../simplemultipart/item/ItemMultipart.java | 79 ++++++++ .../mixin/client/MixinBakedModelManager.java | 28 +++ .../mixin/client/MixinBlockRenderManager.java | 49 +++++ .../mixin/client/MixinFuckYouBiomeColors.java | 21 +++ .../simplemultipart/multipart/Multipart.java | 91 ++++++++++ .../multipart/MultipartSlot.java | 34 ++++ .../multipart/MultipartState.java | 41 +++++ .../simplemultipart/util/MultipartHelper.java | 131 ++++++++++++++ .../util/MultipartHitResult.java | 32 ++++ .../blockstates/container.json | 5 + src/main/resources/fabric.mod.json | 19 ++ .../resources/simplemultipart.client.json | 13 ++ .../resources/simplemultipart.common.json | 10 ++ .../simplemultipart/test/GreenMultipart.java | 25 +++ .../simplemultipart/test/MultipartTest.java | 35 ++++ .../simplemultipart/test/RedMultipart.java | 24 +++ .../test/TestMultipartModel.java | 64 +++++++ .../loot_tables/multiparts/green.json | 14 ++ .../loot_tables/multiparts/red.json | 14 ++ 39 files changed, 1684 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle create mode 100644 src/main/java/net/shadowfacts/simplemultipart/SimpleMultipart.java create mode 100644 src/main/java/net/shadowfacts/simplemultipart/client/MissingMultipartBakedModel.java create mode 100644 src/main/java/net/shadowfacts/simplemultipart/client/MultipartBakedModel.java create mode 100644 src/main/java/net/shadowfacts/simplemultipart/client/MultipartContainerBakedModel.java create mode 100644 src/main/java/net/shadowfacts/simplemultipart/client/MultipartModels.java create mode 100644 src/main/java/net/shadowfacts/simplemultipart/client/RenderStateProvider.java create mode 100644 src/main/java/net/shadowfacts/simplemultipart/client/SimpleMultipartClient.java create mode 100644 src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerBlock.java create mode 100644 src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerBlockEntity.java create mode 100644 src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerBlockState.java create mode 100644 src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerEventHandler.java create mode 100644 src/main/java/net/shadowfacts/simplemultipart/item/ItemMultipart.java create mode 100644 src/main/java/net/shadowfacts/simplemultipart/mixin/client/MixinBakedModelManager.java create mode 100644 src/main/java/net/shadowfacts/simplemultipart/mixin/client/MixinBlockRenderManager.java create mode 100644 src/main/java/net/shadowfacts/simplemultipart/mixin/client/MixinFuckYouBiomeColors.java create mode 100644 src/main/java/net/shadowfacts/simplemultipart/multipart/Multipart.java create mode 100644 src/main/java/net/shadowfacts/simplemultipart/multipart/MultipartSlot.java create mode 100644 src/main/java/net/shadowfacts/simplemultipart/multipart/MultipartState.java create mode 100644 src/main/java/net/shadowfacts/simplemultipart/util/MultipartHelper.java create mode 100644 src/main/java/net/shadowfacts/simplemultipart/util/MultipartHitResult.java create mode 100644 src/main/resources/assets/simplemultipart/blockstates/container.json create mode 100644 src/main/resources/fabric.mod.json create mode 100644 src/main/resources/simplemultipart.client.json create mode 100644 src/main/resources/simplemultipart.common.json create mode 100644 src/test/java/net/shadowfacts/simplemultipart/test/GreenMultipart.java create mode 100644 src/test/java/net/shadowfacts/simplemultipart/test/MultipartTest.java create mode 100644 src/test/java/net/shadowfacts/simplemultipart/test/RedMultipart.java create mode 100644 src/test/java/net/shadowfacts/simplemultipart/test/TestMultipartModel.java create mode 100644 src/test/resources/data/multipart_test/loot_tables/multiparts/green.json create mode 100644 src/test/resources/data/multipart_test/loot_tables/multiparts/red.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6a5fea3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# gradle + +.gradle/ +build/ +out/ + +# idea + +.idea/ +*.iml +*.ipr +*.iws + +# fabric + +run/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/README.md b/README.md new file mode 100644 index 0000000..83fe303 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# SimpleMultipart + +SimpleMultipart is a WIP/experimental multipart API for use with Fabric and the Minecraft 1.14 snapshots. \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..5a81ee8 --- /dev/null +++ b/build.gradle @@ -0,0 +1,22 @@ +plugins { + id 'fabric-loom' version '0.1.0-SNAPSHOT' +} + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +archivesBaseName = "SimpleMultipart" +group = "net.shadowfacts.simplemultipart" +version = "0.1.0" + +minecraft { +} + +dependencies { + minecraft "com.mojang:minecraft:18w50a" + mappings "net.fabricmc:yarn:18w50a.9" + modCompile "net.fabricmc:fabric-loader:0.2.0.70" + + // Fabric API. This is technically optional, but you probably want it anyway. + modCompile "net.fabricmc:fabric:0.1.1.55" +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..deedc7fa5e6310eac3148a7dd0b1f069b07364cb GIT binary patch literal 52818 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≺xP6>xgOOiY)rttk{qsA7{Wbuujxr^65$uRcM}1X8x7pQ*3r*Qf5N?{*Ha zA4~X=r>^-(9p4tcRD+z@dBmFf@nu(6fu&=;YiVbOX``Jn3(0fN68#wz8ONEt{?`K~ zR!yCLBwqkh_1!=Mr{abCuX~-G+^czt z_G(%B<|>~Z8MyXT3Nl{!Rfkt8kJAS-a+vGL_hf~WP!}mrR0K2o`@P-?Giar#rQW#R zQDhcngt>;6sNsZRB-?uR3Lh(B^;jHkv8G3E^gZDttwF&i-g6AnE+tORHO-a!9b8y@ zYSTGPFsGJ>^)OmTza^S;+9CP<+ymMC#BX`;WB`~cE}}ToOb%c^#)w@2(0v~BD+8gtTvEM z?O>9|-ZsR`9As{Zd8?jxmSBh2?d>QPBF7L;DaB{1Xn`~Y$V$-779q6#8;f5jelieI z7)*fIp20U`#P1XTPaa-Robyz)+B@b^DE` zVyu-bF%K;84?rF<^-`H2(fsIfsZLd=fMD9Y*N52cT%)+QxG6{}#B$K3u$gPn`KBFT zVkkD+FiIELcK9G&aAlmdfy#d za2&6Y(p}p_rwFbbHskr2kkbLs%k+# znb+{0T@npHGEMUFLb2W%3l27O`CIwrB=J5)I7{VjlWmB;9+%HQMJxVx{a0WD?c)K! zBhnS{Mewg=?D+NcEv)r~jjU~Kz=6Tk@5bR#k?gu(7rCqCUKu z5PU_v2+)Y{k%G)(Smx`bl&5BN=N3#0Ju-PRA3H~@ec}qP)C}%&AG3L~rfeK^AV|wQ ztn%KT3^f2Q%{Pptxm-P5o?6fX#s#^pc*UR^Twe_wNQOV zPNhmwE^H;m+^|les8j`$pB8Y5fR?^k#<}aQ2;0XM7Il5&WWK?qCaf+@t$E{V@gzGD z8ifI*!9=~9#uC-W1lF*qj3ETgiIe2G+B`M8rg3s+HwJQS|4fyILe(-8kmPe>%;SSV zX)JPl-lo7QCp3S)Df0P3y!~+%nAx&;)UOmMg6FjY9mPJZ^6kJ!_i);U^LCt zrRnx&HYxpqe@ZVW5p95SgUEbTh_v?*c?zb(=gV+Bo}pgy7AGjBIFWYZM&WKGO9b1v zWF^*y!6yajFQWe6gbRBP@gkRj3dkUXE1Oz}_10qo%y6%8Q~t*Kl0r)GSb}fpb`(+WdMBrZ>MexBeCWrmb5l zrJIWAA_HoQGZfS(t9hy)KK;Yh#kF&UKC975zGhI5haWAP%u&Z9c3?sx^yQY$u8X>rt9nns zblH1*gMD__G-}y{K9VzaP2LpE9*P4*98brW284KB(Dg#~beHL3+^$kzS);z-|2juU z192vP^Q`^?n4{T$pQGiRY;5(+{*6r`HEKw_ixrgqkNMrfItA6c;55B)tF z`WxEU`|e42Q<22Tq*MH>;!57o`0W8mWJU-DeBCN3jOSyIBPk8d9?h-K+Mk)m6TpWN znWAK>_>KUZqGkvYcnrQG9fQ8#|WNOq9cxOxbi90%T_lrW3Qw5!k-; zF&8XpWV{siLYazg(9c0uCC@+N{J%q(qLjxu@j?oH#&=Q-0K`fYU>zvhf+D zqHl$o0XZSI%xk@<_GD?xOr*7?0Ue>v;w&%(ykBOiLKSkG9H~Bn{Mw{6sD|F)faYuh z7>gKwZ_=NZ-S3XozilsL<<=}FU!y!oQ=mZGv@gpuA+zGpu^hNEVn`7uCA>F-)Q5Lz z;_YgTQL|a1x#PLr3?b#d0lxu!ahWaX`hXZsrr}?woVxC&EUkICKLA?-^$BAwu`tY! zW*Ki`+EY){FhL|LrCnsr`O3Fg@zZg3jFS}GbM514hTfOnk>7ELQRSMaX(19DH~ir#nmMo7jaj83RcM=`#2}d`sSP$EsJv46tI8$F zN$+4+^KF8MDVpk2F-UfVfy{EHCI#){bd+tFDKxK^$8~bD<{5ssdqP0e ztnB{Tx1?ueg*?vG#|0zA&@zwKQV-EvWuuMi`B!DS3kXL@hk0w|&%*ClzdqZ-rUEm4 z(65dj?5{|Z0ah*rCS~NK2cxWzf9#i3_j2 z9zZVeHe6)xD5+xP)J&gKZkXJQ+OU5_Y*QkxH>V_V`!h=V1#>!6S_V=+SJ+maWxO6H z1$Ti~Pc?hC|2;K+l_23g`mfzexEA7?3$WW5g#4rZ@%L`^pJS!}ve`I%GxZwbL0SzW z=b1QYH>b8<22C|6V!0!Q!pk@0%0d%wGrO_KA)~?0P+fu6o*US{PPF>68yc}Gz;+@A zg(8vMNw<|=QxE97V;;~RJnj0Yh~H=S%T%}+2mo;n$(PHfO$lh5`cURaqDfN`0??dxjJhlA9Pb1@SHq?XK9koRi0)3Gtg?0&W07yA zh2mP)@UQJQk)tP7$bP3^s~G$qW->I7LYRRT9STY%jO`AC4K85wLLZ(cLQKku7)Giw zj$W@z(juv_6jGF-da>CJl|ri1c_CRfdTlVWxp;>NbLw@Cdb9fE?vWEF%k6qx7>?)Y&fIuNQBb*z<8;S(g$L?4fRt- zQnl+| zjGLUjXxIF%7T8sUM93qIS#J);PHsQ0iFquZsq0h0Vqsju5jOHtWhPEoL6r8VZ8!d% z6Llelkam_Vj_4|t+}9AH!Uf_1#)hHXO^kJt%=n4Xthdu_L>X|S6r=(&`|m}|iYZiV z9!(2G+kwLhUu3rMm^Y{JamI}%swO+s=E*8iEuPB3q#dAYjx@7fJ}4b@lJNFU|V9yw%Ll$u(Vl85r=?m~s}bJ%zgbwOV=GW*C;8_015q8Y~rKENR= za>W)A;@LByON7~l+BhQo)f3DykkmVU{SH{>hU!55#_R6(A^p=SpE6uz9$~-zM12*w zRpQcdM-vWIwCKT_63a18{pPOc5xeRFbaj;;$UN0hYKGf{7bm2;2x~(}19o=<`5rlN zJ!D-(_63@Tq@3ZGoOeCLa5|;C0So+)^_D;{Ga}X#dO%A)u%q+O4;r`m(R)G*l94|j zx!61)9#F`ddqw0N*g3~brj7B;!?*{5tR;R=hs-rxeZKQ+yXS{-=Zh0n(om?*5wadkBNym<_#k^|Jy0bq4Tz z@jdysSG5-wU^n%XobulQf5%n&TQ~h_j(S%8W>EmEwk4qCg1-Ph{13pVdo;jq&C!X^ z&ejm1WNW1JL#FvDmmft_%iCAmtn(8S4#NFDU$*hp!aYZ?3#{t;c$!r;Hwg7%FO)gGV*umENLcFF1Qr`pRH5O(7a zweU;WxIY)4ZMAj<8!*I<0J8wW-++L3wO1SFP#00hnZ z1N8SUAmpg0WB31B=uc7Wg5Diw0eUSZpaLoXh6KE;y_f;h=^s%48Wi8Lzh(N*74bA? z?cdPVUigxK#Qk2a|84qt8YA!r-s77;;{DR}|1DzR)7p3%f9?khq{1Ir{&~iE8g}Lf zoR-G_FNNPH;6E;hKj-h8MeS*znIC}d0KoqicIGL{w^ZMTo>=R*y!{0G{Zotb|KKnCG}BMr5q}VDX8sF;pJ%B*m*A;0*bjo9oZkrkUM2pG8TV;Po;q**AaXDG zjp(=T`cK2{>4EqUWZ&Z7kbmz?e?kBGc>HN0o*qR0pmHetC#wIkmOedy`vE&w{!g&q zCyakMjeA;vr&jtOOxQKQF+Kf$_^IyxM}eMNj(^ac)c!{E6YTc_{q_2Xx$mh7@dv(8 u!@t1)?*_%E_4U*$@`GpzU>NuxHj>v8pnz|nZ?R(Nfe-*fa?~x~{`G$$1)d`S literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..b9e1d2c --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Aug 22 17:36:22 EDT 2016 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-bin.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..9aa616c --- /dev/null +++ b/gradlew @@ -0,0 +1,169 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, 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 + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +# 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" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@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= + +@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/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() + } +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/SimpleMultipart.java b/src/main/java/net/shadowfacts/simplemultipart/SimpleMultipart.java new file mode 100644 index 0000000..c65e199 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/SimpleMultipart.java @@ -0,0 +1,67 @@ +package net.shadowfacts.simplemultipart; + +import net.fabricmc.api.ModInitializer; +import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.IdRegistry; +import net.minecraft.util.registry.Registry; +import net.minecraft.world.loot.context.LootContextType; +import net.minecraft.world.loot.context.LootContextTypes; +import net.minecraft.world.loot.context.Parameter; +import net.minecraft.world.loot.context.Parameters; +import net.shadowfacts.simplemultipart.container.MultipartContainerEventHandler; +import net.shadowfacts.simplemultipart.container.MultipartContainerBlock; +import net.shadowfacts.simplemultipart.container.MultipartContainerBlockEntity; +import net.shadowfacts.simplemultipart.multipart.Multipart; +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +import java.lang.reflect.Method; +import java.util.function.Consumer; + +/** + * @author shadowfacts + */ +public class SimpleMultipart implements ModInitializer { + + public static final String MODID = "simplemultipart"; + + public static final Registry MULTIPART = createMultipartRegistry(); + + public static final Parameter MULTIPART_STATE_PARAMETER = new Parameter<>(new Identifier(MODID, "multipart_state")); + public static final LootContextType MULTIPART_LOOT_CONTEXT = createMultipartLootContextType(); + + public static final MultipartContainerBlock containerBlock = new MultipartContainerBlock(); + public static final BlockEntityType containerBlockEntity = createBlockEntityType(); + + @Override + public void onInitialize() { + Registry.register(Registry.BLOCK, new Identifier(MODID, "container"), containerBlock); + + MultipartContainerEventHandler.register(); + } + + private static Registry createMultipartRegistry() { + IdRegistry registry = new IdRegistry<>(); + Registry.REGISTRIES.register(new Identifier(MODID, "multipart"), registry); + return registry; + } + + private static BlockEntityType createBlockEntityType() { + BlockEntityType.Builder builder = BlockEntityType.Builder.create(MultipartContainerBlockEntity::new); + return Registry.register(Registry.BLOCK_ENTITY, new Identifier(MODID, "container"), builder.method_11034(null)); + } + + private static LootContextType createMultipartLootContextType() { + try { + Method register = LootContextTypes.class.getDeclaredMethod("register", String.class, Consumer.class); + register.setAccessible(true); + Consumer initializer = builder -> { + builder.require(MULTIPART_STATE_PARAMETER).require(Parameters.POSITION); + }; + return (LootContextType)register.invoke(null, MODID + ":multipart", initializer); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/client/MissingMultipartBakedModel.java b/src/main/java/net/shadowfacts/simplemultipart/client/MissingMultipartBakedModel.java new file mode 100644 index 0000000..3ace4ec --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/client/MissingMultipartBakedModel.java @@ -0,0 +1,22 @@ +package net.shadowfacts.simplemultipart.client; + +import com.google.common.collect.ImmutableList; +import net.minecraft.client.render.model.BakedQuad; +import net.minecraft.util.math.Direction; +import net.shadowfacts.simplemultipart.multipart.MultipartSlot; +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +import java.util.List; +import java.util.Random; + +/** + * @author shadowfacts + */ +public class MissingMultipartBakedModel implements MultipartBakedModel { + + @Override + public List getQuads(MultipartState state, MultipartSlot slot, Direction side, Random random) { + return ImmutableList.of(); + } + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/client/MultipartBakedModel.java b/src/main/java/net/shadowfacts/simplemultipart/client/MultipartBakedModel.java new file mode 100644 index 0000000..f8d1b48 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/client/MultipartBakedModel.java @@ -0,0 +1,18 @@ +package net.shadowfacts.simplemultipart.client; + +import net.minecraft.client.render.model.BakedQuad; +import net.minecraft.util.math.Direction; +import net.shadowfacts.simplemultipart.multipart.MultipartSlot; +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +import java.util.List; +import java.util.Random; + +/** + * @author shadowfacts + */ +public interface MultipartBakedModel { + + List getQuads(MultipartState state, MultipartSlot slot, Direction side, Random random); + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/client/MultipartContainerBakedModel.java b/src/main/java/net/shadowfacts/simplemultipart/client/MultipartContainerBakedModel.java new file mode 100644 index 0000000..2a5a9e7 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/client/MultipartContainerBakedModel.java @@ -0,0 +1,70 @@ +package net.shadowfacts.simplemultipart.client; + +import net.minecraft.block.BlockState; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.BakedQuad; +import net.minecraft.client.render.model.json.ModelItemPropertyOverrideList; +import net.minecraft.client.render.model.json.ModelTransformations; +import net.minecraft.client.texture.Sprite; +import net.minecraft.util.math.Direction; +import net.shadowfacts.simplemultipart.container.MultipartContainerBlockState; +import net.shadowfacts.simplemultipart.multipart.MultipartSlot; +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.stream.Collectors; + +/** + * @author shadowfacts + */ +public class MultipartContainerBakedModel implements BakedModel { + + @Override + public List getQuads(BlockState state, Direction side, Random random) { + if (!(state instanceof MultipartContainerBlockState)) { + return null; + } + MultipartContainerBlockState containerState = (MultipartContainerBlockState)state; + Map parts = containerState.getParts(); + MultipartModels models = SimpleMultipartClient.getMultipartModels(); + return parts.entrySet().stream() + .flatMap(entry -> { + MultipartBakedModel partModel = models.getModel(entry.getValue()); + return partModel.getQuads(entry.getValue(), entry.getKey(), side, random).stream(); + }) + .collect(Collectors.toList()); + } + + @Override + public boolean useAmbientOcclusion() { + return true; + } + + @Override + public boolean hasDepthInGui() { + return false; + } + + @Override + public boolean isBuiltin() { + return false; + } + + @Override + public Sprite getSprite() { + return MinecraftClient.getInstance().getSpriteAtlas().getSprite("blocks/stone"); + } + + @Override + public ModelTransformations getTransformations() { + return ModelTransformations.ORIGIN; + } + + @Override + public ModelItemPropertyOverrideList getItemPropertyOverrides() { + return ModelItemPropertyOverrideList.ORIGIN; + } +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/client/MultipartModels.java b/src/main/java/net/shadowfacts/simplemultipart/client/MultipartModels.java new file mode 100644 index 0000000..be9a8e8 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/client/MultipartModels.java @@ -0,0 +1,36 @@ +package net.shadowfacts.simplemultipart.client; + +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +import java.util.IdentityHashMap; +import java.util.Map; + +/** + * @author shadowfacts + */ +public class MultipartModels { + + private final Map models = new IdentityHashMap<>(); +// private final BakedModelManager modelManager; + private final MultipartBakedModel missingModel = new MissingMultipartBakedModel(); + + public MultipartModels() { +// this.modelManager = modelManager; + } + + public MultipartBakedModel getMissingModel() { + return missingModel; + } + + public MultipartBakedModel getModel(MultipartState state) { + MultipartBakedModel model = models.get(state); + if (model == null) { + return getMissingModel(); + } + return model; + } + + public void register(MultipartState state, MultipartBakedModel model) { + models.put(state, model); + } +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/client/RenderStateProvider.java b/src/main/java/net/shadowfacts/simplemultipart/client/RenderStateProvider.java new file mode 100644 index 0000000..b963270 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/client/RenderStateProvider.java @@ -0,0 +1,14 @@ +package net.shadowfacts.simplemultipart.client; + +import net.minecraft.block.BlockState; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.ExtendedBlockView; + +/** + * @author shadowfacts + */ +public interface RenderStateProvider { + + BlockState getStateForRendering(BlockState state, BlockPos pos, ExtendedBlockView world); + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/client/SimpleMultipartClient.java b/src/main/java/net/shadowfacts/simplemultipart/client/SimpleMultipartClient.java new file mode 100644 index 0000000..bd5af62 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/client/SimpleMultipartClient.java @@ -0,0 +1,26 @@ +package net.shadowfacts.simplemultipart.client; + +import net.fabricmc.api.ClientModInitializer; +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +/** + * @author shadowfacts + */ +public class SimpleMultipartClient implements ClientModInitializer { + + private static MultipartModels multipartModels = new MultipartModels(); + + @Override + public void onInitializeClient() { +// multipartModels = new MultipartModels(MinecraftClient.getInstance().getBlockRenderManager().getModels().getModelManager()); + } + + public static MultipartModels getMultipartModels() { + return multipartModels; + } + + public static void registerModel(MultipartState state, MultipartBakedModel model) { + getMultipartModels().register(state, model); + } + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerBlock.java b/src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerBlock.java new file mode 100644 index 0000000..21dffec --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerBlock.java @@ -0,0 +1,71 @@ +package net.shadowfacts.simplemultipart.container; + +import net.fabricmc.fabric.block.FabricBlockSettings; +import net.minecraft.block.*; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; +import net.minecraft.world.BlockView; +import net.minecraft.world.ExtendedBlockView; +import net.minecraft.world.World; +import net.shadowfacts.simplemultipart.client.RenderStateProvider; +import net.shadowfacts.simplemultipart.multipart.MultipartSlot; +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +import java.util.Map; + +/** + * @author shadowfacts + */ +public class MultipartContainerBlock extends Block implements BlockEntityProvider, RenderStateProvider { + + public MultipartContainerBlock() { + super(FabricBlockSettings.of(Material.STONE).build()); + } + + @Override + public boolean activate(BlockState var1, World world, BlockPos pos, PlayerEntity player, Hand var5, Direction var6, float var7, float var8, float var9) { + if (player.isSneaking()) { + MultipartContainerBlockEntity container = (MultipartContainerBlockEntity)world.getBlockEntity(pos); + System.out.println(container.getParts()); + return true; + } else { + return false; + } + } + + @Override + public BlockState getStateForRendering(BlockState state, BlockPos pos, ExtendedBlockView world) { + MultipartContainerBlockEntity container = (MultipartContainerBlockEntity)world.getBlockEntity(pos); + Map parts = container.getParts(); + return new MultipartContainerBlockState(state, parts); + } + + @Override + public VoxelShape getBoundingShape(BlockState state, BlockView world, BlockPos pos) { + MultipartContainerBlockEntity container = (MultipartContainerBlockEntity)world.getBlockEntity(pos); + if (container == null) { + return VoxelShapes.empty(); + } + + VoxelShape shape = null; + for (Map.Entry e : container.getParts().entrySet()) { + VoxelShape partShape = e.getValue().getBoundingShape(e.getKey(), container); + shape = shape == null ? partShape : VoxelShapes.method_1084(shape, partShape); + } + return shape == null ? VoxelShapes.empty() : shape; + } + + @Override + public VoxelShape getRayTraceShape(BlockState var1, BlockView var2, BlockPos var3) { + return super.getRayTraceShape(var1, var2, var3); + } + + @Override + public MultipartContainerBlockEntity createBlockEntity(BlockView world) { + return new MultipartContainerBlockEntity(); + } +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerBlockEntity.java b/src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerBlockEntity.java new file mode 100644 index 0000000..e32accb --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerBlockEntity.java @@ -0,0 +1,134 @@ +package net.shadowfacts.simplemultipart.container; + +import com.google.common.collect.ImmutableMap; +import net.fabricmc.fabric.block.entity.ClientSerializable; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.block.entity.BlockEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.world.ServerWorld; +import net.shadowfacts.simplemultipart.SimpleMultipart; +import net.shadowfacts.simplemultipart.multipart.MultipartSlot; +import net.shadowfacts.simplemultipart.multipart.MultipartState; +import net.shadowfacts.simplemultipart.util.MultipartHelper; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author shadowfacts + */ +public class MultipartContainerBlockEntity extends BlockEntity implements ClientSerializable { + + private Map parts = new HashMap<>(); + + public MultipartContainerBlockEntity() { + super(SimpleMultipart.containerBlockEntity); + } + + public ImmutableMap getParts() { + return ImmutableMap.copyOf(parts); + } + + public boolean hasPartInSlot(MultipartSlot slot) { + return parts.containsKey(slot); + } + + public boolean canInsert(MultipartState partState, MultipartSlot slot) { + if (hasPartInSlot(slot)) { + return false; + } + + // TODO: check bounding box intersections + + return true; + } + + public void insert(MultipartState partState, MultipartSlot slot) { + parts.put(slot, partState); + markDirty(); + world.scheduleBlockRender(pos); + } + + public MultipartState get(MultipartSlot slot) { + return parts.get(slot); + } + + public void remove(MultipartSlot slot) { + parts.remove(slot); + + if (parts.isEmpty()) { + world.setBlockState(pos, Blocks.AIR.getDefaultState()); + } + } + + public boolean breakPart(MultipartSlot slot) { + MultipartState state = get(slot); + if (state == null) { + return false; + } + + if (world instanceof ServerWorld) { + List drops = MultipartHelper.getDroppedStacks(state, (ServerWorld)world, pos); + drops.forEach(stack -> Block.dropStack(world, pos, stack)); + // TODO: don't drop if player is creative + } + + remove(slot); + + world.markDirty(pos, this); + world.scheduleBlockRender(pos); + BlockState blockState = world.getBlockState(pos); + world.updateListeners(pos, blockState, blockState, 3); + + return true; + } + + private CompoundTag partsToTag(CompoundTag tag) { + parts.forEach((slot, state) -> { + if (state != null) { + CompoundTag partStateTag = MultipartHelper.serializeMultipartState(state); + tag.put(slot.name(), partStateTag); + } + }); + return tag; + } + + private void partsFromTag(CompoundTag tag) { + parts.clear(); + for (MultipartSlot slot : MultipartSlot.values()) { + if (!(tag.containsKey(slot.name(), 10))) { + continue; + } + CompoundTag partStateTag = tag.getCompound(slot.name()); + MultipartState state = MultipartHelper.deserializeBlockState(partStateTag); + parts.put(slot, state); + } + } + + @Override + public CompoundTag toTag(CompoundTag tag) { + partsToTag(tag); + return super.toTag(tag); + } + + @Override + public void fromTag(CompoundTag tag) { + super.fromTag(tag); + partsFromTag(tag); + } + + @Override + public CompoundTag toClientTag(CompoundTag tag) { + return partsToTag(tag); + } + + @Override + public void fromClientTag(CompoundTag tag) { + partsFromTag(tag); + world.scheduleBlockRender(pos); + } +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerBlockState.java b/src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerBlockState.java new file mode 100644 index 0000000..a8eca78 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerBlockState.java @@ -0,0 +1,24 @@ +package net.shadowfacts.simplemultipart.container; + +import net.minecraft.block.BlockState; +import net.shadowfacts.simplemultipart.multipart.MultipartSlot; +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +import java.util.Map; + +/** + * @author shadowfacts + */ +public class MultipartContainerBlockState extends BlockState { + + private Map parts; + + public MultipartContainerBlockState(BlockState delegate, Map parts) { + super(delegate.getBlock(), delegate.getEntries()); + this.parts = parts; + } + + public Map getParts() { + return parts; + } +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerEventHandler.java b/src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerEventHandler.java new file mode 100644 index 0000000..a653a9e --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/container/MultipartContainerEventHandler.java @@ -0,0 +1,42 @@ +package net.shadowfacts.simplemultipart.container; + +import net.fabricmc.fabric.events.PlayerInteractionEvent; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.ActionResult; +import net.minecraft.util.Hand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.world.World; +import net.shadowfacts.simplemultipart.util.MultipartHitResult; +import net.shadowfacts.simplemultipart.SimpleMultipart; +import net.shadowfacts.simplemultipart.util.MultipartHelper; + +/** + * @author shadowfacts + */ +public class MultipartContainerEventHandler { + + public static void register() { + PlayerInteractionEvent.ATTACK_BLOCK.register(MultipartContainerEventHandler::handleBlockAttack); + } + + private static ActionResult handleBlockAttack(PlayerEntity player, World world, Hand hand, BlockPos pos, Direction direction) { + if (world.isRemote || world.getBlockState(pos).getBlock() != SimpleMultipart.containerBlock) { + return ActionResult.PASS; + } + + MultipartContainerBlockEntity container = (MultipartContainerBlockEntity)world.getBlockEntity(pos); + if (container == null) { + return ActionResult.FAILURE; + } + + MultipartHitResult hit = MultipartHelper.rayTrace(container, world, pos, player); + if (hit == null) { + return ActionResult.FAILURE; + } + + boolean success = container.breakPart(hit.partSlot); + return success ? ActionResult.SUCCESS : ActionResult.FAILURE; + } + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/item/ItemMultipart.java b/src/main/java/net/shadowfacts/simplemultipart/item/ItemMultipart.java new file mode 100644 index 0000000..404656f --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/item/ItemMultipart.java @@ -0,0 +1,79 @@ +package net.shadowfacts.simplemultipart.item; + +import net.minecraft.block.BlockState; +import net.minecraft.item.Item; +import net.minecraft.item.ItemPlacementContext; +import net.minecraft.item.ItemUsageContext; +import net.minecraft.util.ActionResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.shadowfacts.simplemultipart.SimpleMultipart; +import net.shadowfacts.simplemultipart.container.MultipartContainerBlockEntity; +import net.shadowfacts.simplemultipart.multipart.Multipart; +import net.shadowfacts.simplemultipart.multipart.MultipartSlot; +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +/** + * @author shadowfacts + */ +public class ItemMultipart extends Item { + + protected Multipart part; + + public ItemMultipart(Multipart part) { + super(new Settings()); + this.part = part; + } + + @Override + public ActionResult useOnBlock(ItemUsageContext context) { + return place(new ItemPlacementContext(context)); + } + + protected ActionResult place(ItemPlacementContext context) { + MultipartContainerBlockEntity container = getOrCreateContainer(context.getWorld(), context.getPos()); + if (container == null) { + return ActionResult.FAILURE; + } + + MultipartSlot slot = getSlotForPlacement(container, context); + if (slot == null) { + return ActionResult.FAILURE; + } + + MultipartState partState = part.getStateForPlacement(slot, container); + if (!container.canInsert(partState, slot)) { + return ActionResult.FAILURE; + } + + container.insert(partState, slot); + + context.getItemStack().addAmount(-1); + + return ActionResult.SUCCESS; + } + + protected MultipartContainerBlockEntity getOrCreateContainer(World world, BlockPos pos) { + BlockState current = world.getBlockState(pos); + if (current.getBlock() == SimpleMultipart.containerBlock) { + return (MultipartContainerBlockEntity)world.getBlockEntity(pos); + } else if (current.isAir()) { + world.setBlockState(pos, SimpleMultipart.containerBlock.getDefaultState()); + return (MultipartContainerBlockEntity)world.getBlockEntity(pos); + } else { + return null; + } + } + + protected MultipartSlot getSlotForPlacement(MultipartContainerBlockEntity container, ItemPlacementContext context) { + MultipartSlot slot = MultipartSlot.fromClickedSide(context.getFacing()); + if (part.isValidSlot(slot) && !container.hasPartInSlot(slot)) { + return slot; + } + if (part.isValidSlot(MultipartSlot.CENTER) && !container.hasPartInSlot(MultipartSlot.CENTER)) { + return MultipartSlot.CENTER; + } + return null; + } + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/mixin/client/MixinBakedModelManager.java b/src/main/java/net/shadowfacts/simplemultipart/mixin/client/MixinBakedModelManager.java new file mode 100644 index 0000000..0524608 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/mixin/client/MixinBakedModelManager.java @@ -0,0 +1,28 @@ +package net.shadowfacts.simplemultipart.mixin.client; + +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.client.render.model.BakedModelManager; +import net.minecraft.client.render.model.ModelLoader; +import net.minecraft.client.util.ModelIdentifier; +import net.minecraft.util.Identifier; +import net.shadowfacts.simplemultipart.client.MultipartContainerBakedModel; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.util.Map; + +/** + * @author shadowfacts + */ +@Mixin(BakedModelManager.class) +public class MixinBakedModelManager { + + @Redirect(method = "onResourceReload", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/model/ModelLoader;getBakedModelMap()Ljava/util/Map;")) + public Map getBakedModelMap(ModelLoader loader) { + Map map = loader.getBakedModelMap(); + map.put(new ModelIdentifier("simplemultipart:container#"), new MultipartContainerBakedModel()); + return map; + } + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/mixin/client/MixinBlockRenderManager.java b/src/main/java/net/shadowfacts/simplemultipart/mixin/client/MixinBlockRenderManager.java new file mode 100644 index 0000000..2fa2ff4 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/mixin/client/MixinBlockRenderManager.java @@ -0,0 +1,49 @@ +package net.shadowfacts.simplemultipart.mixin.client; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.RenderTypeBlock; +import net.minecraft.client.render.VertexBuffer; +import net.minecraft.client.render.block.BlockRenderManager; +import net.minecraft.client.render.block.BlockRenderer; +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.ExtendedBlockView; +import net.shadowfacts.simplemultipart.client.RenderStateProvider; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.util.Random; + +/** + * @author shadowfacts + */ +@Mixin(BlockRenderManager.class) +public abstract class MixinBlockRenderManager { + + @Shadow + private BlockRenderer renderer; + @Shadow + private Random field_4169; + + @Shadow + public abstract BakedModel getModel(BlockState var1); + + @Inject(at = @At("HEAD"), method = "method_3355", cancellable = true) + public void method_3355(BlockState state, BlockPos pos, ExtendedBlockView world, VertexBuffer buffer, Random random, CallbackInfoReturnable info) { + Block block = state.getBlock(); + if (state.getRenderType() == RenderTypeBlock.MODEL && block instanceof RenderStateProvider) { + RenderStateProvider provider = (RenderStateProvider)block; + BlockState renderState = provider.getStateForRendering(state, pos, world); + + BakedModel model = getModel(state); + boolean result = renderer.tesselate(world, model, renderState, pos, buffer, true, random, state.getPosRandom(pos)); + info.setReturnValue(result); + info.cancel(); + } + } + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/mixin/client/MixinFuckYouBiomeColors.java b/src/main/java/net/shadowfacts/simplemultipart/mixin/client/MixinFuckYouBiomeColors.java new file mode 100644 index 0000000..1338427 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/mixin/client/MixinFuckYouBiomeColors.java @@ -0,0 +1,21 @@ +package net.shadowfacts.simplemultipart.mixin.client; + +import net.minecraft.client.render.block.BiomeColors; +import net.minecraft.client.render.block.GrassColorHandler; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.ExtendedBlockView; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; + +/** + * @author shadowfacts + */ +@Mixin(BiomeColors.class) +public class MixinFuckYouBiomeColors { + + @Overwrite + public static int grassColorAt(ExtendedBlockView world, BlockPos pos) { + return GrassColorHandler.getColor(0.5D, 1.0D); + } + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/multipart/Multipart.java b/src/main/java/net/shadowfacts/simplemultipart/multipart/Multipart.java new file mode 100644 index 0000000..68c70c8 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/multipart/Multipart.java @@ -0,0 +1,91 @@ +package net.shadowfacts.simplemultipart.multipart; + +import com.google.common.collect.ImmutableList; +import net.minecraft.item.ItemStack; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.state.StateFactory; +import net.minecraft.util.Identifier; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.world.loot.LootSupplier; +import net.minecraft.world.loot.LootTables; +import net.minecraft.world.loot.context.LootContext; +import net.shadowfacts.simplemultipart.SimpleMultipart; +import net.shadowfacts.simplemultipart.container.MultipartContainerBlockEntity; + +import java.util.List; + +/** + * @author shadowfacts + */ +public abstract class Multipart { + + private StateFactory stateFactory; + private MultipartState defaultState; + + private Identifier dropTableId; + + public Multipart() { + StateFactory.Builder builder = new StateFactory.Builder<>(this); + appendProperties(builder); + stateFactory = builder.build(MultipartState::new); + defaultState = stateFactory.getDefaultState(); + } + + protected void appendProperties(StateFactory.Builder builder) {} + + public MultipartState getDefaultState() { + return defaultState; + } + + public void setDefaultState(MultipartState defaultState) { + this.defaultState = defaultState; + } + + public StateFactory getStateFactory() { + return stateFactory; + } + + public boolean isValidSlot(MultipartSlot slot) { + return true; + } + + public MultipartState getStateForPlacement(MultipartSlot slot, MultipartContainerBlockEntity container) { + return getDefaultState(); + } + + /** + * Can be overridden, should only be called via {@link MultipartState#getStateForRendering} + */ + @Deprecated + public MultipartState getStateForRendering(MultipartState state, MultipartSlot slot, MultipartContainerBlockEntity container) { + return state; + } + + /** + * Can be overridden, should only be called via {@link MultipartState#getBoundingShape} + */ + @Deprecated + public abstract VoxelShape getBoundingShape(MultipartState state, MultipartSlot slot, MultipartContainerBlockEntity container); + + public Identifier getDropTableId() { + if (dropTableId == null) { + Identifier id = SimpleMultipart.MULTIPART.getId(this); + dropTableId = new Identifier(id.getNamespace(), "multiparts/" + id.getPath()); + } + return dropTableId; + } + + @Deprecated + public List getDroppedStacks(MultipartState state, LootContext.Builder builder) { + Identifier dropTableId = getDropTableId(); + if (dropTableId == LootTables.EMPTY) { + return ImmutableList.of(); + } else { + LootContext context = builder.put(SimpleMultipart.MULTIPART_STATE_PARAMETER, state).build(SimpleMultipart.MULTIPART_LOOT_CONTEXT); + ServerWorld world = context.getWorld(); + LootSupplier supplier = world.getServer().getLootManager().getSupplier(dropTableId); + return supplier.getDrops(context); + } + } + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/multipart/MultipartSlot.java b/src/main/java/net/shadowfacts/simplemultipart/multipart/MultipartSlot.java new file mode 100644 index 0000000..8130e9b --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/multipart/MultipartSlot.java @@ -0,0 +1,34 @@ +package net.shadowfacts.simplemultipart.multipart; + +import net.minecraft.util.math.Direction; + +/** + * @author shadowfacts + */ +public enum MultipartSlot { + TOP, + BOTTOM, + NORTH, + SOUTH, + EAST, + WEST, + CENTER; + + public static MultipartSlot fromClickedSide(Direction side) { + switch (side) { + case UP: + return BOTTOM; + case DOWN: + return TOP; + case NORTH: + return SOUTH; + case SOUTH: + return NORTH; + case EAST: + return WEST; + case WEST: + return EAST; + } + throw new RuntimeException("Unreachable: got direction outside of DUNSWE"); + } +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/multipart/MultipartState.java b/src/main/java/net/shadowfacts/simplemultipart/multipart/MultipartState.java new file mode 100644 index 0000000..b0c9354 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/multipart/MultipartState.java @@ -0,0 +1,41 @@ +package net.shadowfacts.simplemultipart.multipart; + +import com.google.common.collect.ImmutableMap; +import net.minecraft.item.ItemStack; +import net.minecraft.state.AbstractPropertyContainer; +import net.minecraft.state.property.Property; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.world.loot.context.LootContext; +import net.shadowfacts.simplemultipart.container.MultipartContainerBlockEntity; + +import java.util.List; + +/** + * @author shadowfacts + */ +public class MultipartState extends AbstractPropertyContainer { + + public MultipartState(Multipart part, ImmutableMap, Comparable> properties) { + super(part, properties); + } + + public Multipart getMultipart() { + return owner; + } + + public MultipartState getStateForRendering(MultipartSlot slot, MultipartContainerBlockEntity container) { + //noinspection deprecation + return owner.getStateForRendering(this, slot, container); + } + + public VoxelShape getBoundingShape(MultipartSlot slot, MultipartContainerBlockEntity container) { + //noinspection deprecation + return owner.getBoundingShape(this, slot, container); + } + + public List getDroppedStacks(LootContext.Builder builder) { + //noinspection deprecated + return owner.getDroppedStacks(this, builder); + } + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/util/MultipartHelper.java b/src/main/java/net/shadowfacts/simplemultipart/util/MultipartHelper.java new file mode 100644 index 0000000..46da156 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/util/MultipartHelper.java @@ -0,0 +1,131 @@ +package net.shadowfacts.simplemultipart.util; + +import com.google.common.collect.ImmutableMap; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.world.ServerWorld; +import net.minecraft.state.PropertyContainer; +import net.minecraft.state.StateFactory; +import net.minecraft.state.property.Property; +import net.minecraft.util.HitResult; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3d; +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.world.World; +import net.minecraft.world.loot.context.LootContext; +import net.minecraft.world.loot.context.Parameters; +import net.shadowfacts.simplemultipart.SimpleMultipart; +import net.shadowfacts.simplemultipart.container.MultipartContainerBlockEntity; +import net.shadowfacts.simplemultipart.multipart.Multipart; +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +import java.util.*; + +/** + * @author shadowfacts + */ +public class MultipartHelper { + + public static MultipartHitResult rayTrace(MultipartContainerBlockEntity container, World world, BlockPos pos, PlayerEntity player) { + // copied from BoatItem::use + float var6 = MathHelper.lerp(1.0F, player.prevPitch, player.pitch); + float var7 = MathHelper.lerp(1.0F, player.prevYaw, player.yaw); + double var8 = MathHelper.lerp(1.0D, player.prevX, player.x); + double var10 = MathHelper.lerp(1.0D, player.prevY, player.y) + (double)player.getEyeHeight(); + double var12 = MathHelper.lerp(1.0D, player.prevZ, player.z); + Vec3d start = new Vec3d(var8, var10, var12); + + float var15 = MathHelper.cos(-var7 * 0.017453292F - 3.1415927F); + float var16 = MathHelper.sin(-var7 * 0.017453292F - 3.1415927F); + float var17 = -MathHelper.cos(-var6 * 0.017453292F); + float var18 = MathHelper.sin(-var6 * 0.017453292F); + float var19 = var16 * var17; + float var21 = var15 * var17; + Vec3d end = start.add((double)var19 * 5.0D, (double)var18 * 5.0D, (double)var21 * 5.0D); + + return rayTrace(container, world, pos, start, end); + } + + public static MultipartHitResult rayTrace(MultipartContainerBlockEntity container, World world, BlockPos pos, Vec3d start, Vec3d end) { + return container.getParts().entrySet().stream() + .map(e -> { + VoxelShape shape = e.getValue().getBoundingShape(e.getKey(), container); + HitResult result = shape.rayTrace(start, end, pos); + return result == null ? null : new MultipartHitResult(result, e.getKey()); + }) + .filter(Objects::nonNull) + .min(Comparator.comparingDouble(hit -> hit.pos.subtract(start).lengthSquared())) + .orElse(null); + } + + public static List getDroppedStacks(MultipartState state, ServerWorld world, BlockPos pos) { + LootContext.Builder builder = new LootContext.Builder(world); + builder.setRandom(world.random); + builder.put(SimpleMultipart.MULTIPART_STATE_PARAMETER, state); + builder.put(Parameters.POSITION, pos); + return state.getDroppedStacks(builder); + } + + public static CompoundTag serializeMultipartState(MultipartState state) { + CompoundTag tag = new CompoundTag(); + tag.putString("Name", SimpleMultipart.MULTIPART.getId(state.getMultipart()).toString()); + + ImmutableMap, Comparable> propertyMap = state.getEntries(); + if (!propertyMap.isEmpty()) { + CompoundTag propertyTag = new CompoundTag(); + + for (Map.Entry, Comparable> e : propertyMap.entrySet()) { + Property property = e.getKey(); + String str = getValueAsString(state, property); + propertyTag.putString(property.getName(), str); + } + + tag.put("Properties", propertyTag); + } + + return tag; + } + + private static , T extends Comparable> String getValueAsString(C state, Property property) { + return property.getValueAsString(state.get(property)); + } + + public static MultipartState deserializeBlockState(CompoundTag tag) { + if (!tag.containsKey("Name", 8)) { + return null; + } else { + Multipart part = SimpleMultipart.MULTIPART.get(new Identifier(tag.getString("Name"))); + MultipartState state = part.getDefaultState(); + + if (tag.containsKey("Properties", 10)) { + CompoundTag propertyTag = tag.getCompound("Properties"); + + StateFactory stateFactory = part.getStateFactory(); + + for (String propertyName : propertyTag.getKeys()) { + Property property = stateFactory.getProperty(propertyName); + + if (property != null) { + String valueStr = propertyTag.getString(propertyName); + state = withProperty(state, property, valueStr); + } + } + } + + return state; + } + } + + private static , T extends Comparable> C withProperty(C state, Property property, String valueString) { + Optional value = property.getValue(valueString); + if (!value.isPresent()) { + // TODO: logging + return state; + } + return state.with(property, value.get()); + } + +} diff --git a/src/main/java/net/shadowfacts/simplemultipart/util/MultipartHitResult.java b/src/main/java/net/shadowfacts/simplemultipart/util/MultipartHitResult.java new file mode 100644 index 0000000..50ffe90 --- /dev/null +++ b/src/main/java/net/shadowfacts/simplemultipart/util/MultipartHitResult.java @@ -0,0 +1,32 @@ +package net.shadowfacts.simplemultipart.util; + +import net.minecraft.util.HitResult; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.Vec3d; +import net.shadowfacts.simplemultipart.multipart.MultipartSlot; + +/** + * @author shadowfacts + */ +public class MultipartHitResult extends HitResult { + + public MultipartSlot partSlot; + + public MultipartHitResult(Vec3d pos, Direction side, BlockPos blockPos, MultipartSlot partSlot) { + super(pos, side, blockPos); + this.partSlot = partSlot; + } + + public MultipartHitResult(HitResult result, MultipartSlot partSlot) { + this(result.pos, result.side, result.getBlockPos(), partSlot); + if (result.type != Type.BLOCK) { + throw new IllegalArgumentException("Can't create a MultipartHitResult from a non BLOCK-type HitResult"); + } + } + + @Override + public String toString() { + return "HitResult{type=" + type + ", blockpos=" + getBlockPos() + ", f=" + side + ", pos=" + pos + ", partSlot=" + partSlot + '}'; + } +} diff --git a/src/main/resources/assets/simplemultipart/blockstates/container.json b/src/main/resources/assets/simplemultipart/blockstates/container.json new file mode 100644 index 0000000..243ebfb --- /dev/null +++ b/src/main/resources/assets/simplemultipart/blockstates/container.json @@ -0,0 +1,5 @@ +{ + "variants": { + "": { "model": "block/air" } + } +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..565fda8 --- /dev/null +++ b/src/main/resources/fabric.mod.json @@ -0,0 +1,19 @@ +{ + "id": "simplemultipart", + "name": "Simple Multipart", + "description": "A simple multipart API for Fabric.", + "version": "0.1.0", + "side": "universal", + "initializers": [ + "net.shadowfacts.simplemultipart.SimpleMultipart", + "net.shadowfacts.simplemultipart.client.SimpleMultipartClient", + "net.shadowfacts.simplemultipart.test.MultipartTest" + ], + "requires": { + "fabric": "*" + }, + "mixins": { + "client": "simplemultipart.client.json", + "common": "simplemultipart.common.json" + } +} diff --git a/src/main/resources/simplemultipart.client.json b/src/main/resources/simplemultipart.client.json new file mode 100644 index 0000000..ff87340 --- /dev/null +++ b/src/main/resources/simplemultipart.client.json @@ -0,0 +1,13 @@ +{ + "required": true, + "package": "net.shadowfacts.simplemultipart.mixin.client", + "compatibilityLevel": "JAVA_8", + "mixins": [ + "MixinFuckYouBiomeColors", + "MixinBakedModelManager", + "MixinBlockRenderManager" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/src/main/resources/simplemultipart.common.json b/src/main/resources/simplemultipart.common.json new file mode 100644 index 0000000..89c2b25 --- /dev/null +++ b/src/main/resources/simplemultipart.common.json @@ -0,0 +1,10 @@ +{ + "required": true, + "package": "net.shadowfacts.simplemultipart.mixin", + "compatibilityLevel": "JAVA_8", + "mixins": [ + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/src/test/java/net/shadowfacts/simplemultipart/test/GreenMultipart.java b/src/test/java/net/shadowfacts/simplemultipart/test/GreenMultipart.java new file mode 100644 index 0000000..8e8a9fc --- /dev/null +++ b/src/test/java/net/shadowfacts/simplemultipart/test/GreenMultipart.java @@ -0,0 +1,25 @@ +package net.shadowfacts.simplemultipart.test; + +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; +import net.shadowfacts.simplemultipart.container.MultipartContainerBlockEntity; +import net.shadowfacts.simplemultipart.multipart.Multipart; +import net.shadowfacts.simplemultipart.multipart.MultipartSlot; +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +/** + * @author shadowfacts + */ +public class GreenMultipart extends Multipart { + + @Override + public boolean isValidSlot(MultipartSlot slot) { + return slot == MultipartSlot.NORTH; + } + + @Override + public VoxelShape getBoundingShape(MultipartState state, MultipartSlot slot, MultipartContainerBlockEntity container) { + return VoxelShapes.cube(0, 0, 0, 1, 1, 1/16f); + } + +} diff --git a/src/test/java/net/shadowfacts/simplemultipart/test/MultipartTest.java b/src/test/java/net/shadowfacts/simplemultipart/test/MultipartTest.java new file mode 100644 index 0000000..1df7959 --- /dev/null +++ b/src/test/java/net/shadowfacts/simplemultipart/test/MultipartTest.java @@ -0,0 +1,35 @@ +package net.shadowfacts.simplemultipart.test; + +import net.fabricmc.api.ModInitializer; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.Registry; +import net.shadowfacts.simplemultipart.SimpleMultipart; +import net.shadowfacts.simplemultipart.client.SimpleMultipartClient; +import net.shadowfacts.simplemultipart.item.ItemMultipart; + +/** + * @author shadowfacts + */ +public class MultipartTest implements ModInitializer { + + public static final String MODID = "multipart_test"; + + public static final RedMultipart red = new RedMultipart(); + public static final GreenMultipart green = new GreenMultipart(); + + public static final ItemMultipart redItem = new ItemMultipart(red); + public static final ItemMultipart greenItem = new ItemMultipart(green); + + @Override + public void onInitialize() { + Registry.register(SimpleMultipart.MULTIPART, new Identifier(MODID, "red"), red); + Registry.register(SimpleMultipart.MULTIPART, new Identifier(MODID, "green"), green); + + Registry.register(Registry.ITEM, new Identifier(MODID, "red"), redItem); + Registry.register(Registry.ITEM, new Identifier(MODID, "green"), greenItem); + + SimpleMultipartClient.registerModel(red.getDefaultState(), new TestMultipartModel(true)); + SimpleMultipartClient.registerModel(green.getDefaultState(), new TestMultipartModel(false)); + } + +} diff --git a/src/test/java/net/shadowfacts/simplemultipart/test/RedMultipart.java b/src/test/java/net/shadowfacts/simplemultipart/test/RedMultipart.java new file mode 100644 index 0000000..0aea56c --- /dev/null +++ b/src/test/java/net/shadowfacts/simplemultipart/test/RedMultipart.java @@ -0,0 +1,24 @@ +package net.shadowfacts.simplemultipart.test; + +import net.minecraft.util.shape.VoxelShape; +import net.minecraft.util.shape.VoxelShapes; +import net.shadowfacts.simplemultipart.container.MultipartContainerBlockEntity; +import net.shadowfacts.simplemultipart.multipart.Multipart; +import net.shadowfacts.simplemultipart.multipart.MultipartSlot; +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +/** + * @author shadowfacts + */ +public class RedMultipart extends Multipart { + + @Override + public boolean isValidSlot(MultipartSlot slot) { + return slot == MultipartSlot.BOTTOM; + } + + @Override + public VoxelShape getBoundingShape(MultipartState state, MultipartSlot slot, MultipartContainerBlockEntity container) { + return VoxelShapes.cube(0, 0, 0, 1, 1/16f, 1); + } +} diff --git a/src/test/java/net/shadowfacts/simplemultipart/test/TestMultipartModel.java b/src/test/java/net/shadowfacts/simplemultipart/test/TestMultipartModel.java new file mode 100644 index 0000000..e5ef09e --- /dev/null +++ b/src/test/java/net/shadowfacts/simplemultipart/test/TestMultipartModel.java @@ -0,0 +1,64 @@ +package net.shadowfacts.simplemultipart.test; + +import com.google.common.collect.ImmutableList; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.model.BakedQuad; +import net.minecraft.client.render.model.BakedQuadFactory; +import net.minecraft.client.render.model.ModelRotationContainer; +import net.minecraft.client.render.model.json.ModelElementFace; +import net.minecraft.client.render.model.json.ModelElementTexture; +import net.minecraft.client.texture.Sprite; +import net.minecraft.client.texture.SpriteAtlasTexture; +import net.minecraft.sortme.Vector3f; +import net.minecraft.util.math.Direction; +import net.shadowfacts.simplemultipart.client.MultipartBakedModel; +import net.shadowfacts.simplemultipart.multipart.MultipartSlot; +import net.shadowfacts.simplemultipart.multipart.MultipartState; + +import java.util.List; +import java.util.Random; + +/** + * @author shadowfacts + */ +public class TestMultipartModel implements MultipartBakedModel { + + private static final BakedQuadFactory QUAD_FACTORY = new BakedQuadFactory(); + + private boolean flag; + + public TestMultipartModel(boolean flag) { + this.flag = flag; + } + + @Override + public List getQuads(MultipartState state, MultipartSlot slot, Direction side, Random random) { + if (side == null) { + return ImmutableList.of(createQuad()); + } + return ImmutableList.of(); + } + + private BakedQuad createQuad() { + ModelElementTexture elementTexture = new ModelElementTexture(new float[]{0, 0, 16, 16}, 0); + ModelElementFace face; + Vector3f from; + Vector3f to; + Direction side; + if (flag) { + from = new Vector3f(0, 0, 0); + to = new Vector3f(16, 1, 16); + side = Direction.UP; + face = new ModelElementFace(null, 0, "block/iron_block", elementTexture); + } else { + from = new Vector3f(0, 0, 0); + to = new Vector3f(16, 16, 1); + side = Direction.SOUTH; + face = new ModelElementFace(null, 0, "block/gold_block", elementTexture); + } + Sprite sprite = MinecraftClient.getInstance().getSpriteAtlas().getSprite(SpriteAtlasTexture.BLOCK_ATLAS_TEX); + ModelRotationContainer rotationContainer = new ModelRotationContainer() {}; + return QUAD_FACTORY.bake(from, to, face, sprite, side, rotationContainer, null, false); + } + +} diff --git a/src/test/resources/data/multipart_test/loot_tables/multiparts/green.json b/src/test/resources/data/multipart_test/loot_tables/multiparts/green.json new file mode 100644 index 0000000..6e224bb --- /dev/null +++ b/src/test/resources/data/multipart_test/loot_tables/multiparts/green.json @@ -0,0 +1,14 @@ +{ + "type": "multipart:multipart", + "pools": [ + { + "rolls": 1, + "entries": [ + { + "type": "minecraft:item", + "name": "multipart_test:green" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/test/resources/data/multipart_test/loot_tables/multiparts/red.json b/src/test/resources/data/multipart_test/loot_tables/multiparts/red.json new file mode 100644 index 0000000..44a4d3c --- /dev/null +++ b/src/test/resources/data/multipart_test/loot_tables/multiparts/red.json @@ -0,0 +1,14 @@ +{ + "type": "multipart:multipart", + "pools": [ + { + "rolls": 1, + "entries": [ + { + "type": "minecraft:item", + "name": "multipart_test:red" + } + ] + } + ] +} \ No newline at end of file