From 533b1f1cf48cbed2789acd8877ae67576b7c3898 Mon Sep 17 00:00:00 2001 From: drewcassidy Date: Fri, 19 Jun 2020 21:44:43 -0700 Subject: [PATCH] Consolidate config parsing and parse layer value --- .../Plugins/ConformalDecals.dll | Bin 37888 -> 41472 bytes Source/ConformalDecals/ConformalDecals.csproj | 1 + Source/ConformalDecals/DecalConfig.cs | 22 +- .../MaterialColorProperty.cs | 7 +- .../MaterialFloatProperty.cs | 5 +- .../MaterialProperties/MaterialProperty.cs | 53 ----- .../MaterialPropertyCollection.cs | 5 +- .../MaterialTextureProperty.cs | 26 +-- Source/ConformalDecals/Util/ParseUtil.cs | 216 ++++++++++++++++++ 9 files changed, 255 insertions(+), 80 deletions(-) create mode 100644 Source/ConformalDecals/Util/ParseUtil.cs diff --git a/GameData/ConformalDecals/Plugins/ConformalDecals.dll b/GameData/ConformalDecals/Plugins/ConformalDecals.dll index 76e71bad3955ee7487a1894db8134a18ccd0481e..bcdc259b013fd2878083d474adb857341c2c112e 100644 GIT binary patch literal 41472 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

&iETtLya&ck7g|dA>D^YJQr+T@#B7 zcXbsCKfT-YHFxV&DBR(2z`0UxJqsb|2G^K?b9KE{x4TLTAy`~hJ6+mT{vG}&a0)%!5e0*?jH>2tTbdVgh2K?7*qD) z`5#s=*dMZp^rj_L5ON})*oboNcYt(qVfs@qf7ET)4Nt`>^ZQ_Ae*oZ( zcoHSp8>?LZm3+hn(=dlYn_XpW`Gayx=5Y(#|Iw;DjGJ379GK2|wn9pEwCI zPT==>u%pB~D_B2x)KdjKwG6383Jn`0-a4~!hW#LF^i(y@6B0i~ZmhpMP)}JPtEUR) zfuDh&_&Gqs3_BWpV`;A$)2tU+R`=DRGy|pBDP)>-q=L8ZRfVEHGP~OIk=Zq>z>mzX zH9wO01v08q5d^;k!K?6f+%Pwy$r#c3$v>VG9pNIviZZNUIihVW%IhXHt9)GX1uVW0 z?adO|mp~1(9+iB+1X^YN2e^u%C$54IM5|(mP6UXfb1meZSY9wjCnxAb4DFww&eqGK zl4&WU#wmjtTr;f5Jav!iLs(3C4C_B3+&J0RLU`gK&tL4>K-Ke|SCF<3E|478>ik^W zdE2KE2R2MuHvrUOA2o6@0+ZMF+Z=oCL8#FazlMs#*Psm&zX2xuDOEBe{z*t=e=B7u zp7lGHDMuLbI}F@W*hcFQ%tbra53gd=)fyFRID%xL1#G)CQmK7dK*&6vRQ6Dr><%)qURHT=Kg1PbeI9=jS6C)E)l7`9o1y!Vabq zhf%zW1}9#Tz)kNYuukjGkO|gTV9)XwU?FT;{))RufcGl!#viHEiAhCAe$G1?7txGP z_k6;jrLYBVh!%#~J5eDu%qq{QurkCihWI6DHk6vKUrZHV+RI#K8=a~xj3k0#Tx@NeVUxVfW)^aBUM`MF-*2T0_ zmCW+N)e_I=p3bH>*P~vysaN}Ew3YY>OdQYD2RR&6wb6ILtOe68uSgcYA1li~S`S@N z5-pk|g3j0)vV9TPQ`nbHM)ui`t9Xu7xHLe>*HG$n8^EU;KD9$V3-zqC*~^E#h!%4+Nr8BUCilY>*?%+ZXff?9(qdNW@{8WcB!N5%hN%_yfS2% zc&udj9OgDZkh5XIHYKEp#Am?K1N;(=S$C7puPm7d0x-;w<%-U`_ z(G$wcV40(M{AvW&WujJa^}K`Q7cpKmUR(|z7mpWC`&W)@f5X}gg>GxVFtN4KGCb%~ zoX%LLe0QlXF9E43JU_FeFUUNI;lopRwT`tQY?6nr<}jyW!jq&0`VfLUU zaUFQO^u#;(b~)AwKEhZjpP~hE9~@h)7CP!}Q(jFqPI5TcBURQ{jd*~<9;nC01644G zncxNWG088(gi7F~DgtRY>y(^cyj2Hu;s z-(8f-{;JdM4;Hba%#yd_@hWBv#B9gC)%M566`UzT}U%{|ZCs%=Vm}&h%ti;>Ro(1#e?J z{TPEch6S2=$;LJudeUz}KUD&HS+BJnG=FsXW%Klp1Om`Sf7TZAB7{pH!WF~Ca__-) z2p98VJI|5Si3{z>?+41g9{0((SjLMBdgVJY%=6$P`Zz*_KPK(*-&1bVj|6rH829)m zmzUF%<(%5!&y||=sKAd2yez;RL*NvFEyAzy{~hxF-pBApaDw!AFwLa5`7aKc^nD*| z-W#|qVp4n6WswN!TE;Z-TEi4QLJiT38Kip*qRT0bibm*cVoo`5i_XBH9;Ah(M0@Cm zaAAaAj&Z8DhVi9U88bqUL>S&9xlW*1MdjlE%FEyP}G z7x|xiSmv16{EbSsWEJXY(%VC9!$qiLgmzamUq=fDG4(Ml6Zk9mBuF<@GUWE(tUWH) zZiP33^!I}A5f7j5V+~g!=x?VEz=hgj;eS#55EIYbY;>b#Tcqwk##9=V_0zjW=aY0U za+!2HY8RmggBeViSD;=&x=_j*K z7Gkn&WofncKEy+ip7s2!%*1mZ_SHdfs_3fPt7=V(iR5>n4a(^S2BAU)r!b%Bb~wvu_iDD~7V z^3A7(6^nf5(4%EbG`uaLG5kUa!}kOk_6Yt+E#sT38U7{2Fagf`kyF7{no08`48IX< z)6S%us?PV#ptnbt0zMd8j``yjy-k}z|4Enn&|XoN{Bt$KSz(6nEMa(CjNxxRZGew? zW&-}mSPS_>Mh9S}m-!`O=KopvvxW0qEpw)NSkfb$JA%yl50Pmo;aqh3RP5B!tVu@C;y z$cL8FsKqxAxpoW3gikd37VOvP{wjt$*;nR$D1FmNm)7CiG;+Scg#v#`%vmq^R={;} z?n4J_a+p{(+A@Z^N~rhw&nvkH3xys<(MkP7C?%Jt)@nLkC)81_F>c3j`B#U!6!|># zff1Pl58B!D$C+L5+qP?U|WEi7j}GPN<_b`(np^Ui$eMYX2;e z^p{8GAm0l@Jxb^5_dzl|o6{bpc{LB9Ek+#b?pk<{{z0gNbQ0?@AAQZCek+v!OwKn- zAJ@O=^HGOGJ+7DGUC~~L`c<$DqsA8;>OGZZ7^`NT#rc#6Lv(JQdej%9ZbebD>M36d zJ)96pN=2VT@0~Y?sZsy6fxEpGloIMus>4#gflo2USDgaj2~R8{Z`Qt3y3g{i<&YZJDPeKaIuc zH1f|^)W?MCb*Oj2vT4-4P^I01Mane#qeIQ8Wa`U{R9aNhK6x%v2T@y}e;U;+7E0Q7 zIvrZVlxo}QbX2JK(T742|8)9ChuR8i27St*u8c+eGwGil>gCcZ|7?0vsH60RF~gsr zmmJD8=lJU=be@#wUs+CiJ(WAumhyT2dYa-;AB{HY4K&B0?v37MG|;&YwW_j7Z=_Z? zU*%m!BW-Y~UqqVpChBykUq|jTnyAmAZm4O}=h8lhdUwrT#$0-*LoEt6>GS9ohq^F$ zmobm-aH!o{lRlrmD%7ZdM7ztFPrq`~ZbaGwDm$NRH0pmZ(iYGnp$_{00ci_qi<9F@>OCAzcXY0tUz9fzu_THH|t{Yi!WJo^ErfsZ|I48|gC+^%1(+zm*;rY83mS5BsmAL(94J>m+u! z(@~)g`j4XDZl_xv>R7;wb^I3`sy6zv(M4Z&sK>(#F>lwbU@ZszFA25Fp~!cizl*XC zRpz@N)SV9Xsj}be-SkzVeyXj-DAi5#E@3S{)sjN}MW}<+fWFyH4f25CAn{nxO%FI! z-K2;7-Q;PL(y4CJm;5PO=TP4SwSzw3P)~vCp&vNZIg|d)zmxvzP&+0)?Z2A**xhiS zP9kj=B^+vS(zpG6^iGGG6D`9D@?Rb5U7jEME!ugR%D3J7Q-42AS>;j}XxEVKP~WJ1 z$)Bb7In-^zm;5>UyhFWQ_L6^qo^U80dv?>$9qQBJ25mQOSuHxHjrPzkMfpE#{@TBX z_{){-`4>y(V6VZ4zfAqHqzu&i9O~PlyS;nqc87Yjim6XK)JaMExLd|t3Zoj>>R4CO~_R^}gEcqyL><-e`g*r$N!%O?<`wsPXcxi}!m8bsT z-%lOuINwov*wg|C=oO(<9~-x?*ye` zMaS?jUWT7H7$(gE{v{vd57aU|Gcq2IAg@lJ$39M@^C6?taqMdh8o&<4Lk%U&|6!Q< ze@0mboxm#AEk$W=L5Vm~gA7(RMfgVXizD{pPDF!_X94j52%TgYmDV{0Dl?}xl z#&vqdTUKV!a7kGiR!#v|!~aV@*J!Vg?P06Dj#b4zJUNN&TqjX97M5YV>Mt!$EpSxb zRUCfX!!kEWOny}&VTH8ROA?3o2_6t{D^E`JUa#Etm;yVY60T)aum>uy&>Z>0E09i5PLy^%>vs5t{1pf zU{YWju#&D3d{E#h;3S*@v4*378oda32F=hIUn20hz*hv`Dl*Obm6$KXn86p)33{%4 z1>j{htHIe3+k}^&9s1S4_tx}No4ysR$o2Y%Lf6vM&~O7C2Yff+g>)<6$1!(= zMRQVroAC)s>MJWgO(&@#bU&r_$1A@_*XWbY=LP>6Y1&t{{{Z}n_8VHS9Sr>ud*LS| zhPGaNAQI4;_21Q&YqhX;D&QvU%xbltPnxL>>e=c)(pGJHNrSdkifdb;W=Z3^^Q+AP$4KV(tyz0W{|xkgpzHzdMLmg~+ulAo z$I5e5c203VGl*AHoTPg)Jw=-U9!V&7KaD?LfTTdIyvG>SN+-P^a9QL-#!-0q4kN04%yXA4za{I zR&zEuUx}UXIj;SCV5W9l)3Db+uBEDHYS)Pewi;XX%REaAPwCm7_1Y<|({l{g?u56w z7q8d1qTj98uZ&$w4}z2Otk+*I?eiR?!)DHt*1uP|54r9ryVj#=9B-rA7qCYe)xLtg z$*9(iJ;|unkG;sKR$9tUgajo%~EzCs*n+#iohe>|%Ff_tU*P}%$8+Y3tX@f_7srH>$2QsVQAz)=eJ2!q;^ z$bX`Ck@ELIa!I6Lzeb|vki^v?iMK(+^8Ma(SiCU`+~<9-;D>iATs4*!v$9sq1 zpL96yp*7ym3Z6FFycus=pEZel{qJh`c)v`%nqmJO_G*Z84Q+QXR@{f+!<)T_wEF5> z0oNI2^f)~m__+60v8q`QYj;bo`@F}5^EiE}@&WI?#+KSIdmpC@E4~HD+8#&izYKgw zENDK12$^pFfKCJ!0p~IEaq6jE2)HD&)_lx}M3d&zqK)l&ulcn83f6~D>n~tkcuadG zgg&CZP{lA8yu&;}&jdbU9;e66d(7`TzEVB+qg5AZwHOCJYaY}7z4Q*V7M}ksB>w}F z$LU$Xq~7Rz%;Y%X{LWLg0RC z7JN1E82SP8uM|8fc%R_c0QXZ);9ji~_3)aHPHC-^$u z3)qBjHCF*?Kq3+Yb4^98ohUBEA+e*tW#Zvb8{eC&FF@1j2e zW@%=y2~S7n0ltIQ2V3Y}(BFcymP%ij;9Y{>PB+t|B`tWjsM2>r`yed~ofNoKznwl9 zybAE;;4%G!oGS37K+Ryz41vu8lLAKt9us&%;7Ng+N8|-I3rq?e6?jbG34tdCYF?2S z*eoz9a8%$ifhPbrhfa9!qNhX8d)fYz;QS(V5}fWZnat7557Ka0Gg&4soEhN!Dm+6t zONG-6&bE?fa4LOC;FZ2nfyV@%5O`9c<`b&~j{4sP$z%S{0G<%|ygvr~dEuWF4h2|7 z3$V-#fz1Mw0!I};$f?Z&lLAKt9us&%;7Ng6Nc0G77MK*c1S9T;=?nA_RuKQBTgIsI zutAd0)sgMm4KSClsbelCiaH0g>;hMrP}$7&c}8f18##_$6I zKPj-Pb_qDkOxAO!=$RV582D4+R=`&TR`@Ojes9@Y!1I0WfPXDv`Co>(-0xR0{E6V- z6#TmtTfjLXkaOv%fq|MtaFVIx*#g(B|oJ&4^XG`QCp2J z07PHMT2#Y#7NWo}MnBYODWFcx@P>x-lS<$%cwdRXTVD-)xjfNW0jSd@G#UJrfI6N@ zOa;CXP^YVCI`AZ*j+2cUfL-X-I!+d51E%OKr0xLJsfXqO9zbu$o`)L29|6S3foCH+ zU5iz*j()rl@H*5?r|VHC9j6FO0B=A&8u6rar#7HHrF}>98ReMqmmBvP4;nu-erC+{ zG<#n4M7-0z3%pCco4vcd!`^@NR+;stXM|Vr#h>f3#>dtn|M`~ryRib2@+5YH<3ES7 zM;QOP{WQF*umj;ozX3cW#8v{ISr|9@{*ZJ3zH@)px&P3){|xs|yhYJRXKKgjQ(7NA zuf0OY_21z7DLselM|vNvH~JuRp>`(He`|b!4DT1H0@rL@=iyq3tJC|E_H|q@cz;7L zcweCw^LuhVr~d$Yo|CJ^G!QW!TwYuzF8M6qoyz1|ley$o^QmolW}q)+C%5;eKy}!I z>ymah)tc%}^(1r5T9wY`aBH!8EqmbtYER|XrT3x%ot`wfzyG3?bAUg5mmSO<#=1t2tG+(luHn+8Q ztfUpa$)1fHR?!aUzPfEq+v<(0X?61!xo@DA%iAtl*+HGl2KxF}C;L0wJE$j>>)e=0 z=eAHbkGZ_9dEVB%+_pS#8|`LCc6P$O+*H)1Zs{&7v(8TKNNyL4mf6;xY|3^xTvZ26 zCbw^=zHFCe_olat{=WY0v@+eZbCuPT?xN-0X?R`X?St7|s;>#H-HWzB17({oNo7(t z#MWk3S;=lvu3eqXBzsVpmCXsaxx1UJj5Kh|&Sa)1)lHr0Y;!J`?Ai%@ZDv!_PA43twCYhNd379tWc4cjVt3#?(O7AvzO z-6Lp*X|7LSvIn6w0BQx-EVnw9&7wVEk-CfK4crdYHITD*>`-`jsz0|=p?gwETVWg9 zI#={s$(*9B{$y7=H>ilUndUvoT`APCyDhWZ0=6vGlg>E!C8?a#k)%pIG>oA;bE=!E zR2~+bWF89?b7Oxu{G;#z!P(7<=5B)DIdvRHNz_9npd5vCBla-}D55jV-h`>{a7Jyd z>1@B1Td5sH*L#af}ueoq-DUi5yV2v@UezdvWT;kEhvmpa&J^&7!-jA@dWz2?1QPL#?=_0B%wSAzchr7a<%$w5L z^cY@qHk<0(-aFWl&J~O1**4;-FKO>8Xz57WXp|NBRp=DP!=i-cdvmEw7Gr8b)>X3t zHgAGEPMfI4zX4mu&Q1P-Ml|j!` zGza8(8l5?x&e^34QC}MKDAoqFGMQBfyp==vIuxQ;v7MpP$(7uz5cKtI%I(aGwOiC7 z)SJ;4cc*AWs?WlWi)_aXDT@+8c=!@zZDv_&2ZpZvEQ*+J-jPe$Xc>26mHb&ZzaX6( z(tRtiT*Tx=oh>p+@eJZ%j=c`0Mr&T+?cCukKsYL7QsQW7z4zFx^AW63J=dCMRw=1rnQ+c81a7aa}bf zbH%yp4N_W>&UCARysfXlH`Rxc&d~u=&^h77))X(F*qySD!l)yC3zPj$YPFMl1Yrv? z?e7#9Z%%jT6t+5<-IeMt(0e4Mb9r}9YBQ#O7?H~KK-O_|E3XVdFtoN`4Ggm>&&8sa z=Wb{5+2XA5sX05C>B>{=PIrx#XF`SS2DsrQ65LS3OR-R>YR`_AhLi?SfwD7cr@Gxt z&TN_|*_b@RwU>-}jrDiuktaRkm{8+Ucm|3rj6)b6PVq-z1&j(W!*2> zm>#^$vU&;2Ik+@u1LbNtGZrFRx^5Ba&S9LylB?5M9)P(enUzWHanT(Pi>2DIJrGTT zsJ>8$mJXivxH;YMPW2X85>ry&aXf?tFa0fh93KlSO#fq4u1uxvq}{c1u)t=ka(N+M z{}DSPmCQKR%80$I(;3yrWsGGio#k0ygfNbZ5Z^DsAY}Cp^krl?QW!Q3*x^_ividt) z)4MTC0v|UJxZ|J9H7wXZupM^Ek|r;k+TrZ0)EcO;&*J@Z5#`3J+hdB7pc%HLdBPLh zvE)$18idA%)Q%K-c2{co-Y#@zmcgi0Ae!wStdBD}mMw6xpvV5Fdo8xRVtpZHOr;9! z6&$R&0`00Ta8mj5m-lDWy|9C$$=RdgCHZ9oxg3~IJo18*a%yoUc)zm>l^Ql@`w<=+ z*fgQ{rEDv|x5hxEmMD!a*hSg>YMC_9&qMI4bmkgvMIP}Gq&s-GAb1PH!zSJXNucuf zD`ksr8B81+Ig3)VN(enpw!Dz<+-?nIy0hFiY0kgQ+MCDKpifqAClYv|Rt(OiM)5pd zEO*ex)LQ`^S0oI50MNOh$!tJ|t4@XB)q z4s&cmhi^^W`EwZ50zaY5Gql1~mjDYMQJgX3j$(C5CXP1ShA8qldogruYU1t40`)eF+FpE7doBk}RbAeR%GHG^6S;ZSvhvsG~rq%A% zNzB)!DD!ruXWTe~Q#HhQKiY41x+|5p$z5-_OZau(wCmTfN4p}KmNj!O{@okvZ=`P#KT03&s#CE5e zGs#};S+m%^pekAP-L=b>wWrkRlIdyQizLojG~eZFZxx=U$mN^9E zM2EYpLdJAg#p>h*JjMfLcJ`%0jFA*hyKJvGFxGD8X<7_-P`0B7^>9e699aONC3hU3 z71k7Lb?ByHVp1DLQr=~oow;D`jveqA+dh`zu2-CNE*h%=Y|$h{R7?~xcB-^7Bf^T_ zyu57gc$VhXcN#}t)FvxhY!om_kgaYo0x>-HJlbJkVo z(`C!To@3TGN|qbnUBhrdy3aL;&&mg7 z8;GMZYtUVhFUR73kW{p-?U&P9?kq0qEOT7aImcwh%dZ*0Dla!EQJ+b<6gv>h7i>R~ z)RyIy;aYonUq2|UGo5KdaHl`!S*X5DG2{8jwiZWFZvV~8BdiL?4HGtnd4?<}@|ke5 zk;mNvWdzG}-Qx0$e&@jr?IW7rj%S+9c;303HsA^9D&Q@6?_d?adDhN<<*Ou%zXOxR z)65jvM13vzMq&ofGc8bk;P)aY&d>$R;)!b<&q-5|U}?O;M`_?a;4)_bG;@1}E@+60 zrhag9klG740J#)wYr%Kw*3b&T4YV4Ttr9z11Z&6dN4~i=*bAb>?z|mcj{Rv^Mdxmm zy42B`c%#C#lxwQ$6Q_Q$V>i-Lcn3NzdE@wT-F47u!%pDUTsN%EfwNZzKy~9OFg3=- zZ&~m;ol|YekNE^x65MRwk5oTidOIY#VAW}4qn&7{0r;#RK1l*n^>|LFYP%4gh(WR! z|EN^qIoPlR|ERXJsO8px>$WFNE>2ks{cH)|!=yocxvn^^Lu%0j-(-+VGdmGOc)kOA zGcNW}JNVp^90NK2dqR*|2aZKogWCmOT71IwV97LEpU!H>`T?$CB=23spB>DL1VX+iHKay z5y$0sJMF64onwbNh1gjkwPe3=$>ZCb`w7Y)dB*_U+YN&`@R#AvP2LX3og@XJG%BbR z3<$j)g|kZTl7-O9LJPYy4fk-Ujy1*AdpbjA5Vf=;M-Kno4)A6+aR_lZvyJTjCZfyU zs$1U37RM=X*~P1+VN$T-bTxCdZG5YijSWlg4sW2fiwC)4pYc%0ZO9?41~~5E+z#B4 z*-LT2Lc6K~RHbs8t0Ayh16|nu-)>b5OM~NMM0G?eGXuQ8K`e}1 zUnn{Wl@kkggIJA=<6ZYRGNZHuqtOVCfhB|mzJhu%Xy57J6zQT-KKN%Nl!M=o0lxd=+%Wh{m7%nHJ(9u9L(lBm!fkR zy^BT_%)#6cJfpasa7_A@%}8;Zin|`qGKJAI4xEFIYoS6;4H9lyh4xS$S9~>U&mfxJ zD)n^3wosDNrn)X@G)O1fl-a%P#p9!PS{=zQ*oufKAbi+Ld_ zwa;OsZw1D))PQsysxDaAh_T+CN43ggzB{yNW26g%gf_icdSZ9iCST6)QqnNmdx+-a zex+awfKjLm*7I5dHJLj;VwL|hsGV8|Y)74W)^}#!kcuge9tyAwdylasq5#<0T=U`> znW&%FA?#=FxI7P|gKu69A7S^Kj~LhXLTPG6!d+d}ho_SasEOLnf&|wSQQW>g;gZSoQk%;O^IX^qq6R*}Pfv(RHPW#Ttc3e!azD{;5yuSPT!PXsDb z3vJK+NcHuCK5k)-TpH%}qPql8J(njRcUt0dI5v55ipwa%rSjTNO2_Bo+t7;65 z%gF1lakIQ2T0LI>c%hCoE*hF~nn#8e`b{ zUa^Kxc0(8f$n2E3Cwe*S);VDcUc#@D3}9MUYy~ zx=Tln368SUEnJK9%iP+t5hIqnNbZ2uO4sS`)aALpp~|tf-iY$B1Jr2wSbkwtQ!`Zq>f)}=xD_Vuba-Z?_2c){X-wP0}WHiD+_r*P0#E-uj71y=spboi+w=%X>i$G8> zr?Ag6HJ0>OMpIfOV#YNP5r1402_VD?<>ZxgPaJQZv;J}}PA~EK{QhWqYBU|&==bA| zeQgRC=EWy~JJLoQb*R)J5%3vPW7pun--{n$8l5jCT>}d#mYnJfs^ViKqp^|eW3yr- zhwwi-awLokZ_P(w8UFsk$S7_Gh=|989E0QDdHl;1o}d<+<%O=1LwKcLWsd;UW1}dX{U`}F@JDp~=%5(^sz=1q)gpG>XI92WH);{S ziSKl6N{zz{LZ7hNIjG!~C?GbPjFiF&vC(vFv@bS#4WJ)&Q1-AH$6){HH4FviD4&Uzr>^?#tg*0@rW~zO)0oqazQ=|Hq;u>i-e;Qk4@NE9`v&*XN?6qq?`uZK?}=Wtuh(?S{V_6+weQF~?vmDgQ>E<)TLpNdqDY z|GD-{8O%euk%%8H8!3y9JdG#}hq)DwpsFvzZ6V-Ci*T18R!wvi0bi~;IUpVz`H9L= zz>gq&m@lF7U2dLR;K)2lsG~t92UPnWX28j z1~|%K-=TKg463E$rjoAkNf(1N5?kUHv(qYop%bqGl6rvga5ms6(%*`W&cJAoOOLO#dw zf%bY$CUh$6InUKMvPJb}Kc@NmdyxLg&n zPFO<+i%2r!9o&aPJ|AE>jKPdU4|h`nLC1KuQqzr~rYlBR7-2BNis2WA9}F0!V&_%3 zbS3P26i|Ev+8nu9|OFp zQ{ZC_jl_M#?}tk(^+;?nn0mQ}xWzaKsVO0hu(8o^N?ewpS4^G8^s~?gV3#_q8_O7m5~ac;i`1Yi!H78c@X&%`7&5fnd0|(-~jYi#0&RjcmU%o z#Wu8EtcfpBf!ug(=SLh7~IOY*vOp$G^_Og zdrL6Rq6`d!(65K%2D;`WFbv}|e$CCpev6F;xEIDoOV~xapaG8`()g+A2rj=j!IP!H#{@p)N$_|I zF!BhaOCc3zWTPY>!SBZ+u<)72zoOQmS8c|NC2R0B)_obOV<&$}M}r(k8-A`Ezx|hg z6Cy6}9%b>Po*a+(8=zFuf;Z^5TYK?!13R@xei5`ZFYZdu)+nBTT{-@*$K%Z+9mlsx zvgz)@_=0&0<`K=&Xx98xa>?R_JC-yqT97)sanX`Rsm3MeEMC~SqdVEPq-)9i1@m_- z!cU>%86U}@L$t8^L2INeSlO>R90})w(H?^b{)U5jyLf$Iecjzqj|x$X|JnCzNGSIl^n0V zJ;F{=FL&rG7IZ}XAo&gBtk#EL%!4`Mk}>Za;4ji$_?AtQmwx}Rn-u)^?H6SD*Zm4! ziodRxoQKx`Bcs;M>l<6#^w%v;yw;eCsb*1L! z`3@Uz3C+c~GE&LxgtVsq?(NjRvU$PT_{mab!oPmQF@dZ3;%A@VQFH&Af3LsyRK*`q z755Xp3s~Hx#@-E1%YQIp*sZ<2^5(C6Co+|4;uovs^V)1E8SjpK*8g1y|LUy#jfau3 z_2Th_aK0g-RFuSA!n+hiTOP)DS_8VxdB1^0 zAM(6>N(HUba8kl&>-?D|x%gC=&rh;) zqQa-W{47R&KJEE2lwyp;i3*<+^Eu{3O%IFAJk(qLTZEG$_yB(|g8jj#nSJs!gP(X~ zhL+C-`0lrx#wX?(oII|Rh=?>?=H7 z<3hOPu}_uaMej8zOXl%DBd=={N>EQ*Tp!HGiS4}n#cfytdG;coL)p0V>1o08iDj~V z>O7K9f4d>g9fnUhH9Tj;w}V6j=j(?L*vmaQ=fXP-V*glP9KS9V$8$tm)=%e>Y4EkzH( zFPK4}dX~x0BMM$RUE37bR5j(oDr=h<(T53YLi&xh6T T+|QEV{(N)s|1a^s%LD%h@kJ$; diff --git a/Source/ConformalDecals/ConformalDecals.csproj b/Source/ConformalDecals/ConformalDecals.csproj index b6b940e..fd1d246 100644 --- a/Source/ConformalDecals/ConformalDecals.csproj +++ b/Source/ConformalDecals/ConformalDecals.csproj @@ -66,6 +66,7 @@ + diff --git a/Source/ConformalDecals/DecalConfig.cs b/Source/ConformalDecals/DecalConfig.cs index e446b94..b0b8e78 100644 --- a/Source/ConformalDecals/DecalConfig.cs +++ b/Source/ConformalDecals/DecalConfig.cs @@ -1,13 +1,15 @@ using System.Collections.Generic; +using ConformalDecals.Util; using UnityEngine; -using UnityEngine.Experimental.Rendering; namespace ConformalDecals { public static class DecalConfig { - private static Texture2D _blankNormal; + private static Texture2D _blankNormal; private static List _shaderBlacklist; - + private static int _decalLayer = 3; + public static Texture2D BlankNormal => _blankNormal; + public static int DecalLayer => _decalLayer; public static bool IsBlacklisted(Shader shader) { return IsBlacklisted(shader.name); @@ -22,6 +24,8 @@ namespace ConformalDecals { foreach (var shaderName in blacklist.GetValuesList("shader")) { _shaderBlacklist.Add(shaderName); } + + ParseUtil.ParseIntIndirect(ref _decalLayer, node, "decalLayer"); } } @@ -30,7 +34,7 @@ namespace ConformalDecals { var width = 2; var height = 2; var color = new Color32(255, 128, 128, 128); - var colors = new Color32[] { color, color, color, color }; + var colors = new[] {color, color, color, color}; var tex = new Texture2D(width, height, TextureFormat.RGBA32, false); for (var x = 0; x <= width; x++) { @@ -38,11 +42,13 @@ namespace ConformalDecals { tex.SetPixels32(colors); } } + tex.Apply(); return tex; } + // ReSharper disable once UnusedMember.Global public static void ModuleManagerPostLoad() { _shaderBlacklist = new List(); @@ -55,6 +61,14 @@ namespace ConformalDecals { } } + // setup physics for decals, ignore collision with everything + Physics.IgnoreLayerCollision(_decalLayer, 1, true); // default + Physics.IgnoreLayerCollision(_decalLayer, 17, true); // EVA + Physics.IgnoreLayerCollision(_decalLayer, 19, true); // PhysicalObjects + Physics.IgnoreLayerCollision(_decalLayer, 23, true); // AeroFXIgnore + Physics.IgnoreLayerCollision(_decalLayer, 26, true); // wheelCollidersIgnore + Physics.IgnoreLayerCollision(_decalLayer, 27, true); // wheelColliders + _blankNormal = MakeBlankNormal(); } } diff --git a/Source/ConformalDecals/MaterialProperties/MaterialColorProperty.cs b/Source/ConformalDecals/MaterialProperties/MaterialColorProperty.cs index 7a665e3..5b770e6 100644 --- a/Source/ConformalDecals/MaterialProperties/MaterialColorProperty.cs +++ b/Source/ConformalDecals/MaterialProperties/MaterialColorProperty.cs @@ -1,18 +1,19 @@ using System; +using ConformalDecals.Util; using UnityEngine; namespace ConformalDecals.MaterialProperties { public class MaterialColorProperty : MaterialProperty { - [SerializeField] public Color color; + [SerializeField] public Color32 color = new Color32(0, 0, 0, byte.MaxValue); public override void ParseNode(ConfigNode node) { base.ParseNode(node); - color = ParsePropertyColor(node, "color", true, color); + ParseUtil.ParseColor32Indirect(ref color, node, "color"); } public override void Modify(Material material) { - if (material == null) throw new ArgumentNullException("material cannot be null"); + if (material == null) throw new ArgumentNullException(nameof(material)); material.SetColor(_propertyID, color); } diff --git a/Source/ConformalDecals/MaterialProperties/MaterialFloatProperty.cs b/Source/ConformalDecals/MaterialProperties/MaterialFloatProperty.cs index db5c987..6745dfc 100644 --- a/Source/ConformalDecals/MaterialProperties/MaterialFloatProperty.cs +++ b/Source/ConformalDecals/MaterialProperties/MaterialFloatProperty.cs @@ -1,4 +1,5 @@ using System; +using ConformalDecals.Util; using UnityEngine; namespace ConformalDecals.MaterialProperties { @@ -8,11 +9,11 @@ namespace ConformalDecals.MaterialProperties { public override void ParseNode(ConfigNode node) { base.ParseNode(node); - value = ParsePropertyFloat(node, "value", true, value); + ParseUtil.ParseFloatIndirect(ref value, node, "value"); } public override void Modify(Material material) { - if (material == null) throw new ArgumentNullException("material cannot be null"); + if (material == null) throw new ArgumentNullException(nameof(material)); material.SetFloat(_propertyID, value); } diff --git a/Source/ConformalDecals/MaterialProperties/MaterialProperty.cs b/Source/ConformalDecals/MaterialProperties/MaterialProperty.cs index 6416d22..6d792ab 100644 --- a/Source/ConformalDecals/MaterialProperties/MaterialProperty.cs +++ b/Source/ConformalDecals/MaterialProperties/MaterialProperty.cs @@ -22,58 +22,5 @@ namespace ConformalDecals.MaterialProperties { } public abstract void Modify(Material material); - - private delegate bool TryParseDelegate(string valueString, out T value); - - protected bool ParsePropertyBool(ConfigNode node, string valueName, bool isOptional = false, bool defaultValue = false) { - return ParsePropertyValue(node, valueName, bool.TryParse, isOptional, defaultValue); - } - - protected float ParsePropertyFloat(ConfigNode node, string valueName, bool isOptional = false, float defaultValue = 0.0f) { - return ParsePropertyValue(node, valueName, float.TryParse, isOptional, defaultValue); - } - - protected int ParsePropertyInt(ConfigNode node, string valueName, bool isOptional = false, int defaultValue = 0) { - return ParsePropertyValue(node, valueName, int.TryParse, isOptional, defaultValue); - } - - protected Color ParsePropertyColor(ConfigNode node, string valueName, bool isOptional = false, Color defaultValue = default) { - return ParsePropertyValue(node, valueName, ParseExtensions.TryParseColor, isOptional, defaultValue); - } - - protected Rect ParsePropertyRect(ConfigNode node, string valueName, bool isOptional = false, Rect defaultValue = default) { - return ParsePropertyValue(node, valueName, ParseExtensions.TryParseRect, isOptional, defaultValue); - } - - protected Vector2 ParsePropertyVector2(ConfigNode node, string valueName, bool isOptional = false, Vector2 defaultValue = default) { - return ParsePropertyValue(node, valueName, ParseExtensions.TryParseVector2, isOptional, defaultValue); - } - - private T ParsePropertyValue(ConfigNode node, string valueName, TryParseDelegate tryParse, bool isOptional = false, T defaultValue = default) { - string valueString = node.GetValue(valueName); - - if (isOptional) { - if (string.IsNullOrEmpty(valueString)) return defaultValue; - } - else { - if (valueString == null) - throw new FormatException($"Missing {typeof(T)} value for {valueName} in property '{PropertyName}'"); - - if (valueString == string.Empty) - throw new FormatException($"Empty {typeof(T)} value for {valueName} in property '{PropertyName}'"); - } - - if (tryParse(valueString, out var value)) { - return value; - } - - if (isOptional) { - return defaultValue; - } - - else { - throw new FormatException($"Improperly formatted {typeof(T)} value for {valueName} in property '{PropertyName}' : '{valueString}"); - } - } } } \ No newline at end of file diff --git a/Source/ConformalDecals/MaterialProperties/MaterialPropertyCollection.cs b/Source/ConformalDecals/MaterialProperties/MaterialPropertyCollection.cs index ce464eb..b61912b 100644 --- a/Source/ConformalDecals/MaterialProperties/MaterialPropertyCollection.cs +++ b/Source/ConformalDecals/MaterialProperties/MaterialPropertyCollection.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Runtime.Serialization; +using ConformalDecals.Util; using UniLinq; using UnityEngine; using UnityEngine.Rendering; @@ -163,8 +164,8 @@ namespace ConformalDecals.MaterialProperties { } public T ParseProperty(ConfigNode node) where T : MaterialProperty { - var propertyName = node.GetValue("name"); - if (string.IsNullOrEmpty(propertyName)) throw new ArgumentException("node has no name"); + string propertyName = ""; + if (!ParseUtil.ParseStringIndirect(ref propertyName, node, "name")) throw new ArgumentException("node has no name"); var newProperty = AddOrGetProperty(propertyName); newProperty.ParseNode(node); diff --git a/Source/ConformalDecals/MaterialProperties/MaterialTextureProperty.cs b/Source/ConformalDecals/MaterialProperties/MaterialTextureProperty.cs index e1b229c..fbbeacf 100644 --- a/Source/ConformalDecals/MaterialProperties/MaterialTextureProperty.cs +++ b/Source/ConformalDecals/MaterialProperties/MaterialTextureProperty.cs @@ -1,4 +1,5 @@ using System; +using ConformalDecals.Util; using UnityEngine; namespace ConformalDecals.MaterialProperties { @@ -9,7 +10,7 @@ namespace ConformalDecals.MaterialProperties { [SerializeField] public bool autoTile; [SerializeField] private string _textureUrl; - [SerializeField] private Texture2D _texture; + [SerializeField] private Texture2D _texture = Texture2D.whiteTexture; [SerializeField] private bool _hasTile; [SerializeField] private Rect _tileRect; @@ -42,24 +43,17 @@ namespace ConformalDecals.MaterialProperties { public override void ParseNode(ConfigNode node) { base.ParseNode(node); - isNormal = ParsePropertyBool(node, "isNormalMap", true, (PropertyName == "_BumpMap") || (PropertyName == "_DecalBumpMap") || isNormal); - isMain = ParsePropertyBool(node, "isMain", true, isMain); - autoScale = ParsePropertyBool(node, "autoScale", true, autoScale); - autoTile = ParsePropertyBool(node, "autoTile", true, autoTile); + ParseUtil.ParseBoolIndirect(ref isMain, node, "isMain"); + ParseUtil.ParseBoolIndirect(ref isNormal, node, "isNormalMap"); + ParseUtil.ParseBoolIndirect(ref autoScale, node, "autoScale"); + ParseUtil.ParseBoolIndirect(ref autoTile, node, "autoTile"); - var textureUrl = node.GetValue("textureUrl"); - - if (string.IsNullOrEmpty(textureUrl)) { - if (string.IsNullOrEmpty(_textureUrl)) { - TextureUrl = ""; - } - } - else { - TextureUrl = node.GetValue("textureUrl"); + if (!autoTile) { + ParseUtil.ParseRectIndirect(ref _tileRect, node, "tile"); } - if (node.HasValue("tile") && !autoTile) { - SetTile(ParsePropertyRect(node, "tile", true, _tileRect)); + if (ParseUtil.ParseStringIndirect(ref _textureUrl, node, "textureUrl")) { + _texture = LoadTexture(_textureUrl, isNormal); } } diff --git a/Source/ConformalDecals/Util/ParseUtil.cs b/Source/ConformalDecals/Util/ParseUtil.cs new file mode 100644 index 0000000..4cbddd3 --- /dev/null +++ b/Source/ConformalDecals/Util/ParseUtil.cs @@ -0,0 +1,216 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using UnityEngine; + +namespace ConformalDecals.Util { + public static class ParseUtil { + private static readonly Dictionary NamedColors = new Dictionary(); + private static readonly char[] Separator = {',', ' ', '\t'}; + + public delegate bool TryParseDelegate(string valueString, out T value); + + static ParseUtil() { + // setup named colors + foreach (var propertyInfo in typeof(Color).GetProperties(BindingFlags.Static | BindingFlags.Public)) { + if (!propertyInfo.CanRead) continue; + if (propertyInfo.PropertyType != typeof(Color)) continue; + + NamedColors.Add(propertyInfo.Name, (Color) propertyInfo.GetValue(null, null)); + } + + foreach (var propertyInfo in typeof(XKCDColors).GetProperties(BindingFlags.Static | BindingFlags.Public)) { + if (!propertyInfo.CanRead) continue; + if (propertyInfo.PropertyType != typeof(Color)) continue; + + if (NamedColors.ContainsKey(propertyInfo.Name)) throw new Exception("duplicate key " + propertyInfo.Name); + + NamedColors.Add(propertyInfo.Name, (Color) propertyInfo.GetValue(null, null)); + } + } + + public static string ParseString(ConfigNode node, string valueName, bool isOptional = false, string defaultValue = "") { + if (!node.HasValue(valueName)) throw new FormatException($"Missing value for {valueName}"); + + return node.GetValue(valueName); + } + + public static bool ParseStringIndirect(ref string value, ConfigNode node, string valueName) { + if (node.HasValue(valueName)) { + value = node.GetValue(valueName); + return true; + } + + return false; + } + + public static bool ParseBool(ConfigNode node, string valueName, bool isOptional = false, bool defaultValue = false) { + return ParseValue(node, valueName, bool.TryParse, isOptional, defaultValue); + } + + public static bool ParseBoolIndirect(ref bool value, ConfigNode node, string valueName) { + return ParseValueIndirect(ref value, node, valueName, bool.TryParse); + } + + public static float ParseFloat(ConfigNode node, string valueName, bool isOptional = false, float defaultValue = 0.0f) { + return ParseValue(node, valueName, float.TryParse, isOptional, defaultValue); + } + + public static bool ParseFloatIndirect(ref float value, ConfigNode node, string valueName) { + return ParseValueIndirect(ref value, node, valueName, float.TryParse); + } + + public static int ParseInt(ConfigNode node, string valueName, bool isOptional = false, int defaultValue = 0) { + return ParseValue(node, valueName, int.TryParse, isOptional, defaultValue); + } + + public static bool ParseIntIndirect(ref int value, ConfigNode node, string valueName) { + return ParseValueIndirect(ref value, node, valueName, int.TryParse); + } + + public static Color32 ParseColor32(ConfigNode node, string valueName, bool isOptional = false, Color32 defaultValue = default) { + return ParseValue(node, valueName, TryParseColor32, isOptional, defaultValue); + } + + public static bool ParseColor32Indirect(ref Color32 value, ConfigNode node, string valueName) { + return ParseValueIndirect(ref value, node, valueName, TryParseColor32); + } + + public static Rect ParseRect(ConfigNode node, string valueName, bool isOptional = false, Rect defaultValue = default) { + return ParseValue(node, valueName, ParseExtensions.TryParseRect, isOptional, defaultValue); + } + + public static bool ParseRectIndirect(ref Rect value, ConfigNode node, string valueName) { + return ParseValueIndirect(ref value, node, valueName, ParseExtensions.TryParseRect); + } + + public static Vector2 ParseVector2(ConfigNode node, string valueName, bool isOptional = false, Vector2 defaultValue = default) { + return ParseValue(node, valueName, ParseExtensions.TryParseVector2, isOptional, defaultValue); + } + + public static bool ParseVector2Indirect(ref Vector2 value, ConfigNode node, string valueName) { + return ParseValueIndirect(ref value, node, valueName, ParseExtensions.TryParseVector2); + } + + public static Vector3 ParseVector3(ConfigNode node, string valueName, bool isOptional = false, Vector3 defaultValue = default) { + return ParseValue(node, valueName, ParseExtensions.TryParseVector3, isOptional, defaultValue); + } + + public static bool ParseVector3Indirect(ref Vector3 value, ConfigNode node, string valueName) { + return ParseValueIndirect(ref value, node, valueName, ParseExtensions.TryParseVector3); + } + + public static T ParseValue(ConfigNode node, string valueName, TryParseDelegate tryParse, bool isOptional = false, T defaultValue = default) { + string valueString = node.GetValue(valueName); + + if (isOptional) { + if (string.IsNullOrEmpty(valueString)) return defaultValue; + } + else { + if (valueString == null) + throw new FormatException($"Missing {typeof(T)} value for {valueName}"); + + if (valueString == string.Empty) + throw new FormatException($"Empty {typeof(T)} value for {valueName}"); + } + + if (tryParse(valueString, out var value)) { + return value; + } + + if (isOptional) { + return defaultValue; + } + + else { + throw new FormatException($"Improperly formatted {typeof(T)} value for {valueName} : '{valueString}"); + } + } + + public static bool ParseValueIndirect(ref T value, ConfigNode node, string valueName, TryParseDelegate tryParse) { + if (!node.HasValue(valueName)) return false; + + var valueString = node.GetValue(valueName); + if (tryParse(valueString, out var newValue)) { + value = newValue; + return true; + } + + throw new FormatException($"Improperly formatted {typeof(T)} value for {valueName} : '{valueString}"); + } + + public static bool TryParseColor32(string valueString, out Color32 value) { + value = new Color32(0, 0, 0, byte.MaxValue); + + // HTML-style hex color + if (valueString[0] == '#') { + var hexColorString = valueString.Substring(1); + + if (!int.TryParse(hexColorString, System.Globalization.NumberStyles.HexNumber, null, out var hexColor)) return false; + + switch (hexColorString.Length) { + case 8: // RRGGBBAA + value.a = (byte) (hexColor & 0xFF); + hexColor >>= 8; + goto case 6; + + case 6: // RRGGBB + value.b = (byte) (hexColor & 0xFF); + hexColor >>= 8; + value.g = (byte) (hexColor & 0xFF); + hexColor >>= 8; + value.r = (byte) (hexColor & 0xFF); + return true; + + case 4: // RGBA + value.a = (byte) ((hexColor & 0xF) << 4); + hexColor >>= 4; + goto case 3; + + case 3: // RGB + value.b = (byte) (hexColor & 0xF << 4); + hexColor >>= 4; + value.g = (byte) (hexColor & 0xF << 4); + hexColor >>= 4; + value.r = (byte) (hexColor & 0xF << 4); + return true; + + default: + return false; + } + } + + // named color + if (NamedColors.TryGetValue(valueString, out var namedColor)) { + value = namedColor; + return true; + } + + // float color + var split = valueString.Split(Separator, StringSplitOptions.RemoveEmptyEntries); + for (int i = 0; i < split.Length; i++) { + split[i] = split[i].Trim(); + } + + switch (split.Length) { + case 4: + if (!float.TryParse(split[4], out var alpha)) return false; + value.a = (byte) (alpha * 0xFF); + goto case 3; + + case 3: + if (!float.TryParse(split[0], out var red)) return false; + if (!float.TryParse(split[0], out var green)) return false; + if (!float.TryParse(split[0], out var blue)) return false; + + value.r = (byte) (red * 0xFF); + value.g = (byte) (green * 0xFF); + value.b = (byte) (blue * 0xFF); + return true; + + default: + return false; + } + } + } +} \ No newline at end of file